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