github.com/nullne/docker@v1.13.0-rc1/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) TestContainerAPIInvalidRestartPolicyName(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) TestContainerAPIInvalidRestartPolicyRetryMismatch(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 restart count not valid with restart policy") 769 } 770 771 func (s *DockerSuite) TestContainerAPIInvalidRestartPolicyPositiveRetryCount(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 restart count must be a positive integer") 789 } 790 791 // Issue 7941 - test to make sure a "null" in JSON is just ignored. 792 // W/o this fix a null in JSON would be parsed into a string var as "null" 793 func (s *DockerSuite) TestContainerAPIPostCreateNull(c *check.C) { 794 config := `{ 795 "Hostname":"", 796 "Domainname":"", 797 "Memory":0, 798 "MemorySwap":0, 799 "CpuShares":0, 800 "Cpuset":null, 801 "AttachStdin":true, 802 "AttachStdout":true, 803 "AttachStderr":true, 804 "ExposedPorts":{}, 805 "Tty":true, 806 "OpenStdin":true, 807 "StdinOnce":true, 808 "Env":[], 809 "Cmd":"ls", 810 "Image":"busybox", 811 "Volumes":{}, 812 "WorkingDir":"", 813 "Entrypoint":null, 814 "NetworkDisabled":false, 815 "OnBuild":null}` 816 817 res, body, err := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") 818 c.Assert(err, checker.IsNil) 819 c.Assert(res.StatusCode, checker.Equals, http.StatusCreated) 820 821 b, err := readBody(body) 822 c.Assert(err, checker.IsNil) 823 type createResp struct { 824 ID string 825 } 826 var container createResp 827 c.Assert(json.Unmarshal(b, &container), checker.IsNil) 828 out := inspectField(c, container.ID, "HostConfig.CpusetCpus") 829 c.Assert(out, checker.Equals, "") 830 831 outMemory := inspectField(c, container.ID, "HostConfig.Memory") 832 c.Assert(outMemory, checker.Equals, "0") 833 outMemorySwap := inspectField(c, container.ID, "HostConfig.MemorySwap") 834 c.Assert(outMemorySwap, checker.Equals, "0") 835 } 836 837 func (s *DockerSuite) TestCreateWithTooLowMemoryLimit(c *check.C) { 838 // TODO Windows: Port once memory is supported 839 testRequires(c, DaemonIsLinux) 840 config := `{ 841 "Image": "busybox", 842 "Cmd": "ls", 843 "OpenStdin": true, 844 "CpuShares": 100, 845 "Memory": 524287 846 }` 847 848 res, body, err := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") 849 c.Assert(err, checker.IsNil) 850 b, err2 := readBody(body) 851 c.Assert(err2, checker.IsNil) 852 853 c.Assert(res.StatusCode, checker.Equals, http.StatusInternalServerError) 854 c.Assert(string(b), checker.Contains, "Minimum memory limit allowed is 4MB") 855 } 856 857 func (s *DockerSuite) TestContainerAPIRename(c *check.C) { 858 out, _ := dockerCmd(c, "run", "--name", "TestContainerAPIRename", "-d", "busybox", "sh") 859 860 containerID := strings.TrimSpace(out) 861 newName := "TestContainerAPIRenameNew" 862 statusCode, _, err := sockRequest("POST", "/containers/"+containerID+"/rename?name="+newName, nil) 863 c.Assert(err, checker.IsNil) 864 // 204 No Content is expected, not 200 865 c.Assert(statusCode, checker.Equals, http.StatusNoContent) 866 867 name := inspectField(c, containerID, "Name") 868 c.Assert(name, checker.Equals, "/"+newName, check.Commentf("Failed to rename container")) 869 } 870 871 func (s *DockerSuite) TestContainerAPIKill(c *check.C) { 872 name := "test-api-kill" 873 runSleepingContainer(c, "-i", "--name", name) 874 875 status, _, err := sockRequest("POST", "/containers/"+name+"/kill", nil) 876 c.Assert(err, checker.IsNil) 877 c.Assert(status, checker.Equals, http.StatusNoContent) 878 879 state := inspectField(c, name, "State.Running") 880 c.Assert(state, checker.Equals, "false", check.Commentf("got wrong State from container %s: %q", name, state)) 881 } 882 883 func (s *DockerSuite) TestContainerAPIRestart(c *check.C) { 884 name := "test-api-restart" 885 runSleepingContainer(c, "-di", "--name", name) 886 887 status, _, err := sockRequest("POST", "/containers/"+name+"/restart?t=1", nil) 888 c.Assert(err, checker.IsNil) 889 c.Assert(status, checker.Equals, http.StatusNoContent) 890 c.Assert(waitInspect(name, "{{ .State.Restarting }} {{ .State.Running }}", "false true", 15*time.Second), checker.IsNil) 891 } 892 893 func (s *DockerSuite) TestContainerAPIRestartNotimeoutParam(c *check.C) { 894 name := "test-api-restart-no-timeout-param" 895 out, _ := runSleepingContainer(c, "-di", "--name", name) 896 id := strings.TrimSpace(out) 897 c.Assert(waitRun(id), checker.IsNil) 898 899 status, _, err := sockRequest("POST", "/containers/"+name+"/restart", nil) 900 c.Assert(err, checker.IsNil) 901 c.Assert(status, checker.Equals, http.StatusNoContent) 902 c.Assert(waitInspect(name, "{{ .State.Restarting }} {{ .State.Running }}", "false true", 15*time.Second), checker.IsNil) 903 } 904 905 func (s *DockerSuite) TestContainerAPIStart(c *check.C) { 906 name := "testing-start" 907 config := map[string]interface{}{ 908 "Image": "busybox", 909 "Cmd": append([]string{"/bin/sh", "-c"}, sleepCommandForDaemonPlatform()...), 910 "OpenStdin": true, 911 } 912 913 status, _, err := sockRequest("POST", "/containers/create?name="+name, config) 914 c.Assert(err, checker.IsNil) 915 c.Assert(status, checker.Equals, http.StatusCreated) 916 917 status, _, err = sockRequest("POST", "/containers/"+name+"/start", nil) 918 c.Assert(err, checker.IsNil) 919 c.Assert(status, checker.Equals, http.StatusNoContent) 920 921 // second call to start should give 304 922 status, _, err = sockRequest("POST", "/containers/"+name+"/start", nil) 923 c.Assert(err, checker.IsNil) 924 925 // TODO(tibor): figure out why this doesn't work on windows 926 if isLocalDaemon { 927 c.Assert(status, checker.Equals, http.StatusNotModified) 928 } 929 } 930 931 func (s *DockerSuite) TestContainerAPIStop(c *check.C) { 932 name := "test-api-stop" 933 runSleepingContainer(c, "-i", "--name", name) 934 935 status, _, err := sockRequest("POST", "/containers/"+name+"/stop?t=30", nil) 936 c.Assert(err, checker.IsNil) 937 c.Assert(status, checker.Equals, http.StatusNoContent) 938 c.Assert(waitInspect(name, "{{ .State.Running }}", "false", 60*time.Second), checker.IsNil) 939 940 // second call to start should give 304 941 status, _, err = sockRequest("POST", "/containers/"+name+"/stop?t=30", nil) 942 c.Assert(err, checker.IsNil) 943 c.Assert(status, checker.Equals, http.StatusNotModified) 944 } 945 946 func (s *DockerSuite) TestContainerAPIWait(c *check.C) { 947 name := "test-api-wait" 948 949 sleepCmd := "/bin/sleep" 950 if daemonPlatform == "windows" { 951 sleepCmd = "sleep" 952 } 953 dockerCmd(c, "run", "--name", name, "busybox", sleepCmd, "2") 954 955 status, body, err := sockRequest("POST", "/containers/"+name+"/wait", nil) 956 c.Assert(err, checker.IsNil) 957 c.Assert(status, checker.Equals, http.StatusOK) 958 c.Assert(waitInspect(name, "{{ .State.Running }}", "false", 60*time.Second), checker.IsNil) 959 960 var waitres containertypes.ContainerWaitOKBody 961 c.Assert(json.Unmarshal(body, &waitres), checker.IsNil) 962 c.Assert(waitres.StatusCode, checker.Equals, int64(0)) 963 } 964 965 func (s *DockerSuite) TestContainerAPICopyNotExistsAnyMore(c *check.C) { 966 name := "test-container-api-copy" 967 dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt") 968 969 postData := types.CopyConfig{ 970 Resource: "/test.txt", 971 } 972 973 status, _, err := sockRequest("POST", "/containers/"+name+"/copy", postData) 974 c.Assert(err, checker.IsNil) 975 c.Assert(status, checker.Equals, http.StatusNotFound) 976 } 977 978 func (s *DockerSuite) TestContainerAPICopyPre124(c *check.C) { 979 testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later 980 name := "test-container-api-copy" 981 dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt") 982 983 postData := types.CopyConfig{ 984 Resource: "/test.txt", 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.StatusOK) 990 991 found := false 992 for tarReader := tar.NewReader(bytes.NewReader(body)); ; { 993 h, err := tarReader.Next() 994 if err != nil { 995 if err == io.EOF { 996 break 997 } 998 c.Fatal(err) 999 } 1000 if h.Name == "test.txt" { 1001 found = true 1002 break 1003 } 1004 } 1005 c.Assert(found, checker.True) 1006 } 1007 1008 func (s *DockerSuite) TestContainerAPICopyResourcePathEmptyPr124(c *check.C) { 1009 testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later 1010 name := "test-container-api-copy-resource-empty" 1011 dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt") 1012 1013 postData := types.CopyConfig{ 1014 Resource: "", 1015 } 1016 1017 status, body, err := sockRequest("POST", "/v1.23/containers/"+name+"/copy", postData) 1018 c.Assert(err, checker.IsNil) 1019 c.Assert(status, checker.Equals, http.StatusInternalServerError) 1020 c.Assert(string(body), checker.Matches, "Path cannot be empty\n") 1021 } 1022 1023 func (s *DockerSuite) TestContainerAPICopyResourcePathNotFoundPre124(c *check.C) { 1024 testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later 1025 name := "test-container-api-copy-resource-not-found" 1026 dockerCmd(c, "run", "--name", name, "busybox") 1027 1028 postData := types.CopyConfig{ 1029 Resource: "/notexist", 1030 } 1031 1032 status, body, err := sockRequest("POST", "/v1.23/containers/"+name+"/copy", postData) 1033 c.Assert(err, checker.IsNil) 1034 c.Assert(status, checker.Equals, http.StatusInternalServerError) 1035 c.Assert(string(body), checker.Matches, "Could not find the file /notexist in container "+name+"\n") 1036 } 1037 1038 func (s *DockerSuite) TestContainerAPICopyContainerNotFoundPr124(c *check.C) { 1039 testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later 1040 postData := types.CopyConfig{ 1041 Resource: "/something", 1042 } 1043 1044 status, _, err := sockRequest("POST", "/v1.23/containers/notexists/copy", postData) 1045 c.Assert(err, checker.IsNil) 1046 c.Assert(status, checker.Equals, http.StatusNotFound) 1047 } 1048 1049 func (s *DockerSuite) TestContainerAPIDelete(c *check.C) { 1050 out, _ := runSleepingContainer(c) 1051 1052 id := strings.TrimSpace(out) 1053 c.Assert(waitRun(id), checker.IsNil) 1054 1055 dockerCmd(c, "stop", id) 1056 1057 status, _, err := sockRequest("DELETE", "/containers/"+id, nil) 1058 c.Assert(err, checker.IsNil) 1059 c.Assert(status, checker.Equals, http.StatusNoContent) 1060 } 1061 1062 func (s *DockerSuite) TestContainerAPIDeleteNotExist(c *check.C) { 1063 status, body, err := sockRequest("DELETE", "/containers/doesnotexist", nil) 1064 c.Assert(err, checker.IsNil) 1065 c.Assert(status, checker.Equals, http.StatusNotFound) 1066 c.Assert(getErrorMessage(c, body), checker.Matches, "No such container: doesnotexist") 1067 } 1068 1069 func (s *DockerSuite) TestContainerAPIDeleteForce(c *check.C) { 1070 out, _ := runSleepingContainer(c) 1071 1072 id := strings.TrimSpace(out) 1073 c.Assert(waitRun(id), checker.IsNil) 1074 1075 status, _, err := sockRequest("DELETE", "/containers/"+id+"?force=1", nil) 1076 c.Assert(err, checker.IsNil) 1077 c.Assert(status, checker.Equals, http.StatusNoContent) 1078 } 1079 1080 func (s *DockerSuite) TestContainerAPIDeleteRemoveLinks(c *check.C) { 1081 // Windows does not support links 1082 testRequires(c, DaemonIsLinux) 1083 out, _ := dockerCmd(c, "run", "-d", "--name", "tlink1", "busybox", "top") 1084 1085 id := strings.TrimSpace(out) 1086 c.Assert(waitRun(id), checker.IsNil) 1087 1088 out, _ = dockerCmd(c, "run", "--link", "tlink1:tlink1", "--name", "tlink2", "-d", "busybox", "top") 1089 1090 id2 := strings.TrimSpace(out) 1091 c.Assert(waitRun(id2), checker.IsNil) 1092 1093 links := inspectFieldJSON(c, id2, "HostConfig.Links") 1094 c.Assert(links, checker.Equals, "[\"/tlink1:/tlink2/tlink1\"]", check.Commentf("expected to have links between containers")) 1095 1096 status, b, err := sockRequest("DELETE", "/containers/tlink2/tlink1?link=1", nil) 1097 c.Assert(err, check.IsNil) 1098 c.Assert(status, check.Equals, http.StatusNoContent, check.Commentf(string(b))) 1099 1100 linksPostRm := inspectFieldJSON(c, id2, "HostConfig.Links") 1101 c.Assert(linksPostRm, checker.Equals, "null", check.Commentf("call to api deleteContainer links should have removed the specified links")) 1102 } 1103 1104 func (s *DockerSuite) TestContainerAPIDeleteConflict(c *check.C) { 1105 out, _ := runSleepingContainer(c) 1106 1107 id := strings.TrimSpace(out) 1108 c.Assert(waitRun(id), checker.IsNil) 1109 1110 status, _, err := sockRequest("DELETE", "/containers/"+id, nil) 1111 c.Assert(err, checker.IsNil) 1112 c.Assert(status, checker.Equals, http.StatusConflict) 1113 } 1114 1115 func (s *DockerSuite) TestContainerAPIDeleteRemoveVolume(c *check.C) { 1116 testRequires(c, SameHostDaemon) 1117 1118 vol := "/testvolume" 1119 if daemonPlatform == "windows" { 1120 vol = `c:\testvolume` 1121 } 1122 1123 out, _ := runSleepingContainer(c, "-v", vol) 1124 1125 id := strings.TrimSpace(out) 1126 c.Assert(waitRun(id), checker.IsNil) 1127 1128 source, err := inspectMountSourceField(id, vol) 1129 _, err = os.Stat(source) 1130 c.Assert(err, checker.IsNil) 1131 1132 status, _, err := sockRequest("DELETE", "/containers/"+id+"?v=1&force=1", nil) 1133 c.Assert(err, checker.IsNil) 1134 c.Assert(status, checker.Equals, http.StatusNoContent) 1135 _, err = os.Stat(source) 1136 c.Assert(os.IsNotExist(err), checker.True, check.Commentf("expected to get ErrNotExist error, got %v", err)) 1137 } 1138 1139 // Regression test for https://github.com/docker/docker/issues/6231 1140 func (s *DockerSuite) TestContainerAPIChunkedEncoding(c *check.C) { 1141 conn, err := sockConn(time.Duration(10*time.Second), "") 1142 c.Assert(err, checker.IsNil) 1143 client := httputil.NewClientConn(conn, nil) 1144 defer client.Close() 1145 1146 config := map[string]interface{}{ 1147 "Image": "busybox", 1148 "Cmd": append([]string{"/bin/sh", "-c"}, sleepCommandForDaemonPlatform()...), 1149 "OpenStdin": true, 1150 } 1151 b, err := json.Marshal(config) 1152 c.Assert(err, checker.IsNil) 1153 1154 req, err := http.NewRequest("POST", "/containers/create", bytes.NewBuffer(b)) 1155 c.Assert(err, checker.IsNil) 1156 req.Header.Set("Content-Type", "application/json") 1157 // This is a cheat to make the http request do chunked encoding 1158 // Otherwise (just setting the Content-Encoding to chunked) net/http will overwrite 1159 // https://golang.org/src/pkg/net/http/request.go?s=11980:12172 1160 req.ContentLength = -1 1161 1162 resp, err := client.Do(req) 1163 c.Assert(err, checker.IsNil, check.Commentf("error creating container with chunked encoding")) 1164 resp.Body.Close() 1165 c.Assert(resp.StatusCode, checker.Equals, http.StatusCreated) 1166 } 1167 1168 func (s *DockerSuite) TestContainerAPIPostContainerStop(c *check.C) { 1169 out, _ := runSleepingContainer(c) 1170 1171 containerID := strings.TrimSpace(out) 1172 c.Assert(waitRun(containerID), checker.IsNil) 1173 1174 statusCode, _, err := sockRequest("POST", "/containers/"+containerID+"/stop", nil) 1175 c.Assert(err, checker.IsNil) 1176 // 204 No Content is expected, not 200 1177 c.Assert(statusCode, checker.Equals, http.StatusNoContent) 1178 c.Assert(waitInspect(containerID, "{{ .State.Running }}", "false", 60*time.Second), checker.IsNil) 1179 } 1180 1181 // #14170 1182 func (s *DockerSuite) TestPostContainerAPICreateWithStringOrSliceEntrypoint(c *check.C) { 1183 config := struct { 1184 Image string 1185 Entrypoint string 1186 Cmd []string 1187 }{"busybox", "echo", []string{"hello", "world"}} 1188 _, _, err := sockRequest("POST", "/containers/create?name=echotest", config) 1189 c.Assert(err, checker.IsNil) 1190 out, _ := dockerCmd(c, "start", "-a", "echotest") 1191 c.Assert(strings.TrimSpace(out), checker.Equals, "hello world") 1192 1193 config2 := struct { 1194 Image string 1195 Entrypoint []string 1196 Cmd []string 1197 }{"busybox", []string{"echo"}, []string{"hello", "world"}} 1198 _, _, err = sockRequest("POST", "/containers/create?name=echotest2", config2) 1199 c.Assert(err, checker.IsNil) 1200 out, _ = dockerCmd(c, "start", "-a", "echotest2") 1201 c.Assert(strings.TrimSpace(out), checker.Equals, "hello world") 1202 } 1203 1204 // #14170 1205 func (s *DockerSuite) TestPostContainersCreateWithStringOrSliceCmd(c *check.C) { 1206 config := struct { 1207 Image string 1208 Entrypoint string 1209 Cmd string 1210 }{"busybox", "echo", "hello world"} 1211 _, _, err := sockRequest("POST", "/containers/create?name=echotest", config) 1212 c.Assert(err, checker.IsNil) 1213 out, _ := dockerCmd(c, "start", "-a", "echotest") 1214 c.Assert(strings.TrimSpace(out), checker.Equals, "hello world") 1215 1216 config2 := struct { 1217 Image string 1218 Cmd []string 1219 }{"busybox", []string{"echo", "hello", "world"}} 1220 _, _, err = sockRequest("POST", "/containers/create?name=echotest2", config2) 1221 c.Assert(err, checker.IsNil) 1222 out, _ = dockerCmd(c, "start", "-a", "echotest2") 1223 c.Assert(strings.TrimSpace(out), checker.Equals, "hello world") 1224 } 1225 1226 // regression #14318 1227 func (s *DockerSuite) TestPostContainersCreateWithStringOrSliceCapAddDrop(c *check.C) { 1228 // Windows doesn't support CapAdd/CapDrop 1229 testRequires(c, DaemonIsLinux) 1230 config := struct { 1231 Image string 1232 CapAdd string 1233 CapDrop string 1234 }{"busybox", "NET_ADMIN", "SYS_ADMIN"} 1235 status, _, err := sockRequest("POST", "/containers/create?name=capaddtest0", config) 1236 c.Assert(err, checker.IsNil) 1237 c.Assert(status, checker.Equals, http.StatusCreated) 1238 1239 config2 := struct { 1240 Image string 1241 CapAdd []string 1242 CapDrop []string 1243 }{"busybox", []string{"NET_ADMIN", "SYS_ADMIN"}, []string{"SETGID"}} 1244 status, _, err = sockRequest("POST", "/containers/create?name=capaddtest1", config2) 1245 c.Assert(err, checker.IsNil) 1246 c.Assert(status, checker.Equals, http.StatusCreated) 1247 } 1248 1249 // #14915 1250 func (s *DockerSuite) TestContainerAPICreateNoHostConfig118(c *check.C) { 1251 testRequires(c, DaemonIsLinux) // Windows only support 1.25 or later 1252 config := struct { 1253 Image string 1254 }{"busybox"} 1255 status, _, err := sockRequest("POST", "/v1.18/containers/create", config) 1256 c.Assert(err, checker.IsNil) 1257 c.Assert(status, checker.Equals, http.StatusCreated) 1258 } 1259 1260 // Ensure an error occurs when you have a container read-only rootfs but you 1261 // extract an archive to a symlink in a writable volume which points to a 1262 // directory outside of the volume. 1263 func (s *DockerSuite) TestPutContainerArchiveErrSymlinkInVolumeToReadOnlyRootfs(c *check.C) { 1264 // Windows does not support read-only rootfs 1265 // Requires local volume mount bind. 1266 // --read-only + userns has remount issues 1267 testRequires(c, SameHostDaemon, NotUserNamespace, DaemonIsLinux) 1268 1269 testVol := getTestDir(c, "test-put-container-archive-err-symlink-in-volume-to-read-only-rootfs-") 1270 defer os.RemoveAll(testVol) 1271 1272 makeTestContentInDir(c, testVol) 1273 1274 cID := makeTestContainer(c, testContainerOptions{ 1275 readOnly: true, 1276 volumes: defaultVolumes(testVol), // Our bind mount is at /vol2 1277 }) 1278 defer deleteContainer(cID) 1279 1280 // Attempt to extract to a symlink in the volume which points to a 1281 // directory outside the volume. This should cause an error because the 1282 // rootfs is read-only. 1283 query := make(url.Values, 1) 1284 query.Set("path", "/vol2/symlinkToAbsDir") 1285 urlPath := fmt.Sprintf("/v1.20/containers/%s/archive?%s", cID, query.Encode()) 1286 1287 statusCode, body, err := sockRequest("PUT", urlPath, nil) 1288 c.Assert(err, checker.IsNil) 1289 1290 if !isCpCannotCopyReadOnly(fmt.Errorf(string(body))) { 1291 c.Fatalf("expected ErrContainerRootfsReadonly error, but got %d: %s", statusCode, string(body)) 1292 } 1293 } 1294 1295 func (s *DockerSuite) TestContainerAPIGetContainersJSONEmpty(c *check.C) { 1296 status, body, err := sockRequest("GET", "/containers/json?all=1", nil) 1297 c.Assert(err, checker.IsNil) 1298 c.Assert(status, checker.Equals, http.StatusOK) 1299 c.Assert(string(body), checker.Equals, "[]\n") 1300 } 1301 1302 func (s *DockerSuite) TestPostContainersCreateWithWrongCpusetValues(c *check.C) { 1303 // Not supported on Windows 1304 testRequires(c, DaemonIsLinux) 1305 1306 c1 := struct { 1307 Image string 1308 CpusetCpus string 1309 }{"busybox", "1-42,,"} 1310 name := "wrong-cpuset-cpus" 1311 status, body, err := sockRequest("POST", "/containers/create?name="+name, c1) 1312 c.Assert(err, checker.IsNil) 1313 c.Assert(status, checker.Equals, http.StatusInternalServerError) 1314 expected := "Invalid value 1-42,, for cpuset cpus" 1315 c.Assert(getErrorMessage(c, body), checker.Equals, expected) 1316 1317 c2 := struct { 1318 Image string 1319 CpusetMems string 1320 }{"busybox", "42-3,1--"} 1321 name = "wrong-cpuset-mems" 1322 status, body, err = sockRequest("POST", "/containers/create?name="+name, c2) 1323 c.Assert(err, checker.IsNil) 1324 c.Assert(status, checker.Equals, http.StatusInternalServerError) 1325 expected = "Invalid value 42-3,1-- for cpuset mems" 1326 c.Assert(getErrorMessage(c, body), checker.Equals, expected) 1327 } 1328 1329 func (s *DockerSuite) TestPostContainersCreateShmSizeNegative(c *check.C) { 1330 // ShmSize is not supported on Windows 1331 testRequires(c, DaemonIsLinux) 1332 config := map[string]interface{}{ 1333 "Image": "busybox", 1334 "HostConfig": map[string]interface{}{"ShmSize": -1}, 1335 } 1336 1337 status, body, err := sockRequest("POST", "/containers/create", config) 1338 c.Assert(err, check.IsNil) 1339 c.Assert(status, check.Equals, http.StatusInternalServerError) 1340 c.Assert(getErrorMessage(c, body), checker.Contains, "SHM size can not be less than 0") 1341 } 1342 1343 func (s *DockerSuite) TestPostContainersCreateShmSizeHostConfigOmitted(c *check.C) { 1344 // ShmSize is not supported on Windows 1345 testRequires(c, DaemonIsLinux) 1346 var defaultSHMSize int64 = 67108864 1347 config := map[string]interface{}{ 1348 "Image": "busybox", 1349 "Cmd": "mount", 1350 } 1351 1352 status, body, err := sockRequest("POST", "/containers/create", config) 1353 c.Assert(err, check.IsNil) 1354 c.Assert(status, check.Equals, http.StatusCreated) 1355 1356 var container containertypes.ContainerCreateCreatedBody 1357 c.Assert(json.Unmarshal(body, &container), check.IsNil) 1358 1359 status, body, err = sockRequest("GET", "/containers/"+container.ID+"/json", nil) 1360 c.Assert(err, check.IsNil) 1361 c.Assert(status, check.Equals, http.StatusOK) 1362 1363 var containerJSON types.ContainerJSON 1364 c.Assert(json.Unmarshal(body, &containerJSON), check.IsNil) 1365 1366 c.Assert(containerJSON.HostConfig.ShmSize, check.Equals, defaultSHMSize) 1367 1368 out, _ := dockerCmd(c, "start", "-i", containerJSON.ID) 1369 shmRegexp := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=65536k`) 1370 if !shmRegexp.MatchString(out) { 1371 c.Fatalf("Expected shm of 64MB in mount command, got %v", out) 1372 } 1373 } 1374 1375 func (s *DockerSuite) TestPostContainersCreateShmSizeOmitted(c *check.C) { 1376 // ShmSize is not supported on Windows 1377 testRequires(c, DaemonIsLinux) 1378 config := map[string]interface{}{ 1379 "Image": "busybox", 1380 "HostConfig": map[string]interface{}{}, 1381 "Cmd": "mount", 1382 } 1383 1384 status, body, err := sockRequest("POST", "/containers/create", config) 1385 c.Assert(err, check.IsNil) 1386 c.Assert(status, check.Equals, http.StatusCreated) 1387 1388 var container containertypes.ContainerCreateCreatedBody 1389 c.Assert(json.Unmarshal(body, &container), check.IsNil) 1390 1391 status, body, err = sockRequest("GET", "/containers/"+container.ID+"/json", nil) 1392 c.Assert(err, check.IsNil) 1393 c.Assert(status, check.Equals, http.StatusOK) 1394 1395 var containerJSON types.ContainerJSON 1396 c.Assert(json.Unmarshal(body, &containerJSON), check.IsNil) 1397 1398 c.Assert(containerJSON.HostConfig.ShmSize, check.Equals, int64(67108864)) 1399 1400 out, _ := dockerCmd(c, "start", "-i", containerJSON.ID) 1401 shmRegexp := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=65536k`) 1402 if !shmRegexp.MatchString(out) { 1403 c.Fatalf("Expected shm of 64MB in mount command, got %v", out) 1404 } 1405 } 1406 1407 func (s *DockerSuite) TestPostContainersCreateWithShmSize(c *check.C) { 1408 // ShmSize is not supported on Windows 1409 testRequires(c, DaemonIsLinux) 1410 config := map[string]interface{}{ 1411 "Image": "busybox", 1412 "Cmd": "mount", 1413 "HostConfig": map[string]interface{}{"ShmSize": 1073741824}, 1414 } 1415 1416 status, body, err := sockRequest("POST", "/containers/create", config) 1417 c.Assert(err, check.IsNil) 1418 c.Assert(status, check.Equals, http.StatusCreated) 1419 1420 var container containertypes.ContainerCreateCreatedBody 1421 c.Assert(json.Unmarshal(body, &container), check.IsNil) 1422 1423 status, body, err = sockRequest("GET", "/containers/"+container.ID+"/json", nil) 1424 c.Assert(err, check.IsNil) 1425 c.Assert(status, check.Equals, http.StatusOK) 1426 1427 var containerJSON types.ContainerJSON 1428 c.Assert(json.Unmarshal(body, &containerJSON), check.IsNil) 1429 1430 c.Assert(containerJSON.HostConfig.ShmSize, check.Equals, int64(1073741824)) 1431 1432 out, _ := dockerCmd(c, "start", "-i", containerJSON.ID) 1433 shmRegex := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=1048576k`) 1434 if !shmRegex.MatchString(out) { 1435 c.Fatalf("Expected shm of 1GB in mount command, got %v", out) 1436 } 1437 } 1438 1439 func (s *DockerSuite) TestPostContainersCreateMemorySwappinessHostConfigOmitted(c *check.C) { 1440 // Swappiness is not supported on Windows 1441 testRequires(c, DaemonIsLinux) 1442 config := map[string]interface{}{ 1443 "Image": "busybox", 1444 } 1445 1446 status, body, err := sockRequest("POST", "/containers/create", config) 1447 c.Assert(err, check.IsNil) 1448 c.Assert(status, check.Equals, http.StatusCreated) 1449 1450 var container containertypes.ContainerCreateCreatedBody 1451 c.Assert(json.Unmarshal(body, &container), check.IsNil) 1452 1453 status, body, err = sockRequest("GET", "/containers/"+container.ID+"/json", nil) 1454 c.Assert(err, check.IsNil) 1455 c.Assert(status, check.Equals, http.StatusOK) 1456 1457 var containerJSON types.ContainerJSON 1458 c.Assert(json.Unmarshal(body, &containerJSON), check.IsNil) 1459 1460 c.Assert(*containerJSON.HostConfig.MemorySwappiness, check.Equals, int64(-1)) 1461 } 1462 1463 // check validation is done daemon side and not only in cli 1464 func (s *DockerSuite) TestPostContainersCreateWithOomScoreAdjInvalidRange(c *check.C) { 1465 // OomScoreAdj is not supported on Windows 1466 testRequires(c, DaemonIsLinux) 1467 1468 config := struct { 1469 Image string 1470 OomScoreAdj int 1471 }{"busybox", 1001} 1472 name := "oomscoreadj-over" 1473 status, b, err := sockRequest("POST", "/containers/create?name="+name, config) 1474 c.Assert(err, check.IsNil) 1475 c.Assert(status, check.Equals, http.StatusInternalServerError) 1476 1477 expected := "Invalid value 1001, range for oom score adj is [-1000, 1000]" 1478 msg := getErrorMessage(c, b) 1479 if !strings.Contains(msg, expected) { 1480 c.Fatalf("Expected output to contain %q, got %q", expected, msg) 1481 } 1482 1483 config = struct { 1484 Image string 1485 OomScoreAdj int 1486 }{"busybox", -1001} 1487 name = "oomscoreadj-low" 1488 status, b, err = sockRequest("POST", "/containers/create?name="+name, config) 1489 c.Assert(err, check.IsNil) 1490 c.Assert(status, check.Equals, http.StatusInternalServerError) 1491 expected = "Invalid value -1001, range for oom score adj is [-1000, 1000]" 1492 msg = getErrorMessage(c, b) 1493 if !strings.Contains(msg, expected) { 1494 c.Fatalf("Expected output to contain %q, got %q", expected, msg) 1495 } 1496 } 1497 1498 // test case for #22210 where an empty container name caused panic. 1499 func (s *DockerSuite) TestContainerAPIDeleteWithEmptyName(c *check.C) { 1500 status, out, err := sockRequest("DELETE", "/containers/", nil) 1501 c.Assert(err, checker.IsNil) 1502 c.Assert(status, checker.Equals, http.StatusBadRequest) 1503 c.Assert(string(out), checker.Contains, "No container name or ID supplied") 1504 } 1505 1506 func (s *DockerSuite) TestContainerAPIStatsWithNetworkDisabled(c *check.C) { 1507 // Problematic on Windows as Windows does not support stats 1508 testRequires(c, DaemonIsLinux) 1509 1510 name := "testing-network-disabled" 1511 config := map[string]interface{}{ 1512 "Image": "busybox", 1513 "Cmd": []string{"top"}, 1514 "NetworkDisabled": true, 1515 } 1516 1517 status, _, err := sockRequest("POST", "/containers/create?name="+name, config) 1518 c.Assert(err, checker.IsNil) 1519 c.Assert(status, checker.Equals, http.StatusCreated) 1520 1521 status, _, err = sockRequest("POST", "/containers/"+name+"/start", nil) 1522 c.Assert(err, checker.IsNil) 1523 c.Assert(status, checker.Equals, http.StatusNoContent) 1524 1525 c.Assert(waitRun(name), check.IsNil) 1526 1527 type b struct { 1528 status int 1529 body []byte 1530 err error 1531 } 1532 bc := make(chan b, 1) 1533 go func() { 1534 status, body, err := sockRequest("GET", "/containers/"+name+"/stats", nil) 1535 bc <- b{status, body, err} 1536 }() 1537 1538 // allow some time to stream the stats from the container 1539 time.Sleep(4 * time.Second) 1540 dockerCmd(c, "rm", "-f", name) 1541 1542 // collect the results from the stats stream or timeout and fail 1543 // if the stream was not disconnected. 1544 select { 1545 case <-time.After(2 * time.Second): 1546 c.Fatal("stream was not closed after container was removed") 1547 case sr := <-bc: 1548 c.Assert(sr.err, checker.IsNil) 1549 c.Assert(sr.status, checker.Equals, http.StatusOK) 1550 1551 // decode only one object from the stream 1552 var s *types.Stats 1553 dec := json.NewDecoder(bytes.NewBuffer(sr.body)) 1554 c.Assert(dec.Decode(&s), checker.IsNil) 1555 } 1556 } 1557 1558 func (s *DockerSuite) TestContainersAPICreateMountsValidation(c *check.C) { 1559 type m mounttypes.Mount 1560 type hc struct{ Mounts []m } 1561 type cfg struct { 1562 Image string 1563 HostConfig hc 1564 } 1565 type testCase struct { 1566 config cfg 1567 status int 1568 msg string 1569 } 1570 1571 prefix, slash := getPrefixAndSlashFromDaemonPlatform() 1572 destPath := prefix + slash + "foo" 1573 notExistPath := prefix + slash + "notexist" 1574 1575 cases := []testCase{ 1576 { 1577 config: cfg{ 1578 Image: "busybox", 1579 HostConfig: hc{ 1580 Mounts: []m{{ 1581 Type: "notreal", 1582 Target: destPath}}}}, 1583 status: http.StatusBadRequest, 1584 msg: "mount type unknown", 1585 }, 1586 { 1587 config: cfg{ 1588 Image: "busybox", 1589 HostConfig: hc{ 1590 Mounts: []m{{ 1591 Type: "bind"}}}}, 1592 status: http.StatusBadRequest, 1593 msg: "Target must not be empty", 1594 }, 1595 { 1596 config: cfg{ 1597 Image: "busybox", 1598 HostConfig: hc{ 1599 Mounts: []m{{ 1600 Type: "bind", 1601 Target: destPath}}}}, 1602 status: http.StatusBadRequest, 1603 msg: "Source must not be empty", 1604 }, 1605 { 1606 config: cfg{ 1607 Image: "busybox", 1608 HostConfig: hc{ 1609 Mounts: []m{{ 1610 Type: "bind", 1611 Source: notExistPath, 1612 Target: destPath}}}}, 1613 status: http.StatusBadRequest, 1614 msg: "bind source path does not exist", 1615 }, 1616 { 1617 config: cfg{ 1618 Image: "busybox", 1619 HostConfig: hc{ 1620 Mounts: []m{{ 1621 Type: "volume"}}}}, 1622 status: http.StatusBadRequest, 1623 msg: "Target must not be empty", 1624 }, 1625 { 1626 config: cfg{ 1627 Image: "busybox", 1628 HostConfig: hc{ 1629 Mounts: []m{{ 1630 Type: "volume", 1631 Source: "hello", 1632 Target: destPath}}}}, 1633 status: http.StatusCreated, 1634 msg: "", 1635 }, 1636 { 1637 config: cfg{ 1638 Image: "busybox", 1639 HostConfig: hc{ 1640 Mounts: []m{{ 1641 Type: "volume", 1642 Source: "hello2", 1643 Target: destPath, 1644 VolumeOptions: &mounttypes.VolumeOptions{ 1645 DriverConfig: &mounttypes.Driver{ 1646 Name: "local"}}}}}}, 1647 status: http.StatusCreated, 1648 msg: "", 1649 }, 1650 } 1651 1652 if SameHostDaemon.Condition() { 1653 tmpDir, err := ioutils.TempDir("", "test-mounts-api") 1654 c.Assert(err, checker.IsNil) 1655 defer os.RemoveAll(tmpDir) 1656 cases = append(cases, []testCase{ 1657 { 1658 config: cfg{ 1659 Image: "busybox", 1660 HostConfig: hc{ 1661 Mounts: []m{{ 1662 Type: "bind", 1663 Source: tmpDir, 1664 Target: destPath}}}}, 1665 status: http.StatusCreated, 1666 msg: "", 1667 }, 1668 { 1669 config: cfg{ 1670 Image: "busybox", 1671 HostConfig: hc{ 1672 Mounts: []m{{ 1673 Type: "bind", 1674 Source: tmpDir, 1675 Target: destPath, 1676 VolumeOptions: &mounttypes.VolumeOptions{}}}}}, 1677 status: http.StatusBadRequest, 1678 msg: "VolumeOptions must not be specified", 1679 }, 1680 }...) 1681 } 1682 1683 if DaemonIsLinux.Condition() { 1684 cases = append(cases, []testCase{ 1685 { 1686 config: cfg{ 1687 Image: "busybox", 1688 HostConfig: hc{ 1689 Mounts: []m{{ 1690 Type: "volume", 1691 Source: "hello3", 1692 Target: destPath, 1693 VolumeOptions: &mounttypes.VolumeOptions{ 1694 DriverConfig: &mounttypes.Driver{ 1695 Name: "local", 1696 Options: map[string]string{"o": "size=1"}}}}}}}, 1697 status: http.StatusCreated, 1698 msg: "", 1699 }, 1700 { 1701 config: cfg{ 1702 Image: "busybox", 1703 HostConfig: hc{ 1704 Mounts: []m{{ 1705 Type: "tmpfs", 1706 Target: destPath}}}}, 1707 status: http.StatusCreated, 1708 msg: "", 1709 }, 1710 { 1711 config: cfg{ 1712 Image: "busybox", 1713 HostConfig: hc{ 1714 Mounts: []m{{ 1715 Type: "tmpfs", 1716 Target: destPath, 1717 TmpfsOptions: &mounttypes.TmpfsOptions{ 1718 SizeBytes: 4096 * 1024, 1719 Mode: 0700, 1720 }}}}}, 1721 status: http.StatusCreated, 1722 msg: "", 1723 }, 1724 1725 { 1726 config: cfg{ 1727 Image: "busybox", 1728 HostConfig: hc{ 1729 Mounts: []m{{ 1730 Type: "tmpfs", 1731 Source: "/shouldnotbespecified", 1732 Target: destPath}}}}, 1733 status: http.StatusBadRequest, 1734 msg: "Source must not be specified", 1735 }, 1736 }...) 1737 1738 } 1739 1740 for i, x := range cases { 1741 c.Logf("case %d", i) 1742 status, b, err := sockRequest("POST", "/containers/create", x.config) 1743 c.Assert(err, checker.IsNil) 1744 c.Assert(status, checker.Equals, x.status, check.Commentf("%s\n%v", string(b), cases[i].config)) 1745 if len(x.msg) > 0 { 1746 c.Assert(string(b), checker.Contains, x.msg, check.Commentf("%v", cases[i].config)) 1747 } 1748 } 1749 } 1750 1751 func (s *DockerSuite) TestContainerAPICreateMountsBindRead(c *check.C) { 1752 testRequires(c, NotUserNamespace, SameHostDaemon) 1753 // also with data in the host side 1754 prefix, slash := getPrefixAndSlashFromDaemonPlatform() 1755 destPath := prefix + slash + "foo" 1756 tmpDir, err := ioutil.TempDir("", "test-mounts-api-bind") 1757 c.Assert(err, checker.IsNil) 1758 defer os.RemoveAll(tmpDir) 1759 err = ioutil.WriteFile(filepath.Join(tmpDir, "bar"), []byte("hello"), 666) 1760 c.Assert(err, checker.IsNil) 1761 1762 data := map[string]interface{}{ 1763 "Image": "busybox", 1764 "Cmd": []string{"/bin/sh", "-c", "cat /foo/bar"}, 1765 "HostConfig": map[string]interface{}{"Mounts": []map[string]interface{}{{"Type": "bind", "Source": tmpDir, "Target": destPath}}}, 1766 } 1767 status, resp, err := sockRequest("POST", "/containers/create?name=test", data) 1768 c.Assert(err, checker.IsNil, check.Commentf(string(resp))) 1769 c.Assert(status, checker.Equals, http.StatusCreated, check.Commentf(string(resp))) 1770 1771 out, _ := dockerCmd(c, "start", "-a", "test") 1772 c.Assert(out, checker.Equals, "hello") 1773 } 1774 1775 // Test Mounts comes out as expected for the MountPoint 1776 func (s *DockerSuite) TestContainersAPICreateMountsCreate(c *check.C) { 1777 prefix, slash := getPrefixAndSlashFromDaemonPlatform() 1778 destPath := prefix + slash + "foo" 1779 1780 var ( 1781 err error 1782 testImg string 1783 ) 1784 if daemonPlatform != "windows" { 1785 testImg, err = buildImage("test-mount-config", ` 1786 FROM busybox 1787 RUN mkdir `+destPath+` && touch `+destPath+slash+`bar 1788 CMD cat `+destPath+slash+`bar 1789 `, true) 1790 } else { 1791 testImg = "busybox" 1792 } 1793 c.Assert(err, checker.IsNil) 1794 1795 type testCase struct { 1796 cfg mounttypes.Mount 1797 expected types.MountPoint 1798 } 1799 1800 cases := []testCase{ 1801 // use literal strings here for `Type` instead of the defined constants in the volume package to keep this honest 1802 // Validation of the actual `Mount` struct is done in another test is not needed here 1803 {mounttypes.Mount{Type: "volume", Target: destPath}, types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath}}, 1804 {mounttypes.Mount{Type: "volume", Target: destPath + slash}, types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath}}, 1805 {mounttypes.Mount{Type: "volume", Target: destPath, Source: "test1"}, types.MountPoint{Type: "volume", Name: "test1", RW: true, Destination: destPath}}, 1806 {mounttypes.Mount{Type: "volume", Target: destPath, ReadOnly: true, Source: "test2"}, types.MountPoint{Type: "volume", Name: "test2", RW: false, Destination: destPath}}, 1807 {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}}, 1808 } 1809 1810 if SameHostDaemon.Condition() { 1811 // setup temp dir for testing binds 1812 tmpDir1, err := ioutil.TempDir("", "test-mounts-api-1") 1813 c.Assert(err, checker.IsNil) 1814 defer os.RemoveAll(tmpDir1) 1815 cases = append(cases, []testCase{ 1816 {mounttypes.Mount{Type: "bind", Source: tmpDir1, Target: destPath}, types.MountPoint{Type: "bind", RW: true, Destination: destPath, Source: tmpDir1}}, 1817 {mounttypes.Mount{Type: "bind", Source: tmpDir1, Target: destPath, ReadOnly: true}, types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir1}}, 1818 }...) 1819 1820 // for modes only supported on Linux 1821 if DaemonIsLinux.Condition() { 1822 tmpDir3, err := ioutils.TempDir("", "test-mounts-api-3") 1823 c.Assert(err, checker.IsNil) 1824 defer os.RemoveAll(tmpDir3) 1825 1826 c.Assert(mount.Mount(tmpDir3, tmpDir3, "none", "bind,rw"), checker.IsNil) 1827 c.Assert(mount.ForceMount("", tmpDir3, "none", "shared"), checker.IsNil) 1828 1829 cases = append(cases, []testCase{ 1830 {mounttypes.Mount{Type: "bind", Source: tmpDir3, Target: destPath}, types.MountPoint{Type: "bind", RW: true, Destination: destPath, Source: tmpDir3}}, 1831 {mounttypes.Mount{Type: "bind", Source: tmpDir3, Target: destPath, ReadOnly: true}, types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir3}}, 1832 {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"}}, 1833 }...) 1834 } 1835 } 1836 1837 if daemonPlatform != "windows" { // Windows does not support volume populate 1838 cases = append(cases, []testCase{ 1839 {mounttypes.Mount{Type: "volume", Target: destPath, VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true}}, types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath}}, 1840 {mounttypes.Mount{Type: "volume", Target: destPath + slash, VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true}}, types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath}}, 1841 {mounttypes.Mount{Type: "volume", Target: destPath, Source: "test4", VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true}}, types.MountPoint{Type: "volume", Name: "test4", RW: true, Destination: destPath}}, 1842 {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}}, 1843 }...) 1844 } 1845 1846 type wrapper struct { 1847 containertypes.Config 1848 HostConfig containertypes.HostConfig 1849 } 1850 type createResp struct { 1851 ID string `json:"Id"` 1852 } 1853 for i, x := range cases { 1854 c.Logf("case %d - config: %v", i, x.cfg) 1855 status, data, err := sockRequest("POST", "/containers/create", wrapper{containertypes.Config{Image: testImg}, containertypes.HostConfig{Mounts: []mounttypes.Mount{x.cfg}}}) 1856 c.Assert(err, checker.IsNil, check.Commentf(string(data))) 1857 c.Assert(status, checker.Equals, http.StatusCreated, check.Commentf(string(data))) 1858 1859 var resp createResp 1860 err = json.Unmarshal(data, &resp) 1861 c.Assert(err, checker.IsNil, check.Commentf(string(data))) 1862 id := resp.ID 1863 1864 var mps []types.MountPoint 1865 err = json.NewDecoder(strings.NewReader(inspectFieldJSON(c, id, "Mounts"))).Decode(&mps) 1866 c.Assert(err, checker.IsNil) 1867 c.Assert(mps, checker.HasLen, 1) 1868 c.Assert(mps[0].Destination, checker.Equals, x.expected.Destination) 1869 1870 if len(x.expected.Source) > 0 { 1871 c.Assert(mps[0].Source, checker.Equals, x.expected.Source) 1872 } 1873 if len(x.expected.Name) > 0 { 1874 c.Assert(mps[0].Name, checker.Equals, x.expected.Name) 1875 } 1876 if len(x.expected.Driver) > 0 { 1877 c.Assert(mps[0].Driver, checker.Equals, x.expected.Driver) 1878 } 1879 c.Assert(mps[0].RW, checker.Equals, x.expected.RW) 1880 c.Assert(mps[0].Type, checker.Equals, x.expected.Type) 1881 c.Assert(mps[0].Mode, checker.Equals, x.expected.Mode) 1882 if len(x.expected.Propagation) > 0 { 1883 c.Assert(mps[0].Propagation, checker.Equals, x.expected.Propagation) 1884 } 1885 1886 out, _, err := dockerCmdWithError("start", "-a", id) 1887 if (x.cfg.Type != "volume" || (x.cfg.VolumeOptions != nil && x.cfg.VolumeOptions.NoCopy)) && daemonPlatform != "windows" { 1888 c.Assert(err, checker.NotNil, check.Commentf("%s\n%v", out, mps[0])) 1889 } else { 1890 c.Assert(err, checker.IsNil, check.Commentf("%s\n%v", out, mps[0])) 1891 } 1892 1893 dockerCmd(c, "rm", "-fv", id) 1894 if x.cfg.Type == "volume" && len(x.cfg.Source) > 0 { 1895 // This should still exist even though we removed the container 1896 dockerCmd(c, "volume", "inspect", mps[0].Name) 1897 } else { 1898 // This should be removed automatically when we removed the container 1899 out, _, err := dockerCmdWithError("volume", "inspect", mps[0].Name) 1900 c.Assert(err, checker.NotNil, check.Commentf(out)) 1901 } 1902 } 1903 } 1904 1905 func (s *DockerSuite) TestContainersAPICreateMountsTmpfs(c *check.C) { 1906 testRequires(c, DaemonIsLinux) 1907 type testCase struct { 1908 cfg map[string]interface{} 1909 expectedOptions []string 1910 } 1911 target := "/foo" 1912 cases := []testCase{ 1913 { 1914 cfg: map[string]interface{}{ 1915 "Type": "tmpfs", 1916 "Target": target}, 1917 expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime"}, 1918 }, 1919 { 1920 cfg: map[string]interface{}{ 1921 "Type": "tmpfs", 1922 "Target": target, 1923 "TmpfsOptions": map[string]interface{}{ 1924 "SizeBytes": 4096 * 1024, "Mode": 0700}}, 1925 expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime", "size=4096k", "mode=700"}, 1926 }, 1927 } 1928 1929 for i, x := range cases { 1930 cName := fmt.Sprintf("test-tmpfs-%d", i) 1931 data := map[string]interface{}{ 1932 "Image": "busybox", 1933 "Cmd": []string{"/bin/sh", "-c", 1934 fmt.Sprintf("mount | grep 'tmpfs on %s'", target)}, 1935 "HostConfig": map[string]interface{}{"Mounts": []map[string]interface{}{x.cfg}}, 1936 } 1937 status, resp, err := sockRequest("POST", "/containers/create?name="+cName, data) 1938 c.Assert(err, checker.IsNil, check.Commentf(string(resp))) 1939 c.Assert(status, checker.Equals, http.StatusCreated, check.Commentf(string(resp))) 1940 out, _ := dockerCmd(c, "start", "-a", cName) 1941 for _, option := range x.expectedOptions { 1942 c.Assert(out, checker.Contains, option) 1943 } 1944 } 1945 }