github.com/sirupsen/docker@v0.10.1-0.20150325003727-22dba32b4dab/integration-cli/docker_api_containers_test.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "io" 7 "os/exec" 8 "strings" 9 "testing" 10 "time" 11 12 "github.com/docker/docker/api/types" 13 "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" 14 ) 15 16 func TestContainerApiGetAll(t *testing.T) { 17 defer deleteAllContainers() 18 19 startCount, err := getContainerCount() 20 if err != nil { 21 t.Fatalf("Cannot query container count: %v", err) 22 } 23 24 name := "getall" 25 runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "true") 26 out, _, err := runCommandWithOutput(runCmd) 27 if err != nil { 28 t.Fatalf("Error on container creation: %v, output: %q", err, out) 29 } 30 31 body, err := sockRequest("GET", "/containers/json?all=1", nil) 32 if err != nil { 33 t.Fatalf("GET all containers sockRequest failed: %v", err) 34 } 35 36 var inspectJSON []struct { 37 Names []string 38 } 39 if err = json.Unmarshal(body, &inspectJSON); err != nil { 40 t.Fatalf("unable to unmarshal response body: %v", err) 41 } 42 43 if len(inspectJSON) != startCount+1 { 44 t.Fatalf("Expected %d container(s), %d found (started with: %d)", startCount+1, len(inspectJSON), startCount) 45 } 46 47 if actual := inspectJSON[0].Names[0]; actual != "/"+name { 48 t.Fatalf("Container Name mismatch. Expected: %q, received: %q\n", "/"+name, actual) 49 } 50 51 logDone("container REST API - check GET json/all=1") 52 } 53 54 func TestContainerApiGetExport(t *testing.T) { 55 defer deleteAllContainers() 56 57 name := "exportcontainer" 58 runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "touch", "/test") 59 out, _, err := runCommandWithOutput(runCmd) 60 if err != nil { 61 t.Fatalf("Error on container creation: %v, output: %q", err, out) 62 } 63 64 body, err := sockRequest("GET", "/containers/"+name+"/export", nil) 65 if err != nil { 66 t.Fatalf("GET containers/export sockRequest failed: %v", err) 67 } 68 69 found := false 70 for tarReader := tar.NewReader(bytes.NewReader(body)); ; { 71 h, err := tarReader.Next() 72 if err != nil { 73 if err == io.EOF { 74 break 75 } 76 t.Fatal(err) 77 } 78 if h.Name == "test" { 79 found = true 80 break 81 } 82 } 83 84 if !found { 85 t.Fatalf("The created test file has not been found in the exported image") 86 } 87 88 logDone("container REST API - check GET containers/export") 89 } 90 91 func TestContainerApiGetChanges(t *testing.T) { 92 defer deleteAllContainers() 93 94 name := "changescontainer" 95 runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "rm", "/etc/passwd") 96 out, _, err := runCommandWithOutput(runCmd) 97 if err != nil { 98 t.Fatalf("Error on container creation: %v, output: %q", err, out) 99 } 100 101 body, err := sockRequest("GET", "/containers/"+name+"/changes", nil) 102 if err != nil { 103 t.Fatalf("GET containers/changes sockRequest failed: %v", err) 104 } 105 106 changes := []struct { 107 Kind int 108 Path string 109 }{} 110 if err = json.Unmarshal(body, &changes); err != nil { 111 t.Fatalf("unable to unmarshal response body: %v", err) 112 } 113 114 // Check the changelog for removal of /etc/passwd 115 success := false 116 for _, elem := range changes { 117 if elem.Path == "/etc/passwd" && elem.Kind == 2 { 118 success = true 119 } 120 } 121 if !success { 122 t.Fatalf("/etc/passwd has been removed but is not present in the diff") 123 } 124 125 logDone("container REST API - check GET containers/changes") 126 } 127 128 func TestContainerApiStartVolumeBinds(t *testing.T) { 129 defer deleteAllContainers() 130 name := "testing" 131 config := map[string]interface{}{ 132 "Image": "busybox", 133 "Volumes": map[string]struct{}{"/tmp": {}}, 134 } 135 136 if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") { 137 t.Fatal(err) 138 } 139 140 bindPath := randomUnixTmpDirPath("test") 141 config = map[string]interface{}{ 142 "Binds": []string{bindPath + ":/tmp"}, 143 } 144 if _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") { 145 t.Fatal(err) 146 } 147 148 pth, err := inspectFieldMap(name, "Volumes", "/tmp") 149 if err != nil { 150 t.Fatal(err) 151 } 152 153 if pth != bindPath { 154 t.Fatalf("expected volume host path to be %s, got %s", bindPath, pth) 155 } 156 157 logDone("container REST API - check volume binds on start") 158 } 159 160 // Test for GH#10618 161 func TestContainerApiStartDupVolumeBinds(t *testing.T) { 162 defer deleteAllContainers() 163 name := "testdups" 164 config := map[string]interface{}{ 165 "Image": "busybox", 166 "Volumes": map[string]struct{}{"/tmp": {}}, 167 } 168 169 if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") { 170 t.Fatal(err) 171 } 172 173 bindPath1 := randomUnixTmpDirPath("test1") 174 bindPath2 := randomUnixTmpDirPath("test2") 175 176 config = map[string]interface{}{ 177 "Binds": []string{bindPath1 + ":/tmp", bindPath2 + ":/tmp"}, 178 } 179 if body, err := sockRequest("POST", "/containers/"+name+"/start", config); err == nil { 180 t.Fatal("expected container start to fail when duplicate volume binds to same container path") 181 } else { 182 if !strings.Contains(string(body), "Duplicate volume") { 183 t.Fatalf("Expected failure due to duplicate bind mounts to same path, instead got: %q with error: %v", string(body), err) 184 } 185 } 186 187 logDone("container REST API - check for duplicate volume binds error on start") 188 } 189 func TestContainerApiStartVolumesFrom(t *testing.T) { 190 defer deleteAllContainers() 191 volName := "voltst" 192 volPath := "/tmp" 193 194 if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", volName, "-v", volPath, "busybox")); err != nil { 195 t.Fatal(out, err) 196 } 197 198 name := "testing" 199 config := map[string]interface{}{ 200 "Image": "busybox", 201 "Volumes": map[string]struct{}{volPath: {}}, 202 } 203 204 if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") { 205 t.Fatal(err) 206 } 207 208 config = map[string]interface{}{ 209 "VolumesFrom": []string{volName}, 210 } 211 if _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") { 212 t.Fatal(err) 213 } 214 215 pth, err := inspectFieldMap(name, "Volumes", volPath) 216 if err != nil { 217 t.Fatal(err) 218 } 219 pth2, err := inspectFieldMap(volName, "Volumes", volPath) 220 if err != nil { 221 t.Fatal(err) 222 } 223 224 if pth != pth2 { 225 t.Fatalf("expected volume host path to be %s, got %s", pth, pth2) 226 } 227 228 logDone("container REST API - check VolumesFrom on start") 229 } 230 231 // Ensure that volumes-from has priority over binds/anything else 232 // This is pretty much the same as TestRunApplyVolumesFromBeforeVolumes, except with passing the VolumesFrom and the bind on start 233 func TestVolumesFromHasPriority(t *testing.T) { 234 defer deleteAllContainers() 235 volName := "voltst" 236 volPath := "/tmp" 237 238 if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", volName, "-v", volPath, "busybox")); err != nil { 239 t.Fatal(out, err) 240 } 241 242 name := "testing" 243 config := map[string]interface{}{ 244 "Image": "busybox", 245 "Volumes": map[string]struct{}{volPath: {}}, 246 } 247 248 if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") { 249 t.Fatal(err) 250 } 251 252 bindPath := randomUnixTmpDirPath("test") 253 config = map[string]interface{}{ 254 "VolumesFrom": []string{volName}, 255 "Binds": []string{bindPath + ":/tmp"}, 256 } 257 if _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") { 258 t.Fatal(err) 259 } 260 261 pth, err := inspectFieldMap(name, "Volumes", volPath) 262 if err != nil { 263 t.Fatal(err) 264 } 265 pth2, err := inspectFieldMap(volName, "Volumes", volPath) 266 if err != nil { 267 t.Fatal(err) 268 } 269 270 if pth != pth2 { 271 t.Fatalf("expected volume host path to be %s, got %s", pth, pth2) 272 } 273 274 logDone("container REST API - check VolumesFrom has priority") 275 } 276 277 func TestGetContainerStats(t *testing.T) { 278 defer deleteAllContainers() 279 var ( 280 name = "statscontainer" 281 runCmd = exec.Command(dockerBinary, "run", "-d", "--name", name, "busybox", "top") 282 ) 283 out, _, err := runCommandWithOutput(runCmd) 284 if err != nil { 285 t.Fatalf("Error on container creation: %v, output: %q", err, out) 286 } 287 type b struct { 288 body []byte 289 err error 290 } 291 bc := make(chan b, 1) 292 go func() { 293 body, err := sockRequest("GET", "/containers/"+name+"/stats", nil) 294 bc <- b{body, err} 295 }() 296 297 // allow some time to stream the stats from the container 298 time.Sleep(4 * time.Second) 299 if _, err := runCommand(exec.Command(dockerBinary, "rm", "-f", name)); err != nil { 300 t.Fatal(err) 301 } 302 303 // collect the results from the stats stream or timeout and fail 304 // if the stream was not disconnected. 305 select { 306 case <-time.After(2 * time.Second): 307 t.Fatal("stream was not closed after container was removed") 308 case sr := <-bc: 309 if sr.err != nil { 310 t.Fatal(sr.err) 311 } 312 313 dec := json.NewDecoder(bytes.NewBuffer(sr.body)) 314 var s *types.Stats 315 // decode only one object from the stream 316 if err := dec.Decode(&s); err != nil { 317 t.Fatal(err) 318 } 319 } 320 logDone("container REST API - check GET containers/stats") 321 } 322 323 func TestGetStoppedContainerStats(t *testing.T) { 324 defer deleteAllContainers() 325 var ( 326 name = "statscontainer" 327 runCmd = exec.Command(dockerBinary, "create", "--name", name, "busybox", "top") 328 ) 329 out, _, err := runCommandWithOutput(runCmd) 330 if err != nil { 331 t.Fatalf("Error on container creation: %v, output: %q", err, out) 332 } 333 334 go func() { 335 // We'll never get return for GET stats from sockRequest as of now, 336 // just send request and see if panic or error would happen on daemon side. 337 _, err := sockRequest("GET", "/containers/"+name+"/stats", nil) 338 if err != nil { 339 t.Fatal(err) 340 } 341 }() 342 343 // allow some time to send request and let daemon deal with it 344 time.Sleep(1 * time.Second) 345 346 logDone("container REST API - check GET stopped containers/stats") 347 } 348 349 func TestBuildApiDockerfilePath(t *testing.T) { 350 // Test to make sure we stop people from trying to leave the 351 // build context when specifying the path to the dockerfile 352 buffer := new(bytes.Buffer) 353 tw := tar.NewWriter(buffer) 354 defer tw.Close() 355 356 dockerfile := []byte("FROM busybox") 357 if err := tw.WriteHeader(&tar.Header{ 358 Name: "Dockerfile", 359 Size: int64(len(dockerfile)), 360 }); err != nil { 361 t.Fatalf("failed to write tar file header: %v", err) 362 } 363 if _, err := tw.Write(dockerfile); err != nil { 364 t.Fatalf("failed to write tar file content: %v", err) 365 } 366 if err := tw.Close(); err != nil { 367 t.Fatalf("failed to close tar archive: %v", err) 368 } 369 370 out, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar") 371 if err == nil { 372 t.Fatalf("Build was supposed to fail: %s", out) 373 } 374 375 if !strings.Contains(string(out), "must be within the build context") { 376 t.Fatalf("Didn't complain about leaving build context: %s", out) 377 } 378 379 logDone("container REST API - check build w/bad Dockerfile path") 380 } 381 382 func TestBuildApiDockerFileRemote(t *testing.T) { 383 server, err := fakeStorage(map[string]string{ 384 "testD": `FROM busybox 385 COPY * /tmp/ 386 RUN find / -name ba* 387 RUN find /tmp/`, 388 }) 389 if err != nil { 390 t.Fatal(err) 391 } 392 defer server.Close() 393 394 buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json") 395 if err != nil { 396 t.Fatalf("Build failed: %s", err) 397 } 398 399 // Make sure Dockerfile exists. 400 // Make sure 'baz' doesn't exist ANYWHERE despite being mentioned in the URL 401 out := string(buf) 402 if !strings.Contains(out, "/tmp/Dockerfile") || 403 strings.Contains(out, "baz") { 404 t.Fatalf("Incorrect output: %s", out) 405 } 406 407 logDone("container REST API - check build with -f from remote") 408 } 409 410 func TestBuildApiLowerDockerfile(t *testing.T) { 411 git, err := fakeGIT("repo", map[string]string{ 412 "dockerfile": `FROM busybox 413 RUN echo from dockerfile`, 414 }, false) 415 if err != nil { 416 t.Fatal(err) 417 } 418 defer git.Close() 419 420 buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") 421 if err != nil { 422 t.Fatalf("Build failed: %s\n%q", err, buf) 423 } 424 425 out := string(buf) 426 if !strings.Contains(out, "from dockerfile") { 427 t.Fatalf("Incorrect output: %s", out) 428 } 429 430 logDone("container REST API - check build with lower dockerfile") 431 } 432 433 func TestBuildApiBuildGitWithF(t *testing.T) { 434 git, err := fakeGIT("repo", map[string]string{ 435 "baz": `FROM busybox 436 RUN echo from baz`, 437 "Dockerfile": `FROM busybox 438 RUN echo from Dockerfile`, 439 }, false) 440 if err != nil { 441 t.Fatal(err) 442 } 443 defer git.Close() 444 445 // Make sure it tries to 'dockerfile' query param value 446 buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json") 447 if err != nil { 448 t.Fatalf("Build failed: %s\n%q", err, buf) 449 } 450 451 out := string(buf) 452 if !strings.Contains(out, "from baz") { 453 t.Fatalf("Incorrect output: %s", out) 454 } 455 456 logDone("container REST API - check build from git w/F") 457 } 458 459 func TestBuildApiDoubleDockerfile(t *testing.T) { 460 testRequires(t, UnixCli) // dockerfile overwrites Dockerfile on Windows 461 git, err := fakeGIT("repo", map[string]string{ 462 "Dockerfile": `FROM busybox 463 RUN echo from Dockerfile`, 464 "dockerfile": `FROM busybox 465 RUN echo from dockerfile`, 466 }, false) 467 if err != nil { 468 t.Fatal(err) 469 } 470 defer git.Close() 471 472 // Make sure it tries to 'dockerfile' query param value 473 buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") 474 if err != nil { 475 t.Fatalf("Build failed: %s", err) 476 } 477 478 out := string(buf) 479 if !strings.Contains(out, "from Dockerfile") { 480 t.Fatalf("Incorrect output: %s", out) 481 } 482 483 logDone("container REST API - check build with two dockerfiles") 484 } 485 486 func TestBuildApiDockerfileSymlink(t *testing.T) { 487 // Test to make sure we stop people from trying to leave the 488 // build context when specifying a symlink as the path to the dockerfile 489 buffer := new(bytes.Buffer) 490 tw := tar.NewWriter(buffer) 491 defer tw.Close() 492 493 if err := tw.WriteHeader(&tar.Header{ 494 Name: "Dockerfile", 495 Typeflag: tar.TypeSymlink, 496 Linkname: "/etc/passwd", 497 }); err != nil { 498 t.Fatalf("failed to write tar file header: %v", err) 499 } 500 if err := tw.Close(); err != nil { 501 t.Fatalf("failed to close tar archive: %v", err) 502 } 503 504 out, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar") 505 if err == nil { 506 t.Fatalf("Build was supposed to fail: %s", out) 507 } 508 509 // The reason the error is "Cannot locate specified Dockerfile" is because 510 // in the builder, the symlink is resolved within the context, therefore 511 // Dockerfile -> /etc/passwd becomes etc/passwd from the context which is 512 // a nonexistent file. 513 if !strings.Contains(string(out), "Cannot locate specified Dockerfile: Dockerfile") { 514 t.Fatalf("Didn't complain about leaving build context: %s", out) 515 } 516 517 logDone("container REST API - check build w/bad Dockerfile symlink path") 518 } 519 520 // #9981 - Allow a docker created volume (ie, one in /var/lib/docker/volumes) to be used to overwrite (via passing in Binds on api start) an existing volume 521 func TestPostContainerBindNormalVolume(t *testing.T) { 522 defer deleteAllContainers() 523 524 out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "-v", "/foo", "--name=one", "busybox")) 525 if err != nil { 526 t.Fatal(err, out) 527 } 528 529 fooDir, err := inspectFieldMap("one", "Volumes", "/foo") 530 if err != nil { 531 t.Fatal(err) 532 } 533 534 out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "create", "-v", "/foo", "--name=two", "busybox")) 535 if err != nil { 536 t.Fatal(err, out) 537 } 538 539 bindSpec := map[string][]string{"Binds": {fooDir + ":/foo"}} 540 _, err = sockRequest("POST", "/containers/two/start", bindSpec) 541 if err != nil && !strings.Contains(err.Error(), "204 No Content") { 542 t.Fatal(err) 543 } 544 545 fooDir2, err := inspectFieldMap("two", "Volumes", "/foo") 546 if err != nil { 547 t.Fatal(err) 548 } 549 550 if fooDir2 != fooDir { 551 t.Fatal("expected volume path to be %s, got: %s", fooDir, fooDir2) 552 } 553 554 logDone("container REST API - can use path from normal volume as bind-mount to overwrite another volume") 555 }