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