func TestGetPodStatus(t *testing.T) { fr := newFakeRktInterface() fs := newFakeSystemd() fos := &containertesting.FakeOS{} frh := &fakeRuntimeHelper{} r := &Runtime{ apisvc: fr, systemd: fs, runtimeHelper: frh, os: fos, } ns := func(seconds int64) int64 { return seconds * 1e9 } tests := []struct { pods []*rktapi.Pod result *kubecontainer.PodStatus }{ // No pods. { nil, &kubecontainer.PodStatus{ID: "42", Name: "guestbook", Namespace: "default"}, }, // One pod. { []*rktapi.Pod{ makeRktPod(rktapi.PodState_POD_STATE_RUNNING, "uuid-4002", "42", "guestbook", "default", "10.10.10.42", ns(10), ns(20), "7", []string{"app-1", "app-2"}, []string{"img-id-1", "img-id-2"}, []string{"img-name-1", "img-name-2"}, []string{"1001", "1002"}, []rktapi.AppState{rktapi.AppState_APP_STATE_RUNNING, rktapi.AppState_APP_STATE_EXITED}, []int32{0, 0}, ), }, &kubecontainer.PodStatus{ ID: "42", Name: "guestbook", Namespace: "default", IP: "10.10.10.42", ContainerStatuses: []*kubecontainer.ContainerStatus{ { ID: kubecontainer.BuildContainerID("rkt", "uuid-4002:app-1"), Name: "app-1", State: kubecontainer.ContainerStateRunning, CreatedAt: time.Unix(10, 0), StartedAt: time.Unix(20, 0), FinishedAt: time.Unix(0, 30), Image: "img-name-1:latest", ImageID: "rkt://img-id-1", Hash: 1001, RestartCount: 7, }, { ID: kubecontainer.BuildContainerID("rkt", "uuid-4002:app-2"), Name: "app-2", State: kubecontainer.ContainerStateExited, CreatedAt: time.Unix(10, 0), StartedAt: time.Unix(20, 0), FinishedAt: time.Unix(0, 30), Image: "img-name-2:latest", ImageID: "rkt://img-id-2", Hash: 1002, RestartCount: 7, Reason: "Completed", }, }, }, }, // Multiple pods. { []*rktapi.Pod{ makeRktPod(rktapi.PodState_POD_STATE_EXITED, "uuid-4002", "42", "guestbook", "default", "10.10.10.42", ns(10), ns(20), "7", []string{"app-1", "app-2"}, []string{"img-id-1", "img-id-2"}, []string{"img-name-1", "img-name-2"}, []string{"1001", "1002"}, []rktapi.AppState{rktapi.AppState_APP_STATE_RUNNING, rktapi.AppState_APP_STATE_EXITED}, []int32{0, 0}, ), makeRktPod(rktapi.PodState_POD_STATE_RUNNING, // The latest pod is running. "uuid-4003", "42", "guestbook", "default", "10.10.10.42", ns(10), ns(20), "10", []string{"app-1", "app-2"}, []string{"img-id-1", "img-id-2"}, []string{"img-name-1", "img-name-2"}, []string{"1001", "1002"}, []rktapi.AppState{rktapi.AppState_APP_STATE_RUNNING, rktapi.AppState_APP_STATE_EXITED}, []int32{0, 1}, ), }, &kubecontainer.PodStatus{ ID: "42", Name: "guestbook", Namespace: "default", IP: "10.10.10.42", // Result should contain all containers. ContainerStatuses: []*kubecontainer.ContainerStatus{ { ID: kubecontainer.BuildContainerID("rkt", "uuid-4002:app-1"), Name: "app-1", State: kubecontainer.ContainerStateRunning, CreatedAt: time.Unix(10, 0), StartedAt: time.Unix(20, 0), FinishedAt: time.Unix(0, 30), Image: "img-name-1:latest", ImageID: "rkt://img-id-1", Hash: 1001, RestartCount: 7, }, { ID: kubecontainer.BuildContainerID("rkt", "uuid-4002:app-2"), Name: "app-2", State: kubecontainer.ContainerStateExited, CreatedAt: time.Unix(10, 0), StartedAt: time.Unix(20, 0), FinishedAt: time.Unix(0, 30), Image: "img-name-2:latest", ImageID: "rkt://img-id-2", Hash: 1002, RestartCount: 7, Reason: "Completed", }, { ID: kubecontainer.BuildContainerID("rkt", "uuid-4003:app-1"), Name: "app-1", State: kubecontainer.ContainerStateRunning, CreatedAt: time.Unix(10, 0), StartedAt: time.Unix(20, 0), FinishedAt: time.Unix(0, 30), Image: "img-name-1:latest", ImageID: "rkt://img-id-1", Hash: 1001, RestartCount: 10, }, { ID: kubecontainer.BuildContainerID("rkt", "uuid-4003:app-2"), Name: "app-2", State: kubecontainer.ContainerStateExited, CreatedAt: time.Unix(10, 0), StartedAt: time.Unix(20, 0), FinishedAt: time.Unix(0, 30), Image: "img-name-2:latest", ImageID: "rkt://img-id-2", Hash: 1002, RestartCount: 10, ExitCode: 1, Reason: "Error", }, }, }, }, } ctrl := gomock.NewController(t) defer ctrl.Finish() for i, tt := range tests { testCaseHint := fmt.Sprintf("test case #%d", i) fr.pods = tt.pods podTimes := map[string]time.Time{} for _, pod := range tt.pods { podTimes[podFinishedMarkerPath(r.runtimeHelper.GetPodDir(tt.result.ID), pod.Id)] = tt.result.ContainerStatuses[0].FinishedAt } r.os.(*containertesting.FakeOS).StatFn = func(name string) (os.FileInfo, error) { podTime, ok := podTimes[name] if !ok { t.Errorf("osStat called with %v, but only knew about %#v", name, podTimes) } mockFI := mock_os.NewMockFileInfo(ctrl) mockFI.EXPECT().ModTime().Return(podTime) return mockFI, nil } status, err := r.GetPodStatus("42", "guestbook", "default") if err != nil { t.Errorf("test case #%d: unexpected error: %v", i, err) } assert.Equal(t, tt.result, status, testCaseHint) assert.Equal(t, []string{"ListPods"}, fr.called, testCaseHint) fr.CleanCalls() } }
func TestGarbageCollect(t *testing.T) { fr := newFakeRktInterface() fs := newFakeSystemd() cli := newFakeRktCli() fakeOS := kubetesting.NewFakeOS() getter := newFakePodGetter() rkt := &Runtime{ os: fakeOS, cli: cli, apisvc: fr, podGetter: getter, systemd: fs, containerRefManager: kubecontainer.NewRefManager(), } fakeApp := &rktapi.App{Name: "app-foo"} tests := []struct { gcPolicy kubecontainer.ContainerGCPolicy apiPods []*api.Pod pods []*rktapi.Pod serviceFilesOnDisk []string expectedCommands []string expectedServiceFiles []string }{ // All running pods, should not be gc'd. // Dead, new pods should not be gc'd. // Dead, old pods should be gc'd. // Deleted pods should be gc'd. // Service files without corresponded pods should be removed. { kubecontainer.ContainerGCPolicy{ MinAge: 0, MaxContainers: 0, }, []*api.Pod{ {ObjectMeta: api.ObjectMeta{UID: "pod-uid-1"}}, {ObjectMeta: api.ObjectMeta{UID: "pod-uid-2"}}, {ObjectMeta: api.ObjectMeta{UID: "pod-uid-3"}}, {ObjectMeta: api.ObjectMeta{UID: "pod-uid-4"}}, }, []*rktapi.Pod{ { Id: "deleted-foo", State: rktapi.PodState_POD_STATE_EXITED, CreatedAt: time.Now().Add(time.Hour).UnixNano(), StartedAt: time.Now().Add(time.Hour).UnixNano(), Apps: []*rktapi.App{fakeApp}, Annotations: []*rktapi.KeyValue{ { Key: k8sRktUIDAnno, Value: "pod-uid-0", }, }, }, { Id: "running-foo", State: rktapi.PodState_POD_STATE_RUNNING, CreatedAt: 0, StartedAt: 0, Apps: []*rktapi.App{fakeApp}, Annotations: []*rktapi.KeyValue{ { Key: k8sRktUIDAnno, Value: "pod-uid-1", }, }, }, { Id: "running-bar", State: rktapi.PodState_POD_STATE_RUNNING, CreatedAt: 0, StartedAt: 0, Apps: []*rktapi.App{fakeApp}, Annotations: []*rktapi.KeyValue{ { Key: k8sRktUIDAnno, Value: "pod-uid-2", }, }, }, { Id: "dead-old", State: rktapi.PodState_POD_STATE_EXITED, CreatedAt: 0, StartedAt: 0, Apps: []*rktapi.App{fakeApp}, Annotations: []*rktapi.KeyValue{ { Key: k8sRktUIDAnno, Value: "pod-uid-3", }, }, }, { Id: "dead-new", State: rktapi.PodState_POD_STATE_EXITED, CreatedAt: time.Now().Add(time.Hour).UnixNano(), StartedAt: time.Now().Add(time.Hour).UnixNano(), Apps: []*rktapi.App{fakeApp}, Annotations: []*rktapi.KeyValue{ { Key: k8sRktUIDAnno, Value: "pod-uid-4", }, }, }, }, []string{"k8s_dead-old.service", "k8s_deleted-foo.service", "k8s_non-existing-bar.service"}, []string{"rkt rm dead-old", "rkt rm deleted-foo"}, []string{"/run/systemd/system/k8s_dead-old.service", "/run/systemd/system/k8s_deleted-foo.service", "/run/systemd/system/k8s_non-existing-bar.service"}, }, // gcPolicy.MaxContainers should be enforced. // Oldest ones are removed first. { kubecontainer.ContainerGCPolicy{ MinAge: 0, MaxContainers: 1, }, []*api.Pod{ {ObjectMeta: api.ObjectMeta{UID: "pod-uid-0"}}, {ObjectMeta: api.ObjectMeta{UID: "pod-uid-1"}}, {ObjectMeta: api.ObjectMeta{UID: "pod-uid-2"}}, }, []*rktapi.Pod{ { Id: "dead-2", State: rktapi.PodState_POD_STATE_EXITED, CreatedAt: 2, StartedAt: 2, Apps: []*rktapi.App{fakeApp}, Annotations: []*rktapi.KeyValue{ { Key: k8sRktUIDAnno, Value: "pod-uid-2", }, }, }, { Id: "dead-1", State: rktapi.PodState_POD_STATE_EXITED, CreatedAt: 1, StartedAt: 1, Apps: []*rktapi.App{fakeApp}, Annotations: []*rktapi.KeyValue{ { Key: k8sRktUIDAnno, Value: "pod-uid-1", }, }, }, { Id: "dead-0", State: rktapi.PodState_POD_STATE_EXITED, CreatedAt: 0, StartedAt: 0, Apps: []*rktapi.App{fakeApp}, Annotations: []*rktapi.KeyValue{ { Key: k8sRktUIDAnno, Value: "pod-uid-0", }, }, }, }, []string{"k8s_dead-0.service", "k8s_dead-1.service", "k8s_dead-2.service"}, []string{"rkt rm dead-0", "rkt rm dead-1"}, []string{"/run/systemd/system/k8s_dead-0.service", "/run/systemd/system/k8s_dead-1.service"}, }, } for i, tt := range tests { testCaseHint := fmt.Sprintf("test case #%d", i) ctrl := gomock.NewController(t) fakeOS.ReadDirFn = func(dirname string) ([]os.FileInfo, error) { serviceFileNames := tt.serviceFilesOnDisk var fileInfos []os.FileInfo for _, name := range serviceFileNames { mockFI := mock_os.NewMockFileInfo(ctrl) mockFI.EXPECT().Name().Return(name) fileInfos = append(fileInfos, mockFI) } return fileInfos, nil } fr.pods = tt.pods for _, p := range tt.apiPods { getter.pods[p.UID] = p } err := rkt.GarbageCollect(tt.gcPolicy) assert.NoError(t, err, testCaseHint) sort.Sort(sortedStringList(tt.expectedCommands)) sort.Sort(sortedStringList(cli.cmds)) assert.Equal(t, tt.expectedCommands, cli.cmds, testCaseHint) sort.Sort(sortedStringList(tt.expectedServiceFiles)) sort.Sort(sortedStringList(fakeOS.Removes)) assert.Equal(t, tt.expectedServiceFiles, fakeOS.Removes, testCaseHint) // Cleanup after each test. cli.Reset() ctrl.Finish() fakeOS.Removes = []string{} getter.pods = make(map[types.UID]*api.Pod) } }