github.com/jwhonce/docker@v0.6.7-0.20190327063223-da823cf3a5a3/integration-cli/docker_api_containers_test.go (about) 1 package main 2 3 import ( 4 "archive/tar" 5 "bytes" 6 "context" 7 "encoding/json" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "net/http" 12 "os" 13 "path/filepath" 14 "regexp" 15 "runtime" 16 "strconv" 17 "strings" 18 "time" 19 20 "github.com/docker/docker/api/types" 21 containertypes "github.com/docker/docker/api/types/container" 22 mounttypes "github.com/docker/docker/api/types/mount" 23 networktypes "github.com/docker/docker/api/types/network" 24 "github.com/docker/docker/api/types/versions" 25 "github.com/docker/docker/client" 26 "github.com/docker/docker/integration-cli/checker" 27 "github.com/docker/docker/integration-cli/cli" 28 "github.com/docker/docker/integration-cli/cli/build" 29 "github.com/docker/docker/internal/test/request" 30 "github.com/docker/docker/pkg/ioutils" 31 "github.com/docker/docker/pkg/mount" 32 "github.com/docker/docker/pkg/stringid" 33 "github.com/docker/docker/volume" 34 "github.com/docker/go-connections/nat" 35 "github.com/go-check/check" 36 "gotest.tools/assert" 37 is "gotest.tools/assert/cmp" 38 "gotest.tools/poll" 39 ) 40 41 func (s *DockerSuite) TestContainerAPIGetAll(c *check.C) { 42 startCount := getContainerCount(c) 43 name := "getall" 44 dockerCmd(c, "run", "--name", name, "busybox", "true") 45 46 cli, err := client.NewClientWithOpts(client.FromEnv) 47 c.Assert(err, checker.IsNil) 48 defer cli.Close() 49 50 options := types.ContainerListOptions{ 51 All: true, 52 } 53 containers, err := cli.ContainerList(context.Background(), options) 54 c.Assert(err, checker.IsNil) 55 c.Assert(containers, checker.HasLen, startCount+1) 56 actual := containers[0].Names[0] 57 c.Assert(actual, checker.Equals, "/"+name) 58 } 59 60 // regression test for empty json field being omitted #13691 61 func (s *DockerSuite) TestContainerAPIGetJSONNoFieldsOmitted(c *check.C) { 62 startCount := getContainerCount(c) 63 dockerCmd(c, "run", "busybox", "true") 64 65 cli, err := client.NewClientWithOpts(client.FromEnv) 66 c.Assert(err, checker.IsNil) 67 defer cli.Close() 68 69 options := types.ContainerListOptions{ 70 All: true, 71 } 72 containers, err := cli.ContainerList(context.Background(), options) 73 c.Assert(err, checker.IsNil) 74 c.Assert(containers, checker.HasLen, startCount+1) 75 actual := fmt.Sprintf("%+v", containers[0]) 76 77 // empty Labels field triggered this bug, make sense to check for everything 78 // cause even Ports for instance can trigger this bug 79 // better safe than sorry.. 80 fields := []string{ 81 "ID", 82 "Names", 83 "Image", 84 "Command", 85 "Created", 86 "Ports", 87 "Labels", 88 "Status", 89 "NetworkSettings", 90 } 91 92 // decoding into types.Container do not work since it eventually unmarshal 93 // and empty field to an empty go map, so we just check for a string 94 for _, f := range fields { 95 if !strings.Contains(actual, f) { 96 c.Fatalf("Field %s is missing and it shouldn't", f) 97 } 98 } 99 } 100 101 type containerPs struct { 102 Names []string 103 Ports []types.Port 104 } 105 106 // regression test for non-empty fields from #13901 107 func (s *DockerSuite) TestContainerAPIPsOmitFields(c *check.C) { 108 // Problematic for Windows porting due to networking not yet being passed back 109 testRequires(c, DaemonIsLinux) 110 name := "pstest" 111 port := 80 112 runSleepingContainer(c, "--name", name, "--expose", strconv.Itoa(port)) 113 114 cli, err := client.NewClientWithOpts(client.FromEnv) 115 c.Assert(err, checker.IsNil) 116 defer cli.Close() 117 118 options := types.ContainerListOptions{ 119 All: true, 120 } 121 containers, err := cli.ContainerList(context.Background(), options) 122 c.Assert(err, checker.IsNil) 123 var foundContainer containerPs 124 for _, c := range containers { 125 for _, testName := range c.Names { 126 if "/"+name == testName { 127 foundContainer.Names = c.Names 128 foundContainer.Ports = c.Ports 129 break 130 } 131 } 132 } 133 134 c.Assert(foundContainer.Ports, checker.HasLen, 1) 135 c.Assert(foundContainer.Ports[0].PrivatePort, checker.Equals, uint16(port)) 136 c.Assert(foundContainer.Ports[0].PublicPort, checker.NotNil) 137 c.Assert(foundContainer.Ports[0].IP, checker.NotNil) 138 } 139 140 func (s *DockerSuite) TestContainerAPIGetExport(c *check.C) { 141 // Not supported on Windows as Windows does not support docker export 142 testRequires(c, DaemonIsLinux) 143 name := "exportcontainer" 144 dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test") 145 146 cli, err := client.NewClientWithOpts(client.FromEnv) 147 c.Assert(err, checker.IsNil) 148 defer cli.Close() 149 150 body, err := cli.ContainerExport(context.Background(), name) 151 c.Assert(err, checker.IsNil) 152 defer body.Close() 153 found := false 154 for tarReader := tar.NewReader(body); ; { 155 h, err := tarReader.Next() 156 if err != nil && err == io.EOF { 157 break 158 } 159 if h.Name == "test" { 160 found = true 161 break 162 } 163 } 164 c.Assert(found, checker.True, check.Commentf("The created test file has not been found in the exported image")) 165 } 166 167 func (s *DockerSuite) TestContainerAPIGetChanges(c *check.C) { 168 // Not supported on Windows as Windows does not support docker diff (/containers/name/changes) 169 testRequires(c, DaemonIsLinux) 170 name := "changescontainer" 171 dockerCmd(c, "run", "--name", name, "busybox", "rm", "/etc/passwd") 172 173 cli, err := client.NewClientWithOpts(client.FromEnv) 174 c.Assert(err, checker.IsNil) 175 defer cli.Close() 176 177 changes, err := cli.ContainerDiff(context.Background(), name) 178 c.Assert(err, checker.IsNil) 179 180 // Check the changelog for removal of /etc/passwd 181 success := false 182 for _, elem := range changes { 183 if elem.Path == "/etc/passwd" && elem.Kind == 2 { 184 success = true 185 } 186 } 187 c.Assert(success, checker.True, check.Commentf("/etc/passwd has been removed but is not present in the diff")) 188 } 189 190 func (s *DockerSuite) TestGetContainerStats(c *check.C) { 191 var ( 192 name = "statscontainer" 193 ) 194 runSleepingContainer(c, "--name", name) 195 196 type b struct { 197 stats types.ContainerStats 198 err error 199 } 200 201 bc := make(chan b, 1) 202 go func() { 203 cli, err := client.NewClientWithOpts(client.FromEnv) 204 c.Assert(err, checker.IsNil) 205 defer cli.Close() 206 207 stats, err := cli.ContainerStats(context.Background(), name, true) 208 c.Assert(err, checker.IsNil) 209 bc <- b{stats, err} 210 }() 211 212 // allow some time to stream the stats from the container 213 time.Sleep(4 * time.Second) 214 dockerCmd(c, "rm", "-f", name) 215 216 // collect the results from the stats stream or timeout and fail 217 // if the stream was not disconnected. 218 select { 219 case <-time.After(2 * time.Second): 220 c.Fatal("stream was not closed after container was removed") 221 case sr := <-bc: 222 dec := json.NewDecoder(sr.stats.Body) 223 defer sr.stats.Body.Close() 224 var s *types.Stats 225 // decode only one object from the stream 226 c.Assert(dec.Decode(&s), checker.IsNil) 227 } 228 } 229 230 func (s *DockerSuite) TestGetContainerStatsRmRunning(c *check.C) { 231 out := runSleepingContainer(c) 232 id := strings.TrimSpace(out) 233 234 buf := &ChannelBuffer{C: make(chan []byte, 1)} 235 defer buf.Close() 236 237 cli, err := client.NewClientWithOpts(client.FromEnv) 238 c.Assert(err, checker.IsNil) 239 defer cli.Close() 240 241 stats, err := cli.ContainerStats(context.Background(), id, true) 242 c.Assert(err, checker.IsNil) 243 defer stats.Body.Close() 244 245 chErr := make(chan error, 1) 246 go func() { 247 _, err = io.Copy(buf, stats.Body) 248 chErr <- err 249 }() 250 251 b := make([]byte, 32) 252 // make sure we've got some stats 253 _, err = buf.ReadTimeout(b, 2*time.Second) 254 c.Assert(err, checker.IsNil) 255 256 // Now remove without `-f` and make sure we are still pulling stats 257 _, _, err = dockerCmdWithError("rm", id) 258 c.Assert(err, checker.Not(checker.IsNil), check.Commentf("rm should have failed but didn't")) 259 _, err = buf.ReadTimeout(b, 2*time.Second) 260 c.Assert(err, checker.IsNil) 261 262 dockerCmd(c, "rm", "-f", id) 263 c.Assert(<-chErr, checker.IsNil) 264 } 265 266 // ChannelBuffer holds a chan of byte array that can be populate in a goroutine. 267 type ChannelBuffer struct { 268 C chan []byte 269 } 270 271 // Write implements Writer. 272 func (c *ChannelBuffer) Write(b []byte) (int, error) { 273 c.C <- b 274 return len(b), nil 275 } 276 277 // Close closes the go channel. 278 func (c *ChannelBuffer) Close() error { 279 close(c.C) 280 return nil 281 } 282 283 // ReadTimeout reads the content of the channel in the specified byte array with 284 // the specified duration as timeout. 285 func (c *ChannelBuffer) ReadTimeout(p []byte, n time.Duration) (int, error) { 286 select { 287 case b := <-c.C: 288 return copy(p[0:], b), nil 289 case <-time.After(n): 290 return -1, fmt.Errorf("timeout reading from channel") 291 } 292 } 293 294 // regression test for gh13421 295 // previous test was just checking one stat entry so it didn't fail (stats with 296 // stream false always return one stat) 297 func (s *DockerSuite) TestGetContainerStatsStream(c *check.C) { 298 name := "statscontainer" 299 runSleepingContainer(c, "--name", name) 300 301 type b struct { 302 stats types.ContainerStats 303 err error 304 } 305 306 bc := make(chan b, 1) 307 go func() { 308 cli, err := client.NewClientWithOpts(client.FromEnv) 309 c.Assert(err, checker.IsNil) 310 defer cli.Close() 311 312 stats, err := cli.ContainerStats(context.Background(), name, true) 313 c.Assert(err, checker.IsNil) 314 bc <- b{stats, err} 315 }() 316 317 // allow some time to stream the stats from the container 318 time.Sleep(4 * time.Second) 319 dockerCmd(c, "rm", "-f", name) 320 321 // collect the results from the stats stream or timeout and fail 322 // if the stream was not disconnected. 323 select { 324 case <-time.After(2 * time.Second): 325 c.Fatal("stream was not closed after container was removed") 326 case sr := <-bc: 327 b, err := ioutil.ReadAll(sr.stats.Body) 328 defer sr.stats.Body.Close() 329 c.Assert(err, checker.IsNil) 330 s := string(b) 331 // count occurrences of "read" of types.Stats 332 if l := strings.Count(s, "read"); l < 2 { 333 c.Fatalf("Expected more than one stat streamed, got %d", l) 334 } 335 } 336 } 337 338 func (s *DockerSuite) TestGetContainerStatsNoStream(c *check.C) { 339 name := "statscontainer" 340 runSleepingContainer(c, "--name", name) 341 342 type b struct { 343 stats types.ContainerStats 344 err error 345 } 346 347 bc := make(chan b, 1) 348 349 go func() { 350 cli, err := client.NewClientWithOpts(client.FromEnv) 351 c.Assert(err, checker.IsNil) 352 defer cli.Close() 353 354 stats, err := cli.ContainerStats(context.Background(), name, false) 355 c.Assert(err, checker.IsNil) 356 bc <- b{stats, err} 357 }() 358 359 // allow some time to stream the stats from the container 360 time.Sleep(4 * time.Second) 361 dockerCmd(c, "rm", "-f", name) 362 363 // collect the results from the stats stream or timeout and fail 364 // if the stream was not disconnected. 365 select { 366 case <-time.After(2 * time.Second): 367 c.Fatal("stream was not closed after container was removed") 368 case sr := <-bc: 369 b, err := ioutil.ReadAll(sr.stats.Body) 370 defer sr.stats.Body.Close() 371 c.Assert(err, checker.IsNil) 372 s := string(b) 373 // count occurrences of `"read"` of types.Stats 374 c.Assert(strings.Count(s, `"read"`), checker.Equals, 1, check.Commentf("Expected only one stat streamed, got %d", strings.Count(s, `"read"`))) 375 } 376 } 377 378 func (s *DockerSuite) TestGetStoppedContainerStats(c *check.C) { 379 name := "statscontainer" 380 dockerCmd(c, "create", "--name", name, "busybox", "ps") 381 382 chResp := make(chan error) 383 384 // We expect an immediate response, but if it's not immediate, the test would hang, so put it in a goroutine 385 // below we'll check this on a timeout. 386 go func() { 387 cli, err := client.NewClientWithOpts(client.FromEnv) 388 c.Assert(err, checker.IsNil) 389 defer cli.Close() 390 391 resp, err := cli.ContainerStats(context.Background(), name, false) 392 defer resp.Body.Close() 393 chResp <- err 394 }() 395 396 select { 397 case err := <-chResp: 398 c.Assert(err, checker.IsNil) 399 case <-time.After(10 * time.Second): 400 c.Fatal("timeout waiting for stats response for stopped container") 401 } 402 } 403 404 func (s *DockerSuite) TestContainerAPIPause(c *check.C) { 405 // Problematic on Windows as Windows does not support pause 406 testRequires(c, DaemonIsLinux) 407 408 getPaused := func(c *check.C) []string { 409 return strings.Fields(cli.DockerCmd(c, "ps", "-f", "status=paused", "-q", "-a").Combined()) 410 } 411 412 out := cli.DockerCmd(c, "run", "-d", "busybox", "sleep", "30").Combined() 413 ContainerID := strings.TrimSpace(out) 414 415 cli, err := client.NewClientWithOpts(client.FromEnv) 416 c.Assert(err, checker.IsNil) 417 defer cli.Close() 418 419 err = cli.ContainerPause(context.Background(), ContainerID) 420 c.Assert(err, checker.IsNil) 421 422 pausedContainers := getPaused(c) 423 424 if len(pausedContainers) != 1 || stringid.TruncateID(ContainerID) != pausedContainers[0] { 425 c.Fatalf("there should be one paused container and not %d", len(pausedContainers)) 426 } 427 428 err = cli.ContainerUnpause(context.Background(), ContainerID) 429 c.Assert(err, checker.IsNil) 430 431 pausedContainers = getPaused(c) 432 c.Assert(pausedContainers, checker.HasLen, 0, check.Commentf("There should be no paused container.")) 433 } 434 435 func (s *DockerSuite) TestContainerAPITop(c *check.C) { 436 testRequires(c, DaemonIsLinux) 437 out, _ := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "top") 438 id := strings.TrimSpace(string(out)) 439 c.Assert(waitRun(id), checker.IsNil) 440 441 cli, err := client.NewClientWithOpts(client.FromEnv) 442 c.Assert(err, checker.IsNil) 443 defer cli.Close() 444 445 // sort by comm[andline] to make sure order stays the same in case of PID rollover 446 top, err := cli.ContainerTop(context.Background(), id, []string{"aux", "--sort=comm"}) 447 c.Assert(err, checker.IsNil) 448 c.Assert(top.Titles, checker.HasLen, 11, check.Commentf("expected 11 titles, found %d: %v", len(top.Titles), top.Titles)) 449 450 if top.Titles[0] != "USER" || top.Titles[10] != "COMMAND" { 451 c.Fatalf("expected `USER` at `Titles[0]` and `COMMAND` at Titles[10]: %v", top.Titles) 452 } 453 c.Assert(top.Processes, checker.HasLen, 2, check.Commentf("expected 2 processes, found %d: %v", len(top.Processes), top.Processes)) 454 c.Assert(top.Processes[0][10], checker.Equals, "/bin/sh -c top") 455 c.Assert(top.Processes[1][10], checker.Equals, "top") 456 } 457 458 func (s *DockerSuite) TestContainerAPITopWindows(c *check.C) { 459 testRequires(c, DaemonIsWindows) 460 out := runSleepingContainer(c, "-d") 461 id := strings.TrimSpace(string(out)) 462 c.Assert(waitRun(id), checker.IsNil) 463 464 cli, err := client.NewClientWithOpts(client.FromEnv) 465 c.Assert(err, checker.IsNil) 466 defer cli.Close() 467 468 top, err := cli.ContainerTop(context.Background(), id, nil) 469 c.Assert(err, checker.IsNil) 470 c.Assert(top.Titles, checker.HasLen, 4, check.Commentf("expected 4 titles, found %d: %v", len(top.Titles), top.Titles)) 471 472 if top.Titles[0] != "Name" || top.Titles[3] != "Private Working Set" { 473 c.Fatalf("expected `Name` at `Titles[0]` and `Private Working Set` at Titles[3]: %v", top.Titles) 474 } 475 c.Assert(len(top.Processes), checker.GreaterOrEqualThan, 2, check.Commentf("expected at least 2 processes, found %d: %v", len(top.Processes), top.Processes)) 476 477 foundProcess := false 478 expectedProcess := "busybox.exe" 479 for _, process := range top.Processes { 480 if process[0] == expectedProcess { 481 foundProcess = true 482 break 483 } 484 } 485 486 c.Assert(foundProcess, checker.Equals, true, check.Commentf("expected to find %s: %v", expectedProcess, top.Processes)) 487 } 488 489 func (s *DockerSuite) TestContainerAPICommit(c *check.C) { 490 cName := "testapicommit" 491 dockerCmd(c, "run", "--name="+cName, "busybox", "/bin/sh", "-c", "touch /test") 492 493 cli, err := client.NewClientWithOpts(client.FromEnv) 494 c.Assert(err, checker.IsNil) 495 defer cli.Close() 496 497 options := types.ContainerCommitOptions{ 498 Reference: "testcontainerapicommit:testtag", 499 } 500 501 img, err := cli.ContainerCommit(context.Background(), cName, options) 502 c.Assert(err, checker.IsNil) 503 504 cmd := inspectField(c, img.ID, "Config.Cmd") 505 c.Assert(cmd, checker.Equals, "[/bin/sh -c touch /test]", check.Commentf("got wrong Cmd from commit: %q", cmd)) 506 507 // sanity check, make sure the image is what we think it is 508 dockerCmd(c, "run", img.ID, "ls", "/test") 509 } 510 511 func (s *DockerSuite) TestContainerAPICommitWithLabelInConfig(c *check.C) { 512 cName := "testapicommitwithconfig" 513 dockerCmd(c, "run", "--name="+cName, "busybox", "/bin/sh", "-c", "touch /test") 514 515 cli, err := client.NewClientWithOpts(client.FromEnv) 516 c.Assert(err, checker.IsNil) 517 defer cli.Close() 518 519 config := containertypes.Config{ 520 Labels: map[string]string{"key1": "value1", "key2": "value2"}} 521 522 options := types.ContainerCommitOptions{ 523 Reference: "testcontainerapicommitwithconfig", 524 Config: &config, 525 } 526 527 img, err := cli.ContainerCommit(context.Background(), cName, options) 528 c.Assert(err, checker.IsNil) 529 530 label1 := inspectFieldMap(c, img.ID, "Config.Labels", "key1") 531 c.Assert(label1, checker.Equals, "value1") 532 533 label2 := inspectFieldMap(c, img.ID, "Config.Labels", "key2") 534 c.Assert(label2, checker.Equals, "value2") 535 536 cmd := inspectField(c, img.ID, "Config.Cmd") 537 c.Assert(cmd, checker.Equals, "[/bin/sh -c touch /test]", check.Commentf("got wrong Cmd from commit: %q", cmd)) 538 539 // sanity check, make sure the image is what we think it is 540 dockerCmd(c, "run", img.ID, "ls", "/test") 541 } 542 543 func (s *DockerSuite) TestContainerAPIBadPort(c *check.C) { 544 // TODO Windows to Windows CI - Port this test 545 testRequires(c, DaemonIsLinux) 546 547 config := containertypes.Config{ 548 Image: "busybox", 549 Cmd: []string{"/bin/sh", "-c", "echo test"}, 550 } 551 552 hostConfig := containertypes.HostConfig{ 553 PortBindings: nat.PortMap{ 554 "8080/tcp": []nat.PortBinding{ 555 { 556 HostIP: "", 557 HostPort: "aa80"}, 558 }, 559 }, 560 } 561 562 cli, err := client.NewClientWithOpts(client.FromEnv) 563 c.Assert(err, checker.IsNil) 564 defer cli.Close() 565 566 _, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, "") 567 c.Assert(err.Error(), checker.Contains, `invalid port specification: "aa80"`) 568 } 569 570 func (s *DockerSuite) TestContainerAPICreate(c *check.C) { 571 config := containertypes.Config{ 572 Image: "busybox", 573 Cmd: []string{"/bin/sh", "-c", "touch /test && ls /test"}, 574 } 575 576 cli, err := client.NewClientWithOpts(client.FromEnv) 577 c.Assert(err, checker.IsNil) 578 defer cli.Close() 579 580 container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "") 581 c.Assert(err, checker.IsNil) 582 583 out, _ := dockerCmd(c, "start", "-a", container.ID) 584 c.Assert(strings.TrimSpace(out), checker.Equals, "/test") 585 } 586 587 func (s *DockerSuite) TestContainerAPICreateEmptyConfig(c *check.C) { 588 589 cli, err := client.NewClientWithOpts(client.FromEnv) 590 c.Assert(err, checker.IsNil) 591 defer cli.Close() 592 593 _, err = cli.ContainerCreate(context.Background(), &containertypes.Config{}, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "") 594 595 expected := "No command specified" 596 c.Assert(err.Error(), checker.Contains, expected) 597 } 598 599 func (s *DockerSuite) TestContainerAPICreateMultipleNetworksConfig(c *check.C) { 600 // Container creation must fail if client specified configurations for more than one network 601 config := containertypes.Config{ 602 Image: "busybox", 603 } 604 605 networkingConfig := networktypes.NetworkingConfig{ 606 EndpointsConfig: map[string]*networktypes.EndpointSettings{ 607 "net1": {}, 608 "net2": {}, 609 "net3": {}, 610 }, 611 } 612 613 cli, err := client.NewClientWithOpts(client.FromEnv) 614 c.Assert(err, checker.IsNil) 615 defer cli.Close() 616 617 _, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networkingConfig, "") 618 msg := err.Error() 619 // network name order in error message is not deterministic 620 c.Assert(msg, checker.Contains, "Container cannot be connected to network endpoints") 621 c.Assert(msg, checker.Contains, "net1") 622 c.Assert(msg, checker.Contains, "net2") 623 c.Assert(msg, checker.Contains, "net3") 624 } 625 626 func (s *DockerSuite) TestContainerAPICreateWithHostName(c *check.C) { 627 domainName := "test-domain" 628 hostName := "test-hostname" 629 config := containertypes.Config{ 630 Image: "busybox", 631 Hostname: hostName, 632 Domainname: domainName, 633 } 634 635 cli, err := client.NewClientWithOpts(client.FromEnv) 636 c.Assert(err, checker.IsNil) 637 defer cli.Close() 638 639 container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "") 640 c.Assert(err, checker.IsNil) 641 642 containerJSON, err := cli.ContainerInspect(context.Background(), container.ID) 643 c.Assert(err, checker.IsNil) 644 645 c.Assert(containerJSON.Config.Hostname, checker.Equals, hostName, check.Commentf("Mismatched Hostname")) 646 c.Assert(containerJSON.Config.Domainname, checker.Equals, domainName, check.Commentf("Mismatched Domainname")) 647 } 648 649 func (s *DockerSuite) TestContainerAPICreateBridgeNetworkMode(c *check.C) { 650 // Windows does not support bridge 651 testRequires(c, DaemonIsLinux) 652 UtilCreateNetworkMode(c, "bridge") 653 } 654 655 func (s *DockerSuite) TestContainerAPICreateOtherNetworkModes(c *check.C) { 656 // Windows does not support these network modes 657 testRequires(c, DaemonIsLinux, NotUserNamespace) 658 UtilCreateNetworkMode(c, "host") 659 UtilCreateNetworkMode(c, "container:web1") 660 } 661 662 func UtilCreateNetworkMode(c *check.C, networkMode containertypes.NetworkMode) { 663 config := containertypes.Config{ 664 Image: "busybox", 665 } 666 667 hostConfig := containertypes.HostConfig{ 668 NetworkMode: networkMode, 669 } 670 671 cli, err := client.NewClientWithOpts(client.FromEnv) 672 c.Assert(err, checker.IsNil) 673 defer cli.Close() 674 675 container, err := cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, "") 676 c.Assert(err, checker.IsNil) 677 678 containerJSON, err := cli.ContainerInspect(context.Background(), container.ID) 679 c.Assert(err, checker.IsNil) 680 681 c.Assert(containerJSON.HostConfig.NetworkMode, checker.Equals, containertypes.NetworkMode(networkMode), check.Commentf("Mismatched NetworkMode")) 682 } 683 684 func (s *DockerSuite) TestContainerAPICreateWithCpuSharesCpuset(c *check.C) { 685 // TODO Windows to Windows CI. The CpuShares part could be ported. 686 testRequires(c, DaemonIsLinux) 687 config := containertypes.Config{ 688 Image: "busybox", 689 } 690 691 hostConfig := containertypes.HostConfig{ 692 Resources: containertypes.Resources{ 693 CPUShares: 512, 694 CpusetCpus: "0", 695 }, 696 } 697 698 cli, err := client.NewClientWithOpts(client.FromEnv) 699 c.Assert(err, checker.IsNil) 700 defer cli.Close() 701 702 container, err := cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, "") 703 c.Assert(err, checker.IsNil) 704 705 containerJSON, err := cli.ContainerInspect(context.Background(), container.ID) 706 c.Assert(err, checker.IsNil) 707 708 out := inspectField(c, containerJSON.ID, "HostConfig.CpuShares") 709 c.Assert(out, checker.Equals, "512") 710 711 outCpuset := inspectField(c, containerJSON.ID, "HostConfig.CpusetCpus") 712 c.Assert(outCpuset, checker.Equals, "0") 713 } 714 715 func (s *DockerSuite) TestContainerAPIVerifyHeader(c *check.C) { 716 config := map[string]interface{}{ 717 "Image": "busybox", 718 } 719 720 create := func(ct string) (*http.Response, io.ReadCloser, error) { 721 jsonData := bytes.NewBuffer(nil) 722 c.Assert(json.NewEncoder(jsonData).Encode(config), checker.IsNil) 723 return request.Post("/containers/create", request.RawContent(ioutil.NopCloser(jsonData)), request.ContentType(ct)) 724 } 725 726 // Try with no content-type 727 res, body, err := create("") 728 c.Assert(err, checker.IsNil) 729 // todo: we need to figure out a better way to compare between dockerd versions 730 // comparing between daemon API version is not precise. 731 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 732 c.Assert(res.StatusCode, checker.Equals, http.StatusBadRequest) 733 } else { 734 c.Assert(res.StatusCode, checker.Not(checker.Equals), http.StatusOK) 735 } 736 body.Close() 737 738 // Try with wrong content-type 739 res, body, err = create("application/xml") 740 c.Assert(err, checker.IsNil) 741 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 742 c.Assert(res.StatusCode, checker.Equals, http.StatusBadRequest) 743 } else { 744 c.Assert(res.StatusCode, checker.Not(checker.Equals), http.StatusOK) 745 } 746 body.Close() 747 748 // now application/json 749 res, body, err = create("application/json") 750 c.Assert(err, checker.IsNil) 751 c.Assert(res.StatusCode, checker.Equals, http.StatusCreated) 752 body.Close() 753 } 754 755 //Issue 14230. daemon should return 500 for invalid port syntax 756 func (s *DockerSuite) TestContainerAPIInvalidPortSyntax(c *check.C) { 757 config := `{ 758 "Image": "busybox", 759 "HostConfig": { 760 "NetworkMode": "default", 761 "PortBindings": { 762 "19039;1230": [ 763 {} 764 ] 765 } 766 } 767 }` 768 769 res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON) 770 c.Assert(err, checker.IsNil) 771 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 772 c.Assert(res.StatusCode, checker.Equals, http.StatusBadRequest) 773 } else { 774 c.Assert(res.StatusCode, checker.Not(checker.Equals), http.StatusOK) 775 } 776 777 b, err := request.ReadBody(body) 778 c.Assert(err, checker.IsNil) 779 c.Assert(string(b[:]), checker.Contains, "invalid port") 780 } 781 782 func (s *DockerSuite) TestContainerAPIRestartPolicyInvalidPolicyName(c *check.C) { 783 config := `{ 784 "Image": "busybox", 785 "HostConfig": { 786 "RestartPolicy": { 787 "Name": "something", 788 "MaximumRetryCount": 0 789 } 790 } 791 }` 792 793 res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON) 794 c.Assert(err, checker.IsNil) 795 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 796 c.Assert(res.StatusCode, checker.Equals, http.StatusBadRequest) 797 } else { 798 c.Assert(res.StatusCode, checker.Not(checker.Equals), http.StatusOK) 799 } 800 801 b, err := request.ReadBody(body) 802 c.Assert(err, checker.IsNil) 803 c.Assert(string(b[:]), checker.Contains, "invalid restart policy") 804 } 805 806 func (s *DockerSuite) TestContainerAPIRestartPolicyRetryMismatch(c *check.C) { 807 config := `{ 808 "Image": "busybox", 809 "HostConfig": { 810 "RestartPolicy": { 811 "Name": "always", 812 "MaximumRetryCount": 2 813 } 814 } 815 }` 816 817 res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON) 818 c.Assert(err, checker.IsNil) 819 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 820 c.Assert(res.StatusCode, checker.Equals, http.StatusBadRequest) 821 } else { 822 c.Assert(res.StatusCode, checker.Not(checker.Equals), http.StatusOK) 823 } 824 825 b, err := request.ReadBody(body) 826 c.Assert(err, checker.IsNil) 827 c.Assert(string(b[:]), checker.Contains, "maximum retry count cannot be used with restart policy") 828 } 829 830 func (s *DockerSuite) TestContainerAPIRestartPolicyNegativeRetryCount(c *check.C) { 831 config := `{ 832 "Image": "busybox", 833 "HostConfig": { 834 "RestartPolicy": { 835 "Name": "on-failure", 836 "MaximumRetryCount": -2 837 } 838 } 839 }` 840 841 res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON) 842 c.Assert(err, checker.IsNil) 843 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 844 c.Assert(res.StatusCode, checker.Equals, http.StatusBadRequest) 845 } else { 846 c.Assert(res.StatusCode, checker.Not(checker.Equals), http.StatusOK) 847 } 848 849 b, err := request.ReadBody(body) 850 c.Assert(err, checker.IsNil) 851 c.Assert(string(b[:]), checker.Contains, "maximum retry count cannot be negative") 852 } 853 854 func (s *DockerSuite) TestContainerAPIRestartPolicyDefaultRetryCount(c *check.C) { 855 config := `{ 856 "Image": "busybox", 857 "HostConfig": { 858 "RestartPolicy": { 859 "Name": "on-failure", 860 "MaximumRetryCount": 0 861 } 862 } 863 }` 864 865 res, _, err := request.Post("/containers/create", request.RawString(config), request.JSON) 866 c.Assert(err, checker.IsNil) 867 c.Assert(res.StatusCode, checker.Equals, http.StatusCreated) 868 } 869 870 // Issue 7941 - test to make sure a "null" in JSON is just ignored. 871 // W/o this fix a null in JSON would be parsed into a string var as "null" 872 func (s *DockerSuite) TestContainerAPIPostCreateNull(c *check.C) { 873 config := `{ 874 "Hostname":"", 875 "Domainname":"", 876 "Memory":0, 877 "MemorySwap":0, 878 "CpuShares":0, 879 "Cpuset":null, 880 "AttachStdin":true, 881 "AttachStdout":true, 882 "AttachStderr":true, 883 "ExposedPorts":{}, 884 "Tty":true, 885 "OpenStdin":true, 886 "StdinOnce":true, 887 "Env":[], 888 "Cmd":"ls", 889 "Image":"busybox", 890 "Volumes":{}, 891 "WorkingDir":"", 892 "Entrypoint":null, 893 "NetworkDisabled":false, 894 "OnBuild":null}` 895 896 res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON) 897 c.Assert(err, checker.IsNil) 898 c.Assert(res.StatusCode, checker.Equals, http.StatusCreated) 899 900 b, err := request.ReadBody(body) 901 c.Assert(err, checker.IsNil) 902 type createResp struct { 903 ID string 904 } 905 var container createResp 906 c.Assert(json.Unmarshal(b, &container), checker.IsNil) 907 out := inspectField(c, container.ID, "HostConfig.CpusetCpus") 908 c.Assert(out, checker.Equals, "") 909 910 outMemory := inspectField(c, container.ID, "HostConfig.Memory") 911 c.Assert(outMemory, checker.Equals, "0") 912 outMemorySwap := inspectField(c, container.ID, "HostConfig.MemorySwap") 913 c.Assert(outMemorySwap, checker.Equals, "0") 914 } 915 916 func (s *DockerSuite) TestCreateWithTooLowMemoryLimit(c *check.C) { 917 // TODO Windows: Port once memory is supported 918 testRequires(c, DaemonIsLinux) 919 config := `{ 920 "Image": "busybox", 921 "Cmd": "ls", 922 "OpenStdin": true, 923 "CpuShares": 100, 924 "Memory": 524287 925 }` 926 927 res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON) 928 c.Assert(err, checker.IsNil) 929 b, err2 := request.ReadBody(body) 930 c.Assert(err2, checker.IsNil) 931 932 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 933 c.Assert(res.StatusCode, checker.Equals, http.StatusBadRequest) 934 } else { 935 c.Assert(res.StatusCode, checker.Not(checker.Equals), http.StatusOK) 936 } 937 c.Assert(string(b), checker.Contains, "Minimum memory limit allowed is 4MB") 938 } 939 940 func (s *DockerSuite) TestContainerAPIRename(c *check.C) { 941 out, _ := dockerCmd(c, "run", "--name", "TestContainerAPIRename", "-d", "busybox", "sh") 942 943 containerID := strings.TrimSpace(out) 944 newName := "TestContainerAPIRenameNew" 945 946 cli, err := client.NewClientWithOpts(client.FromEnv) 947 c.Assert(err, checker.IsNil) 948 defer cli.Close() 949 950 err = cli.ContainerRename(context.Background(), containerID, newName) 951 c.Assert(err, checker.IsNil) 952 953 name := inspectField(c, containerID, "Name") 954 c.Assert(name, checker.Equals, "/"+newName, check.Commentf("Failed to rename container")) 955 } 956 957 func (s *DockerSuite) TestContainerAPIKill(c *check.C) { 958 name := "test-api-kill" 959 runSleepingContainer(c, "-i", "--name", name) 960 961 cli, err := client.NewClientWithOpts(client.FromEnv) 962 c.Assert(err, checker.IsNil) 963 defer cli.Close() 964 965 err = cli.ContainerKill(context.Background(), name, "SIGKILL") 966 c.Assert(err, checker.IsNil) 967 968 state := inspectField(c, name, "State.Running") 969 c.Assert(state, checker.Equals, "false", check.Commentf("got wrong State from container %s: %q", name, state)) 970 } 971 972 func (s *DockerSuite) TestContainerAPIRestart(c *check.C) { 973 name := "test-api-restart" 974 runSleepingContainer(c, "-di", "--name", name) 975 cli, err := client.NewClientWithOpts(client.FromEnv) 976 c.Assert(err, checker.IsNil) 977 defer cli.Close() 978 979 timeout := 1 * time.Second 980 err = cli.ContainerRestart(context.Background(), name, &timeout) 981 c.Assert(err, checker.IsNil) 982 983 c.Assert(waitInspect(name, "{{ .State.Restarting }} {{ .State.Running }}", "false true", 15*time.Second), checker.IsNil) 984 } 985 986 func (s *DockerSuite) TestContainerAPIRestartNotimeoutParam(c *check.C) { 987 name := "test-api-restart-no-timeout-param" 988 out := runSleepingContainer(c, "-di", "--name", name) 989 id := strings.TrimSpace(out) 990 c.Assert(waitRun(id), checker.IsNil) 991 992 cli, err := client.NewClientWithOpts(client.FromEnv) 993 c.Assert(err, checker.IsNil) 994 defer cli.Close() 995 996 err = cli.ContainerRestart(context.Background(), name, nil) 997 c.Assert(err, checker.IsNil) 998 999 c.Assert(waitInspect(name, "{{ .State.Restarting }} {{ .State.Running }}", "false true", 15*time.Second), checker.IsNil) 1000 } 1001 1002 func (s *DockerSuite) TestContainerAPIStart(c *check.C) { 1003 name := "testing-start" 1004 config := containertypes.Config{ 1005 Image: "busybox", 1006 Cmd: append([]string{"/bin/sh", "-c"}, sleepCommandForDaemonPlatform()...), 1007 OpenStdin: true, 1008 } 1009 1010 cli, err := client.NewClientWithOpts(client.FromEnv) 1011 c.Assert(err, checker.IsNil) 1012 defer cli.Close() 1013 1014 _, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, name) 1015 c.Assert(err, checker.IsNil) 1016 1017 err = cli.ContainerStart(context.Background(), name, types.ContainerStartOptions{}) 1018 c.Assert(err, checker.IsNil) 1019 1020 // second call to start should give 304 1021 // maybe add ContainerStartWithRaw to test it 1022 err = cli.ContainerStart(context.Background(), name, types.ContainerStartOptions{}) 1023 c.Assert(err, checker.IsNil) 1024 1025 // TODO(tibor): figure out why this doesn't work on windows 1026 } 1027 1028 func (s *DockerSuite) TestContainerAPIStop(c *check.C) { 1029 name := "test-api-stop" 1030 runSleepingContainer(c, "-i", "--name", name) 1031 timeout := 30 * time.Second 1032 1033 cli, err := client.NewClientWithOpts(client.FromEnv) 1034 c.Assert(err, checker.IsNil) 1035 defer cli.Close() 1036 1037 err = cli.ContainerStop(context.Background(), name, &timeout) 1038 c.Assert(err, checker.IsNil) 1039 c.Assert(waitInspect(name, "{{ .State.Running }}", "false", 60*time.Second), checker.IsNil) 1040 1041 // second call to start should give 304 1042 // maybe add ContainerStartWithRaw to test it 1043 err = cli.ContainerStop(context.Background(), name, &timeout) 1044 c.Assert(err, checker.IsNil) 1045 } 1046 1047 func (s *DockerSuite) TestContainerAPIWait(c *check.C) { 1048 name := "test-api-wait" 1049 1050 sleepCmd := "/bin/sleep" 1051 if testEnv.OSType == "windows" { 1052 sleepCmd = "sleep" 1053 } 1054 dockerCmd(c, "run", "--name", name, "busybox", sleepCmd, "2") 1055 1056 cli, err := client.NewClientWithOpts(client.FromEnv) 1057 c.Assert(err, checker.IsNil) 1058 defer cli.Close() 1059 1060 waitresC, errC := cli.ContainerWait(context.Background(), name, "") 1061 1062 select { 1063 case err = <-errC: 1064 c.Assert(err, checker.IsNil) 1065 case waitres := <-waitresC: 1066 c.Assert(waitres.StatusCode, checker.Equals, int64(0)) 1067 } 1068 } 1069 1070 func (s *DockerSuite) TestContainerAPICopyNotExistsAnyMore(c *check.C) { 1071 name := "test-container-api-copy" 1072 dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt") 1073 1074 postData := types.CopyConfig{ 1075 Resource: "/test.txt", 1076 } 1077 // no copy in client/ 1078 res, _, err := request.Post("/containers/"+name+"/copy", request.JSONBody(postData)) 1079 c.Assert(err, checker.IsNil) 1080 c.Assert(res.StatusCode, checker.Equals, http.StatusNotFound) 1081 } 1082 1083 func (s *DockerSuite) TestContainerAPICopyPre124(c *check.C) { 1084 testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later 1085 name := "test-container-api-copy" 1086 dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt") 1087 1088 postData := types.CopyConfig{ 1089 Resource: "/test.txt", 1090 } 1091 1092 res, body, err := request.Post("/v1.23/containers/"+name+"/copy", request.JSONBody(postData)) 1093 c.Assert(err, checker.IsNil) 1094 c.Assert(res.StatusCode, checker.Equals, http.StatusOK) 1095 1096 found := false 1097 for tarReader := tar.NewReader(body); ; { 1098 h, err := tarReader.Next() 1099 if err != nil { 1100 if err == io.EOF { 1101 break 1102 } 1103 c.Fatal(err) 1104 } 1105 if h.Name == "test.txt" { 1106 found = true 1107 break 1108 } 1109 } 1110 c.Assert(found, checker.True) 1111 } 1112 1113 func (s *DockerSuite) TestContainerAPICopyResourcePathEmptyPre124(c *check.C) { 1114 testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later 1115 name := "test-container-api-copy-resource-empty" 1116 dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt") 1117 1118 postData := types.CopyConfig{ 1119 Resource: "", 1120 } 1121 1122 res, body, err := request.Post("/v1.23/containers/"+name+"/copy", request.JSONBody(postData)) 1123 c.Assert(err, checker.IsNil) 1124 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 1125 c.Assert(res.StatusCode, checker.Equals, http.StatusBadRequest) 1126 } else { 1127 c.Assert(res.StatusCode, checker.Not(checker.Equals), http.StatusOK) 1128 } 1129 b, err := request.ReadBody(body) 1130 c.Assert(err, checker.IsNil) 1131 c.Assert(string(b), checker.Matches, "Path cannot be empty\n") 1132 } 1133 1134 func (s *DockerSuite) TestContainerAPICopyResourcePathNotFoundPre124(c *check.C) { 1135 testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later 1136 name := "test-container-api-copy-resource-not-found" 1137 dockerCmd(c, "run", "--name", name, "busybox") 1138 1139 postData := types.CopyConfig{ 1140 Resource: "/notexist", 1141 } 1142 1143 res, body, err := request.Post("/v1.23/containers/"+name+"/copy", request.JSONBody(postData)) 1144 c.Assert(err, checker.IsNil) 1145 if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") { 1146 c.Assert(res.StatusCode, checker.Equals, http.StatusInternalServerError) 1147 } else { 1148 c.Assert(res.StatusCode, checker.Equals, http.StatusNotFound) 1149 } 1150 b, err := request.ReadBody(body) 1151 c.Assert(err, checker.IsNil) 1152 c.Assert(string(b), checker.Matches, "Could not find the file /notexist in container "+name+"\n") 1153 } 1154 1155 func (s *DockerSuite) TestContainerAPICopyContainerNotFoundPr124(c *check.C) { 1156 testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later 1157 postData := types.CopyConfig{ 1158 Resource: "/something", 1159 } 1160 1161 res, _, err := request.Post("/v1.23/containers/notexists/copy", request.JSONBody(postData)) 1162 c.Assert(err, checker.IsNil) 1163 c.Assert(res.StatusCode, checker.Equals, http.StatusNotFound) 1164 } 1165 1166 func (s *DockerSuite) TestContainerAPIDelete(c *check.C) { 1167 out := runSleepingContainer(c) 1168 1169 id := strings.TrimSpace(out) 1170 c.Assert(waitRun(id), checker.IsNil) 1171 1172 dockerCmd(c, "stop", id) 1173 1174 cli, err := client.NewClientWithOpts(client.FromEnv) 1175 c.Assert(err, checker.IsNil) 1176 defer cli.Close() 1177 1178 err = cli.ContainerRemove(context.Background(), id, types.ContainerRemoveOptions{}) 1179 c.Assert(err, checker.IsNil) 1180 } 1181 1182 func (s *DockerSuite) TestContainerAPIDeleteNotExist(c *check.C) { 1183 cli, err := client.NewClientWithOpts(client.FromEnv) 1184 c.Assert(err, checker.IsNil) 1185 defer cli.Close() 1186 1187 err = cli.ContainerRemove(context.Background(), "doesnotexist", types.ContainerRemoveOptions{}) 1188 c.Assert(err.Error(), checker.Contains, "No such container: doesnotexist") 1189 } 1190 1191 func (s *DockerSuite) TestContainerAPIDeleteForce(c *check.C) { 1192 out := runSleepingContainer(c) 1193 id := strings.TrimSpace(out) 1194 c.Assert(waitRun(id), checker.IsNil) 1195 1196 removeOptions := types.ContainerRemoveOptions{ 1197 Force: true, 1198 } 1199 1200 cli, err := client.NewClientWithOpts(client.FromEnv) 1201 c.Assert(err, checker.IsNil) 1202 defer cli.Close() 1203 1204 err = cli.ContainerRemove(context.Background(), id, removeOptions) 1205 c.Assert(err, checker.IsNil) 1206 } 1207 1208 func (s *DockerSuite) TestContainerAPIDeleteRemoveLinks(c *check.C) { 1209 // Windows does not support links 1210 testRequires(c, DaemonIsLinux) 1211 out, _ := dockerCmd(c, "run", "-d", "--name", "tlink1", "busybox", "top") 1212 1213 id := strings.TrimSpace(out) 1214 c.Assert(waitRun(id), checker.IsNil) 1215 1216 out, _ = dockerCmd(c, "run", "--link", "tlink1:tlink1", "--name", "tlink2", "-d", "busybox", "top") 1217 1218 id2 := strings.TrimSpace(out) 1219 c.Assert(waitRun(id2), checker.IsNil) 1220 1221 links := inspectFieldJSON(c, id2, "HostConfig.Links") 1222 c.Assert(links, checker.Equals, "[\"/tlink1:/tlink2/tlink1\"]", check.Commentf("expected to have links between containers")) 1223 1224 removeOptions := types.ContainerRemoveOptions{ 1225 RemoveLinks: true, 1226 } 1227 1228 cli, err := client.NewClientWithOpts(client.FromEnv) 1229 c.Assert(err, checker.IsNil) 1230 defer cli.Close() 1231 1232 err = cli.ContainerRemove(context.Background(), "tlink2/tlink1", removeOptions) 1233 c.Assert(err, check.IsNil) 1234 1235 linksPostRm := inspectFieldJSON(c, id2, "HostConfig.Links") 1236 c.Assert(linksPostRm, checker.Equals, "null", check.Commentf("call to api deleteContainer links should have removed the specified links")) 1237 } 1238 1239 func (s *DockerSuite) TestContainerAPIDeleteConflict(c *check.C) { 1240 out := runSleepingContainer(c) 1241 1242 id := strings.TrimSpace(out) 1243 c.Assert(waitRun(id), checker.IsNil) 1244 1245 cli, err := client.NewClientWithOpts(client.FromEnv) 1246 c.Assert(err, checker.IsNil) 1247 defer cli.Close() 1248 1249 err = cli.ContainerRemove(context.Background(), id, types.ContainerRemoveOptions{}) 1250 expected := "cannot remove a running container" 1251 c.Assert(err.Error(), checker.Contains, expected) 1252 } 1253 1254 func (s *DockerSuite) TestContainerAPIDeleteRemoveVolume(c *check.C) { 1255 testRequires(c, testEnv.IsLocalDaemon) 1256 1257 vol := "/testvolume" 1258 if testEnv.OSType == "windows" { 1259 vol = `c:\testvolume` 1260 } 1261 1262 out := runSleepingContainer(c, "-v", vol) 1263 1264 id := strings.TrimSpace(out) 1265 c.Assert(waitRun(id), checker.IsNil) 1266 1267 source, err := inspectMountSourceField(id, vol) 1268 c.Assert(err, checker.IsNil) 1269 _, err = os.Stat(source) 1270 c.Assert(err, checker.IsNil) 1271 1272 removeOptions := types.ContainerRemoveOptions{ 1273 Force: true, 1274 RemoveVolumes: true, 1275 } 1276 1277 cli, err := client.NewClientWithOpts(client.FromEnv) 1278 c.Assert(err, checker.IsNil) 1279 defer cli.Close() 1280 1281 err = cli.ContainerRemove(context.Background(), id, removeOptions) 1282 c.Assert(err, check.IsNil) 1283 1284 _, err = os.Stat(source) 1285 c.Assert(os.IsNotExist(err), checker.True, check.Commentf("expected to get ErrNotExist error, got %v", err)) 1286 } 1287 1288 // Regression test for https://github.com/docker/docker/issues/6231 1289 func (s *DockerSuite) TestContainerAPIChunkedEncoding(c *check.C) { 1290 1291 config := map[string]interface{}{ 1292 "Image": "busybox", 1293 "Cmd": append([]string{"/bin/sh", "-c"}, sleepCommandForDaemonPlatform()...), 1294 "OpenStdin": true, 1295 } 1296 1297 resp, _, err := request.Post("/containers/create", request.JSONBody(config), request.With(func(req *http.Request) error { 1298 // This is a cheat to make the http request do chunked encoding 1299 // Otherwise (just setting the Content-Encoding to chunked) net/http will overwrite 1300 // https://golang.org/src/pkg/net/http/request.go?s=11980:12172 1301 req.ContentLength = -1 1302 return nil 1303 })) 1304 c.Assert(err, checker.IsNil, check.Commentf("error creating container with chunked encoding")) 1305 defer resp.Body.Close() 1306 c.Assert(resp.StatusCode, checker.Equals, http.StatusCreated) 1307 } 1308 1309 func (s *DockerSuite) TestContainerAPIPostContainerStop(c *check.C) { 1310 out := runSleepingContainer(c) 1311 1312 containerID := strings.TrimSpace(out) 1313 c.Assert(waitRun(containerID), checker.IsNil) 1314 1315 cli, err := client.NewClientWithOpts(client.FromEnv) 1316 c.Assert(err, checker.IsNil) 1317 defer cli.Close() 1318 1319 err = cli.ContainerStop(context.Background(), containerID, nil) 1320 c.Assert(err, checker.IsNil) 1321 c.Assert(waitInspect(containerID, "{{ .State.Running }}", "false", 60*time.Second), checker.IsNil) 1322 } 1323 1324 // #14170 1325 func (s *DockerSuite) TestPostContainerAPICreateWithStringOrSliceEntrypoint(c *check.C) { 1326 config := containertypes.Config{ 1327 Image: "busybox", 1328 Entrypoint: []string{"echo"}, 1329 Cmd: []string{"hello", "world"}, 1330 } 1331 1332 cli, err := client.NewClientWithOpts(client.FromEnv) 1333 c.Assert(err, checker.IsNil) 1334 defer cli.Close() 1335 1336 _, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "echotest") 1337 c.Assert(err, checker.IsNil) 1338 out, _ := dockerCmd(c, "start", "-a", "echotest") 1339 c.Assert(strings.TrimSpace(out), checker.Equals, "hello world") 1340 1341 config2 := struct { 1342 Image string 1343 Entrypoint string 1344 Cmd []string 1345 }{"busybox", "echo", []string{"hello", "world"}} 1346 _, _, err = request.Post("/containers/create?name=echotest2", request.JSONBody(config2)) 1347 c.Assert(err, checker.IsNil) 1348 out, _ = dockerCmd(c, "start", "-a", "echotest2") 1349 c.Assert(strings.TrimSpace(out), checker.Equals, "hello world") 1350 } 1351 1352 // #14170 1353 func (s *DockerSuite) TestPostContainersCreateWithStringOrSliceCmd(c *check.C) { 1354 config := containertypes.Config{ 1355 Image: "busybox", 1356 Cmd: []string{"echo", "hello", "world"}, 1357 } 1358 1359 cli, err := client.NewClientWithOpts(client.FromEnv) 1360 c.Assert(err, checker.IsNil) 1361 defer cli.Close() 1362 1363 _, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "echotest") 1364 c.Assert(err, checker.IsNil) 1365 out, _ := dockerCmd(c, "start", "-a", "echotest") 1366 c.Assert(strings.TrimSpace(out), checker.Equals, "hello world") 1367 1368 config2 := struct { 1369 Image string 1370 Entrypoint string 1371 Cmd string 1372 }{"busybox", "echo", "hello world"} 1373 _, _, err = request.Post("/containers/create?name=echotest2", request.JSONBody(config2)) 1374 c.Assert(err, checker.IsNil) 1375 out, _ = dockerCmd(c, "start", "-a", "echotest2") 1376 c.Assert(strings.TrimSpace(out), checker.Equals, "hello world") 1377 } 1378 1379 // regression #14318 1380 // for backward compatibility testing with and without CAP_ prefix 1381 // and with upper and lowercase 1382 func (s *DockerSuite) TestPostContainersCreateWithStringOrSliceCapAddDrop(c *check.C) { 1383 // Windows doesn't support CapAdd/CapDrop 1384 testRequires(c, DaemonIsLinux) 1385 config := struct { 1386 Image string 1387 CapAdd string 1388 CapDrop string 1389 }{"busybox", "NET_ADMIN", "cap_sys_admin"} 1390 res, _, err := request.Post("/containers/create?name=capaddtest0", request.JSONBody(config)) 1391 c.Assert(err, checker.IsNil) 1392 c.Assert(res.StatusCode, checker.Equals, http.StatusCreated) 1393 1394 config2 := containertypes.Config{ 1395 Image: "busybox", 1396 } 1397 hostConfig := containertypes.HostConfig{ 1398 CapAdd: []string{"net_admin", "SYS_ADMIN"}, 1399 CapDrop: []string{"SETGID", "CAP_SETPCAP"}, 1400 } 1401 1402 cli, err := client.NewClientWithOpts(client.FromEnv) 1403 c.Assert(err, checker.IsNil) 1404 defer cli.Close() 1405 1406 _, err = cli.ContainerCreate(context.Background(), &config2, &hostConfig, &networktypes.NetworkingConfig{}, "capaddtest1") 1407 c.Assert(err, checker.IsNil) 1408 } 1409 1410 // #14915 1411 func (s *DockerSuite) TestContainerAPICreateNoHostConfig118(c *check.C) { 1412 testRequires(c, DaemonIsLinux) // Windows only support 1.25 or later 1413 config := containertypes.Config{ 1414 Image: "busybox", 1415 } 1416 1417 cli, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("v1.18")) 1418 c.Assert(err, checker.IsNil) 1419 1420 _, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "") 1421 c.Assert(err, checker.IsNil) 1422 } 1423 1424 // Ensure an error occurs when you have a container read-only rootfs but you 1425 // extract an archive to a symlink in a writable volume which points to a 1426 // directory outside of the volume. 1427 func (s *DockerSuite) TestPutContainerArchiveErrSymlinkInVolumeToReadOnlyRootfs(c *check.C) { 1428 // Windows does not support read-only rootfs 1429 // Requires local volume mount bind. 1430 // --read-only + userns has remount issues 1431 testRequires(c, testEnv.IsLocalDaemon, NotUserNamespace, DaemonIsLinux) 1432 1433 testVol := getTestDir(c, "test-put-container-archive-err-symlink-in-volume-to-read-only-rootfs-") 1434 defer os.RemoveAll(testVol) 1435 1436 makeTestContentInDir(c, testVol) 1437 1438 cID := makeTestContainer(c, testContainerOptions{ 1439 readOnly: true, 1440 volumes: defaultVolumes(testVol), // Our bind mount is at /vol2 1441 }) 1442 1443 // Attempt to extract to a symlink in the volume which points to a 1444 // directory outside the volume. This should cause an error because the 1445 // rootfs is read-only. 1446 cli, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("v1.20")) 1447 c.Assert(err, checker.IsNil) 1448 1449 err = cli.CopyToContainer(context.Background(), cID, "/vol2/symlinkToAbsDir", nil, types.CopyToContainerOptions{}) 1450 c.Assert(err.Error(), checker.Contains, "container rootfs is marked read-only") 1451 } 1452 1453 func (s *DockerSuite) TestPostContainersCreateWithWrongCpusetValues(c *check.C) { 1454 // Not supported on Windows 1455 testRequires(c, DaemonIsLinux) 1456 1457 cli, err := client.NewClientWithOpts(client.FromEnv) 1458 c.Assert(err, checker.IsNil) 1459 defer cli.Close() 1460 1461 config := containertypes.Config{ 1462 Image: "busybox", 1463 } 1464 hostConfig1 := containertypes.HostConfig{ 1465 Resources: containertypes.Resources{ 1466 CpusetCpus: "1-42,,", 1467 }, 1468 } 1469 name := "wrong-cpuset-cpus" 1470 1471 _, err = cli.ContainerCreate(context.Background(), &config, &hostConfig1, &networktypes.NetworkingConfig{}, name) 1472 expected := "Invalid value 1-42,, for cpuset cpus" 1473 c.Assert(err.Error(), checker.Contains, expected) 1474 1475 hostConfig2 := containertypes.HostConfig{ 1476 Resources: containertypes.Resources{ 1477 CpusetMems: "42-3,1--", 1478 }, 1479 } 1480 name = "wrong-cpuset-mems" 1481 _, err = cli.ContainerCreate(context.Background(), &config, &hostConfig2, &networktypes.NetworkingConfig{}, name) 1482 expected = "Invalid value 42-3,1-- for cpuset mems" 1483 c.Assert(err.Error(), checker.Contains, expected) 1484 } 1485 1486 func (s *DockerSuite) TestPostContainersCreateShmSizeNegative(c *check.C) { 1487 // ShmSize is not supported on Windows 1488 testRequires(c, DaemonIsLinux) 1489 config := containertypes.Config{ 1490 Image: "busybox", 1491 } 1492 hostConfig := containertypes.HostConfig{ 1493 ShmSize: -1, 1494 } 1495 1496 cli, err := client.NewClientWithOpts(client.FromEnv) 1497 c.Assert(err, checker.IsNil) 1498 defer cli.Close() 1499 1500 _, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, "") 1501 c.Assert(err.Error(), checker.Contains, "SHM size can not be less than 0") 1502 } 1503 1504 func (s *DockerSuite) TestPostContainersCreateShmSizeHostConfigOmitted(c *check.C) { 1505 // ShmSize is not supported on Windows 1506 testRequires(c, DaemonIsLinux) 1507 var defaultSHMSize int64 = 67108864 1508 config := containertypes.Config{ 1509 Image: "busybox", 1510 Cmd: []string{"mount"}, 1511 } 1512 1513 cli, err := client.NewClientWithOpts(client.FromEnv) 1514 c.Assert(err, checker.IsNil) 1515 defer cli.Close() 1516 1517 container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "") 1518 c.Assert(err, check.IsNil) 1519 1520 containerJSON, err := cli.ContainerInspect(context.Background(), container.ID) 1521 c.Assert(err, check.IsNil) 1522 1523 c.Assert(containerJSON.HostConfig.ShmSize, check.Equals, defaultSHMSize) 1524 1525 out, _ := dockerCmd(c, "start", "-i", containerJSON.ID) 1526 shmRegexp := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=65536k`) 1527 if !shmRegexp.MatchString(out) { 1528 c.Fatalf("Expected shm of 64MB in mount command, got %v", out) 1529 } 1530 } 1531 1532 func (s *DockerSuite) TestPostContainersCreateShmSizeOmitted(c *check.C) { 1533 // ShmSize is not supported on Windows 1534 testRequires(c, DaemonIsLinux) 1535 config := containertypes.Config{ 1536 Image: "busybox", 1537 Cmd: []string{"mount"}, 1538 } 1539 1540 cli, err := client.NewClientWithOpts(client.FromEnv) 1541 c.Assert(err, checker.IsNil) 1542 defer cli.Close() 1543 1544 container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "") 1545 c.Assert(err, check.IsNil) 1546 1547 containerJSON, err := cli.ContainerInspect(context.Background(), container.ID) 1548 c.Assert(err, check.IsNil) 1549 1550 c.Assert(containerJSON.HostConfig.ShmSize, check.Equals, int64(67108864)) 1551 1552 out, _ := dockerCmd(c, "start", "-i", containerJSON.ID) 1553 shmRegexp := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=65536k`) 1554 if !shmRegexp.MatchString(out) { 1555 c.Fatalf("Expected shm of 64MB in mount command, got %v", out) 1556 } 1557 } 1558 1559 func (s *DockerSuite) TestPostContainersCreateWithShmSize(c *check.C) { 1560 // ShmSize is not supported on Windows 1561 testRequires(c, DaemonIsLinux) 1562 config := containertypes.Config{ 1563 Image: "busybox", 1564 Cmd: []string{"mount"}, 1565 } 1566 1567 hostConfig := containertypes.HostConfig{ 1568 ShmSize: 1073741824, 1569 } 1570 1571 cli, err := client.NewClientWithOpts(client.FromEnv) 1572 c.Assert(err, checker.IsNil) 1573 defer cli.Close() 1574 1575 container, err := cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, "") 1576 c.Assert(err, check.IsNil) 1577 1578 containerJSON, err := cli.ContainerInspect(context.Background(), container.ID) 1579 c.Assert(err, check.IsNil) 1580 1581 c.Assert(containerJSON.HostConfig.ShmSize, check.Equals, int64(1073741824)) 1582 1583 out, _ := dockerCmd(c, "start", "-i", containerJSON.ID) 1584 shmRegex := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=1048576k`) 1585 if !shmRegex.MatchString(out) { 1586 c.Fatalf("Expected shm of 1GB in mount command, got %v", out) 1587 } 1588 } 1589 1590 func (s *DockerSuite) TestPostContainersCreateMemorySwappinessHostConfigOmitted(c *check.C) { 1591 // Swappiness is not supported on Windows 1592 testRequires(c, DaemonIsLinux) 1593 config := containertypes.Config{ 1594 Image: "busybox", 1595 } 1596 1597 cli, err := client.NewClientWithOpts(client.FromEnv) 1598 c.Assert(err, checker.IsNil) 1599 defer cli.Close() 1600 1601 container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "") 1602 c.Assert(err, check.IsNil) 1603 1604 containerJSON, err := cli.ContainerInspect(context.Background(), container.ID) 1605 c.Assert(err, check.IsNil) 1606 1607 if versions.LessThan(testEnv.DaemonAPIVersion(), "1.31") { 1608 c.Assert(*containerJSON.HostConfig.MemorySwappiness, check.Equals, int64(-1)) 1609 } else { 1610 c.Assert(containerJSON.HostConfig.MemorySwappiness, check.IsNil) 1611 } 1612 } 1613 1614 // check validation is done daemon side and not only in cli 1615 func (s *DockerSuite) TestPostContainersCreateWithOomScoreAdjInvalidRange(c *check.C) { 1616 // OomScoreAdj is not supported on Windows 1617 testRequires(c, DaemonIsLinux) 1618 1619 config := containertypes.Config{ 1620 Image: "busybox", 1621 } 1622 1623 hostConfig := containertypes.HostConfig{ 1624 OomScoreAdj: 1001, 1625 } 1626 1627 cli, err := client.NewClientWithOpts(client.FromEnv) 1628 c.Assert(err, checker.IsNil) 1629 defer cli.Close() 1630 1631 name := "oomscoreadj-over" 1632 _, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, name) 1633 1634 expected := "Invalid value 1001, range for oom score adj is [-1000, 1000]" 1635 c.Assert(err.Error(), checker.Contains, expected) 1636 1637 hostConfig = containertypes.HostConfig{ 1638 OomScoreAdj: -1001, 1639 } 1640 1641 name = "oomscoreadj-low" 1642 _, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, name) 1643 1644 expected = "Invalid value -1001, range for oom score adj is [-1000, 1000]" 1645 c.Assert(err.Error(), checker.Contains, expected) 1646 } 1647 1648 // test case for #22210 where an empty container name caused panic. 1649 func (s *DockerSuite) TestContainerAPIDeleteWithEmptyName(c *check.C) { 1650 cli, err := client.NewClientWithOpts(client.FromEnv) 1651 c.Assert(err, checker.IsNil) 1652 defer cli.Close() 1653 1654 err = cli.ContainerRemove(context.Background(), "", types.ContainerRemoveOptions{}) 1655 c.Assert(err.Error(), checker.Contains, "No such container") 1656 } 1657 1658 func (s *DockerSuite) TestContainerAPIStatsWithNetworkDisabled(c *check.C) { 1659 // Problematic on Windows as Windows does not support stats 1660 testRequires(c, DaemonIsLinux) 1661 1662 name := "testing-network-disabled" 1663 1664 config := containertypes.Config{ 1665 Image: "busybox", 1666 Cmd: []string{"top"}, 1667 NetworkDisabled: true, 1668 } 1669 1670 cli, err := client.NewClientWithOpts(client.FromEnv) 1671 c.Assert(err, checker.IsNil) 1672 defer cli.Close() 1673 1674 _, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, name) 1675 c.Assert(err, checker.IsNil) 1676 1677 err = cli.ContainerStart(context.Background(), name, types.ContainerStartOptions{}) 1678 c.Assert(err, checker.IsNil) 1679 1680 c.Assert(waitRun(name), check.IsNil) 1681 1682 type b struct { 1683 stats types.ContainerStats 1684 err error 1685 } 1686 bc := make(chan b, 1) 1687 go func() { 1688 stats, err := cli.ContainerStats(context.Background(), name, false) 1689 bc <- b{stats, err} 1690 }() 1691 1692 // allow some time to stream the stats from the container 1693 time.Sleep(4 * time.Second) 1694 dockerCmd(c, "rm", "-f", name) 1695 1696 // collect the results from the stats stream or timeout and fail 1697 // if the stream was not disconnected. 1698 select { 1699 case <-time.After(2 * time.Second): 1700 c.Fatal("stream was not closed after container was removed") 1701 case sr := <-bc: 1702 c.Assert(sr.err, checker.IsNil) 1703 sr.stats.Body.Close() 1704 } 1705 } 1706 1707 func (s *DockerSuite) TestContainersAPICreateMountsValidation(c *check.C) { 1708 type testCase struct { 1709 config containertypes.Config 1710 hostConfig containertypes.HostConfig 1711 msg string 1712 } 1713 1714 prefix, slash := getPrefixAndSlashFromDaemonPlatform() 1715 destPath := prefix + slash + "foo" 1716 notExistPath := prefix + slash + "notexist" 1717 1718 cases := []testCase{ 1719 { 1720 config: containertypes.Config{ 1721 Image: "busybox", 1722 }, 1723 hostConfig: containertypes.HostConfig{ 1724 Mounts: []mounttypes.Mount{{ 1725 Type: "notreal", 1726 Target: destPath, 1727 }, 1728 }, 1729 }, 1730 1731 msg: "mount type unknown", 1732 }, 1733 { 1734 config: containertypes.Config{ 1735 Image: "busybox", 1736 }, 1737 hostConfig: containertypes.HostConfig{ 1738 Mounts: []mounttypes.Mount{{ 1739 Type: "bind"}}}, 1740 msg: "Target must not be empty", 1741 }, 1742 { 1743 config: containertypes.Config{ 1744 Image: "busybox", 1745 }, 1746 hostConfig: containertypes.HostConfig{ 1747 Mounts: []mounttypes.Mount{{ 1748 Type: "bind", 1749 Target: destPath}}}, 1750 msg: "Source must not be empty", 1751 }, 1752 { 1753 config: containertypes.Config{ 1754 Image: "busybox", 1755 }, 1756 hostConfig: containertypes.HostConfig{ 1757 Mounts: []mounttypes.Mount{{ 1758 Type: "bind", 1759 Source: notExistPath, 1760 Target: destPath}}}, 1761 msg: "source path does not exist", 1762 // FIXME(vdemeester) fails into e2e, migrate to integration/container anyway 1763 // msg: "source path does not exist: " + notExistPath, 1764 }, 1765 { 1766 config: containertypes.Config{ 1767 Image: "busybox", 1768 }, 1769 hostConfig: containertypes.HostConfig{ 1770 Mounts: []mounttypes.Mount{{ 1771 Type: "volume"}}}, 1772 msg: "Target must not be empty", 1773 }, 1774 { 1775 config: containertypes.Config{ 1776 Image: "busybox", 1777 }, 1778 hostConfig: containertypes.HostConfig{ 1779 Mounts: []mounttypes.Mount{{ 1780 Type: "volume", 1781 Source: "hello", 1782 Target: destPath}}}, 1783 msg: "", 1784 }, 1785 { 1786 config: containertypes.Config{ 1787 Image: "busybox", 1788 }, 1789 hostConfig: containertypes.HostConfig{ 1790 Mounts: []mounttypes.Mount{{ 1791 Type: "volume", 1792 Source: "hello2", 1793 Target: destPath, 1794 VolumeOptions: &mounttypes.VolumeOptions{ 1795 DriverConfig: &mounttypes.Driver{ 1796 Name: "local"}}}}}, 1797 msg: "", 1798 }, 1799 } 1800 1801 if testEnv.IsLocalDaemon() { 1802 tmpDir, err := ioutils.TempDir("", "test-mounts-api") 1803 c.Assert(err, checker.IsNil) 1804 defer os.RemoveAll(tmpDir) 1805 cases = append(cases, []testCase{ 1806 { 1807 config: containertypes.Config{ 1808 Image: "busybox", 1809 }, 1810 hostConfig: containertypes.HostConfig{ 1811 Mounts: []mounttypes.Mount{{ 1812 Type: "bind", 1813 Source: tmpDir, 1814 Target: destPath}}}, 1815 msg: "", 1816 }, 1817 { 1818 config: containertypes.Config{ 1819 Image: "busybox", 1820 }, 1821 hostConfig: containertypes.HostConfig{ 1822 Mounts: []mounttypes.Mount{{ 1823 Type: "bind", 1824 Source: tmpDir, 1825 Target: destPath, 1826 VolumeOptions: &mounttypes.VolumeOptions{}}}}, 1827 msg: "VolumeOptions must not be specified", 1828 }, 1829 }...) 1830 } 1831 1832 if DaemonIsWindows() { 1833 cases = append(cases, []testCase{ 1834 { 1835 config: containertypes.Config{ 1836 Image: "busybox", 1837 }, 1838 hostConfig: containertypes.HostConfig{ 1839 Mounts: []mounttypes.Mount{ 1840 { 1841 Type: "volume", 1842 Source: "not-supported-on-windows", 1843 Target: destPath, 1844 VolumeOptions: &mounttypes.VolumeOptions{ 1845 DriverConfig: &mounttypes.Driver{ 1846 Name: "local", 1847 Options: map[string]string{"type": "tmpfs"}, 1848 }, 1849 }, 1850 }, 1851 }, 1852 }, 1853 msg: `options are not supported on this platform`, 1854 }, 1855 }...) 1856 } 1857 1858 if DaemonIsLinux() { 1859 cases = append(cases, []testCase{ 1860 { 1861 config: containertypes.Config{ 1862 Image: "busybox", 1863 }, 1864 hostConfig: containertypes.HostConfig{ 1865 Mounts: []mounttypes.Mount{ 1866 { 1867 Type: "volume", 1868 Source: "missing-device-opt", 1869 Target: destPath, 1870 VolumeOptions: &mounttypes.VolumeOptions{ 1871 DriverConfig: &mounttypes.Driver{ 1872 Name: "local", 1873 Options: map[string]string{"foobar": "foobaz"}, 1874 }, 1875 }, 1876 }, 1877 }, 1878 }, 1879 msg: `invalid option: "foobar"`, 1880 }, 1881 { 1882 config: containertypes.Config{ 1883 Image: "busybox", 1884 }, 1885 hostConfig: containertypes.HostConfig{ 1886 Mounts: []mounttypes.Mount{ 1887 { 1888 Type: "volume", 1889 Source: "missing-device-opt", 1890 Target: destPath, 1891 VolumeOptions: &mounttypes.VolumeOptions{ 1892 DriverConfig: &mounttypes.Driver{ 1893 Name: "local", 1894 Options: map[string]string{"type": "tmpfs"}, 1895 }, 1896 }, 1897 }, 1898 }, 1899 }, 1900 msg: `missing required option: "device"`, 1901 }, 1902 { 1903 config: containertypes.Config{ 1904 Image: "busybox", 1905 }, 1906 hostConfig: containertypes.HostConfig{ 1907 Mounts: []mounttypes.Mount{ 1908 { 1909 Type: "volume", 1910 Source: "missing-type-opt", 1911 Target: destPath, 1912 VolumeOptions: &mounttypes.VolumeOptions{ 1913 DriverConfig: &mounttypes.Driver{ 1914 Name: "local", 1915 Options: map[string]string{"device": "tmpfs"}, 1916 }, 1917 }, 1918 }, 1919 }, 1920 }, 1921 msg: `missing required option: "type"`, 1922 }, 1923 { 1924 config: containertypes.Config{ 1925 Image: "busybox", 1926 }, 1927 hostConfig: containertypes.HostConfig{ 1928 Mounts: []mounttypes.Mount{ 1929 { 1930 Type: "volume", 1931 Source: "hello4", 1932 Target: destPath, 1933 VolumeOptions: &mounttypes.VolumeOptions{ 1934 DriverConfig: &mounttypes.Driver{ 1935 Name: "local", 1936 Options: map[string]string{"o": "size=1", "type": "tmpfs", "device": "tmpfs"}, 1937 }, 1938 }, 1939 }, 1940 }, 1941 }, 1942 msg: "", 1943 }, 1944 { 1945 config: containertypes.Config{ 1946 Image: "busybox", 1947 }, 1948 hostConfig: containertypes.HostConfig{ 1949 Mounts: []mounttypes.Mount{{ 1950 Type: "tmpfs", 1951 Target: destPath}}}, 1952 msg: "", 1953 }, 1954 { 1955 config: containertypes.Config{ 1956 Image: "busybox", 1957 }, 1958 hostConfig: containertypes.HostConfig{ 1959 Mounts: []mounttypes.Mount{{ 1960 Type: "tmpfs", 1961 Target: destPath, 1962 TmpfsOptions: &mounttypes.TmpfsOptions{ 1963 SizeBytes: 4096 * 1024, 1964 Mode: 0700, 1965 }}}}, 1966 msg: "", 1967 }, 1968 { 1969 config: containertypes.Config{ 1970 Image: "busybox", 1971 }, 1972 hostConfig: containertypes.HostConfig{ 1973 Mounts: []mounttypes.Mount{{ 1974 Type: "tmpfs", 1975 Source: "/shouldnotbespecified", 1976 Target: destPath}}}, 1977 msg: "Source must not be specified", 1978 }, 1979 }...) 1980 1981 } 1982 cli, err := client.NewClientWithOpts(client.FromEnv) 1983 c.Assert(err, checker.IsNil) 1984 defer cli.Close() 1985 1986 // TODO add checks for statuscode returned by API 1987 for i, x := range cases { 1988 c.Logf("case %d", i) 1989 _, err = cli.ContainerCreate(context.Background(), &x.config, &x.hostConfig, &networktypes.NetworkingConfig{}, "") 1990 if len(x.msg) > 0 { 1991 c.Assert(err.Error(), checker.Contains, x.msg, check.Commentf("%v", cases[i].config)) 1992 } else { 1993 c.Assert(err, checker.IsNil) 1994 } 1995 } 1996 } 1997 1998 func (s *DockerSuite) TestContainerAPICreateMountsBindRead(c *check.C) { 1999 testRequires(c, NotUserNamespace, testEnv.IsLocalDaemon) 2000 // also with data in the host side 2001 prefix, slash := getPrefixAndSlashFromDaemonPlatform() 2002 destPath := prefix + slash + "foo" 2003 tmpDir, err := ioutil.TempDir("", "test-mounts-api-bind") 2004 c.Assert(err, checker.IsNil) 2005 defer os.RemoveAll(tmpDir) 2006 err = ioutil.WriteFile(filepath.Join(tmpDir, "bar"), []byte("hello"), 666) 2007 c.Assert(err, checker.IsNil) 2008 config := containertypes.Config{ 2009 Image: "busybox", 2010 Cmd: []string{"/bin/sh", "-c", "cat /foo/bar"}, 2011 } 2012 hostConfig := containertypes.HostConfig{ 2013 Mounts: []mounttypes.Mount{ 2014 {Type: "bind", Source: tmpDir, Target: destPath}, 2015 }, 2016 } 2017 cli, err := client.NewClientWithOpts(client.FromEnv) 2018 c.Assert(err, checker.IsNil) 2019 defer cli.Close() 2020 2021 _, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, "test") 2022 c.Assert(err, checker.IsNil) 2023 2024 out, _ := dockerCmd(c, "start", "-a", "test") 2025 c.Assert(out, checker.Equals, "hello") 2026 } 2027 2028 // Test Mounts comes out as expected for the MountPoint 2029 func (s *DockerSuite) TestContainersAPICreateMountsCreate(c *check.C) { 2030 prefix, slash := getPrefixAndSlashFromDaemonPlatform() 2031 destPath := prefix + slash + "foo" 2032 2033 var ( 2034 testImg string 2035 ) 2036 if testEnv.OSType != "windows" { 2037 testImg = "test-mount-config" 2038 buildImageSuccessfully(c, testImg, build.WithDockerfile(` 2039 FROM busybox 2040 RUN mkdir `+destPath+` && touch `+destPath+slash+`bar 2041 CMD cat `+destPath+slash+`bar 2042 `)) 2043 } else { 2044 testImg = "busybox" 2045 } 2046 2047 type testCase struct { 2048 spec mounttypes.Mount 2049 expected types.MountPoint 2050 } 2051 2052 var selinuxSharedLabel string 2053 // this test label was added after a bug fix in 1.32, thus add requirements min API >= 1.32 2054 // for the sake of making test pass in earlier versions 2055 // bug fixed in https://github.com/moby/moby/pull/34684 2056 if !versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") { 2057 if runtime.GOOS == "linux" { 2058 selinuxSharedLabel = "z" 2059 } 2060 } 2061 2062 cases := []testCase{ 2063 // use literal strings here for `Type` instead of the defined constants in the volume package to keep this honest 2064 // Validation of the actual `Mount` struct is done in another test is not needed here 2065 { 2066 spec: mounttypes.Mount{Type: "volume", Target: destPath}, 2067 expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2068 }, 2069 { 2070 spec: mounttypes.Mount{Type: "volume", Target: destPath + slash}, 2071 expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2072 }, 2073 { 2074 spec: mounttypes.Mount{Type: "volume", Target: destPath, Source: "test1"}, 2075 expected: types.MountPoint{Type: "volume", Name: "test1", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2076 }, 2077 { 2078 spec: mounttypes.Mount{Type: "volume", Target: destPath, ReadOnly: true, Source: "test2"}, 2079 expected: types.MountPoint{Type: "volume", Name: "test2", RW: false, Destination: destPath, Mode: selinuxSharedLabel}, 2080 }, 2081 { 2082 spec: mounttypes.Mount{Type: "volume", Target: destPath, Source: "test3", VolumeOptions: &mounttypes.VolumeOptions{DriverConfig: &mounttypes.Driver{Name: volume.DefaultDriverName}}}, 2083 expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", Name: "test3", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2084 }, 2085 } 2086 2087 if testEnv.IsLocalDaemon() { 2088 // setup temp dir for testing binds 2089 tmpDir1, err := ioutil.TempDir("", "test-mounts-api-1") 2090 c.Assert(err, checker.IsNil) 2091 defer os.RemoveAll(tmpDir1) 2092 cases = append(cases, []testCase{ 2093 { 2094 spec: mounttypes.Mount{ 2095 Type: "bind", 2096 Source: tmpDir1, 2097 Target: destPath, 2098 }, 2099 expected: types.MountPoint{ 2100 Type: "bind", 2101 RW: true, 2102 Destination: destPath, 2103 Source: tmpDir1, 2104 }, 2105 }, 2106 { 2107 spec: mounttypes.Mount{Type: "bind", Source: tmpDir1, Target: destPath, ReadOnly: true}, 2108 expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir1}, 2109 }, 2110 }...) 2111 2112 // for modes only supported on Linux 2113 if DaemonIsLinux() { 2114 tmpDir3, err := ioutils.TempDir("", "test-mounts-api-3") 2115 c.Assert(err, checker.IsNil) 2116 defer os.RemoveAll(tmpDir3) 2117 2118 c.Assert(mount.Mount(tmpDir3, tmpDir3, "none", "bind,rw"), checker.IsNil) 2119 c.Assert(mount.ForceMount("", tmpDir3, "none", "shared"), checker.IsNil) 2120 2121 cases = append(cases, []testCase{ 2122 { 2123 spec: mounttypes.Mount{Type: "bind", Source: tmpDir3, Target: destPath}, 2124 expected: types.MountPoint{Type: "bind", RW: true, Destination: destPath, Source: tmpDir3}, 2125 }, 2126 { 2127 spec: mounttypes.Mount{Type: "bind", Source: tmpDir3, Target: destPath, ReadOnly: true}, 2128 expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir3}, 2129 }, 2130 { 2131 spec: mounttypes.Mount{Type: "bind", Source: tmpDir3, Target: destPath, ReadOnly: true, BindOptions: &mounttypes.BindOptions{Propagation: "shared"}}, 2132 expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir3, Propagation: "shared"}, 2133 }, 2134 }...) 2135 } 2136 } 2137 2138 if testEnv.OSType != "windows" { // Windows does not support volume populate 2139 cases = append(cases, []testCase{ 2140 { 2141 spec: mounttypes.Mount{Type: "volume", Target: destPath, VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true}}, 2142 expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2143 }, 2144 { 2145 spec: mounttypes.Mount{Type: "volume", Target: destPath + slash, VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true}}, 2146 expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2147 }, 2148 { 2149 spec: mounttypes.Mount{Type: "volume", Target: destPath, Source: "test4", VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true}}, 2150 expected: types.MountPoint{Type: "volume", Name: "test4", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2151 }, 2152 { 2153 spec: mounttypes.Mount{Type: "volume", Target: destPath, Source: "test5", ReadOnly: true, VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true}}, 2154 expected: types.MountPoint{Type: "volume", Name: "test5", RW: false, Destination: destPath, Mode: selinuxSharedLabel}, 2155 }, 2156 }...) 2157 } 2158 2159 type wrapper struct { 2160 containertypes.Config 2161 HostConfig containertypes.HostConfig 2162 } 2163 type createResp struct { 2164 ID string `json:"Id"` 2165 } 2166 2167 ctx := context.Background() 2168 apiclient := testEnv.APIClient() 2169 for i, x := range cases { 2170 c.Logf("case %d - config: %v", i, x.spec) 2171 container, err := apiclient.ContainerCreate( 2172 ctx, 2173 &containertypes.Config{Image: testImg}, 2174 &containertypes.HostConfig{Mounts: []mounttypes.Mount{x.spec}}, 2175 &networktypes.NetworkingConfig{}, 2176 "") 2177 assert.NilError(c, err) 2178 2179 containerInspect, err := apiclient.ContainerInspect(ctx, container.ID) 2180 assert.NilError(c, err) 2181 mps := containerInspect.Mounts 2182 assert.Assert(c, is.Len(mps, 1)) 2183 mountPoint := mps[0] 2184 2185 if x.expected.Source != "" { 2186 assert.Check(c, is.Equal(x.expected.Source, mountPoint.Source)) 2187 } 2188 if x.expected.Name != "" { 2189 assert.Check(c, is.Equal(x.expected.Name, mountPoint.Name)) 2190 } 2191 if x.expected.Driver != "" { 2192 assert.Check(c, is.Equal(x.expected.Driver, mountPoint.Driver)) 2193 } 2194 if x.expected.Propagation != "" { 2195 assert.Check(c, is.Equal(x.expected.Propagation, mountPoint.Propagation)) 2196 } 2197 assert.Check(c, is.Equal(x.expected.RW, mountPoint.RW)) 2198 assert.Check(c, is.Equal(x.expected.Type, mountPoint.Type)) 2199 assert.Check(c, is.Equal(x.expected.Mode, mountPoint.Mode)) 2200 assert.Check(c, is.Equal(x.expected.Destination, mountPoint.Destination)) 2201 2202 err = apiclient.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}) 2203 assert.NilError(c, err) 2204 poll.WaitOn(c, containerExit(apiclient, container.ID), poll.WithDelay(time.Second)) 2205 2206 err = apiclient.ContainerRemove(ctx, container.ID, types.ContainerRemoveOptions{ 2207 RemoveVolumes: true, 2208 Force: true, 2209 }) 2210 assert.NilError(c, err) 2211 2212 switch { 2213 2214 // Named volumes still exist after the container is removed 2215 case x.spec.Type == "volume" && len(x.spec.Source) > 0: 2216 _, err := apiclient.VolumeInspect(ctx, mountPoint.Name) 2217 assert.NilError(c, err) 2218 2219 // Bind mounts are never removed with the container 2220 case x.spec.Type == "bind": 2221 2222 // anonymous volumes are removed 2223 default: 2224 _, err := apiclient.VolumeInspect(ctx, mountPoint.Name) 2225 assert.Check(c, client.IsErrNotFound(err)) 2226 } 2227 } 2228 } 2229 2230 func containerExit(apiclient client.APIClient, name string) func(poll.LogT) poll.Result { 2231 return func(logT poll.LogT) poll.Result { 2232 container, err := apiclient.ContainerInspect(context.Background(), name) 2233 if err != nil { 2234 return poll.Error(err) 2235 } 2236 switch container.State.Status { 2237 case "created", "running": 2238 return poll.Continue("container %s is %s, waiting for exit", name, container.State.Status) 2239 } 2240 return poll.Success() 2241 } 2242 } 2243 2244 func (s *DockerSuite) TestContainersAPICreateMountsTmpfs(c *check.C) { 2245 testRequires(c, DaemonIsLinux) 2246 type testCase struct { 2247 cfg mounttypes.Mount 2248 expectedOptions []string 2249 } 2250 target := "/foo" 2251 cases := []testCase{ 2252 { 2253 cfg: mounttypes.Mount{ 2254 Type: "tmpfs", 2255 Target: target}, 2256 expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime"}, 2257 }, 2258 { 2259 cfg: mounttypes.Mount{ 2260 Type: "tmpfs", 2261 Target: target, 2262 TmpfsOptions: &mounttypes.TmpfsOptions{ 2263 SizeBytes: 4096 * 1024, Mode: 0700}}, 2264 expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime", "size=4096k", "mode=700"}, 2265 }, 2266 } 2267 2268 cli, err := client.NewClientWithOpts(client.FromEnv) 2269 c.Assert(err, checker.IsNil) 2270 defer cli.Close() 2271 2272 config := containertypes.Config{ 2273 Image: "busybox", 2274 Cmd: []string{"/bin/sh", "-c", fmt.Sprintf("mount | grep 'tmpfs on %s'", target)}, 2275 } 2276 for i, x := range cases { 2277 cName := fmt.Sprintf("test-tmpfs-%d", i) 2278 hostConfig := containertypes.HostConfig{ 2279 Mounts: []mounttypes.Mount{x.cfg}, 2280 } 2281 2282 _, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, cName) 2283 c.Assert(err, checker.IsNil) 2284 out, _ := dockerCmd(c, "start", "-a", cName) 2285 for _, option := range x.expectedOptions { 2286 c.Assert(out, checker.Contains, option) 2287 } 2288 } 2289 } 2290 2291 // Regression test for #33334 2292 // Makes sure that when a container which has a custom stop signal + restart=always 2293 // gets killed (with SIGKILL) by the kill API, that the restart policy is cancelled. 2294 func (s *DockerSuite) TestContainerKillCustomStopSignal(c *check.C) { 2295 id := strings.TrimSpace(runSleepingContainer(c, "--stop-signal=SIGTERM", "--restart=always")) 2296 res, _, err := request.Post("/containers/" + id + "/kill") 2297 c.Assert(err, checker.IsNil) 2298 defer res.Body.Close() 2299 2300 b, err := ioutil.ReadAll(res.Body) 2301 c.Assert(err, checker.IsNil) 2302 c.Assert(res.StatusCode, checker.Equals, http.StatusNoContent, check.Commentf(string(b))) 2303 err = waitInspect(id, "{{.State.Running}} {{.State.Restarting}}", "false false", 30*time.Second) 2304 c.Assert(err, checker.IsNil) 2305 }