mirror of
https://github.com/containers/podman.git
synced 2025-06-21 01:19:15 +08:00
Merge pull request #9048 from matejvasek/apiv2_wait
Fix Docker APIv2 container wait endpoint
This commit is contained in:
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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")
|
||||||
)
|
)
|
||||||
|
@ -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) {
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}()
|
}()
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
47
test/apiv2/26-containersWait.at
Normal file
47
test/apiv2/26-containersWait.at
Normal 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
|
@ -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()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user