Add a -s option to the kill command to specify a signal.
| ... | ... |
@@ -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 |