Skip to content

Commit

Permalink
feature: add kill command
Browse files Browse the repository at this point in the history
Signed-off-by: Lang Chi <21860405@zju.edu.cn>
  • Loading branch information
lang710 committed Jul 2, 2019
1 parent a43bd5f commit a3d222f
Show file tree
Hide file tree
Showing 14 changed files with 492 additions and 0 deletions.
19 changes: 19 additions & 0 deletions apis/server/container_bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/http"
"strconv"
"strings"
"syscall"
"time"

"github.com/alibaba/pouch/apis/metrics"
Expand Down Expand Up @@ -186,6 +187,24 @@ func (s *Server) getContainers(ctx context.Context, rw http.ResponseWriter, req
return EncodeResponse(rw, http.StatusOK, containerList)
}

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

// If we have a signal, look at it. Otherwise, do nothing
if sigStr := req.FormValue("signal"); sigStr != "" {
var err error
if sig, err = utils.ParseSignal(sigStr); err != nil {
return err
}
}

if err := s.ContainerMgr.Kill(ctx, name, uint64(sig)); err != nil {
return err
}
return nil
}

func (s *Server) startContainer(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
label := util_metrics.ActionStartLabel
defer func(start time.Time) {
Expand Down
1 change: 1 addition & 0 deletions apis/server/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func initRoute(s *Server) *mux.Router {
{Method: http.MethodGet, Path: "/containers/{name:.*}/checkpoints", HandlerFunc: withCancelHandler(s.listContainerCheckpoint)},
{Method: http.MethodDelete, Path: "/containers/{name}/checkpoints/{id}", HandlerFunc: withCancelHandler(s.deleteContainerCheckpoint)},
{Method: http.MethodPost, Path: "/containers/create", HandlerFunc: s.createContainer},
{Method: http.MethodPost, Path: "/containers/{name:.*}/kill", HandlerFunc: s.killContainer},
{Method: http.MethodPost, Path: "/containers/{name:.*}/start", HandlerFunc: s.startContainer},
{Method: http.MethodPost, Path: "/containers/{name:.*}/stop", HandlerFunc: s.stopContainer},
{Method: http.MethodPost, Path: "/containers/{name:.*}/attach", HandlerFunc: s.attachContainer},
Expand Down
79 changes: 79 additions & 0 deletions cli/kill.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package main

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

"github.com/spf13/cobra"
)

// killDescription is used to describe kill command in detail and auto generate command doc.
var killDescription = "Kill one or more running container objects in Pouchd. " +
"You can kill a container using the container’s ID, ID-prefix, or name. " +
"This is useful when you wish to kill a container which is running."

// KillCommand use to implement 'kill' command, it kill one or more containers.
type KillCommand struct {
baseCommand
signal string
}

// Init initialize kill command.
func (s *KillCommand) Init(c *Cli) {
s.cli = c
s.cmd = &cobra.Command{
Use: "kill [OPTIONS] CONTAINER [CONTAINER...]",
Short: "kill one or more running containers",
Long: killDescription,
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return s.runKill(args)
},
Example: killExample(),
}
s.addFlags()
}

// addFlags adds flags for specific command.
func (s *KillCommand) addFlags() {
flagSet := s.cmd.Flags()
flagSet.StringVarP(&s.signal, "signal", "s", "KILL", "Signal to send to the container")

}

// runKill is the entry of kill command.
func (s *KillCommand) runKill(args []string) error {
ctx := context.Background()
apiClient := s.cli.Client()

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

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

return nil
}

// killExample shows examples in kill command, and is used in auto-generated cli docs.
func killExample() string {
return `$ pouch ps -a
Name ID Status Created Image Runtime
foo2 5a0ede Up 2 seconds 3 second ago registry.hub.docker.com/library/busybox:latest runc
foo1 e05637 Up 6 seconds 7 seconds ago registry.hub.docker.com/library/busybox:latest runc
$ pouch kill foo1
foo1
$ pouch ps
Name ID Status Created Image Runtime
foo2 5a0ede Up 11 seconds 12 seconds ago registry.hub.docker.com/library/busybox:latest runc
`
}
1 change: 1 addition & 0 deletions cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func main() {
cli.AddCommand(base, &PullCommand{})
cli.AddCommand(base, &PushCommand{})
cli.AddCommand(base, &CreateCommand{})
cli.AddCommand(base, &KillCommand{})
cli.AddCommand(base, &StartCommand{})
cli.AddCommand(base, &StopCommand{})
cli.AddCommand(base, &PsCommand{})
Expand Down
17 changes: 17 additions & 0 deletions client/container_kill.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package client

import (
"context"
"net/url"
)

// ContainerKill kills a container.
func (client *APIClient) ContainerKill(ctx context.Context, name, signal string) error {
q := url.Values{}
q.Add("signal", signal)

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

return err
}
1 change: 1 addition & 0 deletions client/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type CommonAPIClient interface {
type ContainerAPIClient interface {
ContainerCreate(ctx context.Context, config types.ContainerConfig, hostConfig *types.HostConfig, networkConfig *types.NetworkingConfig, containerName string) (*types.ContainerCreateResp, error)
ContainerStart(ctx context.Context, name string, options types.ContainerStartOptions) error
ContainerKill(ctx context.Context, name, signal string) error
ContainerStop(ctx context.Context, name, timeout string) error
ContainerRemove(ctx context.Context, name string, options *types.ContainerRemoveOptions) error
ContainerList(ctx context.Context, option types.ContainerListOptions) ([]*types.Container, error)
Expand Down
40 changes: 40 additions & 0 deletions ctrd/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,46 @@ func (c *Client) recoverContainer(ctx context.Context, id string, io *containeri
return nil
}

// KillContainer will kill a container by signal
func (c *Client) KillContainer(ctx context.Context, containerID string, signal int) error {
err := c.killContainer(ctx, containerID, signal)
if err != nil {
return convertCtrdErr(err)
}
return nil
}

// killContainer is the real process of killing a container
func (c *Client) killContainer(ctx context.Context, containerID string, signal int) error {
wrapperCli, err := c.Get(ctx)
if err != nil {
return fmt.Errorf("failed to get a containerd grpc client: %v", err)
}

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

if !c.lock.TrylockWithRetry(ctx, containerID) {
return errtypes.ErrLockfailed
}
defer c.lock.Unlock(containerID)

pack, err := c.watch.get(containerID)
if err != nil {
return err
}
// the caller need to execute the all hooks.
pack.l.Lock()
pack.skipStopHooks = true
pack.l.Unlock()
defer func() {
pack.l.Lock()
pack.skipStopHooks = false
pack.l.Unlock()
}()

return pack.task.Kill(ctx, syscall.Signal(signal), containerd.WithKillAll)
}

// DestroyContainer kill container and delete it.
func (c *Client) DestroyContainer(ctx context.Context, id string, timeout int64) (*Message, error) {
msg, err := c.destroyContainer(ctx, id, timeout)
Expand Down
2 changes: 2 additions & 0 deletions ctrd/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ type APIClient interface {

// ContainerAPIClient provides access to containerd container features.
type ContainerAPIClient interface {
// KillContainer kill container by signal
KillContainer(ctx context.Context, containerID string, signal int) error
// CreateContainer creates a containerd container and start process.
CreateContainer(ctx context.Context, container *Container, checkpointDir string) error
// DestroyContainer kill container and delete it.
Expand Down
3 changes: 3 additions & 0 deletions daemon/mgr/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ type ContainerMgr interface {
// List returns the list of containers.
List(ctx context.Context, option *ContainerListOption) ([]*Container, error)

// Kill a running container
Kill(ctx context.Context, name string, signal uint64) (err error)

// Start a container.
Start(ctx context.Context, id string, options *types.ContainerStartOptions) error

Expand Down
94 changes: 94 additions & 0 deletions daemon/mgr/container_kill.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package mgr

import (
"context"
"fmt"
"strings"
"syscall"

"github.com/pkg/errors"

"github.com/alibaba/pouch/pkg/errtypes"
"github.com/sirupsen/logrus"
)

// Kill kills a container.
func (mgr *ContainerManager) Kill(ctx context.Context, name string, signal uint64) (err error) {
c, err := mgr.container(name)
if err != nil {
return err
}

if syscall.Signal(signal) == syscall.SIGKILL {
return mgr.kill(ctx, c)
}
return mgr.killWithSignal(ctx, c, int(signal))
}

func (mgr *ContainerManager) kill(ctx context.Context, c *Container) error {
if !c.IsRunning() {
return fmt.Errorf("Container %s is not running", c.ID)
}

if err := mgr.killDeadProcess(ctx, c, int(syscall.SIGKILL)); err != nil {
if errtypes.IsNoSuchProcess(err) {
return nil
}

if c.IsRunning() {
return err
}
}

if pid := c.State.Pid; pid != 0 {
if err := syscall.Kill(int(pid), 9); err != nil {
if err != syscall.ESRCH {
return err
}

e := errors.Wrapf(errtypes.ErrNoSuchProcess, "Cannot kill process (pid=%d) with signal %d", c.State.Pid, 9)
logrus.Debug(e)
return nil
}
}
return nil
}

func (mgr *ContainerManager) killDeadProcess(ctx context.Context, c *Container, signal int) error {
err := mgr.killWithSignal(ctx, c, signal)
if err == syscall.ESRCH {
e := errors.Wrapf(errtypes.ErrNoSuchProcess, "Cannot kill process (pid=%d) with signal %d", c.State.Pid, signal)
logrus.Debug(e)
return e
}
return err
}

func (mgr *ContainerManager) killWithSignal(ctx context.Context, c *Container, signal int) error {
logrus.Debugf("Sending %d to %s", signal, c.ID)
c.Lock()
defer c.Unlock()

if c.State.Paused {
return fmt.Errorf("Container %s is paused. Unpause the container before stopping", c.ID)
}

if !c.State.Running {
return fmt.Errorf("Container %s is not running", c.ID)
}

if err := mgr.Client.KillContainer(ctx, c.ID, signal); err != nil {
// if container or process not exists, ignore the error
if strings.Contains(err.Error(), "container not found") ||
strings.Contains(err.Error(), "no such process") {
logrus.Warnf("container kill failed because of 'container not found' or 'no such process': %s", err.Error())
} else {
return fmt.Errorf("Cannot kill container %s: %s", c.ID, err)
}
}
attributes := map[string]string{
"signal": fmt.Sprintf("%d", signal),
}
mgr.LogContainerEventWithAttributes(ctx, c, "kill", attributes)
return nil
}
49 changes: 49 additions & 0 deletions docs/commandline/pouch_kill.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
## pouch kill

Kill one or more running containers

### Synopsis

Kill one or more running containers in Pouchd. You can kill a container using the container’s ID, ID-prefix, or name. This is useful when you wish to kill a container which is running.

```
pouch kill [OPTIONS] CONTAINER [CONTAINER...]
```

### Examples

```
$ pouch ps -a
Name ID Status Created Image Runtime
foo2 5a0ede Up 2 seconds 3 second ago registry.hub.docker.com/library/busybox:latest runc
foo1 e05637 Up 6 seconds 7 seconds ago registry.hub.docker.com/library/busybox:latest runc
$ pouch kill foo1
foo1
$ pouch ps
Name ID Status Created Image Runtime
foo2 5a0ede Up 11 seconds 12 seconds ago registry.hub.docker.com/library/busybox:latest runc
```

### Options

```
-h, --help help for kill
-s, --signal string Signal to send to the container (default "KILL")
```

### Options inherited from parent commands

```
-D, --debug Switch client log level to DEBUG mode
-H, --host string Specify connecting address of Pouch CLI (default "unix:///var/run/pouchd.sock")
--tlscacert string Specify CA file of TLS
--tlscert string Specify cert file of TLS
--tlskey string Specify key file of TLS
--tlsverify Use TLS and verify remote
```

### SEE ALSO

* [pouch](pouch.md) - An efficient container engine


9 changes: 9 additions & 0 deletions pkg/errtypes/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ var (

// ErrPreCheckFailed represents that failed to pre check.
ErrPreCheckFailed = errorType{codePreCheckFailed, "pre check failed"}

// ErrNoSuchProcess represents there is no such process
ErrNoSuchProcess = errorType{codeNoSuchProcess, "no such process"}
)

const (
Expand All @@ -51,6 +54,7 @@ const (
codeInUse
codeNotModified
codePreCheckFailed
codeNoSuchProcess

// volume error code
codeVolumeExisted
Expand Down Expand Up @@ -97,6 +101,11 @@ func IsNotModified(err error) bool {
return checkError(err, codeNotModified)
}

// IsNoSuchProcess checks the error is no such process error or not.
func IsNoSuchProcess(err error) bool {
return checkError(err, codeNoSuchProcess)
}

// IsPreCheckFailed checks the error is failed to pre check or not.
func IsPreCheckFailed(err error) bool {
return checkError(err, codePreCheckFailed)
Expand Down
Loading

0 comments on commit a3d222f

Please sign in to comment.