diff options
Diffstat (limited to 'libpod')
-rw-r--r-- | libpod/container_api.go | 109 | ||||
-rw-r--r-- | libpod/container_internal.go | 6 | ||||
-rw-r--r-- | libpod/define/errors.go | 4 | ||||
-rw-r--r-- | libpod/kube.go | 21 | ||||
-rw-r--r-- | libpod/networking_linux.go | 2 |
5 files changed, 108 insertions, 34 deletions
diff --git a/libpod/container_api.go b/libpod/container_api.go index 951227a4f..2473acec0 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "net/http" "os" + "sync" "time" "github.com/containers/podman/v2/libpod/define" @@ -478,13 +479,13 @@ func (c *Container) RemoveArtifact(name string) error { } // Wait blocks until the container exits and returns its exit code. -func (c *Container) Wait() (int32, error) { - return c.WaitWithInterval(DefaultWaitInterval) +func (c *Container) Wait(ctx context.Context) (int32, error) { + return c.WaitWithInterval(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(waitTimeout time.Duration) (int32, error) { +func (c *Container) WaitWithInterval(ctx context.Context, waitTimeout time.Duration) (int32, error) { if !c.valid { return -1, define.ErrCtrRemoved } @@ -495,41 +496,111 @@ func (c *Container) WaitWithInterval(waitTimeout time.Duration) (int32, error) { } chWait := make(chan error, 1) - defer close(chWait) + go func() { + <-ctx.Done() + chWait <- define.ErrCanceled + }() for { - // ignore errors here, it is only used to avoid waiting + // ignore errors here (with exception of cancellation), it is only used to avoid waiting // too long. - _, _ = WaitForFile(exitFile, chWait, waitTimeout) + _, e := WaitForFile(exitFile, chWait, waitTimeout) + if e == define.ErrCanceled { + return -1, define.ErrCanceled + } - stopped, err := c.isStopped() + stopped, code, err := c.isStopped() if err != nil { return -1, err } if stopped { - return c.state.ExitCode, nil + return code, nil } } } -func (c *Container) WaitForConditionWithInterval(waitTimeout time.Duration, condition define.ContainerStatus) (int32, error) { +type waitResult struct { + code int32 + err error +} + +func (c *Container) WaitForConditionWithInterval(ctx context.Context, waitTimeout time.Duration, conditions ...define.ContainerStatus) (int32, error) { if !c.valid { return -1, define.ErrCtrRemoved } - if condition == define.ContainerStateStopped || condition == define.ContainerStateExited { - return c.WaitWithInterval(waitTimeout) + + if len(conditions) == 0 { + panic("at least one condition should be passed") } - for { - state, err := c.State() - if err != nil { - return -1, err + + ctx, cancelFn := context.WithCancel(ctx) + defer cancelFn() + + resultChan := make(chan waitResult) + waitForExit := false + wantedStates := make(map[define.ContainerStatus]bool, len(conditions)) + + for _, condition := range conditions { + if condition == define.ContainerStateStopped || condition == define.ContainerStateExited { + waitForExit = true + continue } - if state == condition { - break + wantedStates[condition] = true + } + + trySend := func(code int32, err error) { + select { + case resultChan <- waitResult{code, err}: + case <-ctx.Done(): } - time.Sleep(waitTimeout) } - return -1, nil + + var wg sync.WaitGroup + + if waitForExit { + wg.Add(1) + go func() { + defer wg.Done() + + code, err := c.WaitWithInterval(ctx, waitTimeout) + trySend(code, err) + }() + } + + if len(wantedStates) > 0 { + wg.Add(1) + go func() { + defer wg.Done() + + for { + state, err := c.State() + if err != nil { + trySend(-1, err) + return + } + if _, found := wantedStates[state]; found { + trySend(-1, nil) + return + } + select { + case <-ctx.Done(): + return + case <-time.After(waitTimeout): + continue + } + } + }() + } + + var result waitResult + select { + case result = <-resultChan: + cancelFn() + case <-ctx.Done(): + result = waitResult{-1, define.ErrCanceled} + } + wg.Wait() + return result.code, result.err } // Cleanup unmounts all mount points in container and cleans up container storage diff --git a/libpod/container_internal.go b/libpod/container_internal.go index b9ea50783..5a61f7fe6 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -754,17 +754,17 @@ func (c *Container) getArtifactPath(name string) string { } // Used with Wait() to determine if a container has exited -func (c *Container) isStopped() (bool, error) { +func (c *Container) isStopped() (bool, int32, error) { if !c.batched { c.lock.Lock() defer c.lock.Unlock() } err := c.syncContainer() if err != nil { - return true, err + return true, -1, err } - return !c.ensureState(define.ContainerStateRunning, define.ContainerStatePaused, define.ContainerStateStopping), nil + return !c.ensureState(define.ContainerStateRunning, define.ContainerStatePaused, define.ContainerStateStopping), c.state.ExitCode, nil } // save container state to the database diff --git a/libpod/define/errors.go b/libpod/define/errors.go index d37bc397e..2e85454b2 100644 --- a/libpod/define/errors.go +++ b/libpod/define/errors.go @@ -198,4 +198,8 @@ var ( // ErrSecurityAttribute indicates that an error processing security attributes // for the container ErrSecurityAttribute = fmt.Errorf("%w: unable to process security attribute", ErrOCIRuntime) + + // ErrCanceled indicates that an operation has been cancelled by a user. + // Useful for potentially long running tasks. + ErrCanceled = errors.New("cancelled by user") ) diff --git a/libpod/kube.go b/libpod/kube.go index bf314b9a3..f9ead027d 100644 --- a/libpod/kube.go +++ b/libpod/kube.go @@ -353,22 +353,21 @@ func containerToV1Container(c *Container) (v1.Container, []v1.Volume, *v1.PodDNS return kubeContainer, kubeVolumes, nil, err } - containerCommands := c.Command() - kubeContainer.Name = removeUnderscores(c.Name()) + // Handle command and arguments. + if ep := c.Entrypoint(); len(ep) > 0 { + // If we have an entrypoint, set the container's command as + // arguments. + kubeContainer.Command = ep + kubeContainer.Args = c.Command() + } else { + kubeContainer.Command = c.Command() + } + kubeContainer.Name = removeUnderscores(c.Name()) _, image := c.Image() kubeContainer.Image = image kubeContainer.Stdin = c.Stdin() - // prepend the entrypoint of the container to command - if ep := c.Entrypoint(); len(c.Entrypoint()) > 0 { - ep = append(ep, containerCommands...) - containerCommands = ep - } - kubeContainer.Command = containerCommands - // TODO need to figure out how we handle command vs entry point. Kube appears to prefer entrypoint. - // right now we just take the container's command - //container.Args = args kubeContainer.WorkingDir = c.WorkingDir() kubeContainer.Ports = ports // This should not be applicable diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 01e4102d1..55d338e7d 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -1180,7 +1180,7 @@ func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) erro // update network status if container is not running networkStatus := c.state.NetworkStatus // clip out the index of the network - tmpNetworkStatus := make([]*cnitypes.Result, len(networkStatus)-1) + tmpNetworkStatus := make([]*cnitypes.Result, 0, len(networkStatus)-1) for k, v := range networkStatus { if index != k { tmpNetworkStatus = append(tmpNetworkStatus, v) |