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