summaryrefslogtreecommitdiff
path: root/libpod/container_api.go
diff options
context:
space:
mode:
Diffstat (limited to 'libpod/container_api.go')
-rw-r--r--libpod/container_api.go201
1 files changed, 132 insertions, 69 deletions
diff --git a/libpod/container_api.go b/libpod/container_api.go
index a6fcf709d..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()
@@ -123,7 +125,18 @@ func (c *Container) StartAndAttach(ctx context.Context, streams *define.AttachSt
// Attach to the container before starting it
go func() {
- if err := c.attach(streams, keys, resize, true, startedChan, nil); err != nil {
+ // Start resizing
+ if c.LogDriver() != define.PassthroughLogging {
+ registerResizeFunc(resize, c.bundlePath())
+ }
+
+ opts := new(AttachOptions)
+ opts.Streams = streams
+ opts.DetachKeys = &keys
+ opts.Start = true
+ opts.Started = startedChan
+
+ if err := c.ociRuntime.Attach(c, opts); err != nil {
attachChan <- err
}
close(attachChan)
@@ -185,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)
@@ -202,15 +215,14 @@ func (c *Container) Kill(signal uint) error {
}
}
- // TODO: Is killing a paused container OK?
switch c.state.State {
- case define.ContainerStateRunning, define.ContainerStateStopping:
+ case define.ContainerStateRunning, define.ContainerStateStopping, define.ContainerStatePaused:
// Note that killing containers in "stopping" state is okay.
// In that state, the Podman is waiting for the runtime to
// 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.
@@ -228,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()
@@ -243,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
@@ -261,8 +273,18 @@ func (c *Container) Attach(streams *define.AttachStreams, keys string, resize <-
}()
}
+ // Start resizing
+ if c.LogDriver() != define.PassthroughLogging {
+ registerResizeFunc(resize, c.bundlePath())
+ }
+
+ opts := new(AttachOptions)
+ opts.Streams = streams
+ opts.DetachKeys = &keys
+ opts.AttachReady = attachRdy
+
c.newContainerEvent(events.Attach)
- return c.attach(streams, keys, resize, false, nil, attachRdy)
+ return c.ociRuntime.Attach(c, opts)
}
// HTTPAttach forwards an attach session over a hijacked HTTP session.
@@ -299,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())
@@ -314,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()
@@ -325,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())
@@ -362,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)
@@ -394,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()
@@ -415,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()
@@ -434,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)
@@ -447,7 +469,7 @@ func (c *Container) AddArtifact(name string, data []byte) error {
return define.ErrCtrRemoved
}
- return ioutil.WriteFile(c.getArtifactPath(name), data, 0740)
+ return ioutil.WriteFile(c.getArtifactPath(name), data, 0o740)
}
// GetArtifact reads the specified artifact file from the container
@@ -470,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)
}
}
}
@@ -531,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) {
@@ -552,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)
}()
}
@@ -601,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.
@@ -629,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)
@@ -687,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)
@@ -726,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()
@@ -878,7 +941,7 @@ func (c *Container) ShouldRestart(ctx context.Context) bool {
// CopyFromArchive copies the contents from the specified tarStream to path
// *inside* the container.
-func (c *Container) CopyFromArchive(ctx context.Context, containerPath string, chown bool, rename map[string]string, tarStream io.Reader) (func() error, error) {
+func (c *Container) CopyFromArchive(_ context.Context, containerPath string, chown, noOverwriteDirNonDir bool, rename map[string]string, tarStream io.Reader) (func() error, error) {
if !c.batched {
c.lock.Lock()
defer c.lock.Unlock()
@@ -888,7 +951,7 @@ func (c *Container) CopyFromArchive(ctx context.Context, containerPath string, c
}
}
- return c.copyFromArchive(containerPath, chown, rename, tarStream)
+ return c.copyFromArchive(containerPath, chown, noOverwriteDirNonDir, rename, tarStream)
}
// CopyToArchive copies the contents from the specified path *inside* the