From 1f75a0bf435fe9a0f118b027e0387ba41e201c66 Mon Sep 17 00:00:00 2001 From: Paul Lietar Date: Wed, 22 Jan 2014 04:42:36 +0000 Subject: [PATCH] Add a --signal option to the kill command to specify a signal. Docker-DCO-1.1-Signed-off-by: Paul Lietar (github: plietar) --- commands.go | 8 ++- docs/sources/reference/commandline/cli.rst | 8 ++- integration/commands_test.go | 82 ++++++++++++++++++++-- server.go | 53 +++++++++++--- 4 files changed, 132 insertions(+), 19 deletions(-) diff --git a/commands.go b/commands.go index 4201df004f..84466c204a 100644 --- a/commands.go +++ b/commands.go @@ -942,7 +942,9 @@ func (cli *DockerCli) CmdRm(args ...string) error { // 'docker kill NAME' kills a running container func (cli *DockerCli) CmdKill(args ...string) error { - cmd := cli.Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container (send SIGKILL)") + cmd := cli.Subcmd("kill", "[OPTIONS] CONTAINER [CONTAINER...]", "Kill a running container (send SIGKILL, or specified signal)") + signal := cmd.String([]string{"s", "-signal"}, "KILL", "Signal to send to the container") + if err := cmd.Parse(args); err != nil { return nil } @@ -952,8 +954,8 @@ func (cli *DockerCli) CmdKill(args ...string) error { } var encounteredError error - for _, name := range args { - if _, _, err := readBody(cli.call("POST", "/containers/"+name+"/kill", nil, false)); err != nil { + for _, name := range cmd.Args() { + if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", name, *signal), nil, false)); err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to kill one or more containers") } else { diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index c00a97d5c4..1d8fa32a2f 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -754,11 +754,13 @@ we ask for the ``HostPort`` field to get the public address. :: - Usage: docker kill CONTAINER [CONTAINER...] + Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...] - Kill a running container (Send SIGKILL) + Kill a running container (send SIGKILL, or specified signal) -The main process inside the container will be sent SIGKILL. + -s, --signal="KILL": Signal to send to the container + +The main process inside the container will be sent SIGKILL, or any signal specified with option ``--signal``. Known Issues (kill) ~~~~~~~~~~~~~~~~~~~ diff --git a/integration/commands_test.go b/integration/commands_test.go index 9a2168d966..48819670e7 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -12,7 +12,9 @@ import ( "os" "path" "regexp" + "strconv" "strings" + "syscall" "testing" "time" ) @@ -90,18 +92,25 @@ func setTimeout(t *testing.T, msg string, d time.Duration, f func()) { } } +func expectPipe(expected string, r io.Reader) error { + o, err := bufio.NewReader(r).ReadString('\n') + if err != nil { + return err + } + if strings.Trim(o, " \r\n") != expected { + return fmt.Errorf("Unexpected output. Expected [%s], received [%s]", expected, o) + } + return nil +} + func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error { for i := 0; i < count; i++ { if _, err := w.Write([]byte(input)); err != nil { return err } - o, err := bufio.NewReader(r).ReadString('\n') - if err != nil { + if err := expectPipe(output, r); err != nil { return err } - if strings.Trim(o, " \r\n") != output { - return fmt.Errorf("Unexpected output. Expected [%s], received [%s]", output, o) - } } return nil } @@ -1031,3 +1040,66 @@ func TestContainerOrphaning(t *testing.T) { } } + +func TestCmdKill(t *testing.T) { + stdin, stdinPipe := io.Pipe() + stdout, stdoutPipe := io.Pipe() + + cli := docker.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + cli2 := docker.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr) + defer cleanup(globalEngine, t) + + ch := make(chan struct{}) + go func() { + defer close(ch) + cli.CmdRun("-i", "-t", unitTestImageID, "sh", "-c", "trap 'echo SIGUSR1' USR1; trap 'echo SIGUSR2' USR2; echo Ready; while true; do read; done") + }() + + container := waitContainerStart(t, 10*time.Second) + + setTimeout(t, "Read Ready timed out", 3*time.Second, func() { + if err := expectPipe("Ready", stdout); err != nil { + t.Fatal(err) + } + }) + + setTimeout(t, "SIGUSR1 timed out", 2*time.Second, func() { + for i := 0; i < 10; i++ { + if err := cli2.CmdKill("-s", strconv.Itoa(int(syscall.SIGUSR1)), container.ID); err != nil { + t.Fatal(err) + } + if err := expectPipe("SIGUSR1", stdout); err != nil { + t.Fatal(err) + } + } + }) + + setTimeout(t, "SIGUSR2 timed out", 2*time.Second, func() { + for i := 0; i < 10; i++ { + if err := cli2.CmdKill("--signal=USR2", container.ID); err != nil { + t.Fatal(err) + } + if err := expectPipe("SIGUSR2", stdout); err != nil { + t.Fatal(err) + } + } + }) + + time.Sleep(500 * time.Millisecond) + if !container.State.IsRunning() { + t.Fatal("The container should be still running") + } + + setTimeout(t, "Waiting for container timedout", 5*time.Second, func() { + if err := cli2.CmdKill(container.ID); err != nil { + t.Fatal(err) + } + + <-ch + if err := cli2.CmdWait(container.ID); err != nil { + t.Fatal(err) + } + }) + + closeWrap(stdin, stdinPipe, stdout, stdoutPipe) +} diff --git a/server.go b/server.go index 9b86311ccd..60fefc524f 100644 --- a/server.go +++ b/server.go @@ -161,6 +161,40 @@ func (v *simpleVersionInfo) Version() string { // for the container to exit. // If a signal is given, then just send it to the container and return. func (srv *Server) ContainerKill(job *engine.Job) engine.Status { + signalMap := map[string]syscall.Signal{ + "HUP": syscall.SIGHUP, + "INT": syscall.SIGINT, + "QUIT": syscall.SIGQUIT, + "ILL": syscall.SIGILL, + "TRAP": syscall.SIGTRAP, + "ABRT": syscall.SIGABRT, + "BUS": syscall.SIGBUS, + "FPE": syscall.SIGFPE, + "KILL": syscall.SIGKILL, + "USR1": syscall.SIGUSR1, + "SEGV": syscall.SIGSEGV, + "USR2": syscall.SIGUSR2, + "PIPE": syscall.SIGPIPE, + "ALRM": syscall.SIGALRM, + "TERM": syscall.SIGTERM, + //"STKFLT": syscall.SIGSTKFLT, + "CHLD": syscall.SIGCHLD, + "CONT": syscall.SIGCONT, + "STOP": syscall.SIGSTOP, + "TSTP": syscall.SIGTSTP, + "TTIN": syscall.SIGTTIN, + "TTOU": syscall.SIGTTOU, + "URG": syscall.SIGURG, + "XCPU": syscall.SIGXCPU, + "XFSZ": syscall.SIGXFSZ, + "VTALRM": syscall.SIGVTALRM, + "PROF": syscall.SIGPROF, + "WINCH": syscall.SIGWINCH, + "IO": syscall.SIGIO, + //"PWR": syscall.SIGPWR, + "SYS": syscall.SIGSYS, + } + if n := len(job.Args); n < 1 || n > 2 { job.Errorf("Usage: %s CONTAINER [SIGNAL]", job.Name) return engine.StatusErr @@ -168,17 +202,20 @@ func (srv *Server) ContainerKill(job *engine.Job) engine.Status { name := job.Args[0] var sig uint64 if len(job.Args) == 2 && job.Args[1] != "" { - var err error - // The largest legal signal is 31, so let's parse on 5 bits - sig, err = strconv.ParseUint(job.Args[1], 10, 5) - if err != nil { - job.Errorf("Invalid signal: %s", job.Args[1]) - return engine.StatusErr + sig = uint64(signalMap[job.Args[1]]) + if sig == 0 { + var err error + // The largest legal signal is 31, so let's parse on 5 bits + sig, err = strconv.ParseUint(job.Args[1], 10, 5) + if err != nil { + job.Errorf("Invalid signal: %s", job.Args[1]) + return engine.StatusErr + } } } if container := srv.runtime.Get(name); container != nil { - // If no signal is passed, perform regular Kill (SIGKILL + wait()) - if sig == 0 { + // If no signal is passed, or SIGKILL, perform regular Kill (SIGKILL + wait()) + if sig == 0 || syscall.Signal(sig) == syscall.SIGKILL { if err := container.Kill(); err != nil { job.Errorf("Cannot kill container %s: %s", name, err) return engine.StatusErr