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