summaryrefslogtreecommitdiff
path: root/pkg/api/handlers
diff options
context:
space:
mode:
authorMatej Vasek <mvasek@redhat.com>2021-02-01 20:13:04 +0100
committerMatej Vasek <mvasek@redhat.com>2021-02-03 22:10:27 +0100
commit4a219aa234ff4ed3cd9d139ca88d8d5da6406493 (patch)
treed7011e94569f586babaf59304a5db041cf33fd9f /pkg/api/handlers
parentfc385806dfe1d13a7d4e4bdaeea93a22a55bd3d4 (diff)
downloadpodman-4a219aa234ff4ed3cd9d139ca88d8d5da6406493.tar.gz
podman-4a219aa234ff4ed3cd9d139ca88d8d5da6406493.tar.bz2
podman-4a219aa234ff4ed3cd9d139ca88d8d5da6406493.zip
Implement Docker wait conditions
Signed-off-by: Matej Vasek <mvasek@redhat.com>
Diffstat (limited to 'pkg/api/handlers')
-rw-r--r--pkg/api/handlers/compat/containers.go29
-rw-r--r--pkg/api/handlers/libpod/containers.go13
-rw-r--r--pkg/api/handlers/utils/containers.go231
3 files changed, 204 insertions, 69 deletions
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 7443f9b46..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.ContainerStatus{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
}