Merge pull request #9048 from matejvasek/apiv2_wait

Fix Docker APIv2 container wait endpoint
This commit is contained in:
OpenShift Merge Robot
2021-02-05 04:41:41 -05:00
committed by GitHub
17 changed files with 372 additions and 114 deletions

View File

@ -95,10 +95,11 @@ func wait(cmd *cobra.Command, args []string) error {
return errors.New("--latest and containers cannot be used together") return errors.New("--latest and containers cannot be used together")
} }
waitOptions.Condition, err = define.StringToContainerStatus(waitCondition) cond, err := define.StringToContainerStatus(waitCondition)
if err != nil { if err != nil {
return err return err
} }
waitOptions.Condition = []define.ContainerStatus{cond}
responses, err := registry.ContainerEngine().ContainerWait(context.Background(), args, waitOptions) responses, err := registry.ContainerEngine().ContainerWait(context.Background(), args, waitOptions)
if err != nil { if err != nil {

View File

@ -5,6 +5,7 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os" "os"
"sync"
"time" "time"
"github.com/containers/podman/v2/libpod/define" "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. // Wait blocks until the container exits and returns its exit code.
func (c *Container) Wait() (int32, error) { func (c *Container) Wait(ctx context.Context) (int32, error) {
return c.WaitWithInterval(DefaultWaitInterval) return c.WaitWithInterval(ctx, DefaultWaitInterval)
} }
// WaitWithInterval blocks until the container to exit and returns its exit // WaitWithInterval blocks until the container to exit and returns its exit
// code. The argument is the interval at which checks the container's status. // 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 { if !c.valid {
return -1, define.ErrCtrRemoved return -1, define.ErrCtrRemoved
} }
@ -495,41 +496,111 @@ func (c *Container) WaitWithInterval(waitTimeout time.Duration) (int32, error) {
} }
chWait := make(chan error, 1) chWait := make(chan error, 1)
defer close(chWait) go func() {
<-ctx.Done()
chWait <- define.ErrCanceled
}()
for { 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. // 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 { if err != nil {
return -1, err return -1, err
} }
if stopped { 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 { if !c.valid {
return -1, define.ErrCtrRemoved 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() ctx, cancelFn := context.WithCancel(ctx)
if err != nil { defer cancelFn()
return -1, err
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 { wantedStates[condition] = true
break
}
time.Sleep(waitTimeout)
} }
return -1, nil
trySend := func(code int32, err error) {
select {
case resultChan <- waitResult{code, err}:
case <-ctx.Done():
}
}
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 // Cleanup unmounts all mount points in container and cleans up container storage

View File

@ -754,17 +754,17 @@ func (c *Container) getArtifactPath(name string) string {
} }
// Used with Wait() to determine if a container has exited // 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 { if !c.batched {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
} }
err := c.syncContainer() err := c.syncContainer()
if err != nil { 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 // save container state to the database

View File

@ -198,4 +198,8 @@ var (
// ErrSecurityAttribute indicates that an error processing security attributes // ErrSecurityAttribute indicates that an error processing security attributes
// for the container // for the container
ErrSecurityAttribute = fmt.Errorf("%w: unable to process security attribute", ErrOCIRuntime) 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")
) )

View File

@ -23,10 +23,8 @@ import (
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/go-connections/nat" "github.com/docker/go-connections/nat"
"github.com/docker/go-units" "github.com/docker/go-units"
"github.com/gorilla/mux"
"github.com/gorilla/schema" "github.com/gorilla/schema"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus"
) )
func RemoveContainer(w http.ResponseWriter, r *http.Request) { func RemoveContainer(w http.ResponseWriter, r *http.Request) {
@ -233,8 +231,11 @@ func KillContainer(w http.ResponseWriter, r *http.Request) {
return return
} }
if sig == 0 || syscall.Signal(sig) == syscall.SIGKILL { if sig == 0 || syscall.Signal(sig) == syscall.SIGKILL {
if _, err := utils.WaitContainer(w, r); err != nil { opts := entities.WaitOptions{
Condition: []define.ContainerStatus{define.ContainerStateExited, define.ContainerStateStopped},
Interval: time.Millisecond * 250,
}
if _, err := containerEngine.ContainerWait(r.Context(), []string{name}, opts); err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
return return
} }
@ -245,26 +246,8 @@ func KillContainer(w http.ResponseWriter, r *http.Request) {
} }
func WaitContainer(w http.ResponseWriter, r *http.Request) { func WaitContainer(w http.ResponseWriter, r *http.Request) {
var msg string
// /{version}/containers/(name)/wait // /{version}/containers/(name)/wait
exitCode, err := utils.WaitContainer(w, r) utils.WaitContainerDocker(w, r)
if err != nil {
if errors.Cause(err) == define.ErrNoSuchCtr {
logrus.Warnf("container not found %q: %v", utils.GetName(r), err)
return
}
logrus.Warnf("failed to wait on container %q: %v", mux.Vars(r)["name"], err)
return
}
utils.WriteResponse(w, http.StatusOK, handlers.ContainerWaitOKBody{
StatusCode: int(exitCode),
Error: struct {
Message string
}{
Message: msg,
},
})
} }
func LibpodToContainer(l *libpod.Container, sz bool) (*handlers.Container, error) { func LibpodToContainer(l *libpod.Container, sz bool) (*handlers.Container, error) {

View File

@ -4,7 +4,6 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os" "os"
"strconv"
"github.com/containers/podman/v2/libpod" "github.com/containers/podman/v2/libpod"
"github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/libpod/define"
@ -146,17 +145,7 @@ func GetContainer(w http.ResponseWriter, r *http.Request) {
} }
func WaitContainer(w http.ResponseWriter, r *http.Request) { func WaitContainer(w http.ResponseWriter, r *http.Request) {
exitCode, err := utils.WaitContainer(w, r) utils.WaitContainerLibpod(w, r)
if err != nil {
name := utils.GetName(r)
if errors.Cause(err) == define.ErrNoSuchCtr {
utils.ContainerNotFound(w, name, err)
return
}
logrus.Warnf("failed to wait on container %q: %v", name, err)
return
}
utils.WriteResponse(w, http.StatusOK, strconv.Itoa(int(exitCode)))
} }
func UnmountContainer(w http.ResponseWriter, r *http.Request) { func UnmountContainer(w http.ResponseWriter, r *http.Request) {

View File

@ -1,67 +1,230 @@
package utils package utils
import ( import (
"context"
"fmt"
"net/http" "net/http"
"strconv"
"time" "time"
"github.com/containers/podman/v2/libpod"
"github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/domain/entities"
"github.com/containers/podman/v2/pkg/domain/infra/abi" "github.com/containers/podman/v2/pkg/domain/infra/abi"
"github.com/containers/podman/v2/pkg/api/handlers"
"github.com/sirupsen/logrus"
"github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/libpod"
"github.com/gorilla/schema" "github.com/gorilla/schema"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
func WaitContainer(w http.ResponseWriter, r *http.Request) (int32, error) { type waitQueryDocker struct {
var ( Condition string `schema:"condition"`
err error }
interval time.Duration
) type waitQueryLibpod struct {
runtime := r.Context().Value("runtime").(*libpod.Runtime) Interval string `schema:"interval"`
// Now use the ABI implementation to prevent us from having duplicate Condition []define.ContainerStatus `schema:"condition"`
// code. }
containerEngine := abi.ContainerEngine{Libpod: runtime}
decoder := r.Context().Value("decoder").(*schema.Decoder) func WaitContainerDocker(w http.ResponseWriter, r *http.Request) {
query := struct { var err error
Interval string `schema:"interval"` ctx := r.Context()
Condition define.ContainerStatus `schema:"condition"`
}{ query := waitQueryDocker{}
// Override golang default values for types
decoder := ctx.Value("decoder").(*schema.Decoder)
if err = decoder.Decode(&query, r.URL.Query()); err != nil {
Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
return
} }
interval := time.Nanosecond
condition := "not-running"
if _, found := r.URL.Query()["condition"]; found {
condition = query.Condition
if !isValidDockerCondition(query.Condition) {
BadRequest(w, "condition", condition, errors.New("not a valid docker condition"))
return
}
}
name := GetName(r)
exists, err := containerExists(ctx, name)
if err != nil {
InternalServerError(w, err)
return
}
if !exists {
ContainerNotFound(w, name, define.ErrNoSuchCtr)
return
}
// In docker compatibility mode we have to send headers in advance,
// otherwise docker client would freeze.
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(200)
if flusher, ok := w.(http.Flusher); ok {
flusher.Flush()
}
exitCode, err := waitDockerCondition(ctx, name, interval, condition)
msg := ""
if err != nil {
logrus.Errorf("error while waiting on condtion: %q", err)
msg = err.Error()
}
responseData := handlers.ContainerWaitOKBody{
StatusCode: int(exitCode),
Error: struct {
Message string
}{
Message: msg,
},
}
enc := json.NewEncoder(w)
enc.SetEscapeHTML(true)
err = enc.Encode(&responseData)
if err != nil {
logrus.Errorf("unable to write json: %q", err)
}
}
func WaitContainerLibpod(w http.ResponseWriter, r *http.Request) {
var (
err error
interval = time.Millisecond * 250
conditions = []define.ContainerStatus{define.ContainerStateStopped, define.ContainerStateExited}
)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := waitQueryLibpod{}
if err := decoder.Decode(&query, r.URL.Query()); err != nil { if err := decoder.Decode(&query, r.URL.Query()); err != nil {
Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
return 0, err
} }
options := entities.WaitOptions{
Condition: define.ContainerStateStopped,
}
name := GetName(r)
if _, found := r.URL.Query()["interval"]; found { if _, found := r.URL.Query()["interval"]; found {
interval, err = time.ParseDuration(query.Interval) interval, err = time.ParseDuration(query.Interval)
if err != nil { if err != nil {
InternalServerError(w, err) InternalServerError(w, err)
return 0, err return
}
} else {
interval, err = time.ParseDuration("250ms")
if err != nil {
InternalServerError(w, err)
return 0, err
} }
} }
options.Interval = interval
if _, found := r.URL.Query()["condition"]; found { if _, found := r.URL.Query()["condition"]; found {
options.Condition = query.Condition if len(query.Condition) > 0 {
conditions = query.Condition
}
} }
report, err := containerEngine.ContainerWait(r.Context(), []string{name}, options) name := GetName(r)
waitFn := createContainerWaitFn(r.Context(), name, interval)
exitCode, err := waitFn(conditions...)
if err != nil { if err != nil {
return 0, err if errors.Cause(err) == define.ErrNoSuchCtr {
ContainerNotFound(w, name, err)
return
} else {
InternalServerError(w, err)
return
}
} }
if len(report) == 0 { WriteResponse(w, http.StatusOK, strconv.Itoa(int(exitCode)))
InternalServerError(w, errors.New("No reports returned")) }
return 0, err
} type containerWaitFn func(conditions ...define.ContainerStatus) (int32, error)
return report[0].ExitCode, report[0].Error
func createContainerWaitFn(ctx context.Context, containerName string, interval time.Duration) containerWaitFn {
runtime := ctx.Value("runtime").(*libpod.Runtime)
var containerEngine entities.ContainerEngine = &abi.ContainerEngine{Libpod: runtime}
return func(conditions ...define.ContainerStatus) (int32, error) {
opts := entities.WaitOptions{
Condition: conditions,
Interval: interval,
}
ctrWaitReport, err := containerEngine.ContainerWait(ctx, []string{containerName}, opts)
if err != nil {
return -1, err
}
if len(ctrWaitReport) != 1 {
return -1, fmt.Errorf("the ContainerWait() function returned unexpected count of reports: %d", len(ctrWaitReport))
}
return ctrWaitReport[0].ExitCode, ctrWaitReport[0].Error
}
}
func isValidDockerCondition(cond string) bool {
switch cond {
case "next-exit", "removed", "not-running", "":
return true
}
return false
}
func waitDockerCondition(ctx context.Context, containerName string, interval time.Duration, dockerCondition string) (int32, error) {
containerWait := createContainerWaitFn(ctx, containerName, interval)
var err error
var code int32
switch dockerCondition {
case "next-exit":
code, err = waitNextExit(containerWait)
case "removed":
code, err = waitRemoved(containerWait)
case "not-running", "":
code, err = waitNotRunning(containerWait)
default:
panic("not a valid docker condition")
}
return code, err
}
var notRunningStates = []define.ContainerStatus{
define.ContainerStateCreated,
define.ContainerStateRemoving,
define.ContainerStateStopped,
define.ContainerStateExited,
define.ContainerStateConfigured,
}
func waitRemoved(ctrWait containerWaitFn) (int32, error) {
code, err := ctrWait(define.ContainerStateUnknown)
if err != nil && errors.Cause(err) == define.ErrNoSuchCtr {
return code, nil
} else {
return code, err
}
}
func waitNextExit(ctrWait containerWaitFn) (int32, error) {
_, err := ctrWait(define.ContainerStateRunning)
if err != nil {
return -1, err
}
return ctrWait(notRunningStates...)
}
func waitNotRunning(ctrWait containerWaitFn) (int32, error) {
return ctrWait(notRunningStates...)
}
func containerExists(ctx context.Context, name string) (bool, error) {
runtime := ctx.Value("runtime").(*libpod.Runtime)
var containerEngine entities.ContainerEngine = &abi.ContainerEngine{Libpod: runtime}
var ctrExistsOpts entities.ContainerExistsOptions
ctrExistRep, err := containerEngine.ContainerExists(ctx, name, ctrExistsOpts)
if err != nil {
return false, err
}
return ctrExistRep.Value, nil
} }

View File

@ -176,7 +176,7 @@ type UnpauseOptions struct{}
//go:generate go run ../generator/generator.go WaitOptions //go:generate go run ../generator/generator.go WaitOptions
// WaitOptions are optional options for waiting on containers // WaitOptions are optional options for waiting on containers
type WaitOptions struct { type WaitOptions struct {
Condition *define.ContainerStatus Condition []define.ContainerStatus
Interval *string Interval *string
} }

View File

@ -76,19 +76,19 @@ func (o *WaitOptions) ToParams() (url.Values, error) {
} }
// WithCondition // WithCondition
func (o *WaitOptions) WithCondition(value define.ContainerStatus) *WaitOptions { func (o *WaitOptions) WithCondition(value []define.ContainerStatus) *WaitOptions {
v := &value v := value
o.Condition = v o.Condition = v
return o return o
} }
// GetCondition // GetCondition
func (o *WaitOptions) GetCondition() define.ContainerStatus { func (o *WaitOptions) GetCondition() []define.ContainerStatus {
var condition define.ContainerStatus var condition []define.ContainerStatus
if o.Condition == nil { if o.Condition == nil {
return condition return condition
} }
return *o.Condition return o.Condition
} }
// WithInterval // WithInterval

View File

@ -75,7 +75,7 @@ var _ = Describe("Podman containers attach", func() {
Expect(err).ShouldNot(HaveOccurred()) Expect(err).ShouldNot(HaveOccurred())
wait := define.ContainerStateRunning wait := define.ContainerStateRunning
_, err = containers.Wait(bt.conn, ctnr.ID, new(containers.WaitOptions).WithCondition(wait)) _, err = containers.Wait(bt.conn, ctnr.ID, new(containers.WaitOptions).WithCondition([]define.ContainerStatus{wait}))
Expect(err).ShouldNot(HaveOccurred()) Expect(err).ShouldNot(HaveOccurred())
tickTock := time.NewTimer(2 * time.Second) tickTock := time.NewTimer(2 * time.Second)

View File

@ -207,7 +207,7 @@ func (b *bindingTest) RunTopContainer(containerName *string, insidePod *bool, po
return "", err return "", err
} }
wait := define.ContainerStateRunning wait := define.ContainerStateRunning
_, err = containers.Wait(b.conn, ctr.ID, new(containers.WaitOptions).WithCondition(wait)) _, err = containers.Wait(b.conn, ctr.ID, new(containers.WaitOptions).WithCondition([]define.ContainerStatus{wait}))
return ctr.ID, err return ctr.ID, err
} }

View File

@ -281,7 +281,7 @@ var _ = Describe("Podman containers ", func() {
_, err := bt.RunTopContainer(&name, nil, nil) _, err := bt.RunTopContainer(&name, nil, nil)
Expect(err).To(BeNil()) Expect(err).To(BeNil())
go func() { go func() {
exitCode, err = containers.Wait(bt.conn, name, new(containers.WaitOptions).WithCondition(pause)) exitCode, err = containers.Wait(bt.conn, name, new(containers.WaitOptions).WithCondition([]define.ContainerStatus{pause}))
errChan <- err errChan <- err
close(errChan) close(errChan)
}() }()
@ -295,7 +295,7 @@ var _ = Describe("Podman containers ", func() {
go func() { go func() {
defer GinkgoRecover() defer GinkgoRecover()
_, waitErr := containers.Wait(bt.conn, name, new(containers.WaitOptions).WithCondition(running)) _, waitErr := containers.Wait(bt.conn, name, new(containers.WaitOptions).WithCondition([]define.ContainerStatus{running}))
unpauseErrChan <- waitErr unpauseErrChan <- waitErr
close(unpauseErrChan) close(unpauseErrChan)
}() }()

View File

@ -51,7 +51,7 @@ type ContainerRunlabelReport struct {
} }
type WaitOptions struct { type WaitOptions struct {
Condition define.ContainerStatus Condition []define.ContainerStatus
Interval time.Duration Interval time.Duration
Latest bool Latest bool
} }

View File

@ -100,7 +100,7 @@ func (ic *ContainerEngine) ContainerWait(ctx context.Context, namesOrIds []strin
responses := make([]entities.WaitReport, 0, len(ctrs)) responses := make([]entities.WaitReport, 0, len(ctrs))
for _, c := range ctrs { for _, c := range ctrs {
response := entities.WaitReport{Id: c.ID()} response := entities.WaitReport{Id: c.ID()}
exitCode, err := c.WaitForConditionWithInterval(options.Interval, options.Condition) exitCode, err := c.WaitForConditionWithInterval(ctx, options.Interval, options.Condition...)
if err != nil { if err != nil {
response.Error = err response.Error = err
} else { } else {
@ -728,7 +728,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri
return reports, errors.Wrapf(err, "unable to start container %s", ctr.ID()) return reports, errors.Wrapf(err, "unable to start container %s", ctr.ID())
} }
if ecode, err := ctr.Wait(); err != nil { if ecode, err := ctr.Wait(ctx); err != nil {
if errors.Cause(err) == define.ErrNoSuchCtr { if errors.Cause(err) == define.ErrNoSuchCtr {
// Check events // Check events
event, err := ic.Libpod.GetLastContainerEvent(ctx, ctr.ID(), events.Exited) event, err := ic.Libpod.GetLastContainerEvent(ctx, ctr.ID(), events.Exited)
@ -867,7 +867,7 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta
return &report, err return &report, err
} }
if ecode, err := ctr.Wait(); err != nil { if ecode, err := ctr.Wait(ctx); err != nil {
if errors.Cause(err) == define.ErrNoSuchCtr { if errors.Cause(err) == define.ErrNoSuchCtr {
// Check events // Check events
event, err := ic.Libpod.GetLastContainerEvent(ctx, ctr.ID(), events.Exited) event, err := ic.Libpod.GetLastContainerEvent(ctx, ctr.ID(), events.Exited)

View File

@ -0,0 +1,47 @@
# -*- sh -*-
#
# test more container-related endpoints
#
red='\e[31m'
nc='\e[0m'
podman pull "${IMAGE}" &>/dev/null
# Ensure clean slate
podman rm -a -f &>/dev/null
CTR="WaitTestingCtr"
t POST "containers/nonExistent/wait?condition=next-exit" '' 404
podman create --name "${CTR}" --entrypoint '["sleep", "0.5"]' "${IMAGE}"
t POST "containers/${CTR}/wait?condition=non-existent-cond" '' 400
t POST "containers/${CTR}/wait?condition=not-running" '' 200
t POST "containers/${CTR}/wait?condition=next-exit" '' 200 &
child_pid=$!
podman start "${CTR}"
wait "${child_pid}"
# check if headers are sent in advance before body
WAIT_TEST_ERROR=""
curl -I -X POST "http://$HOST:$PORT/containers/${CTR}/wait?condition=next-exit" &> "/dev/null" &
child_pid=$!
sleep 0.5
if kill -2 "${child_pid}" 2> "/dev/null"; then
echo -e "${red}NOK: Failed to get response headers immediately.${nc}" 1>&2;
WAIT_TEST_ERROR="1"
fi
t POST "containers/${CTR}/wait?condition=removed" '' 200 &
child_pid=$!
podman container rm "${CTR}"
wait "${child_pid}"
if [[ "${WAIT_TEST_ERROR}" ]] ; then
exit 1;
fi

View File

@ -441,7 +441,7 @@ func (p *PodmanTestIntegration) BuildImage(dockerfile, imageName string, layers
err := ioutil.WriteFile(dockerfilePath, []byte(dockerfile), 0755) err := ioutil.WriteFile(dockerfilePath, []byte(dockerfile), 0755)
Expect(err).To(BeNil()) Expect(err).To(BeNil())
session := p.Podman([]string{"build", "--layers=" + layers, "-t", imageName, "--file", dockerfilePath, p.TempDir}) session := p.Podman([]string{"build", "--layers=" + layers, "-t", imageName, "--file", dockerfilePath, p.TempDir})
session.Wait(120) session.Wait(240)
Expect(session).Should(Exit(0), fmt.Sprintf("BuildImage session output: %q", session.OutputToString())) Expect(session).Should(Exit(0), fmt.Sprintf("BuildImage session output: %q", session.OutputToString()))
} }

View File

@ -34,7 +34,7 @@ var _ = Describe("Podman wait", func() {
It("podman wait on bogus container", func() { It("podman wait on bogus container", func() {
session := podmanTest.Podman([]string{"wait", "1234"}) session := podmanTest.Podman([]string{"wait", "1234"})
session.Wait() session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(125)) Expect(session.ExitCode()).To(Equal(125))
}) })
@ -45,7 +45,7 @@ var _ = Describe("Podman wait", func() {
cid := session.OutputToString() cid := session.OutputToString()
Expect(session.ExitCode()).To(Equal(0)) Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.Podman([]string{"wait", cid}) session = podmanTest.Podman([]string{"wait", cid})
session.Wait() session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0)) Expect(session.ExitCode()).To(Equal(0))
}) })