From 626680d289e3aae4937152b0775aa84705b4507c Mon Sep 17 00:00:00 2001 From: Random-Liu Date: Mon, 30 Jan 2017 18:57:29 -0800 Subject: [PATCH] Add unit test for legacy container cleanup --- pkg/kubelet/dockershim/docker_legacy_test.go | 266 ++++++++++++++++++ pkg/kubelet/dockershim/docker_service_test.go | 3 +- pkg/kubelet/dockertools/fake_docker_client.go | 29 +- 3 files changed, 296 insertions(+), 2 deletions(-) create mode 100644 pkg/kubelet/dockershim/docker_legacy_test.go diff --git a/pkg/kubelet/dockershim/docker_legacy_test.go b/pkg/kubelet/dockershim/docker_legacy_test.go new file mode 100644 index 00000000000..d0efb1da5ad --- /dev/null +++ b/pkg/kubelet/dockershim/docker_legacy_test.go @@ -0,0 +1,266 @@ +/* +Copyright 2017 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 ( + "testing" + + dockercontainer "github.com/docker/engine-api/types/container" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "k8s.io/kubernetes/pkg/kubelet/dockertools" + "k8s.io/kubernetes/pkg/kubelet/types" +) + +func TestConvertLegacyNameAndLabels(t *testing.T) { + for desc, test := range map[string]struct { + names []string + labels map[string]string + expectNames []string + expectLabels map[string]string + expectError bool + }{ + + "legacy infra container": { + names: []string{"k8s_POD.hash1_podname_podnamespace_poduid_randomid"}, + labels: map[string]string{ + types.KubernetesPodNameLabel: "podname", + types.KubernetesPodNamespaceLabel: "podnamespace", + types.KubernetesPodUIDLabel: "poduid", + types.KubernetesContainerNameLabel: "POD", + containerHashLabel: "hash", + containerRestartCountLabel: "0", + }, + expectNames: []string{"k8s_POD_podname_podnamespace_poduid_0"}, + expectLabels: map[string]string{ + types.KubernetesPodNameLabel: "podname", + types.KubernetesPodNamespaceLabel: "podnamespace", + types.KubernetesPodUIDLabel: "poduid", + types.KubernetesContainerNameLabel: "POD", + annotationPrefix + containerHashLabel: "hash", + annotationPrefix + containerRestartCountLabel: "0", + containerTypeLabelKey: containerTypeLabelSandbox, + }, + }, + "legacy application container": { + names: []string{"k8s_containername.hash_podname_podnamespace_poduid_randomid"}, + labels: map[string]string{ + types.KubernetesPodNameLabel: "podname", + types.KubernetesPodNamespaceLabel: "podnamespace", + types.KubernetesPodUIDLabel: "poduid", + types.KubernetesContainerNameLabel: "containername", + containerHashLabel: "hash", + containerRestartCountLabel: "5", + containerTerminationMessagePathLabel: "terminationmessagepath", + containerTerminationMessagePolicyLabel: "terminationmessagepolicy", + containerPreStopHandlerLabel: "prestophandler", + containerPortsLabel: "ports", + }, + expectNames: []string{"k8s_containername_podname_podnamespace_poduid_5"}, + expectLabels: map[string]string{ + types.KubernetesPodNameLabel: "podname", + types.KubernetesPodNamespaceLabel: "podnamespace", + types.KubernetesPodUIDLabel: "poduid", + types.KubernetesContainerNameLabel: "containername", + annotationPrefix + containerHashLabel: "hash", + annotationPrefix + containerRestartCountLabel: "5", + annotationPrefix + containerTerminationMessagePathLabel: "terminationmessagepath", + annotationPrefix + containerTerminationMessagePolicyLabel: "terminationmessagepolicy", + annotationPrefix + containerPreStopHandlerLabel: "prestophandler", + annotationPrefix + containerPortsLabel: "ports", + containerTypeLabelKey: containerTypeLabelContainer, + }, + expectError: false, + }, + "invalid sandbox name": { + names: []string{"POD_podname_podnamespace_poduid_0"}, + expectError: true, + }, + "invalid dockershim container": { + names: []string{"containername_podname_podnamespace_poduid_5"}, + expectError: true, + }, + } { + t.Logf("TestCase %q", desc) + names, labels, err := convertLegacyNameAndLabels(test.names, test.labels) + require.Equal(t, test.expectError, err != nil) + assert.Equal(t, test.expectNames, names) + assert.Equal(t, test.expectLabels, labels) + } +} + +// getFakeLegacyContainers returns a list of fake legacy containers. +func getFakeLegacyContainers() []*dockertools.FakeContainer { + return []*dockertools.FakeContainer{ + { + ID: "12", + Name: "k8s_POD.hash1_podname_podnamespace_poduid_randomid", + Config: &dockercontainer.Config{ + Labels: map[string]string{ + types.KubernetesPodNameLabel: "podname", + types.KubernetesPodNamespaceLabel: "podnamespace", + types.KubernetesPodUIDLabel: "poduid", + types.KubernetesContainerNameLabel: "POD", + containerHashLabel: "hash1", + containerRestartCountLabel: "0", + }, + }, + }, + { + ID: "34", + Name: "k8s_legacycontainer.hash2_podname_podnamespace_poduid_randomid", + Config: &dockercontainer.Config{ + Labels: map[string]string{ + types.KubernetesPodNameLabel: "podname", + types.KubernetesPodNamespaceLabel: "podnamespace", + types.KubernetesPodUIDLabel: "poduid", + types.KubernetesContainerNameLabel: "legacyContainer", + containerHashLabel: "hash2", + containerRestartCountLabel: "5", + }, + }, + }, + } +} + +// getFakeNewContainers returns a list of fake new containers. +func getFakeNewContainers() []*dockertools.FakeContainer { + return []*dockertools.FakeContainer{ + { + ID: "56", + Name: "k8s_POD_podname_podnamespace_poduid_0", + Config: &dockercontainer.Config{ + Labels: map[string]string{ + types.KubernetesPodNameLabel: "podname", + types.KubernetesPodNamespaceLabel: "podnamespace", + types.KubernetesPodUIDLabel: "poduid", + types.KubernetesContainerNameLabel: "POD", + containerTypeLabelKey: containerTypeLabelSandbox, + }, + }, + }, + { + ID: "78", + Name: "k8s_newcontainer_podname_podnamespace_poduid_3", + Config: &dockercontainer.Config{ + Labels: map[string]string{ + types.KubernetesPodNameLabel: "podname", + types.KubernetesPodNamespaceLabel: "podnamespace", + types.KubernetesPodUIDLabel: "poduid", + types.KubernetesContainerNameLabel: "newcontainer", + annotationPrefix + containerHashLabel: "hash4", + annotationPrefix + containerRestartCountLabel: "3", + containerTypeLabelKey: containerTypeLabelContainer, + }, + }, + }, + } + +} + +func TestListLegacyContainers(t *testing.T) { + ds, fDocker, _ := newTestDockerService() + newContainers := getFakeLegacyContainers() + legacyContainers := getFakeNewContainers() + fDocker.SetFakeContainers(append(newContainers, legacyContainers...)) + + // ListContainers should list only new containers when legacyCleanup is done. + containers, err := ds.ListContainers(nil) + assert.NoError(t, err) + require.Len(t, containers, 1) + assert.Equal(t, "78", containers[0].Id) + + // ListLegacyContainers should list only legacy containers. + containers, err = ds.ListLegacyContainers(nil) + assert.NoError(t, err) + require.Len(t, containers, 1) + assert.Equal(t, "34", containers[0].Id) + + // Mark legacyCleanup as not done. + ds.legacyCleanup.done = 0 + + // ListContainers should list all containers when legacyCleanup is not done. + containers, err = ds.ListContainers(nil) + assert.NoError(t, err) + require.Len(t, containers, 2) + assert.Contains(t, []string{containers[0].Id, containers[1].Id}, "34") + assert.Contains(t, []string{containers[0].Id, containers[1].Id}, "78") +} + +func TestListLegacyPodSandbox(t *testing.T) { + ds, fDocker, _ := newTestDockerService() + newContainers := getFakeLegacyContainers() + legacyContainers := getFakeNewContainers() + fDocker.SetFakeContainers(append(newContainers, legacyContainers...)) + + // ListPodSandbox should list only new sandboxes when legacyCleanup is done. + sandboxes, err := ds.ListPodSandbox(nil) + assert.NoError(t, err) + require.Len(t, sandboxes, 1) + assert.Equal(t, "56", sandboxes[0].Id) + + // ListLegacyPodSandbox should list only legacy sandboxes. + sandboxes, err = ds.ListLegacyPodSandbox(nil) + assert.NoError(t, err) + require.Len(t, sandboxes, 1) + assert.Equal(t, "12", sandboxes[0].Id) + + // Mark legacyCleanup as not done. + ds.legacyCleanup.done = 0 + + // ListPodSandbox should list all sandboxes when legacyCleanup is not done. + sandboxes, err = ds.ListPodSandbox(nil) + assert.NoError(t, err) + require.Len(t, sandboxes, 2) + assert.Contains(t, []string{sandboxes[0].Id, sandboxes[1].Id}, "12") + assert.Contains(t, []string{sandboxes[0].Id, sandboxes[1].Id}, "56") +} + +func TestCheckLegacyCleanup(t *testing.T) { + for desc, test := range map[string]struct { + containers []*dockertools.FakeContainer + done bool + }{ + "no containers": { + containers: []*dockertools.FakeContainer{}, + done: true, + }, + "only new containers": { + containers: getFakeNewContainers(), + done: true, + }, + "only legacy containers": { + containers: getFakeLegacyContainers(), + done: false, + }, + "both legacy and new containers": { + containers: append(getFakeNewContainers(), getFakeLegacyContainers()...), + done: false, + }, + } { + t.Logf("TestCase %q", desc) + ds, fDocker, _ := newTestDockerService() + fDocker.SetFakeContainers(test.containers) + ds.legacyCleanup.done = 0 + + clean, err := ds.checkLegacyCleanup() + assert.NoError(t, err) + assert.Equal(t, test.done, clean) + assert.Equal(t, test.done, ds.legacyCleanup.Done()) + } +} diff --git a/pkg/kubelet/dockershim/docker_service_test.go b/pkg/kubelet/dockershim/docker_service_test.go index 8255a5a16eb..60aae23cf8f 100644 --- a/pkg/kubelet/dockershim/docker_service_test.go +++ b/pkg/kubelet/dockershim/docker_service_test.go @@ -41,7 +41,8 @@ func newTestNetworkPlugin(t *testing.T) *mock_network.MockNetworkPlugin { func newTestDockerService() (*dockerService, *dockertools.FakeDockerClient, *clock.FakeClock) { fakeClock := clock.NewFakeClock(time.Time{}) c := dockertools.NewFakeDockerClient().WithClock(fakeClock) - return &dockerService{client: c, os: &containertest.FakeOS{}, networkPlugin: &network.NoopNetworkPlugin{}, checkpointHandler: NewTestPersistentCheckpointHandler()}, c, fakeClock + return &dockerService{client: c, os: &containertest.FakeOS{}, networkPlugin: &network.NoopNetworkPlugin{}, + legacyCleanup: legacyCleanupFlag{done: 1}, checkpointHandler: NewTestPersistentCheckpointHandler()}, c, fakeClock } // TestStatus tests the runtime status logic. diff --git a/pkg/kubelet/dockertools/fake_docker_client.go b/pkg/kubelet/dockertools/fake_docker_client.go index b94d9acf21a..ee97828f235 100644 --- a/pkg/kubelet/dockertools/fake_docker_client.go +++ b/pkg/kubelet/dockertools/fake_docker_client.go @@ -23,6 +23,7 @@ import ( "os" "reflect" "sort" + "strings" "sync" "time" @@ -232,6 +233,9 @@ func (f *FakeDockerClient) SetFakeContainers(containers []*FakeContainer) { Names: []string{c.Name}, ID: c.ID, } + if c.Config != nil { + container.Labels = c.Config.Labels + } if c.Running { f.RunningContainerList = append(f.RunningContainerList, container) } else { @@ -349,7 +353,30 @@ func (f *FakeDockerClient) ListContainers(options dockertypes.ContainerListOptio // TODO(random-liu): Is a fully sorted array needed? containerList = append(containerList, f.ExitedContainerList...) } - return containerList, err + // TODO: Support other filters. + // Filter containers with label filter. + labelFilters := options.Filter.Get("label") + if len(labelFilters) == 0 { + return containerList, err + } + var filtered []dockertypes.Container + for _, container := range containerList { + match := true + for _, labelFilter := range labelFilters { + kv := strings.Split(labelFilter, "=") + if len(kv) != 2 { + return nil, fmt.Errorf("invalid label filter %q", labelFilter) + } + if container.Labels[kv[0]] != kv[1] { + match = false + break + } + } + if match { + filtered = append(filtered, container) + } + } + return filtered, err } // InspectContainer is a test-spy implementation of DockerInterface.InspectContainer.