Kaynağa Gözat

Add a --signal option to the kill command to specify a signal.

Docker-DCO-1.1-Signed-off-by: Paul Lietar <paul@lietar.net> (github: plietar)
Paul Lietar 11 yıl önce
ebeveyn
işleme
1f75a0bf43

+ 5 - 3
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 {

+ 5 - 3
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)
 ~~~~~~~~~~~~~~~~~~~

+ 77 - 5
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)
+}

+ 45 - 8
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