github.com/zhouyu0/docker-note@v0.0.0-20190722021225-b8d3825084db/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 assert.NilError(c, err) 48 defer cli.Close() 49 50 options := types.ContainerListOptions{ 51 All: true, 52 } 53 containers, err := cli.ContainerList(context.Background(), options) 54 assert.NilError(c, err) 55 assert.Equal(c, len(containers), 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 assert.NilError(c, err) 67 defer cli.Close() 68 69 options := types.ContainerListOptions{ 70 All: true, 71 } 72 containers, err := cli.ContainerList(context.Background(), options) 73 assert.NilError(c, err) 74 assert.Equal(c, len(containers), 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 assert.NilError(c, err) 116 defer cli.Close() 117 118 options := types.ContainerListOptions{ 119 All: true, 120 } 121 containers, err := cli.ContainerList(context.Background(), options) 122 assert.NilError(c, err) 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 assert.NilError(c, err) 148 defer cli.Close() 149 150 body, err := cli.ContainerExport(context.Background(), name) 151 assert.NilError(c, err) 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 assert.NilError(c, err) 175 defer cli.Close() 176 177 changes, err := cli.ContainerDiff(context.Background(), name) 178 assert.NilError(c, err) 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 assert.NilError(c, err) 205 defer cli.Close() 206 207 stats, err := cli.ContainerStats(context.Background(), name, true) 208 assert.NilError(c, err) 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 assert.NilError(c, dec.Decode(&s)) 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 assert.NilError(c, err) 239 defer cli.Close() 240 241 stats, err := cli.ContainerStats(context.Background(), id, true) 242 assert.NilError(c, err) 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 assert.NilError(c, err) 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 assert.NilError(c, err) 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 assert.NilError(c, err) 310 defer cli.Close() 311 312 stats, err := cli.ContainerStats(context.Background(), name, true) 313 assert.NilError(c, err) 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 assert.NilError(c, err) 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 assert.NilError(c, err) 352 defer cli.Close() 353 354 stats, err := cli.ContainerStats(context.Background(), name, false) 355 assert.NilError(c, err) 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 assert.NilError(c, err) 372 s := string(b) 373 // count occurrences of `"read"` of types.Stats 374 assert.Assert(c, strings.Count(s, `"read"`) == 1, "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 assert.NilError(c, err) 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 assert.NilError(c, err) 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 assert.NilError(c, err) 417 defer cli.Close() 418 419 err = cli.ContainerPause(context.Background(), ContainerID) 420 assert.NilError(c, err) 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 assert.NilError(c, err) 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 assert.NilError(c, waitRun(id)) 440 441 cli, err := client.NewClientWithOpts(client.FromEnv) 442 assert.NilError(c, err) 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 assert.NilError(c, err) 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 assert.NilError(c, waitRun(id)) 463 464 cli, err := client.NewClientWithOpts(client.FromEnv) 465 assert.NilError(c, err) 466 defer cli.Close() 467 468 top, err := cli.ContainerTop(context.Background(), id, nil) 469 assert.NilError(c, err) 470 assert.Equal(c, len(top.Titles), 4, "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 assert.Assert(c, len(top.Processes) >= 2, "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 assert.Assert(c, foundProcess, "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 assert.NilError(c, err) 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 assert.NilError(c, err) 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 assert.NilError(c, err) 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 assert.NilError(c, err) 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 assert.NilError(c, err) 564 defer cli.Close() 565 566 _, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, "") 567 assert.ErrorContains(c, err, `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 assert.NilError(c, err) 578 defer cli.Close() 579 580 container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "") 581 assert.NilError(c, err) 582 583 out, _ := dockerCmd(c, "start", "-a", container.ID) 584 assert.Equal(c, strings.TrimSpace(out), "/test") 585 } 586 587 func (s *DockerSuite) TestContainerAPICreateEmptyConfig(c *check.C) { 588 589 cli, err := client.NewClientWithOpts(client.FromEnv) 590 assert.NilError(c, err) 591 defer cli.Close() 592 593 _, err = cli.ContainerCreate(context.Background(), &containertypes.Config{}, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "") 594 595 expected := "No command specified" 596 assert.ErrorContains(c, err, 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 assert.NilError(c, err) 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 assert.NilError(c, err) 637 defer cli.Close() 638 639 container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "") 640 assert.NilError(c, err) 641 642 containerJSON, err := cli.ContainerInspect(context.Background(), container.ID) 643 assert.NilError(c, err) 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 assert.NilError(c, err) 673 defer cli.Close() 674 675 container, err := cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, "") 676 assert.NilError(c, err) 677 678 containerJSON, err := cli.ContainerInspect(context.Background(), container.ID) 679 assert.NilError(c, err) 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 assert.NilError(c, err) 700 defer cli.Close() 701 702 container, err := cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, "") 703 assert.NilError(c, err) 704 705 containerJSON, err := cli.ContainerInspect(context.Background(), container.ID) 706 assert.NilError(c, err) 707 708 out := inspectField(c, containerJSON.ID, "HostConfig.CpuShares") 709 assert.Equal(c, out, "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 assert.NilError(c, err) 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 assert.Equal(c, res.StatusCode, 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 assert.NilError(c, err) 741 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 742 assert.Equal(c, res.StatusCode, 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 assert.NilError(c, err) 751 assert.Equal(c, res.StatusCode, 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 assert.NilError(c, err) 771 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 772 assert.Equal(c, res.StatusCode, http.StatusBadRequest) 773 } else { 774 c.Assert(res.StatusCode, checker.Not(checker.Equals), http.StatusOK) 775 } 776 777 b, err := request.ReadBody(body) 778 assert.NilError(c, err) 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 assert.NilError(c, err) 795 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 796 assert.Equal(c, res.StatusCode, http.StatusBadRequest) 797 } else { 798 c.Assert(res.StatusCode, checker.Not(checker.Equals), http.StatusOK) 799 } 800 801 b, err := request.ReadBody(body) 802 assert.NilError(c, err) 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 assert.NilError(c, err) 819 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 820 assert.Equal(c, res.StatusCode, http.StatusBadRequest) 821 } else { 822 c.Assert(res.StatusCode, checker.Not(checker.Equals), http.StatusOK) 823 } 824 825 b, err := request.ReadBody(body) 826 assert.NilError(c, err) 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 assert.NilError(c, err) 843 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 844 assert.Equal(c, res.StatusCode, http.StatusBadRequest) 845 } else { 846 c.Assert(res.StatusCode, checker.Not(checker.Equals), http.StatusOK) 847 } 848 849 b, err := request.ReadBody(body) 850 assert.NilError(c, err) 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 assert.NilError(c, err) 867 assert.Equal(c, res.StatusCode, 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 assert.NilError(c, err) 898 assert.Equal(c, res.StatusCode, http.StatusCreated) 899 900 b, err := request.ReadBody(body) 901 assert.NilError(c, err) 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 assert.Equal(c, out, "") 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 assert.NilError(c, err) 929 b, err2 := request.ReadBody(body) 930 c.Assert(err2, checker.IsNil) 931 932 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 933 assert.Equal(c, res.StatusCode, 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 assert.NilError(c, err) 948 defer cli.Close() 949 950 err = cli.ContainerRename(context.Background(), containerID, newName) 951 assert.NilError(c, err) 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 assert.NilError(c, err) 963 defer cli.Close() 964 965 err = cli.ContainerKill(context.Background(), name, "SIGKILL") 966 assert.NilError(c, err) 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 assert.NilError(c, err) 977 defer cli.Close() 978 979 timeout := 1 * time.Second 980 err = cli.ContainerRestart(context.Background(), name, &timeout) 981 assert.NilError(c, err) 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 assert.NilError(c, waitRun(id)) 991 992 cli, err := client.NewClientWithOpts(client.FromEnv) 993 assert.NilError(c, err) 994 defer cli.Close() 995 996 err = cli.ContainerRestart(context.Background(), name, nil) 997 assert.NilError(c, err) 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 assert.NilError(c, err) 1012 defer cli.Close() 1013 1014 _, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, name) 1015 assert.NilError(c, err) 1016 1017 err = cli.ContainerStart(context.Background(), name, types.ContainerStartOptions{}) 1018 assert.NilError(c, err) 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 assert.NilError(c, err) 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 assert.NilError(c, err) 1035 defer cli.Close() 1036 1037 err = cli.ContainerStop(context.Background(), name, &timeout) 1038 assert.NilError(c, err) 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 assert.NilError(c, err) 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 assert.NilError(c, err) 1058 defer cli.Close() 1059 1060 waitresC, errC := cli.ContainerWait(context.Background(), name, "") 1061 1062 select { 1063 case err = <-errC: 1064 assert.NilError(c, err) 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 assert.NilError(c, err) 1080 assert.Equal(c, res.StatusCode, 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 assert.NilError(c, err) 1094 assert.Equal(c, res.StatusCode, 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 assert.NilError(c, err) 1124 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 1125 assert.Equal(c, res.StatusCode, http.StatusBadRequest) 1126 } else { 1127 c.Assert(res.StatusCode, checker.Not(checker.Equals), http.StatusOK) 1128 } 1129 b, err := request.ReadBody(body) 1130 assert.NilError(c, err) 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 assert.NilError(c, err) 1145 if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") { 1146 assert.Equal(c, res.StatusCode, http.StatusInternalServerError) 1147 } else { 1148 assert.Equal(c, res.StatusCode, http.StatusNotFound) 1149 } 1150 b, err := request.ReadBody(body) 1151 assert.NilError(c, err) 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 assert.NilError(c, err) 1163 assert.Equal(c, res.StatusCode, http.StatusNotFound) 1164 } 1165 1166 func (s *DockerSuite) TestContainerAPIDelete(c *check.C) { 1167 out := runSleepingContainer(c) 1168 1169 id := strings.TrimSpace(out) 1170 assert.NilError(c, waitRun(id)) 1171 1172 dockerCmd(c, "stop", id) 1173 1174 cli, err := client.NewClientWithOpts(client.FromEnv) 1175 assert.NilError(c, err) 1176 defer cli.Close() 1177 1178 err = cli.ContainerRemove(context.Background(), id, types.ContainerRemoveOptions{}) 1179 assert.NilError(c, err) 1180 } 1181 1182 func (s *DockerSuite) TestContainerAPIDeleteNotExist(c *check.C) { 1183 cli, err := client.NewClientWithOpts(client.FromEnv) 1184 assert.NilError(c, err) 1185 defer cli.Close() 1186 1187 err = cli.ContainerRemove(context.Background(), "doesnotexist", types.ContainerRemoveOptions{}) 1188 assert.ErrorContains(c, err, "No such container: doesnotexist") 1189 } 1190 1191 func (s *DockerSuite) TestContainerAPIDeleteForce(c *check.C) { 1192 out := runSleepingContainer(c) 1193 id := strings.TrimSpace(out) 1194 assert.NilError(c, waitRun(id)) 1195 1196 removeOptions := types.ContainerRemoveOptions{ 1197 Force: true, 1198 } 1199 1200 cli, err := client.NewClientWithOpts(client.FromEnv) 1201 assert.NilError(c, err) 1202 defer cli.Close() 1203 1204 err = cli.ContainerRemove(context.Background(), id, removeOptions) 1205 assert.NilError(c, err) 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 assert.NilError(c, waitRun(id)) 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 assert.NilError(c, err) 1230 defer cli.Close() 1231 1232 err = cli.ContainerRemove(context.Background(), "tlink2/tlink1", removeOptions) 1233 assert.NilError(c, err) 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 assert.NilError(c, waitRun(id)) 1244 1245 cli, err := client.NewClientWithOpts(client.FromEnv) 1246 assert.NilError(c, err) 1247 defer cli.Close() 1248 1249 err = cli.ContainerRemove(context.Background(), id, types.ContainerRemoveOptions{}) 1250 expected := "cannot remove a running container" 1251 assert.ErrorContains(c, err, 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 assert.NilError(c, waitRun(id)) 1266 1267 source, err := inspectMountSourceField(id, vol) 1268 assert.NilError(c, err) 1269 _, err = os.Stat(source) 1270 assert.NilError(c, err) 1271 1272 removeOptions := types.ContainerRemoveOptions{ 1273 Force: true, 1274 RemoveVolumes: true, 1275 } 1276 1277 cli, err := client.NewClientWithOpts(client.FromEnv) 1278 assert.NilError(c, err) 1279 defer cli.Close() 1280 1281 err = cli.ContainerRemove(context.Background(), id, removeOptions) 1282 assert.NilError(c, err) 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 assert.Equal(c, resp.StatusCode, 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 assert.NilError(c, err) 1317 defer cli.Close() 1318 1319 err = cli.ContainerStop(context.Background(), containerID, nil) 1320 assert.NilError(c, err) 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 assert.NilError(c, err) 1334 defer cli.Close() 1335 1336 _, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "echotest") 1337 assert.NilError(c, err) 1338 out, _ := dockerCmd(c, "start", "-a", "echotest") 1339 assert.Equal(c, strings.TrimSpace(out), "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 assert.NilError(c, err) 1348 out, _ = dockerCmd(c, "start", "-a", "echotest2") 1349 assert.Equal(c, strings.TrimSpace(out), "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 assert.NilError(c, err) 1361 defer cli.Close() 1362 1363 _, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "echotest") 1364 assert.NilError(c, err) 1365 out, _ := dockerCmd(c, "start", "-a", "echotest") 1366 assert.Equal(c, strings.TrimSpace(out), "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 assert.NilError(c, err) 1375 out, _ = dockerCmd(c, "start", "-a", "echotest2") 1376 assert.Equal(c, strings.TrimSpace(out), "hello world") 1377 } 1378 1379 // regression #14318 1380 func (s *DockerSuite) TestPostContainersCreateWithStringOrSliceCapAddDrop(c *check.C) { 1381 // Windows doesn't support CapAdd/CapDrop 1382 testRequires(c, DaemonIsLinux) 1383 config := struct { 1384 Image string 1385 CapAdd string 1386 CapDrop string 1387 }{"busybox", "NET_ADMIN", "SYS_ADMIN"} 1388 res, _, err := request.Post("/containers/create?name=capaddtest0", request.JSONBody(config)) 1389 assert.NilError(c, err) 1390 assert.Equal(c, res.StatusCode, http.StatusCreated) 1391 1392 config2 := containertypes.Config{ 1393 Image: "busybox", 1394 } 1395 hostConfig := containertypes.HostConfig{ 1396 CapAdd: []string{"NET_ADMIN", "SYS_ADMIN"}, 1397 CapDrop: []string{"SETGID"}, 1398 } 1399 1400 cli, err := client.NewClientWithOpts(client.FromEnv) 1401 assert.NilError(c, err) 1402 defer cli.Close() 1403 1404 _, err = cli.ContainerCreate(context.Background(), &config2, &hostConfig, &networktypes.NetworkingConfig{}, "capaddtest1") 1405 assert.NilError(c, err) 1406 } 1407 1408 // #14915 1409 func (s *DockerSuite) TestContainerAPICreateNoHostConfig118(c *check.C) { 1410 testRequires(c, DaemonIsLinux) // Windows only support 1.25 or later 1411 config := containertypes.Config{ 1412 Image: "busybox", 1413 } 1414 1415 cli, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("v1.18")) 1416 assert.NilError(c, err) 1417 1418 _, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "") 1419 assert.NilError(c, err) 1420 } 1421 1422 // Ensure an error occurs when you have a container read-only rootfs but you 1423 // extract an archive to a symlink in a writable volume which points to a 1424 // directory outside of the volume. 1425 func (s *DockerSuite) TestPutContainerArchiveErrSymlinkInVolumeToReadOnlyRootfs(c *check.C) { 1426 // Windows does not support read-only rootfs 1427 // Requires local volume mount bind. 1428 // --read-only + userns has remount issues 1429 testRequires(c, testEnv.IsLocalDaemon, NotUserNamespace, DaemonIsLinux) 1430 1431 testVol := getTestDir(c, "test-put-container-archive-err-symlink-in-volume-to-read-only-rootfs-") 1432 defer os.RemoveAll(testVol) 1433 1434 makeTestContentInDir(c, testVol) 1435 1436 cID := makeTestContainer(c, testContainerOptions{ 1437 readOnly: true, 1438 volumes: defaultVolumes(testVol), // Our bind mount is at /vol2 1439 }) 1440 1441 // Attempt to extract to a symlink in the volume which points to a 1442 // directory outside the volume. This should cause an error because the 1443 // rootfs is read-only. 1444 cli, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("v1.20")) 1445 assert.NilError(c, err) 1446 1447 err = cli.CopyToContainer(context.Background(), cID, "/vol2/symlinkToAbsDir", nil, types.CopyToContainerOptions{}) 1448 assert.ErrorContains(c, err, "container rootfs is marked read-only") 1449 } 1450 1451 func (s *DockerSuite) TestPostContainersCreateWithWrongCpusetValues(c *check.C) { 1452 // Not supported on Windows 1453 testRequires(c, DaemonIsLinux) 1454 1455 cli, err := client.NewClientWithOpts(client.FromEnv) 1456 assert.NilError(c, err) 1457 defer cli.Close() 1458 1459 config := containertypes.Config{ 1460 Image: "busybox", 1461 } 1462 hostConfig1 := containertypes.HostConfig{ 1463 Resources: containertypes.Resources{ 1464 CpusetCpus: "1-42,,", 1465 }, 1466 } 1467 name := "wrong-cpuset-cpus" 1468 1469 _, err = cli.ContainerCreate(context.Background(), &config, &hostConfig1, &networktypes.NetworkingConfig{}, name) 1470 expected := "Invalid value 1-42,, for cpuset cpus" 1471 assert.ErrorContains(c, err, expected) 1472 1473 hostConfig2 := containertypes.HostConfig{ 1474 Resources: containertypes.Resources{ 1475 CpusetMems: "42-3,1--", 1476 }, 1477 } 1478 name = "wrong-cpuset-mems" 1479 _, err = cli.ContainerCreate(context.Background(), &config, &hostConfig2, &networktypes.NetworkingConfig{}, name) 1480 expected = "Invalid value 42-3,1-- for cpuset mems" 1481 assert.ErrorContains(c, err, expected) 1482 } 1483 1484 func (s *DockerSuite) TestPostContainersCreateShmSizeNegative(c *check.C) { 1485 // ShmSize is not supported on Windows 1486 testRequires(c, DaemonIsLinux) 1487 config := containertypes.Config{ 1488 Image: "busybox", 1489 } 1490 hostConfig := containertypes.HostConfig{ 1491 ShmSize: -1, 1492 } 1493 1494 cli, err := client.NewClientWithOpts(client.FromEnv) 1495 assert.NilError(c, err) 1496 defer cli.Close() 1497 1498 _, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, "") 1499 assert.ErrorContains(c, err, "SHM size can not be less than 0") 1500 } 1501 1502 func (s *DockerSuite) TestPostContainersCreateShmSizeHostConfigOmitted(c *check.C) { 1503 // ShmSize is not supported on Windows 1504 testRequires(c, DaemonIsLinux) 1505 var defaultSHMSize int64 = 67108864 1506 config := containertypes.Config{ 1507 Image: "busybox", 1508 Cmd: []string{"mount"}, 1509 } 1510 1511 cli, err := client.NewClientWithOpts(client.FromEnv) 1512 assert.NilError(c, err) 1513 defer cli.Close() 1514 1515 container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "") 1516 assert.NilError(c, err) 1517 1518 containerJSON, err := cli.ContainerInspect(context.Background(), container.ID) 1519 assert.NilError(c, err) 1520 1521 c.Assert(containerJSON.HostConfig.ShmSize, check.Equals, defaultSHMSize) 1522 1523 out, _ := dockerCmd(c, "start", "-i", containerJSON.ID) 1524 shmRegexp := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=65536k`) 1525 if !shmRegexp.MatchString(out) { 1526 c.Fatalf("Expected shm of 64MB in mount command, got %v", out) 1527 } 1528 } 1529 1530 func (s *DockerSuite) TestPostContainersCreateShmSizeOmitted(c *check.C) { 1531 // ShmSize is not supported on Windows 1532 testRequires(c, DaemonIsLinux) 1533 config := containertypes.Config{ 1534 Image: "busybox", 1535 Cmd: []string{"mount"}, 1536 } 1537 1538 cli, err := client.NewClientWithOpts(client.FromEnv) 1539 assert.NilError(c, err) 1540 defer cli.Close() 1541 1542 container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "") 1543 assert.NilError(c, err) 1544 1545 containerJSON, err := cli.ContainerInspect(context.Background(), container.ID) 1546 assert.NilError(c, err) 1547 1548 c.Assert(containerJSON.HostConfig.ShmSize, check.Equals, int64(67108864)) 1549 1550 out, _ := dockerCmd(c, "start", "-i", containerJSON.ID) 1551 shmRegexp := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=65536k`) 1552 if !shmRegexp.MatchString(out) { 1553 c.Fatalf("Expected shm of 64MB in mount command, got %v", out) 1554 } 1555 } 1556 1557 func (s *DockerSuite) TestPostContainersCreateWithShmSize(c *check.C) { 1558 // ShmSize is not supported on Windows 1559 testRequires(c, DaemonIsLinux) 1560 config := containertypes.Config{ 1561 Image: "busybox", 1562 Cmd: []string{"mount"}, 1563 } 1564 1565 hostConfig := containertypes.HostConfig{ 1566 ShmSize: 1073741824, 1567 } 1568 1569 cli, err := client.NewClientWithOpts(client.FromEnv) 1570 assert.NilError(c, err) 1571 defer cli.Close() 1572 1573 container, err := cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, "") 1574 assert.NilError(c, err) 1575 1576 containerJSON, err := cli.ContainerInspect(context.Background(), container.ID) 1577 assert.NilError(c, err) 1578 1579 c.Assert(containerJSON.HostConfig.ShmSize, check.Equals, int64(1073741824)) 1580 1581 out, _ := dockerCmd(c, "start", "-i", containerJSON.ID) 1582 shmRegex := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=1048576k`) 1583 if !shmRegex.MatchString(out) { 1584 c.Fatalf("Expected shm of 1GB in mount command, got %v", out) 1585 } 1586 } 1587 1588 func (s *DockerSuite) TestPostContainersCreateMemorySwappinessHostConfigOmitted(c *check.C) { 1589 // Swappiness is not supported on Windows 1590 testRequires(c, DaemonIsLinux) 1591 config := containertypes.Config{ 1592 Image: "busybox", 1593 } 1594 1595 cli, err := client.NewClientWithOpts(client.FromEnv) 1596 assert.NilError(c, err) 1597 defer cli.Close() 1598 1599 container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "") 1600 assert.NilError(c, err) 1601 1602 containerJSON, err := cli.ContainerInspect(context.Background(), container.ID) 1603 assert.NilError(c, err) 1604 1605 if versions.LessThan(testEnv.DaemonAPIVersion(), "1.31") { 1606 c.Assert(*containerJSON.HostConfig.MemorySwappiness, check.Equals, int64(-1)) 1607 } else { 1608 c.Assert(containerJSON.HostConfig.MemorySwappiness, check.IsNil) 1609 } 1610 } 1611 1612 // check validation is done daemon side and not only in cli 1613 func (s *DockerSuite) TestPostContainersCreateWithOomScoreAdjInvalidRange(c *check.C) { 1614 // OomScoreAdj is not supported on Windows 1615 testRequires(c, DaemonIsLinux) 1616 1617 config := containertypes.Config{ 1618 Image: "busybox", 1619 } 1620 1621 hostConfig := containertypes.HostConfig{ 1622 OomScoreAdj: 1001, 1623 } 1624 1625 cli, err := client.NewClientWithOpts(client.FromEnv) 1626 assert.NilError(c, err) 1627 defer cli.Close() 1628 1629 name := "oomscoreadj-over" 1630 _, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, name) 1631 1632 expected := "Invalid value 1001, range for oom score adj is [-1000, 1000]" 1633 assert.ErrorContains(c, err, expected) 1634 1635 hostConfig = containertypes.HostConfig{ 1636 OomScoreAdj: -1001, 1637 } 1638 1639 name = "oomscoreadj-low" 1640 _, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, name) 1641 1642 expected = "Invalid value -1001, range for oom score adj is [-1000, 1000]" 1643 assert.ErrorContains(c, err, expected) 1644 } 1645 1646 // test case for #22210 where an empty container name caused panic. 1647 func (s *DockerSuite) TestContainerAPIDeleteWithEmptyName(c *check.C) { 1648 cli, err := client.NewClientWithOpts(client.FromEnv) 1649 assert.NilError(c, err) 1650 defer cli.Close() 1651 1652 err = cli.ContainerRemove(context.Background(), "", types.ContainerRemoveOptions{}) 1653 assert.ErrorContains(c, err, "No such container") 1654 } 1655 1656 func (s *DockerSuite) TestContainerAPIStatsWithNetworkDisabled(c *check.C) { 1657 // Problematic on Windows as Windows does not support stats 1658 testRequires(c, DaemonIsLinux) 1659 1660 name := "testing-network-disabled" 1661 1662 config := containertypes.Config{ 1663 Image: "busybox", 1664 Cmd: []string{"top"}, 1665 NetworkDisabled: true, 1666 } 1667 1668 cli, err := client.NewClientWithOpts(client.FromEnv) 1669 assert.NilError(c, err) 1670 defer cli.Close() 1671 1672 _, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, name) 1673 assert.NilError(c, err) 1674 1675 err = cli.ContainerStart(context.Background(), name, types.ContainerStartOptions{}) 1676 assert.NilError(c, err) 1677 1678 c.Assert(waitRun(name), check.IsNil) 1679 1680 type b struct { 1681 stats types.ContainerStats 1682 err error 1683 } 1684 bc := make(chan b, 1) 1685 go func() { 1686 stats, err := cli.ContainerStats(context.Background(), name, false) 1687 bc <- b{stats, err} 1688 }() 1689 1690 // allow some time to stream the stats from the container 1691 time.Sleep(4 * time.Second) 1692 dockerCmd(c, "rm", "-f", name) 1693 1694 // collect the results from the stats stream or timeout and fail 1695 // if the stream was not disconnected. 1696 select { 1697 case <-time.After(2 * time.Second): 1698 c.Fatal("stream was not closed after container was removed") 1699 case sr := <-bc: 1700 c.Assert(sr.err, checker.IsNil) 1701 sr.stats.Body.Close() 1702 } 1703 } 1704 1705 func (s *DockerSuite) TestContainersAPICreateMountsValidation(c *check.C) { 1706 type testCase struct { 1707 config containertypes.Config 1708 hostConfig containertypes.HostConfig 1709 msg string 1710 } 1711 1712 prefix, slash := getPrefixAndSlashFromDaemonPlatform() 1713 destPath := prefix + slash + "foo" 1714 notExistPath := prefix + slash + "notexist" 1715 1716 cases := []testCase{ 1717 { 1718 config: containertypes.Config{ 1719 Image: "busybox", 1720 }, 1721 hostConfig: containertypes.HostConfig{ 1722 Mounts: []mounttypes.Mount{{ 1723 Type: "notreal", 1724 Target: destPath, 1725 }, 1726 }, 1727 }, 1728 1729 msg: "mount type unknown", 1730 }, 1731 { 1732 config: containertypes.Config{ 1733 Image: "busybox", 1734 }, 1735 hostConfig: containertypes.HostConfig{ 1736 Mounts: []mounttypes.Mount{{ 1737 Type: "bind"}}}, 1738 msg: "Target must not be empty", 1739 }, 1740 { 1741 config: containertypes.Config{ 1742 Image: "busybox", 1743 }, 1744 hostConfig: containertypes.HostConfig{ 1745 Mounts: []mounttypes.Mount{{ 1746 Type: "bind", 1747 Target: destPath}}}, 1748 msg: "Source must not be empty", 1749 }, 1750 { 1751 config: containertypes.Config{ 1752 Image: "busybox", 1753 }, 1754 hostConfig: containertypes.HostConfig{ 1755 Mounts: []mounttypes.Mount{{ 1756 Type: "bind", 1757 Source: notExistPath, 1758 Target: destPath}}}, 1759 msg: "source path does not exist", 1760 // FIXME(vdemeester) fails into e2e, migrate to integration/container anyway 1761 // msg: "source path does not exist: " + notExistPath, 1762 }, 1763 { 1764 config: containertypes.Config{ 1765 Image: "busybox", 1766 }, 1767 hostConfig: containertypes.HostConfig{ 1768 Mounts: []mounttypes.Mount{{ 1769 Type: "volume"}}}, 1770 msg: "Target must not be empty", 1771 }, 1772 { 1773 config: containertypes.Config{ 1774 Image: "busybox", 1775 }, 1776 hostConfig: containertypes.HostConfig{ 1777 Mounts: []mounttypes.Mount{{ 1778 Type: "volume", 1779 Source: "hello", 1780 Target: destPath}}}, 1781 msg: "", 1782 }, 1783 { 1784 config: containertypes.Config{ 1785 Image: "busybox", 1786 }, 1787 hostConfig: containertypes.HostConfig{ 1788 Mounts: []mounttypes.Mount{{ 1789 Type: "volume", 1790 Source: "hello2", 1791 Target: destPath, 1792 VolumeOptions: &mounttypes.VolumeOptions{ 1793 DriverConfig: &mounttypes.Driver{ 1794 Name: "local"}}}}}, 1795 msg: "", 1796 }, 1797 } 1798 1799 if testEnv.IsLocalDaemon() { 1800 tmpDir, err := ioutils.TempDir("", "test-mounts-api") 1801 assert.NilError(c, err) 1802 defer os.RemoveAll(tmpDir) 1803 cases = append(cases, []testCase{ 1804 { 1805 config: containertypes.Config{ 1806 Image: "busybox", 1807 }, 1808 hostConfig: containertypes.HostConfig{ 1809 Mounts: []mounttypes.Mount{{ 1810 Type: "bind", 1811 Source: tmpDir, 1812 Target: destPath}}}, 1813 msg: "", 1814 }, 1815 { 1816 config: containertypes.Config{ 1817 Image: "busybox", 1818 }, 1819 hostConfig: containertypes.HostConfig{ 1820 Mounts: []mounttypes.Mount{{ 1821 Type: "bind", 1822 Source: tmpDir, 1823 Target: destPath, 1824 VolumeOptions: &mounttypes.VolumeOptions{}}}}, 1825 msg: "VolumeOptions must not be specified", 1826 }, 1827 }...) 1828 } 1829 1830 if DaemonIsLinux() { 1831 cases = append(cases, []testCase{ 1832 { 1833 config: containertypes.Config{ 1834 Image: "busybox", 1835 }, 1836 hostConfig: containertypes.HostConfig{ 1837 Mounts: []mounttypes.Mount{{ 1838 Type: "volume", 1839 Source: "hello3", 1840 Target: destPath, 1841 VolumeOptions: &mounttypes.VolumeOptions{ 1842 DriverConfig: &mounttypes.Driver{ 1843 Name: "local", 1844 Options: map[string]string{"o": "size=1"}}}}}}, 1845 msg: "", 1846 }, 1847 { 1848 config: containertypes.Config{ 1849 Image: "busybox", 1850 }, 1851 hostConfig: containertypes.HostConfig{ 1852 Mounts: []mounttypes.Mount{{ 1853 Type: "tmpfs", 1854 Target: destPath}}}, 1855 msg: "", 1856 }, 1857 { 1858 config: containertypes.Config{ 1859 Image: "busybox", 1860 }, 1861 hostConfig: containertypes.HostConfig{ 1862 Mounts: []mounttypes.Mount{{ 1863 Type: "tmpfs", 1864 Target: destPath, 1865 TmpfsOptions: &mounttypes.TmpfsOptions{ 1866 SizeBytes: 4096 * 1024, 1867 Mode: 0700, 1868 }}}}, 1869 msg: "", 1870 }, 1871 1872 { 1873 config: containertypes.Config{ 1874 Image: "busybox", 1875 }, 1876 hostConfig: containertypes.HostConfig{ 1877 Mounts: []mounttypes.Mount{{ 1878 Type: "tmpfs", 1879 Source: "/shouldnotbespecified", 1880 Target: destPath}}}, 1881 msg: "Source must not be specified", 1882 }, 1883 }...) 1884 1885 } 1886 cli, err := client.NewClientWithOpts(client.FromEnv) 1887 assert.NilError(c, err) 1888 defer cli.Close() 1889 1890 for i, x := range cases { 1891 c.Logf("case %d", i) 1892 _, err = cli.ContainerCreate(context.Background(), &x.config, &x.hostConfig, &networktypes.NetworkingConfig{}, "") 1893 if len(x.msg) > 0 { 1894 assert.ErrorContains(c, err, x.msg, "%v", cases[i].config) 1895 } else { 1896 assert.NilError(c, err) 1897 } 1898 } 1899 } 1900 1901 func (s *DockerSuite) TestContainerAPICreateMountsBindRead(c *check.C) { 1902 testRequires(c, NotUserNamespace, testEnv.IsLocalDaemon) 1903 // also with data in the host side 1904 prefix, slash := getPrefixAndSlashFromDaemonPlatform() 1905 destPath := prefix + slash + "foo" 1906 tmpDir, err := ioutil.TempDir("", "test-mounts-api-bind") 1907 assert.NilError(c, err) 1908 defer os.RemoveAll(tmpDir) 1909 err = ioutil.WriteFile(filepath.Join(tmpDir, "bar"), []byte("hello"), 666) 1910 assert.NilError(c, err) 1911 config := containertypes.Config{ 1912 Image: "busybox", 1913 Cmd: []string{"/bin/sh", "-c", "cat /foo/bar"}, 1914 } 1915 hostConfig := containertypes.HostConfig{ 1916 Mounts: []mounttypes.Mount{ 1917 {Type: "bind", Source: tmpDir, Target: destPath}, 1918 }, 1919 } 1920 cli, err := client.NewClientWithOpts(client.FromEnv) 1921 assert.NilError(c, err) 1922 defer cli.Close() 1923 1924 _, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, "test") 1925 assert.NilError(c, err) 1926 1927 out, _ := dockerCmd(c, "start", "-a", "test") 1928 assert.Equal(c, out, "hello") 1929 } 1930 1931 // Test Mounts comes out as expected for the MountPoint 1932 func (s *DockerSuite) TestContainersAPICreateMountsCreate(c *check.C) { 1933 prefix, slash := getPrefixAndSlashFromDaemonPlatform() 1934 destPath := prefix + slash + "foo" 1935 1936 var ( 1937 testImg string 1938 ) 1939 if testEnv.OSType != "windows" { 1940 testImg = "test-mount-config" 1941 buildImageSuccessfully(c, testImg, build.WithDockerfile(` 1942 FROM busybox 1943 RUN mkdir `+destPath+` && touch `+destPath+slash+`bar 1944 CMD cat `+destPath+slash+`bar 1945 `)) 1946 } else { 1947 testImg = "busybox" 1948 } 1949 1950 type testCase struct { 1951 spec mounttypes.Mount 1952 expected types.MountPoint 1953 } 1954 1955 var selinuxSharedLabel string 1956 // this test label was added after a bug fix in 1.32, thus add requirements min API >= 1.32 1957 // for the sake of making test pass in earlier versions 1958 // bug fixed in https://github.com/moby/moby/pull/34684 1959 if !versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") { 1960 if runtime.GOOS == "linux" { 1961 selinuxSharedLabel = "z" 1962 } 1963 } 1964 1965 cases := []testCase{ 1966 // use literal strings here for `Type` instead of the defined constants in the volume package to keep this honest 1967 // Validation of the actual `Mount` struct is done in another test is not needed here 1968 { 1969 spec: mounttypes.Mount{Type: "volume", Target: destPath}, 1970 expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 1971 }, 1972 { 1973 spec: mounttypes.Mount{Type: "volume", Target: destPath + slash}, 1974 expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 1975 }, 1976 { 1977 spec: mounttypes.Mount{Type: "volume", Target: destPath, Source: "test1"}, 1978 expected: types.MountPoint{Type: "volume", Name: "test1", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 1979 }, 1980 { 1981 spec: mounttypes.Mount{Type: "volume", Target: destPath, ReadOnly: true, Source: "test2"}, 1982 expected: types.MountPoint{Type: "volume", Name: "test2", RW: false, Destination: destPath, Mode: selinuxSharedLabel}, 1983 }, 1984 { 1985 spec: mounttypes.Mount{Type: "volume", Target: destPath, Source: "test3", VolumeOptions: &mounttypes.VolumeOptions{DriverConfig: &mounttypes.Driver{Name: volume.DefaultDriverName}}}, 1986 expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", Name: "test3", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 1987 }, 1988 } 1989 1990 if testEnv.IsLocalDaemon() { 1991 // setup temp dir for testing binds 1992 tmpDir1, err := ioutil.TempDir("", "test-mounts-api-1") 1993 assert.NilError(c, err) 1994 defer os.RemoveAll(tmpDir1) 1995 cases = append(cases, []testCase{ 1996 { 1997 spec: mounttypes.Mount{ 1998 Type: "bind", 1999 Source: tmpDir1, 2000 Target: destPath, 2001 }, 2002 expected: types.MountPoint{ 2003 Type: "bind", 2004 RW: true, 2005 Destination: destPath, 2006 Source: tmpDir1, 2007 }, 2008 }, 2009 { 2010 spec: mounttypes.Mount{Type: "bind", Source: tmpDir1, Target: destPath, ReadOnly: true}, 2011 expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir1}, 2012 }, 2013 }...) 2014 2015 // for modes only supported on Linux 2016 if DaemonIsLinux() { 2017 tmpDir3, err := ioutils.TempDir("", "test-mounts-api-3") 2018 assert.NilError(c, err) 2019 defer os.RemoveAll(tmpDir3) 2020 2021 c.Assert(mount.Mount(tmpDir3, tmpDir3, "none", "bind,rw"), checker.IsNil) 2022 c.Assert(mount.ForceMount("", tmpDir3, "none", "shared"), checker.IsNil) 2023 2024 cases = append(cases, []testCase{ 2025 { 2026 spec: mounttypes.Mount{Type: "bind", Source: tmpDir3, Target: destPath}, 2027 expected: types.MountPoint{Type: "bind", RW: true, Destination: destPath, Source: tmpDir3}, 2028 }, 2029 { 2030 spec: mounttypes.Mount{Type: "bind", Source: tmpDir3, Target: destPath, ReadOnly: true}, 2031 expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir3}, 2032 }, 2033 { 2034 spec: mounttypes.Mount{Type: "bind", Source: tmpDir3, Target: destPath, ReadOnly: true, BindOptions: &mounttypes.BindOptions{Propagation: "shared"}}, 2035 expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir3, Propagation: "shared"}, 2036 }, 2037 }...) 2038 } 2039 } 2040 2041 if testEnv.OSType != "windows" { // Windows does not support volume populate 2042 cases = append(cases, []testCase{ 2043 { 2044 spec: mounttypes.Mount{Type: "volume", Target: destPath, VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true}}, 2045 expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2046 }, 2047 { 2048 spec: mounttypes.Mount{Type: "volume", Target: destPath + slash, VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true}}, 2049 expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2050 }, 2051 { 2052 spec: mounttypes.Mount{Type: "volume", Target: destPath, Source: "test4", VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true}}, 2053 expected: types.MountPoint{Type: "volume", Name: "test4", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2054 }, 2055 { 2056 spec: mounttypes.Mount{Type: "volume", Target: destPath, Source: "test5", ReadOnly: true, VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true}}, 2057 expected: types.MountPoint{Type: "volume", Name: "test5", RW: false, Destination: destPath, Mode: selinuxSharedLabel}, 2058 }, 2059 }...) 2060 } 2061 2062 type wrapper struct { 2063 containertypes.Config 2064 HostConfig containertypes.HostConfig 2065 } 2066 type createResp struct { 2067 ID string `json:"Id"` 2068 } 2069 2070 ctx := context.Background() 2071 apiclient := testEnv.APIClient() 2072 for i, x := range cases { 2073 c.Logf("case %d - config: %v", i, x.spec) 2074 container, err := apiclient.ContainerCreate( 2075 ctx, 2076 &containertypes.Config{Image: testImg}, 2077 &containertypes.HostConfig{Mounts: []mounttypes.Mount{x.spec}}, 2078 &networktypes.NetworkingConfig{}, 2079 "") 2080 assert.NilError(c, err) 2081 2082 containerInspect, err := apiclient.ContainerInspect(ctx, container.ID) 2083 assert.NilError(c, err) 2084 mps := containerInspect.Mounts 2085 assert.Assert(c, is.Len(mps, 1)) 2086 mountPoint := mps[0] 2087 2088 if x.expected.Source != "" { 2089 assert.Check(c, is.Equal(x.expected.Source, mountPoint.Source)) 2090 } 2091 if x.expected.Name != "" { 2092 assert.Check(c, is.Equal(x.expected.Name, mountPoint.Name)) 2093 } 2094 if x.expected.Driver != "" { 2095 assert.Check(c, is.Equal(x.expected.Driver, mountPoint.Driver)) 2096 } 2097 if x.expected.Propagation != "" { 2098 assert.Check(c, is.Equal(x.expected.Propagation, mountPoint.Propagation)) 2099 } 2100 assert.Check(c, is.Equal(x.expected.RW, mountPoint.RW)) 2101 assert.Check(c, is.Equal(x.expected.Type, mountPoint.Type)) 2102 assert.Check(c, is.Equal(x.expected.Mode, mountPoint.Mode)) 2103 assert.Check(c, is.Equal(x.expected.Destination, mountPoint.Destination)) 2104 2105 err = apiclient.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}) 2106 assert.NilError(c, err) 2107 poll.WaitOn(c, containerExit(apiclient, container.ID), poll.WithDelay(time.Second)) 2108 2109 err = apiclient.ContainerRemove(ctx, container.ID, types.ContainerRemoveOptions{ 2110 RemoveVolumes: true, 2111 Force: true, 2112 }) 2113 assert.NilError(c, err) 2114 2115 switch { 2116 2117 // Named volumes still exist after the container is removed 2118 case x.spec.Type == "volume" && len(x.spec.Source) > 0: 2119 _, err := apiclient.VolumeInspect(ctx, mountPoint.Name) 2120 assert.NilError(c, err) 2121 2122 // Bind mounts are never removed with the container 2123 case x.spec.Type == "bind": 2124 2125 // anonymous volumes are removed 2126 default: 2127 _, err := apiclient.VolumeInspect(ctx, mountPoint.Name) 2128 assert.Check(c, client.IsErrNotFound(err)) 2129 } 2130 } 2131 } 2132 2133 func containerExit(apiclient client.APIClient, name string) func(poll.LogT) poll.Result { 2134 return func(logT poll.LogT) poll.Result { 2135 container, err := apiclient.ContainerInspect(context.Background(), name) 2136 if err != nil { 2137 return poll.Error(err) 2138 } 2139 switch container.State.Status { 2140 case "created", "running": 2141 return poll.Continue("container %s is %s, waiting for exit", name, container.State.Status) 2142 } 2143 return poll.Success() 2144 } 2145 } 2146 2147 func (s *DockerSuite) TestContainersAPICreateMountsTmpfs(c *check.C) { 2148 testRequires(c, DaemonIsLinux) 2149 type testCase struct { 2150 cfg mounttypes.Mount 2151 expectedOptions []string 2152 } 2153 target := "/foo" 2154 cases := []testCase{ 2155 { 2156 cfg: mounttypes.Mount{ 2157 Type: "tmpfs", 2158 Target: target}, 2159 expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime"}, 2160 }, 2161 { 2162 cfg: mounttypes.Mount{ 2163 Type: "tmpfs", 2164 Target: target, 2165 TmpfsOptions: &mounttypes.TmpfsOptions{ 2166 SizeBytes: 4096 * 1024, Mode: 0700}}, 2167 expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime", "size=4096k", "mode=700"}, 2168 }, 2169 } 2170 2171 cli, err := client.NewClientWithOpts(client.FromEnv) 2172 assert.NilError(c, err) 2173 defer cli.Close() 2174 2175 config := containertypes.Config{ 2176 Image: "busybox", 2177 Cmd: []string{"/bin/sh", "-c", fmt.Sprintf("mount | grep 'tmpfs on %s'", target)}, 2178 } 2179 for i, x := range cases { 2180 cName := fmt.Sprintf("test-tmpfs-%d", i) 2181 hostConfig := containertypes.HostConfig{ 2182 Mounts: []mounttypes.Mount{x.cfg}, 2183 } 2184 2185 _, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, cName) 2186 assert.NilError(c, err) 2187 out, _ := dockerCmd(c, "start", "-a", cName) 2188 for _, option := range x.expectedOptions { 2189 c.Assert(out, checker.Contains, option) 2190 } 2191 } 2192 } 2193 2194 // Regression test for #33334 2195 // Makes sure that when a container which has a custom stop signal + restart=always 2196 // gets killed (with SIGKILL) by the kill API, that the restart policy is cancelled. 2197 func (s *DockerSuite) TestContainerKillCustomStopSignal(c *check.C) { 2198 id := strings.TrimSpace(runSleepingContainer(c, "--stop-signal=SIGTERM", "--restart=always")) 2199 res, _, err := request.Post("/containers/" + id + "/kill") 2200 assert.NilError(c, err) 2201 defer res.Body.Close() 2202 2203 b, err := ioutil.ReadAll(res.Body) 2204 assert.NilError(c, err) 2205 assert.Equal(c, res.StatusCode, http.StatusNoContent, string(b)) 2206 err = waitInspect(id, "{{.State.Running}} {{.State.Restarting}}", "false false", 30*time.Second) 2207 assert.NilError(c, err) 2208 }