github.com/vieux/docker@v0.6.3-0.20161004191708-e097c2a938c7/integration-cli/docker_cli_exec_test.go (about) 1 // +build !test_no_exec 2 3 package main 4 5 import ( 6 "bufio" 7 "fmt" 8 "net/http" 9 "os" 10 "os/exec" 11 "reflect" 12 "runtime" 13 "sort" 14 "strings" 15 "sync" 16 "time" 17 18 "github.com/docker/docker/pkg/integration/checker" 19 icmd "github.com/docker/docker/pkg/integration/cmd" 20 "github.com/go-check/check" 21 ) 22 23 func (s *DockerSuite) TestExec(c *check.C) { 24 testRequires(c, DaemonIsLinux) 25 out, _ := dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "sh", "-c", "echo test > /tmp/file && top") 26 c.Assert(waitRun(strings.TrimSpace(out)), check.IsNil) 27 28 out, _ = dockerCmd(c, "exec", "testing", "cat", "/tmp/file") 29 out = strings.Trim(out, "\r\n") 30 c.Assert(out, checker.Equals, "test") 31 32 } 33 34 func (s *DockerSuite) TestExecInteractive(c *check.C) { 35 testRequires(c, DaemonIsLinux) 36 dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "sh", "-c", "echo test > /tmp/file && top") 37 38 execCmd := exec.Command(dockerBinary, "exec", "-i", "testing", "sh") 39 stdin, err := execCmd.StdinPipe() 40 c.Assert(err, checker.IsNil) 41 stdout, err := execCmd.StdoutPipe() 42 c.Assert(err, checker.IsNil) 43 44 err = execCmd.Start() 45 c.Assert(err, checker.IsNil) 46 _, err = stdin.Write([]byte("cat /tmp/file\n")) 47 c.Assert(err, checker.IsNil) 48 49 r := bufio.NewReader(stdout) 50 line, err := r.ReadString('\n') 51 c.Assert(err, checker.IsNil) 52 line = strings.TrimSpace(line) 53 c.Assert(line, checker.Equals, "test") 54 err = stdin.Close() 55 c.Assert(err, checker.IsNil) 56 errChan := make(chan error) 57 go func() { 58 errChan <- execCmd.Wait() 59 close(errChan) 60 }() 61 select { 62 case err := <-errChan: 63 c.Assert(err, checker.IsNil) 64 case <-time.After(1 * time.Second): 65 c.Fatal("docker exec failed to exit on stdin close") 66 } 67 68 } 69 70 func (s *DockerSuite) TestExecAfterContainerRestart(c *check.C) { 71 out, _ := runSleepingContainer(c) 72 cleanedContainerID := strings.TrimSpace(out) 73 c.Assert(waitRun(cleanedContainerID), check.IsNil) 74 dockerCmd(c, "restart", cleanedContainerID) 75 c.Assert(waitRun(cleanedContainerID), check.IsNil) 76 77 out, _ = dockerCmd(c, "exec", cleanedContainerID, "echo", "hello") 78 outStr := strings.TrimSpace(out) 79 c.Assert(outStr, checker.Equals, "hello") 80 } 81 82 func (s *DockerDaemonSuite) TestExecAfterDaemonRestart(c *check.C) { 83 // TODO Windows CI: Requires a little work to get this ported. 84 testRequires(c, DaemonIsLinux) 85 testRequires(c, SameHostDaemon) 86 87 err := s.d.StartWithBusybox() 88 c.Assert(err, checker.IsNil) 89 90 out, err := s.d.Cmd("run", "-d", "--name", "top", "-p", "80", "busybox:latest", "top") 91 c.Assert(err, checker.IsNil, check.Commentf("Could not run top: %s", out)) 92 93 err = s.d.Restart() 94 c.Assert(err, checker.IsNil, check.Commentf("Could not restart daemon")) 95 96 out, err = s.d.Cmd("start", "top") 97 c.Assert(err, checker.IsNil, check.Commentf("Could not start top after daemon restart: %s", out)) 98 99 out, err = s.d.Cmd("exec", "top", "echo", "hello") 100 c.Assert(err, checker.IsNil, check.Commentf("Could not exec on container top: %s", out)) 101 102 outStr := strings.TrimSpace(string(out)) 103 c.Assert(outStr, checker.Equals, "hello") 104 } 105 106 // Regression test for #9155, #9044 107 func (s *DockerSuite) TestExecEnv(c *check.C) { 108 // TODO Windows CI: This one is interesting and may just end up being a feature 109 // difference between Windows and Linux. On Windows, the environment is passed 110 // into the process that is launched, not into the machine environment. Hence 111 // a subsequent exec will not have LALA set/ 112 testRequires(c, DaemonIsLinux) 113 runSleepingContainer(c, "-e", "LALA=value1", "-e", "LALA=value2", "-d", "--name", "testing") 114 c.Assert(waitRun("testing"), check.IsNil) 115 116 out, _ := dockerCmd(c, "exec", "testing", "env") 117 c.Assert(out, checker.Not(checker.Contains), "LALA=value1") 118 c.Assert(out, checker.Contains, "LALA=value2") 119 c.Assert(out, checker.Contains, "HOME=/root") 120 } 121 122 func (s *DockerSuite) TestExecExitStatus(c *check.C) { 123 runSleepingContainer(c, "-d", "--name", "top") 124 125 result := icmd.RunCommand(dockerBinary, "exec", "top", "sh", "-c", "exit 23") 126 c.Assert(result, icmd.Matches, icmd.Expected{ExitCode: 23, Error: "exit status 23"}) 127 } 128 129 func (s *DockerSuite) TestExecPausedContainer(c *check.C) { 130 // Windows does not support pause 131 testRequires(c, DaemonIsLinux) 132 defer unpauseAllContainers() 133 134 out, _ := dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "top") 135 ContainerID := strings.TrimSpace(out) 136 137 dockerCmd(c, "pause", "testing") 138 out, _, err := dockerCmdWithError("exec", "-i", "-t", ContainerID, "echo", "hello") 139 c.Assert(err, checker.NotNil, check.Commentf("container should fail to exec new conmmand if it is paused")) 140 141 expected := ContainerID + " is paused, unpause the container before exec" 142 c.Assert(out, checker.Contains, expected, check.Commentf("container should not exec new command if it is paused")) 143 } 144 145 // regression test for #9476 146 func (s *DockerSuite) TestExecTTYCloseStdin(c *check.C) { 147 // TODO Windows CI: This requires some work to port to Windows. 148 testRequires(c, DaemonIsLinux) 149 dockerCmd(c, "run", "-d", "-it", "--name", "exec_tty_stdin", "busybox") 150 151 cmd := exec.Command(dockerBinary, "exec", "-i", "exec_tty_stdin", "cat") 152 stdinRw, err := cmd.StdinPipe() 153 c.Assert(err, checker.IsNil) 154 155 stdinRw.Write([]byte("test")) 156 stdinRw.Close() 157 158 out, _, err := runCommandWithOutput(cmd) 159 c.Assert(err, checker.IsNil, check.Commentf(out)) 160 161 out, _ = dockerCmd(c, "top", "exec_tty_stdin") 162 outArr := strings.Split(out, "\n") 163 c.Assert(len(outArr), checker.LessOrEqualThan, 3, check.Commentf("exec process left running")) 164 c.Assert(out, checker.Not(checker.Contains), "nsenter-exec") 165 } 166 167 func (s *DockerSuite) TestExecTTYWithoutStdin(c *check.C) { 168 out, _ := dockerCmd(c, "run", "-d", "-ti", "busybox") 169 id := strings.TrimSpace(out) 170 c.Assert(waitRun(id), checker.IsNil) 171 172 errChan := make(chan error) 173 go func() { 174 defer close(errChan) 175 176 cmd := exec.Command(dockerBinary, "exec", "-ti", id, "true") 177 if _, err := cmd.StdinPipe(); err != nil { 178 errChan <- err 179 return 180 } 181 182 expected := "the input device is not a TTY" 183 if runtime.GOOS == "windows" { 184 expected += ". If you are using mintty, try prefixing the command with 'winpty'" 185 } 186 if out, _, err := runCommandWithOutput(cmd); err == nil { 187 errChan <- fmt.Errorf("exec should have failed") 188 return 189 } else if !strings.Contains(out, expected) { 190 errChan <- fmt.Errorf("exec failed with error %q: expected %q", out, expected) 191 return 192 } 193 }() 194 195 select { 196 case err := <-errChan: 197 c.Assert(err, check.IsNil) 198 case <-time.After(3 * time.Second): 199 c.Fatal("exec is running but should have failed") 200 } 201 } 202 203 func (s *DockerSuite) TestExecParseError(c *check.C) { 204 // TODO Windows CI: Requires some extra work. Consider copying the 205 // runSleepingContainer helper to have an exec version. 206 testRequires(c, DaemonIsLinux) 207 dockerCmd(c, "run", "-d", "--name", "top", "busybox", "top") 208 209 // Test normal (non-detached) case first 210 cmd := exec.Command(dockerBinary, "exec", "top") 211 _, stderr, _, err := runCommandWithStdoutStderr(cmd) 212 c.Assert(err, checker.NotNil) 213 c.Assert(stderr, checker.Contains, "See 'docker exec --help'") 214 } 215 216 func (s *DockerSuite) TestExecStopNotHanging(c *check.C) { 217 // TODO Windows CI: Requires some extra work. Consider copying the 218 // runSleepingContainer helper to have an exec version. 219 testRequires(c, DaemonIsLinux) 220 dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "top") 221 222 err := exec.Command(dockerBinary, "exec", "testing", "top").Start() 223 c.Assert(err, checker.IsNil) 224 225 type dstop struct { 226 out []byte 227 err error 228 } 229 230 ch := make(chan dstop) 231 go func() { 232 out, err := exec.Command(dockerBinary, "stop", "testing").CombinedOutput() 233 ch <- dstop{out, err} 234 close(ch) 235 }() 236 select { 237 case <-time.After(3 * time.Second): 238 c.Fatal("Container stop timed out") 239 case s := <-ch: 240 c.Assert(s.err, check.IsNil) 241 } 242 } 243 244 func (s *DockerSuite) TestExecCgroup(c *check.C) { 245 // Not applicable on Windows - using Linux specific functionality 246 testRequires(c, NotUserNamespace) 247 testRequires(c, DaemonIsLinux) 248 dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "top") 249 250 out, _ := dockerCmd(c, "exec", "testing", "cat", "/proc/1/cgroup") 251 containerCgroups := sort.StringSlice(strings.Split(out, "\n")) 252 253 var wg sync.WaitGroup 254 var mu sync.Mutex 255 execCgroups := []sort.StringSlice{} 256 errChan := make(chan error) 257 // exec a few times concurrently to get consistent failure 258 for i := 0; i < 5; i++ { 259 wg.Add(1) 260 go func() { 261 out, _, err := dockerCmdWithError("exec", "testing", "cat", "/proc/self/cgroup") 262 if err != nil { 263 errChan <- err 264 return 265 } 266 cg := sort.StringSlice(strings.Split(out, "\n")) 267 268 mu.Lock() 269 execCgroups = append(execCgroups, cg) 270 mu.Unlock() 271 wg.Done() 272 }() 273 } 274 wg.Wait() 275 close(errChan) 276 277 for err := range errChan { 278 c.Assert(err, checker.IsNil) 279 } 280 281 for _, cg := range execCgroups { 282 if !reflect.DeepEqual(cg, containerCgroups) { 283 fmt.Println("exec cgroups:") 284 for _, name := range cg { 285 fmt.Printf(" %s\n", name) 286 } 287 288 fmt.Println("container cgroups:") 289 for _, name := range containerCgroups { 290 fmt.Printf(" %s\n", name) 291 } 292 c.Fatal("cgroups mismatched") 293 } 294 } 295 } 296 297 func (s *DockerSuite) TestExecInspectID(c *check.C) { 298 out, _ := runSleepingContainer(c, "-d") 299 id := strings.TrimSuffix(out, "\n") 300 301 out = inspectField(c, id, "ExecIDs") 302 c.Assert(out, checker.Equals, "[]", check.Commentf("ExecIDs should be empty, got: %s", out)) 303 304 // Start an exec, have it block waiting so we can do some checking 305 cmd := exec.Command(dockerBinary, "exec", id, "sh", "-c", 306 "while ! test -e /execid1; do sleep 1; done") 307 308 err := cmd.Start() 309 c.Assert(err, checker.IsNil, check.Commentf("failed to start the exec cmd")) 310 311 // Give the exec 10 chances/seconds to start then give up and stop the test 312 tries := 10 313 for i := 0; i < tries; i++ { 314 // Since its still running we should see exec as part of the container 315 out = strings.TrimSpace(inspectField(c, id, "ExecIDs")) 316 317 if out != "[]" && out != "<no value>" { 318 break 319 } 320 c.Assert(i+1, checker.Not(checker.Equals), tries, check.Commentf("ExecIDs still empty after 10 second")) 321 time.Sleep(1 * time.Second) 322 } 323 324 // Save execID for later 325 execID, err := inspectFilter(id, "index .ExecIDs 0") 326 c.Assert(err, checker.IsNil, check.Commentf("failed to get the exec id")) 327 328 // End the exec by creating the missing file 329 err = exec.Command(dockerBinary, "exec", id, 330 "sh", "-c", "touch /execid1").Run() 331 332 c.Assert(err, checker.IsNil, check.Commentf("failed to run the 2nd exec cmd")) 333 334 // Wait for 1st exec to complete 335 cmd.Wait() 336 337 // Give the exec 10 chances/seconds to stop then give up and stop the test 338 for i := 0; i < tries; i++ { 339 // Since its still running we should see exec as part of the container 340 out = strings.TrimSpace(inspectField(c, id, "ExecIDs")) 341 342 if out == "[]" { 343 break 344 } 345 c.Assert(i+1, checker.Not(checker.Equals), tries, check.Commentf("ExecIDs still not empty after 10 second")) 346 time.Sleep(1 * time.Second) 347 } 348 349 // But we should still be able to query the execID 350 sc, body, err := sockRequest("GET", "/exec/"+execID+"/json", nil) 351 c.Assert(sc, checker.Equals, http.StatusOK, check.Commentf("received status != 200 OK: %d\n%s", sc, body)) 352 353 // Now delete the container and then an 'inspect' on the exec should 354 // result in a 404 (not 'container not running') 355 out, ec := dockerCmd(c, "rm", "-f", id) 356 c.Assert(ec, checker.Equals, 0, check.Commentf("error removing container: %s", out)) 357 sc, body, err = sockRequest("GET", "/exec/"+execID+"/json", nil) 358 c.Assert(sc, checker.Equals, http.StatusNotFound, check.Commentf("received status != 404: %d\n%s", sc, body)) 359 } 360 361 func (s *DockerSuite) TestLinksPingLinkedContainersOnRename(c *check.C) { 362 // Problematic on Windows as Windows does not support links 363 testRequires(c, DaemonIsLinux) 364 var out string 365 out, _ = dockerCmd(c, "run", "-d", "--name", "container1", "busybox", "top") 366 idA := strings.TrimSpace(out) 367 c.Assert(idA, checker.Not(checker.Equals), "", check.Commentf("%s, id should not be nil", out)) 368 out, _ = dockerCmd(c, "run", "-d", "--link", "container1:alias1", "--name", "container2", "busybox", "top") 369 idB := strings.TrimSpace(out) 370 c.Assert(idB, checker.Not(checker.Equals), "", check.Commentf("%s, id should not be nil", out)) 371 372 dockerCmd(c, "exec", "container2", "ping", "-c", "1", "alias1", "-W", "1") 373 dockerCmd(c, "rename", "container1", "container_new") 374 dockerCmd(c, "exec", "container2", "ping", "-c", "1", "alias1", "-W", "1") 375 } 376 377 func (s *DockerSuite) TestRunMutableNetworkFiles(c *check.C) { 378 // Not applicable on Windows to Windows CI. 379 testRequires(c, SameHostDaemon, DaemonIsLinux) 380 for _, fn := range []string{"resolv.conf", "hosts"} { 381 deleteAllContainers() 382 383 content, err := runCommandAndReadContainerFile(fn, exec.Command(dockerBinary, "run", "-d", "--name", "c1", "busybox", "sh", "-c", fmt.Sprintf("echo success >/etc/%s && top", fn))) 384 c.Assert(err, checker.IsNil) 385 386 c.Assert(strings.TrimSpace(string(content)), checker.Equals, "success", check.Commentf("Content was not what was modified in the container", string(content))) 387 388 out, _ := dockerCmd(c, "run", "-d", "--name", "c2", "busybox", "top") 389 contID := strings.TrimSpace(out) 390 netFilePath := containerStorageFile(contID, fn) 391 392 f, err := os.OpenFile(netFilePath, os.O_WRONLY|os.O_SYNC|os.O_APPEND, 0644) 393 c.Assert(err, checker.IsNil) 394 395 if _, err := f.Seek(0, 0); err != nil { 396 f.Close() 397 c.Fatal(err) 398 } 399 400 if err := f.Truncate(0); err != nil { 401 f.Close() 402 c.Fatal(err) 403 } 404 405 if _, err := f.Write([]byte("success2\n")); err != nil { 406 f.Close() 407 c.Fatal(err) 408 } 409 f.Close() 410 411 res, _ := dockerCmd(c, "exec", contID, "cat", "/etc/"+fn) 412 c.Assert(res, checker.Equals, "success2\n") 413 } 414 } 415 416 func (s *DockerSuite) TestExecWithUser(c *check.C) { 417 // TODO Windows CI: This may be fixable in the future once Windows 418 // supports users 419 testRequires(c, DaemonIsLinux) 420 dockerCmd(c, "run", "-d", "--name", "parent", "busybox", "top") 421 422 out, _ := dockerCmd(c, "exec", "-u", "1", "parent", "id") 423 c.Assert(out, checker.Contains, "uid=1(daemon) gid=1(daemon)") 424 425 out, _ = dockerCmd(c, "exec", "-u", "root", "parent", "id") 426 c.Assert(out, checker.Contains, "uid=0(root) gid=0(root)", check.Commentf("exec with user by id expected daemon user got %s", out)) 427 } 428 429 func (s *DockerSuite) TestExecWithPrivileged(c *check.C) { 430 // Not applicable on Windows 431 testRequires(c, DaemonIsLinux, NotUserNamespace) 432 // Start main loop which attempts mknod repeatedly 433 dockerCmd(c, "run", "-d", "--name", "parent", "--cap-drop=ALL", "busybox", "sh", "-c", `while (true); do if [ -e /exec_priv ]; then cat /exec_priv && mknod /tmp/sda b 8 0 && echo "Success"; else echo "Privileged exec has not run yet"; fi; usleep 10000; done`) 434 435 // Check exec mknod doesn't work 436 cmd := exec.Command(dockerBinary, "exec", "parent", "sh", "-c", "mknod /tmp/sdb b 8 16") 437 out, _, err := runCommandWithOutput(cmd) 438 c.Assert(err, checker.NotNil, check.Commentf("exec mknod in --cap-drop=ALL container without --privileged should fail")) 439 c.Assert(out, checker.Contains, "Operation not permitted", check.Commentf("exec mknod in --cap-drop=ALL container without --privileged should fail")) 440 441 // Check exec mknod does work with --privileged 442 cmd = exec.Command(dockerBinary, "exec", "--privileged", "parent", "sh", "-c", `echo "Running exec --privileged" > /exec_priv && mknod /tmp/sdb b 8 16 && usleep 50000 && echo "Finished exec --privileged" > /exec_priv && echo ok`) 443 out, _, err = runCommandWithOutput(cmd) 444 c.Assert(err, checker.IsNil) 445 446 actual := strings.TrimSpace(out) 447 c.Assert(actual, checker.Equals, "ok", check.Commentf("exec mknod in --cap-drop=ALL container with --privileged failed, output: %q", out)) 448 449 // Check subsequent unprivileged exec cannot mknod 450 cmd = exec.Command(dockerBinary, "exec", "parent", "sh", "-c", "mknod /tmp/sdc b 8 32") 451 out, _, err = runCommandWithOutput(cmd) 452 c.Assert(err, checker.NotNil, check.Commentf("repeating exec mknod in --cap-drop=ALL container after --privileged without --privileged should fail")) 453 c.Assert(out, checker.Contains, "Operation not permitted", check.Commentf("repeating exec mknod in --cap-drop=ALL container after --privileged without --privileged should fail")) 454 455 // Confirm at no point was mknod allowed 456 logCmd := exec.Command(dockerBinary, "logs", "parent") 457 out, _, err = runCommandWithOutput(logCmd) 458 c.Assert(err, checker.IsNil) 459 c.Assert(out, checker.Not(checker.Contains), "Success") 460 461 } 462 463 func (s *DockerSuite) TestExecWithImageUser(c *check.C) { 464 // Not applicable on Windows 465 testRequires(c, DaemonIsLinux) 466 name := "testbuilduser" 467 _, err := buildImage(name, 468 `FROM busybox 469 RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd 470 USER dockerio`, 471 true) 472 c.Assert(err, checker.IsNil) 473 474 dockerCmd(c, "run", "-d", "--name", "dockerioexec", name, "top") 475 476 out, _ := dockerCmd(c, "exec", "dockerioexec", "whoami") 477 c.Assert(out, checker.Contains, "dockerio", check.Commentf("exec with user by id expected dockerio user got %s", out)) 478 } 479 480 func (s *DockerSuite) TestExecOnReadonlyContainer(c *check.C) { 481 // Windows does not support read-only 482 // --read-only + userns has remount issues 483 testRequires(c, DaemonIsLinux, NotUserNamespace) 484 dockerCmd(c, "run", "-d", "--read-only", "--name", "parent", "busybox", "top") 485 dockerCmd(c, "exec", "parent", "true") 486 } 487 488 func (s *DockerSuite) TestExecUlimits(c *check.C) { 489 testRequires(c, DaemonIsLinux) 490 name := "testexeculimits" 491 runSleepingContainer(c, "-d", "--ulimit", "nproc=21", "--name", name) 492 c.Assert(waitRun(name), checker.IsNil) 493 494 out, _, err := dockerCmdWithError("exec", name, "sh", "-c", "ulimit -p") 495 c.Assert(err, checker.IsNil) 496 c.Assert(strings.TrimSpace(out), checker.Equals, "21") 497 } 498 499 // #15750 500 func (s *DockerSuite) TestExecStartFails(c *check.C) { 501 // TODO Windows CI. This test should be portable. Figure out why it fails 502 // currently. 503 testRequires(c, DaemonIsLinux) 504 name := "exec-15750" 505 runSleepingContainer(c, "-d", "--name", name) 506 c.Assert(waitRun(name), checker.IsNil) 507 508 out, _, err := dockerCmdWithError("exec", name, "no-such-cmd") 509 c.Assert(err, checker.NotNil, check.Commentf(out)) 510 c.Assert(out, checker.Contains, "executable file not found") 511 } 512 513 // Fix regression in https://github.com/docker/docker/pull/26461#issuecomment-250287297 514 func (s *DockerSuite) TestExecWindowsPathNotWiped(c *check.C) { 515 testRequires(c, DaemonIsWindows) 516 out, _ := dockerCmd(c, "run", "-d", "--name", "testing", minimalBaseImage(), "powershell", "start-sleep", "60") 517 c.Assert(waitRun(strings.TrimSpace(out)), check.IsNil) 518 519 out, _ = dockerCmd(c, "exec", "testing", "powershell", "write-host", "$env:PATH") 520 out = strings.ToLower(strings.Trim(out, "\r\n")) 521 c.Assert(out, checker.Contains, `windowspowershell\v1.0`) 522 } 523 524 func (s *DockerSuite) TestExecEnvLinksHost(c *check.C) { 525 testRequires(c, DaemonIsLinux) 526 runSleepingContainer(c, "-d", "--name", "foo") 527 runSleepingContainer(c, "-d", "--link", "foo:db", "--hostname", "myhost", "--name", "bar") 528 out, _ := dockerCmd(c, "exec", "bar", "env") 529 c.Assert(out, checker.Contains, "HOSTNAME=myhost") 530 c.Assert(out, checker.Contains, "DB_NAME=/bar/db") 531 }