diff --git a/pkg/sentry/control/lifecycle.go b/pkg/sentry/control/lifecycle.go index 4c31b45ea..4563f91e8 100644 --- a/pkg/sentry/control/lifecycle.go +++ b/pkg/sentry/control/lifecycle.go @@ -46,9 +46,35 @@ type Lifecycle struct { // namespaces. MountNamespacesMap map[string]*vfs.MountNamespace - // containerInitProcessMap is a map of container ID and the init(PID 1) - // threadgroup of the container. - containerInitProcessMap map[string]*kernel.ThreadGroup + // containerMap is a map of the container id and the container. + containerMap map[string]*Container +} + +// containerState is the state of the container. +type containerState int + +const ( + // stateCreated is the state when the container was created. It is the + // initial state. + stateCreated containerState = iota + + // stateRunning is the state when the container/application is running. + stateRunning + + // stateStopped is the state when the container has exited. + stateStopped +) + +// Container contains the set of parameters to represent a container. +type Container struct { + // containerID. + containerID string + + // tg is the init(PID 1) threadgroup of the container. + tg *kernel.ThreadGroup + + // state is the current state of the container. + state containerState } // StartContainerArgs is the set of arguments to start a container. @@ -104,6 +130,38 @@ func (args StartContainerArgs) String() string { return strings.Join(a, " ") } +func (l *Lifecycle) updateContainerState(containerID string, newState containerState) error { + l.mu.Lock() + defer l.mu.Unlock() + + c, ok := l.containerMap[containerID] + if !ok { + return fmt.Errorf("container %v not started", containerID) + } + + switch newState { + case stateCreated: + // Impossible. + panic(fmt.Sprintf("invalid state transition: %v => %v", c.state, newState)) + + case stateRunning: + if c.state != stateCreated { + // Impossible. + panic(fmt.Sprintf("invalid state transition: %v => %v", c.state, newState)) + } + + case stateStopped: + // Valid state transition. + + default: + // Invalid new state. + panic(fmt.Sprintf("invalid new state: %v", newState)) + } + + c.state = newState + return nil +} + // StartContainer will start a new container in the sandbox. func (l *Lifecycle) StartContainer(args *StartContainerArgs, _ *uint32) error { // Import file descriptors. @@ -177,16 +235,24 @@ func (l *Lifecycle) StartContainer(args *StartContainerArgs, _ *uint32) error { return err } + c := &Container{ + containerID: initArgs.ContainerID, + tg: tg, + state: stateCreated, + } + + l.mu.Lock() + if l.containerMap == nil { + l.containerMap = make(map[string]*Container) + } + l.containerMap[initArgs.ContainerID] = c + l.mu.Unlock() + // Start the newly created process. l.Kernel.StartProcess(tg) log.Infof("Started the new container %v ", initArgs.ContainerID) - l.mu.Lock() - if l.containerInitProcessMap == nil { - l.containerInitProcessMap = make(map[string]*kernel.ThreadGroup) - } - l.containerInitProcessMap[initArgs.ContainerID] = tg - l.mu.Unlock() + l.updateContainerState(initArgs.ContainerID, stateRunning) return nil } @@ -212,11 +278,11 @@ func (l *Lifecycle) getInitContainerProcess(containerID string) (*kernel.ThreadG l.mu.Lock() defer l.mu.Unlock() - tg, ok := l.containerInitProcessMap[containerID] + c, ok := l.containerMap[containerID] if !ok { return nil, fmt.Errorf("container %v not started", containerID) } - return tg, nil + return c.tg, nil } // ContainerArgs is the set of arguments for container related APIs after @@ -235,5 +301,30 @@ func (l *Lifecycle) WaitContainer(args *ContainerArgs, waitStatus *uint32) error tg.WaitExited() *waitStatus = uint32(tg.ExitStatus()) + if err := l.updateContainerState(args.ContainerID, stateStopped); err != nil { + return err + } + return nil +} + +// IsContainerRunning returns true if the container is running. +func (l *Lifecycle) IsContainerRunning(args *ContainerArgs, isRunning *bool) error { + l.mu.Lock() + defer l.mu.Unlock() + + c, ok := l.containerMap[args.ContainerID] + if !ok || c.state != stateRunning { + return nil + } + + // The state will not be updated to "stopped" if the + // WaitContainer(...) method is not called. In this case, check + // whether the number of non-exited tasks in tg is zero to get + // the correct state of the container. + if c.tg.Count() == 0 { + c.state = stateStopped + return nil + } + *isRunning = true return nil }