From f351691493c2b16105dc9cfa49e165fc5e1fd36e Mon Sep 17 00:00:00 2001 From: jhadvig Date: Wed, 27 Aug 2014 21:41:32 +0200 Subject: [PATCH 1/5] Adding endpoint for log retrieval on the minion --- pkg/kubelet/dockertools/docker.go | 22 ++++++++++ pkg/kubelet/dockertools/fake_docker_client.go | 9 ++++ pkg/kubelet/handlers.go | 20 +++++++++ pkg/kubelet/kubelet.go | 6 +++ pkg/kubelet/server.go | 42 ++++++++++++++++++- pkg/kubelet/server_test.go | 18 +++++--- 6 files changed, 110 insertions(+), 7 deletions(-) diff --git a/pkg/kubelet/dockertools/docker.go b/pkg/kubelet/dockertools/docker.go index b42e2ce9a36..1413681cb6f 100644 --- a/pkg/kubelet/dockertools/docker.go +++ b/pkg/kubelet/dockertools/docker.go @@ -25,6 +25,7 @@ import ( "sort" "strconv" "strings" + "io" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/fsouza/go-dockerclient" @@ -46,6 +47,7 @@ type DockerInterface interface { StartContainer(id string, hostConfig *docker.HostConfig) error StopContainer(id string, timeout uint) error PullImage(opts docker.PullImageOptions, auth docker.AuthConfiguration) error + Logs(opts docker.LogsOptions) error } // DockerID is an ID of docker container. It is a type to make it clear when we're working with docker container Ids @@ -202,6 +204,26 @@ func GetRecentDockerContainersWithNameAndUUID(client DockerInterface, podFullNam return result, nil } +// GetKubeletDockerContainerLogs returns logs of specific container +func GetKubeletDockerContainerLogs(client DockerInterface, containerID, tail string, follow bool, writer io.Writer) (err error) { + opts := docker.LogsOptions{ + Container: containerID, + Stdout: true, + Stderr: true, + OutputStream: writer, + ErrorStream: writer, + Timestamps: true, + RawTerminal: true, + } + + if opts.Follow = follow; follow == false { + opts.Tail = tail + } + + err = client.Logs(opts) + return +} + // ErrNoContainersInPod is returned when there are no containers for a given pod var ErrNoContainersInPod = errors.New("no containers exist for this pod") diff --git a/pkg/kubelet/dockertools/fake_docker_client.go b/pkg/kubelet/dockertools/fake_docker_client.go index c5d36a50570..2c222001c9b 100644 --- a/pkg/kubelet/dockertools/fake_docker_client.go +++ b/pkg/kubelet/dockertools/fake_docker_client.go @@ -111,6 +111,15 @@ func (f *FakeDockerClient) StopContainer(id string, timeout uint) error { return f.Err } +// Logs is a test-spy implementation of DockerInterface.Logs. +// It adds an entry "logs" to the internal method call record. +func (f *FakeDockerClient) Logs(opts docker.LogsOptions) error { + f.Lock() + defer f.Unlock() + f.called = append(f.called, "logs") + return f.Err +} + // PullImage is a test-spy implementation of DockerInterface.StopContainer. // It adds an entry "pull" to the internal method call record. func (f *FakeDockerClient) PullImage(opts docker.PullImageOptions, auth docker.AuthConfiguration) error { diff --git a/pkg/kubelet/handlers.go b/pkg/kubelet/handlers.go index bf207c07f90..2936b963176 100644 --- a/pkg/kubelet/handlers.go +++ b/pkg/kubelet/handlers.go @@ -20,6 +20,8 @@ import ( "fmt" "net" "strconv" + "net/http" + "io" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" @@ -95,3 +97,21 @@ func (h *httpActionHandler) Run(podFullName, uuid string, container *api.Contain _, err := h.client.Get(url) return err } + +// flusherWriter provides wrapper for responseWriter with HTTP streaming capabilities +type FlushWriter struct { + flusher http.Flusher + writer io.Writer +} + +// Write is a flushWriter implementation of the io.Writer that sends any buffered data to the client. +func (fw *FlushWriter) Write(p []byte) (n int, err error) { + n, err = fw.writer.Write(p) + if err != nil { + return n, err + } + if fw.flusher != nil { + fw.flusher.Flush() + } + return +} diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index ee0edd6c2ec..d6e342454e5 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -26,6 +26,7 @@ import ( "strings" "sync" "time" + "io" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation" @@ -751,6 +752,11 @@ func (kl *Kubelet) statsFromContainerPath(containerPath string, req *info.Contai return cinfo, nil } +// GetKubeletContainerLogs returns logs from the container +func (kl *Kubelet) GetKubeletContainerLogs(containerID, tail string, follow bool, writer io.Writer) error { + return dockertools.GetKubeletDockerContainerLogs(kl.dockerClient, containerID, tail , follow, writer) +} + // GetPodInfo returns information from Docker about the containers in a pod func (kl *Kubelet) GetPodInfo(podFullName, uuid string) (api.PodInfo, error) { return dockertools.GetDockerPodInfo(kl.dockerClient, podFullName, uuid) diff --git a/pkg/kubelet/server.go b/pkg/kubelet/server.go index 14ea087515a..c910f0cb86d 100644 --- a/pkg/kubelet/server.go +++ b/pkg/kubelet/server.go @@ -68,6 +68,7 @@ type HostInterface interface { GetMachineInfo() (*info.MachineInfo, error) GetPodInfo(name, uuid string) (api.PodInfo, error) RunInContainer(name, uuid, container string, cmd []string) ([]byte, error) + GetKubeletContainerLogs(containerID, tail string, follow bool, writer io.Writer) error ServeLogs(w http.ResponseWriter, req *http.Request) } @@ -92,6 +93,7 @@ func (s *Server) InstallDefaultHandlers() { s.mux.HandleFunc("/logs/", s.handleLogs) s.mux.HandleFunc("/spec/", s.handleSpec) s.mux.HandleFunc("/run/", s.handleRun) + s.mux.HandleFunc("/containerLogs", s.handleContainerLogs) } // error serializes an error object into an HTTP response. @@ -143,7 +145,45 @@ func (s *Server) handleContainers(w http.ResponseWriter, req *http.Request) { } -// handlePodInfo handles podInfo requests against the Kubelet. +// handleContainerLogs handles containerLogs request againts the Kubelet +func (s *Server) handleContainerLogs(w http.ResponseWriter, req *http.Request) { + defer req.Body.Close() + u, err := url.ParseRequestURI(req.RequestURI) + if err != nil { + s.error(w, err) + return + } + uriValues := u.Query() + + containerID := uriValues.Get("containerID") + follow := uriValues.Get("follow") == "1" + tail := uriValues.Get("tail") + + if len(containerID) == 0 { + w.WriteHeader(http.StatusBadRequest) + http.Error(w, "Missing 'containerID=' query entry.", http.StatusBadRequest) + return + } + logWriter := httplog.LogOf(req, w) + w = httplog.Unlogged(w) + fw := FlushWriter{writer: w} + if flusher, ok := w.(http.Flusher); ok { + fw.flusher = flusher + } else { + logWriter.Addf("unable to get Flusher") + http.NotFound(w, req) + return + } + w.Header().Set("Transfer-Encoding", "chunked") + w.WriteHeader(http.StatusOK) + err = s.host.GetKubeletContainerLogs(containerID, tail, follow, &fw) + if err != nil { + s.error(w, err) + return + } +} + +// handlePodInfo handles podInfo requests against the Kubelet func (s *Server) handlePodInfo(w http.ResponseWriter, req *http.Request) { u, err := url.ParseRequestURI(req.RequestURI) if err != nil { diff --git a/pkg/kubelet/server_test.go b/pkg/kubelet/server_test.go index a47fb2d1770..baec2523ecd 100644 --- a/pkg/kubelet/server_test.go +++ b/pkg/kubelet/server_test.go @@ -27,6 +27,7 @@ import ( "reflect" "strings" "testing" + "io" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" @@ -35,12 +36,17 @@ import ( ) type fakeKubelet struct { - infoFunc func(name string) (api.PodInfo, error) - containerInfoFunc func(podFullName, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) - rootInfoFunc func(query *info.ContainerInfoRequest) (*info.ContainerInfo, error) - machineInfoFunc func() (*info.MachineInfo, error) - logFunc func(w http.ResponseWriter, req *http.Request) - runFunc func(podFullName, uuid, containerName string, cmd []string) ([]byte, error) + infoFunc func(name string) (api.PodInfo, error) + containerInfoFunc func(podFullName, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) + rootInfoFunc func(query *info.ContainerInfoRequest) (*info.ContainerInfo, error) + machineInfoFunc func() (*info.MachineInfo, error) + logFunc func(w http.ResponseWriter, req *http.Request) + runFunc func(podFullName, uuid, containerName string, cmd []string) ([]byte, error) + containerLogsFunc func(containerID, tail string, follow bool, writer io.Writer) error +} + +func (fk *fakeKubelet) GetKubeletContainerLogs(containerID, tail string, follow bool, writer io.Writer) error { + return fk.containerLogsFunc(containerID, tail, follow, writer) } func (fk *fakeKubelet) GetPodInfo(name, uuid string) (api.PodInfo, error) { From 6da2653b4a1f70cec89d34b7a1872a595b7ded0e Mon Sep 17 00:00:00 2001 From: jhadvig Date: Mon, 15 Sep 2014 18:20:01 +0200 Subject: [PATCH 2/5] Update according to review --- pkg/kubelet/dockertools/docker.go | 6 +++++- pkg/kubelet/handlers.go | 6 +++--- pkg/kubelet/server.go | 22 ++++++++-------------- pkg/kubelet/server_test.go | 8 ++++---- 4 files changed, 20 insertions(+), 22 deletions(-) diff --git a/pkg/kubelet/dockertools/docker.go b/pkg/kubelet/dockertools/docker.go index 1413681cb6f..999934ac272 100644 --- a/pkg/kubelet/dockertools/docker.go +++ b/pkg/kubelet/dockertools/docker.go @@ -205,6 +205,9 @@ func GetRecentDockerContainersWithNameAndUUID(client DockerInterface, podFullNam } // GetKubeletDockerContainerLogs returns logs of specific container +// By default the function will return snapshot of the container log +// Log streaming is possible if 'follow' param is set to true +// Log tailing is possible when number of tailed lines are set and only if 'follow' is false func GetKubeletDockerContainerLogs(client DockerInterface, containerID, tail string, follow bool, writer io.Writer) (err error) { opts := docker.LogsOptions{ Container: containerID, @@ -214,9 +217,10 @@ func GetKubeletDockerContainerLogs(client DockerInterface, containerID, tail str ErrorStream: writer, Timestamps: true, RawTerminal: true, + Follow: follow, } - if opts.Follow = follow; follow == false { + if !follow { opts.Tail = tail } diff --git a/pkg/kubelet/handlers.go b/pkg/kubelet/handlers.go index 2936b963176..f2d8a3f3d3d 100644 --- a/pkg/kubelet/handlers.go +++ b/pkg/kubelet/handlers.go @@ -98,17 +98,17 @@ func (h *httpActionHandler) Run(podFullName, uuid string, container *api.Contain return err } -// flusherWriter provides wrapper for responseWriter with HTTP streaming capabilities +// FlusherWriter provides wrapper for responseWriter with HTTP streaming capabilities type FlushWriter struct { flusher http.Flusher writer io.Writer } -// Write is a flushWriter implementation of the io.Writer that sends any buffered data to the client. +// Write is a FlushWriter implementation of the io.Writer that sends any buffered data to the client. func (fw *FlushWriter) Write(p []byte) (n int, err error) { n, err = fw.writer.Write(p) if err != nil { - return n, err + return } if fw.flusher != nil { fw.flusher.Flush() diff --git a/pkg/kubelet/server.go b/pkg/kubelet/server.go index c910f0cb86d..0174de37341 100644 --- a/pkg/kubelet/server.go +++ b/pkg/kubelet/server.go @@ -145,7 +145,7 @@ func (s *Server) handleContainers(w http.ResponseWriter, req *http.Request) { } -// handleContainerLogs handles containerLogs request againts the Kubelet +// handleContainerLogs handles containerLogs request against the Kubelet func (s *Server) handleContainerLogs(w http.ResponseWriter, req *http.Request) { defer req.Body.Close() u, err := url.ParseRequestURI(req.RequestURI) @@ -153,27 +153,21 @@ func (s *Server) handleContainerLogs(w http.ResponseWriter, req *http.Request) { s.error(w, err) return } + uriValues := u.Query() - - containerID := uriValues.Get("containerID") - follow := uriValues.Get("follow") == "1" - tail := uriValues.Get("tail") - + containerID := uriValues.Get("containerid") if len(containerID) == 0 { - w.WriteHeader(http.StatusBadRequest) - http.Error(w, "Missing 'containerID=' query entry.", http.StatusBadRequest) + http.Error(w, `{"message": "Missing containerID= query entry."}`, http.StatusBadRequest) return } - logWriter := httplog.LogOf(req, w) - w = httplog.Unlogged(w) + follow, _ := strconv.ParseBool(uriValues.Get("follow")) + tail := uriValues.Get("tail") + fw := FlushWriter{writer: w} if flusher, ok := w.(http.Flusher); ok { fw.flusher = flusher - } else { - logWriter.Addf("unable to get Flusher") - http.NotFound(w, req) - return } + w.Header().Set("Transfer-Encoding", "chunked") w.WriteHeader(http.StatusOK) err = s.host.GetKubeletContainerLogs(containerID, tail, follow, &fw) diff --git a/pkg/kubelet/server_test.go b/pkg/kubelet/server_test.go index baec2523ecd..8c7abe410f6 100644 --- a/pkg/kubelet/server_test.go +++ b/pkg/kubelet/server_test.go @@ -45,10 +45,6 @@ type fakeKubelet struct { containerLogsFunc func(containerID, tail string, follow bool, writer io.Writer) error } -func (fk *fakeKubelet) GetKubeletContainerLogs(containerID, tail string, follow bool, writer io.Writer) error { - return fk.containerLogsFunc(containerID, tail, follow, writer) -} - func (fk *fakeKubelet) GetPodInfo(name, uuid string) (api.PodInfo, error) { return fk.infoFunc(name) } @@ -69,6 +65,10 @@ func (fk *fakeKubelet) ServeLogs(w http.ResponseWriter, req *http.Request) { fk.logFunc(w, req) } +func (fk *fakeKubelet) GetKubeletContainerLogs(containerID, tail string, follow bool, writer io.Writer) error { + return fk.containerLogsFunc(containerID, tail, follow, writer) +} + func (fk *fakeKubelet) RunInContainer(podFullName, uuid, containerName string, cmd []string) ([]byte, error) { return fk.runFunc(podFullName, uuid, containerName, cmd) } From f3f5d0200c867b8cba07373e2876024c802c3928 Mon Sep 17 00:00:00 2001 From: jhadvig Date: Wed, 17 Sep 2014 21:00:09 +0200 Subject: [PATCH 3/5] Using podID+containerName for querying the logs --- pkg/kubelet/kubelet.go | 13 +++++++++++-- pkg/kubelet/server.go | 31 +++++++++++++++++++++++-------- pkg/kubelet/server_test.go | 6 +++--- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index d6e342454e5..a75d12e1067 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -753,8 +753,17 @@ func (kl *Kubelet) statsFromContainerPath(containerPath string, req *info.Contai } // GetKubeletContainerLogs returns logs from the container -func (kl *Kubelet) GetKubeletContainerLogs(containerID, tail string, follow bool, writer io.Writer) error { - return dockertools.GetKubeletDockerContainerLogs(kl.dockerClient, containerID, tail , follow, writer) +func (kl *Kubelet) GetKubeletContainerLogs(podFullName, containerName, tail string, follow bool, writer io.Writer) error { + dockerContainers, err := dockertools.GetKubeletDockerContainers(kl.dockerClient) + if err != nil { + return err + } + var uuid string + dockerContainer, found, _ := dockerContainers.FindPodContainer(podFullName, uuid, containerName) + if !found { + return fmt.Errorf("container not found (%s)\n", containerName) + } + return dockertools.GetKubeletDockerContainerLogs(kl.dockerClient, dockerContainer.ID, tail , follow, writer) } // GetPodInfo returns information from Docker about the containers in a pod diff --git a/pkg/kubelet/server.go b/pkg/kubelet/server.go index 0174de37341..077640ab486 100644 --- a/pkg/kubelet/server.go +++ b/pkg/kubelet/server.go @@ -68,7 +68,7 @@ type HostInterface interface { GetMachineInfo() (*info.MachineInfo, error) GetPodInfo(name, uuid string) (api.PodInfo, error) RunInContainer(name, uuid, container string, cmd []string) ([]byte, error) - GetKubeletContainerLogs(containerID, tail string, follow bool, writer io.Writer) error + GetKubeletContainerLogs(podFullName, containerName, tail string, follow bool, writer io.Writer) error ServeLogs(w http.ResponseWriter, req *http.Request) } @@ -93,7 +93,7 @@ func (s *Server) InstallDefaultHandlers() { s.mux.HandleFunc("/logs/", s.handleLogs) s.mux.HandleFunc("/spec/", s.handleSpec) s.mux.HandleFunc("/run/", s.handleRun) - s.mux.HandleFunc("/containerLogs", s.handleContainerLogs) + s.mux.HandleFunc("/containerLogs/", s.handleContainerLogs) } // error serializes an error object into an HTTP response. @@ -153,24 +153,39 @@ func (s *Server) handleContainerLogs(w http.ResponseWriter, req *http.Request) { s.error(w, err) return } + parts := strings.Split(u.Path, "/") - uriValues := u.Query() - containerID := uriValues.Get("containerid") - if len(containerID) == 0 { - http.Error(w, `{"message": "Missing containerID= query entry."}`, http.StatusBadRequest) + var podID, containerName string + if len(parts) == 4 { + podID = parts[2] + containerName = parts[3] + } else { + http.Error(w, "Unexpected path for command running", http.StatusBadRequest) return } + + if len(podID) == 0 { + http.Error(w, `{"message": "Missing podID."}`, http.StatusBadRequest) + return + } + if len(containerName) == 0 { + http.Error(w, `{"message": "Missing container name."}`, http.StatusBadRequest) + return + } + + uriValues := u.Query() follow, _ := strconv.ParseBool(uriValues.Get("follow")) tail := uriValues.Get("tail") + podFullName := GetPodFullName(&Pod{Name: podID, Namespace: "etcd"}) + fw := FlushWriter{writer: w} if flusher, ok := w.(http.Flusher); ok { fw.flusher = flusher } - w.Header().Set("Transfer-Encoding", "chunked") w.WriteHeader(http.StatusOK) - err = s.host.GetKubeletContainerLogs(containerID, tail, follow, &fw) + err = s.host.GetKubeletContainerLogs(podFullName, containerName, tail, follow, &fw) if err != nil { s.error(w, err) return diff --git a/pkg/kubelet/server_test.go b/pkg/kubelet/server_test.go index 8c7abe410f6..2f07bc9d72c 100644 --- a/pkg/kubelet/server_test.go +++ b/pkg/kubelet/server_test.go @@ -42,7 +42,7 @@ type fakeKubelet struct { machineInfoFunc func() (*info.MachineInfo, error) logFunc func(w http.ResponseWriter, req *http.Request) runFunc func(podFullName, uuid, containerName string, cmd []string) ([]byte, error) - containerLogsFunc func(containerID, tail string, follow bool, writer io.Writer) error + containerLogsFunc func(podFullName, containerName, tail string, follow bool, writer io.Writer) error } func (fk *fakeKubelet) GetPodInfo(name, uuid string) (api.PodInfo, error) { @@ -65,8 +65,8 @@ func (fk *fakeKubelet) ServeLogs(w http.ResponseWriter, req *http.Request) { fk.logFunc(w, req) } -func (fk *fakeKubelet) GetKubeletContainerLogs(containerID, tail string, follow bool, writer io.Writer) error { - return fk.containerLogsFunc(containerID, tail, follow, writer) +func (fk *fakeKubelet) GetKubeletContainerLogs(podFullName, containerName, tail string, follow bool, writer io.Writer) error { + return fk.containerLogsFunc(podFullName, containerName, tail, follow, writer) } func (fk *fakeKubelet) RunInContainer(podFullName, uuid, containerName string, cmd []string) ([]byte, error) { From 9f34eaef9ebd4f3021b1cc7cdf10fc6114703548 Mon Sep 17 00:00:00 2001 From: jhadvig Date: Thu, 18 Sep 2014 14:08:20 +0200 Subject: [PATCH 4/5] kubelet server test cases --- pkg/kubelet/server_test.go | 118 +++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/pkg/kubelet/server_test.go b/pkg/kubelet/server_test.go index 2f07bc9d72c..465958fac63 100644 --- a/pkg/kubelet/server_test.go +++ b/pkg/kubelet/server_test.go @@ -381,3 +381,121 @@ func TestServeRunInContainerWithUUID(t *testing.T) { t.Errorf("expected %s, got %s", output, result) } } + +func TestContainerLogs(t *testing.T) { + fw := newServerTest() + output := "foo bar" + podName := "foo" + expectedPodName := podName + ".etcd" + expectedContainerName := "baz" + expectedTail := "" + expectedFollow := false + // expected := api.Container{"goodpod": docker.Container{ID: "myContainerID"}} + fw.fakeKubelet.containerLogsFunc = func(podFullName, containerName, tail string, follow bool, writer io.Writer) error { + if podFullName != expectedPodName { + t.Errorf("expected %s, got %s", expectedPodName, podFullName) + } + if containerName != expectedContainerName { + t.Errorf("expected %s, got %s", expectedContainerName, containerName) + } + if tail != expectedTail { + t.Errorf("expected %s, got %s", expectedTail, tail) + } + if follow != expectedFollow { + t.Errorf("expected %t, got %t", expectedFollow, follow) + } + return nil + } + resp, err := http.Get(fw.testHTTPServer.URL+"/containerLogs/" + podName + "/" + expectedContainerName) + if err != nil { + t.Errorf("Got error GETing: %v", err) + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("Error reading container logs: %v", err) + } + result := string(body) + if result != string(body) { + t.Errorf("Expected: '%v', got: '%v'", output, result) + } +} + +func TestContainerLogsWithTail(t *testing.T) { + fw := newServerTest() + output := "foo bar" + podName := "foo" + expectedPodName := podName + ".etcd" + expectedContainerName := "baz" + expectedTail := "5" + expectedFollow := false + fw.fakeKubelet.containerLogsFunc = func(podFullName, containerName, tail string, follow bool, writer io.Writer) error { + if podFullName != expectedPodName { + t.Errorf("expected %s, got %s", expectedPodName, podFullName) + } + if containerName != expectedContainerName { + t.Errorf("expected %s, got %s", expectedContainerName, containerName) + } + if tail != expectedTail { + t.Errorf("expected %s, got %s", expectedTail, tail) + } + if follow != expectedFollow { + t.Errorf("expected %t, got %t", expectedFollow, follow) + } + return nil + } + resp, err := http.Get(fw.testHTTPServer.URL+"/containerLogs/" + podName + "/" + expectedContainerName + "?tail=5") + if err != nil { + t.Errorf("Got error GETing: %v", err) + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("Error reading container logs: %v", err) + } + result := string(body) + if result != string(body) { + t.Errorf("Expected: '%v', got: '%v'", output, result) + } +} + +func TestContainerLogsWithFollow(t *testing.T) { + fw := newServerTest() + output := "foo bar" + podName := "foo" + expectedPodName := podName + ".etcd" + expectedContainerName := "baz" + expectedTail := "" + expectedFollow := true + fw.fakeKubelet.containerLogsFunc = func(podFullName, containerName, tail string, follow bool, writer io.Writer) error { + if podFullName != expectedPodName { + t.Errorf("expected %s, got %s", expectedPodName, podFullName) + } + if containerName != expectedContainerName { + t.Errorf("expected %s, got %s", expectedContainerName, containerName) + } + if tail != expectedTail { + t.Errorf("expected %s, got %s", expectedTail, tail) + } + if follow != expectedFollow { + t.Errorf("expected %t, got %t", expectedFollow, follow) + } + return nil + } + resp, err := http.Get(fw.testHTTPServer.URL+"/containerLogs/" + podName + "/" + expectedContainerName + "?follow=1") + if err != nil { + t.Errorf("Got error GETing: %v", err) + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("Error reading container logs: %v", err) + } + result := string(body) + if result != string(body) { + t.Errorf("Expected: '%v', got: '%v'", output, result) + } +} From 960f1b2693c09fa4ca2d4a983b7a47fcf8fed02f Mon Sep 17 00:00:00 2001 From: jhadvig Date: Sun, 21 Sep 2014 22:25:44 +0200 Subject: [PATCH 5/5] Consistency update --- pkg/kubelet/handlers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/kubelet/handlers.go b/pkg/kubelet/handlers.go index f2d8a3f3d3d..77934595721 100644 --- a/pkg/kubelet/handlers.go +++ b/pkg/kubelet/handlers.go @@ -98,7 +98,7 @@ func (h *httpActionHandler) Run(podFullName, uuid string, container *api.Contain return err } -// FlusherWriter provides wrapper for responseWriter with HTTP streaming capabilities +// FlushWriter provides wrapper for responseWriter with HTTP streaming capabilities type FlushWriter struct { flusher http.Flusher writer io.Writer