Commit 86479cc5 authored by Dawn Chen's avatar Dawn Chen

Add support to pull log for last terminated container

parent ffa59470
...@@ -1373,21 +1373,31 @@ func (kl *Kubelet) validatePodPhase(podStatus *api.PodStatus) error { ...@@ -1373,21 +1373,31 @@ func (kl *Kubelet) validatePodPhase(podStatus *api.PodStatus) error {
return fmt.Errorf("pod is not in 'Running', 'Succeeded' or 'Failed' state - State: %q", podStatus.Phase) return fmt.Errorf("pod is not in 'Running', 'Succeeded' or 'Failed' state - State: %q", podStatus.Phase)
} }
func (kl *Kubelet) validateContainerStatus(podStatus *api.PodStatus, containerName string) (containerID string, err error) { func (kl *Kubelet) validateContainerStatus(podStatus *api.PodStatus, containerName string, previous bool) (containerID string, err error) {
var cID string
cStatus, found := api.GetContainerStatus(podStatus.ContainerStatuses, containerName) cStatus, found := api.GetContainerStatus(podStatus.ContainerStatuses, containerName)
if !found { if !found {
return "", fmt.Errorf("container %q not found in pod", containerName) return "", fmt.Errorf("container %q not found in pod", containerName)
} }
if cStatus.State.Waiting != nil { if previous {
return "", fmt.Errorf("container %q is in waiting state.", containerName) if cStatus.LastTerminationState.Termination == nil {
return "", fmt.Errorf("previous terminated container %q not found in pod", containerName)
}
cID = cStatus.LastTerminationState.Termination.ContainerID
} else {
if cStatus.State.Waiting != nil {
return "", fmt.Errorf("container %q is in waiting state.", containerName)
}
cID = cStatus.ContainerID
} }
return kubecontainer.TrimRuntimePrefix(cStatus.ContainerID), nil return kubecontainer.TrimRuntimePrefix(cID), nil
} }
// GetKubeletContainerLogs returns logs from the container // GetKubeletContainerLogs returns logs from the container
// TODO: this method is returning logs of random container attempts, when it should be returning the most recent attempt // TODO: this method is returning logs of random container attempts, when it should be returning the most recent attempt
// or all of them. // or all of them.
func (kl *Kubelet) GetKubeletContainerLogs(podFullName, containerName, tail string, follow bool, stdout, stderr io.Writer) error { func (kl *Kubelet) GetKubeletContainerLogs(podFullName, containerName, tail string, follow, previous bool, stdout, stderr io.Writer) error {
// TODO(vmarmol): Refactor to not need the pod status and verification. // TODO(vmarmol): Refactor to not need the pod status and verification.
podStatus, err := kl.GetPodStatus(podFullName) podStatus, err := kl.GetPodStatus(podFullName)
if err != nil { if err != nil {
...@@ -1397,7 +1407,7 @@ func (kl *Kubelet) GetKubeletContainerLogs(podFullName, containerName, tail stri ...@@ -1397,7 +1407,7 @@ func (kl *Kubelet) GetKubeletContainerLogs(podFullName, containerName, tail stri
// No log is available if pod is not in a "known" phase (e.g. Unknown). // No log is available if pod is not in a "known" phase (e.g. Unknown).
return err return err
} }
containerID, err := kl.validateContainerStatus(&podStatus, containerName) containerID, err := kl.validateContainerStatus(&podStatus, containerName, previous)
if err != nil { if err != nil {
// No log is available if the container status is missing or is in the // No log is available if the container status is missing or is in the
// waiting state. // waiting state.
......
...@@ -3141,6 +3141,9 @@ func TestValidateContainerStatus(t *testing.T) { ...@@ -3141,6 +3141,9 @@ func TestValidateContainerStatus(t *testing.T) {
State: api.ContainerState{ State: api.ContainerState{
Running: &api.ContainerStateRunning{}, Running: &api.ContainerStateRunning{},
}, },
LastTerminationState: api.ContainerState{
Termination: &api.ContainerStateTerminated{},
},
}, },
}, },
success: true, success: true,
...@@ -3172,7 +3175,7 @@ func TestValidateContainerStatus(t *testing.T) { ...@@ -3172,7 +3175,7 @@ func TestValidateContainerStatus(t *testing.T) {
for i, tc := range testCases { for i, tc := range testCases {
_, err := kubelet.validateContainerStatus(&api.PodStatus{ _, err := kubelet.validateContainerStatus(&api.PodStatus{
ContainerStatuses: tc.statuses, ContainerStatuses: tc.statuses,
}, containerName) }, containerName, false)
if tc.success { if tc.success {
if err != nil { if err != nil {
t.Errorf("[case %d]: unexpected failure - %v", i, err) t.Errorf("[case %d]: unexpected failure - %v", i, err)
...@@ -3183,9 +3186,19 @@ func TestValidateContainerStatus(t *testing.T) { ...@@ -3183,9 +3186,19 @@ func TestValidateContainerStatus(t *testing.T) {
} }
if _, err := kubelet.validateContainerStatus(&api.PodStatus{ if _, err := kubelet.validateContainerStatus(&api.PodStatus{
ContainerStatuses: testCases[0].statuses, ContainerStatuses: testCases[0].statuses,
}, "blah"); err == nil { }, "blah", false); err == nil {
t.Errorf("expected error with invalid container name") t.Errorf("expected error with invalid container name")
} }
if _, err := kubelet.validateContainerStatus(&api.PodStatus{
ContainerStatuses: testCases[0].statuses,
}, containerName, true); err != nil {
t.Errorf("unexpected error with for previous terminated container - %v", err)
}
if _, err := kubelet.validateContainerStatus(&api.PodStatus{
ContainerStatuses: testCases[1].statuses,
}, containerName, true); err == nil {
t.Errorf("expected error with for previous terminated container")
}
} }
func TestUpdateNewNodeStatus(t *testing.T) { func TestUpdateNewNodeStatus(t *testing.T) {
......
...@@ -107,7 +107,7 @@ type HostInterface interface { ...@@ -107,7 +107,7 @@ type HostInterface interface {
GetPodStatus(name string) (api.PodStatus, error) GetPodStatus(name string) (api.PodStatus, error)
RunInContainer(name string, uid types.UID, container string, cmd []string) ([]byte, error) RunInContainer(name string, uid types.UID, container string, cmd []string) ([]byte, error)
ExecInContainer(name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool) error ExecInContainer(name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool) error
GetKubeletContainerLogs(podFullName, containerName, tail string, follow bool, stdout, stderr io.Writer) error GetKubeletContainerLogs(podFullName, containerName, tail string, follow, previous bool, stdout, stderr io.Writer) error
ServeLogs(w http.ResponseWriter, req *http.Request) ServeLogs(w http.ResponseWriter, req *http.Request)
PortForward(name string, uid types.UID, port uint16, stream io.ReadWriteCloser) error PortForward(name string, uid types.UID, port uint16, stream io.ReadWriteCloser) error
StreamingConnectionIdleTimeout() time.Duration StreamingConnectionIdleTimeout() time.Duration
...@@ -230,6 +230,7 @@ func (s *Server) handleContainerLogs(w http.ResponseWriter, req *http.Request) { ...@@ -230,6 +230,7 @@ func (s *Server) handleContainerLogs(w http.ResponseWriter, req *http.Request) {
uriValues := u.Query() uriValues := u.Query()
follow, _ := strconv.ParseBool(uriValues.Get("follow")) follow, _ := strconv.ParseBool(uriValues.Get("follow"))
previous, _ := strconv.ParseBool(uriValues.Get("previous"))
tail := uriValues.Get("tail") tail := uriValues.Get("tail")
pod, ok := s.host.GetPodByName(podNamespace, podID) pod, ok := s.host.GetPodByName(podNamespace, podID)
...@@ -256,7 +257,7 @@ func (s *Server) handleContainerLogs(w http.ResponseWriter, req *http.Request) { ...@@ -256,7 +257,7 @@ func (s *Server) handleContainerLogs(w http.ResponseWriter, req *http.Request) {
fw := flushwriter.Wrap(w) fw := flushwriter.Wrap(w)
w.Header().Set("Transfer-Encoding", "chunked") w.Header().Set("Transfer-Encoding", "chunked")
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
err = s.host.GetKubeletContainerLogs(kubecontainer.GetPodFullName(pod), containerName, tail, follow, fw, fw) err = s.host.GetKubeletContainerLogs(kubecontainer.GetPodFullName(pod), containerName, tail, follow, previous, fw, fw)
if err != nil { if err != nil {
s.error(w, err) s.error(w, err)
return return
......
...@@ -53,7 +53,7 @@ type fakeKubelet struct { ...@@ -53,7 +53,7 @@ type fakeKubelet struct {
containerVersionFunc func() (kubecontainer.Version, error) containerVersionFunc func() (kubecontainer.Version, error)
execFunc func(pod string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool) error execFunc func(pod string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool) error
portForwardFunc func(name string, uid types.UID, port uint16, stream io.ReadWriteCloser) error portForwardFunc func(name string, uid types.UID, port uint16, stream io.ReadWriteCloser) error
containerLogsFunc func(podFullName, containerName, tail string, follow bool, stdout, stderr io.Writer) error containerLogsFunc func(podFullName, containerName, tail string, follow, pervious bool, stdout, stderr io.Writer) error
streamingConnectionIdleTimeoutFunc func() time.Duration streamingConnectionIdleTimeoutFunc func() time.Duration
hostnameFunc func() string hostnameFunc func() string
} }
...@@ -90,8 +90,8 @@ func (fk *fakeKubelet) ServeLogs(w http.ResponseWriter, req *http.Request) { ...@@ -90,8 +90,8 @@ func (fk *fakeKubelet) ServeLogs(w http.ResponseWriter, req *http.Request) {
fk.logFunc(w, req) fk.logFunc(w, req)
} }
func (fk *fakeKubelet) GetKubeletContainerLogs(podFullName, containerName, tail string, follow bool, stdout, stderr io.Writer) error { func (fk *fakeKubelet) GetKubeletContainerLogs(podFullName, containerName, tail string, follow, previous bool, stdout, stderr io.Writer) error {
return fk.containerLogsFunc(podFullName, containerName, tail, follow, stdout, stderr) return fk.containerLogsFunc(podFullName, containerName, tail, follow, previous, stdout, stderr)
} }
func (fk *fakeKubelet) GetHostname() string { func (fk *fakeKubelet) GetHostname() string {
...@@ -553,8 +553,8 @@ func setPodByNameFunc(fw *serverTestFramework, namespace, pod, container string) ...@@ -553,8 +553,8 @@ func setPodByNameFunc(fw *serverTestFramework, namespace, pod, container string)
} }
} }
func setGetContainerLogsFunc(fw *serverTestFramework, t *testing.T, expectedPodName, expectedContainerName, expectedTail string, expectedFollow bool, output string) { func setGetContainerLogsFunc(fw *serverTestFramework, t *testing.T, expectedPodName, expectedContainerName, expectedTail string, expectedFollow, expectedPrevious bool, output string) {
fw.fakeKubelet.containerLogsFunc = func(podFullName, containerName, tail string, follow bool, stdout, stderr io.Writer) error { fw.fakeKubelet.containerLogsFunc = func(podFullName, containerName, tail string, follow, previous bool, stdout, stderr io.Writer) error {
if podFullName != expectedPodName { if podFullName != expectedPodName {
t.Errorf("expected %s, got %s", expectedPodName, podFullName) t.Errorf("expected %s, got %s", expectedPodName, podFullName)
} }
...@@ -567,6 +567,10 @@ func setGetContainerLogsFunc(fw *serverTestFramework, t *testing.T, expectedPodN ...@@ -567,6 +567,10 @@ func setGetContainerLogsFunc(fw *serverTestFramework, t *testing.T, expectedPodN
if follow != expectedFollow { if follow != expectedFollow {
t.Errorf("expected %t, got %t", expectedFollow, follow) t.Errorf("expected %t, got %t", expectedFollow, follow)
} }
if previous != expectedPrevious {
t.Errorf("expected %t, got %t", expectedPrevious, previous)
}
io.WriteString(stdout, output) io.WriteString(stdout, output)
return nil return nil
} }
...@@ -581,8 +585,9 @@ func TestContainerLogs(t *testing.T) { ...@@ -581,8 +585,9 @@ func TestContainerLogs(t *testing.T) {
expectedContainerName := "baz" expectedContainerName := "baz"
expectedTail := "" expectedTail := ""
expectedFollow := false expectedFollow := false
expectedPrevious := false
setPodByNameFunc(fw, podNamespace, podName, expectedContainerName) setPodByNameFunc(fw, podNamespace, podName, expectedContainerName)
setGetContainerLogsFunc(fw, t, expectedPodName, expectedContainerName, expectedTail, expectedFollow, output) setGetContainerLogsFunc(fw, t, expectedPodName, expectedContainerName, expectedTail, expectedFollow, expectedPrevious, output)
resp, err := http.Get(fw.testHTTPServer.URL + "/containerLogs/" + podNamespace + "/" + podName + "/" + expectedContainerName) resp, err := http.Get(fw.testHTTPServer.URL + "/containerLogs/" + podNamespace + "/" + podName + "/" + expectedContainerName)
if err != nil { if err != nil {
t.Errorf("Got error GETing: %v", err) t.Errorf("Got error GETing: %v", err)
...@@ -608,8 +613,9 @@ func TestContainerLogsWithTail(t *testing.T) { ...@@ -608,8 +613,9 @@ func TestContainerLogsWithTail(t *testing.T) {
expectedContainerName := "baz" expectedContainerName := "baz"
expectedTail := "5" expectedTail := "5"
expectedFollow := false expectedFollow := false
expectedPrevious := false
setPodByNameFunc(fw, podNamespace, podName, expectedContainerName) setPodByNameFunc(fw, podNamespace, podName, expectedContainerName)
setGetContainerLogsFunc(fw, t, expectedPodName, expectedContainerName, expectedTail, expectedFollow, output) setGetContainerLogsFunc(fw, t, expectedPodName, expectedContainerName, expectedTail, expectedFollow, expectedPrevious, output)
resp, err := http.Get(fw.testHTTPServer.URL + "/containerLogs/" + podNamespace + "/" + podName + "/" + expectedContainerName + "?tail=5") resp, err := http.Get(fw.testHTTPServer.URL + "/containerLogs/" + podNamespace + "/" + podName + "/" + expectedContainerName + "?tail=5")
if err != nil { if err != nil {
t.Errorf("Got error GETing: %v", err) t.Errorf("Got error GETing: %v", err)
...@@ -635,8 +641,9 @@ func TestContainerLogsWithFollow(t *testing.T) { ...@@ -635,8 +641,9 @@ func TestContainerLogsWithFollow(t *testing.T) {
expectedContainerName := "baz" expectedContainerName := "baz"
expectedTail := "" expectedTail := ""
expectedFollow := true expectedFollow := true
expectedPrevious := false
setPodByNameFunc(fw, podNamespace, podName, expectedContainerName) setPodByNameFunc(fw, podNamespace, podName, expectedContainerName)
setGetContainerLogsFunc(fw, t, expectedPodName, expectedContainerName, expectedTail, expectedFollow, output) setGetContainerLogsFunc(fw, t, expectedPodName, expectedContainerName, expectedTail, expectedFollow, expectedPrevious, output)
resp, err := http.Get(fw.testHTTPServer.URL + "/containerLogs/" + podNamespace + "/" + podName + "/" + expectedContainerName + "?follow=1") resp, err := http.Get(fw.testHTTPServer.URL + "/containerLogs/" + podNamespace + "/" + podName + "/" + expectedContainerName + "?follow=1")
if err != nil { if err != nil {
t.Errorf("Got error GETing: %v", err) t.Errorf("Got error GETing: %v", err)
......
...@@ -212,6 +212,9 @@ func LogLocation(getter ResourceGetter, connInfo client.ConnectionInfoGetter, ct ...@@ -212,6 +212,9 @@ func LogLocation(getter ResourceGetter, connInfo client.ConnectionInfoGetter, ct
if opts.Follow { if opts.Follow {
params.Add("follow", "true") params.Add("follow", "true")
} }
if opts.Previous {
params.Add("previous", "true")
}
loc := &url.URL{ loc := &url.URL{
Scheme: nodeScheme, Scheme: nodeScheme,
Host: fmt.Sprintf("%s:%d", nodeHost, nodePort), Host: fmt.Sprintf("%s:%d", nodeHost, nodePort),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment