From ea4224cbe7c7bb441f2b17078b9b2a1a9a9dcb38 Mon Sep 17 00:00:00 2001 From: Nan Deng Date: Wed, 23 Jul 2014 11:18:45 -0700 Subject: [PATCH] update github.com/fsouza/go-dockerclient --- pkg/kubelet/kubelet.go | 2 +- .../fsouza/go-dockerclient/.travis.yml | 1 + .../github.com/fsouza/go-dockerclient/AUTHORS | 2 + .../fsouza/go-dockerclient/README.markdown | 6 - .../fsouza/go-dockerclient/client.go | 52 +++- .../fsouza/go-dockerclient/client_test.go | 54 ++++ .../fsouza/go-dockerclient/container.go | 56 +++- .../fsouza/go-dockerclient/container_test.go | 267 ++++++++++++++++- .../fsouza/go-dockerclient/image.go | 14 +- .../fsouza/go-dockerclient/testing/server.go | 165 +++++++--- .../go-dockerclient/testing/server_test.go | 283 ++++++++++++++---- .../fsouza/go-dockerclient/utils/stdcopy.go | 28 +- .../go-dockerclient/utils/stdcopy_test.go | 217 ++++++++++++++ 13 files changed, 1002 insertions(+), 145 deletions(-) create mode 100644 third_party/src/github.com/fsouza/go-dockerclient/utils/stdcopy_test.go diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index 70818eb5ed8a7..3027464fd14bb 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -239,7 +239,7 @@ func (kl *Kubelet) runContainer(pod *Pod, container *api.Container, podVolumes v ExposedPorts: exposedPorts, Hostname: container.Name, Image: container.Image, - Memory: int64(container.Memory), + Memory: uint64(container.Memory), CpuShares: int64(milliCPUToShares(container.CPU)), Volumes: volumes, WorkingDir: container.WorkingDir, diff --git a/third_party/src/github.com/fsouza/go-dockerclient/.travis.yml b/third_party/src/github.com/fsouza/go-dockerclient/.travis.yml index 3bb9989bb79d5..cfcd173a7f32d 100644 --- a/third_party/src/github.com/fsouza/go-dockerclient/.travis.yml +++ b/third_party/src/github.com/fsouza/go-dockerclient/.travis.yml @@ -2,6 +2,7 @@ language: go go: - 1.1.2 - 1.2 + - 1.3 - tip env: - GOARCH=amd64 diff --git a/third_party/src/github.com/fsouza/go-dockerclient/AUTHORS b/third_party/src/github.com/fsouza/go-dockerclient/AUTHORS index cd6f48208be85..d42a14a8324d0 100644 --- a/third_party/src/github.com/fsouza/go-dockerclient/AUTHORS +++ b/third_party/src/github.com/fsouza/go-dockerclient/AUTHORS @@ -10,6 +10,7 @@ Ed Eric Anderson Flavia Missi Francisco Souza +Jari Kolehmainen Jason Wilder Jean-Baptiste Dalido Jeff Mitchell @@ -19,6 +20,7 @@ Omeid Matten Paul Morie Peter Jihoon Kim Philippe Lafoucrière +Rafe Colton Salvador Gironès Simon Eskildsen Simon Menke diff --git a/third_party/src/github.com/fsouza/go-dockerclient/README.markdown b/third_party/src/github.com/fsouza/go-dockerclient/README.markdown index f7571f50d6bb6..7b4e959e0a235 100644 --- a/third_party/src/github.com/fsouza/go-dockerclient/README.markdown +++ b/third_party/src/github.com/fsouza/go-dockerclient/README.markdown @@ -9,12 +9,6 @@ This package presents a client for the Docker remote API. For more details, check the [remote API documentation](http://docs.docker.io/en/latest/reference/api/docker_remote_api/). -##Versioning - -* Version 0.1 is compatible with Docker v0.7.1 -* The master is compatible with Docker's master - - ## Example package main diff --git a/third_party/src/github.com/fsouza/go-dockerclient/client.go b/third_party/src/github.com/fsouza/go-dockerclient/client.go index 59447e98bb4ca..681b7bf7835ad 100644 --- a/third_party/src/github.com/fsouza/go-dockerclient/client.go +++ b/third_party/src/github.com/fsouza/go-dockerclient/client.go @@ -47,20 +47,17 @@ type ApiVersion []int // and are integer numbers. func NewApiVersion(input string) (ApiVersion, error) { if !strings.Contains(input, ".") { - return nil, fmt.Errorf("Unable to parse version '%s'", input) + return nil, fmt.Errorf("Unable to parse version %q", input) } - arr := strings.Split(input, ".") ret := make(ApiVersion, len(arr)) - var err error for i, val := range arr { ret[i], err = strconv.Atoi(val) if err != nil { - return nil, err + return nil, fmt.Errorf("Unable to parse version %q: %q is not an integer", input, val) } } - return ret, nil } @@ -203,6 +200,21 @@ func parseApiVersionString(input string) (version uint16, err error) { return version, nil } +// Ping pings the docker server +// +// See http://goo.gl/stJENm for more details. +func (c *Client) Ping() error { + path := "/_ping" + body, status, err := c.do("GET", path, nil) + if err != nil { + return err + } + if status != http.StatusOK { + return newError(status, body) + } + return nil +} + func (c *Client) getServerApiVersionString() (version string, err error) { body, status, err := c.do("GET", "/version", nil) if err != nil { @@ -257,6 +269,7 @@ func (c *Client) do(method, path string, data interface{}) ([]byte, int, error) if err != nil { return nil, -1, err } + defer dial.Close() clientconn := httputil.NewClientConn(dial, nil) resp, err = clientconn.Do(req) if err != nil { @@ -283,11 +296,10 @@ func (c *Client) do(method, path string, data interface{}) ([]byte, int, error) return body, resp.StatusCode, nil } -func (c *Client) stream(method, path string, headers map[string]string, in io.Reader, out io.Writer) error { +func (c *Client) stream(method, path string, setRawTerminal bool, headers map[string]string, in io.Reader, stdout, stderr io.Writer) error { if (method == "POST" || method == "PUT") && in == nil { in = bytes.NewReader(nil) } - if path != "/version" && !c.SkipServerVersionCheck && c.expectedApiVersion == nil { err := c.checkApiVersion() if err != nil { @@ -308,8 +320,11 @@ func (c *Client) stream(method, path string, headers map[string]string, in io.Re var resp *http.Response protocol := c.endpointURL.Scheme address := c.endpointURL.Path - if out == nil { - out = ioutil.Discard + if stdout == nil { + stdout = ioutil.Discard + } + if stderr == nil { + stderr = ioutil.Discard } if protocol == "unix" { dial, err := net.Dial(protocol, address) @@ -346,20 +361,23 @@ func (c *Client) stream(method, path string, headers map[string]string, in io.Re return err } if m.Stream != "" { - fmt.Fprint(out, m.Stream) + fmt.Fprint(stdout, m.Stream) } else if m.Progress != "" { - fmt.Fprintf(out, "%s %s\r", m.Status, m.Progress) + fmt.Fprintf(stdout, "%s %s\r", m.Status, m.Progress) } else if m.Error != "" { return errors.New(m.Error) } if m.Status != "" { - fmt.Fprintln(out, m.Status) + fmt.Fprintln(stdout, m.Status) } } } else { - if _, err := io.Copy(out, resp.Body); err != nil { - return err + if setRawTerminal { + _, err = io.Copy(stdout, resp.Body) + } else { + _, err = utils.StdCopy(stdout, stderr, resp.Body) } + return err } return nil } @@ -371,6 +389,12 @@ func (c *Client) hijack(method, path string, success chan struct{}, setRawTermin return err } } + if stdout == nil { + stdout = ioutil.Discard + } + if stderr == nil { + stderr = ioutil.Discard + } req, err := http.NewRequest(method, c.getURL(path), nil) if err != nil { return err diff --git a/third_party/src/github.com/fsouza/go-dockerclient/client_test.go b/third_party/src/github.com/fsouza/go-dockerclient/client_test.go index deab835826368..53edd6c24c013 100644 --- a/third_party/src/github.com/fsouza/go-dockerclient/client_test.go +++ b/third_party/src/github.com/fsouza/go-dockerclient/client_test.go @@ -148,6 +148,25 @@ func TestQueryString(t *testing.T) { } } +func TestNewApiVersionFailures(t *testing.T) { + var tests = []struct { + input string + expectedError string + }{ + {"1-0", `Unable to parse version "1-0"`}, + {"1.0-beta", `Unable to parse version "1.0-beta": "0-beta" is not an integer`}, + } + for _, tt := range tests { + v, err := NewApiVersion(tt.input) + if v != nil { + t.Errorf("Expected version, got %v.", v) + } + if err.Error() != tt.expectedError { + t.Errorf("NewApiVersion(%q): wrong error. Want %q. Got %q", tt.input, tt.expectedError, err.Error()) + } + } +} + func TestApiVersions(t *testing.T) { var tests = []struct { a string @@ -192,6 +211,41 @@ func TestApiVersions(t *testing.T) { } } +func TestPing(t *testing.T) { + fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} + client := newTestClient(fakeRT) + err := client.Ping() + if err != nil { + t.Fatal(err) + } +} + +func TestPingFailing(t *testing.T) { + fakeRT := &FakeRoundTripper{message: "", status: http.StatusInternalServerError} + client := newTestClient(fakeRT) + err := client.Ping() + if err == nil { + t.Fatal("Expected non nil error, got nil") + } + expectedErrMsg := "API error (500): " + if err.Error() != expectedErrMsg { + t.Fatalf("Expected error to be %q, got: %q", expectedErrMsg, err.Error()) + } +} + +func TestPingFailingWrongStatus(t *testing.T) { + fakeRT := &FakeRoundTripper{message: "", status: http.StatusAccepted} + client := newTestClient(fakeRT) + err := client.Ping() + if err == nil { + t.Fatal("Expected non nil error, got nil") + } + expectedErrMsg := "API error (202): " + if err.Error() != expectedErrMsg { + t.Fatalf("Expected error to be %q, got: %q", expectedErrMsg, err.Error()) + } +} + type FakeRoundTripper struct { message string status int diff --git a/third_party/src/github.com/fsouza/go-dockerclient/container.go b/third_party/src/github.com/fsouza/go-dockerclient/container.go index 0baaf3803fb51..25e1fad5f6747 100644 --- a/third_party/src/github.com/fsouza/go-dockerclient/container.go +++ b/third_party/src/github.com/fsouza/go-dockerclient/container.go @@ -13,7 +13,6 @@ import ( "net/url" "strconv" "strings" - "sync" "time" ) @@ -87,22 +86,19 @@ func (p Port) Proto() string { // State represents the state of a container. type State struct { - sync.RWMutex Running bool + Paused bool Pid int ExitCode int StartedAt time.Time FinishedAt time.Time - Ghost bool } // String returns the string representation of a state. func (s *State) String() string { - s.RLock() - defer s.RUnlock() if s.Running { - if s.Ghost { - return "Ghost" + if s.Paused { + return "paused" } return fmt.Sprintf("Up %s", time.Now().UTC().Sub(s.StartedAt)) } @@ -162,8 +158,8 @@ type Config struct { Hostname string Domainname string User string - Memory int64 - MemorySwap int64 + Memory uint64 + MemorySwap uint64 CpuShares int64 AttachStdin bool AttachStdout bool @@ -351,6 +347,36 @@ func (c *Client) RestartContainer(id string, timeout uint) error { return nil } +// PauseContainer pauses the given container. +// +// See http://goo.gl/AM5t42 for more details. +func (c *Client) PauseContainer(id string) error { + path := fmt.Sprintf("/containers/%s/pause", id) + _, status, err := c.do("POST", path, nil) + if status == http.StatusNotFound { + return &NoSuchContainer{ID: id} + } + if err != nil { + return err + } + return nil +} + +// UnpauseContainer pauses the given container. +// +// See http://goo.gl/eBrNSL for more details. +func (c *Client) UnpauseContainer(id string) error { + path := fmt.Sprintf("/containers/%s/unpause", id) + _, status, err := c.do("POST", path, nil) + if status == http.StatusNotFound { + return &NoSuchContainer{ID: id} + } + if err != nil { + return err + } + return nil +} + // KillContainerOptions represents the set of options that can be used in a // call to KillContainer. type KillContainerOptions struct { @@ -542,10 +568,15 @@ func (c *Client) AttachToContainer(opts AttachToContainerOptions) error { type LogsOptions struct { Container string `qs:"-"` OutputStream io.Writer `qs:"-"` + ErrorStream io.Writer `qs:"-"` Follow bool Stdout bool Stderr bool Timestamps bool + Tail string + + // Use raw terminal? Usually true when the container contains a TTY. + RawTerminal bool `qs:"-"` } // Logs gets stdout and stderr logs from the specified container. @@ -555,8 +586,11 @@ func (c *Client) Logs(opts LogsOptions) error { if opts.Container == "" { return &NoSuchContainer{ID: opts.Container} } + if opts.Tail == "" { + opts.Tail = "all" + } path := "/containers/" + opts.Container + "/logs?" + queryString(opts) - return c.stream("GET", path, nil, nil, opts.OutputStream) + return c.stream("GET", path, opts.RawTerminal, nil, nil, opts.OutputStream, opts.ErrorStream) } // ResizeContainerTTY resizes the terminal to the given height and width. @@ -586,7 +620,7 @@ func (c *Client) ExportContainer(opts ExportContainerOptions) error { return NoSuchContainer{ID: opts.ID} } url := fmt.Sprintf("/containers/%s/export", opts.ID) - return c.stream("GET", url, nil, nil, opts.OutputStream) + return c.stream("GET", url, true, nil, nil, opts.OutputStream, nil) } // NoSuchContainer is the error returned when a given container does not exist. diff --git a/third_party/src/github.com/fsouza/go-dockerclient/container_test.go b/third_party/src/github.com/fsouza/go-dockerclient/container_test.go index 0517834e385fa..fbe9d1f74b49a 100644 --- a/third_party/src/github.com/fsouza/go-dockerclient/container_test.go +++ b/third_party/src/github.com/fsouza/go-dockerclient/container_test.go @@ -14,12 +14,32 @@ import ( "net/url" "os" "reflect" + "regexp" "runtime" "strconv" "strings" "testing" + "time" ) +func TestStateString(t *testing.T) { + started := time.Now().Add(-3 * time.Hour) + var tests = []struct { + input State + expected string + }{ + {State{Running: true, Paused: true}, "^paused$"}, + {State{Running: true, StartedAt: started}, "^Up 3h.*$"}, + {State{Running: false, ExitCode: 7}, "^Exit 7$"}, + } + for _, tt := range tests { + re := regexp.MustCompile(tt.expected) + if got := tt.input.String(); !re.MatchString(got) { + t.Errorf("State.String(): wrong result. Want %q. Got %q.", tt.expected, got) + } + } +} + func TestListContainers(t *testing.T) { jsonContainers := `[ { @@ -132,8 +152,8 @@ func TestInspectContainer(t *testing.T) { "Config": { "Hostname": "4fa6e0f0c678", "User": "", - "Memory": 0, - "MemorySwap": 0, + "Memory": 17179869184, + "MemorySwap": 34359738368, "AttachStdin": false, "AttachStdout": true, "AttachStderr": true, @@ -445,6 +465,60 @@ func TestRestartContainerNotFound(t *testing.T) { } } +func TestPauseContainer(t *testing.T) { + fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent} + client := newTestClient(fakeRT) + id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" + err := client.PauseContainer(id) + if err != nil { + t.Fatal(err) + } + req := fakeRT.requests[0] + if req.Method != "POST" { + t.Errorf("PauseContainer(%q): wrong HTTP method. Want %q. Got %q.", id, "POST", req.Method) + } + expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/pause")) + if gotPath := req.URL.Path; gotPath != expectedURL.Path { + t.Errorf("PauseContainer(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) + } +} + +func TestPauseContainerNotFound(t *testing.T) { + client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound}) + err := client.PauseContainer("a2334") + expected := &NoSuchContainer{ID: "a2334"} + if !reflect.DeepEqual(err, expected) { + t.Errorf("PauseContainer: Wrong error returned. Want %#v. Got %#v.", expected, err) + } +} + +func TestUnpauseContainer(t *testing.T) { + fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent} + client := newTestClient(fakeRT) + id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" + err := client.UnpauseContainer(id) + if err != nil { + t.Fatal(err) + } + req := fakeRT.requests[0] + if req.Method != "POST" { + t.Errorf("PauseContainer(%q): wrong HTTP method. Want %q. Got %q.", id, "POST", req.Method) + } + expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/unpause")) + if gotPath := req.URL.Path; gotPath != expectedURL.Path { + t.Errorf("PauseContainer(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) + } +} + +func TestUnpauseContainerNotFound(t *testing.T) { + client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound}) + err := client.UnpauseContainer("a2334") + expected := &NoSuchContainer{ID: "a2334"} + if !reflect.DeepEqual(err, expected) { + t.Errorf("PauseContainer: Wrong error returned. Want %#v. Got %#v.", expected, err) + } +} + func TestKillContainer(t *testing.T) { fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent} client := newTestClient(fakeRT) @@ -736,7 +810,7 @@ func TestAttachToContainer(t *testing.T) { Stream: true, RawTerminal: true, } - var err = client.AttachToContainer(opts) + err := client.AttachToContainer(opts) if err != nil { t.Fatal(err) } @@ -781,6 +855,63 @@ func TestAttachToContainerSentinel(t *testing.T) { success <- <-success } +func TestAttachToContainerNilStdout(t *testing.T) { + var reader = strings.NewReader("send value") + var req http.Request + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte{1, 0, 0, 0, 0, 0, 0, 5}) + w.Write([]byte("hello")) + req = *r + })) + defer server.Close() + client, _ := NewClient(server.URL) + client.SkipServerVersionCheck = true + var stderr bytes.Buffer + opts := AttachToContainerOptions{ + Container: "a123456", + OutputStream: nil, + ErrorStream: &stderr, + InputStream: reader, + Stdin: true, + Stdout: true, + Stderr: true, + Stream: true, + RawTerminal: true, + } + err := client.AttachToContainer(opts) + if err != nil { + t.Fatal(err) + } +} + +func TestAttachToContainerNilStderr(t *testing.T) { + var reader = strings.NewReader("send value") + var req http.Request + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte{1, 0, 0, 0, 0, 0, 0, 5}) + w.Write([]byte("hello")) + req = *r + })) + defer server.Close() + client, _ := NewClient(server.URL) + client.SkipServerVersionCheck = true + var stdout bytes.Buffer + opts := AttachToContainerOptions{ + Container: "a123456", + OutputStream: &stdout, + InputStream: reader, + Stdin: true, + Stdout: true, + Stderr: true, + Stream: true, + RawTerminal: true, + } + err := client.AttachToContainer(opts) + if err != nil { + t.Fatal(err) + } +} + func TestAttachToContainerRawTerminalFalse(t *testing.T) { input := strings.NewReader("send value") var req http.Request @@ -838,6 +969,8 @@ func TestAttachToContainerWithoutContainer(t *testing.T) { func TestLogs(t *testing.T) { var req http.Request server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + prefix := []byte{1, 0, 0, 0, 0, 0, 0, 19} + w.Write(prefix) w.Write([]byte("something happened!")) req = *r })) @@ -873,6 +1006,7 @@ func TestLogs(t *testing.T) { "stdout": {"1"}, "stderr": {"1"}, "timestamps": {"1"}, + "tail": {"all"}, } got := map[string][]string(req.URL.Query()) if !reflect.DeepEqual(got, expectedQs) { @@ -880,6 +1014,133 @@ func TestLogs(t *testing.T) { } } +func TestLogsNilStdoutDoesntFail(t *testing.T) { + var req http.Request + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + prefix := []byte{1, 0, 0, 0, 0, 0, 0, 19} + w.Write(prefix) + w.Write([]byte("something happened!")) + req = *r + })) + defer server.Close() + client, _ := NewClient(server.URL) + client.SkipServerVersionCheck = true + opts := LogsOptions{ + Container: "a123456", + Follow: true, + Stdout: true, + Stderr: true, + Timestamps: true, + } + err := client.Logs(opts) + if err != nil { + t.Fatal(err) + } +} + +func TestLogsNilStderrDoesntFail(t *testing.T) { + var req http.Request + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + prefix := []byte{2, 0, 0, 0, 0, 0, 0, 19} + w.Write(prefix) + w.Write([]byte("something happened!")) + req = *r + })) + defer server.Close() + client, _ := NewClient(server.URL) + client.SkipServerVersionCheck = true + opts := LogsOptions{ + Container: "a123456", + Follow: true, + Stdout: true, + Stderr: true, + Timestamps: true, + } + err := client.Logs(opts) + if err != nil { + t.Fatal(err) + } +} + +func TestLogsSpecifyingTail(t *testing.T) { + var req http.Request + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + prefix := []byte{1, 0, 0, 0, 0, 0, 0, 19} + w.Write(prefix) + w.Write([]byte("something happened!")) + req = *r + })) + defer server.Close() + client, _ := NewClient(server.URL) + client.SkipServerVersionCheck = true + var buf bytes.Buffer + opts := LogsOptions{ + Container: "a123456", + OutputStream: &buf, + Follow: true, + Stdout: true, + Stderr: true, + Timestamps: true, + Tail: "100", + } + err := client.Logs(opts) + if err != nil { + t.Fatal(err) + } + expected := "something happened!" + if buf.String() != expected { + t.Errorf("Logs: wrong output. Want %q. Got %q.", expected, buf.String()) + } + if req.Method != "GET" { + t.Errorf("Logs: wrong HTTP method. Want GET. Got %s.", req.Method) + } + u, _ := url.Parse(client.getURL("/containers/a123456/logs")) + if req.URL.Path != u.Path { + t.Errorf("AttachToContainer for logs: wrong HTTP path. Want %q. Got %q.", u.Path, req.URL.Path) + } + expectedQs := map[string][]string{ + "follow": {"1"}, + "stdout": {"1"}, + "stderr": {"1"}, + "timestamps": {"1"}, + "tail": {"100"}, + } + got := map[string][]string(req.URL.Query()) + if !reflect.DeepEqual(got, expectedQs) { + t.Errorf("Logs: wrong query string. Want %#v. Got %#v.", expectedQs, got) + } +} + +func TestLogsRawTerminal(t *testing.T) { + var req http.Request + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("something happened!")) + req = *r + })) + defer server.Close() + client, _ := NewClient(server.URL) + client.SkipServerVersionCheck = true + var buf bytes.Buffer + opts := LogsOptions{ + Container: "a123456", + OutputStream: &buf, + Follow: true, + RawTerminal: true, + Stdout: true, + Stderr: true, + Timestamps: true, + Tail: "100", + } + err := client.Logs(opts) + if err != nil { + t.Fatal(err) + } + expected := "something happened!" + if buf.String() != expected { + t.Errorf("Logs: wrong output. Want %q. Got %q.", expected, buf.String()) + } +} + func TestLogsNoContainer(t *testing.T) { var client Client err := client.Logs(LogsOptions{}) diff --git a/third_party/src/github.com/fsouza/go-dockerclient/image.go b/third_party/src/github.com/fsouza/go-dockerclient/image.go index 3350e98e23c24..4280d13552f68 100644 --- a/third_party/src/github.com/fsouza/go-dockerclient/image.go +++ b/third_party/src/github.com/fsouza/go-dockerclient/image.go @@ -154,6 +154,9 @@ type PushImageOptions struct { // Name of the image Name string + // Tag of the image + Tag string + // Registry server to push the image Registry string @@ -187,7 +190,7 @@ func (c *Client) PushImage(opts PushImageOptions, auth AuthConfiguration) error headers["X-Registry-Auth"] = base64.URLEncoding.EncodeToString(buf.Bytes()) - return c.stream("POST", path, headers, nil, opts.OutputStream) + return c.stream("POST", path, true, headers, nil, opts.OutputStream, nil) } // PullImageOptions present the set of options available for pulling an image @@ -219,7 +222,7 @@ func (c *Client) PullImage(opts PullImageOptions, auth AuthConfiguration) error func (c *Client) createImage(qs string, headers map[string]string, in io.Reader, w io.Writer) error { path := "/images/create?" + qs - return c.stream("POST", path, headers, in, w) + return c.stream("POST", path, true, headers, in, w, nil) } // ImportImageOptions present the set of informations available for importing @@ -286,13 +289,14 @@ func (c *Client) BuildImage(opts BuildImageOptions) error { return ErrMissingRepo } return c.stream("POST", fmt.Sprintf("/build?%s", - queryString(&opts)), headers, opts.InputStream, opts.OutputStream) + queryString(&opts)), true, headers, opts.InputStream, opts.OutputStream, nil) } // TagImageOptions present the set of options to tag an image type TagImageOptions struct { - Repo string `qs:"repo"` - Force bool `qs:"force"` + Repo string + Tag string + Force bool } // TagImage adds a tag to the image 'name' diff --git a/third_party/src/github.com/fsouza/go-dockerclient/testing/server.go b/third_party/src/github.com/fsouza/go-dockerclient/testing/server.go index 2081acc528c75..d79fcd492341d 100644 --- a/third_party/src/github.com/fsouza/go-dockerclient/testing/server.go +++ b/third_party/src/github.com/fsouza/go-dockerclient/testing/server.go @@ -18,7 +18,6 @@ import ( mathrand "math/rand" "net" "net/http" - "reflect" "regexp" "strconv" "strings" @@ -34,43 +33,55 @@ import ( // // For more details on the remote API, check http://goo.gl/yMI1S. type DockerServer struct { - containers []*docker.Container - cMut sync.RWMutex - images []docker.Image - iMut sync.RWMutex - imgIDs map[string]string - listener net.Listener - mux *mux.Router - hook func(*http.Request) - failures map[string]FailureSpec -} - -// FailureSpec is used with PrepareFailure and describes in which situations -// the request should fail. UrlRegex is mandatory, if a container id is sent -// on the request you can also specify the other properties. -type FailureSpec struct { - UrlRegex string - ContainerPath string - ContainerArgs []string + containers []*docker.Container + cMut sync.RWMutex + images []docker.Image + iMut sync.RWMutex + imgIDs map[string]string + listener net.Listener + mux *mux.Router + hook func(*http.Request) + failures map[string]string + customHandlers map[string]http.Handler + handlerMutex sync.RWMutex + cChan chan<- *docker.Container } // NewServer returns a new instance of the fake server, in standalone mode. Use // the method URL to get the URL of the server. // // It receives the bind address (use 127.0.0.1:0 for getting an available port -// on the host) and a hook function, that will be called on every request. -func NewServer(bind string, hook func(*http.Request)) (*DockerServer, error) { +// on the host), a channel of containers and a hook function, that will be +// called on every request. +// +// The fake server will send containers in the channel whenever the container +// changes its state, via the HTTP API (i.e.: create, start and stop). This +// channel may be nil, which means that the server won't notify on state +// changes. +func NewServer(bind string, containerChan chan<- *docker.Container, hook func(*http.Request)) (*DockerServer, error) { listener, err := net.Listen("tcp", bind) if err != nil { return nil, err } - server := DockerServer{listener: listener, imgIDs: make(map[string]string), hook: hook, - failures: make(map[string]FailureSpec)} + server := DockerServer{ + listener: listener, + imgIDs: make(map[string]string), + hook: hook, + failures: make(map[string]string), + customHandlers: make(map[string]http.Handler), + cChan: containerChan, + } server.buildMuxer() go http.Serve(listener, &server) return &server, nil } +func (s *DockerServer) notify(container *docker.Container) { + if s.cChan != nil { + s.cChan <- container + } +} + func (s *DockerServer) buildMuxer() { s.mux = mux.NewRouter() s.mux.Path("/commit").Methods("POST").HandlerFunc(s.handlerWrapper(s.commitContainer)) @@ -79,6 +90,8 @@ func (s *DockerServer) buildMuxer() { s.mux.Path("/containers/{id:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectContainer)) s.mux.Path("/containers/{id:.*}/start").Methods("POST").HandlerFunc(s.handlerWrapper(s.startContainer)) s.mux.Path("/containers/{id:.*}/stop").Methods("POST").HandlerFunc(s.handlerWrapper(s.stopContainer)) + s.mux.Path("/containers/{id:.*}/pause").Methods("POST").HandlerFunc(s.handlerWrapper(s.pauseContainer)) + s.mux.Path("/containers/{id:.*}/unpause").Methods("POST").HandlerFunc(s.handlerWrapper(s.unpauseContainer)) s.mux.Path("/containers/{id:.*}/wait").Methods("POST").HandlerFunc(s.handlerWrapper(s.waitContainer)) s.mux.Path("/containers/{id:.*}/attach").Methods("POST").HandlerFunc(s.handlerWrapper(s.attachContainer)) s.mux.Path("/containers/{id:.*}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.removeContainer)) @@ -89,19 +102,45 @@ func (s *DockerServer) buildMuxer() { s.mux.Path("/images/{name:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectImage)) s.mux.Path("/images/{name:.*}/push").Methods("POST").HandlerFunc(s.handlerWrapper(s.pushImage)) s.mux.Path("/events").Methods("GET").HandlerFunc(s.listEvents) + s.mux.Path("/_ping").Methods("GET").HandlerFunc(s.handlerWrapper(s.pingDocker)) } -// PrepareFailure adds a new expected failure based on a FailureSpec -// it receives an id for the failure and the spec. -func (s *DockerServer) PrepareFailure(id string, spec FailureSpec) { - s.failures[id] = spec +// PrepareFailure adds a new expected failure based on a URL regexp it receives +// an id for the failure. +func (s *DockerServer) PrepareFailure(id string, urlRegexp string) { + s.failures[id] = urlRegexp } -// ResetFailure removes an expected failure identified by the id +// ResetFailure removes an expected failure identified by the given id. func (s *DockerServer) ResetFailure(id string) { delete(s.failures, id) } +// CustomHandler registers a custom handler for a specific path. +// +// For example: +// +// server.CustomHandler("/containers/json", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { +// http.Error(w, "Something wrong is not right", http.StatusInternalServerError) +// })) +func (s *DockerServer) CustomHandler(path string, handler http.Handler) { + s.handlerMutex.Lock() + s.customHandlers[path] = handler + s.handlerMutex.Unlock() +} + +// MutateContainer changes the state of a container, returning an error if the +// given id does not match to any container "running" in the server. +func (s *DockerServer) MutateContainer(id string, state docker.State) error { + for _, container := range s.containers { + if container.ID == id { + container.State = state + return nil + } + } + return errors.New("container not found") +} + // Stop stops the server. func (s *DockerServer) Stop() { if s.listener != nil { @@ -119,6 +158,12 @@ func (s *DockerServer) URL() string { // ServeHTTP handles HTTP requests sent to the server. func (s *DockerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { + s.handlerMutex.RLock() + defer s.handlerMutex.RUnlock() + if handler, ok := s.customHandlers[r.URL.Path]; ok { + handler.ServeHTTP(w, r) + return + } s.mux.ServeHTTP(w, r) if s.hook != nil { s.hook(r) @@ -127,8 +172,8 @@ func (s *DockerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (s *DockerServer) handlerWrapper(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { - for errorId, spec := range s.failures { - matched, err := regexp.MatchString(spec.UrlRegex, r.URL.Path) + for errorID, urlRegexp := range s.failures { + matched, err := regexp.MatchString(urlRegexp, r.URL.Path) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return @@ -136,21 +181,7 @@ func (s *DockerServer) handlerWrapper(f func(http.ResponseWriter, *http.Request) if !matched { continue } - id := mux.Vars(r)["id"] - if id != "" { - container, _, err := s.findContainer(id) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - if spec.ContainerPath != "" && container.Path != spec.ContainerPath { - continue - } - if spec.ContainerArgs != nil && reflect.DeepEqual(container.Args, spec.ContainerArgs) { - continue - } - } - http.Error(w, errorId, http.StatusBadRequest) + http.Error(w, errorID, http.StatusBadRequest) return } f(w, r) @@ -272,6 +303,7 @@ func (s *DockerServer) createContainer(w http.ResponseWriter, r *http.Request) { s.cMut.Lock() s.containers = append(s.containers, &container) s.cMut.Unlock() + s.notify(&container) var c = struct{ ID string }{ID: container.ID} json.NewEncoder(w).Encode(c) } @@ -308,6 +340,7 @@ func (s *DockerServer) startContainer(w http.ResponseWriter, r *http.Request) { return } container.State.Running = true + s.notify(container) } func (s *DockerServer) stopContainer(w http.ResponseWriter, r *http.Request) { @@ -325,6 +358,41 @@ func (s *DockerServer) stopContainer(w http.ResponseWriter, r *http.Request) { } w.WriteHeader(http.StatusNoContent) container.State.Running = false + s.notify(container) +} + +func (s *DockerServer) pauseContainer(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["id"] + container, _, err := s.findContainer(id) + if err != nil { + http.Error(w, err.Error(), http.StatusNotFound) + return + } + s.cMut.Lock() + defer s.cMut.Unlock() + if container.State.Paused { + http.Error(w, "Container already paused", http.StatusBadRequest) + return + } + w.WriteHeader(http.StatusNoContent) + container.State.Paused = true +} + +func (s *DockerServer) unpauseContainer(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["id"] + container, _, err := s.findContainer(id) + if err != nil { + http.Error(w, err.Error(), http.StatusNotFound) + return + } + s.cMut.Lock() + defer s.cMut.Unlock() + if !container.State.Paused { + http.Error(w, "Container not paused", http.StatusBadRequest) + return + } + w.WriteHeader(http.StatusNoContent) + container.State.Paused = false } func (s *DockerServer) attachContainer(w http.ResponseWriter, r *http.Request) { @@ -361,7 +429,8 @@ func (s *DockerServer) waitContainer(w http.ResponseWriter, r *http.Request) { } s.cMut.RUnlock() } - w.Write([]byte(`{"StatusCode":0}`)) + result := map[string]int{"StatusCode": container.State.ExitCode} + json.NewEncoder(w).Encode(result) } func (s *DockerServer) removeContainer(w http.ResponseWriter, r *http.Request) { @@ -547,6 +616,10 @@ func (s *DockerServer) listEvents(w http.ResponseWriter, r *http.Request) { } } +func (s *DockerServer) pingDocker(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) +} + func (s *DockerServer) generateEvent() *docker.APIEvents { var eventType string switch mathrand.Intn(4) { diff --git a/third_party/src/github.com/fsouza/go-dockerclient/testing/server_test.go b/third_party/src/github.com/fsouza/go-dockerclient/testing/server_test.go index 547b23cd224a5..bbd4654fd6c75 100644 --- a/third_party/src/github.com/fsouza/go-dockerclient/testing/server_test.go +++ b/third_party/src/github.com/fsouza/go-dockerclient/testing/server_test.go @@ -20,7 +20,7 @@ import ( ) func TestNewServer(t *testing.T) { - server, err := NewServer("127.0.0.1:0", nil) + server, err := NewServer("127.0.0.1:0", nil, nil) if err != nil { t.Fatal(err) } @@ -33,7 +33,7 @@ func TestNewServer(t *testing.T) { } func TestServerStop(t *testing.T) { - server, err := NewServer("127.0.0.1:0", nil) + server, err := NewServer("127.0.0.1:0", nil, nil) if err != nil { t.Fatal(err) } @@ -50,7 +50,7 @@ func TestServerStopNoListener(t *testing.T) { } func TestServerURL(t *testing.T) { - server, err := NewServer("127.0.0.1:0", nil) + server, err := NewServer("127.0.0.1:0", nil, nil) if err != nil { t.Fatal(err) } @@ -71,7 +71,7 @@ func TestServerURLNoListener(t *testing.T) { func TestHandleWithHook(t *testing.T) { var called bool - server, _ := NewServer("127.0.0.1:0", func(*http.Request) { called = true }) + server, _ := NewServer("127.0.0.1:0", nil, func(*http.Request) { called = true }) defer server.Stop() recorder := httptest.NewRecorder() request, _ := http.NewRequest("GET", "/containers/json?all=1", nil) @@ -81,6 +81,25 @@ func TestHandleWithHook(t *testing.T) { } } +func TestCustomHandler(t *testing.T) { + var called bool + server, _ := NewServer("127.0.0.1:0", nil, nil) + addContainers(server, 2) + server.CustomHandler("/containers/json", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + called = true + fmt.Fprint(w, "Hello world") + })) + recorder := httptest.NewRecorder() + request, _ := http.NewRequest("GET", "/containers/json?all=1", nil) + server.ServeHTTP(recorder, request) + if !called { + t.Error("Did not call the custom handler") + } + if got := recorder.Body.String(); got != "Hello world" { + t.Errorf("Wrong output for custom handler: want %q. Got %q.", "Hello world", got) + } +} + func TestListContainers(t *testing.T) { server := DockerServer{} addContainers(&server, 2) @@ -158,6 +177,25 @@ func TestCreateContainer(t *testing.T) { } } +func TestCreateContainerWithNotifyChannel(t *testing.T) { + ch := make(chan *docker.Container, 1) + server := DockerServer{} + server.imgIDs = map[string]string{"base": "a1234"} + server.cChan = ch + server.buildMuxer() + recorder := httptest.NewRecorder() + body := `{"Hostname":"", "User":"", "Memory":0, "MemorySwap":0, "AttachStdin":false, "AttachStdout":true, "AttachStderr":true, +"PortSpecs":null, "Tty":false, "OpenStdin":false, "StdinOnce":false, "Env":null, "Cmd":["date"], "Image":"base", "Volumes":{}, "VolumesFrom":""}` + request, _ := http.NewRequest("POST", "/containers/create", strings.NewReader(body)) + server.ServeHTTP(recorder, request) + if recorder.Code != http.StatusCreated { + t.Errorf("CreateContainer: wrong status. Want %d. Got %d.", http.StatusCreated, recorder.Code) + } + if notified := <-ch; notified != server.containers[0] { + t.Errorf("CreateContainer: did not notify the proper container. Want %q. Got %q.", server.containers[0].ID, notified.ID) + } +} + func TestCreateContainerInvalidBody(t *testing.T) { server := DockerServer{} server.buildMuxer() @@ -320,6 +358,25 @@ func TestStartContainer(t *testing.T) { } } +func TestStartContainerWithNotifyChannel(t *testing.T) { + ch := make(chan *docker.Container, 1) + server := DockerServer{} + server.cChan = ch + addContainers(&server, 1) + addContainers(&server, 1) + server.buildMuxer() + recorder := httptest.NewRecorder() + path := fmt.Sprintf("/containers/%s/start", server.containers[1].ID) + request, _ := http.NewRequest("POST", path, nil) + server.ServeHTTP(recorder, request) + if recorder.Code != http.StatusOK { + t.Errorf("StartContainer: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) + } + if notified := <-ch; notified != server.containers[1] { + t.Errorf("StartContainer: did not notify the proper container. Want %q. Got %q.", server.containers[1].ID, notified.ID) + } +} + func TestStartContainerNotFound(t *testing.T) { server := DockerServer{} server.buildMuxer() @@ -363,6 +420,26 @@ func TestStopContainer(t *testing.T) { } } +func TestStopContainerWithNotifyChannel(t *testing.T) { + ch := make(chan *docker.Container, 1) + server := DockerServer{} + server.cChan = ch + addContainers(&server, 1) + addContainers(&server, 1) + server.containers[1].State.Running = true + server.buildMuxer() + recorder := httptest.NewRecorder() + path := fmt.Sprintf("/containers/%s/stop", server.containers[1].ID) + request, _ := http.NewRequest("POST", path, nil) + server.ServeHTTP(recorder, request) + if recorder.Code != http.StatusNoContent { + t.Errorf("StopContainer: wrong status code. Want %d. Got %d.", http.StatusNoContent, recorder.Code) + } + if notified := <-ch; notified != server.containers[1] { + t.Errorf("StopContainer: did not notify the proper container. Want %q. Got %q.", server.containers[1].ID, notified.ID) + } +} + func TestStopContainerNotFound(t *testing.T) { server := DockerServer{} server.buildMuxer() @@ -388,6 +465,90 @@ func TestStopContainerNotRunning(t *testing.T) { } } +func TestPauseContainer(t *testing.T) { + server := DockerServer{} + addContainers(&server, 1) + server.buildMuxer() + recorder := httptest.NewRecorder() + path := fmt.Sprintf("/containers/%s/pause", server.containers[0].ID) + request, _ := http.NewRequest("POST", path, nil) + server.ServeHTTP(recorder, request) + if recorder.Code != http.StatusNoContent { + t.Errorf("PauseContainer: wrong status code. Want %d. Got %d.", http.StatusNoContent, recorder.Code) + } + if !server.containers[0].State.Paused { + t.Error("PauseContainer: did not pause the container") + } +} + +func TestPauseContainerAlreadyPaused(t *testing.T) { + server := DockerServer{} + addContainers(&server, 1) + server.containers[0].State.Paused = true + server.buildMuxer() + recorder := httptest.NewRecorder() + path := fmt.Sprintf("/containers/%s/pause", server.containers[0].ID) + request, _ := http.NewRequest("POST", path, nil) + server.ServeHTTP(recorder, request) + if recorder.Code != http.StatusBadRequest { + t.Errorf("PauseContainer: wrong status code. Want %d. Got %d.", http.StatusBadRequest, recorder.Code) + } +} + +func TestPauseContainerNotFound(t *testing.T) { + server := DockerServer{} + server.buildMuxer() + recorder := httptest.NewRecorder() + path := "/containers/abc123/pause" + request, _ := http.NewRequest("POST", path, nil) + server.ServeHTTP(recorder, request) + if recorder.Code != http.StatusNotFound { + t.Errorf("PauseContainer: wrong status code. Want %d. Got %d.", http.StatusNotFound, recorder.Code) + } +} + +func TestUnpauseContainer(t *testing.T) { + server := DockerServer{} + addContainers(&server, 1) + server.containers[0].State.Paused = true + server.buildMuxer() + recorder := httptest.NewRecorder() + path := fmt.Sprintf("/containers/%s/unpause", server.containers[0].ID) + request, _ := http.NewRequest("POST", path, nil) + server.ServeHTTP(recorder, request) + if recorder.Code != http.StatusNoContent { + t.Errorf("UnpauseContainer: wrong status code. Want %d. Got %d.", http.StatusNoContent, recorder.Code) + } + if server.containers[0].State.Paused { + t.Error("UnpauseContainer: did not unpause the container") + } +} + +func TestUnpauseContainerNotPaused(t *testing.T) { + server := DockerServer{} + addContainers(&server, 1) + server.buildMuxer() + recorder := httptest.NewRecorder() + path := fmt.Sprintf("/containers/%s/unpause", server.containers[0].ID) + request, _ := http.NewRequest("POST", path, nil) + server.ServeHTTP(recorder, request) + if recorder.Code != http.StatusBadRequest { + t.Errorf("UnpauseContainer: wrong status code. Want %d. Got %d.", http.StatusBadRequest, recorder.Code) + } +} + +func TestUnpauseContainerNotFound(t *testing.T) { + server := DockerServer{} + server.buildMuxer() + recorder := httptest.NewRecorder() + path := "/containers/abc123/unpause" + request, _ := http.NewRequest("POST", path, nil) + server.ServeHTTP(recorder, request) + if recorder.Code != http.StatusNotFound { + t.Errorf("UnpauseContainer: wrong status code. Want %d. Got %d.", http.StatusNotFound, recorder.Code) + } +} + func TestWaitContainer(t *testing.T) { server := DockerServer{} addContainers(&server, 1) @@ -405,7 +566,25 @@ func TestWaitContainer(t *testing.T) { if recorder.Code != http.StatusOK { t.Errorf("WaitContainer: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } - expected := `{"StatusCode":0}` + expected := `{"StatusCode":0}` + "\n" + if body := recorder.Body.String(); body != expected { + t.Errorf("WaitContainer: wrong body. Want %q. Got %q.", expected, body) + } +} + +func TestWaitContainerStatus(t *testing.T) { + server := DockerServer{} + addContainers(&server, 1) + server.buildMuxer() + server.containers[0].State.ExitCode = 63 + recorder := httptest.NewRecorder() + path := fmt.Sprintf("/containers/%s/wait", server.containers[0].ID) + request, _ := http.NewRequest("POST", path, nil) + server.ServeHTTP(recorder, request) + if recorder.Code != http.StatusOK { + t.Errorf("WaitContainer: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) + } + expected := `{"StatusCode":63}` + "\n" if body := recorder.Body.String(); body != expected { t.Errorf("WaitContainer: wrong body. Want %q. Got %q.", expected, body) } @@ -659,75 +838,66 @@ func TestRemoveImageByName(t *testing.T) { } func TestPrepareFailure(t *testing.T) { - server := DockerServer{failures: make(map[string]FailureSpec)} + server := DockerServer{failures: make(map[string]string)} server.buildMuxer() - errorId := "my_error" - failure := FailureSpec{UrlRegex: "containers/json"} - server.PrepareFailure(errorId, failure) + errorID := "my_error" + server.PrepareFailure(errorID, "containers/json") recorder := httptest.NewRecorder() request, _ := http.NewRequest("GET", "/containers/json?all=1", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusBadRequest { t.Errorf("PrepareFailure: wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code) } - if recorder.Body.String() != errorId+"\n" { - t.Errorf("PrepareFailure: wrong message. Want %s. Got %s.", errorId, recorder.Body.String()) + if recorder.Body.String() != errorID+"\n" { + t.Errorf("PrepareFailure: wrong message. Want %s. Got %s.", errorID, recorder.Body.String()) } } -func TestPrepareFailureUsingContainerPath(t *testing.T) { - server := DockerServer{failures: make(map[string]FailureSpec)} - addContainers(&server, 1) +func TestRemoveFailure(t *testing.T) { + server := DockerServer{failures: make(map[string]string)} server.buildMuxer() - errorId := "my_path_error" - failure := FailureSpec{UrlRegex: "containers/.*?/start", ContainerPath: "ls"} - server.PrepareFailure(errorId, failure) + errorID := "my_error" + server.PrepareFailure(errorID, "containers/json") recorder := httptest.NewRecorder() - path := fmt.Sprintf("/containers/%s/start", server.containers[0].ID) - request, _ := http.NewRequest("POST", path, nil) + request, _ := http.NewRequest("GET", "/containers/json?all=1", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusBadRequest { - t.Errorf("TestPrepareFailureUsingContainerPath: wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code) + t.Errorf("PrepareFailure: wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code) } - if recorder.Body.String() != errorId+"\n" { - t.Errorf("TestPrepareFailureUsingContainerPath: wrong message. Want %s. Got %s.", errorId, recorder.Body.String()) + server.ResetFailure(errorID) + recorder = httptest.NewRecorder() + request, _ = http.NewRequest("GET", "/containers/json?all=1", nil) + server.ServeHTTP(recorder, request) + if recorder.Code != http.StatusOK { + t.Errorf("RemoveFailure: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } } -func TestPrepareFailureUsingContainerPathWithWrongPath(t *testing.T) { - server := DockerServer{failures: make(map[string]FailureSpec)} - addContainers(&server, 1) +func TestMutateContainer(t *testing.T) { + server := DockerServer{failures: make(map[string]string)} server.buildMuxer() - errorId := "my_path_error" - failure := FailureSpec{UrlRegex: "containers/.*?/start", ContainerPath: "xxx"} - server.PrepareFailure(errorId, failure) - recorder := httptest.NewRecorder() - path := fmt.Sprintf("/containers/%s/start", server.containers[0].ID) - request, _ := http.NewRequest("POST", path, nil) - server.ServeHTTP(recorder, request) - if recorder.Code != http.StatusOK { - t.Errorf("StartContainer: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) + server.containers = append(server.containers, &docker.Container{ID: "id123"}) + state := docker.State{Running: false, ExitCode: 1} + err := server.MutateContainer("id123", state) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(server.containers[0].State, state) { + t.Errorf("Wrong state after mutation.\nWant %#v.\nGot %#v.", + state, server.containers[0].State) } } -func TestRemoveFailure(t *testing.T) { - server := DockerServer{failures: make(map[string]FailureSpec)} +func TestMutateContainerNotFound(t *testing.T) { + server := DockerServer{failures: make(map[string]string)} server.buildMuxer() - errorId := "my_error" - failure := FailureSpec{UrlRegex: "containers/json"} - server.PrepareFailure(errorId, failure) - recorder := httptest.NewRecorder() - request, _ := http.NewRequest("GET", "/containers/json?all=1", nil) - server.ServeHTTP(recorder, request) - if recorder.Code != http.StatusBadRequest { - t.Errorf("PrepareFailure: wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code) + state := docker.State{Running: false, ExitCode: 1} + err := server.MutateContainer("id123", state) + if err == nil { + t.Error("Unexpected error") } - server.ResetFailure(errorId) - recorder = httptest.NewRecorder() - request, _ = http.NewRequest("GET", "/containers/json?all=1", nil) - server.ServeHTTP(recorder, request) - if recorder.Code != http.StatusOK { - t.Errorf("RemoveFailure: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) + if err.Error() != "container not found" { + t.Errorf("wrong error message. Want %q. Got %q.", "container not found", err) } } @@ -762,3 +932,16 @@ func TestBuildImageWithRemoteDockerfile(t *testing.T) { t.Errorf("BuildImage: image %s not builded", imageName) } } + +func TestPing(t *testing.T) { + server := DockerServer{} + recorder := httptest.NewRecorder() + request, _ := http.NewRequest("GET", "/_ping", nil) + server.pingDocker(recorder, request) + if recorder.Body.String() != "" { + t.Errorf("Ping: Unexpected body: %s", recorder.Body.String()) + } + if recorder.Code != http.StatusOK { + t.Errorf("Ping: Expected code %d, got: %d", http.StatusOK, recorder.Code) + } +} diff --git a/third_party/src/github.com/fsouza/go-dockerclient/utils/stdcopy.go b/third_party/src/github.com/fsouza/go-dockerclient/utils/stdcopy.go index b46cbfd5008d3..dec604ce48947 100644 --- a/third_party/src/github.com/fsouza/go-dockerclient/utils/stdcopy.go +++ b/third_party/src/github.com/fsouza/go-dockerclient/utils/stdcopy.go @@ -87,9 +87,12 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) var nr2 int nr2, er = src.Read(buf[nr:]) if er == io.EOF { - return written, nil - } - if er != nil { + if nr < StdWriterPrefixLen && nr2 < StdWriterPrefixLen { + return written, nil + } + nr += nr2 + break + } else if er != nil { return 0, er } nr += nr2 @@ -117,7 +120,7 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) // Extend it if necessary. if frameSize+StdWriterPrefixLen > bufLen { Debugf("Extending buffer cap.") - buf = append(buf, make([]byte, frameSize-len(buf)+1)...) + buf = append(buf, make([]byte, frameSize+StdWriterPrefixLen-len(buf)+1)...) bufLen = len(buf) } @@ -126,9 +129,12 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) var nr2 int nr2, er = src.Read(buf[nr:]) if er == io.EOF { - return written, nil - } - if er != nil { + if nr == 0 { + return written, nil + } + nr += nr2 + break + } else if er != nil { Debugf("Error reading frame: %s", er) return 0, er } @@ -136,7 +142,11 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) } // Write the retrieved frame (without header) - nw, ew = out.Write(buf[StdWriterPrefixLen : frameSize+StdWriterPrefixLen]) + bound := frameSize + StdWriterPrefixLen + if bound > nr { + bound = nr + } + nw, ew = out.Write(buf[StdWriterPrefixLen:bound]) if nw > 0 { written += int64(nw) } @@ -147,7 +157,7 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) // If the frame has not been fully written: error if nw != frameSize { Debugf("Error Short Write: (%d on %d)", nw, frameSize) - return 0, io.ErrShortWrite + return written, io.ErrShortWrite } // Move the rest of the buffer to the beginning diff --git a/third_party/src/github.com/fsouza/go-dockerclient/utils/stdcopy_test.go b/third_party/src/github.com/fsouza/go-dockerclient/utils/stdcopy_test.go new file mode 100644 index 0000000000000..81820ebb94f94 --- /dev/null +++ b/third_party/src/github.com/fsouza/go-dockerclient/utils/stdcopy_test.go @@ -0,0 +1,217 @@ +// Copyright 2014 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the DOCKER-LICENSE file. + +package utils + +import ( + "bytes" + "errors" + "io" + "strings" + "testing" + "testing/iotest" +) + +type errorWriter struct { +} + +func (errorWriter) Write([]byte) (int, error) { + return 0, errors.New("something went wrong") +} + +func TestStdCopy(t *testing.T) { + var input, stdout, stderr bytes.Buffer + input.Write([]byte{2, 0, 0, 0, 0, 0, 0, 19}) + input.Write([]byte("something happened!")) + input.Write([]byte{1, 0, 0, 0, 0, 0, 0, 12}) + input.Write([]byte("just kidding")) + input.Write([]byte{0, 0, 0, 0, 0, 0, 0, 6}) + input.Write([]byte("\nyeah!")) + n, err := StdCopy(&stdout, &stderr, &input) + if err != nil { + t.Fatal(err) + } + if expected := int64(19 + 12 + 6); n != expected { + t.Errorf("Wrong number of bytes. Want %d. Got %d.", expected, n) + } + if got := stderr.String(); got != "something happened!" { + t.Errorf("StdCopy: wrong stderr. Want %q. Got %q.", "something happened!", got) + } + if got := stdout.String(); got != "just kidding\nyeah!" { + t.Errorf("StdCopy: wrong stdout. Want %q. Got %q.", "just kidding\nyeah!", got) + } +} + +func TestStdCopyStress(t *testing.T) { + var input, stdout, stderr bytes.Buffer + value := strings.Repeat("something ", 4096) + writer := NewStdWriter(&input, Stdout) + writer.Write([]byte(value)) + n, err := StdCopy(&stdout, &stderr, &input) + if err != nil { + t.Fatal(err) + } + if n != 40960 { + t.Errorf("Wrong number of bytes. Want 40960. Got %d.", n) + } + if got := stderr.String(); got != "" { + t.Errorf("StdCopy: wrong stderr. Want empty string. Got %q", got) + } + if got := stdout.String(); got != value { + t.Errorf("StdCopy: wrong stdout. Want %q. Got %q", value, got) + } +} + +func TestStdCopyInvalidStdHeader(t *testing.T) { + var input, stdout, stderr bytes.Buffer + input.Write([]byte{3, 0, 0, 0, 0, 0, 0, 19}) + n, err := StdCopy(&stdout, &stderr, &input) + if n != 0 { + t.Errorf("StdCopy: wrong number of bytes. Want 0. Got %d", n) + } + if err != ErrInvalidStdHeader { + t.Errorf("StdCopy: wrong error. Want ErrInvalidStdHeader. Got %#v", err) + } +} + +func TestStdCopyBigFrame(t *testing.T) { + var input, stdout, stderr bytes.Buffer + input.Write([]byte{2, 0, 0, 0, 0, 0, 0, 18}) + input.Write([]byte("something happened!")) + n, err := StdCopy(&stdout, &stderr, &input) + if err != nil { + t.Fatal(err) + } + if expected := int64(18); n != expected { + t.Errorf("Wrong number of bytes. Want %d. Got %d.", expected, n) + } + if got := stderr.String(); got != "something happened" { + t.Errorf("StdCopy: wrong stderr. Want %q. Got %q.", "something happened", got) + } + if got := stdout.String(); got != "" { + t.Errorf("StdCopy: wrong stdout. Want %q. Got %q.", "", got) + } +} + +func TestStdCopySmallFrame(t *testing.T) { + var input, stdout, stderr bytes.Buffer + input.Write([]byte{2, 0, 0, 0, 0, 0, 0, 20}) + input.Write([]byte("something happened!")) + n, err := StdCopy(&stdout, &stderr, &input) + if err != io.ErrShortWrite { + t.Errorf("StdCopy: wrong error. Want ShortWrite. Got %#v", err) + } + if expected := int64(19); n != expected { + t.Errorf("Wrong number of bytes. Want %d. Got %d.", expected, n) + } + if got := stderr.String(); got != "something happened!" { + t.Errorf("StdCopy: wrong stderr. Want %q. Got %q.", "something happened", got) + } + if got := stdout.String(); got != "" { + t.Errorf("StdCopy: wrong stdout. Want %q. Got %q.", "", got) + } +} + +func TestStdCopyEmpty(t *testing.T) { + var input, stdout, stderr bytes.Buffer + n, err := StdCopy(&stdout, &stderr, &input) + if err != nil { + t.Fatal(err) + } + if n != 0 { + t.Errorf("StdCopy: wrong number of bytes. Want 0. Got %d.", n) + } +} + +func TestStdCopyCorruptedHeader(t *testing.T) { + var input, stdout, stderr bytes.Buffer + input.Write([]byte{2, 0, 0, 0, 0}) + n, err := StdCopy(&stdout, &stderr, &input) + if err != nil { + t.Fatal(err) + } + if n != 0 { + t.Errorf("StdCopy: wrong number of bytes. Want 0. Got %d.", n) + } +} + +func TestStdCopyTruncateWriter(t *testing.T) { + var input, stdout, stderr bytes.Buffer + input.Write([]byte{2, 0, 0, 0, 0, 0, 0, 19}) + input.Write([]byte("something happened!")) + n, err := StdCopy(&stdout, iotest.TruncateWriter(&stderr, 7), &input) + if err != nil { + t.Fatal(err) + } + if expected := int64(19); n != expected { + t.Errorf("Wrong number of bytes. Want %d. Got %d.", expected, n) + } + if got := stderr.String(); got != "somethi" { + t.Errorf("StdCopy: wrong stderr. Want %q. Got %q.", "somethi", got) + } + if got := stdout.String(); got != "" { + t.Errorf("StdCopy: wrong stdout. Want %q. Got %q.", "", got) + } +} + +func TestStdCopyHeaderOnly(t *testing.T) { + var input, stdout, stderr bytes.Buffer + input.Write([]byte{2, 0, 0, 0, 0, 0, 0, 19}) + n, err := StdCopy(&stdout, iotest.TruncateWriter(&stderr, 7), &input) + if err != io.ErrShortWrite { + t.Errorf("StdCopy: wrong error. Want ShortWrite. Got %#v", err) + } + if n != 0 { + t.Errorf("Wrong number of bytes. Want 0. Got %d.", n) + } + if got := stderr.String(); got != "" { + t.Errorf("StdCopy: wrong stderr. Want %q. Got %q.", "", got) + } + if got := stdout.String(); got != "" { + t.Errorf("StdCopy: wrong stdout. Want %q. Got %q.", "", got) + } +} + +func TestStdCopyDataErrReader(t *testing.T) { + var input, stdout, stderr bytes.Buffer + input.Write([]byte{2, 0, 0, 0, 0, 0, 0, 19}) + input.Write([]byte("something happened!")) + n, err := StdCopy(&stdout, &stderr, iotest.DataErrReader(&input)) + if err != nil { + t.Fatal(err) + } + if expected := int64(19); n != expected { + t.Errorf("Wrong number of bytes. Want %d. Got %d.", expected, n) + } + if got := stderr.String(); got != "something happened!" { + t.Errorf("StdCopy: wrong stderr. Want %q. Got %q.", "something happened!", got) + } + if got := stdout.String(); got != "" { + t.Errorf("StdCopy: wrong stdout. Want %q. Got %q.", "", got) + } +} + +func TestStdCopyTimeoutReader(t *testing.T) { + var input, stdout, stderr bytes.Buffer + input.Write([]byte{2, 0, 0, 0, 0, 0, 0, 19}) + input.Write([]byte("something happened!")) + _, err := StdCopy(&stdout, &stderr, iotest.TimeoutReader(&input)) + if err != iotest.ErrTimeout { + t.Errorf("StdCopy: wrong error. Want ErrTimeout. Got %#v.", err) + } +} + +func TestStdCopyWriteError(t *testing.T) { + var input bytes.Buffer + input.Write([]byte{2, 0, 0, 0, 0, 0, 0, 19}) + input.Write([]byte("something happened!")) + var stdout, stderr errorWriter + n, err := StdCopy(stdout, stderr, &input) + if err.Error() != "something went wrong" { + t.Errorf("StdCopy: wrong error. Want %q. Got %q", "something went wrong", err) + } + if n != 0 { + t.Errorf("StdCopy: wrong number of bytes. Want 0. Got %d.", n) + } +}