github.com/vieux/docker@v0.6.3-0.20161004191708-e097c2a938c7/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 // TODO: Investigate why this fails on Windows to Windows CI 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 // Problematic on Windows as Windows does not support top 375 testRequires(c, DaemonIsLinux) 376 out, _ := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "top") 377 id := strings.TrimSpace(string(out)) 378 c.Assert(waitRun(id), checker.IsNil) 379 380 type topResp struct { 381 Titles []string 382 Processes [][]string 383 } 384 var top topResp 385 status, b, err := sockRequest("GET", "/containers/"+id+"/top?ps_args=aux", nil) 386 c.Assert(err, checker.IsNil) 387 c.Assert(status, checker.Equals, http.StatusOK) 388 c.Assert(json.Unmarshal(b, &top), checker.IsNil) 389 c.Assert(top.Titles, checker.HasLen, 11, check.Commentf("expected 11 titles, found %d: %v", len(top.Titles), top.Titles)) 390 391 if top.Titles[0] != "USER" || top.Titles[10] != "COMMAND" { 392 c.Fatalf("expected `USER` at `Titles[0]` and `COMMAND` at Titles[10]: %v", top.Titles) 393 } 394 c.Assert(top.Processes, checker.HasLen, 2, check.Commentf("expected 2 processes, found %d: %v", len(top.Processes), top.Processes)) 395 c.Assert(top.Processes[0][10], checker.Equals, "/bin/sh -c top") 396 c.Assert(top.Processes[1][10], checker.Equals, "top") 397 } 398 399 func (s *DockerSuite) TestContainerAPICommit(c *check.C) { 400 cName := "testapicommit" 401 dockerCmd(c, "run", "--name="+cName, "busybox", "/bin/sh", "-c", "touch /test") 402 403 name := "testcontainerapicommit" 404 status, b, err := sockRequest("POST", "/commit?repo="+name+"&testtag=tag&container="+cName, nil) 405 c.Assert(err, checker.IsNil) 406 c.Assert(status, checker.Equals, http.StatusCreated) 407 408 type resp struct { 409 ID string 410 } 411 var img resp 412 c.Assert(json.Unmarshal(b, &img), checker.IsNil) 413 414 cmd := inspectField(c, img.ID, "Config.Cmd") 415 c.Assert(cmd, checker.Equals, "[/bin/sh -c touch /test]", check.Commentf("got wrong Cmd from commit: %q", cmd)) 416 417 // sanity check, make sure the image is what we think it is 418 dockerCmd(c, "run", img.ID, "ls", "/test") 419 } 420 421 func (s *DockerSuite) TestContainerAPICommitWithLabelInConfig(c *check.C) { 422 cName := "testapicommitwithconfig" 423 dockerCmd(c, "run", "--name="+cName, "busybox", "/bin/sh", "-c", "touch /test") 424 425 config := map[string]interface{}{ 426 "Labels": map[string]string{"key1": "value1", "key2": "value2"}, 427 } 428 429 name := "testcontainerapicommitwithconfig" 430 status, b, err := sockRequest("POST", "/commit?repo="+name+"&container="+cName, config) 431 c.Assert(err, checker.IsNil) 432 c.Assert(status, checker.Equals, http.StatusCreated) 433 434 type resp struct { 435 ID string 436 } 437 var img resp 438 c.Assert(json.Unmarshal(b, &img), checker.IsNil) 439 440 label1 := inspectFieldMap(c, img.ID, "Config.Labels", "key1") 441 c.Assert(label1, checker.Equals, "value1") 442 443 label2 := inspectFieldMap(c, img.ID, "Config.Labels", "key2") 444 c.Assert(label2, checker.Equals, "value2") 445 446 cmd := inspectField(c, img.ID, "Config.Cmd") 447 c.Assert(cmd, checker.Equals, "[/bin/sh -c touch /test]", check.Commentf("got wrong Cmd from commit: %q", cmd)) 448 449 // sanity check, make sure the image is what we think it is 450 dockerCmd(c, "run", img.ID, "ls", "/test") 451 } 452 453 func (s *DockerSuite) TestContainerAPIBadPort(c *check.C) { 454 // TODO Windows to Windows CI - Port this test 455 testRequires(c, DaemonIsLinux) 456 config := map[string]interface{}{ 457 "Image": "busybox", 458 "Cmd": []string{"/bin/sh", "-c", "echo test"}, 459 "PortBindings": map[string]interface{}{ 460 "8080/tcp": []map[string]interface{}{ 461 { 462 "HostIP": "", 463 "HostPort": "aa80", 464 }, 465 }, 466 }, 467 } 468 469 jsonData := bytes.NewBuffer(nil) 470 json.NewEncoder(jsonData).Encode(config) 471 472 status, body, err := sockRequest("POST", "/containers/create", config) 473 c.Assert(err, checker.IsNil) 474 c.Assert(status, checker.Equals, http.StatusInternalServerError) 475 c.Assert(getErrorMessage(c, body), checker.Equals, `invalid port specification: "aa80"`, check.Commentf("Incorrect error msg: %s", body)) 476 } 477 478 func (s *DockerSuite) TestContainerAPICreate(c *check.C) { 479 config := map[string]interface{}{ 480 "Image": "busybox", 481 "Cmd": []string{"/bin/sh", "-c", "touch /test && ls /test"}, 482 } 483 484 status, b, err := sockRequest("POST", "/containers/create", config) 485 c.Assert(err, checker.IsNil) 486 c.Assert(status, checker.Equals, http.StatusCreated) 487 488 type createResp struct { 489 ID string 490 } 491 var container createResp 492 c.Assert(json.Unmarshal(b, &container), checker.IsNil) 493 494 out, _ := dockerCmd(c, "start", "-a", container.ID) 495 c.Assert(strings.TrimSpace(out), checker.Equals, "/test") 496 } 497 498 func (s *DockerSuite) TestContainerAPICreateEmptyConfig(c *check.C) { 499 config := map[string]interface{}{} 500 501 status, body, err := sockRequest("POST", "/containers/create", config) 502 c.Assert(err, checker.IsNil) 503 c.Assert(status, checker.Equals, http.StatusInternalServerError) 504 505 expected := "Config cannot be empty in order to create a container" 506 c.Assert(getErrorMessage(c, body), checker.Equals, expected) 507 } 508 509 func (s *DockerSuite) TestContainerAPICreateMultipleNetworksConfig(c *check.C) { 510 // Container creation must fail if client specified configurations for more than one network 511 config := map[string]interface{}{ 512 "Image": "busybox", 513 "NetworkingConfig": networktypes.NetworkingConfig{ 514 EndpointsConfig: map[string]*networktypes.EndpointSettings{ 515 "net1": {}, 516 "net2": {}, 517 "net3": {}, 518 }, 519 }, 520 } 521 522 status, body, err := sockRequest("POST", "/containers/create", config) 523 c.Assert(err, checker.IsNil) 524 c.Assert(status, checker.Equals, http.StatusBadRequest) 525 msg := getErrorMessage(c, body) 526 // network name order in error message is not deterministic 527 c.Assert(msg, checker.Contains, "Container cannot be connected to network endpoints") 528 c.Assert(msg, checker.Contains, "net1") 529 c.Assert(msg, checker.Contains, "net2") 530 c.Assert(msg, checker.Contains, "net3") 531 } 532 533 func (s *DockerSuite) TestContainerAPICreateWithHostName(c *check.C) { 534 hostName := "test-host" 535 config := map[string]interface{}{ 536 "Image": "busybox", 537 "Hostname": hostName, 538 } 539 540 status, body, err := sockRequest("POST", "/containers/create", config) 541 c.Assert(err, checker.IsNil) 542 c.Assert(status, checker.Equals, http.StatusCreated) 543 544 var container types.ContainerCreateResponse 545 c.Assert(json.Unmarshal(body, &container), checker.IsNil) 546 547 status, body, err = sockRequest("GET", "/containers/"+container.ID+"/json", nil) 548 c.Assert(err, checker.IsNil) 549 c.Assert(status, checker.Equals, http.StatusOK) 550 551 var containerJSON types.ContainerJSON 552 c.Assert(json.Unmarshal(body, &containerJSON), checker.IsNil) 553 c.Assert(containerJSON.Config.Hostname, checker.Equals, hostName, check.Commentf("Mismatched Hostname")) 554 } 555 556 func (s *DockerSuite) TestContainerAPICreateWithDomainName(c *check.C) { 557 domainName := "test-domain" 558 config := map[string]interface{}{ 559 "Image": "busybox", 560 "Domainname": domainName, 561 } 562 563 status, body, err := sockRequest("POST", "/containers/create", config) 564 c.Assert(err, checker.IsNil) 565 c.Assert(status, checker.Equals, http.StatusCreated) 566 567 var container types.ContainerCreateResponse 568 c.Assert(json.Unmarshal(body, &container), checker.IsNil) 569 570 status, body, err = sockRequest("GET", "/containers/"+container.ID+"/json", nil) 571 c.Assert(err, checker.IsNil) 572 c.Assert(status, checker.Equals, http.StatusOK) 573 574 var containerJSON types.ContainerJSON 575 c.Assert(json.Unmarshal(body, &containerJSON), checker.IsNil) 576 c.Assert(containerJSON.Config.Domainname, checker.Equals, domainName, check.Commentf("Mismatched Domainname")) 577 } 578 579 func (s *DockerSuite) TestContainerAPICreateBridgeNetworkMode(c *check.C) { 580 // Windows does not support bridge 581 testRequires(c, DaemonIsLinux) 582 UtilCreateNetworkMode(c, "bridge") 583 } 584 585 func (s *DockerSuite) TestContainerAPICreateOtherNetworkModes(c *check.C) { 586 // Windows does not support these network modes 587 testRequires(c, DaemonIsLinux, NotUserNamespace) 588 UtilCreateNetworkMode(c, "host") 589 UtilCreateNetworkMode(c, "container:web1") 590 } 591 592 func UtilCreateNetworkMode(c *check.C, networkMode string) { 593 config := map[string]interface{}{ 594 "Image": "busybox", 595 "HostConfig": map[string]interface{}{"NetworkMode": networkMode}, 596 } 597 598 status, body, err := sockRequest("POST", "/containers/create", config) 599 c.Assert(err, checker.IsNil) 600 c.Assert(status, checker.Equals, http.StatusCreated) 601 602 var container types.ContainerCreateResponse 603 c.Assert(json.Unmarshal(body, &container), checker.IsNil) 604 605 status, body, err = sockRequest("GET", "/containers/"+container.ID+"/json", nil) 606 c.Assert(err, checker.IsNil) 607 c.Assert(status, checker.Equals, http.StatusOK) 608 609 var containerJSON types.ContainerJSON 610 c.Assert(json.Unmarshal(body, &containerJSON), checker.IsNil) 611 c.Assert(containerJSON.HostConfig.NetworkMode, checker.Equals, containertypes.NetworkMode(networkMode), check.Commentf("Mismatched NetworkMode")) 612 } 613 614 func (s *DockerSuite) TestContainerAPICreateWithCpuSharesCpuset(c *check.C) { 615 // TODO Windows to Windows CI. The CpuShares part could be ported. 616 testRequires(c, DaemonIsLinux) 617 config := map[string]interface{}{ 618 "Image": "busybox", 619 "CpuShares": 512, 620 "CpusetCpus": "0", 621 } 622 623 status, body, err := sockRequest("POST", "/containers/create", config) 624 c.Assert(err, checker.IsNil) 625 c.Assert(status, checker.Equals, http.StatusCreated) 626 627 var container types.ContainerCreateResponse 628 c.Assert(json.Unmarshal(body, &container), checker.IsNil) 629 630 status, body, err = sockRequest("GET", "/containers/"+container.ID+"/json", nil) 631 c.Assert(err, checker.IsNil) 632 c.Assert(status, checker.Equals, http.StatusOK) 633 634 var containerJSON types.ContainerJSON 635 636 c.Assert(json.Unmarshal(body, &containerJSON), checker.IsNil) 637 638 out := inspectField(c, containerJSON.ID, "HostConfig.CpuShares") 639 c.Assert(out, checker.Equals, "512") 640 641 outCpuset := inspectField(c, containerJSON.ID, "HostConfig.CpusetCpus") 642 c.Assert(outCpuset, checker.Equals, "0") 643 } 644 645 func (s *DockerSuite) TestContainerAPIVerifyHeader(c *check.C) { 646 config := map[string]interface{}{ 647 "Image": "busybox", 648 } 649 650 create := func(ct string) (*http.Response, io.ReadCloser, error) { 651 jsonData := bytes.NewBuffer(nil) 652 c.Assert(json.NewEncoder(jsonData).Encode(config), checker.IsNil) 653 return sockRequestRaw("POST", "/containers/create", jsonData, ct) 654 } 655 656 // Try with no content-type 657 res, body, err := create("") 658 c.Assert(err, checker.IsNil) 659 c.Assert(res.StatusCode, checker.Equals, http.StatusInternalServerError) 660 body.Close() 661 662 // Try with wrong content-type 663 res, body, err = create("application/xml") 664 c.Assert(err, checker.IsNil) 665 c.Assert(res.StatusCode, checker.Equals, http.StatusInternalServerError) 666 body.Close() 667 668 // now application/json 669 res, body, err = create("application/json") 670 c.Assert(err, checker.IsNil) 671 c.Assert(res.StatusCode, checker.Equals, http.StatusCreated) 672 body.Close() 673 } 674 675 //Issue 14230. daemon should return 500 for invalid port syntax 676 func (s *DockerSuite) TestContainerAPIInvalidPortSyntax(c *check.C) { 677 config := `{ 678 "Image": "busybox", 679 "HostConfig": { 680 "NetworkMode": "default", 681 "PortBindings": { 682 "19039;1230": [ 683 {} 684 ] 685 } 686 } 687 }` 688 689 res, body, err := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") 690 c.Assert(err, checker.IsNil) 691 c.Assert(res.StatusCode, checker.Equals, http.StatusInternalServerError) 692 693 b, err := readBody(body) 694 c.Assert(err, checker.IsNil) 695 c.Assert(string(b[:]), checker.Contains, "invalid port") 696 } 697 698 func (s *DockerSuite) TestContainerAPIInvalidRestartPolicyName(c *check.C) { 699 config := `{ 700 "Image": "busybox", 701 "HostConfig": { 702 "RestartPolicy": { 703 "Name": "something", 704 "MaximumRetryCount": 0 705 } 706 } 707 }` 708 709 res, body, err := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") 710 c.Assert(err, checker.IsNil) 711 c.Assert(res.StatusCode, checker.Equals, http.StatusInternalServerError) 712 713 b, err := readBody(body) 714 c.Assert(err, checker.IsNil) 715 c.Assert(string(b[:]), checker.Contains, "invalid restart policy") 716 } 717 718 func (s *DockerSuite) TestContainerAPIInvalidRestartPolicyRetryMismatch(c *check.C) { 719 config := `{ 720 "Image": "busybox", 721 "HostConfig": { 722 "RestartPolicy": { 723 "Name": "always", 724 "MaximumRetryCount": 2 725 } 726 } 727 }` 728 729 res, body, err := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") 730 c.Assert(err, checker.IsNil) 731 c.Assert(res.StatusCode, checker.Equals, http.StatusInternalServerError) 732 733 b, err := readBody(body) 734 c.Assert(err, checker.IsNil) 735 c.Assert(string(b[:]), checker.Contains, "maximum restart count not valid with restart policy") 736 } 737 738 func (s *DockerSuite) TestContainerAPIInvalidRestartPolicyPositiveRetryCount(c *check.C) { 739 config := `{ 740 "Image": "busybox", 741 "HostConfig": { 742 "RestartPolicy": { 743 "Name": "on-failure", 744 "MaximumRetryCount": -2 745 } 746 } 747 }` 748 749 res, body, err := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") 750 c.Assert(err, checker.IsNil) 751 c.Assert(res.StatusCode, checker.Equals, http.StatusInternalServerError) 752 753 b, err := readBody(body) 754 c.Assert(err, checker.IsNil) 755 c.Assert(string(b[:]), checker.Contains, "maximum restart count must be a positive integer") 756 } 757 758 // Issue 7941 - test to make sure a "null" in JSON is just ignored. 759 // W/o this fix a null in JSON would be parsed into a string var as "null" 760 func (s *DockerSuite) TestContainerAPIPostCreateNull(c *check.C) { 761 config := `{ 762 "Hostname":"", 763 "Domainname":"", 764 "Memory":0, 765 "MemorySwap":0, 766 "CpuShares":0, 767 "Cpuset":null, 768 "AttachStdin":true, 769 "AttachStdout":true, 770 "AttachStderr":true, 771 "ExposedPorts":{}, 772 "Tty":true, 773 "OpenStdin":true, 774 "StdinOnce":true, 775 "Env":[], 776 "Cmd":"ls", 777 "Image":"busybox", 778 "Volumes":{}, 779 "WorkingDir":"", 780 "Entrypoint":null, 781 "NetworkDisabled":false, 782 "OnBuild":null}` 783 784 res, body, err := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") 785 c.Assert(err, checker.IsNil) 786 c.Assert(res.StatusCode, checker.Equals, http.StatusCreated) 787 788 b, err := readBody(body) 789 c.Assert(err, checker.IsNil) 790 type createResp struct { 791 ID string 792 } 793 var container createResp 794 c.Assert(json.Unmarshal(b, &container), checker.IsNil) 795 out := inspectField(c, container.ID, "HostConfig.CpusetCpus") 796 c.Assert(out, checker.Equals, "") 797 798 outMemory := inspectField(c, container.ID, "HostConfig.Memory") 799 c.Assert(outMemory, checker.Equals, "0") 800 outMemorySwap := inspectField(c, container.ID, "HostConfig.MemorySwap") 801 c.Assert(outMemorySwap, checker.Equals, "0") 802 } 803 804 func (s *DockerSuite) TestCreateWithTooLowMemoryLimit(c *check.C) { 805 // TODO Windows: Port once memory is supported 806 testRequires(c, DaemonIsLinux) 807 config := `{ 808 "Image": "busybox", 809 "Cmd": "ls", 810 "OpenStdin": true, 811 "CpuShares": 100, 812 "Memory": 524287 813 }` 814 815 res, body, err := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") 816 c.Assert(err, checker.IsNil) 817 b, err2 := readBody(body) 818 c.Assert(err2, checker.IsNil) 819 820 c.Assert(res.StatusCode, checker.Equals, http.StatusInternalServerError) 821 c.Assert(string(b), checker.Contains, "Minimum memory limit allowed is 4MB") 822 } 823 824 func (s *DockerSuite) TestContainerAPIRename(c *check.C) { 825 out, _ := dockerCmd(c, "run", "--name", "TestContainerAPIRename", "-d", "busybox", "sh") 826 827 containerID := strings.TrimSpace(out) 828 newName := "TestContainerAPIRenameNew" 829 statusCode, _, err := sockRequest("POST", "/containers/"+containerID+"/rename?name="+newName, nil) 830 c.Assert(err, checker.IsNil) 831 // 204 No Content is expected, not 200 832 c.Assert(statusCode, checker.Equals, http.StatusNoContent) 833 834 name := inspectField(c, containerID, "Name") 835 c.Assert(name, checker.Equals, "/"+newName, check.Commentf("Failed to rename container")) 836 } 837 838 func (s *DockerSuite) TestContainerAPIKill(c *check.C) { 839 name := "test-api-kill" 840 runSleepingContainer(c, "-i", "--name", name) 841 842 status, _, err := sockRequest("POST", "/containers/"+name+"/kill", nil) 843 c.Assert(err, checker.IsNil) 844 c.Assert(status, checker.Equals, http.StatusNoContent) 845 846 state := inspectField(c, name, "State.Running") 847 c.Assert(state, checker.Equals, "false", check.Commentf("got wrong State from container %s: %q", name, state)) 848 } 849 850 func (s *DockerSuite) TestContainerAPIRestart(c *check.C) { 851 // TODO Windows to Windows CI. This is flaky due to the timing 852 testRequires(c, DaemonIsLinux) 853 name := "test-api-restart" 854 dockerCmd(c, "run", "-di", "--name", name, "busybox", "top") 855 856 status, _, err := sockRequest("POST", "/containers/"+name+"/restart?t=1", nil) 857 c.Assert(err, checker.IsNil) 858 c.Assert(status, checker.Equals, http.StatusNoContent) 859 c.Assert(waitInspect(name, "{{ .State.Restarting }} {{ .State.Running }}", "false true", 5*time.Second), checker.IsNil) 860 } 861 862 func (s *DockerSuite) TestContainerAPIRestartNotimeoutParam(c *check.C) { 863 // TODO Windows to Windows CI. This is flaky due to the timing 864 testRequires(c, DaemonIsLinux) 865 name := "test-api-restart-no-timeout-param" 866 out, _ := dockerCmd(c, "run", "-di", "--name", name, "busybox", "top") 867 id := strings.TrimSpace(out) 868 c.Assert(waitRun(id), checker.IsNil) 869 870 status, _, err := sockRequest("POST", "/containers/"+name+"/restart", nil) 871 c.Assert(err, checker.IsNil) 872 c.Assert(status, checker.Equals, http.StatusNoContent) 873 c.Assert(waitInspect(name, "{{ .State.Restarting }} {{ .State.Running }}", "false true", 5*time.Second), checker.IsNil) 874 } 875 876 func (s *DockerSuite) TestContainerAPIStart(c *check.C) { 877 name := "testing-start" 878 config := map[string]interface{}{ 879 "Image": "busybox", 880 "Cmd": append([]string{"/bin/sh", "-c"}, sleepCommandForDaemonPlatform()...), 881 "OpenStdin": true, 882 } 883 884 status, _, err := sockRequest("POST", "/containers/create?name="+name, config) 885 c.Assert(err, checker.IsNil) 886 c.Assert(status, checker.Equals, http.StatusCreated) 887 888 status, _, err = sockRequest("POST", "/containers/"+name+"/start", nil) 889 c.Assert(err, checker.IsNil) 890 c.Assert(status, checker.Equals, http.StatusNoContent) 891 892 // second call to start should give 304 893 status, _, err = sockRequest("POST", "/containers/"+name+"/start", nil) 894 c.Assert(err, checker.IsNil) 895 896 // TODO(tibor): figure out why this doesn't work on windows 897 if isLocalDaemon { 898 c.Assert(status, checker.Equals, http.StatusNotModified) 899 } 900 } 901 902 func (s *DockerSuite) TestContainerAPIStop(c *check.C) { 903 name := "test-api-stop" 904 runSleepingContainer(c, "-i", "--name", name) 905 906 status, _, err := sockRequest("POST", "/containers/"+name+"/stop?t=30", nil) 907 c.Assert(err, checker.IsNil) 908 c.Assert(status, checker.Equals, http.StatusNoContent) 909 c.Assert(waitInspect(name, "{{ .State.Running }}", "false", 60*time.Second), checker.IsNil) 910 911 // second call to start should give 304 912 status, _, err = sockRequest("POST", "/containers/"+name+"/stop?t=30", nil) 913 c.Assert(err, checker.IsNil) 914 c.Assert(status, checker.Equals, http.StatusNotModified) 915 } 916 917 func (s *DockerSuite) TestContainerAPIWait(c *check.C) { 918 name := "test-api-wait" 919 920 sleepCmd := "/bin/sleep" 921 if daemonPlatform == "windows" { 922 sleepCmd = "sleep" 923 } 924 dockerCmd(c, "run", "--name", name, "busybox", sleepCmd, "2") 925 926 status, body, err := sockRequest("POST", "/containers/"+name+"/wait", nil) 927 c.Assert(err, checker.IsNil) 928 c.Assert(status, checker.Equals, http.StatusOK) 929 c.Assert(waitInspect(name, "{{ .State.Running }}", "false", 60*time.Second), checker.IsNil) 930 931 var waitres types.ContainerWaitResponse 932 c.Assert(json.Unmarshal(body, &waitres), checker.IsNil) 933 c.Assert(waitres.StatusCode, checker.Equals, 0) 934 } 935 936 func (s *DockerSuite) TestContainerAPICopyNotExistsAnyMore(c *check.C) { 937 name := "test-container-api-copy" 938 dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt") 939 940 postData := types.CopyConfig{ 941 Resource: "/test.txt", 942 } 943 944 status, _, err := sockRequest("POST", "/containers/"+name+"/copy", postData) 945 c.Assert(err, checker.IsNil) 946 c.Assert(status, checker.Equals, http.StatusNotFound) 947 } 948 949 func (s *DockerSuite) TestContainerAPICopyPre124(c *check.C) { 950 951 name := "test-container-api-copy" 952 dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt") 953 954 postData := types.CopyConfig{ 955 Resource: "/test.txt", 956 } 957 958 status, body, err := sockRequest("POST", "/v1.23/containers/"+name+"/copy", postData) 959 c.Assert(err, checker.IsNil) 960 c.Assert(status, checker.Equals, http.StatusOK) 961 962 found := false 963 for tarReader := tar.NewReader(bytes.NewReader(body)); ; { 964 h, err := tarReader.Next() 965 if err != nil { 966 if err == io.EOF { 967 break 968 } 969 c.Fatal(err) 970 } 971 if h.Name == "test.txt" { 972 found = true 973 break 974 } 975 } 976 c.Assert(found, checker.True) 977 } 978 979 func (s *DockerSuite) TestContainerAPICopyResourcePathEmptyPr124(c *check.C) { 980 name := "test-container-api-copy-resource-empty" 981 dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt") 982 983 postData := types.CopyConfig{ 984 Resource: "", 985 } 986 987 status, body, err := sockRequest("POST", "/v1.23/containers/"+name+"/copy", postData) 988 c.Assert(err, checker.IsNil) 989 c.Assert(status, checker.Equals, http.StatusInternalServerError) 990 c.Assert(string(body), checker.Matches, "Path cannot be empty\n") 991 } 992 993 func (s *DockerSuite) TestContainerAPICopyResourcePathNotFoundPre124(c *check.C) { 994 name := "test-container-api-copy-resource-not-found" 995 dockerCmd(c, "run", "--name", name, "busybox") 996 997 postData := types.CopyConfig{ 998 Resource: "/notexist", 999 } 1000 1001 status, body, err := sockRequest("POST", "/v1.23/containers/"+name+"/copy", postData) 1002 c.Assert(err, checker.IsNil) 1003 c.Assert(status, checker.Equals, http.StatusInternalServerError) 1004 c.Assert(string(body), checker.Matches, "Could not find the file /notexist in container "+name+"\n") 1005 } 1006 1007 func (s *DockerSuite) TestContainerAPICopyContainerNotFoundPr124(c *check.C) { 1008 postData := types.CopyConfig{ 1009 Resource: "/something", 1010 } 1011 1012 status, _, err := sockRequest("POST", "/v1.23/containers/notexists/copy", postData) 1013 c.Assert(err, checker.IsNil) 1014 c.Assert(status, checker.Equals, http.StatusNotFound) 1015 } 1016 1017 func (s *DockerSuite) TestContainerAPIDelete(c *check.C) { 1018 out, _ := runSleepingContainer(c) 1019 1020 id := strings.TrimSpace(out) 1021 c.Assert(waitRun(id), checker.IsNil) 1022 1023 dockerCmd(c, "stop", id) 1024 1025 status, _, err := sockRequest("DELETE", "/containers/"+id, nil) 1026 c.Assert(err, checker.IsNil) 1027 c.Assert(status, checker.Equals, http.StatusNoContent) 1028 } 1029 1030 func (s *DockerSuite) TestContainerAPIDeleteNotExist(c *check.C) { 1031 status, body, err := sockRequest("DELETE", "/containers/doesnotexist", nil) 1032 c.Assert(err, checker.IsNil) 1033 c.Assert(status, checker.Equals, http.StatusNotFound) 1034 c.Assert(getErrorMessage(c, body), checker.Matches, "No such container: doesnotexist") 1035 } 1036 1037 func (s *DockerSuite) TestContainerAPIDeleteForce(c *check.C) { 1038 out, _ := runSleepingContainer(c) 1039 1040 id := strings.TrimSpace(out) 1041 c.Assert(waitRun(id), checker.IsNil) 1042 1043 status, _, err := sockRequest("DELETE", "/containers/"+id+"?force=1", nil) 1044 c.Assert(err, checker.IsNil) 1045 c.Assert(status, checker.Equals, http.StatusNoContent) 1046 } 1047 1048 func (s *DockerSuite) TestContainerAPIDeleteRemoveLinks(c *check.C) { 1049 // Windows does not support links 1050 testRequires(c, DaemonIsLinux) 1051 out, _ := dockerCmd(c, "run", "-d", "--name", "tlink1", "busybox", "top") 1052 1053 id := strings.TrimSpace(out) 1054 c.Assert(waitRun(id), checker.IsNil) 1055 1056 out, _ = dockerCmd(c, "run", "--link", "tlink1:tlink1", "--name", "tlink2", "-d", "busybox", "top") 1057 1058 id2 := strings.TrimSpace(out) 1059 c.Assert(waitRun(id2), checker.IsNil) 1060 1061 links := inspectFieldJSON(c, id2, "HostConfig.Links") 1062 c.Assert(links, checker.Equals, "[\"/tlink1:/tlink2/tlink1\"]", check.Commentf("expected to have links between containers")) 1063 1064 status, b, err := sockRequest("DELETE", "/containers/tlink2/tlink1?link=1", nil) 1065 c.Assert(err, check.IsNil) 1066 c.Assert(status, check.Equals, http.StatusNoContent, check.Commentf(string(b))) 1067 1068 linksPostRm := inspectFieldJSON(c, id2, "HostConfig.Links") 1069 c.Assert(linksPostRm, checker.Equals, "null", check.Commentf("call to api deleteContainer links should have removed the specified links")) 1070 } 1071 1072 func (s *DockerSuite) TestContainerAPIDeleteConflict(c *check.C) { 1073 out, _ := runSleepingContainer(c) 1074 1075 id := strings.TrimSpace(out) 1076 c.Assert(waitRun(id), checker.IsNil) 1077 1078 status, _, err := sockRequest("DELETE", "/containers/"+id, nil) 1079 c.Assert(err, checker.IsNil) 1080 c.Assert(status, checker.Equals, http.StatusConflict) 1081 } 1082 1083 func (s *DockerSuite) TestContainerAPIDeleteRemoveVolume(c *check.C) { 1084 testRequires(c, SameHostDaemon) 1085 1086 vol := "/testvolume" 1087 if daemonPlatform == "windows" { 1088 vol = `c:\testvolume` 1089 } 1090 1091 out, _ := runSleepingContainer(c, "-v", vol) 1092 1093 id := strings.TrimSpace(out) 1094 c.Assert(waitRun(id), checker.IsNil) 1095 1096 source, err := inspectMountSourceField(id, vol) 1097 _, err = os.Stat(source) 1098 c.Assert(err, checker.IsNil) 1099 1100 status, _, err := sockRequest("DELETE", "/containers/"+id+"?v=1&force=1", nil) 1101 c.Assert(err, checker.IsNil) 1102 c.Assert(status, checker.Equals, http.StatusNoContent) 1103 _, err = os.Stat(source) 1104 c.Assert(os.IsNotExist(err), checker.True, check.Commentf("expected to get ErrNotExist error, got %v", err)) 1105 } 1106 1107 // Regression test for https://github.com/docker/docker/issues/6231 1108 func (s *DockerSuite) TestContainerAPIChunkedEncoding(c *check.C) { 1109 conn, err := sockConn(time.Duration(10*time.Second), "") 1110 c.Assert(err, checker.IsNil) 1111 client := httputil.NewClientConn(conn, nil) 1112 defer client.Close() 1113 1114 config := map[string]interface{}{ 1115 "Image": "busybox", 1116 "Cmd": append([]string{"/bin/sh", "-c"}, sleepCommandForDaemonPlatform()...), 1117 "OpenStdin": true, 1118 } 1119 b, err := json.Marshal(config) 1120 c.Assert(err, checker.IsNil) 1121 1122 req, err := http.NewRequest("POST", "/containers/create", bytes.NewBuffer(b)) 1123 c.Assert(err, checker.IsNil) 1124 req.Header.Set("Content-Type", "application/json") 1125 // This is a cheat to make the http request do chunked encoding 1126 // Otherwise (just setting the Content-Encoding to chunked) net/http will overwrite 1127 // https://golang.org/src/pkg/net/http/request.go?s=11980:12172 1128 req.ContentLength = -1 1129 1130 resp, err := client.Do(req) 1131 c.Assert(err, checker.IsNil, check.Commentf("error creating container with chunked encoding")) 1132 resp.Body.Close() 1133 c.Assert(resp.StatusCode, checker.Equals, http.StatusCreated) 1134 } 1135 1136 func (s *DockerSuite) TestContainerAPIPostContainerStop(c *check.C) { 1137 out, _ := runSleepingContainer(c) 1138 1139 containerID := strings.TrimSpace(out) 1140 c.Assert(waitRun(containerID), checker.IsNil) 1141 1142 statusCode, _, err := sockRequest("POST", "/containers/"+containerID+"/stop", nil) 1143 c.Assert(err, checker.IsNil) 1144 // 204 No Content is expected, not 200 1145 c.Assert(statusCode, checker.Equals, http.StatusNoContent) 1146 c.Assert(waitInspect(containerID, "{{ .State.Running }}", "false", 60*time.Second), checker.IsNil) 1147 } 1148 1149 // #14170 1150 func (s *DockerSuite) TestPostContainerAPICreateWithStringOrSliceEntrypoint(c *check.C) { 1151 config := struct { 1152 Image string 1153 Entrypoint string 1154 Cmd []string 1155 }{"busybox", "echo", []string{"hello", "world"}} 1156 _, _, err := sockRequest("POST", "/containers/create?name=echotest", config) 1157 c.Assert(err, checker.IsNil) 1158 out, _ := dockerCmd(c, "start", "-a", "echotest") 1159 c.Assert(strings.TrimSpace(out), checker.Equals, "hello world") 1160 1161 config2 := struct { 1162 Image string 1163 Entrypoint []string 1164 Cmd []string 1165 }{"busybox", []string{"echo"}, []string{"hello", "world"}} 1166 _, _, err = sockRequest("POST", "/containers/create?name=echotest2", config2) 1167 c.Assert(err, checker.IsNil) 1168 out, _ = dockerCmd(c, "start", "-a", "echotest2") 1169 c.Assert(strings.TrimSpace(out), checker.Equals, "hello world") 1170 } 1171 1172 // #14170 1173 func (s *DockerSuite) TestPostContainersCreateWithStringOrSliceCmd(c *check.C) { 1174 config := struct { 1175 Image string 1176 Entrypoint string 1177 Cmd string 1178 }{"busybox", "echo", "hello world"} 1179 _, _, err := sockRequest("POST", "/containers/create?name=echotest", config) 1180 c.Assert(err, checker.IsNil) 1181 out, _ := dockerCmd(c, "start", "-a", "echotest") 1182 c.Assert(strings.TrimSpace(out), checker.Equals, "hello world") 1183 1184 config2 := struct { 1185 Image string 1186 Cmd []string 1187 }{"busybox", []string{"echo", "hello", "world"}} 1188 _, _, err = sockRequest("POST", "/containers/create?name=echotest2", config2) 1189 c.Assert(err, checker.IsNil) 1190 out, _ = dockerCmd(c, "start", "-a", "echotest2") 1191 c.Assert(strings.TrimSpace(out), checker.Equals, "hello world") 1192 } 1193 1194 // regression #14318 1195 func (s *DockerSuite) TestPostContainersCreateWithStringOrSliceCapAddDrop(c *check.C) { 1196 // Windows doesn't support CapAdd/CapDrop 1197 testRequires(c, DaemonIsLinux) 1198 config := struct { 1199 Image string 1200 CapAdd string 1201 CapDrop string 1202 }{"busybox", "NET_ADMIN", "SYS_ADMIN"} 1203 status, _, err := sockRequest("POST", "/containers/create?name=capaddtest0", config) 1204 c.Assert(err, checker.IsNil) 1205 c.Assert(status, checker.Equals, http.StatusCreated) 1206 1207 config2 := struct { 1208 Image string 1209 CapAdd []string 1210 CapDrop []string 1211 }{"busybox", []string{"NET_ADMIN", "SYS_ADMIN"}, []string{"SETGID"}} 1212 status, _, err = sockRequest("POST", "/containers/create?name=capaddtest1", config2) 1213 c.Assert(err, checker.IsNil) 1214 c.Assert(status, checker.Equals, http.StatusCreated) 1215 } 1216 1217 // #14915 1218 func (s *DockerSuite) TestContainerAPICreateNoHostConfig118(c *check.C) { 1219 config := struct { 1220 Image string 1221 }{"busybox"} 1222 status, _, err := sockRequest("POST", "/v1.18/containers/create", config) 1223 c.Assert(err, checker.IsNil) 1224 c.Assert(status, checker.Equals, http.StatusCreated) 1225 } 1226 1227 // Ensure an error occurs when you have a container read-only rootfs but you 1228 // extract an archive to a symlink in a writable volume which points to a 1229 // directory outside of the volume. 1230 func (s *DockerSuite) TestPutContainerArchiveErrSymlinkInVolumeToReadOnlyRootfs(c *check.C) { 1231 // Windows does not support read-only rootfs 1232 // Requires local volume mount bind. 1233 // --read-only + userns has remount issues 1234 testRequires(c, SameHostDaemon, NotUserNamespace, DaemonIsLinux) 1235 1236 testVol := getTestDir(c, "test-put-container-archive-err-symlink-in-volume-to-read-only-rootfs-") 1237 defer os.RemoveAll(testVol) 1238 1239 makeTestContentInDir(c, testVol) 1240 1241 cID := makeTestContainer(c, testContainerOptions{ 1242 readOnly: true, 1243 volumes: defaultVolumes(testVol), // Our bind mount is at /vol2 1244 }) 1245 defer deleteContainer(cID) 1246 1247 // Attempt to extract to a symlink in the volume which points to a 1248 // directory outside the volume. This should cause an error because the 1249 // rootfs is read-only. 1250 query := make(url.Values, 1) 1251 query.Set("path", "/vol2/symlinkToAbsDir") 1252 urlPath := fmt.Sprintf("/v1.20/containers/%s/archive?%s", cID, query.Encode()) 1253 1254 statusCode, body, err := sockRequest("PUT", urlPath, nil) 1255 c.Assert(err, checker.IsNil) 1256 1257 if !isCpCannotCopyReadOnly(fmt.Errorf(string(body))) { 1258 c.Fatalf("expected ErrContainerRootfsReadonly error, but got %d: %s", statusCode, string(body)) 1259 } 1260 } 1261 1262 func (s *DockerSuite) TestContainerAPIGetContainersJSONEmpty(c *check.C) { 1263 status, body, err := sockRequest("GET", "/containers/json?all=1", nil) 1264 c.Assert(err, checker.IsNil) 1265 c.Assert(status, checker.Equals, http.StatusOK) 1266 c.Assert(string(body), checker.Equals, "[]\n") 1267 } 1268 1269 func (s *DockerSuite) TestPostContainersCreateWithWrongCpusetValues(c *check.C) { 1270 // Not supported on Windows 1271 testRequires(c, DaemonIsLinux) 1272 1273 c1 := struct { 1274 Image string 1275 CpusetCpus string 1276 }{"busybox", "1-42,,"} 1277 name := "wrong-cpuset-cpus" 1278 status, body, err := sockRequest("POST", "/containers/create?name="+name, c1) 1279 c.Assert(err, checker.IsNil) 1280 c.Assert(status, checker.Equals, http.StatusInternalServerError) 1281 expected := "Invalid value 1-42,, for cpuset cpus" 1282 c.Assert(getErrorMessage(c, body), checker.Equals, expected) 1283 1284 c2 := struct { 1285 Image string 1286 CpusetMems string 1287 }{"busybox", "42-3,1--"} 1288 name = "wrong-cpuset-mems" 1289 status, body, err = sockRequest("POST", "/containers/create?name="+name, c2) 1290 c.Assert(err, checker.IsNil) 1291 c.Assert(status, checker.Equals, http.StatusInternalServerError) 1292 expected = "Invalid value 42-3,1-- for cpuset mems" 1293 c.Assert(getErrorMessage(c, body), checker.Equals, expected) 1294 } 1295 1296 func (s *DockerSuite) TestPostContainersCreateShmSizeNegative(c *check.C) { 1297 // ShmSize is not supported on Windows 1298 testRequires(c, DaemonIsLinux) 1299 config := map[string]interface{}{ 1300 "Image": "busybox", 1301 "HostConfig": map[string]interface{}{"ShmSize": -1}, 1302 } 1303 1304 status, body, err := sockRequest("POST", "/containers/create", config) 1305 c.Assert(err, check.IsNil) 1306 c.Assert(status, check.Equals, http.StatusInternalServerError) 1307 c.Assert(getErrorMessage(c, body), checker.Contains, "SHM size can not be less than 0") 1308 } 1309 1310 func (s *DockerSuite) TestPostContainersCreateShmSizeHostConfigOmitted(c *check.C) { 1311 // ShmSize is not supported on Windows 1312 testRequires(c, DaemonIsLinux) 1313 var defaultSHMSize int64 = 67108864 1314 config := map[string]interface{}{ 1315 "Image": "busybox", 1316 "Cmd": "mount", 1317 } 1318 1319 status, body, err := sockRequest("POST", "/containers/create", config) 1320 c.Assert(err, check.IsNil) 1321 c.Assert(status, check.Equals, http.StatusCreated) 1322 1323 var container types.ContainerCreateResponse 1324 c.Assert(json.Unmarshal(body, &container), check.IsNil) 1325 1326 status, body, err = sockRequest("GET", "/containers/"+container.ID+"/json", nil) 1327 c.Assert(err, check.IsNil) 1328 c.Assert(status, check.Equals, http.StatusOK) 1329 1330 var containerJSON types.ContainerJSON 1331 c.Assert(json.Unmarshal(body, &containerJSON), check.IsNil) 1332 1333 c.Assert(containerJSON.HostConfig.ShmSize, check.Equals, defaultSHMSize) 1334 1335 out, _ := dockerCmd(c, "start", "-i", containerJSON.ID) 1336 shmRegexp := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=65536k`) 1337 if !shmRegexp.MatchString(out) { 1338 c.Fatalf("Expected shm of 64MB in mount command, got %v", out) 1339 } 1340 } 1341 1342 func (s *DockerSuite) TestPostContainersCreateShmSizeOmitted(c *check.C) { 1343 // ShmSize is not supported on Windows 1344 testRequires(c, DaemonIsLinux) 1345 config := map[string]interface{}{ 1346 "Image": "busybox", 1347 "HostConfig": map[string]interface{}{}, 1348 "Cmd": "mount", 1349 } 1350 1351 status, body, err := sockRequest("POST", "/containers/create", config) 1352 c.Assert(err, check.IsNil) 1353 c.Assert(status, check.Equals, http.StatusCreated) 1354 1355 var container types.ContainerCreateResponse 1356 c.Assert(json.Unmarshal(body, &container), check.IsNil) 1357 1358 status, body, err = sockRequest("GET", "/containers/"+container.ID+"/json", nil) 1359 c.Assert(err, check.IsNil) 1360 c.Assert(status, check.Equals, http.StatusOK) 1361 1362 var containerJSON types.ContainerJSON 1363 c.Assert(json.Unmarshal(body, &containerJSON), check.IsNil) 1364 1365 c.Assert(containerJSON.HostConfig.ShmSize, check.Equals, int64(67108864)) 1366 1367 out, _ := dockerCmd(c, "start", "-i", containerJSON.ID) 1368 shmRegexp := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=65536k`) 1369 if !shmRegexp.MatchString(out) { 1370 c.Fatalf("Expected shm of 64MB in mount command, got %v", out) 1371 } 1372 } 1373 1374 func (s *DockerSuite) TestPostContainersCreateWithShmSize(c *check.C) { 1375 // ShmSize is not supported on Windows 1376 testRequires(c, DaemonIsLinux) 1377 config := map[string]interface{}{ 1378 "Image": "busybox", 1379 "Cmd": "mount", 1380 "HostConfig": map[string]interface{}{"ShmSize": 1073741824}, 1381 } 1382 1383 status, body, err := sockRequest("POST", "/containers/create", config) 1384 c.Assert(err, check.IsNil) 1385 c.Assert(status, check.Equals, http.StatusCreated) 1386 1387 var container types.ContainerCreateResponse 1388 c.Assert(json.Unmarshal(body, &container), check.IsNil) 1389 1390 status, body, err = sockRequest("GET", "/containers/"+container.ID+"/json", nil) 1391 c.Assert(err, check.IsNil) 1392 c.Assert(status, check.Equals, http.StatusOK) 1393 1394 var containerJSON types.ContainerJSON 1395 c.Assert(json.Unmarshal(body, &containerJSON), check.IsNil) 1396 1397 c.Assert(containerJSON.HostConfig.ShmSize, check.Equals, int64(1073741824)) 1398 1399 out, _ := dockerCmd(c, "start", "-i", containerJSON.ID) 1400 shmRegex := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=1048576k`) 1401 if !shmRegex.MatchString(out) { 1402 c.Fatalf("Expected shm of 1GB in mount command, got %v", out) 1403 } 1404 } 1405 1406 func (s *DockerSuite) TestPostContainersCreateMemorySwappinessHostConfigOmitted(c *check.C) { 1407 // Swappiness is not supported on Windows 1408 testRequires(c, DaemonIsLinux) 1409 config := map[string]interface{}{ 1410 "Image": "busybox", 1411 } 1412 1413 status, body, err := sockRequest("POST", "/containers/create", config) 1414 c.Assert(err, check.IsNil) 1415 c.Assert(status, check.Equals, http.StatusCreated) 1416 1417 var container types.ContainerCreateResponse 1418 c.Assert(json.Unmarshal(body, &container), check.IsNil) 1419 1420 status, body, err = sockRequest("GET", "/containers/"+container.ID+"/json", nil) 1421 c.Assert(err, check.IsNil) 1422 c.Assert(status, check.Equals, http.StatusOK) 1423 1424 var containerJSON types.ContainerJSON 1425 c.Assert(json.Unmarshal(body, &containerJSON), check.IsNil) 1426 1427 c.Assert(*containerJSON.HostConfig.MemorySwappiness, check.Equals, int64(-1)) 1428 } 1429 1430 // check validation is done daemon side and not only in cli 1431 func (s *DockerSuite) TestPostContainersCreateWithOomScoreAdjInvalidRange(c *check.C) { 1432 // OomScoreAdj is not supported on Windows 1433 testRequires(c, DaemonIsLinux) 1434 1435 config := struct { 1436 Image string 1437 OomScoreAdj int 1438 }{"busybox", 1001} 1439 name := "oomscoreadj-over" 1440 status, b, err := sockRequest("POST", "/containers/create?name="+name, config) 1441 c.Assert(err, check.IsNil) 1442 c.Assert(status, check.Equals, http.StatusInternalServerError) 1443 1444 expected := "Invalid value 1001, range for oom score adj is [-1000, 1000]" 1445 msg := getErrorMessage(c, b) 1446 if !strings.Contains(msg, expected) { 1447 c.Fatalf("Expected output to contain %q, got %q", expected, msg) 1448 } 1449 1450 config = struct { 1451 Image string 1452 OomScoreAdj int 1453 }{"busybox", -1001} 1454 name = "oomscoreadj-low" 1455 status, b, err = sockRequest("POST", "/containers/create?name="+name, config) 1456 c.Assert(err, check.IsNil) 1457 c.Assert(status, check.Equals, http.StatusInternalServerError) 1458 expected = "Invalid value -1001, range for oom score adj is [-1000, 1000]" 1459 msg = getErrorMessage(c, b) 1460 if !strings.Contains(msg, expected) { 1461 c.Fatalf("Expected output to contain %q, got %q", expected, msg) 1462 } 1463 } 1464 1465 // test case for #22210 where an empty container name caused panic. 1466 func (s *DockerSuite) TestContainerAPIDeleteWithEmptyName(c *check.C) { 1467 status, out, err := sockRequest("DELETE", "/containers/", nil) 1468 c.Assert(err, checker.IsNil) 1469 c.Assert(status, checker.Equals, http.StatusBadRequest) 1470 c.Assert(string(out), checker.Contains, "No container name or ID supplied") 1471 } 1472 1473 func (s *DockerSuite) TestContainerAPIStatsWithNetworkDisabled(c *check.C) { 1474 // Problematic on Windows as Windows does not support stats 1475 testRequires(c, DaemonIsLinux) 1476 1477 name := "testing-network-disabled" 1478 config := map[string]interface{}{ 1479 "Image": "busybox", 1480 "Cmd": []string{"top"}, 1481 "NetworkDisabled": true, 1482 } 1483 1484 status, _, err := sockRequest("POST", "/containers/create?name="+name, config) 1485 c.Assert(err, checker.IsNil) 1486 c.Assert(status, checker.Equals, http.StatusCreated) 1487 1488 status, _, err = sockRequest("POST", "/containers/"+name+"/start", nil) 1489 c.Assert(err, checker.IsNil) 1490 c.Assert(status, checker.Equals, http.StatusNoContent) 1491 1492 c.Assert(waitRun(name), check.IsNil) 1493 1494 type b struct { 1495 status int 1496 body []byte 1497 err error 1498 } 1499 bc := make(chan b, 1) 1500 go func() { 1501 status, body, err := sockRequest("GET", "/containers/"+name+"/stats", nil) 1502 bc <- b{status, body, err} 1503 }() 1504 1505 // allow some time to stream the stats from the container 1506 time.Sleep(4 * time.Second) 1507 dockerCmd(c, "rm", "-f", name) 1508 1509 // collect the results from the stats stream or timeout and fail 1510 // if the stream was not disconnected. 1511 select { 1512 case <-time.After(2 * time.Second): 1513 c.Fatal("stream was not closed after container was removed") 1514 case sr := <-bc: 1515 c.Assert(sr.err, checker.IsNil) 1516 c.Assert(sr.status, checker.Equals, http.StatusOK) 1517 1518 // decode only one object from the stream 1519 var s *types.Stats 1520 dec := json.NewDecoder(bytes.NewBuffer(sr.body)) 1521 c.Assert(dec.Decode(&s), checker.IsNil) 1522 } 1523 } 1524 1525 func (s *DockerSuite) TestContainersAPICreateMountsValidation(c *check.C) { 1526 type m mounttypes.Mount 1527 type hc struct{ Mounts []m } 1528 type cfg struct { 1529 Image string 1530 HostConfig hc 1531 } 1532 type testCase struct { 1533 config cfg 1534 status int 1535 msg string 1536 } 1537 1538 prefix, slash := getPrefixAndSlashFromDaemonPlatform() 1539 destPath := prefix + slash + "foo" 1540 notExistPath := prefix + slash + "notexist" 1541 1542 cases := []testCase{ 1543 {cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "notreal", Target: destPath}}}}, http.StatusBadRequest, "mount type unknown"}, 1544 {cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "bind"}}}}, http.StatusBadRequest, "Target must not be empty"}, 1545 {cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "bind", Target: destPath}}}}, http.StatusBadRequest, "Source must not be empty"}, 1546 {cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "bind", Source: notExistPath, Target: destPath}}}}, http.StatusBadRequest, "bind source path does not exist"}, 1547 {cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "volume"}}}}, http.StatusBadRequest, "Target must not be empty"}, 1548 {cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "volume", Source: "hello", Target: destPath}}}}, http.StatusCreated, ""}, 1549 {cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "volume", Source: "hello2", Target: destPath, VolumeOptions: &mounttypes.VolumeOptions{DriverConfig: &mounttypes.Driver{Name: "local"}}}}}}, http.StatusCreated, ""}, 1550 } 1551 1552 if SameHostDaemon.Condition() { 1553 tmpDir, err := ioutils.TempDir("", "test-mounts-api") 1554 c.Assert(err, checker.IsNil) 1555 defer os.RemoveAll(tmpDir) 1556 cases = append(cases, []testCase{ 1557 {cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "bind", Source: tmpDir, Target: destPath}}}}, http.StatusCreated, ""}, 1558 {cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "bind", Source: tmpDir, Target: destPath, VolumeOptions: &mounttypes.VolumeOptions{}}}}}, http.StatusBadRequest, "VolumeOptions must not be specified"}, 1559 }...) 1560 } 1561 1562 if DaemonIsLinux.Condition() { 1563 cases = append(cases, []testCase{ 1564 {cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "volume", Source: "hello3", Target: destPath, VolumeOptions: &mounttypes.VolumeOptions{DriverConfig: &mounttypes.Driver{Name: "local", Options: map[string]string{"o": "size=1"}}}}}}}, http.StatusCreated, ""}, 1565 }...) 1566 1567 } 1568 1569 for i, x := range cases { 1570 c.Logf("case %d", i) 1571 status, b, err := sockRequest("POST", "/containers/create", x.config) 1572 c.Assert(err, checker.IsNil) 1573 c.Assert(status, checker.Equals, x.status, check.Commentf("%s\n%v", string(b), cases[i].config)) 1574 if len(x.msg) > 0 { 1575 c.Assert(string(b), checker.Contains, x.msg, check.Commentf("%v", cases[i].config)) 1576 } 1577 } 1578 } 1579 1580 func (s *DockerSuite) TestContainerAPICreateMountsBindRead(c *check.C) { 1581 testRequires(c, NotUserNamespace, SameHostDaemon) 1582 // also with data in the host side 1583 prefix, slash := getPrefixAndSlashFromDaemonPlatform() 1584 destPath := prefix + slash + "foo" 1585 tmpDir, err := ioutil.TempDir("", "test-mounts-api-bind") 1586 c.Assert(err, checker.IsNil) 1587 defer os.RemoveAll(tmpDir) 1588 err = ioutil.WriteFile(filepath.Join(tmpDir, "bar"), []byte("hello"), 666) 1589 c.Assert(err, checker.IsNil) 1590 1591 data := map[string]interface{}{ 1592 "Image": "busybox", 1593 "Cmd": []string{"/bin/sh", "-c", "cat /foo/bar"}, 1594 "HostConfig": map[string]interface{}{"Mounts": []map[string]interface{}{{"Type": "bind", "Source": tmpDir, "Target": destPath}}}, 1595 } 1596 status, resp, err := sockRequest("POST", "/containers/create?name=test", data) 1597 c.Assert(err, checker.IsNil, check.Commentf(string(resp))) 1598 c.Assert(status, checker.Equals, http.StatusCreated, check.Commentf(string(resp))) 1599 1600 out, _ := dockerCmd(c, "start", "-a", "test") 1601 c.Assert(out, checker.Equals, "hello") 1602 } 1603 1604 // Test Mounts comes out as expected for the MountPoint 1605 func (s *DockerSuite) TestContainersAPICreateMountsCreate(c *check.C) { 1606 prefix, slash := getPrefixAndSlashFromDaemonPlatform() 1607 destPath := prefix + slash + "foo" 1608 1609 var ( 1610 err error 1611 testImg string 1612 ) 1613 if daemonPlatform != "windows" { 1614 testImg, err = buildImage("test-mount-config", ` 1615 FROM busybox 1616 RUN mkdir `+destPath+` && touch `+destPath+slash+`bar 1617 CMD cat `+destPath+slash+`bar 1618 `, true) 1619 } else { 1620 testImg = "busybox" 1621 } 1622 c.Assert(err, checker.IsNil) 1623 1624 type testCase struct { 1625 cfg mounttypes.Mount 1626 expected types.MountPoint 1627 } 1628 1629 cases := []testCase{ 1630 // use literal strings here for `Type` instead of the defined constants in the volume package to keep this honest 1631 // Validation of the actual `Mount` struct is done in another test is not needed here 1632 {mounttypes.Mount{Type: "volume", Target: destPath}, types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath}}, 1633 {mounttypes.Mount{Type: "volume", Target: destPath + slash}, types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath}}, 1634 {mounttypes.Mount{Type: "volume", Target: destPath, Source: "test1"}, types.MountPoint{Type: "volume", Name: "test1", RW: true, Destination: destPath}}, 1635 {mounttypes.Mount{Type: "volume", Target: destPath, ReadOnly: true, Source: "test2"}, types.MountPoint{Type: "volume", Name: "test2", RW: false, Destination: destPath}}, 1636 {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}}, 1637 } 1638 1639 if SameHostDaemon.Condition() { 1640 // setup temp dir for testing binds 1641 tmpDir1, err := ioutil.TempDir("", "test-mounts-api-1") 1642 c.Assert(err, checker.IsNil) 1643 defer os.RemoveAll(tmpDir1) 1644 cases = append(cases, []testCase{ 1645 {mounttypes.Mount{Type: "bind", Source: tmpDir1, Target: destPath}, types.MountPoint{Type: "bind", RW: true, Destination: destPath, Source: tmpDir1}}, 1646 {mounttypes.Mount{Type: "bind", Source: tmpDir1, Target: destPath, ReadOnly: true}, types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir1}}, 1647 }...) 1648 1649 // for modes only supported on Linux 1650 if DaemonIsLinux.Condition() { 1651 tmpDir3, err := ioutils.TempDir("", "test-mounts-api-3") 1652 c.Assert(err, checker.IsNil) 1653 defer os.RemoveAll(tmpDir3) 1654 1655 c.Assert(mount.Mount(tmpDir3, tmpDir3, "none", "bind,rw"), checker.IsNil) 1656 c.Assert(mount.ForceMount("", tmpDir3, "none", "shared"), checker.IsNil) 1657 1658 cases = append(cases, []testCase{ 1659 {mounttypes.Mount{Type: "bind", Source: tmpDir3, Target: destPath}, types.MountPoint{Type: "bind", RW: true, Destination: destPath, Source: tmpDir3}}, 1660 {mounttypes.Mount{Type: "bind", Source: tmpDir3, Target: destPath, ReadOnly: true}, types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir3}}, 1661 {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"}}, 1662 }...) 1663 } 1664 } 1665 1666 if daemonPlatform != "windows" { // Windows does not support volume populate 1667 cases = append(cases, []testCase{ 1668 {mounttypes.Mount{Type: "volume", Target: destPath, VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true}}, types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath}}, 1669 {mounttypes.Mount{Type: "volume", Target: destPath + slash, VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true}}, types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath}}, 1670 {mounttypes.Mount{Type: "volume", Target: destPath, Source: "test4", VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true}}, types.MountPoint{Type: "volume", Name: "test4", RW: true, Destination: destPath}}, 1671 {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}}, 1672 }...) 1673 } 1674 1675 type wrapper struct { 1676 containertypes.Config 1677 HostConfig containertypes.HostConfig 1678 } 1679 type createResp struct { 1680 ID string `json:"Id"` 1681 } 1682 for i, x := range cases { 1683 c.Logf("case %d - config: %v", i, x.cfg) 1684 status, data, err := sockRequest("POST", "/containers/create", wrapper{containertypes.Config{Image: testImg}, containertypes.HostConfig{Mounts: []mounttypes.Mount{x.cfg}}}) 1685 c.Assert(err, checker.IsNil, check.Commentf(string(data))) 1686 c.Assert(status, checker.Equals, http.StatusCreated, check.Commentf(string(data))) 1687 1688 var resp createResp 1689 err = json.Unmarshal(data, &resp) 1690 c.Assert(err, checker.IsNil, check.Commentf(string(data))) 1691 id := resp.ID 1692 1693 var mps []types.MountPoint 1694 err = json.NewDecoder(strings.NewReader(inspectFieldJSON(c, id, "Mounts"))).Decode(&mps) 1695 c.Assert(err, checker.IsNil) 1696 c.Assert(mps, checker.HasLen, 1) 1697 c.Assert(mps[0].Destination, checker.Equals, x.expected.Destination) 1698 1699 if len(x.expected.Source) > 0 { 1700 c.Assert(mps[0].Source, checker.Equals, x.expected.Source) 1701 } 1702 if len(x.expected.Name) > 0 { 1703 c.Assert(mps[0].Name, checker.Equals, x.expected.Name) 1704 } 1705 if len(x.expected.Driver) > 0 { 1706 c.Assert(mps[0].Driver, checker.Equals, x.expected.Driver) 1707 } 1708 c.Assert(mps[0].RW, checker.Equals, x.expected.RW) 1709 c.Assert(mps[0].Type, checker.Equals, x.expected.Type) 1710 c.Assert(mps[0].Mode, checker.Equals, x.expected.Mode) 1711 if len(x.expected.Propagation) > 0 { 1712 c.Assert(mps[0].Propagation, checker.Equals, x.expected.Propagation) 1713 } 1714 1715 out, _, err := dockerCmdWithError("start", "-a", id) 1716 if (x.cfg.Type != "volume" || (x.cfg.VolumeOptions != nil && x.cfg.VolumeOptions.NoCopy)) && daemonPlatform != "windows" { 1717 c.Assert(err, checker.NotNil, check.Commentf("%s\n%v", out, mps[0])) 1718 } else { 1719 c.Assert(err, checker.IsNil, check.Commentf("%s\n%v", out, mps[0])) 1720 } 1721 1722 dockerCmd(c, "rm", "-fv", id) 1723 if x.cfg.Type == "volume" && len(x.cfg.Source) > 0 { 1724 // This should still exist even though we removed the container 1725 dockerCmd(c, "volume", "inspect", mps[0].Name) 1726 } else { 1727 // This should be removed automatically when we removed the container 1728 out, _, err := dockerCmdWithError("volume", "inspect", mps[0].Name) 1729 c.Assert(err, checker.NotNil, check.Commentf(out)) 1730 } 1731 } 1732 }