summaryrefslogtreecommitdiffhomepage
path: root/runsc/container
diff options
context:
space:
mode:
authorgVisor bot <gvisor-bot@google.com>2020-12-28 22:05:49 +0000
committergVisor bot <gvisor-bot@google.com>2020-12-28 22:05:49 +0000
commit5c21c7c3bd1552f4d5f87ef588fc213e2a2278ef (patch)
treeb62b3f2c71f46e145c15d7740262f7d59c91c87f /runsc/container
parentb0f23fb7e0cf908622bc6b8c90e2819de6de6ccb (diff)
parent3ff7324dfa7c096a50b628189d5c3f2d4d5ec2f6 (diff)
Merge release-20201208.0-89-g3ff7324df (automated)
Diffstat (limited to 'runsc/container')
-rw-r--r--runsc/container/container.go182
-rw-r--r--runsc/container/state_file.go236
2 files changed, 241 insertions, 177 deletions
diff --git a/runsc/container/container.go b/runsc/container/container.go
index 418a27beb..8b78660f7 100644
--- a/runsc/container/container.go
+++ b/runsc/container/container.go
@@ -128,125 +128,6 @@ type Container struct {
goferIsChild bool
}
-// loadSandbox loads all containers that belong to the sandbox with the given
-// ID.
-func loadSandbox(rootDir, id string) ([]*Container, error) {
- cids, err := List(rootDir)
- if err != nil {
- return nil, err
- }
-
- // Load the container metadata.
- var containers []*Container
- for _, cid := range cids {
- container, err := Load(rootDir, cid)
- if err != nil {
- // Container file may not exist if it raced with creation/deletion or
- // directory was left behind. Load provides a snapshot in time, so it's
- // fine to skip it.
- if os.IsNotExist(err) {
- continue
- }
- return nil, fmt.Errorf("loading container %q: %v", id, err)
- }
- if container.Sandbox.ID == id {
- containers = append(containers, container)
- }
- }
- return containers, nil
-}
-
-// Load loads a container with the given id from a metadata file. partialID may
-// be an abbreviation of the full container id, in which case Load loads the
-// container to which id unambiguously refers to. Returns ErrNotExist if
-// container doesn't exist.
-func Load(rootDir, partialID string) (*Container, error) {
- log.Debugf("Load container, rootDir: %q, partial cid: %s", rootDir, partialID)
- if err := validateID(partialID); err != nil {
- return nil, fmt.Errorf("invalid container id: %v", err)
- }
-
- id, err := findContainerID(rootDir, partialID)
- if err != nil {
- // Preserve error so that callers can distinguish 'not found' errors.
- return nil, err
- }
-
- state := StateFile{
- RootDir: rootDir,
- ID: id,
- }
- defer state.close()
-
- c := &Container{}
- if err := state.load(c); err != nil {
- if os.IsNotExist(err) {
- // Preserve error so that callers can distinguish 'not found' errors.
- return nil, err
- }
- return nil, fmt.Errorf("reading container metadata file %q: %v", state.statePath(), err)
- }
- return c, nil
-}
-
-// LoadAndCheck is similar to Load(), but also checks if the container is still
-// running to get an error earlier to the caller.
-func LoadAndCheck(rootDir, partialID string) (*Container, error) {
- c, err := Load(rootDir, partialID)
- if err != nil {
- // Preserve error so that callers can distinguish 'not found' errors.
- return nil, err
- }
-
- // If the status is "Running" or "Created", check that the sandbox/container
- // is still running, setting it to Stopped if not.
- //
- // This is inherently racy.
- switch c.Status {
- case Created:
- if !c.isSandboxRunning() {
- // Sandbox no longer exists, so this container definitely does not exist.
- c.changeStatus(Stopped)
- }
- case Running:
- if err := c.SignalContainer(syscall.Signal(0), false); err != nil {
- c.changeStatus(Stopped)
- }
- }
-
- return c, nil
-}
-
-func findContainerID(rootDir, partialID string) (string, error) {
- // Check whether the id fully specifies an existing container.
- stateFile := buildStatePath(rootDir, partialID)
- if _, err := os.Stat(stateFile); err == nil {
- return partialID, nil
- }
-
- // Now see whether id could be an abbreviation of exactly 1 of the
- // container ids. If id is ambiguous (it could match more than 1
- // container), it is an error.
- ids, err := List(rootDir)
- if err != nil {
- return "", err
- }
- rv := ""
- for _, id := range ids {
- if strings.HasPrefix(id, partialID) {
- if rv != "" {
- return "", fmt.Errorf("id %q is ambiguous and could refer to multiple containers: %q, %q", partialID, rv, id)
- }
- rv = id
- }
- }
- if rv == "" {
- return "", os.ErrNotExist
- }
- log.Debugf("abbreviated id %q resolves to full id %q", partialID, rv)
- return rv, nil
-}
-
// Args is used to configure a new container.
type Args struct {
// ID is the container unique identifier.
@@ -291,6 +172,15 @@ func New(conf *config.Config, args Args) (*Container, error) {
return nil, fmt.Errorf("creating container root directory %q: %v", conf.RootDir, err)
}
+ sandboxID := args.ID
+ if !isRoot(args.Spec) {
+ var ok bool
+ sandboxID, ok = specutils.SandboxID(args.Spec)
+ if !ok {
+ return nil, fmt.Errorf("no sandbox ID found when creating container")
+ }
+ }
+
c := &Container{
ID: args.ID,
Spec: args.Spec,
@@ -301,7 +191,10 @@ func New(conf *config.Config, args Args) (*Container, error) {
Owner: os.Getenv("USER"),
Saver: StateFile{
RootDir: conf.RootDir,
- ID: args.ID,
+ ID: FullID{
+ SandboxID: sandboxID,
+ ContainerID: args.ID,
+ },
},
}
// The Cleanup object cleans up partially created containers when an error
@@ -316,10 +209,17 @@ func New(conf *config.Config, args Args) (*Container, error) {
}
defer c.Saver.unlock()
- // If the metadata annotations indicate that this container should be
- // started in an existing sandbox, we must do so. The metadata will
- // indicate the ID of the sandbox, which is the same as the ID of the
- // init container in the sandbox.
+ // If the metadata annotations indicate that this container should be started
+ // in an existing sandbox, we must do so. These are the possible metadata
+ // annotation states:
+ // 1. No annotations: it means that there is a single container and this
+ // container is obviously the root. Both container and sandbox share the
+ // ID.
+ // 2. Container type == sandbox: it means this is the root container
+ // starting the sandbox. Both container and sandbox share the same ID.
+ // 3. Container type == container: it means this is a subcontainer of an
+ // already started sandbox. In this case, container ID is different than
+ // the sandbox ID.
if isRoot(args.Spec) {
log.Debugf("Creating new sandbox for container, cid: %s", args.ID)
@@ -358,7 +258,7 @@ func New(conf *config.Config, args Args) (*Container, error) {
// Start a new sandbox for this container. Any errors after this point
// must destroy the container.
sandArgs := &sandbox.Args{
- ID: args.ID,
+ ID: sandboxID,
Spec: args.Spec,
BundleDir: args.BundleDir,
ConsoleSocket: args.ConsoleSocket,
@@ -379,22 +279,14 @@ func New(conf *config.Config, args Args) (*Container, error) {
return nil, err
}
} else {
- // This is sort of confusing. For a sandbox with a root
- // container and a child container in it, runsc sees:
- // * A container struct whose sandbox ID is equal to the
- // container ID. This is the root container that is tied to
- // the creation of the sandbox.
- // * A container struct whose sandbox ID is equal to the above
- // container/sandbox ID, but that has a different container
- // ID. This is the child container.
- sbid, ok := specutils.SandboxID(args.Spec)
- if !ok {
- return nil, fmt.Errorf("no sandbox ID found when creating container")
- }
- log.Debugf("Creating new container, cid: %s, sandbox: %s", c.ID, sbid)
+ log.Debugf("Creating new container, cid: %s, sandbox: %s", c.ID, sandboxID)
// Find the sandbox associated with this ID.
- sb, err := LoadAndCheck(conf.RootDir, sbid)
+ fullID := FullID{
+ SandboxID: sandboxID,
+ ContainerID: sandboxID,
+ }
+ sb, err := Load(conf.RootDir, fullID, LoadOpts{Exact: true})
if err != nil {
return nil, err
}
@@ -628,7 +520,7 @@ func (c *Container) Wait() (syscall.WaitStatus, error) {
// returns its WaitStatus.
func (c *Container) WaitRootPID(pid int32) (syscall.WaitStatus, error) {
log.Debugf("Wait on process %d in sandbox, cid: %s", pid, c.Sandbox.ID)
- if !c.isSandboxRunning() {
+ if !c.IsSandboxRunning() {
return 0, fmt.Errorf("sandbox is not running")
}
return c.Sandbox.WaitPID(c.Sandbox.ID, pid)
@@ -638,7 +530,7 @@ func (c *Container) WaitRootPID(pid int32) (syscall.WaitStatus, error) {
// its WaitStatus.
func (c *Container) WaitPID(pid int32) (syscall.WaitStatus, error) {
log.Debugf("Wait on process %d in container, cid: %s", pid, c.ID)
- if !c.isSandboxRunning() {
+ if !c.IsSandboxRunning() {
return 0, fmt.Errorf("sandbox is not running")
}
return c.Sandbox.WaitPID(c.ID, pid)
@@ -658,7 +550,7 @@ func (c *Container) SignalContainer(sig syscall.Signal, all bool) error {
if err := c.requireStatus("signal", Running, Stopped); err != nil {
return err
}
- if !c.isSandboxRunning() {
+ if !c.IsSandboxRunning() {
return fmt.Errorf("sandbox is not running")
}
return c.Sandbox.SignalContainer(c.ID, sig, all)
@@ -670,7 +562,7 @@ func (c *Container) SignalProcess(sig syscall.Signal, pid int32) error {
if err := c.requireStatus("signal a process inside", Running); err != nil {
return err
}
- if !c.isSandboxRunning() {
+ if !c.IsSandboxRunning() {
return fmt.Errorf("sandbox is not running")
}
return c.Sandbox.SignalProcess(c.ID, int32(pid), sig, false)
@@ -889,7 +781,7 @@ func (c *Container) waitForStopped() error {
defer cancel()
b := backoff.WithContext(backoff.NewConstantBackOff(100*time.Millisecond), ctx)
op := func() error {
- if c.isSandboxRunning() {
+ if c.IsSandboxRunning() {
if err := c.SignalContainer(syscall.Signal(0), false); err == nil {
return fmt.Errorf("container is still running")
}
@@ -1091,7 +983,7 @@ func (c *Container) changeStatus(s Status) {
c.Status = s
}
-func (c *Container) isSandboxRunning() bool {
+func (c *Container) IsSandboxRunning() bool {
return c.Sandbox != nil && c.Sandbox.IsRunning()
}
diff --git a/runsc/container/state_file.go b/runsc/container/state_file.go
index 17a251530..dfbf1f2d3 100644
--- a/runsc/container/state_file.go
+++ b/runsc/container/state_file.go
@@ -20,58 +20,228 @@ import (
"io/ioutil"
"os"
"path/filepath"
+ "regexp"
+ "strings"
+ "syscall"
"github.com/gofrs/flock"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sync"
)
-const stateFileExtension = ".state"
+const stateFileExtension = "state"
-// StateFile handles load from/save to container state safely from multiple
-// processes. It uses a lock file to provide synchronization between operations.
+// LoadOpts provides options for Load()ing a container.
+type LoadOpts struct {
+ // Exact tells whether the search should be exact. See Load() for more.
+ Exact bool
+
+ // SkipCheck tells Load() to skip checking if container is runnning.
+ SkipCheck bool
+}
+
+// Load loads a container with the given id from a metadata file. "id" may
+// be an abbreviation of the full container id in case LoadOpts.Exact if not
+// set. It also checks if the container is still running, in order to return
+// an error to the caller earlier. This check is skipped if LoadOpts.SkipCheck
+// is set.
//
-// The lock file is located at: "${s.RootDir}/${s.ID}.lock".
-// The state file is located at: "${s.RootDir}/${s.ID}.state".
-type StateFile struct {
- // RootDir is the directory containing the container metadata file.
- RootDir string `json:"rootDir"`
+// Returns ErrNotExist if no container is found. Returns error in case more than
+// one containers matching the ID prefix is found.
+func Load(rootDir string, id FullID, opts LoadOpts) (*Container, error) {
+ //log.Debugf("Load container, rootDir: %q, partial cid: %s", rootDir, partialID)
+ if !opts.Exact {
+ var err error
+ id, err = findContainerID(rootDir, id.ContainerID)
+ if err != nil {
+ // Preserve error so that callers can distinguish 'not found' errors.
+ return nil, err
+ }
+ }
- // ID is the container ID.
- ID string `json:"id"`
+ if err := id.validate(); err != nil {
+ return nil, fmt.Errorf("invalid container id: %v", err)
+ }
+ state := StateFile{
+ RootDir: rootDir,
+ ID: id,
+ }
+ defer state.close()
- //
- // Fields below this line are not saved in the state file and will not
- // be preserved across commands.
- //
+ c := &Container{}
+ if err := state.load(c); err != nil {
+ if os.IsNotExist(err) {
+ // Preserve error so that callers can distinguish 'not found' errors.
+ return nil, err
+ }
+ return nil, fmt.Errorf("reading container metadata file %q: %v", state.statePath(), err)
+ }
- once sync.Once
- flock *flock.Flock
+ if !opts.SkipCheck {
+ // If the status is "Running" or "Created", check that the sandbox/container
+ // is still running, setting it to Stopped if not.
+ //
+ // This is inherently racy.
+ switch c.Status {
+ case Created:
+ if !c.IsSandboxRunning() {
+ // Sandbox no longer exists, so this container definitely does not exist.
+ c.changeStatus(Stopped)
+ }
+ case Running:
+ if err := c.SignalContainer(syscall.Signal(0), false); err != nil {
+ c.changeStatus(Stopped)
+ }
+ }
+ }
+
+ return c, nil
}
// List returns all container ids in the given root directory.
-func List(rootDir string) ([]string, error) {
+func List(rootDir string) ([]FullID, error) {
log.Debugf("List containers %q", rootDir)
- list, err := filepath.Glob(filepath.Join(rootDir, "*"+stateFileExtension))
+ return listMatch(rootDir, FullID{})
+}
+
+// listMatch returns all container ids that match the provided id.
+func listMatch(rootDir string, id FullID) ([]FullID, error) {
+ id.SandboxID += "*"
+ id.ContainerID += "*"
+ pattern := buildPath(rootDir, id, stateFileExtension)
+ list, err := filepath.Glob(pattern)
if err != nil {
return nil, err
}
- var out []string
+ var out []FullID
for _, path := range list {
- // Filter out files that do no belong to a container.
- fileName := filepath.Base(path)
- if len(fileName) < len(stateFileExtension) {
- panic(fmt.Sprintf("invalid file match %q", path))
- }
- // Remove the extension.
- cid := fileName[:len(fileName)-len(stateFileExtension)]
- if validateID(cid) == nil {
- out = append(out, cid)
+ id, err := parseFileName(filepath.Base(path))
+ if err == nil {
+ out = append(out, id)
}
}
return out, nil
}
+// loadSandbox loads all containers that belong to the sandbox with the given
+// ID.
+func loadSandbox(rootDir, id string) ([]*Container, error) {
+ cids, err := listMatch(rootDir, FullID{SandboxID: id})
+ if err != nil {
+ return nil, err
+ }
+
+ // Load the container metadata.
+ var containers []*Container
+ for _, cid := range cids {
+ container, err := Load(rootDir, cid, LoadOpts{Exact: true, SkipCheck: true})
+ if err != nil {
+ // Container file may not exist if it raced with creation/deletion or
+ // directory was left behind. Load provides a snapshot in time, so it's
+ // fine to skip it.
+ if os.IsNotExist(err) {
+ continue
+ }
+ return nil, fmt.Errorf("loading sandbox %q, failed to load container %q: %v", id, cid, err)
+ }
+ containers = append(containers, container)
+ }
+ return containers, nil
+}
+
+func findContainerID(rootDir, partialID string) (FullID, error) {
+ // Check whether the id fully specifies an existing container.
+ pattern := buildPath(rootDir, FullID{SandboxID: "*", ContainerID: partialID + "*"}, stateFileExtension)
+ list, err := filepath.Glob(pattern)
+ if err != nil {
+ return FullID{}, err
+ }
+ switch len(list) {
+ case 0:
+ return FullID{}, os.ErrNotExist
+ case 1:
+ return parseFileName(filepath.Base(list[0]))
+ }
+
+ // Now see whether id could be an abbreviation of exactly 1 of the
+ // container ids. If id is ambiguous (it could match more than 1
+ // container), it is an error.
+ ids, err := List(rootDir)
+ if err != nil {
+ return FullID{}, err
+ }
+ var rv *FullID
+ for _, id := range ids {
+ if strings.HasPrefix(id.ContainerID, partialID) {
+ if rv != nil {
+ return FullID{}, fmt.Errorf("id %q is ambiguous and could refer to multiple containers: %q, %q", partialID, rv, id)
+ }
+ rv = &id
+ }
+ }
+ if rv == nil {
+ return FullID{}, os.ErrNotExist
+ }
+ log.Debugf("abbreviated id %q resolves to full id %v", partialID, *rv)
+ return *rv, nil
+}
+
+func parseFileName(name string) (FullID, error) {
+ re := regexp.MustCompile(`([\w+-\.]+)_sandbox:([\w+-\.]+)\.` + stateFileExtension)
+ groups := re.FindStringSubmatch(name)
+ if len(groups) != 3 {
+ return FullID{}, fmt.Errorf("invalid state file name format: %q", name)
+ }
+ id := FullID{
+ SandboxID: groups[2],
+ ContainerID: groups[1],
+ }
+ if err := id.validate(); err != nil {
+ return FullID{}, fmt.Errorf("invalid state file name %q: %w", name, err)
+ }
+ return id, nil
+}
+
+// FullID combines sandbox and container ID to identify a container. Sandbox ID
+// is used to allow all containers for a given sandbox to be loaded by matching
+// sandbox ID in the file name.
+type FullID struct {
+ SandboxID string `json:"sandboxId"`
+ ContainerID string `json:"containerId"`
+}
+
+func (f *FullID) String() string {
+ return f.SandboxID + "/" + f.ContainerID
+}
+
+func (f *FullID) validate() error {
+ if err := validateID(f.SandboxID); err != nil {
+ return err
+ }
+ return validateID(f.ContainerID)
+}
+
+// StateFile handles load from/save to container state safely from multiple
+// processes. It uses a lock file to provide synchronization between operations.
+//
+// The lock file is located at: "${s.RootDir}/${containerd-id}_sand:{sandbox-id}.lock".
+// The state file is located at: "${s.RootDir}/${containerd-id}_sand:{sandbox-id}.state".
+type StateFile struct {
+ // RootDir is the directory containing the container metadata file.
+ RootDir string `json:"rootDir"`
+
+ // ID is the sandbox+container ID.
+ ID FullID `json:"id"`
+
+ //
+ // Fields below this line are not saved in the state file and will not
+ // be preserved across commands.
+ //
+
+ once sync.Once
+ flock *flock.Flock
+}
+
// lock globally locks all locking operations for the container.
func (s *StateFile) lock() error {
s.once.Do(func() {
@@ -157,18 +327,20 @@ func (s *StateFile) close() error {
return s.flock.Close()
}
-func buildStatePath(rootDir, id string) string {
- return filepath.Join(rootDir, id+stateFileExtension)
+func buildPath(rootDir string, id FullID, extension string) string {
+ // Note: "_" and ":" are not valid in IDs.
+ name := fmt.Sprintf("%s_sandbox:%s.%s", id.ContainerID, id.SandboxID, extension)
+ return filepath.Join(rootDir, name)
}
// statePath is the full path to the state file.
func (s *StateFile) statePath() string {
- return buildStatePath(s.RootDir, s.ID)
+ return buildPath(s.RootDir, s.ID, stateFileExtension)
}
// lockPath is the full path to the lock file.
func (s *StateFile) lockPath() string {
- return filepath.Join(s.RootDir, s.ID+".lock")
+ return buildPath(s.RootDir, s.ID, "lock")
}
// destroy deletes all state created by the stateFile. It may be called with the