Skip to content

Commit

Permalink
add wait client command for pouch
Browse files Browse the repository at this point in the history
add wait client command for pouch
  • Loading branch information
xiechengsheng committed May 19, 2018
1 parent 33e585d commit 3a9bfa6
Show file tree
Hide file tree
Showing 14 changed files with 406 additions and 0 deletions.
14 changes: 14 additions & 0 deletions apis/server/container_bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,3 +383,17 @@ func (s *Server) removeContainers(ctx context.Context, rw http.ResponseWriter, r
rw.WriteHeader(http.StatusNoContent)
return nil
}

func (s *Server) waitContainer(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
name := mux.Vars(req)["name"]

status, err := s.ContainerMgr.Wait(ctx, name, -1*time.Second)

if err != nil {
return err
}

return EncodeResponse(rw, http.StatusOK, &types.ContainerWaitOKBody{
StatusCode: status,
})
}
1 change: 1 addition & 0 deletions apis/server/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ func initRoute(s *Server) http.Handler {
s.addRoute(r, http.MethodGet, "/containers/{name:.*}/logs", withCancelHandler(s.logsContainer))
s.addRoute(r, http.MethodPost, "/containers/{name:.*}/resize", s.resizeContainer)
s.addRoute(r, http.MethodPost, "/containers/{name:.*}/restart", s.restartContainer)
s.addRoute(r, http.MethodPost, "/containers/{name:.*}/wait", s.waitContainer)

// image
s.addRoute(r, http.MethodPost, "/images/create", s.pullImage)
Expand Down
24 changes: 24 additions & 0 deletions apis/swagger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,29 @@ paths:
$ref: "#/responses/500ErrorResponse"
tags: ["Container"]

/containers/{id}/wait:
post:
summary: "Block until a container stops, then returns the exit code."
operationId: "ContainerWait"
parameters:
- $ref: "#/parameters/id"
responses:
200:
description: "The container has exited."
schema:
type: "object"
required: [StatusCode]
properties:
StatusCode:
description: "Exit code of the container"
type: "integer"
x-nullable: false
404:
$ref: "#/responses/404ErrorResponse"
500:
$ref: "#/responses/500ErrorResponse"
tags: ["Container"]

/containers/{id}:
delete:
summary: "Remove one container"
Expand Down Expand Up @@ -3428,3 +3451,4 @@ responses:
description: An unexpected server error occurred.
schema:
$ref: "#/definitions/Error"

65 changes: 65 additions & 0 deletions apis/types/container_wait_okbody.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ func main() {
cli.AddCommand(base, &TopCommand{})
cli.AddCommand(base, &LogsCommand{})
cli.AddCommand(base, &RemountLxcfsCommand{})
cli.AddCommand(base, &WaitCommand{})

// add generate doc command
cli.AddCommand(base, &GenDocCommand{})
Expand Down
70 changes: 70 additions & 0 deletions cli/wait.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package main

import (
"context"
"errors"
"fmt"
"strings"

"github.com/spf13/cobra"
)

// waitDescription is used to describe wait command in detail and auto generate command doc.
var waitDescription = "Block until one or more containers stop, then print their exit codes. " +
"If container state is already stopped, the command will return exit code immediately. " +
"On a successful stop, the exit code of the container is returned. "

// WaitCommand is used to implement 'wait' command.
type WaitCommand struct {
baseCommand
}

// Init initialize wait command.
func (wait *WaitCommand) Init(c *Cli) {
wait.cli = c
wait.cmd = &cobra.Command{
Use: "wait CONTAINER [CONTAINER...]",
Short: "Block until one or more containers stop, then print their exit codes",
Long: waitDescription,
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return wait.runWait(args)
},
Example: waitExamples(),
}
}

// runWait is the entry of wait command.
func (wait *WaitCommand) runWait(args []string) error {
ctx := context.Background()
apiClient := wait.cli.Client()

var errs []string
for _, name := range args {
status, err := apiClient.ContainerWait(ctx, name)
if err != nil {
errs = append(errs, err.Error())
continue
}
fmt.Printf("%d\n", status)
}

if len(errs) > 0 {
return errors.New(strings.Join(errs, "\n"))
}

return nil
}

// waitExamples shows examples in wait command, and is used in auto-generated cli docs.
func waitExamples() string {
return `$ pouch ps
Name ID Status Created Image Runtime
foo f6717e Up 2 seconds 3 seconds ago registry.hub.docker.com/library/busybox:latest runc
$ pouch stop foo
$ pouch ps -a
Name ID Status Created Image Runtime
foo f6717e Stopped (0) 1 minute 2 minutes ago registry.hub.docker.com/library/busybox:latest runc
$ pouch wait foo
0`
}
21 changes: 21 additions & 0 deletions client/container_wait.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package client

import (
"context"

"github.com/alibaba/pouch/apis/types"
)

// ContainerWait pauses execution until a container exits.
// It returns the API status code as response of its readiness.
func (client *APIClient) ContainerWait(ctx context.Context, name string) (int64, error) {
resp, err := client.post(ctx, "/containers/"+name+"/wait", nil, nil, nil)
if err != nil {
return -1, err
}

var response types.ContainerWaitOKBody
err = decodeBody(&response, resp.Body)
ensureCloseReader(resp)
return response.StatusCode, err
}
1 change: 1 addition & 0 deletions client/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type ContainerAPIClient interface {
ContainerTop(ctx context.Context, name string, arguments []string) (types.ContainerProcessList, error)
ContainerLogs(ctx context.Context, name string, options types.ContainerLogsOptions) (io.ReadCloser, error)
ContainerResize(ctx context.Context, name, height, width string) error
ContainerWait(ctx context.Context, name string) (int64, error)
}

// ImageAPIClient defines methods of Image client.
Expand Down
24 changes: 24 additions & 0 deletions ctrd/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -500,3 +500,27 @@ func (c *Client) ResizeContainer(ctx context.Context, id string, opts types.Resi

return pack.task.Resize(ctx, uint32(opts.Height), uint32(opts.Width))
}

// WaitContainer waits until container's status is stopped.
func (c *Client) WaitContainer(ctx context.Context, id string, timeout time.Duration) (int64, error) {
wrapperCli, err := c.Get(ctx)
if err != nil {
return -1, fmt.Errorf("failed to get a containerd grpc client: %v", err)
}

ctx = leases.WithLease(ctx, wrapperCli.lease.ID())

waitExit := func() *Message {
return c.ProbeContainer(ctx, id, timeout)
}

var msg *Message
// wait for the task to exit.
msg = waitExit()

if err := msg.RawError(); err != nil && errtypes.IsTimeout(err) {
return -1, err
}

return int64(msg.ExitCode()), nil
}
2 changes: 2 additions & 0 deletions ctrd/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ type ContainerAPIClient interface {
// ResizeContainer changes the size of the TTY of the init process running
// in the container to the given height and width.
ResizeContainer(ctx context.Context, id string, opts types.ResizeOptions) error
// WaitContainer waits until container's status is stopped.
WaitContainer(ctx context.Context, id string, timeout time.Duration) (int64, error)
// UpdateResources updates the configurations of a container.
UpdateResources(ctx context.Context, id string, resources types.Resources) error
// SetExitHooks specified the handlers of container exit.
Expand Down
20 changes: 20 additions & 0 deletions daemon/mgr/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ type ContainerMgr interface {
// Remove removes a container, it may be running or stopped and so on.
Remove(ctx context.Context, name string, option *types.ContainerRemoveOptions) error

// Wait stops processing until the given container is stopped.
Wait(ctx context.Context, name string, timeout time.Duration) (int64, error)

// 2. The following five functions is related to container exec.

// CreateExec creates exec process's environment.
Expand Down Expand Up @@ -1265,6 +1268,23 @@ func (mgr *ContainerManager) Resize(ctx context.Context, name string, opts types
return mgr.Client.ResizeContainer(ctx, c.ID, opts)
}

// Wait stops processing until the given container is stopped.
func (mgr *ContainerManager) Wait(ctx context.Context, name string, timeout time.Duration) (int64, error) {
c, err := mgr.container(name)
if err != nil {
return -1, err
}

// We should notice that container's meta data shouldn't be locked in wait process, otherwise waiting for
// a running container to stop would make other client commands which manage this container are blocked.
// If a container status is exited or stopped, return exit code immediately.
if c.IsExited() || c.IsStopped() {
return c.ExitCode(), nil
}

return mgr.Client.WaitContainer(ctx, c.ID, timeout)
}

// Connect is used to connect a container to a network.
func (mgr *ContainerManager) Connect(ctx context.Context, name string, networkIDOrName string, epConfig *types.EndpointSettings) error {
c, err := mgr.container(name)
Expand Down
5 changes: 5 additions & 0 deletions daemon/mgr/container_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,11 @@ func (c *Container) Key() string {
return c.ID
}

// ExitCode returns container's ExitCode.
func (c *Container) ExitCode() int64 {
return c.State.ExitCode
}

// IsRunning returns container is running or not.
func (c *Container) IsRunning() bool {
return c.State.Status == types.StatusRunning
Expand Down
44 changes: 44 additions & 0 deletions test/api_container_wait_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package main

import (
"github.com/alibaba/pouch/test/environment"
"github.com/alibaba/pouch/test/request"

"github.com/go-check/check"
)

// APIContainerWaitSuite is the test suite for container wait API.
type APIContainerWaitSuite struct{}

func init() {
check.Suite(&APIContainerWaitSuite{})
}

// SetUpTest does common setup in the beginning of each test.
func (suite *APIContainerWaitSuite) SetUpTest(c *check.C) {
SkipIfFalse(c, environment.IsLinux)
PullImage(c, busyboxImage)
}

// TestWaitOk tests waiting a stopped container return 200.
func (suite *APIContainerWaitSuite) TestWaitOk(c *check.C) {
cname := "TestWaitOk"

CreateBusyboxContainerOk(c, cname)
StartContainerOk(c, cname)
StopContainerOk(c, cname)

resp, err := request.Post("/containers/" + cname + "/wait")
c.Assert(err, check.IsNil)
CheckRespStatus(c, resp, 200)

DelContainerForceOk(c, cname)
}

// TestNonExistingContainer tests waiting a non-existing container return 404.
func (suite *APIContainerWaitSuite) TestNonExistingContainer(c *check.C) {
cname := "TestNonExistingContainer"
resp, err := request.Post("/containers/" + cname + "/wait")
c.Assert(err, check.IsNil)
CheckRespStatus(c, resp, 404)
}
Loading

0 comments on commit 3a9bfa6

Please sign in to comment.