diff options
26 files changed, 473 insertions, 134 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index eda03bf23..a23595712 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -433,7 +433,6 @@ apiv2_test_task: path: ./*.html type: text/html package_versions_script: '$SCRIPT_BASE/logcollector.sh packages' - ginkgo_node_logs_script: '$SCRIPT_BASE/logcollector.sh ginkgo' df_script: '$SCRIPT_BASE/logcollector.sh df' audit_log_script: '$SCRIPT_BASE/logcollector.sh audit' journal_script: '$SCRIPT_BASE/logcollector.sh journal' @@ -478,7 +477,11 @@ local_integration_test_task: &local_integration_test_task gopath_cache: *ro_gopath_cache setup_script: *setup main_script: *main - always: *logs_artifacts + always: &int_logs_artifacts + <<: *logs_artifacts + ginkgo_node_logs_artifacts: + path: ./test/e2e/ginkgo-node-*.log + type: text/plain # Nearly identical to `local_integration_test` except all operations @@ -521,7 +524,7 @@ container_integration_test_task: gopath_cache: *ro_gopath_cache setup_script: *setup main_script: *main - always: *logs_artifacts + always: *int_logs_artifacts # Execute most integration tests as a regular (non-root) user. @@ -542,7 +545,7 @@ rootless_integration_test_task: gopath_cache: *ro_gopath_cache setup_script: *setup main_script: *main - always: *logs_artifacts + always: *int_logs_artifacts # Always run subsequent to integration tests. While parallelism is lost diff --git a/cmd/podman/containers/wait.go b/cmd/podman/containers/wait.go index 14d660678..7a531b98a 100644 --- a/cmd/podman/containers/wait.go +++ b/cmd/podman/containers/wait.go @@ -95,10 +95,11 @@ func wait(cmd *cobra.Command, args []string) error { return errors.New("--latest and containers cannot be used together") } - waitOptions.Condition, err = define.StringToContainerStatus(waitCondition) + cond, err := define.StringToContainerStatus(waitCondition) if err != nil { return err } + waitOptions.Condition = []define.ContainerStatus{cond} responses, err := registry.ContainerEngine().ContainerWait(context.Background(), args, waitOptions) if err != nil { diff --git a/contrib/cirrus/logcollector.sh b/contrib/cirrus/logcollector.sh index 323015cef..38a15ded1 100755 --- a/contrib/cirrus/logcollector.sh +++ b/contrib/cirrus/logcollector.sh @@ -31,7 +31,6 @@ case $1 in esac ;; df) showrun df -lhTx tmpfs ;; - ginkgo) showrun cat $CIRRUS_WORKING_DIR/test/e2e/ginkgo-node-*.log ;; journal) showrun journalctl -b ;; podman) showrun ./bin/podman system info ;; server) 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) diff --git a/nix/nixpkgs.json b/nix/nixpkgs.json index 0cfb251f2..459fb28f8 100644 --- a/nix/nixpkgs.json +++ b/nix/nixpkgs.json @@ -1,9 +1,9 @@ { "url": "https://github.com/nixos/nixpkgs", - "rev": "ce7b327a52d1b82f82ae061754545b1c54b06c66", - "date": "2021-01-25T11:28:05+01:00", - "path": "/nix/store/dpsa6a1sy8hwhwjkklc52brs9z1k5fx9-nixpkgs", - "sha256": "1rc4if8nmy9lrig0ddihdwpzg2s8y36vf20hfywb8hph5hpsg4vj", + "rev": "30c2fb65feaf1068b1c413a0b75470afd351c291", + "date": "2021-01-28T21:27:34-05:00", + "path": "/nix/store/zk71rlw37vg9hqc5j0vqi9x8qzb2ir0m-nixpkgs", + "sha256": "0b1y1lgzbagpgh9cvi9szkm162laifz0q2ss4pibns3j3gqpf5gl", "fetchSubmodules": false, "deepClone": false, "leaveDotGit": false diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go index 86508f938..9c0893a80 100644 --- a/pkg/api/handlers/compat/containers.go +++ b/pkg/api/handlers/compat/containers.go @@ -23,10 +23,8 @@ import ( "github.com/docker/docker/api/types/container" "github.com/docker/go-connections/nat" "github.com/docker/go-units" - "github.com/gorilla/mux" "github.com/gorilla/schema" "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) func RemoveContainer(w http.ResponseWriter, r *http.Request) { @@ -233,8 +231,11 @@ func KillContainer(w http.ResponseWriter, r *http.Request) { return } 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) return } @@ -245,26 +246,8 @@ func KillContainer(w http.ResponseWriter, r *http.Request) { } func WaitContainer(w http.ResponseWriter, r *http.Request) { - var msg string // /{version}/containers/(name)/wait - exitCode, err := utils.WaitContainer(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, - }, - }) + utils.WaitContainerDocker(w, r) } func LibpodToContainer(l *libpod.Container, sz bool) (*handlers.Container, error) { diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go index f6e348cef..619cbfd8b 100644 --- a/pkg/api/handlers/libpod/containers.go +++ b/pkg/api/handlers/libpod/containers.go @@ -4,7 +4,6 @@ import ( "io/ioutil" "net/http" "os" - "strconv" "github.com/containers/podman/v2/libpod" "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) { - exitCode, err := utils.WaitContainer(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))) + utils.WaitContainerLibpod(w, r) } func UnmountContainer(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/api/handlers/utils/containers.go b/pkg/api/handlers/utils/containers.go index fac237f87..518309a03 100644 --- a/pkg/api/handlers/utils/containers.go +++ b/pkg/api/handlers/utils/containers.go @@ -1,67 +1,230 @@ package utils import ( + "context" + "fmt" "net/http" + "strconv" "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/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/pkg/errors" ) -func WaitContainer(w http.ResponseWriter, r *http.Request) (int32, error) { +type waitQueryDocker struct { + Condition string `schema:"condition"` +} + +type waitQueryLibpod struct { + Interval string `schema:"interval"` + Condition []define.ContainerStatus `schema:"condition"` +} + +func WaitContainerDocker(w http.ResponseWriter, r *http.Request) { + var err error + ctx := r.Context() + + query := waitQueryDocker{} + + 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.Duration + err error + interval = time.Millisecond * 250 + conditions = []define.ContainerStatus{define.ContainerStateStopped, define.ContainerStateExited} ) - runtime := r.Context().Value("runtime").(*libpod.Runtime) - // Now use the ABI implementation to prevent us from having duplicate - // code. - containerEngine := abi.ContainerEngine{Libpod: runtime} decoder := r.Context().Value("decoder").(*schema.Decoder) - query := struct { - Interval string `schema:"interval"` - Condition define.ContainerStatus `schema:"condition"` - }{ - // Override golang default values for types - } + query := waitQueryLibpod{} 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 0, err - } - options := entities.WaitOptions{ - Condition: define.ContainerStateStopped, } - name := GetName(r) + if _, found := r.URL.Query()["interval"]; found { interval, err = time.ParseDuration(query.Interval) if err != nil { InternalServerError(w, err) - return 0, err + return } - } else { - interval, err = time.ParseDuration("250ms") - if err != nil { + } + + if _, found := r.URL.Query()["condition"]; found { + if len(query.Condition) > 0 { + conditions = query.Condition + } + } + + name := GetName(r) + + waitFn := createContainerWaitFn(r.Context(), name, interval) + + exitCode, err := waitFn(conditions...) + if err != nil { + if errors.Cause(err) == define.ErrNoSuchCtr { + ContainerNotFound(w, name, err) + return + } else { InternalServerError(w, err) - return 0, err + return } } - options.Interval = interval + WriteResponse(w, http.StatusOK, strconv.Itoa(int(exitCode))) +} - if _, found := r.URL.Query()["condition"]; found { - options.Condition = query.Condition +type containerWaitFn func(conditions ...define.ContainerStatus) (int32, 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 } +} - report, err := containerEngine.ContainerWait(r.Context(), []string{name}, options) +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 0, err + return -1, err } - if len(report) == 0 { - InternalServerError(w, errors.New("No reports returned")) - return 0, 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 report[0].ExitCode, report[0].Error + return ctrExistRep.Value, nil } diff --git a/pkg/api/handlers/utils/handler.go b/pkg/api/handlers/utils/handler.go index ebbe7f24f..b3c674788 100644 --- a/pkg/api/handlers/utils/handler.go +++ b/pkg/api/handlers/utils/handler.go @@ -44,8 +44,8 @@ var ( // clients to shop for the Version they wish to support APIVersion = map[VersionTree]map[VersionLevel]semver.Version{ LibpodTree: { - CurrentAPIVersion: semver.MustParse("2.0.0"), - MinimalAPIVersion: semver.MustParse("2.0.0"), + CurrentAPIVersion: semver.MustParse("3.0.0"), + MinimalAPIVersion: semver.MustParse("3.0.0"), }, CompatTree: { CurrentAPIVersion: semver.MustParse("1.40.0"), diff --git a/pkg/bindings/containers/types.go b/pkg/bindings/containers/types.go index 771cde72c..4889b444a 100644 --- a/pkg/bindings/containers/types.go +++ b/pkg/bindings/containers/types.go @@ -176,7 +176,7 @@ type UnpauseOptions struct{} //go:generate go run ../generator/generator.go WaitOptions // WaitOptions are optional options for waiting on containers type WaitOptions struct { - Condition *define.ContainerStatus + Condition []define.ContainerStatus Interval *string } diff --git a/pkg/bindings/containers/types_wait_options.go b/pkg/bindings/containers/types_wait_options.go index 005cc38cb..a3f1e3b8c 100644 --- a/pkg/bindings/containers/types_wait_options.go +++ b/pkg/bindings/containers/types_wait_options.go @@ -76,19 +76,19 @@ func (o *WaitOptions) ToParams() (url.Values, error) { } // WithCondition -func (o *WaitOptions) WithCondition(value define.ContainerStatus) *WaitOptions { - v := &value +func (o *WaitOptions) WithCondition(value []define.ContainerStatus) *WaitOptions { + v := value o.Condition = v return o } // GetCondition -func (o *WaitOptions) GetCondition() define.ContainerStatus { - var condition define.ContainerStatus +func (o *WaitOptions) GetCondition() []define.ContainerStatus { + var condition []define.ContainerStatus if o.Condition == nil { return condition } - return *o.Condition + return o.Condition } // WithInterval diff --git a/pkg/bindings/test/attach_test.go b/pkg/bindings/test/attach_test.go index 9a46f6309..771b2d528 100644 --- a/pkg/bindings/test/attach_test.go +++ b/pkg/bindings/test/attach_test.go @@ -75,7 +75,7 @@ var _ = Describe("Podman containers attach", func() { Expect(err).ShouldNot(HaveOccurred()) 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()) tickTock := time.NewTimer(2 * time.Second) diff --git a/pkg/bindings/test/common_test.go b/pkg/bindings/test/common_test.go index c2b1347d2..8fbc631d8 100644 --- a/pkg/bindings/test/common_test.go +++ b/pkg/bindings/test/common_test.go @@ -207,7 +207,7 @@ func (b *bindingTest) RunTopContainer(containerName *string, insidePod *bool, po return "", err } 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 } diff --git a/pkg/bindings/test/containers_test.go b/pkg/bindings/test/containers_test.go index 9b9f98047..14eb1ffc6 100644 --- a/pkg/bindings/test/containers_test.go +++ b/pkg/bindings/test/containers_test.go @@ -281,7 +281,7 @@ var _ = Describe("Podman containers ", func() { _, err := bt.RunTopContainer(&name, nil, nil) Expect(err).To(BeNil()) 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 close(errChan) }() @@ -295,7 +295,7 @@ var _ = Describe("Podman containers ", func() { go func() { 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 close(unpauseErrChan) }() diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index 63be5578f..2d50d6826 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -51,7 +51,7 @@ type ContainerRunlabelReport struct { } type WaitOptions struct { - Condition define.ContainerStatus + Condition []define.ContainerStatus Interval time.Duration Latest bool } diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index d0599a595..7a672d863 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -100,7 +100,7 @@ func (ic *ContainerEngine) ContainerWait(ctx context.Context, namesOrIds []strin responses := make([]entities.WaitReport, 0, len(ctrs)) for _, c := range ctrs { 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 { response.Error = err } 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()) } - if ecode, err := ctr.Wait(); err != nil { + if ecode, err := ctr.Wait(ctx); err != nil { if errors.Cause(err) == define.ErrNoSuchCtr { // Check events 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 } - if ecode, err := ctr.Wait(); err != nil { + if ecode, err := ctr.Wait(ctx); err != nil { if errors.Cause(err) == define.ErrNoSuchCtr { // Check events event, err := ic.Libpod.GetLastContainerEvent(ctx, ctr.ID(), events.Exited) diff --git a/test/apiv2/01-basic.at b/test/apiv2/01-basic.at index f550d5fc3..1ddf49c6f 100644 --- a/test/apiv2/01-basic.at +++ b/test/apiv2/01-basic.at @@ -18,8 +18,8 @@ t HEAD libpod/_ping 200 for i in /version version; do t GET $i 200 \ .Components[0].Name="Podman Engine" \ - .Components[0].Details.APIVersion=2.0.0 \ - .Components[0].Details.MinAPIVersion=2.0.0 \ + .Components[0].Details.APIVersion=3.0.0 \ + .Components[0].Details.MinAPIVersion=3.0.0 \ .Components[0].Details.Os=linux \ .ApiVersion=1.40 \ .MinAPIVersion=1.24 \ diff --git a/test/apiv2/26-containersWait.at b/test/apiv2/26-containersWait.at new file mode 100644 index 000000000..3f530c3f0 --- /dev/null +++ b/test/apiv2/26-containersWait.at @@ -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 diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go index 61c0cb4fe..54d801e12 100644 --- a/test/e2e/common_test.go +++ b/test/e2e/common_test.go @@ -441,7 +441,7 @@ func (p *PodmanTestIntegration) BuildImage(dockerfile, imageName string, layers err := ioutil.WriteFile(dockerfilePath, []byte(dockerfile), 0755) Expect(err).To(BeNil()) 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())) } diff --git a/test/e2e/generate_kube_test.go b/test/e2e/generate_kube_test.go index dba366a1e..bcfab0f68 100644 --- a/test/e2e/generate_kube_test.go +++ b/test/e2e/generate_kube_test.go @@ -1,6 +1,7 @@ package integration import ( + "io/ioutil" "os" "path/filepath" "strconv" @@ -639,4 +640,63 @@ var _ = Describe("Podman generate kube", func() { Expect(pod.Spec.DNSConfig.Options[0].Name).To(Equal("color")) Expect(*pod.Spec.DNSConfig.Options[0].Value).To(Equal("blue")) }) + + It("podman generate kube - set entrypoint as command", func() { + session := podmanTest.Podman([]string{"create", "--pod", "new:testpod", "--entrypoint", "/bin/sleep", ALPINE, "10s"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + kube := podmanTest.Podman([]string{"generate", "kube", "testpod"}) + kube.WaitWithDefaultTimeout() + Expect(kube.ExitCode()).To(Equal(0)) + + // Now make sure that the container's command is set to the + // entrypoint and it's arguments to "10s". + pod := new(v1.Pod) + err := yaml.Unmarshal(kube.Out.Contents(), pod) + Expect(err).To(BeNil()) + + containers := pod.Spec.Containers + Expect(len(containers)).To(Equal(1)) + + Expect(containers[0].Command).To(Equal([]string{"/bin/sleep"})) + Expect(containers[0].Args).To(Equal([]string{"10s"})) + }) + + It("podman generate kube - use entrypoint from image", func() { + // Build an image with an entrypoint. + containerfile := `FROM quay.io/libpod/alpine:latest +ENTRYPOINT /bin/sleep` + + targetPath, err := CreateTempDirInTempDir() + Expect(err).To(BeNil()) + containerfilePath := filepath.Join(targetPath, "Containerfile") + err = ioutil.WriteFile(containerfilePath, []byte(containerfile), 0644) + Expect(err).To(BeNil()) + + image := "generatekube:test" + session := podmanTest.Podman([]string{"build", "-f", containerfilePath, "-t", image}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"create", "--pod", "new:testpod", image, "10s"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + kube := podmanTest.Podman([]string{"generate", "kube", "testpod"}) + kube.WaitWithDefaultTimeout() + Expect(kube.ExitCode()).To(Equal(0)) + + // Now make sure that the container's command is set to the + // entrypoint and it's arguments to "10s". + pod := new(v1.Pod) + err = yaml.Unmarshal(kube.Out.Contents(), pod) + Expect(err).To(BeNil()) + + containers := pod.Spec.Containers + Expect(len(containers)).To(Equal(1)) + + Expect(containers[0].Command).To(Equal([]string{"/bin/sh", "-c", "/bin/sleep"})) + Expect(containers[0].Args).To(Equal([]string{"10s"})) + }) }) diff --git a/test/e2e/network_connect_disconnect_test.go b/test/e2e/network_connect_disconnect_test.go index dd94bd7ca..cc23b10c1 100644 --- a/test/e2e/network_connect_disconnect_test.go +++ b/test/e2e/network_connect_disconnect_test.go @@ -74,6 +74,11 @@ var _ = Describe("Podman network connect and disconnect", func() { dis.WaitWithDefaultTimeout() Expect(dis.ExitCode()).To(BeZero()) + inspect := podmanTest.Podman([]string{"container", "inspect", "test", "--format", "{{len .NetworkSettings.Networks}}"}) + inspect.WaitWithDefaultTimeout() + Expect(inspect.ExitCode()).To(BeZero()) + Expect(inspect.OutputToString()).To(Equal("0")) + exec = podmanTest.Podman([]string{"exec", "-it", "test", "ip", "addr", "show", "eth0"}) exec.WaitWithDefaultTimeout() Expect(exec.ExitCode()).ToNot(BeZero()) @@ -146,6 +151,11 @@ var _ = Describe("Podman network connect and disconnect", func() { connect.WaitWithDefaultTimeout() Expect(connect.ExitCode()).To(BeZero()) + inspect := podmanTest.Podman([]string{"container", "inspect", "test", "--format", "{{len .NetworkSettings.Networks}}"}) + inspect.WaitWithDefaultTimeout() + Expect(inspect.ExitCode()).To(BeZero()) + Expect(inspect.OutputToString()).To(Equal("2")) + exec = podmanTest.Podman([]string{"exec", "-it", "test", "ip", "addr", "show", "eth1"}) exec.WaitWithDefaultTimeout() Expect(exec.ExitCode()).To(BeZero()) @@ -167,6 +177,11 @@ var _ = Describe("Podman network connect and disconnect", func() { dis.WaitWithDefaultTimeout() Expect(dis.ExitCode()).To(BeZero()) + inspect := podmanTest.Podman([]string{"container", "inspect", "test", "--format", "{{len .NetworkSettings.Networks}}"}) + inspect.WaitWithDefaultTimeout() + Expect(inspect.ExitCode()).To(BeZero()) + Expect(inspect.OutputToString()).To(Equal("2")) + start := podmanTest.Podman([]string{"start", "test"}) start.WaitWithDefaultTimeout() Expect(start.ExitCode()).To(BeZero()) @@ -202,6 +217,11 @@ var _ = Describe("Podman network connect and disconnect", func() { dis.WaitWithDefaultTimeout() Expect(dis.ExitCode()).To(BeZero()) + inspect := podmanTest.Podman([]string{"container", "inspect", "test", "--format", "{{len .NetworkSettings.Networks}}"}) + inspect.WaitWithDefaultTimeout() + Expect(inspect.ExitCode()).To(BeZero()) + Expect(inspect.OutputToString()).To(Equal("1")) + start := podmanTest.Podman([]string{"start", "test"}) start.WaitWithDefaultTimeout() Expect(start.ExitCode()).To(BeZero()) diff --git a/test/e2e/wait_test.go b/test/e2e/wait_test.go index aa8a1f245..4f1e74977 100644 --- a/test/e2e/wait_test.go +++ b/test/e2e/wait_test.go @@ -34,7 +34,7 @@ var _ = Describe("Podman wait", func() { It("podman wait on bogus container", func() { session := podmanTest.Podman([]string{"wait", "1234"}) - session.Wait() + session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(125)) }) @@ -45,7 +45,7 @@ var _ = Describe("Podman wait", func() { cid := session.OutputToString() Expect(session.ExitCode()).To(Equal(0)) session = podmanTest.Podman([]string{"wait", cid}) - session.Wait() + session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) }) |