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