From 83c599e4d4721a7aa886b0d513f404c3057e8e0a Mon Sep 17 00:00:00 2001 From: Yu-Ju Hong Date: Thu, 30 Apr 2015 10:12:23 -0700 Subject: [PATCH] Add image-related methods to DockerManager This change is part of the efforts to make DockerManager implement the Runtime interface. The change also modifies the interface slightly to work with existing code, and aggregates the type converting functions to convert.go. --- pkg/kubelet/container/runtime.go | 6 +-- pkg/kubelet/dockertools/convert.go | 60 +++++++++++++++++++++ pkg/kubelet/dockertools/convert_test.go | 71 +++++++++++++++++++++++++ pkg/kubelet/dockertools/manager.go | 53 ++++++++++-------- pkg/kubelet/dockertools/manager_test.go | 50 ++++++++--------- pkg/kubelet/kubelet.go | 2 +- 6 files changed, 190 insertions(+), 52 deletions(-) create mode 100644 pkg/kubelet/dockertools/convert.go create mode 100644 pkg/kubelet/dockertools/convert_test.go diff --git a/pkg/kubelet/container/runtime.go b/pkg/kubelet/container/runtime.go index 7c093a3ddf..f83649f281 100644 --- a/pkg/kubelet/container/runtime.go +++ b/pkg/kubelet/container/runtime.go @@ -54,18 +54,18 @@ type Runtime interface { KillContainerInPod(api.Container, *api.Pod) error // GetPodStatus retrieves the status of the pod, including the information of // all containers in the pod. - GetPodStatus(*api.Pod) (api.PodStatus, error) + GetPodStatus(*api.Pod) (*api.PodStatus, error) // TODO(vmarmol): Merge RunInContainer and ExecInContainer. // Runs the command in the container of the specified pod using nsinit. // TODO(yifan): Use strong type for containerID. - RunInContainer(containerID string, cmd []string) error + RunInContainer(containerID string, cmd []string) ([]byte, error) // Runs the command in the container of the specified pod using nsenter. // Attaches the processes stdin, stdout, and stderr. Optionally uses a // tty. // TODO(yifan): Use strong type for containerID. ExecInContainer(containerID string, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool) error // Forward the specified port from the specified pod to the stream. - PortForward(pod Pod, port uint16, stream io.ReadWriteCloser) error + PortForward(pod *Pod, port uint16, stream io.ReadWriteCloser) error // PullImage pulls an image from the network to local storage. PullImage(image string) error // IsImagePresent checks whether the container image is already in the local storage. diff --git a/pkg/kubelet/dockertools/convert.go b/pkg/kubelet/dockertools/convert.go new file mode 100644 index 0000000000..eb73290bc9 --- /dev/null +++ b/pkg/kubelet/dockertools/convert.go @@ -0,0 +1,60 @@ +/* +Copyright 2015 Google Inc. All rights reserved. + +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 dockertools + +import ( + "fmt" + + kubecontainer "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/container" + "github.com/GoogleCloudPlatform/kubernetes/pkg/types" + docker "github.com/fsouza/go-dockerclient" +) + +// This file contains helper functions to convert docker API types to runtime +// (kubecontainer) types. + +// Converts docker.APIContainers to kubecontainer.Container. +func toRuntimeContainer(c *docker.APIContainers) (*kubecontainer.Container, error) { + if c == nil { + return nil, fmt.Errorf("unable to convert a nil pointer to a runtime container") + } + + dockerName, hash, err := getDockerContainerNameInfo(c) + if err != nil { + return nil, err + } + return &kubecontainer.Container{ + ID: types.UID(c.ID), + Name: dockerName.ContainerName, + Image: c.Image, + Hash: hash, + Created: c.Created, + }, nil +} + +// Converts docker.APIImages to kubecontainer.Image. +func toRuntimeImage(image *docker.APIImages) (*kubecontainer.Image, error) { + if image == nil { + return nil, fmt.Errorf("unable to convert a nil pointer to a runtime image") + } + + return &kubecontainer.Image{ + ID: image.ID, + Tags: image.RepoTags, + Size: image.Size, + }, nil +} diff --git a/pkg/kubelet/dockertools/convert_test.go b/pkg/kubelet/dockertools/convert_test.go new file mode 100644 index 0000000000..faf84be225 --- /dev/null +++ b/pkg/kubelet/dockertools/convert_test.go @@ -0,0 +1,71 @@ +/* +Copyright 2015 Google Inc. All rights reserved. + +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 dockertools + +import ( + "reflect" + "testing" + + kubecontainer "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/container" + "github.com/GoogleCloudPlatform/kubernetes/pkg/types" + docker "github.com/fsouza/go-dockerclient" +) + +func TestToRuntimeContainer(t *testing.T) { + original := &docker.APIContainers{ + ID: "ab2cdf", + Image: "bar_image", + Created: 12345, + Names: []string{"/k8s_bar.5678_foo_ns_1234_42"}, + } + expected := &kubecontainer.Container{ + ID: types.UID("ab2cdf"), + Name: "bar", + Image: "bar_image", + Hash: 0x5678, + Created: 12345, + } + + actual, err := toRuntimeContainer(original) + if err != nil { + t.Fatalf("unexpected error %v", err) + } + if !reflect.DeepEqual(expected, actual) { + t.Errorf("expected %#v, got %#v", expected, actual) + } +} + +func TestToRuntimeImage(t *testing.T) { + original := &docker.APIImages{ + ID: "aeeea", + RepoTags: []string{"abc", "def"}, + Size: 1234, + } + expected := &kubecontainer.Image{ + ID: "aeeea", + Tags: []string{"abc", "def"}, + Size: 1234, + } + + actual, err := toRuntimeImage(original) + if err != nil { + t.Fatalf("unexpected error %v", err) + } + if !reflect.DeepEqual(expected, actual) { + t.Errorf("expected %#v, got %#v", expected, actual) + } +} diff --git a/pkg/kubelet/dockertools/manager.go b/pkg/kubelet/dockertools/manager.go index 780300b350..48f23696b0 100644 --- a/pkg/kubelet/dockertools/manager.go +++ b/pkg/kubelet/dockertools/manager.go @@ -53,8 +53,8 @@ const ( maxReasonCacheEntries = 200 ) -// TODO: Eventually DockerManager should implement kubecontainer.Runtime -// interface. +// TODO(yjhong): DockerManager should implement the Runtime interface. + type DockerManager struct { client DockerInterface recorder record.EventRecorder @@ -640,21 +640,6 @@ func getDockerContainerNameInfo(c *docker.APIContainers) (*KubeletContainerName, return dockerName, hash, nil } -// Converts docker.APIContainers to kubecontainer.Container. -func convertDockerToRuntimeContainer(c *docker.APIContainers) (*kubecontainer.Container, error) { - dockerName, hash, err := getDockerContainerNameInfo(c) - if err != nil { - return nil, err - } - return &kubecontainer.Container{ - ID: types.UID(c.ID), - Name: dockerName.ContainerName, - Image: c.Image, - Hash: hash, - Created: c.Created, - }, nil -} - // Get pod UID, name, and namespace by examining the container names. func getPodInfoFromContainer(c *docker.APIContainers) (types.UID, string, string, error) { dockerName, _, err := getDockerContainerNameInfo(c) @@ -678,7 +663,7 @@ func (dm *DockerManager) GetContainers(all bool) ([]*kubecontainer.Container, er // Convert DockerContainers to []*kubecontainer.Container result := make([]*kubecontainer.Container, 0, len(containers)) for _, c := range containers { - converted, err := convertDockerToRuntimeContainer(c) + converted, err := toRuntimeContainer(c) if err != nil { glog.Errorf("Error examining the container: %v", err) continue @@ -699,7 +684,7 @@ func (dm *DockerManager) GetPods(all bool) ([]*kubecontainer.Pod, error) { // Group containers by pod. for _, c := range containers { - converted, err := convertDockerToRuntimeContainer(c) + converted, err := toRuntimeContainer(c) if err != nil { glog.Errorf("Error examining the container: %v", err) continue @@ -730,14 +715,40 @@ func (dm *DockerManager) GetPods(all bool) ([]*kubecontainer.Pod, error) { return result, nil } -func (dm *DockerManager) Pull(image string) error { +// List all images in the local storage. +func (dm *DockerManager) ListImages() ([]kubecontainer.Image, error) { + var images []kubecontainer.Image + + dockerImages, err := dm.client.ListImages(docker.ListImagesOptions{}) + if err != nil { + return images, err + } + + for _, di := range dockerImages { + image, err := toRuntimeImage(&di) + if err != nil { + continue + } + images = append(images, *image) + } + return images, nil +} + +// PullImage pulls an image from network to local storage. +func (dm *DockerManager) PullImage(image string) error { return dm.Puller.Pull(image) } +// IsImagePresent checks whether the container image is already in the local storage. func (dm *DockerManager) IsImagePresent(image string) (bool, error) { return dm.Puller.IsImagePresent(image) } +// Removes the specified image. +func (dm *DockerManager) RemoveImage(image string) error { + return dm.client.RemoveImage(image) +} + // podInfraContainerChanged returns true if the pod infra container has changed. func (dm *DockerManager) podInfraContainerChanged(pod *api.Pod, podInfraContainer *kubecontainer.Container) (bool, error) { networkMode := "" @@ -1128,7 +1139,7 @@ func (dm *DockerManager) CreatePodInfraContainer(pod *api.Pod, generator kubecon return "", err } if !ok { - if err := dm.Pull(container.Image); err != nil { + if err := dm.PullImage(container.Image); err != nil { if ref != nil { dm.recorder.Eventf(ref, "failed", "Failed to pull image %q: %v", container.Image, err) } diff --git a/pkg/kubelet/dockertools/manager_test.go b/pkg/kubelet/dockertools/manager_test.go index ec8b194b8d..1d6d14c567 100644 --- a/pkg/kubelet/dockertools/manager_test.go +++ b/pkg/kubelet/dockertools/manager_test.go @@ -116,30 +116,6 @@ func TestSetEntrypointAndCommand(t *testing.T) { } } -func TestConvertDockerToRuntimeContainer(t *testing.T) { - dockerContainer := &docker.APIContainers{ - ID: "ab2cdf", - Image: "bar_image", - Created: 12345, - Names: []string{"/k8s_bar.5678_foo_ns_1234_42"}, - } - expected := &kubecontainer.Container{ - ID: types.UID("ab2cdf"), - Name: "bar", - Image: "bar_image", - Hash: 0x5678, - Created: 12345, - } - - actual, err := convertDockerToRuntimeContainer(dockerContainer) - if err != nil { - t.Fatalf("unexpected error %v", err) - } - if !reflect.DeepEqual(expected, actual) { - t.Errorf("expected %#v, got %#v", expected, actual) - } -} - // verifyPods returns true if the two pod slices are equal. func verifyPods(a, b []*kubecontainer.Pod) bool { if len(a) != len(b) { @@ -179,11 +155,10 @@ func TestGetPods(t *testing.T) { } // Convert the docker containers. This does not affect the test coverage - // because the conversion is tested separately in - // TestConvertDockerToRuntimeContainer. + // because the conversion is tested separately in convert_test.go containers := make([]*kubecontainer.Container, len(dockerContainers)) for i := range containers { - c, err := convertDockerToRuntimeContainer(&dockerContainers[i]) + c, err := toRuntimeContainer(&dockerContainers[i]) if err != nil { t.Fatalf("unexpected error %v", err) } @@ -214,3 +189,24 @@ func TestGetPods(t *testing.T) { t.Errorf("expected %#v, got %#v", expected, actual) } } + +func TestListImages(t *testing.T) { + manager, fakeDocker := NewFakeDockerManager() + dockerImages := []docker.APIImages{{ID: "1111"}, {ID: "2222"}, {ID: "3333"}} + expected := util.NewStringSet([]string{"1111", "2222", "3333"}...) + + fakeDocker.Images = dockerImages + actualImages, err := manager.ListImages() + if err != nil { + t.Fatalf("unexpected error %v", err) + } + actual := util.NewStringSet() + for _, i := range actualImages { + actual.Insert(i.ID) + } + // We can compare the two sets directly because util.StringSet.List() + // returns a "sorted" list. + if !reflect.DeepEqual(expected.List(), actual.List()) { + t.Errorf("expected %#v, got %#v", expected.List(), actual.List()) + } +} diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index 5f9787f24c..964b35feda 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -887,7 +887,7 @@ func (kl *Kubelet) pullImage(pod *api.Pod, container *api.Container) error { return nil } - err = kl.containerManager.Pull(container.Image) + err = kl.containerManager.PullImage(container.Image) kl.runtimeHooks.ReportImagePull(pod, container, err) return err }