package docker import ( "archive/tar" "bufio" "bytes" "encoding/json" "fmt" "github.com/dotcloud/docker/utils" "io" "net" "net/http" "net/http/httptest" "os" "path" "strings" "testing" "time" ) func TestGetBoolParam(t *testing.T) { if ret, err := getBoolParam("true"); err != nil || !ret { t.Fatalf("true -> true, nil | got %t %s", ret, err) } if ret, err := getBoolParam("True"); err != nil || !ret { t.Fatalf("True -> true, nil | got %t %s", ret, err) } if ret, err := getBoolParam("1"); err != nil || !ret { t.Fatalf("1 -> true, nil | got %t %s", ret, err) } if ret, err := getBoolParam(""); err != nil || ret { t.Fatalf("\"\" -> false, nil | got %t %s", ret, err) } if ret, err := getBoolParam("false"); err != nil || ret { t.Fatalf("false -> false, nil | got %t %s", ret, err) } if ret, err := getBoolParam("0"); err != nil || ret { t.Fatalf("0 -> false, nil | got %t %s", ret, err) } if ret, err := getBoolParam("faux"); err == nil || ret { t.Fatalf("faux -> false, err | got %t %s", ret, err) } } func TesthttpError(t *testing.T) { r := httptest.NewRecorder() httpError(r, fmt.Errorf("No such method")) if r.Code != http.StatusNotFound { t.Fatalf("Expected %d, got %d", http.StatusNotFound, r.Code) } httpError(r, fmt.Errorf("This accound hasn't been activated")) if r.Code != http.StatusForbidden { t.Fatalf("Expected %d, got %d", http.StatusForbidden, r.Code) } httpError(r, fmt.Errorf("Some error")) if r.Code != http.StatusInternalServerError { t.Fatalf("Expected %d, got %d", http.StatusInternalServerError, r.Code) } } func TestGetVersion(t *testing.T) { var err error runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{runtime: runtime} r := httptest.NewRecorder() if err := getVersion(srv, APIVERSION, r, nil, nil); err != nil { t.Fatal(err) } v := &APIVersion{} if err = json.Unmarshal(r.Body.Bytes(), v); err != nil { t.Fatal(err) } if v.Version != VERSION { t.Errorf("Expected version %s, %s found", VERSION, v.Version) } } func TestGetInfo(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{runtime: runtime} initialImages, err := srv.runtime.graph.Map() if err != nil { t.Fatal(err) } r := httptest.NewRecorder() if err := getInfo(srv, APIVERSION, r, nil, nil); err != nil { t.Fatal(err) } infos := &APIInfo{} err = json.Unmarshal(r.Body.Bytes(), infos) if err != nil { t.Fatal(err) } if infos.Images != len(initialImages) { t.Errorf("Expected images: %d, %d found", len(initialImages), infos.Images) } } func TestGetEvents(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{ runtime: runtime, events: make([]utils.JSONMessage, 0, 64), listeners: make(map[string]chan utils.JSONMessage), } srv.LogEvent("fakeaction", "fakeid", "fakeimage") srv.LogEvent("fakeaction2", "fakeid", "fakeimage") req, err := http.NewRequest("GET", "/events?since=1", nil) if err != nil { t.Fatal(err) } r := httptest.NewRecorder() setTimeout(t, "", 500*time.Millisecond, func() { if err := getEvents(srv, APIVERSION, r, req, nil); err != nil { t.Fatal(err) } }) dec := json.NewDecoder(r.Body) for i := 0; i < 2; i++ { var jm utils.JSONMessage if err := dec.Decode(&jm); err == io.EOF { break } else if err != nil { t.Fatal(err) } if jm != srv.events[i] { t.Fatalf("Event received it different than expected") } } } func TestGetImagesJSON(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{runtime: runtime} // all=0 initialImages, err := srv.Images(false, "") if err != nil { t.Fatal(err) } req, err := http.NewRequest("GET", "/images/json?all=0", nil) if err != nil { t.Fatal(err) } r := httptest.NewRecorder() if err := getImagesJSON(srv, APIVERSION, r, req, nil); err != nil { t.Fatal(err) } images := []APIImages{} if err := json.Unmarshal(r.Body.Bytes(), &images); err != nil { t.Fatal(err) } if len(images) != len(initialImages) { t.Errorf("Expected %d image, %d found", len(initialImages), len(images)) } found := false for _, img := range images { if strings.Contains(img.RepoTags[0], unitTestImageName) { found = true break } } if !found { t.Errorf("Expected image %s, %+v found", unitTestImageName, images) } r2 := httptest.NewRecorder() // all=1 initialImages, err = srv.Images(true, "") if err != nil { t.Fatal(err) } req2, err := http.NewRequest("GET", "/images/json?all=true", nil) if err != nil { t.Fatal(err) } if err := getImagesJSON(srv, APIVERSION, r2, req2, nil); err != nil { t.Fatal(err) } images2 := []APIImages{} if err := json.Unmarshal(r2.Body.Bytes(), &images2); err != nil { t.Fatal(err) } if len(images2) != len(initialImages) { t.Errorf("Expected %d image, %d found", len(initialImages), len(images2)) } found = false for _, img := range images2 { if img.ID == GetTestImage(runtime).ID { found = true break } } if !found { t.Errorf("Retrieved image Id differs, expected %s, received %+v", GetTestImage(runtime).ID, images2) } r3 := httptest.NewRecorder() // filter=a req3, err := http.NewRequest("GET", "/images/json?filter=aaaaaaaaaa", nil) if err != nil { t.Fatal(err) } if err := getImagesJSON(srv, APIVERSION, r3, req3, nil); err != nil { t.Fatal(err) } images3 := []APIImages{} if err := json.Unmarshal(r3.Body.Bytes(), &images3); err != nil { t.Fatal(err) } if len(images3) != 0 { t.Errorf("Expected 0 image, %d found", len(images3)) } r4 := httptest.NewRecorder() // all=foobar req4, err := http.NewRequest("GET", "/images/json?all=foobar", nil) if err != nil { t.Fatal(err) } err = getImagesJSON(srv, APIVERSION, r4, req4, nil) if err == nil { t.Fatalf("Error expected, received none") } if !strings.HasPrefix(err.Error(), "Bad parameter") { t.Fatalf("Error should starts with \"Bad parameter\"") } http.Error(r4, err.Error(), http.StatusBadRequest) if r4.Code != http.StatusBadRequest { t.Fatalf("%d Bad Request expected, received %d\n", http.StatusBadRequest, r4.Code) } } func TestGetImagesHistory(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{runtime: runtime} r := httptest.NewRecorder() if err := getImagesHistory(srv, APIVERSION, r, nil, map[string]string{"name": unitTestImageName}); err != nil { t.Fatal(err) } history := []APIHistory{} if err := json.Unmarshal(r.Body.Bytes(), &history); err != nil { t.Fatal(err) } if len(history) != 1 { t.Errorf("Expected 1 line, %d found", len(history)) } } func TestGetImagesByName(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{runtime: runtime} r := httptest.NewRecorder() if err := getImagesByName(srv, APIVERSION, r, nil, map[string]string{"name": unitTestImageName}); err != nil { t.Fatal(err) } img := &Image{} if err := json.Unmarshal(r.Body.Bytes(), img); err != nil { t.Fatal(err) } if img.ID != unitTestImageID { t.Errorf("Error inspecting image") } } func TestGetContainersJSON(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{runtime: runtime} beginLen := runtime.containers.Len() container, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "test"}, }, "") if err != nil { t.Fatal(err) } defer runtime.Destroy(container) req, err := http.NewRequest("GET", "/containers/json?all=1", nil) if err != nil { t.Fatal(err) } r := httptest.NewRecorder() if err := getContainersJSON(srv, APIVERSION, r, req, nil); err != nil { t.Fatal(err) } containers := []APIContainers{} if err := json.Unmarshal(r.Body.Bytes(), &containers); err != nil { t.Fatal(err) } if len(containers) != beginLen+1 { t.Fatalf("Expected %d container, %d found (started with: %d)", beginLen+1, len(containers), beginLen) } if containers[0].ID != container.ID { t.Fatalf("Container ID mismatch. Expected: %s, received: %s\n", container.ID, containers[0].ID) } } func TestGetContainersExport(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{runtime: runtime} // Create a container and remove a file container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"touch", "/test"}, }, "", ) if err != nil { t.Fatal(err) } defer runtime.Destroy(container) if err := container.Run(); err != nil { t.Fatal(err) } r := httptest.NewRecorder() if err = getContainersExport(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil { t.Fatal(err) } if r.Code != http.StatusOK { t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code) } found := false for tarReader := tar.NewReader(r.Body); ; { h, err := tarReader.Next() if err != nil { if err == io.EOF { break } t.Fatal(err) } if h.Name == "./test" { found = true break } } if !found { t.Fatalf("The created test file has not been found in the exported image") } } func TestGetContainersChanges(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{runtime: runtime} // Create a container and remove a file container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/rm", "/etc/passwd"}, }, "", ) if err != nil { t.Fatal(err) } defer runtime.Destroy(container) if err := container.Run(); err != nil { t.Fatal(err) } r := httptest.NewRecorder() if err := getContainersChanges(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil { t.Fatal(err) } changes := []Change{} if err := json.Unmarshal(r.Body.Bytes(), &changes); err != nil { t.Fatal(err) } // Check the changelog success := false for _, elem := range changes { if elem.Path == "/etc/passwd" && elem.Kind == 2 { success = true } } if !success { t.Fatalf("/etc/passwd as been removed but is not present in the diff") } } func TestGetContainersTop(t *testing.T) { t.Skip("Fixme. Skipping test for now. Reported error when testing using dind: 'api_test.go:527: Expected 2 processes, found 0.'") runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{runtime: runtime} container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/sh", "-c", "cat"}, OpenStdin: true, }, "", ) if err != nil { t.Fatal(err) } defer runtime.Destroy(container) defer func() { // Make sure the process dies before destroying runtime container.stdin.Close() container.WaitTimeout(2 * time.Second) }() if err := container.Start(); err != nil { t.Fatal(err) } setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() { for { if container.State.Running { break } time.Sleep(10 * time.Millisecond) } }) if !container.State.Running { t.Fatalf("Container should be running") } // Make sure sh spawn up cat setTimeout(t, "read/write assertion timed out", 2*time.Second, func() { in, _ := container.StdinPipe() out, _ := container.StdoutPipe() if err := assertPipe("hello\n", "hello", out, in, 15); err != nil { t.Fatal(err) } }) r := httptest.NewRecorder() req, err := http.NewRequest("GET", "/"+container.ID+"/top?ps_args=u", bytes.NewReader([]byte{})) if err != nil { t.Fatal(err) } if err := getContainersTop(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil { t.Fatal(err) } procs := APITop{} if err := json.Unmarshal(r.Body.Bytes(), &procs); err != nil { t.Fatal(err) } if len(procs.Titles) != 11 { t.Fatalf("Expected 11 titles, found %d.", len(procs.Titles)) } if procs.Titles[0] != "USER" || procs.Titles[10] != "COMMAND" { t.Fatalf("Expected Titles[0] to be USER and Titles[10] to be COMMAND, found %s and %s.", procs.Titles[0], procs.Titles[10]) } if len(procs.Processes) != 2 { t.Fatalf("Expected 2 processes, found %d.", len(procs.Processes)) } if procs.Processes[0][10] != "/bin/sh" && procs.Processes[0][10] != "cat" { t.Fatalf("Expected `cat` or `/bin/sh`, found %s.", procs.Processes[0][10]) } if procs.Processes[1][10] != "/bin/sh" && procs.Processes[1][10] != "cat" { t.Fatalf("Expected `cat` or `/bin/sh`, found %s.", procs.Processes[1][10]) } } func TestGetContainersByName(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{runtime: runtime} // Create a container and remove a file container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "test"}, }, "", ) if err != nil { t.Fatal(err) } defer runtime.Destroy(container) r := httptest.NewRecorder() if err := getContainersByName(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil { t.Fatal(err) } outContainer := &Container{} if err := json.Unmarshal(r.Body.Bytes(), outContainer); err != nil { t.Fatal(err) } if outContainer.ID != container.ID { t.Fatalf("Wrong containers retrieved. Expected %s, received %s", container.ID, outContainer.ID) } } func TestPostCommit(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{runtime: runtime} // Create a container and remove a file container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"touch", "/test"}, }, "", ) if err != nil { t.Fatal(err) } defer runtime.Destroy(container) if err := container.Run(); err != nil { t.Fatal(err) } req, err := http.NewRequest("POST", "/commit?repo=testrepo&testtag=tag&container="+container.ID, bytes.NewReader([]byte{})) if err != nil { t.Fatal(err) } r := httptest.NewRecorder() if err := postCommit(srv, APIVERSION, r, req, nil); err != nil { t.Fatal(err) } if r.Code != http.StatusCreated { t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code) } apiID := &APIID{} if err := json.Unmarshal(r.Body.Bytes(), apiID); err != nil { t.Fatal(err) } if _, err := runtime.graph.Get(apiID.ID); err != nil { t.Fatalf("The image has not been commited") } } func TestPostContainersCreate(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{runtime: runtime} configJSON, err := json.Marshal(&Config{ Image: GetTestImage(runtime).ID, Memory: 33554432, Cmd: []string{"touch", "/test"}, }) if err != nil { t.Fatal(err) } req, err := http.NewRequest("POST", "/containers/create", bytes.NewReader(configJSON)) if err != nil { t.Fatal(err) } r := httptest.NewRecorder() if err := postContainersCreate(srv, APIVERSION, r, req, nil); err != nil { t.Fatal(err) } if r.Code != http.StatusCreated { t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code) } apiRun := &APIRun{} if err := json.Unmarshal(r.Body.Bytes(), apiRun); err != nil { t.Fatal(err) } container := srv.runtime.Get(apiRun.ID) if container == nil { t.Fatalf("Container not created") } if err := container.Run(); err != nil { t.Fatal(err) } if _, err := os.Stat(path.Join(container.rwPath(), "test")); err != nil { if os.IsNotExist(err) { utils.Debugf("Err: %s", err) t.Fatalf("The test file has not been created") } t.Fatal(err) } } func TestPostContainersKill(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{runtime: runtime} container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/cat"}, OpenStdin: true, }, "", ) if err != nil { t.Fatal(err) } defer runtime.Destroy(container) if err := container.Start(); err != nil { t.Fatal(err) } // Give some time to the process to start container.WaitTimeout(500 * time.Millisecond) if !container.State.Running { t.Errorf("Container should be running") } r := httptest.NewRecorder() if err := postContainersKill(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil { t.Fatal(err) } if r.Code != http.StatusNoContent { t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) } if container.State.Running { t.Fatalf("The container hasn't been killed") } } func TestPostContainersRestart(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{runtime: runtime} container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/top"}, OpenStdin: true, }, "", ) if err != nil { t.Fatal(err) } defer runtime.Destroy(container) if err := container.Start(); err != nil { t.Fatal(err) } // Give some time to the process to start container.WaitTimeout(500 * time.Millisecond) if !container.State.Running { t.Errorf("Container should be running") } req, err := http.NewRequest("POST", "/containers/"+container.ID+"/restart?t=1", bytes.NewReader([]byte{})) if err != nil { t.Fatal(err) } r := httptest.NewRecorder() if err := postContainersRestart(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil { t.Fatal(err) } if r.Code != http.StatusNoContent { t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) } // Give some time to the process to restart container.WaitTimeout(500 * time.Millisecond) if !container.State.Running { t.Fatalf("Container should be running") } if err := container.Kill(); err != nil { t.Fatal(err) } } func TestPostContainersStart(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{runtime: runtime} container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/cat"}, OpenStdin: true, }, "", ) if err != nil { t.Fatal(err) } defer runtime.Destroy(container) hostConfigJSON, err := json.Marshal(&HostConfig{}) req, err := http.NewRequest("POST", "/containers/"+container.ID+"/start", bytes.NewReader(hostConfigJSON)) if err != nil { t.Fatal(err) } req.Header.Set("Content-Type", "application/json") r := httptest.NewRecorder() if err := postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil { t.Fatal(err) } if r.Code != http.StatusNoContent { t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) } // Give some time to the process to start container.WaitTimeout(500 * time.Millisecond) if !container.State.Running { t.Errorf("Container should be running") } r = httptest.NewRecorder() if err = postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err == nil { t.Fatalf("A running container should be able to be started") } if err := container.Kill(); err != nil { t.Fatal(err) } } func TestPostContainersStop(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{runtime: runtime} container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/top"}, OpenStdin: true, }, "", ) if err != nil { t.Fatal(err) } defer runtime.Destroy(container) if err := container.Start(); err != nil { t.Fatal(err) } // Give some time to the process to start container.WaitTimeout(500 * time.Millisecond) if !container.State.Running { t.Errorf("Container should be running") } // Note: as it is a POST request, it requires a body. req, err := http.NewRequest("POST", "/containers/"+container.ID+"/stop?t=1", bytes.NewReader([]byte{})) if err != nil { t.Fatal(err) } r := httptest.NewRecorder() if err := postContainersStop(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil { t.Fatal(err) } if r.Code != http.StatusNoContent { t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) } if container.State.Running { t.Fatalf("The container hasn't been stopped") } } func TestPostContainersWait(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{runtime: runtime} container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/sleep", "1"}, OpenStdin: true, }, "", ) if err != nil { t.Fatal(err) } defer runtime.Destroy(container) if err := container.Start(); err != nil { t.Fatal(err) } setTimeout(t, "Wait timed out", 3*time.Second, func() { r := httptest.NewRecorder() if err := postContainersWait(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil { t.Fatal(err) } apiWait := &APIWait{} if err := json.Unmarshal(r.Body.Bytes(), apiWait); err != nil { t.Fatal(err) } if apiWait.StatusCode != 0 { t.Fatalf("Non zero exit code for sleep: %d\n", apiWait.StatusCode) } }) if container.State.Running { t.Fatalf("The container should be stopped after wait") } } func TestPostContainersAttach(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{runtime: runtime} container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/cat"}, OpenStdin: true, }, "", ) if err != nil { t.Fatal(err) } defer runtime.Destroy(container) // Start the process if err := container.Start(); err != nil { t.Fatal(err) } stdin, stdinPipe := io.Pipe() stdout, stdoutPipe := io.Pipe() // Try to avoid the timeout in destroy. Best effort, don't check error defer func() { closeWrap(stdin, stdinPipe, stdout, stdoutPipe) container.Kill() }() // Attach to it c1 := make(chan struct{}) go func() { defer close(c1) r := &hijackTester{ ResponseRecorder: httptest.NewRecorder(), in: stdin, out: stdoutPipe, } req, err := http.NewRequest("POST", "/containers/"+container.ID+"/attach?stream=1&stdin=1&stdout=1&stderr=1", bytes.NewReader([]byte{})) if err != nil { t.Fatal(err) } if err := postContainersAttach(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil { t.Fatal(err) } }() // Acknowledge hijack setTimeout(t, "hijack acknowledge timed out", 2*time.Second, func() { stdout.Read([]byte{}) stdout.Read(make([]byte, 4096)) }) setTimeout(t, "read/write assertion timed out", 2*time.Second, func() { if err := assertPipe("hello\n", string([]byte{1, 0, 0, 0, 0, 0, 0, 6})+"hello", stdout, stdinPipe, 15); err != nil { t.Fatal(err) } }) // Close pipes (client disconnects) if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil { t.Fatal(err) } // Wait for attach to finish, the client disconnected, therefore, Attach finished his job setTimeout(t, "Waiting for CmdAttach timed out", 10*time.Second, func() { <-c1 }) // We closed stdin, expect /bin/cat to still be running // Wait a little bit to make sure container.monitor() did his thing err = container.WaitTimeout(500 * time.Millisecond) if err == nil || !container.State.Running { t.Fatalf("/bin/cat is not running after closing stdin") } // Try to avoid the timeout in destroy. Best effort, don't check error cStdin, _ := container.StdinPipe() cStdin.Close() container.Wait() } func TestPostContainersAttachStderr(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{runtime: runtime} container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/sh", "-c", "/bin/cat >&2"}, OpenStdin: true, }, "", ) if err != nil { t.Fatal(err) } defer runtime.Destroy(container) // Start the process if err := container.Start(); err != nil { t.Fatal(err) } stdin, stdinPipe := io.Pipe() stdout, stdoutPipe := io.Pipe() // Try to avoid the timeout in destroy. Best effort, don't check error defer func() { closeWrap(stdin, stdinPipe, stdout, stdoutPipe) container.Kill() }() // Attach to it c1 := make(chan struct{}) go func() { defer close(c1) r := &hijackTester{ ResponseRecorder: httptest.NewRecorder(), in: stdin, out: stdoutPipe, } req, err := http.NewRequest("POST", "/containers/"+container.ID+"/attach?stream=1&stdin=1&stdout=1&stderr=1", bytes.NewReader([]byte{})) if err != nil { t.Fatal(err) } if err := postContainersAttach(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil { t.Fatal(err) } }() // Acknowledge hijack setTimeout(t, "hijack acknowledge timed out", 2*time.Second, func() { stdout.Read([]byte{}) stdout.Read(make([]byte, 4096)) }) setTimeout(t, "read/write assertion timed out", 2*time.Second, func() { if err := assertPipe("hello\n", string([]byte{2, 0, 0, 0, 0, 0, 0, 6})+"hello", stdout, stdinPipe, 15); err != nil { t.Fatal(err) } }) // Close pipes (client disconnects) if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil { t.Fatal(err) } // Wait for attach to finish, the client disconnected, therefore, Attach finished his job setTimeout(t, "Waiting for CmdAttach timed out", 10*time.Second, func() { <-c1 }) // We closed stdin, expect /bin/cat to still be running // Wait a little bit to make sure container.monitor() did his thing err = container.WaitTimeout(500 * time.Millisecond) if err == nil || !container.State.Running { t.Fatalf("/bin/cat is not running after closing stdin") } // Try to avoid the timeout in destroy. Best effort, don't check error cStdin, _ := container.StdinPipe() cStdin.Close() container.Wait() } // FIXME: Test deleting running container // FIXME: Test deleting container with volume // FIXME: Test deleting volume in use by other container func TestDeleteContainers(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{runtime: runtime} container, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"touch", "/test"}, }, "") if err != nil { t.Fatal(err) } defer runtime.Destroy(container) if err := container.Run(); err != nil { t.Fatal(err) } req, err := http.NewRequest("DELETE", "/containers/"+container.ID, nil) if err != nil { t.Fatal(err) } r := httptest.NewRecorder() if err := deleteContainers(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil { t.Fatal(err) } if r.Code != http.StatusNoContent { t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) } if c := runtime.Get(container.ID); c != nil { t.Fatalf("The container as not been deleted") } if _, err := os.Stat(path.Join(container.rwPath(), "test")); err == nil { t.Fatalf("The test file has not been deleted") } } func TestOptionsRoute(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) runtime.config.EnableCors = true srv := &Server{runtime: runtime} r := httptest.NewRecorder() router, err := createRouter(srv, false) if err != nil { t.Fatal(err) } req, err := http.NewRequest("OPTIONS", "/", nil) if err != nil { t.Fatal(err) } router.ServeHTTP(r, req) if r.Code != http.StatusOK { t.Errorf("Expected response for OPTIONS request to be \"200\", %v found.", r.Code) } } func TestGetEnabledCors(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) runtime.config.EnableCors = true srv := &Server{runtime: runtime} r := httptest.NewRecorder() router, err := createRouter(srv, false) if err != nil { t.Fatal(err) } req, err := http.NewRequest("GET", "/version", nil) if err != nil { t.Fatal(err) } router.ServeHTTP(r, req) if r.Code != http.StatusOK { t.Errorf("Expected response for OPTIONS request to be \"200\", %v found.", r.Code) } allowOrigin := r.Header().Get("Access-Control-Allow-Origin") allowHeaders := r.Header().Get("Access-Control-Allow-Headers") allowMethods := r.Header().Get("Access-Control-Allow-Methods") if allowOrigin != "*" { t.Errorf("Expected header Access-Control-Allow-Origin to be \"*\", %s found.", allowOrigin) } if allowHeaders != "Origin, X-Requested-With, Content-Type, Accept" { t.Errorf("Expected header Access-Control-Allow-Headers to be \"Origin, X-Requested-With, Content-Type, Accept\", %s found.", allowHeaders) } if allowMethods != "GET, POST, DELETE, PUT, OPTIONS" { t.Errorf("Expected hearder Access-Control-Allow-Methods to be \"GET, POST, DELETE, PUT, OPTIONS\", %s found.", allowMethods) } } func TestDeleteImages(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{runtime: runtime} initialImages, err := srv.Images(false, "") if err != nil { t.Fatal(err) } if err := srv.runtime.repositories.Set("test", "test", unitTestImageName, true); err != nil { t.Fatal(err) } images, err := srv.Images(false, "") if err != nil { t.Fatal(err) } if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+1 { t.Errorf("Expected %d images, %d found", len(initialImages)+1, len(images)) } req, err := http.NewRequest("DELETE", "/images/"+unitTestImageID, nil) if err != nil { t.Fatal(err) } r := httptest.NewRecorder() if err := deleteImages(srv, APIVERSION, r, req, map[string]string{"name": unitTestImageID}); err == nil { t.Fatalf("Expected conflict error, got none") } req2, err := http.NewRequest("DELETE", "/images/test:test", nil) if err != nil { t.Fatal(err) } r2 := httptest.NewRecorder() if err := deleteImages(srv, APIVERSION, r2, req2, map[string]string{"name": "test:test"}); err != nil { t.Fatal(err) } if r2.Code != http.StatusOK { t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code) } var outs []APIRmi if err := json.Unmarshal(r2.Body.Bytes(), &outs); err != nil { t.Fatal(err) } if len(outs) != 1 { t.Fatalf("Expected %d event (untagged), got %d", 1, len(outs)) } images, err = srv.Images(false, "") if err != nil { t.Fatal(err) } if len(images[0].RepoTags) != len(initialImages[0].RepoTags) { t.Errorf("Expected %d image, %d found", len(initialImages), len(images)) } /* if c := runtime.Get(container.Id); c != nil { t.Fatalf("The container as not been deleted") } if _, err := os.Stat(path.Join(container.rwPath(), "test")); err == nil { t.Fatalf("The test file has not been deleted") } */ } func TestJsonContentType(t *testing.T) { if !matchesContentType("application/json", "application/json") { t.Fail() } if !matchesContentType("application/json; charset=utf-8", "application/json") { t.Fail() } if matchesContentType("dockerapplication/json", "application/json") { t.Fail() } } func TestPostContainersCopy(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{runtime: runtime} // Create a container and remove a file container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"touch", "/test.txt"}, }, "", ) if err != nil { t.Fatal(err) } defer runtime.Destroy(container) if err := container.Run(); err != nil { t.Fatal(err) } r := httptest.NewRecorder() copyData := APICopy{HostPath: ".", Resource: "/test.txt"} jsonData, err := json.Marshal(copyData) if err != nil { t.Fatal(err) } req, err := http.NewRequest("POST", "/containers/"+container.ID+"/copy", bytes.NewReader(jsonData)) if err != nil { t.Fatal(err) } req.Header.Add("Content-Type", "application/json") if err = postContainersCopy(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil { t.Fatal(err) } if r.Code != http.StatusOK { t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code) } found := false for tarReader := tar.NewReader(r.Body); ; { h, err := tarReader.Next() if err != nil { if err == io.EOF { break } t.Fatal(err) } if h.Name == "test.txt" { found = true break } } if !found { t.Fatalf("The created test file has not been found in the copied output") } } // Mocked types for tests type NopConn struct { io.ReadCloser io.Writer } func (c *NopConn) LocalAddr() net.Addr { return nil } func (c *NopConn) RemoteAddr() net.Addr { return nil } func (c *NopConn) SetDeadline(t time.Time) error { return nil } func (c *NopConn) SetReadDeadline(t time.Time) error { return nil } func (c *NopConn) SetWriteDeadline(t time.Time) error { return nil } type hijackTester struct { *httptest.ResponseRecorder in io.ReadCloser out io.Writer } func (t *hijackTester) Hijack() (net.Conn, *bufio.ReadWriter, error) { bufrw := bufio.NewReadWriter(bufio.NewReader(t.in), bufio.NewWriter(t.out)) conn := &NopConn{ ReadCloser: t.in, Writer: t.out, } return conn, bufrw, nil }