Skip to content

Commit

Permalink
add wait client command for pouch
Browse files Browse the repository at this point in the history
Signed-off-by: xiechengsheng <[email protected]>
  • Loading branch information
xiechengsheng committed May 21, 2018
1 parent 0d0d697 commit 70e58f1
Show file tree
Hide file tree
Showing 14 changed files with 456 additions and 0 deletions.
20 changes: 20 additions & 0 deletions apis/server/container_bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,3 +383,23 @@ 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)

if err != nil {
return err
}

errMsg := ""
if err != nil {
errMsg = err.Error()
}

return EncodeResponse(rw, http.StatusOK, &types.ContainerWaitOKBody{
Error: errMsg,
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", withCancelHandler(s.waitContainer))

// image
s.addRoute(r, http.MethodPost, "/images/create", s.pullImage)
Expand Down
26 changes: 26 additions & 0 deletions apis/swagger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,32 @@ 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
Error:
description: "The error message of waiting container"
type: "string"
404:
$ref: "#/responses/404ErrorResponse"
500:
$ref: "#/responses/500ErrorResponse"
tags: ["Container"]

/containers/{id}:
delete:
summary: "Remove one container"
Expand Down
68 changes: 68 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`
}
26 changes: 26 additions & 0 deletions client/container_wait.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package client

import (
"context"
"errors"

"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)

if response.Error == "" {
return response.StatusCode, nil
}
return response.StatusCode, errors.New(response.Error)
}
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) (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, -1*time.Second)
}

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) (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) (int64, error)

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

// CreateExec creates exec process's environment.
Expand Down Expand Up @@ -1299,6 +1302,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) (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)
}

// 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
Loading

0 comments on commit 70e58f1

Please sign in to comment.