diff options
Diffstat (limited to 'libpod/container_api.go')
-rw-r--r-- | libpod/container_api.go | 167 |
1 files changed, 105 insertions, 62 deletions
diff --git a/libpod/container_api.go b/libpod/container_api.go index b064d3528..dbd5fc1fb 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -2,6 +2,8 @@ package libpod import ( "context" + "errors" + "fmt" "io" "io/ioutil" "net/http" @@ -9,11 +11,11 @@ import ( "sync" "time" + "github.com/containers/common/pkg/resize" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/libpod/events" "github.com/containers/podman/v4/pkg/signal" "github.com/containers/storage/pkg/archive" - "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -38,7 +40,7 @@ func (c *Container) Init(ctx context.Context, recursive bool) error { } if !c.ensureState(define.ContainerStateConfigured, define.ContainerStateStopped, define.ContainerStateExited) { - return errors.Wrapf(define.ErrCtrStateInvalid, "container %s has already been created in runtime", c.ID()) + return fmt.Errorf("container %s has already been created in runtime: %w", c.ID(), define.ErrCtrStateInvalid) } if !recursive { @@ -102,7 +104,7 @@ func (c *Container) Start(ctx context.Context, recursive bool) error { // Attach call occurs before Start). // In overall functionality, it is identical to the Start call, with the added // side effect that an attach session will also be started. -func (c *Container) StartAndAttach(ctx context.Context, streams *define.AttachStreams, keys string, resize <-chan define.TerminalSize, recursive bool) (<-chan error, error) { +func (c *Container) StartAndAttach(ctx context.Context, streams *define.AttachStreams, keys string, resize <-chan resize.TerminalSize, recursive bool) (<-chan error, error) { if !c.batched { c.lock.Lock() defer c.lock.Unlock() @@ -196,7 +198,7 @@ func (c *Container) StopWithTimeout(timeout uint) error { } if !c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning, define.ContainerStateStopping) { - return errors.Wrapf(define.ErrCtrStateInvalid, "can only stop created or running containers. %s is in state %s", c.ID(), c.state.State.String()) + return fmt.Errorf("can only stop created or running containers. %s is in state %s: %w", c.ID(), c.state.State.String(), define.ErrCtrStateInvalid) } return c.stop(timeout) @@ -220,7 +222,7 @@ func (c *Container) Kill(signal uint) error { // stop the container and if that is taking too long, a user // may have decided to kill the container after all. default: - return errors.Wrapf(define.ErrCtrStateInvalid, "can only kill running containers. %s is in state %s", c.ID(), c.state.State.String()) + return fmt.Errorf("can only kill running containers. %s is in state %s: %w", c.ID(), c.state.State.String(), define.ErrCtrStateInvalid) } // Hardcode all = false, we only use all when removing. @@ -238,9 +240,9 @@ func (c *Container) Kill(signal uint) error { // Attach attaches to a container. // This function returns when the attach finishes. It does not hold the lock for // the duration of its runtime, only using it at the beginning to verify state. -func (c *Container) Attach(streams *define.AttachStreams, keys string, resize <-chan define.TerminalSize) error { +func (c *Container) Attach(streams *define.AttachStreams, keys string, resize <-chan resize.TerminalSize) error { if c.LogDriver() == define.PassthroughLogging { - return errors.Wrapf(define.ErrNoLogs, "this container is using the 'passthrough' log driver, cannot attach") + return fmt.Errorf("this container is using the 'passthrough' log driver, cannot attach: %w", define.ErrNoLogs) } if !c.batched { c.lock.Lock() @@ -253,7 +255,7 @@ func (c *Container) Attach(streams *define.AttachStreams, keys string, resize <- } if !c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning) { - return errors.Wrapf(define.ErrCtrStateInvalid, "can only attach to created or running containers") + return fmt.Errorf("can only attach to created or running containers: %w", define.ErrCtrStateInvalid) } // HACK: This is really gross, but there isn't a better way without @@ -319,11 +321,11 @@ func (c *Container) HTTPAttach(r *http.Request, w http.ResponseWriter, streams * } if !c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning) { - return errors.Wrapf(define.ErrCtrStateInvalid, "can only attach to created or running containers") + return fmt.Errorf("can only attach to created or running containers: %w", define.ErrCtrStateInvalid) } if !streamAttach && !streamLogs { - return errors.Wrapf(define.ErrInvalidArg, "must specify at least one of stream or logs") + return fmt.Errorf("must specify at least one of stream or logs: %w", define.ErrInvalidArg) } logrus.Infof("Performing HTTP Hijack attach to container %s", c.ID()) @@ -334,7 +336,7 @@ func (c *Container) HTTPAttach(r *http.Request, w http.ResponseWriter, streams * // AttachResize resizes the container's terminal, which is displayed by Attach // and HTTPAttach. -func (c *Container) AttachResize(newSize define.TerminalSize) error { +func (c *Container) AttachResize(newSize resize.TerminalSize) error { if !c.batched { c.lock.Lock() defer c.lock.Unlock() @@ -345,7 +347,7 @@ func (c *Container) AttachResize(newSize define.TerminalSize) error { } if !c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning) { - return errors.Wrapf(define.ErrCtrStateInvalid, "can only resize created or running containers") + return fmt.Errorf("can only resize created or running containers: %w", define.ErrCtrStateInvalid) } logrus.Infof("Resizing TTY of container %s", c.ID()) @@ -382,20 +384,20 @@ func (c *Container) Unmount(force bool) error { if c.state.Mounted { mounted, err := c.runtime.storageService.MountedContainerImage(c.ID()) if err != nil { - return errors.Wrapf(err, "can't determine how many times %s is mounted, refusing to unmount", c.ID()) + return fmt.Errorf("can't determine how many times %s is mounted, refusing to unmount: %w", c.ID(), err) } if mounted == 1 { if c.ensureState(define.ContainerStateRunning, define.ContainerStatePaused) { - return errors.Wrapf(define.ErrCtrStateInvalid, "cannot unmount storage for container %s as it is running or paused", c.ID()) + return fmt.Errorf("cannot unmount storage for container %s as it is running or paused: %w", c.ID(), define.ErrCtrStateInvalid) } execSessions, err := c.getActiveExecSessions() if err != nil { return err } if len(execSessions) != 0 { - return errors.Wrapf(define.ErrCtrStateInvalid, "container %s has active exec sessions, refusing to unmount", c.ID()) + return fmt.Errorf("container %s has active exec sessions, refusing to unmount: %w", c.ID(), define.ErrCtrStateInvalid) } - return errors.Wrapf(define.ErrInternal, "can't unmount %s last mount, it is still in use", c.ID()) + return fmt.Errorf("can't unmount %s last mount, it is still in use: %w", c.ID(), define.ErrInternal) } } defer c.newContainerEvent(events.Unmount) @@ -414,10 +416,10 @@ func (c *Container) Pause() error { } if c.state.State == define.ContainerStatePaused { - return errors.Wrapf(define.ErrCtrStateInvalid, "%q is already paused", c.ID()) + return fmt.Errorf("%q is already paused: %w", c.ID(), define.ErrCtrStateInvalid) } if c.state.State != define.ContainerStateRunning { - return errors.Wrapf(define.ErrCtrStateInvalid, "%q is not running, can't pause", c.state.State) + return fmt.Errorf("%q is not running, can't pause: %w", c.state.State, define.ErrCtrStateInvalid) } defer c.newContainerEvent(events.Pause) return c.pause() @@ -435,7 +437,7 @@ func (c *Container) Unpause() error { } if c.state.State != define.ContainerStatePaused { - return errors.Wrapf(define.ErrCtrStateInvalid, "%q is not paused, can't unpause", c.ID()) + return fmt.Errorf("%q is not paused, can't unpause: %w", c.ID(), define.ErrCtrStateInvalid) } defer c.newContainerEvent(events.Unpause) return c.unpause() @@ -454,7 +456,7 @@ func (c *Container) Export(path string) error { } if c.state.State == define.ContainerStateRemoving { - return errors.Wrapf(define.ErrCtrStateInvalid, "cannot mount container %s as it is being removed", c.ID()) + return fmt.Errorf("cannot mount container %s as it is being removed: %w", c.ID(), define.ErrCtrStateInvalid) } defer c.newContainerEvent(events.Mount) @@ -490,41 +492,84 @@ func (c *Container) RemoveArtifact(name string) error { // Wait blocks until the container exits and returns its exit code. func (c *Container) Wait(ctx context.Context) (int32, error) { - return c.WaitWithInterval(ctx, DefaultWaitInterval) + return c.WaitForExit(ctx, DefaultWaitInterval) } -// WaitWithInterval blocks until the container to exit and returns its exit -// code. The argument is the interval at which checks the container's status. -func (c *Container) WaitWithInterval(ctx context.Context, waitTimeout time.Duration) (int32, error) { +// WaitForExit blocks until the container exits and returns its exit code. The +// argument is the interval at which checks the container's status. +func (c *Container) WaitForExit(ctx context.Context, pollInterval time.Duration) (int32, error) { if !c.valid { return -1, define.ErrCtrRemoved } - exitFile, err := c.exitFilePath() - if err != nil { - return -1, err - } - chWait := make(chan error, 1) + id := c.ID() + var conmonTimer time.Timer + conmonTimerSet := false - go func() { - <-ctx.Done() - chWait <- define.ErrCanceled - }() + getExitCode := func() (bool, int32, error) { + containerRemoved := false + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + } - for { - // ignore errors here (with exception of cancellation), it is only used to avoid waiting - // too long. - _, e := WaitForFile(exitFile, chWait, waitTimeout) - if e == define.ErrCanceled { - return -1, define.ErrCanceled + if err := c.syncContainer(); err != nil { + if !errors.Is(err, define.ErrNoSuchCtr) { + return false, -1, err + } + containerRemoved = true + } + + // If conmon is not alive anymore set a timer to make sure + // we're returning even if conmon has forcefully been killed. + if !conmonTimerSet && !containerRemoved { + conmonAlive, err := c.ociRuntime.CheckConmonRunning(c) + switch { + case errors.Is(err, define.ErrNoSuchCtr): + containerRemoved = true + case err != nil: + return false, -1, err + case !conmonAlive: + timerDuration := time.Second * 20 + conmonTimer = *time.NewTimer(timerDuration) + conmonTimerSet = true + } + } + + if !containerRemoved { + // If conmon is dead for more than $timerDuration or if the + // container has exited properly, try to look up the exit code. + select { + case <-conmonTimer.C: + logrus.Debugf("Exceeded conmon timeout waiting for container %s to exit", id) + default: + if !c.ensureState(define.ContainerStateExited, define.ContainerStateConfigured) { + return false, -1, nil + } + } } - stopped, code, err := c.isStopped() + exitCode, err := c.runtime.state.GetContainerExitCode(id) + if err != nil { + return true, -1, err + } + + return true, exitCode, nil + } + + for { + hasExited, exitCode, err := getExitCode() + if hasExited { + return exitCode, err + } if err != nil { return -1, err } - if stopped { - return code, nil + select { + case <-ctx.Done(): + return -1, fmt.Errorf("waiting for exit code of container %s canceled", id) + default: + time.Sleep(pollInterval) } } } @@ -551,11 +596,12 @@ func (c *Container) WaitForConditionWithInterval(ctx context.Context, waitTimeou wantedStates := make(map[define.ContainerStatus]bool, len(conditions)) for _, condition := range conditions { - if condition == define.ContainerStateStopped || condition == define.ContainerStateExited { + switch condition { + case define.ContainerStateExited, define.ContainerStateStopped: waitForExit = true - continue + default: + wantedStates[condition] = true } - wantedStates[condition] = true } trySend := func(code int32, err error) { @@ -572,7 +618,7 @@ func (c *Container) WaitForConditionWithInterval(ctx context.Context, waitTimeou go func() { defer wg.Done() - code, err := c.WaitWithInterval(ctx, waitTimeout) + code, err := c.WaitForExit(ctx, waitTimeout) trySend(code, err) }() } @@ -621,13 +667,21 @@ func (c *Container) Cleanup(ctx context.Context) error { defer c.lock.Unlock() if err := c.syncContainer(); err != nil { + // When the container has already been removed, the OCI runtime directory remain. + if errors.Is(err, define.ErrNoSuchCtr) || errors.Is(err, define.ErrCtrRemoved) { + if err := c.cleanupRuntime(ctx); err != nil { + return fmt.Errorf("error cleaning up container %s from OCI runtime: %w", c.ID(), err) + } + return nil + } + logrus.Errorf("Syncing container %s status: %v", c.ID(), err) return err } } // Check if state is good if !c.ensureState(define.ContainerStateConfigured, define.ContainerStateCreated, define.ContainerStateStopped, define.ContainerStateStopping, define.ContainerStateExited) { - return errors.Wrapf(define.ErrCtrStateInvalid, "container %s is running or paused, refusing to clean up", c.ID()) + return fmt.Errorf("container %s is running or paused, refusing to clean up: %w", c.ID(), define.ErrCtrStateInvalid) } // Handle restart policy. @@ -649,7 +703,7 @@ func (c *Container) Cleanup(ctx context.Context) error { return err } if len(sessions) > 0 { - return errors.Wrapf(define.ErrCtrStateInvalid, "container %s has active exec sessions, refusing to clean up", c.ID()) + return fmt.Errorf("container %s has active exec sessions, refusing to clean up: %w", c.ID(), define.ErrCtrStateInvalid) } defer c.newContainerEvent(events.Cleanup) @@ -707,19 +761,8 @@ func (c *Container) Sync() error { defer c.lock.Unlock() } - // If runtime knows about the container, update its status in runtime - // And then save back to disk - if c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning, define.ContainerStatePaused, define.ContainerStateStopped, define.ContainerStateStopping) { - oldState := c.state.State - if err := c.ociRuntime.UpdateContainerStatus(c); err != nil { - return err - } - // Only save back to DB if state changed - if c.state.State != oldState { - if err := c.save(); err != nil { - return err - } - } + if err := c.syncContainer(); err != nil { + return err } defer c.newContainerEvent(events.Sync) @@ -746,7 +789,7 @@ func (c *Container) ReloadNetwork() error { } if !c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning) { - return errors.Wrapf(define.ErrCtrStateInvalid, "cannot reload network unless container network has been configured") + return fmt.Errorf("cannot reload network unless container network has been configured: %w", define.ErrCtrStateInvalid) } return c.reloadNetwork() |