diff --git a/pkg/kubelet/dockershim/docker_container.go b/pkg/kubelet/dockershim/docker_container.go index d7ca149d2b6..aef66ff157b 100644 --- a/pkg/kubelet/dockershim/docker_container.go +++ b/pkg/kubelet/dockershim/docker_container.go @@ -19,12 +19,15 @@ package dockershim import ( "fmt" "io" + "time" dockertypes "github.com/docker/engine-api/types" dockercontainer "github.com/docker/engine-api/types/container" dockerfilters "github.com/docker/engine-api/types/filters" dockerstrslice "github.com/docker/engine-api/types/strslice" + runtimeApi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime" + "k8s.io/kubernetes/pkg/kubelet/dockertools" ) // ListContainers lists all containers matching the filter. @@ -190,14 +193,112 @@ func (ds *dockerService) RemoveContainer(rawContainerID string) error { return ds.client.RemoveContainer(rawContainerID, dockertypes.ContainerRemoveOptions{RemoveVolumes: true}) } +func getContainerTimestamps(r *dockertypes.ContainerJSON) (time.Time, time.Time, time.Time, error) { + var createdAt, startedAt, finishedAt time.Time + var err error + + createdAt, err = dockertools.ParseDockerTimestamp(r.Created) + if err != nil { + return createdAt, startedAt, finishedAt, err + } + startedAt, err = dockertools.ParseDockerTimestamp(r.State.StartedAt) + if err != nil { + return createdAt, startedAt, finishedAt, err + } + finishedAt, err = dockertools.ParseDockerTimestamp(r.State.FinishedAt) + if err != nil { + return createdAt, startedAt, finishedAt, err + } + return createdAt, startedAt, finishedAt, nil +} + // ContainerStatus returns the container status. -// TODO: Implement the function. func (ds *dockerService) ContainerStatus(rawContainerID string) (*runtimeApi.ContainerStatus, error) { - return nil, fmt.Errorf("not implemented") + r, err := ds.client.InspectContainer(rawContainerID) + if err != nil { + return nil, err + } + + // Parse the timstamps. + createdAt, startedAt, finishedAt, err := getContainerTimestamps(r) + if err != nil { + return nil, fmt.Errorf("failed to parse timestamp for container %q: %v", rawContainerID, err) + } + + // Convert the mounts. + mounts := []*runtimeApi.Mount{} + for _, m := range r.Mounts { + readonly := !m.RW + mounts = append(mounts, &runtimeApi.Mount{ + Name: &m.Name, + HostPath: &m.Source, + ContainerPath: &m.Destination, + Readonly: &readonly, + // Note: Can't set SeLinuxRelabel + }) + } + // Interpret container states. + var state runtimeApi.ContainerState + var reason string + if r.State.Running { + // Container is running. + state = runtimeApi.ContainerState_RUNNING + } else { + // Container is *not* running. We need to get more details. + // * Case 1: container has run and exited with non-zero finishedAt + // time. + // * Case 2: container has failed to start; it has a zero finishedAt + // time, but a non-zero exit code. + // * Case 3: container has been created, but not started (yet). + if !finishedAt.IsZero() { // Case 1 + state = runtimeApi.ContainerState_EXITED + switch { + case r.State.OOMKilled: + // TODO: consider exposing OOMKilled via the runtimeAPI. + // Note: if an application handles OOMKilled gracefully, the + // exit code could be zero. + reason = "OOMKilled" + case r.State.ExitCode == 0: + reason = "Completed" + default: + reason = fmt.Sprintf("Error: %s", r.State.Error) + } + } else if !finishedAt.IsZero() && r.State.ExitCode != 0 { // Case 2 + state = runtimeApi.ContainerState_EXITED + // Adjust finshedAt and startedAt time to createdAt time to avoid + // the confusion. + finishedAt, startedAt = createdAt, createdAt + reason = "ContainerCannotRun" + } else { // Case 3 + state = runtimeApi.ContainerState_CREATED + } + } + + // Convert to unix timestamps. + ct, st, ft := createdAt.Unix(), startedAt.Unix(), finishedAt.Unix() + exitCode := int32(r.State.ExitCode) + + return &runtimeApi.ContainerStatus{ + Id: &r.ID, + Name: &r.Name, + Image: &runtimeApi.ImageSpec{Image: &r.Config.Image}, + ImageRef: &r.Image, + Mounts: mounts, + ExitCode: &exitCode, + State: &state, + CreatedAt: &ct, + StartedAt: &st, + FinishedAt: &ft, + Reason: &reason, + // TODO: We write annotations as labels on the docker containers. All + // these annotations will be read back as labels. Need to fix this. + Labels: r.Config.Labels, + }, nil } // Exec execute a command in the container. -// TODO: Implement the function. +// TODO: Need to handle terminal resizing before implementing this function. +// https://github.com/kubernetes/kubernetes/issues/29579. func (ds *dockerService) Exec(rawContainerID string, cmd []string, tty bool, stdin io.Reader, stdout, stderr io.WriteCloser) error { return fmt.Errorf("not implemented") } diff --git a/pkg/kubelet/dockershim/docker_service_test.go b/pkg/kubelet/dockershim/docker_service_test.go new file mode 100644 index 00000000000..5d33bc2297c --- /dev/null +++ b/pkg/kubelet/dockershim/docker_service_test.go @@ -0,0 +1,27 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package dockershim + +import ( + "k8s.io/kubernetes/pkg/kubelet/dockertools" +) + +func newFakeDockerSevice() *dockerService { + return &dockerService{ + client: dockertools.NewFakeDockerClient(), + } +} diff --git a/pkg/kubelet/dockershim/legacy.go b/pkg/kubelet/dockershim/legacy.go index 5f1bbc46fae..2abeeca819d 100644 --- a/pkg/kubelet/dockershim/legacy.go +++ b/pkg/kubelet/dockershim/legacy.go @@ -22,6 +22,7 @@ import ( "k8s.io/kubernetes/pkg/api" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/dockertools" "k8s.io/kubernetes/pkg/util/term" ) @@ -31,11 +32,11 @@ import ( // TODO: implement the methods in this file. func (ds *dockerService) AttachContainer(id kubecontainer.ContainerID, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size) (err error) { - return fmt.Errorf("not implemented") + return dockertools.AttachContainer(ds.client, id, stdin, stdout, stderr, tty, resize) } func (ds *dockerService) GetContainerLogs(pod *api.Pod, containerID kubecontainer.ContainerID, logOptions *api.PodLogOptions, stdout, stderr io.Writer) (err error) { - return fmt.Errorf("not implemented") + return dockertools.GetContainerLogs(ds.client, pod, containerID, logOptions, stdout, stderr) } func (ds *dockerService) PortForward(pod *kubecontainer.Pod, port uint16, stream io.ReadWriteCloser) error { diff --git a/pkg/kubelet/dockertools/container_gc.go b/pkg/kubelet/dockertools/container_gc.go index 5b00875b70e..c5ab19e331f 100644 --- a/pkg/kubelet/dockertools/container_gc.go +++ b/pkg/kubelet/dockertools/container_gc.go @@ -139,7 +139,7 @@ func (cgc *containerGC) evictableContainers(minAge time.Duration) (containersByE continue } - created, err := parseDockerTimestamp(data.Created) + created, err := ParseDockerTimestamp(data.Created) if err != nil { glog.Errorf("Failed to parse Created timestamp %q for container %q", data.Created, container.ID) } diff --git a/pkg/kubelet/dockertools/docker_manager.go b/pkg/kubelet/dockertools/docker_manager.go index bff3e8891b6..d7b0dddd1a1 100644 --- a/pkg/kubelet/dockertools/docker_manager.go +++ b/pkg/kubelet/dockertools/docker_manager.go @@ -285,7 +285,13 @@ func NewDockerManager( // stream the log. Set 'follow' to false and specify the number of lines (e.g. // "100" or "all") to tail the log. // TODO: Make 'RawTerminal' option flagable. -func (dm *DockerManager) GetContainerLogs(pod *api.Pod, containerID kubecontainer.ContainerID, logOptions *api.PodLogOptions, stdout, stderr io.Writer) (err error) { +func (dm *DockerManager) GetContainerLogs(pod *api.Pod, containerID kubecontainer.ContainerID, logOptions *api.PodLogOptions, stdout, stderr io.Writer) error { + return GetContainerLogs(dm.client, pod, containerID, logOptions, stdout, stderr) +} + +// Temporarily export this function to share with dockershim. +// TODO: clean this up. +func GetContainerLogs(client DockerInterface, pod *api.Pod, containerID kubecontainer.ContainerID, logOptions *api.PodLogOptions, stdout, stderr io.Writer) error { var since int64 if logOptions.SinceSeconds != nil { t := unversioned.Now().Add(-time.Duration(*logOptions.SinceSeconds) * time.Second) @@ -309,8 +315,7 @@ func (dm *DockerManager) GetContainerLogs(pod *api.Pod, containerID kubecontaine ErrorStream: stderr, RawTerminal: false, } - err = dm.client.Logs(containerID.ID, opts, sopts) - return + return client.Logs(containerID.ID, opts, sopts) } var ( @@ -374,13 +379,13 @@ func (dm *DockerManager) inspectContainer(id string, podName, podNamespace strin glog.Errorf("Failed to parse %q timestamp %q for container %q of pod %q", label, s, id, kubecontainer.BuildPodFullName(podName, podNamespace)) } var createdAt, startedAt, finishedAt time.Time - if createdAt, err = parseDockerTimestamp(iResult.Created); err != nil { + if createdAt, err = ParseDockerTimestamp(iResult.Created); err != nil { parseTimestampError("Created", iResult.Created) } - if startedAt, err = parseDockerTimestamp(iResult.State.StartedAt); err != nil { + if startedAt, err = ParseDockerTimestamp(iResult.State.StartedAt); err != nil { parseTimestampError("StartedAt", iResult.State.StartedAt) } - if finishedAt, err = parseDockerTimestamp(iResult.State.FinishedAt); err != nil { + if finishedAt, err = ParseDockerTimestamp(iResult.State.FinishedAt); err != nil { parseTimestampError("FinishedAt", iResult.State.FinishedAt) } @@ -1123,10 +1128,16 @@ func (dm *DockerManager) ExecInContainer(containerID kubecontainer.ContainerID, } func (dm *DockerManager) AttachContainer(containerID kubecontainer.ContainerID, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size) error { + return AttachContainer(dm.client, containerID, stdin, stdout, stderr, tty, resize) +} + +// Temporarily export this function to share with dockershim. +// TODO: clean this up. +func AttachContainer(client DockerInterface, containerID kubecontainer.ContainerID, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size) error { // Have to start this before the call to client.AttachToContainer because client.AttachToContainer is a blocking // call :-( Otherwise, resize events don't get processed and the terminal never resizes. kubecontainer.HandleResizing(resize, func(size term.Size) { - dm.client.ResizeContainerTTY(containerID.ID, int(size.Height), int(size.Width)) + client.ResizeContainerTTY(containerID.ID, int(size.Height), int(size.Width)) }) // TODO(random-liu): Do we really use the *Logs* field here? @@ -1142,7 +1153,7 @@ func (dm *DockerManager) AttachContainer(containerID kubecontainer.ContainerID, ErrorStream: stderr, RawTerminal: tty, } - return dm.client.AttachToContainer(containerID.ID, opts, sopts) + return client.AttachToContainer(containerID.ID, opts, sopts) } func noPodInfraContainerError(podName, podNamespace string) error { diff --git a/pkg/kubelet/dockertools/kube_docker_client.go b/pkg/kubelet/dockertools/kube_docker_client.go index 9b31b4b6e5b..a2de527c193 100644 --- a/pkg/kubelet/dockertools/kube_docker_client.go +++ b/pkg/kubelet/dockertools/kube_docker_client.go @@ -531,8 +531,8 @@ func (d *kubeDockerClient) getTimeoutContext() (context.Context, context.CancelF return context.WithTimeout(context.Background(), d.timeout) } -// parseDockerTimestamp parses the timestamp returned by DockerInterface from string to time.Time -func parseDockerTimestamp(s string) (time.Time, error) { +// ParseDockerTimestamp parses the timestamp returned by DockerInterface from string to time.Time +func ParseDockerTimestamp(s string) (time.Time, error) { // Timestamp returned by Docker is in time.RFC3339Nano format. return time.Parse(time.RFC3339Nano, s) }