From 6c1b9f1765a9e1af17330a32a2e17c0e27394de8 Mon Sep 17 00:00:00 2001 From: Poor12 Date: Fri, 8 Apr 2022 16:53:26 +0800 Subject: [PATCH] add UT for aggregatestatus Signed-off-by: Poor12 --- .../defaultinterpreter/aggregatestatus.go | 9 +- .../aggregatestatus_test.go | 335 ++++++++++++++++++ 2 files changed, 341 insertions(+), 3 deletions(-) create mode 100644 pkg/resourceinterpreter/defaultinterpreter/aggregatestatus_test.go diff --git a/pkg/resourceinterpreter/defaultinterpreter/aggregatestatus.go b/pkg/resourceinterpreter/defaultinterpreter/aggregatestatus.go index 0db5186e9..4ef7565ef 100644 --- a/pkg/resourceinterpreter/defaultinterpreter/aggregatestatus.go +++ b/pkg/resourceinterpreter/defaultinterpreter/aggregatestatus.go @@ -190,14 +190,15 @@ func aggregateDaemonSetStatus(object *unstructured.Unstructured, aggregatedStatu if err = json.Unmarshal(item.Status.Raw, temp); err != nil { return nil, err } - klog.V(3).Infof("Grab daemonSet(%s/%s) status from cluster(%s), currentNumberScheduled: %d, desiredNumberScheduled: %d, numberAvailable: %d, numberMisscheduled: %d, numberReady: %d, updatedNumberScheduled: %d", - daemonSet.Namespace, daemonSet.Name, item.ClusterName, temp.CurrentNumberScheduled, temp.DesiredNumberScheduled, temp.NumberAvailable, temp.NumberMisscheduled, temp.NumberReady, temp.UpdatedNumberScheduled) + klog.V(3).Infof("Grab daemonSet(%s/%s) status from cluster(%s), currentNumberScheduled: %d, desiredNumberScheduled: %d, numberAvailable: %d, numberMisscheduled: %d, numberReady: %d, updatedNumberScheduled: %d, numberUnavailable: %d", + daemonSet.Namespace, daemonSet.Name, item.ClusterName, temp.CurrentNumberScheduled, temp.DesiredNumberScheduled, temp.NumberAvailable, temp.NumberMisscheduled, temp.NumberReady, temp.UpdatedNumberScheduled, temp.NumberUnavailable) newStatus.CurrentNumberScheduled += temp.CurrentNumberScheduled newStatus.DesiredNumberScheduled += temp.DesiredNumberScheduled newStatus.NumberAvailable += temp.NumberAvailable newStatus.NumberMisscheduled += temp.NumberMisscheduled newStatus.NumberReady += temp.NumberReady newStatus.UpdatedNumberScheduled += temp.UpdatedNumberScheduled + newStatus.NumberUnavailable += temp.NumberUnavailable } if oldStatus.CurrentNumberScheduled == newStatus.CurrentNumberScheduled && @@ -205,7 +206,8 @@ func aggregateDaemonSetStatus(object *unstructured.Unstructured, aggregatedStatu oldStatus.NumberAvailable == newStatus.NumberAvailable && oldStatus.NumberMisscheduled == newStatus.NumberMisscheduled && oldStatus.NumberReady == newStatus.NumberReady && - oldStatus.UpdatedNumberScheduled == newStatus.UpdatedNumberScheduled { + oldStatus.UpdatedNumberScheduled == newStatus.UpdatedNumberScheduled && + oldStatus.NumberUnavailable == newStatus.NumberUnavailable { klog.V(3).Infof("ignore update daemonSet(%s/%s) status as up to date", daemonSet.Namespace, daemonSet.Name) return object, nil } @@ -216,6 +218,7 @@ func aggregateDaemonSetStatus(object *unstructured.Unstructured, aggregatedStatu oldStatus.NumberMisscheduled = newStatus.NumberMisscheduled oldStatus.NumberReady = newStatus.NumberReady oldStatus.UpdatedNumberScheduled = newStatus.UpdatedNumberScheduled + oldStatus.NumberUnavailable = newStatus.NumberUnavailable return helper.ToUnstructured(daemonSet) } diff --git a/pkg/resourceinterpreter/defaultinterpreter/aggregatestatus_test.go b/pkg/resourceinterpreter/defaultinterpreter/aggregatestatus_test.go new file mode 100644 index 000000000..0fdeb8517 --- /dev/null +++ b/pkg/resourceinterpreter/defaultinterpreter/aggregatestatus_test.go @@ -0,0 +1,335 @@ +package defaultinterpreter + +import ( + "testing" + + "github.com/stretchr/testify/assert" + appsv1 "k8s.io/api/apps/v1" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2" + "github.com/karmada-io/karmada/pkg/util/helper" +) + +func TestAggregateDeploymentStatus(t *testing.T) { + statusMap := map[string]interface{}{ + "replicas": 1, + "readyReplicas": 1, + "updatedReplicas": 1, + "availableReplicas": 1, + "unavailableReplicas": 0, + } + raw, _ := helper.BuildStatusRawExtension(statusMap) + aggregatedStatusItems := []workv1alpha2.AggregatedStatusItem{ + {ClusterName: "member1", Status: raw, Applied: true}, + {ClusterName: "member2", Status: raw, Applied: true}, + } + + oldDeploy := &appsv1.Deployment{} + newDeploy := &appsv1.Deployment{Status: appsv1.DeploymentStatus{Replicas: 2, ReadyReplicas: 2, UpdatedReplicas: 2, AvailableReplicas: 2, UnavailableReplicas: 0}} + oldObj, _ := helper.ToUnstructured(oldDeploy) + newObj, _ := helper.ToUnstructured(newDeploy) + + tests := []struct { + name string + curObj *unstructured.Unstructured + aggregatedStatusItems []workv1alpha2.AggregatedStatusItem + expectedObj *unstructured.Unstructured + }{ + { + name: "update deployment status", + curObj: oldObj, + aggregatedStatusItems: aggregatedStatusItems, + expectedObj: newObj, + }, + { + name: "ignore update deployment status as up to date", + curObj: newObj, + aggregatedStatusItems: aggregatedStatusItems, + expectedObj: newObj, + }, + } + + for _, tt := range tests { + actualObj, _ := aggregateDeploymentStatus(tt.curObj, tt.aggregatedStatusItems) + assert.Equal(t, tt.expectedObj, actualObj) + } +} + +func TestAggregateServiceStatus(t *testing.T) { + statusMapNotLB := map[string]interface{}{} + rawNotLB, _ := helper.BuildStatusRawExtension(statusMapNotLB) + aggregatedStatusItemsNotLB := []workv1alpha2.AggregatedStatusItem{ + {ClusterName: "member1", Status: rawNotLB, Applied: true}, + } + + statusMapLB := map[string]interface{}{ + "loadBalancer": corev1.LoadBalancerStatus{Ingress: []corev1.LoadBalancerIngress{{IP: "8.8.8.8"}}}, + } + rawLB, _ := helper.BuildStatusRawExtension(statusMapLB) + aggregatedStatusItemsLB := []workv1alpha2.AggregatedStatusItem{ + {ClusterName: "member1", Status: rawLB, Applied: true}, + } + + serviceClusterIP := &corev1.Service{Spec: corev1.ServiceSpec{Type: corev1.ServiceTypeClusterIP}} + serviceNodePort := &corev1.Service{Spec: corev1.ServiceSpec{Type: corev1.ServiceTypeNodePort}} + serviceExternalName := &corev1.Service{Spec: corev1.ServiceSpec{Type: corev1.ServiceTypeExternalName}} + objServiceClusterIP, _ := helper.ToUnstructured(serviceClusterIP) + objServiceNodePort, _ := helper.ToUnstructured(serviceNodePort) + objServiceExternalName, _ := helper.ToUnstructured(serviceExternalName) + + oldServiceLoadBalancer := &corev1.Service{Spec: corev1.ServiceSpec{Type: corev1.ServiceTypeLoadBalancer}} + newServiceLoadBalancer := &corev1.Service{Spec: corev1.ServiceSpec{Type: corev1.ServiceTypeLoadBalancer}, Status: corev1.ServiceStatus{LoadBalancer: corev1.LoadBalancerStatus{Ingress: []corev1.LoadBalancerIngress{{IP: "8.8.8.8", Hostname: "member1"}}}}} + oldObjServiceLoadBalancer, _ := helper.ToUnstructured(oldServiceLoadBalancer) + newObjServiceLoadBalancer, _ := helper.ToUnstructured(newServiceLoadBalancer) + + tests := []struct { + name string + curObj *unstructured.Unstructured + aggregatedStatusItems []workv1alpha2.AggregatedStatusItem + expectedObj *unstructured.Unstructured + }{ + { + name: "ignore update service status when type is ClusterIP", + curObj: objServiceClusterIP, + aggregatedStatusItems: aggregatedStatusItemsNotLB, + expectedObj: objServiceClusterIP, + }, + { + name: "ignore update service status when type is NodePort", + curObj: objServiceNodePort, + aggregatedStatusItems: aggregatedStatusItemsNotLB, + expectedObj: objServiceNodePort, + }, + { + name: "ignore update service status when type is ExternalName", + curObj: objServiceExternalName, + aggregatedStatusItems: aggregatedStatusItemsNotLB, + expectedObj: objServiceExternalName, + }, + { + name: "update service status when type is LoadBalancer", + curObj: oldObjServiceLoadBalancer, + aggregatedStatusItems: aggregatedStatusItemsLB, + expectedObj: newObjServiceLoadBalancer, + }, + { + name: "ignore update service status as up to date when type is LoadBalancer", + curObj: newObjServiceLoadBalancer, + aggregatedStatusItems: aggregatedStatusItemsLB, + expectedObj: newObjServiceLoadBalancer, + }, + } + + for _, tt := range tests { + actualObj, _ := aggregateServiceStatus(tt.curObj, tt.aggregatedStatusItems) + assert.Equal(t, tt.expectedObj, actualObj) + } +} + +func TestAggregateIngressStatus(t *testing.T) { + statusMap := map[string]interface{}{ + "loadBalancer": corev1.LoadBalancerStatus{Ingress: []corev1.LoadBalancerIngress{{IP: "8.8.8.8"}}}, + } + raw, _ := helper.BuildStatusRawExtension(statusMap) + aggregatedStatusItems := []workv1alpha2.AggregatedStatusItem{ + {ClusterName: "member1", Status: raw, Applied: true}, + } + + oldIngress := &extensionsv1beta1.Ingress{} + newIngress := &extensionsv1beta1.Ingress{Status: extensionsv1beta1.IngressStatus{LoadBalancer: corev1.LoadBalancerStatus{Ingress: []corev1.LoadBalancerIngress{{IP: "8.8.8.8", Hostname: "member1"}}}}} + oldObj, _ := helper.ToUnstructured(oldIngress) + newObj, _ := helper.ToUnstructured(newIngress) + + tests := []struct { + name string + curObj *unstructured.Unstructured + aggregatedStatusItems []workv1alpha2.AggregatedStatusItem + expectedObj *unstructured.Unstructured + }{ + { + name: "update ingress status", + curObj: oldObj, + aggregatedStatusItems: aggregatedStatusItems, + expectedObj: newObj, + }, + { + name: "ignore update ingress status as up to date", + curObj: newObj, + aggregatedStatusItems: aggregatedStatusItems, + expectedObj: newObj, + }, + } + + for _, tt := range tests { + actualObj, _ := aggregateIngressStatus(tt.curObj, tt.aggregatedStatusItems) + assert.Equal(t, tt.expectedObj, actualObj) + } +} + +func TestAggregateJobStatus(t *testing.T) { + startTime := metav1.Now() + completionTime := startTime + statusMap := map[string]interface{}{ + "active": 0, + "succeeded": 1, + "failed": 0, + "startTime": startTime, + "completionTime": completionTime, + "conditions": []batchv1.JobCondition{{Type: batchv1.JobComplete, Status: corev1.ConditionTrue}}, + } + raw, _ := helper.BuildStatusRawExtension(statusMap) + aggregatedStatusItems := []workv1alpha2.AggregatedStatusItem{ + {ClusterName: "member1", Status: raw, Applied: true}, + {ClusterName: "member2", Status: raw, Applied: true}, + } + + statusMapWithJobfailed := map[string]interface{}{ + "active": 0, + "succeeded": 0, + "failed": 1, + "startTime": startTime, + "completionTime": completionTime, + "conditions": []batchv1.JobCondition{{Type: batchv1.JobFailed, Status: corev1.ConditionTrue}}, + } + rawWithJobFailed, _ := helper.BuildStatusRawExtension(statusMapWithJobfailed) + aggregatedStatusItemsWithJobFailed := []workv1alpha2.AggregatedStatusItem{ + {ClusterName: "member1", Status: raw, Applied: true}, + {ClusterName: "member2", Status: rawWithJobFailed, Applied: true}, + } + + oldJob := &batchv1.Job{} + newJob := &batchv1.Job{Status: batchv1.JobStatus{Active: 0, Succeeded: 2, Failed: 0, StartTime: &startTime, CompletionTime: &completionTime, Conditions: []batchv1.JobCondition{{Type: batchv1.JobComplete, Status: corev1.ConditionTrue, LastProbeTime: metav1.Now(), LastTransitionTime: metav1.Now(), Reason: "Completed", Message: "Job completed"}}}} + oldObj, _ := helper.ToUnstructured(oldJob) + newObj, _ := helper.ToUnstructured(newJob) + + newJobWithJobFailed := &batchv1.Job{Status: batchv1.JobStatus{Active: 0, Succeeded: 1, Failed: 1, StartTime: &startTime, CompletionTime: &completionTime, Conditions: []batchv1.JobCondition{{Type: batchv1.JobFailed, Status: corev1.ConditionTrue, LastProbeTime: metav1.Now(), LastTransitionTime: metav1.Now(), Reason: "JobFailed", Message: "Job executed failed in member clusters member2"}}}} + newObjWithJobFailed, _ := helper.ToUnstructured(newJobWithJobFailed) + + tests := []struct { + name string + curObj *unstructured.Unstructured + aggregatedStatusItems []workv1alpha2.AggregatedStatusItem + expectedObj *unstructured.Unstructured + }{ + { + name: "update job status without job failed", + curObj: oldObj, + aggregatedStatusItems: aggregatedStatusItems, + expectedObj: newObj, + }, + { + name: "update job status with job failed", + curObj: oldObj, + aggregatedStatusItems: aggregatedStatusItemsWithJobFailed, + expectedObj: newObjWithJobFailed, + }, + { + name: "ignore update job status as up to date", + curObj: newObj, + aggregatedStatusItems: aggregatedStatusItems, + expectedObj: newObj, + }, + } + + for _, tt := range tests { + actualObj, _ := aggregateJobStatus(tt.curObj, tt.aggregatedStatusItems) + assert.Equal(t, tt.expectedObj, actualObj) + } +} + +func TestAggregateDaemonSetStatus(t *testing.T) { + statusMap := map[string]interface{}{ + "currentNumberScheduled": 1, + "numberMisscheduled": 0, + "desiredNumberScheduled": 1, + "numberReady": 1, + "updatedNumberScheduled": 1, + "numberAvailable": 1, + "numberUnavailable": 0, + } + raw, _ := helper.BuildStatusRawExtension(statusMap) + aggregatedStatusItems := []workv1alpha2.AggregatedStatusItem{ + {ClusterName: "member1", Status: raw, Applied: true}, + {ClusterName: "member2", Status: raw, Applied: true}, + } + + oldDaemonSet := &appsv1.DaemonSet{} + newDaemonSet := &appsv1.DaemonSet{Status: appsv1.DaemonSetStatus{CurrentNumberScheduled: 2, NumberMisscheduled: 0, DesiredNumberScheduled: 2, NumberReady: 2, UpdatedNumberScheduled: 2, NumberAvailable: 2, NumberUnavailable: 0}} + oldObj, _ := helper.ToUnstructured(oldDaemonSet) + newObj, _ := helper.ToUnstructured(newDaemonSet) + + tests := []struct { + name string + curObj *unstructured.Unstructured + aggregatedStatusItems []workv1alpha2.AggregatedStatusItem + expectedObj *unstructured.Unstructured + }{ + { + name: "update daemonSet status", + curObj: oldObj, + aggregatedStatusItems: aggregatedStatusItems, + expectedObj: newObj, + }, + { + name: "ignore update daemonSet status as up to date", + curObj: newObj, + aggregatedStatusItems: aggregatedStatusItems, + expectedObj: newObj, + }, + } + + for _, tt := range tests { + actualObj, _ := aggregateDaemonSetStatus(tt.curObj, tt.aggregatedStatusItems) + assert.Equal(t, tt.expectedObj, actualObj) + } +} + +func TestAggregateStatefulSetStatus(t *testing.T) { + statusMap := map[string]interface{}{ + "replicas": 1, + "readyReplicas": 1, + "currentReplicas": 1, + "updatedReplicas": 1, + "availableReplicas": 1, + } + raw, _ := helper.BuildStatusRawExtension(statusMap) + aggregatedStatusItems := []workv1alpha2.AggregatedStatusItem{ + {ClusterName: "member1", Status: raw, Applied: true}, + {ClusterName: "member2", Status: raw, Applied: true}, + } + + oldStatefulSet := &appsv1.StatefulSet{} + newStatefulSet := &appsv1.StatefulSet{Status: appsv1.StatefulSetStatus{Replicas: 2, ReadyReplicas: 2, UpdatedReplicas: 2, AvailableReplicas: 2, CurrentReplicas: 2}} + oldObj, _ := helper.ToUnstructured(oldStatefulSet) + newObj, _ := helper.ToUnstructured(newStatefulSet) + + tests := []struct { + name string + curObj *unstructured.Unstructured + aggregatedStatusItems []workv1alpha2.AggregatedStatusItem + expectedObj *unstructured.Unstructured + }{ + { + name: "update statefulSet status", + curObj: oldObj, + aggregatedStatusItems: aggregatedStatusItems, + expectedObj: newObj, + }, + { + name: "ignore update statefulSet status as up to date", + curObj: newObj, + aggregatedStatusItems: aggregatedStatusItems, + expectedObj: newObj, + }, + } + + for _, tt := range tests { + actualObj, _ := aggregateStatefulSetStatus(tt.curObj, tt.aggregatedStatusItems) + assert.Equal(t, tt.expectedObj, actualObj) + } +}