Skip to content

Commit

Permalink
feature: cli support 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 Jun 22, 2019
1 parent a43bd5f commit 0eec6d6
Show file tree
Hide file tree
Showing 10 changed files with 379 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 @@ -8,6 +8,7 @@ import (
"net/http"
"strconv"
"strings"
"syscall"
"time"

"github.com/alibaba/pouch/apis/metrics"
Expand Down Expand Up @@ -186,6 +187,25 @@ 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
}
}

fmt.Printf("signal: %d\n", uint64(sig))
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
78 changes: 78 additions & 0 deletions cli/kill.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package main

import (
"context"
"errors"
"fmt"
"github.com/spf13/cobra"
"strings"
)

// 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
16 changes: 16 additions & 0 deletions client/container_kill.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package client

import (
"context"
"net/url"
)

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
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 one or more running containers
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
107 changes: 107 additions & 0 deletions daemon/mgr/container_kill.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package mgr

import (
"context"
"fmt"
"github.com/sirupsen/logrus"
"strings"
"syscall"
)

type errNoSuchProcess struct {
pid int64
signal int
}

func isErrNoSuchProcess(err error) bool {
_, ok := err.(errNoSuchProcess)
return ok
}

func (e errNoSuchProcess) Error() string {
return fmt.Sprintf("Cannot kill process (pid=%d) with signal %d: no such process.", e.pid, e.signal)
}

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

if signal == 0 || 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 isErrNoSuchProcess(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 := errNoSuchProcess{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 := errNoSuchProcess{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)
}

msg, err := mgr.Client.DestroyContainer(ctx, c.ID, 10)
if err != nil {
err = fmt.Errorf("Cannot kill container %s: %s", c.ID, err)
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 err
}
}

attributes := map[string]string{
"signal": fmt.Sprintf("%d", signal),
}
mgr.LogContainerEventWithAttributes(ctx, c, "kill", attributes)

return mgr.markStoppedAndRelease(c, msg)

}
92 changes: 92 additions & 0 deletions pkg/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,81 @@ import (
"github.com/sirupsen/logrus"
)

const (
sigrtmin = 34
sigrtmax = 64
)

// SignalMap is a map of Linux signals.
var SignalMap = map[string]syscall.Signal{
"ABRT": syscall.SIGABRT,
"ALRM": syscall.SIGALRM,
"BUS": syscall.SIGBUS,
"CHLD": syscall.SIGCHLD,
"CLD": syscall.SIGCLD,
"CONT": syscall.SIGCONT,
"FPE": syscall.SIGFPE,
"HUP": syscall.SIGHUP,
"ILL": syscall.SIGILL,
"INT": syscall.SIGINT,
"IO": syscall.SIGIO,
"IOT": syscall.SIGIOT,
"KILL": syscall.SIGKILL,
"PIPE": syscall.SIGPIPE,
"POLL": syscall.SIGPOLL,
"PROF": syscall.SIGPROF,
"PWR": syscall.SIGPWR,
"QUIT": syscall.SIGQUIT,
"SEGV": syscall.SIGSEGV,
"STKFLT": syscall.SIGSTKFLT,
"STOP": syscall.SIGSTOP,
"SYS": syscall.SIGSYS,
"TERM": syscall.SIGTERM,
"TRAP": syscall.SIGTRAP,
"TSTP": syscall.SIGTSTP,
"TTIN": syscall.SIGTTIN,
"TTOU": syscall.SIGTTOU,
"UNUSED": syscall.SIGUNUSED,
"URG": syscall.SIGURG,
"USR1": syscall.SIGUSR1,
"USR2": syscall.SIGUSR2,
"VTALRM": syscall.SIGVTALRM,
"WINCH": syscall.SIGWINCH,
"XCPU": syscall.SIGXCPU,
"XFSZ": syscall.SIGXFSZ,
"RTMIN": sigrtmin,
"RTMIN+1": sigrtmin + 1,
"RTMIN+2": sigrtmin + 2,
"RTMIN+3": sigrtmin + 3,
"RTMIN+4": sigrtmin + 4,
"RTMIN+5": sigrtmin + 5,
"RTMIN+6": sigrtmin + 6,
"RTMIN+7": sigrtmin + 7,
"RTMIN+8": sigrtmin + 8,
"RTMIN+9": sigrtmin + 9,
"RTMIN+10": sigrtmin + 10,
"RTMIN+11": sigrtmin + 11,
"RTMIN+12": sigrtmin + 12,
"RTMIN+13": sigrtmin + 13,
"RTMIN+14": sigrtmin + 14,
"RTMIN+15": sigrtmin + 15,
"RTMAX-14": sigrtmax - 14,
"RTMAX-13": sigrtmax - 13,
"RTMAX-12": sigrtmax - 12,
"RTMAX-11": sigrtmax - 11,
"RTMAX-10": sigrtmax - 10,
"RTMAX-9": sigrtmax - 9,
"RTMAX-8": sigrtmax - 8,
"RTMAX-7": sigrtmax - 7,
"RTMAX-6": sigrtmax - 6,
"RTMAX-5": sigrtmax - 5,
"RTMAX-4": sigrtmax - 4,
"RTMAX-3": sigrtmax - 3,
"RTMAX-2": sigrtmax - 2,
"RTMAX-1": sigrtmax - 1,
"RTMAX": sigrtmax,
}

// If implements ternary operator. if cond is true return v1, or return v2 instead.
func If(cond bool, v1, v2 interface{}) interface{} {
if cond {
Expand Down Expand Up @@ -475,3 +550,20 @@ func ExtractIPAndPortFromAddresses(addresses []string) (string, string) {
}
return "", ""
}

// ParseSignal translates a string to a valid syscall signal.
// It returns an error if the signal map doesn't include the given signal.
func ParseSignal(rawSignal string) (syscall.Signal, error) {
s, err := strconv.Atoi(rawSignal)
if err == nil {
if s == 0 {
return -1, fmt.Errorf("Invalid signal: %s", rawSignal)
}
return syscall.Signal(s), nil
}
signal, ok := SignalMap[strings.TrimPrefix(strings.ToUpper(rawSignal), "SIG")]
if !ok {
return -1, fmt.Errorf("Invalid signal: %s", rawSignal)
}
return signal, nil
}
Loading

0 comments on commit 0eec6d6

Please sign in to comment.