Skip to content

Commit

Permalink
Merge pull request #944 from HusterWan/zr/finish-restart-interface
Browse files Browse the repository at this point in the history
feature: finish restart interface
  • Loading branch information
allencloud authored Mar 23, 2018
2 parents 9dbb6de + 59fa836 commit 359ad41
Show file tree
Hide file tree
Showing 9 changed files with 203 additions and 10 deletions.
19 changes: 18 additions & 1 deletion apis/server/container_bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,24 @@ func (s *Server) renameContainer(ctx context.Context, rw http.ResponseWriter, re
}

func (s *Server) restartContainer(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
// TODO
var (
t int
err error
)

if v := req.FormValue("t"); v != "" {
if t, err = strconv.Atoi(v); err != nil {
return httputils.NewHTTPError(err, http.StatusBadRequest)
}
}

name := mux.Vars(req)["name"]

if err = s.ContainerMgr.Restart(ctx, name, int64(t)); err != nil {
return err
}

rw.WriteHeader(http.StatusNoContent)
return nil
}

Expand Down
1 change: 1 addition & 0 deletions apis/server/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func initRoute(s *Server) http.Handler {
r.Path(versionMatcher + "/containers/{name:.*}/top").Methods(http.MethodGet).Handler(s.filter(s.topContainer))
r.Path(versionMatcher + "/containers/{name:.*}/logs").Methods(http.MethodGet).Handler(s.filter(s.logsContainer))
r.Path(versionMatcher + "/containers/{name:.*}/resize").Methods(http.MethodPost).Handler(s.filter(s.resizeContainer))
r.Path(versionMatcher + "/containers/{name:.*}/restart").Methods(http.MethodPost).Handler(s.filter(s.restartContainer))

// image
r.Path(versionMatcher + "/images/create").Methods(http.MethodPost).Handler(s.filter(s.pullImage))
Expand Down
7 changes: 4 additions & 3 deletions cli/restart.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package main

import (
"context"
"strconv"

"fmt"

"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -36,8 +38,7 @@ func (rc *RestartCommand) Init(c *Cli) {
// addFlags adds flags for specific command.
func (rc *RestartCommand) addFlags() {
flagSet := rc.cmd.Flags()
flagSet.IntVarP(&rc.timeout, "time", "t", 10,
"Seconds to wait for stop before killing the container")
flagSet.IntVarP(&rc.timeout, "time", "t", 10, "Seconds to wait for stop before killing the container")
}

// runRestart is the entry of restart command.
Expand All @@ -46,7 +47,7 @@ func (rc *RestartCommand) runRestart(args []string) error {
apiClient := rc.cli.Client()

for _, name := range args {
if err := apiClient.ContainerRestart(ctx, name, rc.timeout); err != nil {
if err := apiClient.ContainerRestart(ctx, name, strconv.Itoa(rc.timeout)); err != nil {
return fmt.Errorf("failed to restart container: %v", err)
}
fmt.Printf("%s\n", name)
Expand Down
13 changes: 9 additions & 4 deletions client/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,15 @@ func (client *APIClient) ContainerRename(ctx context.Context, id string, name st
return err
}

// ContainerRestart restarts a contianer.
func (client *APIClient) ContainerRestart(ctx context.Context, name string, time int) error {
// TODO
return nil
// ContainerRestart restarts a running contianer.
func (client *APIClient) ContainerRestart(ctx context.Context, id string, timeout string) error {
q := url.Values{}
q.Add("t", timeout)

resp, err := client.post(ctx, "/containers/"+id+"/restart", q, nil, nil)
ensureCloseReader(resp)

return err
}

// ContainerPause pauses a container.
Expand Down
2 changes: 1 addition & 1 deletion client/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type ContainerAPIClient interface {
ContainerStartExec(ctx context.Context, execid string, config *types.ExecStartConfig) (net.Conn, *bufio.Reader, error)
ContainerGet(ctx context.Context, name string) (*types.ContainerJSON, error)
ContainerRename(ctx context.Context, id string, name string) error
ContainerRestart(ctx context.Context, name string, time int) error
ContainerRestart(ctx context.Context, name string, timeout string) error
ContainerPause(ctx context.Context, name string) error
ContainerUnpause(ctx context.Context, name string) error
ContainerUpdate(ctx context.Context, name string, config *types.UpdateConfig) error
Expand Down
6 changes: 6 additions & 0 deletions ctrd/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/containerd/containerd"
"github.com/containerd/containerd/cio"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/leases"
"github.com/containerd/containerd/linux/runctypes"
"github.com/containerd/containerd/oci"
"github.com/pkg/errors"
Expand Down Expand Up @@ -185,6 +186,11 @@ func (c *Client) RecoverContainer(ctx context.Context, id string, io *containeri

// DestroyContainer kill container and delete it.
func (c *Client) DestroyContainer(ctx context.Context, id string, timeout int64) (*Message, error) {
// TODO(ziren): if we just want to stop a container,
// we may need lease to lock the snapshot of container,
// in case, it be deleted by gc.
ctx = leases.WithLease(ctx, c.lease.ID())

if !c.lock.Trylock(id) {
return nil, errtypes.ErrLockfailed
}
Expand Down
41 changes: 40 additions & 1 deletion daemon/mgr/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ import (
"github.com/alibaba/pouch/pkg/meta"
"github.com/alibaba/pouch/pkg/randomid"
"github.com/alibaba/pouch/pkg/utils"
"github.com/containerd/containerd/namespaces"

"github.com/containerd/containerd/namespaces"
"github.com/go-openapi/strfmt"
"github.com/opencontainers/image-spec/specs-go/v1"
"github.com/opencontainers/runtime-spec/specs-go"
Expand Down Expand Up @@ -83,6 +83,9 @@ type ContainerMgr interface {

// Resize resizes the size of container tty.
Resize(ctx context.Context, name string, opts types.ResizeOptions) error

// Restart restart a running container.
Restart(ctx context.Context, name string, timeout int64) error
}

// ContainerManager is the default implement of interface ContainerMgr.
Expand Down Expand Up @@ -454,6 +457,10 @@ func (mgr *ContainerManager) Start(ctx context.Context, id, detachKeys string) (
c.Lock()
defer c.Unlock()

return mgr.start(ctx, c, detachKeys)
}

func (mgr *ContainerManager) start(ctx context.Context, c *Container, detachKeys string) error {
if c.meta.Config == nil || c.meta.State == nil {
return errors.Wrap(errtypes.ErrNotfound, "container "+c.ID())
}
Expand Down Expand Up @@ -604,6 +611,10 @@ func (mgr *ContainerManager) Stop(ctx context.Context, name string, timeout int6
timeout = c.StopTimeout()
}

return mgr.stop(ctx, c, timeout)
}

func (mgr *ContainerManager) stop(ctx context.Context, c *Container, timeout int64) error {
msg, err := mgr.Client.DestroyContainer(ctx, c.ID(), timeout)
if err != nil {
return errors.Wrapf(err, "failed to destroy container: %s", c.ID())
Expand Down Expand Up @@ -897,6 +908,34 @@ func (mgr *ContainerManager) Resize(ctx context.Context, name string, opts types
return mgr.Client.ResizeContainer(ctx, c.ID(), opts)
}

// Restart restarts a running container.
func (mgr *ContainerManager) Restart(ctx context.Context, name string, timeout int64) error {
c, err := mgr.container(name)
if err != nil {
return err
}

c.Lock()
defer c.Unlock()

if !c.IsRunning() {
return fmt.Errorf("cannot restart a non running container")
}

if timeout == 0 {
timeout = c.StopTimeout()
}

// stop container
if err := mgr.stop(ctx, c, timeout); err != nil {
return errors.Wrapf(err, "failed to stop container")
}
logrus.Debug("Restart: container " + c.ID() + " stopped succeeded")

// start container
return mgr.start(ctx, c, "")
}

func (mgr *ContainerManager) openContainerIO(id string, attach *AttachConfig) (*containerio.IO, error) {
return mgr.openIO(id, attach, false)
}
Expand Down
60 changes: 60 additions & 0 deletions test/api_restart_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package main

import (
"net/url"

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

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

// APIContainerRestartSuite is the test suite for container upgrade API.
type APIContainerRestartSuite struct{}

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

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

// TestAPIContainerRestart is to verify restarting container.
func (suite *APIContainerRestartSuite) TestAPIContainerRestart(c *check.C) {
cname := "TestAPIContainerRestart"

CreateBusyboxContainerOk(c, cname)

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

q := url.Values{}
q.Add("t", "1")
query := request.WithQuery(q)

resp, err = request.Post("/containers/"+cname+"/restart", query)
c.Assert(err, check.IsNil)
CheckRespStatus(c, resp, 204)

DelContainerForceOk(c, cname)
}

// TestAPIRestartStoppedContainer it to verify restarting a stopped container.
func (suite *APIContainerRestartSuite) TestAPIRestartStoppedContainer(c *check.C) {
cname := "TestAPIContainerRestart"

CreateBusyboxContainerOk(c, cname)

q := url.Values{}
q.Add("t", "1")
query := request.WithQuery(q)

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

DelContainerForceOk(c, cname)
}
64 changes: 64 additions & 0 deletions test/cli_restart_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package main

import (
"strings"

"github.com/alibaba/pouch/test/command"
"github.com/alibaba/pouch/test/environment"

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

// PouchRestartSuite is the test suite for restart CLI.
type PouchRestartSuite struct{}

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

// SetUpSuite does common setup in the beginning of each test suite.
func (suite *PouchRestartSuite) SetUpSuite(c *check.C) {
SkipIfFalse(c, environment.IsLinux)

environment.PruneAllContainers(apiClient)

command.PouchRun("pull", busyboxImage).Assert(c, icmd.Success)
}

// TearDownTest does cleanup work in the end of each test.
func (suite *PouchRestartSuite) TearDownTest(c *check.C) {
}

// TestPouchRestart is to verify the correctness of restarting a running container.
func (suite *PouchRestartSuite) TestPouchRestart(c *check.C) {
name := "TestPouchRestart"

command.PouchRun("run", "-d", "--cpu-share", "20", "--name", name, busyboxImage).Assert(c, icmd.Success)

res := command.PouchRun("restart", "-t", "1", name)
c.Assert(res.Error, check.IsNil)

if out := res.Combined(); !strings.Contains(out, name) {
c.Fatalf("unexpected output: %s, expected: %s", out, name)
}

command.PouchRun("rm", "-f", name).Assert(c, icmd.Success)
}

// TestPouchRestartStoppedContainer is to verify the correctness of restarting a stopped container.
func (suite *PouchRestartSuite) TestPouchRestartStoppedContainer(c *check.C) {
name := "TestPouchRestartStoppedContainer"

command.PouchRun("create", "--name", name, busyboxImage).Assert(c, icmd.Success)

res := command.PouchRun("restart", "-t", "1", name)
c.Assert(res.Error, check.NotNil)

expectString := "cannot restart a non running container"
if out := res.Combined(); !strings.Contains(out, expectString) {
c.Fatalf("unexpected output: %s, expected: %s", out, expectString)
}

command.PouchRun("rm", "-f", name).Assert(c, icmd.Success)
}

0 comments on commit 359ad41

Please sign in to comment.