From c605fb3ec0a29201a7eba0c76d5f0440c3afd6c9 Mon Sep 17 00:00:00 2001 From: shaofan-hs Date: Fri, 22 Dec 2023 15:55:52 +0800 Subject: [PATCH] optimize ut in podopslifecycle controller --- go.mod | 2 +- .../podopslifecycle_controller.go | 21 +- .../podopslifecycle_controller_test.go | 327 ++++++++++++++---- pkg/controllers/podopslifecycle/predicate.go | 2 +- pkg/controllers/podopslifecycle/utils_test.go | 170 +++++++++ pkg/controllers/utils/pod_utils.go | 2 + 6 files changed, 455 insertions(+), 69 deletions(-) create mode 100644 pkg/controllers/podopslifecycle/utils_test.go diff --git a/go.mod b/go.mod index d443f3ff..6cd65506 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module kusionstack.io/operating go 1.19 require ( - github.com/davecgh/go-spew v1.1.1 github.com/docker/distribution v2.8.2+incompatible github.com/go-logr/logr v1.2.4 github.com/google/uuid v1.3.0 @@ -50,6 +49,7 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/clbanning/mxj/v2 v2.5.5 // indirect github.com/cyphar/filepath-securejoin v0.2.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/evanphx/json-patch v4.11.0+incompatible // indirect github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect diff --git a/pkg/controllers/podopslifecycle/podopslifecycle_controller.go b/pkg/controllers/podopslifecycle/podopslifecycle_controller.go index dfcdcd3d..1031f092 100644 --- a/pkg/controllers/podopslifecycle/podopslifecycle_controller.go +++ b/pkg/controllers/podopslifecycle/podopslifecycle_controller.go @@ -125,6 +125,8 @@ func (r *ReconcilePodOpsLifecycle) Reconcile(ctx context.Context, request reconc if err != nil { return reconcile.Result{}, err } + + // If all lifecycle is finished, or the is no lifecycle begined if len(idToLabelsMap) == 0 { updated, err := r.addServiceAvailable(pod) if updated { @@ -139,6 +141,7 @@ func (r *ReconcilePodOpsLifecycle) Reconcile(ctx context.Context, request reconc } } + // Get the state of pod managed by TransitionRule state, err := r.podTransitionRuleManager.GetState(ctx, r.Client, pod) if err != nil { logger.Error(err, "failed to get pod state") @@ -164,8 +167,8 @@ func (r *ReconcilePodOpsLifecycle) Reconcile(ctx context.Context, request reconc } expected := map[string]bool{ - v1alpha1.PodPreparingLabelPrefix: false, // set readiness gate to false, traffic off - v1alpha1.PodCompletingLabelPrefix: true, // set readiness gate to true, traffic on + v1alpha1.PodPreparingLabelPrefix: false, // Set readiness gate to false + v1alpha1.PodCompletingLabelPrefix: true, // Set readiness gate to true } for _, labels := range idToLabelsMap { for k, v := range expected { @@ -175,7 +178,7 @@ func (r *ReconcilePodOpsLifecycle) Reconcile(ctx context.Context, request reconc updated, err := r.updateServiceReadiness(ctx, pod, v) if err != nil { - return reconcile.Result{}, err // only need set once + return reconcile.Result{}, err // Only need set once } if updated { r.Recorder.Eventf(pod, corev1.EventTypeNormal, "ReadinessGate", "Set service ready readiness gate to %v", v) @@ -186,6 +189,7 @@ func (r *ReconcilePodOpsLifecycle) Reconcile(ctx context.Context, request reconc return reconcile.Result{}, nil } +// addServiceAvailable try to add service available label to pod func (r *ReconcilePodOpsLifecycle) addServiceAvailable(pod *corev1.Pod) (bool, error) { if pod.Labels == nil { return false, nil @@ -194,7 +198,8 @@ func (r *ReconcilePodOpsLifecycle) addServiceAvailable(pod *corev1.Pod) (bool, e return false, nil } - satisfied, notSatisfiedFinalizers, err := controllerutils.IsExpectedFinalizerSatisfied(pod) // whether all expected finalizers are satisfied + // Whether all expected finalizers are satisfied + satisfied, notSatisfiedFinalizers, err := controllerutils.IsExpectedFinalizerSatisfied(pod) if err != nil { return false, err } @@ -207,7 +212,7 @@ func (r *ReconcilePodOpsLifecycle) addServiceAvailable(pod *corev1.Pod) (bool, e if !allDirty { return false, nil } - // all not satisfied expected finalizers are dirty, so actually the pod satisfied expected finalizer now + // All not satisfied finalizers are dirty, so actually the pod satisfied expected finalizers now } if !controllerutils.IsPodReady(pod) { @@ -221,7 +226,7 @@ func (r *ReconcilePodOpsLifecycle) addServiceAvailable(pod *corev1.Pod) (bool, e } func (r *ReconcilePodOpsLifecycle) removeDirtyExpectedFinalizer(pod *corev1.Pod, notSatisfiedFinalizers map[string]string) (bool, error) { - var allDirty bool + var allDirty bool // Whether all not atisfied finalizers are dirty dirtyExpectedFinalizer := make(map[string]string) for expectedFlzKey, finalizer := range notSatisfiedFinalizers { @@ -348,7 +353,7 @@ func (r *ReconcilePodOpsLifecycle) setServiceReadiness(pod *corev1.Pod, isReady if !isReady { status = corev1.ConditionFalse } - if index == -1 { // append readiness gate + if index == -1 { // Append readiness gate pod.Status.Conditions = append(pod.Status.Conditions, corev1.PodCondition{ Type: v1alpha1.ReadinessGatePodServiceReady, Status: status, @@ -362,7 +367,7 @@ func (r *ReconcilePodOpsLifecycle) setServiceReadiness(pod *corev1.Pod, isReady return false, "" } - // update readiness gate + // Update readiness gate pod.Status.Conditions[index].Status = status pod.Status.Conditions[index].LastTransitionTime = metav1.Now() pod.Status.Conditions[index].Message = "updated by PodOpsLifecycle" diff --git a/pkg/controllers/podopslifecycle/podopslifecycle_controller_test.go b/pkg/controllers/podopslifecycle/podopslifecycle_controller_test.go index 6d09327b..47e8cab9 100644 --- a/pkg/controllers/podopslifecycle/podopslifecycle_controller_test.go +++ b/pkg/controllers/podopslifecycle/podopslifecycle_controller_test.go @@ -27,8 +27,10 @@ import ( . "github.com/onsi/gomega" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" logf "sigs.k8s.io/controller-runtime/pkg/log" @@ -88,7 +90,7 @@ var _ = BeforeSuite(func() { }) var _ = AfterSuite(func() { - By("tearing down the test environment") + By("Tearing down the test environment") cancel() @@ -96,7 +98,7 @@ var _ = AfterSuite(func() { Expect(err).NotTo(HaveOccurred()) }) -var _ = Describe("podopslifecycle controller", func() { +var _ = Describe("PodOpsLifecycle controller", func() { var ( podSpec = corev1.PodSpec{ Containers: []corev1.Container{ @@ -111,6 +113,8 @@ var _ = Describe("podopslifecycle controller", func() { }, }, } + name = "test" + namespace = "default" id = "123" timestamp = "1402144848" ) @@ -118,12 +122,11 @@ var _ = Describe("podopslifecycle controller", func() { AfterEach(func() { pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", + Name: name, + Namespace: namespace, }, } - err := mgr.GetClient().Delete(context.Background(), pod) - Expect(err).NotTo(HaveOccurred()) + mgr.GetClient().Delete(context.Background(), pod) for { if len(request) == 0 { @@ -133,11 +136,11 @@ var _ = Describe("podopslifecycle controller", func() { } }) - It("update pod with stage pre-check", func() { + It("Create a pod controlled by kusionstack", func() { pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", + Name: name, + Namespace: namespace, Labels: map[string]string{v1alpha1.ControlledByKusionStackLabelKey: "true"}, }, Spec: podSpec, @@ -147,22 +150,44 @@ var _ = Describe("podopslifecycle controller", func() { <-request - pod = &corev1.Pod{} + // Not found the pod + pod1 := &corev1.Pod{} + name1 := name + "1" err = mgr.GetAPIReader().Get(context.Background(), client.ObjectKey{ - Name: "test", - Namespace: "default", - }, pod) + Name: name1, + Namespace: namespace, + }, pod1) + Expect(errors.IsNotFound(err)).To(Equal(true)) + + _, err = podOpsLifecycle.Reconcile(context.Background(), reconcile.Request{NamespacedName: types.NamespacedName{Name: name1, Namespace: namespace}}) Expect(err).NotTo(HaveOccurred()) - Expect(pod.Status.Conditions).To(HaveLen(1)) - Expect(string(pod.Status.Conditions[0].Type)).To(Equal(v1alpha1.ReadinessGatePodServiceReady)) - Expect(pod.Status.Conditions[0].Status).To(Equal(corev1.ConditionTrue)) + + // Found the pod + Eventually(func() error { + pod = &corev1.Pod{} + err = mgr.GetAPIReader().Get(context.Background(), client.ObjectKey{ + Name: name, + Namespace: namespace, + }, pod) + + if len(pod.Status.Conditions) != 1 { + return fmt.Errorf("expected 1 condition, got %d", len(pod.Status.Conditions)) + } + if string(pod.Status.Conditions[0].Type) != v1alpha1.ReadinessGatePodServiceReady { + return fmt.Errorf("expected type %s, got %s", v1alpha1.ReadinessGatePodServiceReady, pod.Status.Conditions[0].Type) + } + if pod.Status.Conditions[0].Status != corev1.ConditionTrue { + return fmt.Errorf("expected status %s, got %s", corev1.ConditionFalse, pod.Status.Conditions[0].Status) + } + return nil + }, 3*time.Second, 200*time.Millisecond).Should(BeNil()) }) - It("create pod with label prepare", func() { + It("Create pod with label preparing", func() { pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", + Name: name, + Namespace: namespace, Labels: map[string]string{ v1alpha1.ControlledByKusionStackLabelKey: "true", fmt.Sprintf("%s/%s", v1alpha1.PodOperatingLabelPrefix, id): timestamp, @@ -174,36 +199,36 @@ var _ = Describe("podopslifecycle controller", func() { err := mgr.GetClient().Create(context.Background(), pod) Expect(err).NotTo(HaveOccurred()) - pod = &corev1.Pod{} + <-request + Eventually(func() error { + pod := &corev1.Pod{} if err := mgr.GetAPIReader().Get(context.Background(), client.ObjectKey{ - Name: "test", - Namespace: "default", + Name: name, + Namespace: namespace, }, pod); err != nil { return fmt.Errorf("fail to get pod: %s", err) } + // Need set readiness gate to false if len(pod.Status.Conditions) != 1 { return fmt.Errorf("expected 1 condition, got %d", len(pod.Status.Conditions)) } - if string(pod.Status.Conditions[0].Type) != v1alpha1.ReadinessGatePodServiceReady { return fmt.Errorf("expected type %s, got %s", v1alpha1.ReadinessGatePodServiceReady, pod.Status.Conditions[0].Type) } - if pod.Status.Conditions[0].Status != corev1.ConditionFalse { return fmt.Errorf("expected status %s, got %s", corev1.ConditionFalse, pod.Status.Conditions[0].Status) } - return nil - }, 5*time.Second, 1*time.Second).Should(BeNil()) + }, 3*time.Second, 200*time.Millisecond).Should(BeNil()) }) - It("create pod with label complete", func() { + It("Create pod with label completing", func() { pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", + Name: name, + Namespace: namespace, Labels: map[string]string{ v1alpha1.ControlledByKusionStackLabelKey: "true", fmt.Sprintf("%s/%s", v1alpha1.PodOperateLabelPrefix, id): timestamp, @@ -217,36 +242,35 @@ var _ = Describe("podopslifecycle controller", func() { <-request - pod = &corev1.Pod{} Eventually(func() error { + pod := &corev1.Pod{} if err := mgr.GetAPIReader().Get(context.Background(), client.ObjectKey{ - Name: "test", - Namespace: "default", + Name: name, + Namespace: namespace, }, pod); err != nil { return fmt.Errorf("fail to get pod: %s", err) } + // Need set readiness gate to true if len(pod.Status.Conditions) != 1 { return fmt.Errorf("expected 1 condition, got %d", len(pod.Status.Conditions)) } - if string(pod.Status.Conditions[0].Type) != v1alpha1.ReadinessGatePodServiceReady { return fmt.Errorf("expected type %s, got %s", v1alpha1.ReadinessGatePodServiceReady, pod.Status.Conditions[0].Type) } - if pod.Status.Conditions[0].Status != corev1.ConditionTrue { return fmt.Errorf("expected status %s, got %s", corev1.ConditionTrue, pod.Status.Conditions[0].Status) } return nil - }, 5*time.Second, 1*time.Second).Should(BeNil()) + }, 3*time.Second, 200*time.Millisecond).Should(BeNil()) }) - It("update pod with label complete", func() { + It("Update pod with label compling", func() { pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", + Name: name, + Namespace: namespace, Labels: map[string]string{v1alpha1.ControlledByKusionStackLabelKey: "true"}, }, Spec: podSpec, @@ -256,44 +280,229 @@ var _ = Describe("podopslifecycle controller", func() { <-request - pod = &corev1.Pod{} - err = mgr.GetAPIReader().Get(context.Background(), client.ObjectKey{ - Name: "test", - Namespace: "default", - }, pod) - Expect(err).NotTo(HaveOccurred()) + Eventually(func() error { + pod := &corev1.Pod{} + err = mgr.GetAPIReader().Get(context.Background(), client.ObjectKey{ + Name: name, + Namespace: namespace, + }, pod) + if err != nil { + return fmt.Errorf("fail to get pod: %v", err) + } - pod.ObjectMeta.Labels = map[string]string{ - v1alpha1.ControlledByKusionStackLabelKey: "true", - fmt.Sprintf("%s/%s", v1alpha1.PodOperateLabelPrefix, id): timestamp, - fmt.Sprintf("%s/%s", v1alpha1.PodCompletingLabelPrefix, id): timestamp, - } - err = mgr.GetClient().Update(context.Background(), pod) - Expect(err).NotTo(HaveOccurred()) + pod.ObjectMeta.Labels = map[string]string{ + v1alpha1.ControlledByKusionStackLabelKey: "true", + fmt.Sprintf("%s/%s", v1alpha1.PodOperateLabelPrefix, id): timestamp, + fmt.Sprintf("%s/%s", v1alpha1.PodCompletingLabelPrefix, id): timestamp, + } + return mgr.GetClient().Update(context.Background(), pod) + }, 3*time.Second, 200*time.Millisecond).Should(BeNil()) - pod = &corev1.Pod{} Eventually(func() error { + pod := &corev1.Pod{} if err := mgr.GetAPIReader().Get(context.Background(), client.ObjectKey{ - Name: "test", - Namespace: "default", + Name: name, + Namespace: namespace, }, pod); err != nil { - return fmt.Errorf("fail to get pod: %s", err) + return fmt.Errorf("fail to get pod: %v", err) } + // Need set readiness gate to true if len(pod.Status.Conditions) != 1 { return fmt.Errorf("expected 1 condition, got %d", len(pod.Status.Conditions)) } - if string(pod.Status.Conditions[0].Type) != v1alpha1.ReadinessGatePodServiceReady { return fmt.Errorf("expected type %s, got %s", v1alpha1.ReadinessGatePodServiceReady, pod.Status.Conditions[0].Type) } - if pod.Status.Conditions[0].Status != corev1.ConditionTrue { return fmt.Errorf("expected status %s, got %s", corev1.ConditionTrue, pod.Status.Conditions[0].Status) } - return nil - }, 5*time.Second, 1*time.Second).Should(BeNil()) + }, 3*time.Second, 200*time.Millisecond).Should(BeNil()) + }) +}) + +var _ = Describe("Stage processing", func() { + var ( + podSpec = corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "nginx", + Image: "nginx:latest", + }, + }, + ReadinessGates: []corev1.PodReadinessGate{ + { + ConditionType: v1alpha1.ReadinessGatePodServiceReady, + }, + }, + } + name = "test" + namespace = "default" + ) + + It("Process pre-check stage", func() { + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: map[string]string{ + fmt.Sprintf("%s/%s", v1alpha1.PodOperatingLabelPrefix, "123"): "1402144848", + fmt.Sprintf("%s/%s", v1alpha1.PodOperationTypeLabelPrefix, "123"): "abc", + + fmt.Sprintf("%s/%s", v1alpha1.PodOperatingLabelPrefix, "456"): "1402144849", + fmt.Sprintf("%s/%s", v1alpha1.PodOperationTypeLabelPrefix, "456"): "def", + }, + }, + Spec: podSpec, + } + + idToLabelsMap, _, err := PodIDAndTypesMap(pod) + Expect(err).NotTo(HaveOccurred()) + Expect(len(idToLabelsMap)).To(Equal(2)) + fmt.Println(idToLabelsMap) + + labels, err := podOpsLifecycle.preCheckStage(pod, idToLabelsMap) + Expect(err).NotTo(HaveOccurred()) + Expect(len(labels)).To(Equal(4)) + + for k, v := range idToLabelsMap { + key := fmt.Sprintf("%s/%s", v1alpha1.PodOperationPermissionLabelPrefix, v[v1alpha1.PodOperationTypeLabelPrefix]) + operationPermission, ok := labels[key] + Expect(ok).To(Equal(true)) + Expect(operationPermission).NotTo(BeEmpty()) + + key = fmt.Sprintf("%s/%s", v1alpha1.PodPreCheckedLabelPrefix, k) + preChecked, ok := labels[key] + Expect(ok).To(Equal(true)) + Expect(preChecked).NotTo(BeEmpty()) + } + }) + + It("Process post-check stage", func() { + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: map[string]string{ + fmt.Sprintf("%s/%s", v1alpha1.PodOperateLabelPrefix, "123"): "1402144848", + + fmt.Sprintf("%s/%s", v1alpha1.PodOperateLabelPrefix, "456"): "1402144849", + }, + }, + Spec: podSpec, + } + + idToLabelsMap, _, err := PodIDAndTypesMap(pod) + Expect(err).NotTo(HaveOccurred()) + Expect(len(idToLabelsMap)).To(Equal(2)) + fmt.Println(idToLabelsMap) + + labels, err := podOpsLifecycle.postCheckStage(pod, idToLabelsMap) + Expect(err).NotTo(HaveOccurred()) + Expect(len(labels)).To(Equal(2)) + + for k, _ := range idToLabelsMap { + key := fmt.Sprintf("%s/%s", v1alpha1.PodPostCheckedLabelPrefix, k) + preChecked, ok := labels[key] + Expect(ok).To(Equal(true)) + Expect(preChecked).NotTo(BeEmpty()) + } + }) +}) + +var _ = Describe("Expected finalizer processng", func() { + var ( + name = "test" + namespace = "default" + + podSpec = corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "nginx", + Image: "nginx:latest", + }, + }, + ReadinessGates: []corev1.PodReadinessGate{ + { + ConditionType: v1alpha1.ReadinessGatePodServiceReady, + }, + }, + } + pod = &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: map[string]string{ + "app": name, + }, + }, + Spec: podSpec, + } + ) + + AfterEach(func() { + svc := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + } + mgr.GetClient().Delete(context.Background(), svc) + }) + + It("Available condition is not dirty", func() { + svc := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: corev1.ServiceSpec{ + Selector: map[string]string{ + "app": name, + }, + Ports: []corev1.ServicePort{ + { + Name: "http", + Port: 80, + }, + }, + }, + } + err := mgr.GetClient().Create(context.Background(), svc) + Expect(err).NotTo(HaveOccurred()) + + key := fmt.Sprintf("%s/%s/%s", "Service", svc.GetNamespace(), svc.GetName()) + isAvailableConditionDirty, err := podOpsLifecycle.isAvailableConditionDirty(pod, key) + Expect(err).NotTo(HaveOccurred()) + Expect(isAvailableConditionDirty).To(Equal(false)) + }) + + It("Available condition is dirty", func() { + svc := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: corev1.ServiceSpec{ + Selector: map[string]string{ + "app1": name, + }, + Ports: []corev1.ServicePort{ + { + Name: "http", + Port: 80, + }, + }, + }, + } + err := mgr.GetClient().Create(context.Background(), svc) + Expect(err).NotTo(HaveOccurred()) + + key := fmt.Sprintf("%s/%s/%s", "Service", svc.GetNamespace(), svc.GetName()) + isAvailableConditionDirty, err := podOpsLifecycle.isAvailableConditionDirty(pod, key) + Expect(err).NotTo(HaveOccurred()) + Expect(isAvailableConditionDirty).To(Equal(true)) }) }) diff --git a/pkg/controllers/podopslifecycle/predicate.go b/pkg/controllers/podopslifecycle/predicate.go index 3a2f55d7..b88b65c7 100644 --- a/pkg/controllers/podopslifecycle/predicate.go +++ b/pkg/controllers/podopslifecycle/predicate.go @@ -24,7 +24,7 @@ import ( type NeedOpsLifecycle func(oldPod, newPod *corev1.Pod) bool type PodPredicate struct { - NeedOpsLifecycle // check if pod need use lifecycle + NeedOpsLifecycle // Check if pod need be reconciled } func (pp *PodPredicate) Create(evt event.CreateEvent) bool { diff --git a/pkg/controllers/podopslifecycle/utils_test.go b/pkg/controllers/podopslifecycle/utils_test.go new file mode 100644 index 00000000..cbec015d --- /dev/null +++ b/pkg/controllers/podopslifecycle/utils_test.go @@ -0,0 +1,170 @@ +/** + * Copyright 2023 KusionStack 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 podopslifecycle + +import ( + "fmt" + "reflect" + "testing" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "kusionstack.io/operating/apis/apps/v1alpha1" +) + +func TestPodIDAndTypesMap(t *testing.T) { + var ( + name = "test" + namespace = "default" + ) + casee := []struct { + keyWords string + + labels map[string]string + + idToLabelsMap map[string]map[string]string + typeToNumsMap map[string]int + err error + }{ + { + keyWords: "A pod with wrong label", + labels: map[string]string{ + v1alpha1.PodOperatingLabelPrefix: "1402144848", + }, + + idToLabelsMap: nil, + typeToNumsMap: nil, + err: fmt.Errorf("invalid label %s", v1alpha1.PodOperatingLabelPrefix), + }, + + { + keyWords: "A pod with no label", + labels: nil, + + idToLabelsMap: nil, + typeToNumsMap: nil, + err: nil, + }, + + { + keyWords: "A pod with normal labels", + labels: map[string]string{ + fmt.Sprintf("%s/%s", v1alpha1.PodOperatingLabelPrefix, "123"): "1402144848", + fmt.Sprintf("%s/%s", v1alpha1.PodOperationTypeLabelPrefix, "123"): "abc", + }, + + idToLabelsMap: map[string]map[string]string{ + "123": { + v1alpha1.PodOperatingLabelPrefix: "1402144848", + v1alpha1.PodOperationTypeLabelPrefix: "abc", + }, + }, + typeToNumsMap: map[string]int{ + "abc": 1, + }, + err: nil, + }, + + { + keyWords: "A pod with multi labels which have different types", + labels: map[string]string{ + fmt.Sprintf("%s/%s", v1alpha1.PodOperatingLabelPrefix, "123"): "1402144848", + fmt.Sprintf("%s/%s", v1alpha1.PodOperationTypeLabelPrefix, "123"): "abc", + + fmt.Sprintf("%s/%s", v1alpha1.PodOperatingLabelPrefix, "456"): "1402144849", + fmt.Sprintf("%s/%s", v1alpha1.PodOperationTypeLabelPrefix, "456"): "def", + }, + + idToLabelsMap: map[string]map[string]string{ + "123": { + v1alpha1.PodOperatingLabelPrefix: "1402144848", + v1alpha1.PodOperationTypeLabelPrefix: "abc", + }, + "456": { + v1alpha1.PodOperatingLabelPrefix: "1402144849", + v1alpha1.PodOperationTypeLabelPrefix: "def", + }, + }, + typeToNumsMap: map[string]int{ + "abc": 1, + "def": 1, + }, + err: nil, + }, + + { + keyWords: "A pod with multi labels which have the same type", + labels: map[string]string{ + fmt.Sprintf("%s/%s", v1alpha1.PodOperatingLabelPrefix, "123"): "1402144848", + fmt.Sprintf("%s/%s", v1alpha1.PodOperationTypeLabelPrefix, "123"): "abc", + + fmt.Sprintf("%s/%s", v1alpha1.PodOperatingLabelPrefix, "456"): "1402144849", + fmt.Sprintf("%s/%s", v1alpha1.PodOperationTypeLabelPrefix, "456"): "abc", + }, + + idToLabelsMap: map[string]map[string]string{ + "123": { + v1alpha1.PodOperatingLabelPrefix: "1402144848", + v1alpha1.PodOperationTypeLabelPrefix: "abc", + }, + "456": { + v1alpha1.PodOperatingLabelPrefix: "1402144849", + v1alpha1.PodOperationTypeLabelPrefix: "abc", + }, + }, + typeToNumsMap: map[string]int{ + "abc": 2, + }, + err: nil, + }, + } + + for _, c := range casee { + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: c.labels, + }, + } + + idToLabelsMap, typeToNumsMap, err := PodIDAndTypesMap(pod) + if c.err != nil { + if err == nil { + t.Errorf("%s, expect err %v, got nil", c.keyWords, c.err) + } else if err.Error() != c.err.Error() { + t.Errorf("%s, expect err %v, got %v", c.keyWords, c.err, err) + } + } + + if c.idToLabelsMap == nil { + if len(idToLabelsMap) != 0 { + t.Errorf("%s, expect idToLabelsMap nil, got %v", c.keyWords, idToLabelsMap) + } + } else if !reflect.DeepEqual(idToLabelsMap, c.idToLabelsMap) { + t.Errorf("%s, expect idToLabelsMap %v, got %v", c.keyWords, c.idToLabelsMap, idToLabelsMap) + } + + if c.typeToNumsMap == nil { + if len(typeToNumsMap) != 0 { + t.Errorf("%s, expect typeToNumsMap nil, got %v", c.keyWords, typeToNumsMap) + } + } else if !reflect.DeepEqual(typeToNumsMap, c.typeToNumsMap) { + t.Errorf("%s, expect typeToNumsMap %v, got %v", c.keyWords, c.typeToNumsMap, typeToNumsMap) + } + } +} diff --git a/pkg/controllers/utils/pod_utils.go b/pkg/controllers/utils/pod_utils.go index ae7c533d..e27db0d3 100644 --- a/pkg/controllers/utils/pod_utils.go +++ b/pkg/controllers/utils/pod_utils.go @@ -18,6 +18,7 @@ package utils import ( "encoding/json" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/sets" @@ -114,6 +115,7 @@ func IsExpectedFinalizerSatisfied(pod *corev1.Pod) (bool, map[string]string, err existFinalizers.Insert(finalizer) } + // Check if all expected finalizers are satisfied for expectedFlzKey, finalizer := range availableConditions.ExpectedFinalizers { if !existFinalizers.Has(finalizer) { satisfied = false