diff --git a/pkg/kubelet/dockershim/docker_container_test.go b/pkg/kubelet/dockershim/docker_container_test.go new file mode 100644 index 00000000000..2d9e17d2313 --- /dev/null +++ b/pkg/kubelet/dockershim/docker_container_test.go @@ -0,0 +1,153 @@ +/* +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 ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + runtimeApi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime" +) + +// A helper to create a basic config. +func makeContainerConfig(sConfig *runtimeApi.PodSandboxConfig, name, image string, attempt uint32) *runtimeApi.ContainerConfig { + return &runtimeApi.ContainerConfig{ + Metadata: &runtimeApi.ContainerMetadata{ + Name: &name, + Attempt: &attempt, + }, + Image: &runtimeApi.ImageSpec{Image: &image}, + } +} + +// TestListContainers creates several containers and then list them to check +// whether the correct metadatas, states, and labels are returned. +func TestListContainers(t *testing.T) { + ds, _, _ := newTestDockerSevice() + podName, namespace := "foo", "bar" + containerName, image := "sidecar", "logger" + + configs := []*runtimeApi.ContainerConfig{} + sConfigs := []*runtimeApi.PodSandboxConfig{} + for i := 0; i < 3; i++ { + s := makeSandboxConfig(fmt.Sprintf("%s%d", podName, i), + fmt.Sprintf("%s%d", namespace, i), fmt.Sprintf("%d", i), 0) + c := makeContainerConfig(s, fmt.Sprintf("%s%d", containerName, i), + fmt.Sprintf("%s:v%d", image, i), uint32(i)) + sConfigs = append(sConfigs, s) + configs = append(configs, c) + } + + expected := []*runtimeApi.Container{} + state := runtimeApi.ContainerState_RUNNING + for i := range configs { + // We don't care about the sandbox id; pass a bogus one. + sandboxID := fmt.Sprintf("sandboxid%d", i) + id, err := ds.CreateContainer(sandboxID, configs[i], sConfigs[i]) + assert.NoError(t, err) + err = ds.StartContainer(id) + assert.NoError(t, err) + + imageRef := "" // FakeDockerClient doesn't populate ImageRef yet. + // Prepend to the expected list because ListContainers returns + // the most recent containers first. + expected = append([]*runtimeApi.Container{{ + Metadata: configs[i].Metadata, + Id: &id, + State: &state, + Image: configs[i].Image, + ImageRef: &imageRef, + Labels: map[string]string{containerTypeLabelKey: containerTypeLabelContainer}, + }}, expected...) + } + containers, err := ds.ListContainers(nil) + assert.NoError(t, err) + assert.Len(t, containers, len(expected)) + assert.Equal(t, expected, containers) +} + +// TestContainerStatus tests the basic lifecycle operations and verify that +// the status returned reflects the operations performed. +func TestContainerStatus(t *testing.T) { + ds, _, fClock := newTestDockerSevice() + sConfig := makeSandboxConfig("foo", "bar", "1", 0) + config := makeContainerConfig(sConfig, "pause", "iamimage", 0) + + var defaultTime time.Time + dt := defaultTime.Unix() + ct, st, ft := dt, dt, dt + state := runtimeApi.ContainerState_CREATED + // The following variables are not set in FakeDockerClient. + imageRef := "" + exitCode := int32(0) + reason := "" + + expected := &runtimeApi.ContainerStatus{ + State: &state, + CreatedAt: &ct, + StartedAt: &st, + FinishedAt: &ft, + Metadata: config.Metadata, + Image: config.Image, + ImageRef: &imageRef, + ExitCode: &exitCode, + Reason: &reason, + Mounts: []*runtimeApi.Mount{}, + Labels: map[string]string{containerTypeLabelKey: containerTypeLabelContainer}, + } + + // Create the container. + fClock.SetTime(time.Now()) + *expected.CreatedAt = fClock.Now().Unix() + id, err := ds.CreateContainer("sandboxid", config, sConfig) + // Set the id manually since we don't know the id until it's created. + expected.Id = &id + assert.NoError(t, err) + status, err := ds.ContainerStatus(id) + assert.NoError(t, err) + assert.Equal(t, expected, status) + + // Advance the clock and start the container. + fClock.SetTime(time.Now()) + *expected.StartedAt = fClock.Now().Unix() + *expected.State = runtimeApi.ContainerState_RUNNING + + err = ds.StartContainer(id) + assert.NoError(t, err) + status, err = ds.ContainerStatus(id) + assert.Equal(t, expected, status) + + // Advance the clock and stop the container. + fClock.SetTime(time.Now()) + *expected.FinishedAt = fClock.Now().Unix() + *expected.State = runtimeApi.ContainerState_EXITED + *expected.Reason = "Completed" + + err = ds.StopContainer(id, 0) + assert.NoError(t, err) + status, err = ds.ContainerStatus(id) + assert.Equal(t, expected, status) + + // Remove the container. + err = ds.RemoveContainer(id) + assert.NoError(t, err) + status, err = ds.ContainerStatus(id) + assert.Error(t, err, fmt.Sprintf("status of container: %+v", status)) +} diff --git a/pkg/kubelet/dockershim/docker_sandbox_test.go b/pkg/kubelet/dockershim/docker_sandbox_test.go index 72df1a38efc..15f488e4ea5 100644 --- a/pkg/kubelet/dockershim/docker_sandbox_test.go +++ b/pkg/kubelet/dockershim/docker_sandbox_test.go @@ -18,9 +18,10 @@ package dockershim import ( "fmt" + "os" "testing" + "time" - dockertypes "github.com/docker/engine-api/types" "github.com/stretchr/testify/assert" runtimeApi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime" @@ -38,27 +39,10 @@ func makeSandboxConfig(name, namespace, uid string, attempt uint32) *runtimeApi. } } -// TestRunSandbox tests that RunSandbox creates and starts a container -// acting a the sandbox for the pod. -func TestRunSandbox(t *testing.T) { - ds, fakeDocker := newTestDockerSevice() - config := makeSandboxConfig("foo", "bar", "1", 0) - id, err := ds.RunPodSandbox(config) - assert.NoError(t, err) - assert.NoError(t, fakeDocker.AssertStarted([]string{id})) - - // List running containers and verify that there is one (and only one) - // running container that we just created. - containers, err := fakeDocker.ListContainers(dockertypes.ContainerListOptions{All: false}) - assert.NoError(t, err) - assert.Len(t, containers, 1) - assert.Equal(t, id, containers[0].ID) -} - // TestListSandboxes creates several sandboxes and then list them to check // whether the correct metadatas, states, and labels are returned. func TestListSandboxes(t *testing.T) { - ds, _ := newTestDockerSevice() + ds, _, _ := newTestDockerSevice() name, namespace := "foo", "bar" configs := []*runtimeApi.PodSandboxConfig{} for i := 0; i < 3; i++ { @@ -88,3 +72,48 @@ func TestListSandboxes(t *testing.T) { assert.Len(t, sandboxes, len(expected)) assert.Equal(t, expected, sandboxes) } + +// TestSandboxStatus tests the basic lifecycle operations and verify that +// the status returned reflects the operations performed. +func TestSandboxStatus(t *testing.T) { + ds, _, fClock := newTestDockerSevice() + config := makeSandboxConfig("foo", "bar", "1", 0) + + // TODO: The following variables depend on the internal + // implementation of FakeDockerClient, and should be fixed. + fakeIP := "2.3.4.5" + fakeNS := fmt.Sprintf("/proc/%d/ns/net", os.Getpid()) + + state := runtimeApi.PodSandBoxState_READY + ct := int64(0) + expected := &runtimeApi.PodSandboxStatus{ + State: &state, + CreatedAt: &ct, + Metadata: config.Metadata, + Labels: map[string]string{containerTypeLabelKey: containerTypeLabelSandbox}, + Network: &runtimeApi.PodSandboxNetworkStatus{Ip: &fakeIP}, + Linux: &runtimeApi.LinuxPodSandboxStatus{Namespaces: &runtimeApi.Namespace{Network: &fakeNS}}, + } + + // Create the sandbox. + fClock.SetTime(time.Now()) + *expected.CreatedAt = fClock.Now().Unix() + id, err := ds.RunPodSandbox(config) + expected.Id = &id // ID is only known after the creation. + status, err := ds.PodSandboxStatus(id) + assert.NoError(t, err) + assert.Equal(t, expected, status) + + // Stop the sandbox. + *expected.State = runtimeApi.PodSandBoxState_NOTREADY + err = ds.StopPodSandbox(id) + assert.NoError(t, err) + status, err = ds.PodSandboxStatus(id) + assert.Equal(t, expected, status) + + // Remove the container. + err = ds.RemovePodSandbox(id) + assert.NoError(t, err) + status, err = ds.PodSandboxStatus(id) + assert.Error(t, err, fmt.Sprintf("status of sandbox: %+v", status)) +} diff --git a/pkg/kubelet/dockershim/docker_service_test.go b/pkg/kubelet/dockershim/docker_service_test.go index e1890851710..92032de5dc8 100644 --- a/pkg/kubelet/dockershim/docker_service_test.go +++ b/pkg/kubelet/dockershim/docker_service_test.go @@ -17,10 +17,14 @@ limitations under the License. package dockershim import ( + "time" + "k8s.io/kubernetes/pkg/kubelet/dockertools" + "k8s.io/kubernetes/pkg/util/clock" ) -func newTestDockerSevice() (*dockerService, *dockertools.FakeDockerClient) { +func newTestDockerSevice() (*dockerService, *dockertools.FakeDockerClient, *clock.FakeClock) { c := dockertools.NewFakeDockerClient() - return &dockerService{client: c}, c + fakeClock := clock.NewFakeClock(time.Time{}) + return &dockerService{client: c}, c, fakeClock } diff --git a/pkg/kubelet/dockertools/fake_docker_client.go b/pkg/kubelet/dockertools/fake_docker_client.go index b0d5ce88987..85bea450b0c 100644 --- a/pkg/kubelet/dockertools/fake_docker_client.go +++ b/pkg/kubelet/dockertools/fake_docker_client.go @@ -28,6 +28,7 @@ import ( dockertypes "github.com/docker/engine-api/types" dockercontainer "github.com/docker/engine-api/types/container" + "k8s.io/kubernetes/pkg/util/clock" "k8s.io/kubernetes/pkg/api" ) @@ -40,6 +41,7 @@ type calledDetail struct { // FakeDockerClient is a simple fake docker client, so that kubelet can be run for testing without requiring a real docker setup. type FakeDockerClient struct { sync.Mutex + Clock clock.Clock RunningContainerList []dockertypes.Container ExitedContainerList []dockertypes.Container ContainerMap map[string]*dockertypes.ContainerJSON @@ -70,11 +72,20 @@ func NewFakeDockerClient() *FakeDockerClient { return NewFakeDockerClientWithVersion(fakeDockerVersion, minimumDockerAPIVersion) } +func NewFakeDockerClientWithClock(c clock.Clock) *FakeDockerClient { + return newClientWithVersionAndClock(fakeDockerVersion, minimumDockerAPIVersion, c) +} + func NewFakeDockerClientWithVersion(version, apiVersion string) *FakeDockerClient { + return newClientWithVersionAndClock(version, apiVersion, clock.RealClock{}) +} + +func newClientWithVersionAndClock(version, apiVersion string, c clock.Clock) *FakeDockerClient { return &FakeDockerClient{ VersionInfo: dockertypes.Version{Version: version, APIVersion: apiVersion}, Errors: make(map[string]error), ContainerMap: make(map[string]*dockertypes.ContainerJSON), + Clock: c, } } @@ -292,7 +303,11 @@ func (f *FakeDockerClient) InspectContainer(id string) (*dockertypes.ContainerJS if container, ok := f.ContainerMap[id]; ok { return container, err } - return nil, err + if err != nil { + // Use the custom error if it exists. + return nil, err + } + return nil, fmt.Errorf("container %q not found", id) } // InspectImage is a test-spy implementation of DockerInterface.InspectImage. @@ -337,7 +352,8 @@ func (f *FakeDockerClient) CreateContainer(c dockertypes.ContainerCreateConfig) f.RunningContainerList = append([]dockertypes.Container{ {ID: name, Names: []string{name}, Image: c.Config.Image, Labels: c.Config.Labels}, }, f.RunningContainerList...) - f.ContainerMap[name] = convertFakeContainer(&FakeContainer{ID: id, Name: name, Config: c.Config, HostConfig: c.HostConfig}) + f.ContainerMap[name] = convertFakeContainer(&FakeContainer{ + ID: id, Name: name, Config: c.Config, HostConfig: c.HostConfig, CreatedAt: f.Clock.Now()}) f.normalSleep(100, 25, 25) return &dockertypes.ContainerCreateResponse{ID: id}, nil } @@ -358,7 +374,7 @@ func (f *FakeDockerClient) StartContainer(id string) error { } container.State.Running = true container.State.Pid = os.Getpid() - container.State.StartedAt = dockerTimestampToString(time.Now()) + container.State.StartedAt = dockerTimestampToString(f.Clock.Now()) container.NetworkSettings.IPAddress = "2.3.4.5" f.ContainerMap[id] = container f.updateContainerStatus(id, statusRunningPrefix) @@ -398,7 +414,7 @@ func (f *FakeDockerClient) StopContainer(id string, timeout int) error { FinishedAt: time.Now(), }) } else { - container.State.FinishedAt = dockerTimestampToString(time.Now()) + container.State.FinishedAt = dockerTimestampToString(f.Clock.Now()) container.State.Running = false } f.ContainerMap[id] = container