rework apiv2 wait endpoint|binding

added the ability to wait on a condition (stopped, running, paused...) for a container.  if a condition is not provided, wait will default to the stopped condition which uses the original wait code paths.  if the condition is stopped, the container exit code will be returned.

also, correct a mux issue we discovered.

Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
Brent Baude
2020-02-27 10:59:53 -06:00
parent baf27fa25e
commit 0904873100
13 changed files with 161 additions and 60 deletions

View File

@ -629,6 +629,26 @@ func (c *Container) WaitWithInterval(waitTimeout time.Duration) (int32, error) {
}
}
func (c *Container) WaitForConditionWithInterval(waitTimeout time.Duration, condition define.ContainerStatus) (int32, error) {
if !c.valid {
return -1, define.ErrCtrRemoved
}
if condition == define.ContainerStateStopped || condition == define.ContainerStateExited {
return c.WaitWithInterval(waitTimeout)
}
for {
state, err := c.State()
if err != nil {
return -1, err
}
if state == condition {
break
}
time.Sleep(waitTimeout)
}
return -1, nil
}
// Cleanup unmounts all mount points in container and cleans up container storage
// It also cleans up the network stack
func (c *Container) Cleanup(ctx context.Context) error {

View File

@ -8,7 +8,6 @@ import (
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/gorilla/mux"
"github.com/gorilla/schema"
"github.com/pkg/errors"
)
@ -179,7 +178,7 @@ func RestartContainer(w http.ResponseWriter, r *http.Request) {
}
timeout := con.StopTimeout()
if _, found := mux.Vars(r)["t"]; found {
if _, found := r.URL.Query()["t"]; found {
timeout = uint(query.Timeout)
}

View File

@ -6,7 +6,6 @@ import (
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/gorilla/mux"
"github.com/gorilla/schema"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@ -30,12 +29,10 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) {
return
}
muxVars := mux.Vars(r)
// Detach keys: explicitly set to "" is very different from unset
// TODO: Our format for parsing these may be different from Docker.
var detachKeys *string
if _, found := muxVars["detachKeys"]; found {
if _, found := r.URL.Query()["detachKeys"]; found {
detachKeys = &query.DetachKeys
}
@ -44,15 +41,15 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) {
streams.Stderr = true
streams.Stdin = true
useStreams := false
if _, found := muxVars["stdin"]; found {
if _, found := r.URL.Query()["stdin"]; found {
streams.Stdin = query.Stdin
useStreams = true
}
if _, found := muxVars["stdout"]; found {
if _, found := r.URL.Query()["stdout"]; found {
streams.Stdout = query.Stdout
useStreams = true
}
if _, found := muxVars["stderr"]; found {
if _, found := r.URL.Query()["stderr"]; found {
streams.Stderr = query.Stderr
useStreams = true
}
@ -72,7 +69,7 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) {
return
}
// We only support stream=true or unset
if _, found := muxVars["stream"]; found && query.Stream {
if _, found := r.URL.Query()["stream"]; found && query.Stream {
utils.Error(w, "Unsupported parameter", http.StatusBadRequest, errors.Errorf("the stream parameter to attach is not presently supported"))
return
}

View File

@ -14,7 +14,6 @@ import (
"github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/util"
"github.com/gorilla/mux"
"github.com/gorilla/schema"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
@ -71,7 +70,7 @@ func ListContainers(w http.ResponseWriter, r *http.Request) {
utils.InternalServerError(w, err)
return
}
if _, found := mux.Vars(r)["limit"]; found {
if _, found := r.URL.Query()["limit"]; found {
last := query.Limit
if len(containers) > last {
containers = containers[len(containers)-last:]
@ -136,7 +135,7 @@ func WaitContainer(w http.ResponseWriter, r *http.Request) {
// /{version}/containers/(name)/wait
exitCode, err := utils.WaitContainer(w, r)
if err != nil {
msg = err.Error()
return
}
utils.WriteResponse(w, http.StatusOK, handlers.ContainerWaitOKBody{
StatusCode: int(exitCode),
@ -191,7 +190,7 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
}
var since time.Time
if _, found := mux.Vars(r)["since"]; found {
if _, found := r.URL.Query()["since"]; found {
since, err = util.ParseInputTime(query.Since)
if err != nil {
utils.BadRequest(w, "since", query.Since, err)
@ -200,7 +199,7 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
}
var until time.Time
if _, found := mux.Vars(r)["until"]; found {
if _, found := r.URL.Query()["until"]; found {
since, err = util.ParseInputTime(query.Until)
if err != nil {
utils.BadRequest(w, "until", query.Until, err)
@ -233,7 +232,7 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
var builder strings.Builder
for ok := true; ok; ok = query.Follow {
for line := range logChannel {
if _, found := mux.Vars(r)["until"]; found {
if _, found := r.URL.Query()["until"]; found {
if line.Time.After(until) {
break
}

View File

@ -11,7 +11,6 @@ import (
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/gorilla/mux"
"github.com/gorilla/schema"
"github.com/pkg/errors"
)
@ -87,8 +86,7 @@ func RemoveImage(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
return
}
muxVars := mux.Vars(r)
if _, found := muxVars["noprune"]; found {
if _, found := r.URL.Query()["noprune"]; found {
if query.noPrune {
utils.UnSupportedParameter("noprune")
}

View File

@ -17,7 +17,6 @@ import (
"github.com/containers/buildah/imagebuildah"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/storage/pkg/archive"
"github.com/gorilla/mux"
)
func BuildImage(w http.ResponseWriter, r *http.Request) {
@ -114,24 +113,24 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
tag = tokens[1]
}
if t, found := mux.Vars(r)["target"]; found {
name = t
if _, found := r.URL.Query()["target"]; found {
name = query.Target
}
var buildArgs = map[string]string{}
if a, found := mux.Vars(r)["buildargs"]; found {
if err := json.Unmarshal([]byte(a), &buildArgs); err != nil {
utils.BadRequest(w, "buildargs", a, err)
if _, found := r.URL.Query()["buildargs"]; found {
if err := json.Unmarshal([]byte(query.BuildArgs), &buildArgs); err != nil {
utils.BadRequest(w, "buildargs", query.BuildArgs, err)
return
}
}
// convert label formats
var labels = []string{}
if l, found := mux.Vars(r)["labels"]; found {
if _, found := r.URL.Query()["labels"]; found {
var m = map[string]string{}
if err := json.Unmarshal([]byte(l), &m); err != nil {
utils.BadRequest(w, "labels", l, err)
if err := json.Unmarshal([]byte(query.Labels), &m); err != nil {
utils.BadRequest(w, "labels", query.Labels, err)
return
}
@ -141,7 +140,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
}
pullPolicy := buildah.PullIfMissing
if _, found := mux.Vars(r)["pull"]; found {
if _, found := r.URL.Query()["pull"]; found {
if query.Pull {
pullPolicy = buildah.PullAlways
}

View File

@ -178,7 +178,6 @@ func KillContainer(w http.ResponseWriter, r *http.Request) {
func WaitContainer(w http.ResponseWriter, r *http.Request) {
exitCode, err := utils.WaitContainer(w, r)
if err != nil {
utils.InternalServerError(w, err)
return
}
utils.WriteResponse(w, http.StatusOK, strconv.Itoa(int(exitCode)))

View File

@ -13,7 +13,6 @@ import (
"github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/util"
"github.com/gorilla/mux"
"github.com/gorilla/schema"
"github.com/pkg/errors"
)
@ -353,7 +352,7 @@ func PodKill(w http.ResponseWriter, r *http.Request) {
signal = "SIGKILL"
)
query := struct {
signal string `schema:"signal"`
Signal string `schema:"signal"`
}{
// override any golang type defaults
}
@ -362,9 +361,8 @@ func PodKill(w http.ResponseWriter, r *http.Request) {
errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
return
}
muxVars := mux.Vars(r)
if _, found := muxVars["signal"]; found {
signal = query.signal
if _, found := r.URL.Query()["signal"]; found {
signal = query.Signal
}
sig, err := util.ParseSignal(signal)

View File

@ -78,9 +78,12 @@ func RemoveContainer(w http.ResponseWriter, r *http.Request, force, vols bool) {
}
func WaitContainer(w http.ResponseWriter, r *http.Request) (int32, error) {
var (
err error
interval time.Duration
)
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
// /{version}/containers/(name)/restart
query := struct {
Interval string `schema:"interval"`
Condition string `schema:"condition"`
@ -91,25 +94,34 @@ func WaitContainer(w http.ResponseWriter, r *http.Request) (int32, error) {
Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
return 0, err
}
if len(query.Condition) > 0 {
UnSupportedParameter("condition")
if _, found := r.URL.Query()["interval"]; found {
interval, err = time.ParseDuration(query.Interval)
if err != nil {
InternalServerError(w, err)
return 0, err
}
} else {
interval, err = time.ParseDuration("250ms")
if err != nil {
InternalServerError(w, err)
return 0, err
}
}
condition := define.ContainerStateStopped
if _, found := r.URL.Query()["condition"]; found {
condition, err = define.StringToContainerStatus(query.Condition)
if err != nil {
InternalServerError(w, err)
return 0, err
}
}
name := GetName(r)
con, err := runtime.LookupContainer(name)
if err != nil {
ContainerNotFound(w, name, err)
return 0, err
}
if len(query.Interval) > 0 {
d, err := time.ParseDuration(query.Interval)
if err != nil {
Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse %s for interval", query.Interval))
}
return con.WaitWithInterval(d)
}
return con.Wait()
return con.WaitForConditionWithInterval(interval, condition)
}
// GenerateFilterFuncsFromMap is used to generate un-executed functions that can be used to filter

View File

@ -6,7 +6,6 @@ import (
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/image"
"github.com/gorilla/mux"
"github.com/gorilla/schema"
)
@ -28,7 +27,7 @@ func GetImages(w http.ResponseWriter, r *http.Request) ([]*image.Image, error) {
return nil, err
}
var filters = []string{}
if _, found := mux.Vars(r)["digests"]; found && query.Digests {
if _, found := r.URL.Query()["digests"]; found && query.Digests {
UnSupportedParameter("digests")
}

View File

@ -436,8 +436,8 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// ---
// tags:
// - containers (compat)
// summary: Wait on a container to exit
// description: Block until a container stops, then returns the exit code.
// summary: Wait on a container
// description: Block until a container stops or given condition is met.
// parameters:
// - in: path
// name: name
@ -447,7 +447,14 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// - in: query
// name: condition
// type: string
// description: not supported
// description: |
// wait until container is to a given condition. default is stopped. valid conditions are:
// - configured
// - created
// - exited
// - paused
// - running
// - stopped
// produces:
// - application/json
// responses:
@ -1030,18 +1037,30 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// ---
// tags:
// - containers
// summary: Wait on a container to exit
// summary: Wait on a container
// description: Wait on a container to met a given condition
// parameters:
// - in: path
// name: name
// type: string
// required: true
// description: the name or ID of the container
// - in: query
// name: condition
// type: string
// description: |
// wait until container is to a given condition. default is stopped. valid conditions are:
// - configured
// - created
// - exited
// - paused
// - running
// - stopped
// produces:
// - application/json
// responses:
// 204:
// description: no error
// 200:
// $ref: "#/responses/ContainerWaitResponse"
// 404:
// $ref: "#/responses/NoSuchContainer"
// 500:

View File

@ -210,15 +210,20 @@ func Unpause(ctx context.Context, nameOrID string) error {
return response.Process(nil)
}
// Wait blocks until the given container exits and returns its exit code. The nameOrID can be a container name
// or a partial/full ID.
func Wait(ctx context.Context, nameOrID string) (int32, error) {
// Wait blocks until the given container reaches a condition. If not provided, the condition will
// default to stopped. If the condition is stopped, an exit code for the container will be provided. The
// nameOrID can be a container name or a partial/full ID.
func Wait(ctx context.Context, nameOrID string, condition *string) (int32, error) {
var exitCode int32
conn, err := bindings.GetClient(ctx)
if err != nil {
return exitCode, err
}
response, err := conn.DoRequest(nil, http.MethodPost, "containers/%s/wait", nil, nameOrID)
params := url.Values{}
if condition != nil {
params.Set("condition", *condition)
}
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/wait", params, nameOrID)
if err != nil {
return exitCode, err
}

View File

@ -250,4 +250,61 @@ var _ = Describe("Podman containers ", func() {
Expect(data.State.Status).To(Equal("exited"))
})
It("podman wait no condition", func() {
var (
name = "top"
exitCode int32 = -1
)
_, err := containers.Wait(connText, "foobar", nil)
Expect(err).ToNot(BeNil())
code, _ := bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusNotFound))
errChan := make(chan error)
bt.RunTopContainer(&name, nil, nil)
go func() {
exitCode, err = containers.Wait(connText, name, nil)
errChan <- err
close(errChan)
}()
err = containers.Stop(connText, name, nil)
Expect(err).To(BeNil())
wait := <-errChan
Expect(wait).To(BeNil())
Expect(exitCode).To(BeNumerically("==", 143))
})
It("podman wait to pause|unpause condition", func() {
var (
name = "top"
exitCode int32 = -1
pause = "paused"
unpause = "running"
)
errChan := make(chan error)
bt.RunTopContainer(&name, nil, nil)
go func() {
exitCode, err = containers.Wait(connText, name, &pause)
errChan <- err
close(errChan)
}()
err := containers.Pause(connText, name)
Expect(err).To(BeNil())
wait := <-errChan
Expect(wait).To(BeNil())
Expect(exitCode).To(BeNumerically("==", -1))
errChan = make(chan error)
go func() {
exitCode, err = containers.Wait(connText, name, &unpause)
errChan <- err
close(errChan)
}()
err = containers.Unpause(connText, name)
Expect(err).To(BeNil())
unPausewait := <-errChan
Expect(unPausewait).To(BeNil())
Expect(exitCode).To(BeNumerically("==", -1))
})
})