Browse code

Merge pull request #3712 from plietar/kill-signal

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

Guillaume J. Charmes authored on 2014/01/23 04:38:16
Showing 4 changed files
... ...
@@ -942,7 +942,9 @@ func (cli *DockerCli) CmdRm(args ...string) error {
942 942
 
943 943
 // 'docker kill NAME' kills a running container
944 944
 func (cli *DockerCli) CmdKill(args ...string) error {
945
-	cmd := cli.Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container (send SIGKILL)")
945
+	cmd := cli.Subcmd("kill", "[OPTIONS] CONTAINER [CONTAINER...]", "Kill a running container (send SIGKILL, or specified signal)")
946
+	signal := cmd.String([]string{"s", "-signal"}, "KILL", "Signal to send to the container")
947
+
946 948
 	if err := cmd.Parse(args); err != nil {
947 949
 		return nil
948 950
 	}
... ...
@@ -952,8 +954,8 @@ func (cli *DockerCli) CmdKill(args ...string) error {
952 952
 	}
953 953
 
954 954
 	var encounteredError error
955
-	for _, name := range args {
956
-		if _, _, err := readBody(cli.call("POST", "/containers/"+name+"/kill", nil, false)); err != nil {
955
+	for _, name := range cmd.Args() {
956
+		if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", name, *signal), nil, false)); err != nil {
957 957
 			fmt.Fprintf(cli.err, "%s\n", err)
958 958
 			encounteredError = fmt.Errorf("Error: failed to kill one or more containers")
959 959
 		} else {
... ...
@@ -754,11 +754,13 @@ we ask for the ``HostPort`` field to get the public address.
754 754
 
755 755
 ::
756 756
 
757
-    Usage: docker kill CONTAINER [CONTAINER...]
757
+    Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...]
758 758
 
759
-    Kill a running container (Send SIGKILL)
759
+    Kill a running container (send SIGKILL, or specified signal)
760 760
 
761
-The main process inside the container will be sent SIGKILL.
761
+      -s, --signal="KILL": Signal to send to the container
762
+
763
+The main process inside the container will be sent SIGKILL, or any signal specified with option ``--signal``.
762 764
 
763 765
 Known Issues (kill)
764 766
 ~~~~~~~~~~~~~~~~~~~
... ...
@@ -12,7 +12,9 @@ import (
12 12
 	"os"
13 13
 	"path"
14 14
 	"regexp"
15
+	"strconv"
15 16
 	"strings"
17
+	"syscall"
16 18
 	"testing"
17 19
 	"time"
18 20
 )
... ...
@@ -90,18 +92,25 @@ func setTimeout(t *testing.T, msg string, d time.Duration, f func()) {
90 90
 	}
91 91
 }
92 92
 
93
+func expectPipe(expected string, r io.Reader) error {
94
+	o, err := bufio.NewReader(r).ReadString('\n')
95
+	if err != nil {
96
+		return err
97
+	}
98
+	if strings.Trim(o, " \r\n") != expected {
99
+		return fmt.Errorf("Unexpected output. Expected [%s], received [%s]", expected, o)
100
+	}
101
+	return nil
102
+}
103
+
93 104
 func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error {
94 105
 	for i := 0; i < count; i++ {
95 106
 		if _, err := w.Write([]byte(input)); err != nil {
96 107
 			return err
97 108
 		}
98
-		o, err := bufio.NewReader(r).ReadString('\n')
99
-		if err != nil {
109
+		if err := expectPipe(output, r); err != nil {
100 110
 			return err
101 111
 		}
102
-		if strings.Trim(o, " \r\n") != output {
103
-			return fmt.Errorf("Unexpected output. Expected [%s], received [%s]", output, o)
104
-		}
105 112
 	}
106 113
 	return nil
107 114
 }
... ...
@@ -1031,3 +1040,66 @@ func TestContainerOrphaning(t *testing.T) {
1031 1031
 	}
1032 1032
 
1033 1033
 }
1034
+
1035
+func TestCmdKill(t *testing.T) {
1036
+	stdin, stdinPipe := io.Pipe()
1037
+	stdout, stdoutPipe := io.Pipe()
1038
+
1039
+	cli := docker.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
1040
+	cli2 := docker.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr)
1041
+	defer cleanup(globalEngine, t)
1042
+
1043
+	ch := make(chan struct{})
1044
+	go func() {
1045
+		defer close(ch)
1046
+		cli.CmdRun("-i", "-t", unitTestImageID, "sh", "-c", "trap 'echo SIGUSR1' USR1; trap 'echo SIGUSR2' USR2; echo Ready; while true; do read; done")
1047
+	}()
1048
+
1049
+	container := waitContainerStart(t, 10*time.Second)
1050
+
1051
+	setTimeout(t, "Read Ready timed out", 3*time.Second, func() {
1052
+		if err := expectPipe("Ready", stdout); err != nil {
1053
+			t.Fatal(err)
1054
+		}
1055
+	})
1056
+
1057
+	setTimeout(t, "SIGUSR1 timed out", 2*time.Second, func() {
1058
+		for i := 0; i < 10; i++ {
1059
+			if err := cli2.CmdKill("-s", strconv.Itoa(int(syscall.SIGUSR1)), container.ID); err != nil {
1060
+				t.Fatal(err)
1061
+			}
1062
+			if err := expectPipe("SIGUSR1", stdout); err != nil {
1063
+				t.Fatal(err)
1064
+			}
1065
+		}
1066
+	})
1067
+
1068
+	setTimeout(t, "SIGUSR2 timed out", 2*time.Second, func() {
1069
+		for i := 0; i < 10; i++ {
1070
+			if err := cli2.CmdKill("--signal=USR2", container.ID); err != nil {
1071
+				t.Fatal(err)
1072
+			}
1073
+			if err := expectPipe("SIGUSR2", stdout); err != nil {
1074
+				t.Fatal(err)
1075
+			}
1076
+		}
1077
+	})
1078
+
1079
+	time.Sleep(500 * time.Millisecond)
1080
+	if !container.State.IsRunning() {
1081
+		t.Fatal("The container should be still running")
1082
+	}
1083
+
1084
+	setTimeout(t, "Waiting for container timedout", 5*time.Second, func() {
1085
+		if err := cli2.CmdKill(container.ID); err != nil {
1086
+			t.Fatal(err)
1087
+		}
1088
+
1089
+		<-ch
1090
+		if err := cli2.CmdWait(container.ID); err != nil {
1091
+			t.Fatal(err)
1092
+		}
1093
+	})
1094
+
1095
+	closeWrap(stdin, stdinPipe, stdout, stdoutPipe)
1096
+}
... ...
@@ -161,6 +161,40 @@ func (v *simpleVersionInfo) Version() string {
161 161
 // for the container to exit.
162 162
 // If a signal is given, then just send it to the container and return.
163 163
 func (srv *Server) ContainerKill(job *engine.Job) engine.Status {
164
+	signalMap := map[string]syscall.Signal{
165
+		"HUP":  syscall.SIGHUP,
166
+		"INT":  syscall.SIGINT,
167
+		"QUIT": syscall.SIGQUIT,
168
+		"ILL":  syscall.SIGILL,
169
+		"TRAP": syscall.SIGTRAP,
170
+		"ABRT": syscall.SIGABRT,
171
+		"BUS":  syscall.SIGBUS,
172
+		"FPE":  syscall.SIGFPE,
173
+		"KILL": syscall.SIGKILL,
174
+		"USR1": syscall.SIGUSR1,
175
+		"SEGV": syscall.SIGSEGV,
176
+		"USR2": syscall.SIGUSR2,
177
+		"PIPE": syscall.SIGPIPE,
178
+		"ALRM": syscall.SIGALRM,
179
+		"TERM": syscall.SIGTERM,
180
+		//"STKFLT": syscall.SIGSTKFLT,
181
+		"CHLD":   syscall.SIGCHLD,
182
+		"CONT":   syscall.SIGCONT,
183
+		"STOP":   syscall.SIGSTOP,
184
+		"TSTP":   syscall.SIGTSTP,
185
+		"TTIN":   syscall.SIGTTIN,
186
+		"TTOU":   syscall.SIGTTOU,
187
+		"URG":    syscall.SIGURG,
188
+		"XCPU":   syscall.SIGXCPU,
189
+		"XFSZ":   syscall.SIGXFSZ,
190
+		"VTALRM": syscall.SIGVTALRM,
191
+		"PROF":   syscall.SIGPROF,
192
+		"WINCH":  syscall.SIGWINCH,
193
+		"IO":     syscall.SIGIO,
194
+		//"PWR":    syscall.SIGPWR,
195
+		"SYS": syscall.SIGSYS,
196
+	}
197
+
164 198
 	if n := len(job.Args); n < 1 || n > 2 {
165 199
 		job.Errorf("Usage: %s CONTAINER [SIGNAL]", job.Name)
166 200
 		return engine.StatusErr
... ...
@@ -168,17 +202,20 @@ func (srv *Server) ContainerKill(job *engine.Job) engine.Status {
168 168
 	name := job.Args[0]
169 169
 	var sig uint64
170 170
 	if len(job.Args) == 2 && job.Args[1] != "" {
171
-		var err error
172
-		// The largest legal signal is 31, so let's parse on 5 bits
173
-		sig, err = strconv.ParseUint(job.Args[1], 10, 5)
174
-		if err != nil {
175
-			job.Errorf("Invalid signal: %s", job.Args[1])
176
-			return engine.StatusErr
171
+		sig = uint64(signalMap[job.Args[1]])
172
+		if sig == 0 {
173
+			var err error
174
+			// The largest legal signal is 31, so let's parse on 5 bits
175
+			sig, err = strconv.ParseUint(job.Args[1], 10, 5)
176
+			if err != nil {
177
+				job.Errorf("Invalid signal: %s", job.Args[1])
178
+				return engine.StatusErr
179
+			}
177 180
 		}
178 181
 	}
179 182
 	if container := srv.runtime.Get(name); container != nil {
180
-		// If no signal is passed, perform regular Kill (SIGKILL + wait())
181
-		if sig == 0 {
183
+		// If no signal is passed, or SIGKILL, perform regular Kill (SIGKILL + wait())
184
+		if sig == 0 || syscall.Signal(sig) == syscall.SIGKILL {
182 185
 			if err := container.Kill(); err != nil {
183 186
 				job.Errorf("Cannot kill container %s: %s", name, err)
184 187
 				return engine.StatusErr