github.com/a4a881d4/docker@v1.9.0-rc2/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 "path/filepath" 12 "reflect" 13 "sort" 14 "strings" 15 "sync" 16 "time" 17 18 "github.com/docker/docker/pkg/integration/checker" 19 "github.com/go-check/check" 20 ) 21 22 func (s *DockerSuite) TestExec(c *check.C) { 23 testRequires(c, DaemonIsLinux) 24 dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "sh", "-c", "echo test > /tmp/file && top") 25 26 out, _ := dockerCmd(c, "exec", "testing", "cat", "/tmp/file") 27 out = strings.Trim(out, "\r\n") 28 if out != "test" { 29 c.Errorf("container exec should've printed test but printed %q", out) 30 } 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 if err != nil { 41 c.Fatal(err) 42 } 43 stdout, err := execCmd.StdoutPipe() 44 if err != nil { 45 c.Fatal(err) 46 } 47 48 if err := execCmd.Start(); err != nil { 49 c.Fatal(err) 50 } 51 if _, err := stdin.Write([]byte("cat /tmp/file\n")); err != nil { 52 c.Fatal(err) 53 } 54 55 r := bufio.NewReader(stdout) 56 line, err := r.ReadString('\n') 57 if err != nil { 58 c.Fatal(err) 59 } 60 line = strings.TrimSpace(line) 61 if line != "test" { 62 c.Fatalf("Output should be 'test', got '%q'", line) 63 } 64 if err := stdin.Close(); err != nil { 65 c.Fatal(err) 66 } 67 errChan := make(chan error) 68 go func() { 69 errChan <- execCmd.Wait() 70 close(errChan) 71 }() 72 select { 73 case err := <-errChan: 74 c.Assert(err, check.IsNil) 75 case <-time.After(1 * time.Second): 76 c.Fatal("docker exec failed to exit on stdin close") 77 } 78 79 } 80 81 func (s *DockerSuite) TestExecAfterContainerRestart(c *check.C) { 82 testRequires(c, DaemonIsLinux) 83 out, _ := dockerCmd(c, "run", "-d", "busybox", "top") 84 cleanedContainerID := strings.TrimSpace(out) 85 dockerCmd(c, "restart", cleanedContainerID) 86 87 out, _ = dockerCmd(c, "exec", cleanedContainerID, "echo", "hello") 88 outStr := strings.TrimSpace(out) 89 if outStr != "hello" { 90 c.Errorf("container should've printed hello, instead printed %q", outStr) 91 } 92 } 93 94 func (s *DockerDaemonSuite) TestExecAfterDaemonRestart(c *check.C) { 95 testRequires(c, DaemonIsLinux) 96 testRequires(c, SameHostDaemon) 97 98 if err := s.d.StartWithBusybox(); err != nil { 99 c.Fatalf("Could not start daemon with busybox: %v", err) 100 } 101 102 if out, err := s.d.Cmd("run", "-d", "--name", "top", "-p", "80", "busybox:latest", "top"); err != nil { 103 c.Fatalf("Could not run top: err=%v\n%s", err, out) 104 } 105 106 if err := s.d.Restart(); err != nil { 107 c.Fatalf("Could not restart daemon: %v", err) 108 } 109 110 if out, err := s.d.Cmd("start", "top"); err != nil { 111 c.Fatalf("Could not start top after daemon restart: err=%v\n%s", err, out) 112 } 113 114 out, err := s.d.Cmd("exec", "top", "echo", "hello") 115 if err != nil { 116 c.Fatalf("Could not exec on container top: err=%v\n%s", err, out) 117 } 118 119 outStr := strings.TrimSpace(string(out)) 120 if outStr != "hello" { 121 c.Errorf("container should've printed hello, instead printed %q", outStr) 122 } 123 } 124 125 // Regression test for #9155, #9044 126 func (s *DockerSuite) TestExecEnv(c *check.C) { 127 testRequires(c, DaemonIsLinux) 128 dockerCmd(c, "run", "-e", "LALA=value1", "-e", "LALA=value2", 129 "-d", "--name", "testing", "busybox", "top") 130 131 out, _ := dockerCmd(c, "exec", "testing", "env") 132 if strings.Contains(out, "LALA=value1") || 133 !strings.Contains(out, "LALA=value2") || 134 !strings.Contains(out, "HOME=/root") { 135 c.Errorf("exec env(%q), expect %q, %q", out, "LALA=value2", "HOME=/root") 136 } 137 } 138 139 func (s *DockerSuite) TestExecExitStatus(c *check.C) { 140 testRequires(c, DaemonIsLinux) 141 dockerCmd(c, "run", "-d", "--name", "top", "busybox", "top") 142 143 // Test normal (non-detached) case first 144 cmd := exec.Command(dockerBinary, "exec", "top", "sh", "-c", "exit 23") 145 ec, _ := runCommand(cmd) 146 if ec != 23 { 147 c.Fatalf("Should have had an ExitCode of 23, not: %d", ec) 148 } 149 } 150 151 func (s *DockerSuite) TestExecPausedContainer(c *check.C) { 152 testRequires(c, DaemonIsLinux) 153 defer unpauseAllContainers() 154 155 out, _ := dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "top") 156 ContainerID := strings.TrimSpace(out) 157 158 dockerCmd(c, "pause", "testing") 159 out, _, err := dockerCmdWithError("exec", "-i", "-t", ContainerID, "echo", "hello") 160 if err == nil { 161 c.Fatal("container should fail to exec new command if it is paused") 162 } 163 164 expected := ContainerID + " is paused, unpause the container before exec" 165 if !strings.Contains(out, expected) { 166 c.Fatal("container should not exec new command if it is paused") 167 } 168 } 169 170 // regression test for #9476 171 func (s *DockerSuite) TestExecTtyCloseStdin(c *check.C) { 172 testRequires(c, DaemonIsLinux) 173 dockerCmd(c, "run", "-d", "-it", "--name", "exec_tty_stdin", "busybox") 174 175 cmd := exec.Command(dockerBinary, "exec", "-i", "exec_tty_stdin", "cat") 176 stdinRw, err := cmd.StdinPipe() 177 if err != nil { 178 c.Fatal(err) 179 } 180 181 stdinRw.Write([]byte("test")) 182 stdinRw.Close() 183 184 if out, _, err := runCommandWithOutput(cmd); err != nil { 185 c.Fatal(out, err) 186 } 187 188 out, _ := dockerCmd(c, "top", "exec_tty_stdin") 189 outArr := strings.Split(out, "\n") 190 if len(outArr) > 3 || strings.Contains(out, "nsenter-exec") { 191 c.Fatalf("exec process left running\n\t %s", out) 192 } 193 } 194 195 func (s *DockerSuite) TestExecTtyWithoutStdin(c *check.C) { 196 testRequires(c, DaemonIsLinux) 197 out, _ := dockerCmd(c, "run", "-d", "-ti", "busybox") 198 id := strings.TrimSpace(out) 199 c.Assert(waitRun(id), check.IsNil) 200 201 errChan := make(chan error) 202 go func() { 203 defer close(errChan) 204 205 cmd := exec.Command(dockerBinary, "exec", "-ti", id, "true") 206 if _, err := cmd.StdinPipe(); err != nil { 207 errChan <- err 208 return 209 } 210 211 expected := "cannot enable tty mode" 212 if out, _, err := runCommandWithOutput(cmd); err == nil { 213 errChan <- fmt.Errorf("exec should have failed") 214 return 215 } else if !strings.Contains(out, expected) { 216 errChan <- fmt.Errorf("exec failed with error %q: expected %q", out, expected) 217 return 218 } 219 }() 220 221 select { 222 case err := <-errChan: 223 c.Assert(err, check.IsNil) 224 case <-time.After(3 * time.Second): 225 c.Fatal("exec is running but should have failed") 226 } 227 } 228 229 func (s *DockerSuite) TestExecParseError(c *check.C) { 230 testRequires(c, DaemonIsLinux) 231 dockerCmd(c, "run", "-d", "--name", "top", "busybox", "top") 232 233 // Test normal (non-detached) case first 234 cmd := exec.Command(dockerBinary, "exec", "top") 235 if _, stderr, code, err := runCommandWithStdoutStderr(cmd); err == nil || !strings.Contains(stderr, "See '"+dockerBinary+" exec --help'") || code == 0 { 236 c.Fatalf("Should have thrown error & point to help: %s", stderr) 237 } 238 } 239 240 func (s *DockerSuite) TestExecStopNotHanging(c *check.C) { 241 testRequires(c, DaemonIsLinux) 242 dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "top") 243 244 if err := exec.Command(dockerBinary, "exec", "testing", "top").Start(); err != nil { 245 c.Fatal(err) 246 } 247 248 type dstop struct { 249 out []byte 250 err error 251 } 252 253 ch := make(chan dstop) 254 go func() { 255 out, err := exec.Command(dockerBinary, "stop", "testing").CombinedOutput() 256 ch <- dstop{out, err} 257 close(ch) 258 }() 259 select { 260 case <-time.After(3 * time.Second): 261 c.Fatal("Container stop timed out") 262 case s := <-ch: 263 c.Assert(s.err, check.IsNil) 264 } 265 } 266 267 func (s *DockerSuite) TestExecCgroup(c *check.C) { 268 testRequires(c, NotUserNamespace) 269 testRequires(c, DaemonIsLinux) 270 dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "top") 271 272 out, _ := dockerCmd(c, "exec", "testing", "cat", "/proc/1/cgroup") 273 containerCgroups := sort.StringSlice(strings.Split(out, "\n")) 274 275 var wg sync.WaitGroup 276 var mu sync.Mutex 277 execCgroups := []sort.StringSlice{} 278 errChan := make(chan error) 279 // exec a few times concurrently to get consistent failure 280 for i := 0; i < 5; i++ { 281 wg.Add(1) 282 go func() { 283 out, _, err := dockerCmdWithError("exec", "testing", "cat", "/proc/self/cgroup") 284 if err != nil { 285 errChan <- err 286 return 287 } 288 cg := sort.StringSlice(strings.Split(out, "\n")) 289 290 mu.Lock() 291 execCgroups = append(execCgroups, cg) 292 mu.Unlock() 293 wg.Done() 294 }() 295 } 296 wg.Wait() 297 close(errChan) 298 299 for err := range errChan { 300 c.Assert(err, check.IsNil) 301 } 302 303 for _, cg := range execCgroups { 304 if !reflect.DeepEqual(cg, containerCgroups) { 305 fmt.Println("exec cgroups:") 306 for _, name := range cg { 307 fmt.Printf(" %s\n", name) 308 } 309 310 fmt.Println("container cgroups:") 311 for _, name := range containerCgroups { 312 fmt.Printf(" %s\n", name) 313 } 314 c.Fatal("cgroups mismatched") 315 } 316 } 317 } 318 319 func (s *DockerSuite) TestInspectExecID(c *check.C) { 320 testRequires(c, DaemonIsLinux) 321 out, _ := dockerCmd(c, "run", "-d", "busybox", "top") 322 id := strings.TrimSuffix(out, "\n") 323 324 out, err := inspectField(id, "ExecIDs") 325 if err != nil { 326 c.Fatalf("failed to inspect container: %s, %v", out, err) 327 } 328 if out != "[]" { 329 c.Fatalf("ExecIDs should be empty, got: %s", out) 330 } 331 332 // Start an exec, have it block waiting so we can do some checking 333 cmd := exec.Command(dockerBinary, "exec", id, "sh", "-c", 334 "while ! test -e /tmp/execid1; do sleep 1; done") 335 336 if err = cmd.Start(); err != nil { 337 c.Fatalf("failed to start the exec cmd: %q", err) 338 } 339 340 // Give the exec 10 chances/seconds to start then give up and stop the test 341 tries := 10 342 for i := 0; i < tries; i++ { 343 // Since its still running we should see exec as part of the container 344 out, err = inspectField(id, "ExecIDs") 345 if err != nil { 346 c.Fatalf("failed to inspect container: %s, %v", out, err) 347 } 348 349 out = strings.TrimSuffix(out, "\n") 350 if out != "[]" && out != "<no value>" { 351 break 352 } 353 if i+1 == tries { 354 c.Fatalf("ExecIDs should not be empty, got: %s", out) 355 } 356 time.Sleep(1 * time.Second) 357 } 358 359 // Save execID for later 360 execID, err := inspectFilter(id, "index .ExecIDs 0") 361 if err != nil { 362 c.Fatalf("failed to get the exec id: %v", err) 363 } 364 365 // End the exec by creating the missing file 366 err = exec.Command(dockerBinary, "exec", id, 367 "sh", "-c", "touch /tmp/execid1").Run() 368 369 if err != nil { 370 c.Fatalf("failed to run the 2nd exec cmd: %q", err) 371 } 372 373 // Wait for 1st exec to complete 374 cmd.Wait() 375 376 // All execs for the container should be gone now 377 out, err = inspectField(id, "ExecIDs") 378 if err != nil { 379 c.Fatalf("failed to inspect container: %s, %v", out, err) 380 } 381 382 out = strings.TrimSuffix(out, "\n") 383 if out != "[]" && out != "<no value>" { 384 c.Fatalf("ExecIDs should be empty, got: %s", out) 385 } 386 387 // But we should still be able to query the execID 388 sc, body, err := sockRequest("GET", "/exec/"+execID+"/json", nil) 389 if sc != http.StatusOK { 390 c.Fatalf("received status != 200 OK: %d\n%s", sc, body) 391 } 392 393 // Now delete the container and then an 'inspect' on the exec should 394 // result in a 404 (not 'container not running') 395 out, ec := dockerCmd(c, "rm", "-f", id) 396 if ec != 0 { 397 c.Fatalf("error removing container: %s", out) 398 } 399 sc, body, err = sockRequest("GET", "/exec/"+execID+"/json", nil) 400 if sc != http.StatusNotFound { 401 c.Fatalf("received status != 404: %d\n%s", sc, body) 402 } 403 } 404 405 func (s *DockerSuite) TestLinksPingLinkedContainersOnRename(c *check.C) { 406 testRequires(c, DaemonIsLinux) 407 var out string 408 out, _ = dockerCmd(c, "run", "-d", "--name", "container1", "busybox", "top") 409 idA := strings.TrimSpace(out) 410 if idA == "" { 411 c.Fatal(out, "id should not be nil") 412 } 413 out, _ = dockerCmd(c, "run", "-d", "--link", "container1:alias1", "--name", "container2", "busybox", "top") 414 idB := strings.TrimSpace(out) 415 if idB == "" { 416 c.Fatal(out, "id should not be nil") 417 } 418 419 dockerCmd(c, "exec", "container2", "ping", "-c", "1", "alias1", "-W", "1") 420 dockerCmd(c, "rename", "container1", "container_new") 421 dockerCmd(c, "exec", "container2", "ping", "-c", "1", "alias1", "-W", "1") 422 } 423 424 func (s *DockerSuite) TestRunExecDir(c *check.C) { 425 testRequires(c, SameHostDaemon) 426 427 out, _ := dockerCmd(c, "run", "-d", "busybox", "top") 428 id := strings.TrimSpace(out) 429 execDir := filepath.Join(execDriverPath, id) 430 stateFile := filepath.Join(execDir, "state.json") 431 432 { 433 fi, err := os.Stat(execDir) 434 if err != nil { 435 c.Fatal(err) 436 } 437 if !fi.IsDir() { 438 c.Fatalf("%q must be a directory", execDir) 439 } 440 fi, err = os.Stat(stateFile) 441 if err != nil { 442 c.Fatal(err) 443 } 444 } 445 446 dockerCmd(c, "stop", id) 447 { 448 _, err := os.Stat(execDir) 449 if err == nil { 450 c.Fatal(err) 451 } 452 if err == nil { 453 c.Fatalf("Exec directory %q exists for removed container!", execDir) 454 } 455 if !os.IsNotExist(err) { 456 c.Fatalf("Error should be about non-existing, got %s", err) 457 } 458 } 459 dockerCmd(c, "start", id) 460 { 461 fi, err := os.Stat(execDir) 462 if err != nil { 463 c.Fatal(err) 464 } 465 if !fi.IsDir() { 466 c.Fatalf("%q must be a directory", execDir) 467 } 468 fi, err = os.Stat(stateFile) 469 if err != nil { 470 c.Fatal(err) 471 } 472 } 473 dockerCmd(c, "rm", "-f", id) 474 { 475 _, err := os.Stat(execDir) 476 if err == nil { 477 c.Fatal(err) 478 } 479 if err == nil { 480 c.Fatalf("Exec directory %q is exists for removed container!", execDir) 481 } 482 if !os.IsNotExist(err) { 483 c.Fatalf("Error should be about non-existing, got %s", err) 484 } 485 } 486 } 487 488 func (s *DockerSuite) TestRunMutableNetworkFiles(c *check.C) { 489 testRequires(c, SameHostDaemon) 490 491 for _, fn := range []string{"resolv.conf", "hosts"} { 492 deleteAllContainers() 493 494 content, err := runCommandAndReadContainerFile(fn, exec.Command(dockerBinary, "run", "-d", "--name", "c1", "busybox", "sh", "-c", fmt.Sprintf("echo success >/etc/%s && top", fn))) 495 if err != nil { 496 c.Fatal(err) 497 } 498 499 if strings.TrimSpace(string(content)) != "success" { 500 c.Fatal("Content was not what was modified in the container", string(content)) 501 } 502 503 out, _ := dockerCmd(c, "run", "-d", "--name", "c2", "busybox", "top") 504 contID := strings.TrimSpace(out) 505 netFilePath := containerStorageFile(contID, fn) 506 507 f, err := os.OpenFile(netFilePath, os.O_WRONLY|os.O_SYNC|os.O_APPEND, 0644) 508 if err != nil { 509 c.Fatal(err) 510 } 511 512 if _, err := f.Seek(0, 0); err != nil { 513 f.Close() 514 c.Fatal(err) 515 } 516 517 if err := f.Truncate(0); err != nil { 518 f.Close() 519 c.Fatal(err) 520 } 521 522 if _, err := f.Write([]byte("success2\n")); err != nil { 523 f.Close() 524 c.Fatal(err) 525 } 526 f.Close() 527 528 res, _ := dockerCmd(c, "exec", contID, "cat", "/etc/"+fn) 529 if res != "success2\n" { 530 c.Fatalf("Expected content of %s: %q, got: %q", fn, "success2\n", res) 531 } 532 } 533 } 534 535 func (s *DockerSuite) TestExecWithUser(c *check.C) { 536 testRequires(c, DaemonIsLinux) 537 dockerCmd(c, "run", "-d", "--name", "parent", "busybox", "top") 538 539 out, _ := dockerCmd(c, "exec", "-u", "1", "parent", "id") 540 if !strings.Contains(out, "uid=1(daemon) gid=1(daemon)") { 541 c.Fatalf("exec with user by id expected daemon user got %s", out) 542 } 543 544 out, _ = dockerCmd(c, "exec", "-u", "root", "parent", "id") 545 if !strings.Contains(out, "uid=0(root) gid=0(root)") { 546 c.Fatalf("exec with user by root expected root user got %s", out) 547 } 548 } 549 550 func (s *DockerSuite) TestExecWithPrivileged(c *check.C) { 551 testRequires(c, DaemonIsLinux, NotUserNamespace) 552 // Start main loop which attempts mknod repeatedly 553 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`) 554 555 // Check exec mknod doesn't work 556 cmd := exec.Command(dockerBinary, "exec", "parent", "sh", "-c", "mknod /tmp/sdb b 8 16") 557 out, _, err := runCommandWithOutput(cmd) 558 if err == nil || !strings.Contains(out, "Operation not permitted") { 559 c.Fatalf("exec mknod in --cap-drop=ALL container without --privileged should fail") 560 } 561 562 // Check exec mknod does work with --privileged 563 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`) 564 out, _, err = runCommandWithOutput(cmd) 565 if err != nil { 566 c.Fatal(err, out) 567 } 568 569 if actual := strings.TrimSpace(out); actual != "ok" { 570 c.Fatalf("exec mknod in --cap-drop=ALL container with --privileged failed: %v, output: %q", err, out) 571 } 572 573 // Check subsequent unprivileged exec cannot mknod 574 cmd = exec.Command(dockerBinary, "exec", "parent", "sh", "-c", "mknod /tmp/sdc b 8 32") 575 out, _, err = runCommandWithOutput(cmd) 576 if err == nil || !strings.Contains(out, "Operation not permitted") { 577 c.Fatalf("repeating exec mknod in --cap-drop=ALL container after --privileged without --privileged should fail") 578 } 579 580 // Confirm at no point was mknod allowed 581 logCmd := exec.Command(dockerBinary, "logs", "parent") 582 if out, _, err := runCommandWithOutput(logCmd); err != nil || strings.Contains(out, "Success") { 583 c.Fatal(out, err) 584 } 585 586 } 587 588 func (s *DockerSuite) TestExecWithImageUser(c *check.C) { 589 testRequires(c, DaemonIsLinux) 590 name := "testbuilduser" 591 _, err := buildImage(name, 592 `FROM busybox 593 RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd 594 USER dockerio`, 595 true) 596 if err != nil { 597 c.Fatalf("Could not build image %s: %v", name, err) 598 } 599 600 dockerCmd(c, "run", "-d", "--name", "dockerioexec", name, "top") 601 602 out, _ := dockerCmd(c, "exec", "dockerioexec", "whoami") 603 if !strings.Contains(out, "dockerio") { 604 c.Fatalf("exec with user by id expected dockerio user got %s", out) 605 } 606 } 607 608 func (s *DockerSuite) TestExecOnReadonlyContainer(c *check.C) { 609 // --read-only + userns has remount issues 610 testRequires(c, DaemonIsLinux, NotUserNamespace) 611 dockerCmd(c, "run", "-d", "--read-only", "--name", "parent", "busybox", "top") 612 if _, status := dockerCmd(c, "exec", "parent", "true"); status != 0 { 613 c.Fatalf("exec into a read-only container failed with exit status %d", status) 614 } 615 } 616 617 // #15750 618 func (s *DockerSuite) TestExecStartFails(c *check.C) { 619 testRequires(c, DaemonIsLinux) 620 name := "exec-15750" 621 dockerCmd(c, "run", "-d", "--name", name, "busybox", "top") 622 c.Assert(waitRun(name), check.IsNil) 623 624 out, _, err := dockerCmdWithError("exec", name, "no-such-cmd") 625 c.Assert(err, check.NotNil, check.Commentf(out)) 626 c.Assert(out, checker.Contains, "executable file not found") 627 }