123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680 |
- package docker
- import (
- "bufio"
- "bytes"
- "encoding/json"
- "io"
- "io/ioutil"
- "net"
- "net/http"
- "net/http/httptest"
- "testing"
- "time"
- "github.com/docker/docker/api"
- "github.com/docker/docker/api/server"
- "github.com/docker/docker/api/types"
- "github.com/docker/docker/engine"
- "github.com/docker/docker/runconfig"
- "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
- )
- func TestPostContainersKill(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
- containerID := createTestContainer(eng,
- &runconfig.Config{
- Image: unitTestImageID,
- Cmd: runconfig.NewCommand("/bin/cat"),
- OpenStdin: true,
- },
- t,
- )
- startContainer(eng, containerID, t)
- // Give some time to the process to start
- containerWaitTimeout(eng, containerID, t)
- if !containerRunning(eng, containerID, t) {
- t.Errorf("Container should be running")
- }
- r := httptest.NewRecorder()
- req, err := http.NewRequest("POST", "/containers/"+containerID+"/kill", bytes.NewReader([]byte{}))
- if err != nil {
- t.Fatal(err)
- }
- server.ServeRequest(eng, api.APIVERSION, r, req)
- assertHttpNotError(r, t)
- if r.Code != http.StatusNoContent {
- t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code)
- }
- if containerRunning(eng, containerID, t) {
- t.Fatalf("The container hasn't been killed")
- }
- }
- func TestPostContainersRestart(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
- containerID := createTestContainer(eng,
- &runconfig.Config{
- Image: unitTestImageID,
- Cmd: runconfig.NewCommand("/bin/top"),
- OpenStdin: true,
- },
- t,
- )
- startContainer(eng, containerID, t)
- // Give some time to the process to start
- containerWaitTimeout(eng, containerID, t)
- if !containerRunning(eng, containerID, t) {
- t.Errorf("Container should be running")
- }
- req, err := http.NewRequest("POST", "/containers/"+containerID+"/restart?t=1", bytes.NewReader([]byte{}))
- if err != nil {
- t.Fatal(err)
- }
- r := httptest.NewRecorder()
- server.ServeRequest(eng, api.APIVERSION, r, req)
- assertHttpNotError(r, t)
- 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
- containerWaitTimeout(eng, containerID, t)
- if !containerRunning(eng, containerID, t) {
- t.Fatalf("Container should be running")
- }
- containerKill(eng, containerID, t)
- }
- func TestPostContainersStart(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
- containerID := createTestContainer(
- eng,
- &runconfig.Config{
- Image: unitTestImageID,
- Cmd: runconfig.NewCommand("/bin/cat"),
- OpenStdin: true,
- },
- t,
- )
- hostConfigJSON, err := json.Marshal(&runconfig.HostConfig{})
- req, err := http.NewRequest("POST", "/containers/"+containerID+"/start", bytes.NewReader(hostConfigJSON))
- if err != nil {
- t.Fatal(err)
- }
- req.Header.Set("Content-Type", "application/json")
- r := httptest.NewRecorder()
- server.ServeRequest(eng, api.APIVERSION, r, req)
- assertHttpNotError(r, t)
- if r.Code != http.StatusNoContent {
- t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code)
- }
- containerAssertExists(eng, containerID, t)
- req, err = http.NewRequest("POST", "/containers/"+containerID+"/start", bytes.NewReader(hostConfigJSON))
- if err != nil {
- t.Fatal(err)
- }
- req.Header.Set("Content-Type", "application/json")
- r = httptest.NewRecorder()
- server.ServeRequest(eng, api.APIVERSION, r, req)
- // Starting an already started container should return a 304
- assertHttpNotError(r, t)
- if r.Code != http.StatusNotModified {
- t.Fatalf("%d NOT MODIFIER expected, received %d\n", http.StatusNotModified, r.Code)
- }
- containerAssertExists(eng, containerID, t)
- containerKill(eng, containerID, t)
- }
- func TestPostContainersStop(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
- containerID := createTestContainer(eng,
- &runconfig.Config{
- Image: unitTestImageID,
- Cmd: runconfig.NewCommand("/bin/top"),
- OpenStdin: true,
- },
- t,
- )
- startContainer(eng, containerID, t)
- // Give some time to the process to start
- containerWaitTimeout(eng, containerID, t)
- if !containerRunning(eng, containerID, t) {
- t.Errorf("Container should be running")
- }
- // Note: as it is a POST request, it requires a body.
- req, err := http.NewRequest("POST", "/containers/"+containerID+"/stop?t=1", bytes.NewReader([]byte{}))
- if err != nil {
- t.Fatal(err)
- }
- r := httptest.NewRecorder()
- server.ServeRequest(eng, api.APIVERSION, r, req)
- assertHttpNotError(r, t)
- if r.Code != http.StatusNoContent {
- t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code)
- }
- if containerRunning(eng, containerID, t) {
- t.Fatalf("The container hasn't been stopped")
- }
- req, err = http.NewRequest("POST", "/containers/"+containerID+"/stop?t=1", bytes.NewReader([]byte{}))
- if err != nil {
- t.Fatal(err)
- }
- r = httptest.NewRecorder()
- server.ServeRequest(eng, api.APIVERSION, r, req)
- // Stopping an already stopper container should return a 304
- assertHttpNotError(r, t)
- if r.Code != http.StatusNotModified {
- t.Fatalf("%d NOT MODIFIER expected, received %d\n", http.StatusNotModified, r.Code)
- }
- }
- func TestPostContainersWait(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
- containerID := createTestContainer(eng,
- &runconfig.Config{
- Image: unitTestImageID,
- Cmd: runconfig.NewCommand("/bin/sleep", "1"),
- OpenStdin: true,
- },
- t,
- )
- startContainer(eng, containerID, t)
- setTimeout(t, "Wait timed out", 3*time.Second, func() {
- r := httptest.NewRecorder()
- req, err := http.NewRequest("POST", "/containers/"+containerID+"/wait", bytes.NewReader([]byte{}))
- if err != nil {
- t.Fatal(err)
- }
- server.ServeRequest(eng, api.APIVERSION, r, req)
- assertHttpNotError(r, t)
- var apiWait engine.Env
- if err := apiWait.Decode(r.Body); err != nil {
- t.Fatal(err)
- }
- if apiWait.GetInt("StatusCode") != 0 {
- t.Fatalf("Non zero exit code for sleep: %d\n", apiWait.GetInt("StatusCode"))
- }
- })
- if containerRunning(eng, containerID, t) {
- t.Fatalf("The container should be stopped after wait")
- }
- }
- func TestPostContainersAttach(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
- containerID := createTestContainer(eng,
- &runconfig.Config{
- Image: unitTestImageID,
- Cmd: runconfig.NewCommand("/bin/cat"),
- OpenStdin: true,
- },
- t,
- )
- // Start the process
- startContainer(eng, containerID, t)
- 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)
- containerKill(eng, containerID, t)
- }()
- // 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/"+containerID+"/attach?stream=1&stdin=1&stdout=1&stderr=1", bytes.NewReader([]byte{}))
- if err != nil {
- t.Fatal(err)
- }
- server.ServeRequest(eng, api.APIVERSION, r, req)
- assertHttpNotError(r.ResponseRecorder, t)
- }()
- // 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, 150); 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
- containerWaitTimeout(eng, containerID, t)
- // Try to avoid the timeout in destroy. Best effort, don't check error
- cStdin, _ := containerAttach(eng, containerID, t)
- cStdin.Close()
- containerWait(eng, containerID, t)
- }
- func TestPostContainersAttachStderr(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
- containerID := createTestContainer(eng,
- &runconfig.Config{
- Image: unitTestImageID,
- Cmd: runconfig.NewCommand("/bin/sh", "-c", "/bin/cat >&2"),
- OpenStdin: true,
- },
- t,
- )
- // Start the process
- startContainer(eng, containerID, t)
- 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)
- containerKill(eng, containerID, t)
- }()
- // 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/"+containerID+"/attach?stream=1&stdin=1&stdout=1&stderr=1", bytes.NewReader([]byte{}))
- if err != nil {
- t.Fatal(err)
- }
- server.ServeRequest(eng, api.APIVERSION, r, req)
- assertHttpNotError(r.ResponseRecorder, t)
- }()
- // 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, 150); 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
- containerWaitTimeout(eng, containerID, t)
- // Try to avoid the timeout in destroy. Best effort, don't check error
- cStdin, _ := containerAttach(eng, containerID, t)
- cStdin.Close()
- containerWait(eng, containerID, t)
- }
- func TestOptionsRoute(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
- r := httptest.NewRecorder()
- req, err := http.NewRequest("OPTIONS", "/", nil)
- if err != nil {
- t.Fatal(err)
- }
- server.ServeRequest(eng, api.APIVERSION, r, req)
- assertHttpNotError(r, t)
- if r.Code != http.StatusOK {
- t.Errorf("Expected response for OPTIONS request to be \"200\", %v found.", r.Code)
- }
- }
- func TestGetEnabledCors(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
- r := httptest.NewRecorder()
- req, err := http.NewRequest("GET", "/version", nil)
- if err != nil {
- t.Fatal(err)
- }
- server.ServeRequest(eng, api.APIVERSION, r, req)
- assertHttpNotError(r, t)
- 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, X-Registry-Auth" {
- t.Errorf("Expected header Access-Control-Allow-Headers to be \"Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth\", %s found.", allowHeaders)
- }
- if allowMethods != "GET, POST, DELETE, PUT, OPTIONS" {
- t.Errorf("Expected header Access-Control-Allow-Methods to be \"GET, POST, DELETE, PUT, OPTIONS\", %s found.", allowMethods)
- }
- }
- func TestDeleteImages(t *testing.T) {
- eng := NewTestEngine(t)
- //we expect errors, so we disable stderr
- eng.Stderr = ioutil.Discard
- defer mkDaemonFromEngine(eng, t).Nuke()
- initialImages := getImages(eng, t, true, "")
- d := getDaemon(eng)
- if err := d.Repositories().Tag("test", "test", unitTestImageName, true); err != nil {
- t.Fatal(err)
- }
- images := getImages(eng, t, true, "")
- if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+1 {
- t.Errorf("Expected %d images, %d found", len(initialImages[0].RepoTags)+1, len(images[0].RepoTags))
- }
- req, err := http.NewRequest("DELETE", "/images/"+unitTestImageID, nil)
- if err != nil {
- t.Fatal(err)
- }
- r := httptest.NewRecorder()
- server.ServeRequest(eng, api.APIVERSION, r, req)
- if r.Code != http.StatusConflict {
- t.Fatalf("Expected http status 409-conflict, got %v", r.Code)
- }
- req2, err := http.NewRequest("DELETE", "/images/test:test", nil)
- if err != nil {
- t.Fatal(err)
- }
- r2 := httptest.NewRecorder()
- server.ServeRequest(eng, api.APIVERSION, r2, req2)
- assertHttpNotError(r2, t)
- if r2.Code != http.StatusOK {
- t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code)
- }
- delImages := []types.ImageDelete{}
- err = json.Unmarshal(r2.Body.Bytes(), &delImages)
- if err != nil {
- t.Fatal(err)
- }
- if len(delImages) != 1 {
- t.Fatalf("Expected %d event (untagged), got %d", 1, len(delImages))
- }
- images = getImages(eng, t, false, "")
- if len(images) != len(initialImages) {
- t.Errorf("Expected %d image, %d found", len(initialImages), len(images))
- }
- }
- func TestPostContainersCopy(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
- // Create a container and remove a file
- containerID := createTestContainer(eng,
- &runconfig.Config{
- Image: unitTestImageID,
- Cmd: runconfig.NewCommand("touch", "/test.txt"),
- },
- t,
- )
- containerRun(eng, containerID, t)
- r := httptest.NewRecorder()
- var copyData engine.Env
- copyData.Set("Resource", "/test.txt")
- copyData.Set("HostPath", ".")
- jsonData := bytes.NewBuffer(nil)
- if err := copyData.Encode(jsonData); err != nil {
- t.Fatal(err)
- }
- req, err := http.NewRequest("POST", "/containers/"+containerID+"/copy", jsonData)
- if err != nil {
- t.Fatal(err)
- }
- req.Header.Add("Content-Type", "application/json")
- server.ServeRequest(eng, api.APIVERSION, r, req)
- assertHttpNotError(r, t)
- 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")
- }
- }
- func TestPostContainersCopyWhenContainerNotFound(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
- r := httptest.NewRecorder()
- var copyData engine.Env
- copyData.Set("Resource", "/test.txt")
- copyData.Set("HostPath", ".")
- jsonData := bytes.NewBuffer(nil)
- if err := copyData.Encode(jsonData); err != nil {
- t.Fatal(err)
- }
- req, err := http.NewRequest("POST", "/containers/id_not_found/copy", jsonData)
- if err != nil {
- t.Fatal(err)
- }
- req.Header.Add("Content-Type", "application/json")
- server.ServeRequest(eng, api.APIVERSION, r, req)
- if r.Code != http.StatusNotFound {
- t.Fatalf("404 expected for id_not_found Container, received %v", r.Code)
- }
- }
- // Regression test for https://github.com/docker/docker/issues/6231
- func TestConstainersStartChunkedEncodingHostConfig(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
- r := httptest.NewRecorder()
- var testData engine.Env
- testData.Set("Image", "docker-test-image")
- testData.SetAuto("Volumes", map[string]struct{}{"/foo": {}})
- testData.Set("Cmd", "true")
- jsonData := bytes.NewBuffer(nil)
- if err := testData.Encode(jsonData); err != nil {
- t.Fatal(err)
- }
- req, err := http.NewRequest("POST", "/containers/create?name=chunk_test", jsonData)
- if err != nil {
- t.Fatal(err)
- }
- req.Header.Add("Content-Type", "application/json")
- server.ServeRequest(eng, api.APIVERSION, r, req)
- assertHttpNotError(r, t)
- var testData2 engine.Env
- testData2.SetAuto("Binds", []string{"/tmp:/foo"})
- jsonData = bytes.NewBuffer(nil)
- if err := testData2.Encode(jsonData); err != nil {
- t.Fatal(err)
- }
- req, err = http.NewRequest("POST", "/containers/chunk_test/start", jsonData)
- if err != nil {
- t.Fatal(err)
- }
- req.Header.Add("Content-Type", "application/json")
- // This is a cheat to make the http request do chunked encoding
- // Otherwise (just setting the Content-Encoding to chunked) net/http will overwrite
- // https://golang.org/src/pkg/net/http/request.go?s=11980:12172
- req.ContentLength = -1
- server.ServeRequest(eng, api.APIVERSION, r, req)
- assertHttpNotError(r, t)
- type config struct {
- HostConfig struct {
- Binds []string
- }
- }
- req, err = http.NewRequest("GET", "/containers/chunk_test/json", nil)
- if err != nil {
- t.Fatal(err)
- }
- r2 := httptest.NewRecorder()
- req.Header.Add("Content-Type", "application/json")
- server.ServeRequest(eng, api.APIVERSION, r2, req)
- assertHttpNotError(r, t)
- c := config{}
- json.Unmarshal(r2.Body.Bytes(), &c)
- if len(c.HostConfig.Binds) == 0 {
- t.Fatal("Chunked Encoding not handled")
- }
- if c.HostConfig.Binds[0] != "/tmp:/foo" {
- t.Fatal("Chunked encoding not properly handled, expected binds to be /tmp:/foo, got:", c.HostConfig.Binds[0])
- }
- }
- // 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
- }
|