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