karmada/pkg/resourceinterpreter/default/native/aggregatestatus_test.go

1151 lines
38 KiB
Go

/*
Copyright 2022 The Karmada 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 native
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
appsv1 "k8s.io/api/apps/v1"
autoscalingv2 "k8s.io/api/autoscaling/v2"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
policyv1 "k8s.io/api/policy/v1"
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 := &networkingv1.Ingress{}
newIngress := &networkingv1.Ingress{Status: networkingv1.IngressStatus{LoadBalancer: networkingv1.IngressLoadBalancerStatus{Ingress: []networkingv1.IngressLoadBalancerIngress{{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, 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, 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)
// Clean condition time before compare, due to issue: https://github.com/karmada-io/karmada/issues/1767
actualObj = cleanUnstructuredJobConditionTime(actualObj)
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)
}
}
func cleanUnstructuredJobConditionTime(object *unstructured.Unstructured) *unstructured.Unstructured {
job := &batchv1.Job{}
_ = helper.ConvertToTypedObject(object, job)
for i := range job.Status.Conditions {
cond := &job.Status.Conditions[i]
cond.LastProbeTime = metav1.Time{}
cond.LastTransitionTime = metav1.Time{}
}
ret, _ := helper.ToUnstructured(job)
return ret
}
func TestAggregatePodStatus(t *testing.T) {
startedTrue := true
timeNow := time.Now()
containerStatuses1 := []corev1.ContainerStatus{
{
ContainerID: "containerd://6cee0afa333472f672341352e0d",
Image: "nginx:latest",
ImageID: "docker.io/library/import-2022-06-05@sha256:dfb593",
Name: "busybox-2",
Ready: true,
RestartCount: 0,
Started: &startedTrue,
State: corev1.ContainerState{
Running: &corev1.ContainerStateRunning{
StartedAt: metav1.Time{
Time: timeNow,
},
},
},
},
{
ContainerID: "containerd://b373fb05ebf57020573cdf4a4518a3b2a8",
Image: "nginx:latest",
ImageID: "docker.io/library/import-2022-06-05@sha256:a29d07a75",
Name: "busybox-2",
Ready: true,
RestartCount: 0,
Started: &startedTrue,
State: corev1.ContainerState{
Running: &corev1.ContainerStateRunning{
StartedAt: metav1.Time{
Time: timeNow,
},
},
},
},
}
newContainerStatuses1 := []corev1.ContainerStatus{
{
Ready: true,
State: corev1.ContainerState{
Running: &corev1.ContainerStateRunning{
StartedAt: metav1.Time{
Time: timeNow,
},
},
},
},
{
Ready: true,
State: corev1.ContainerState{
Running: &corev1.ContainerStateRunning{
StartedAt: metav1.Time{
Time: timeNow,
},
},
},
},
}
newInitContainerStatuses1 := []corev1.ContainerStatus{
{
Ready: false,
State: corev1.ContainerState{
Waiting: &corev1.ContainerStateWaiting{Reason: "PodInitializing"},
},
},
{
Ready: false,
State: corev1.ContainerState{
Running: &corev1.ContainerStateRunning{
StartedAt: metav1.Time{
Time: timeNow,
},
},
},
},
}
newInitContainerObj, _ := helper.ToUnstructured(&corev1.Pod{Status: corev1.PodStatus{
InitContainerStatuses: newInitContainerStatuses1,
Phase: corev1.PodPending,
}})
initContainerStatusMap1 := map[string]interface{}{
"initContainerStatuses": []corev1.ContainerStatus{newInitContainerStatuses1[0], newInitContainerStatuses1[1]},
"phase": corev1.PodPending,
}
initContainerRaw1, _ := helper.BuildStatusRawExtension(initContainerStatusMap1)
aggregatedInitContainerStatusItems1 := []workv1alpha2.AggregatedStatusItem{
{ClusterName: "member1", Status: initContainerRaw1, Applied: true},
}
curObj, _ := helper.ToUnstructured(&corev1.Pod{})
newObj, _ := helper.ToUnstructured(&corev1.Pod{Status: corev1.PodStatus{
ContainerStatuses: newContainerStatuses1,
Phase: corev1.PodRunning,
}})
statusMap1 := map[string]interface{}{
"containerStatuses": []corev1.ContainerStatus{containerStatuses1[0]},
"phase": corev1.PodRunning,
}
raw1, _ := helper.BuildStatusRawExtension(statusMap1)
statusMap2 := map[string]interface{}{
"containerStatuses": []corev1.ContainerStatus{containerStatuses1[1]},
"phase": corev1.PodRunning,
}
raw2, _ := helper.BuildStatusRawExtension(statusMap2)
aggregatedStatusItems1 := []workv1alpha2.AggregatedStatusItem{
{ClusterName: "member1", Status: raw1, Applied: true},
{ClusterName: "member2", Status: raw2, Applied: true},
}
containerStatuses2 := []corev1.ContainerStatus{
{
ContainerID: "containerd://6cee0afa333472f672341352e0d",
Image: "nginx:latest",
ImageID: "docker.io/library/import-2022-06-05@sha256:dfb593",
Name: "busybox-2",
Ready: true,
RestartCount: 0,
Started: &startedTrue,
State: corev1.ContainerState{
Running: &corev1.ContainerStateRunning{
StartedAt: metav1.Time{
Time: timeNow,
},
},
},
},
{
ContainerID: "containerd://b373fb05ebf57020573cdf4a4518a3b2a8",
Image: "nginx:latest",
ImageID: "docker.io/library/import-2022-06-05@sha256:a29d07a75",
Name: "busybox-2",
Ready: false,
RestartCount: 0,
Started: &startedTrue,
State: corev1.ContainerState{
Running: &corev1.ContainerStateRunning{
StartedAt: metav1.Time{
Time: timeNow,
},
},
},
},
}
statusMap3 := map[string]interface{}{
"containerStatuses": []corev1.ContainerStatus{containerStatuses2[0]},
"phase": corev1.PodRunning,
}
raw3, _ := helper.BuildStatusRawExtension(statusMap3)
statusMap4 := map[string]interface{}{
"containerStatuses": []corev1.ContainerStatus{containerStatuses2[1]},
"phase": corev1.PodPending,
}
raw4, _ := helper.BuildStatusRawExtension(statusMap4)
aggregatedStatusItems2 := []workv1alpha2.AggregatedStatusItem{
{ClusterName: "member1", Status: raw3, Applied: true},
{ClusterName: "member2", Status: raw4, Applied: true},
}
newContainerStatuses2 := []corev1.ContainerStatus{
{
Ready: true,
State: corev1.ContainerState{
Running: &corev1.ContainerStateRunning{
StartedAt: metav1.Time{
Time: timeNow,
},
},
},
},
{
Ready: false,
State: corev1.ContainerState{
Running: &corev1.ContainerStateRunning{
StartedAt: metav1.Time{
Time: timeNow,
},
},
},
},
}
newPodFailed := &corev1.Pod{Status: corev1.PodStatus{
ContainerStatuses: newContainerStatuses2,
Phase: corev1.PodPending,
}}
newObjFailed, _ := helper.ToUnstructured(newPodFailed)
containerStatusesRunning := []corev1.ContainerStatus{
{
Ready: true,
State: corev1.ContainerState{Running: &corev1.ContainerStateRunning{StartedAt: metav1.Time{Time: time.Now()}}},
},
}
newContainerStatusesFail := []corev1.ContainerStatus{
{
Ready: false,
State: corev1.ContainerState{Terminated: &corev1.ContainerStateTerminated{ExitCode: 125}},
},
}
newContainerStatusesSucceeded := []corev1.ContainerStatus{
{
Ready: false,
State: corev1.ContainerState{Terminated: &corev1.ContainerStateTerminated{ExitCode: 0}},
},
}
statusMapRunning := map[string]interface{}{
"containerStatuses": []corev1.ContainerStatus{containerStatusesRunning[0]},
"phase": corev1.PodRunning,
}
statusMapFailed := map[string]interface{}{
"containerStatuses": []corev1.ContainerStatus{newContainerStatusesFail[0]},
"phase": corev1.PodFailed,
}
statusMapSucceeded := map[string]interface{}{
"containerStatuses": []corev1.ContainerStatus{newContainerStatusesSucceeded[0]},
"phase": corev1.PodSucceeded,
}
rawRunning, _ := helper.BuildStatusRawExtension(statusMapRunning)
// test failed
rawFail, _ := helper.BuildStatusRawExtension(statusMapFailed)
aggregatedStatusItemsFail := []workv1alpha2.AggregatedStatusItem{
{ClusterName: "member1", Status: rawRunning, Applied: true},
{ClusterName: "member2", Status: rawFail, Applied: true},
}
aggregatedStatusItemsPending := []workv1alpha2.AggregatedStatusItem{
{ClusterName: "member1", Status: rawRunning, Applied: true},
{ClusterName: "member2", Status: nil, Applied: true},
}
failObj, _ := helper.ToUnstructured(&corev1.Pod{Status: corev1.PodStatus{
ContainerStatuses: []corev1.ContainerStatus{containerStatusesRunning[0], newContainerStatusesFail[0]},
Phase: corev1.PodFailed,
}})
// test succeeded
rawSucceeded, _ := helper.BuildStatusRawExtension(statusMapSucceeded)
aggregatedStatusItemsSucceeded := []workv1alpha2.AggregatedStatusItem{
{ClusterName: "member1", Status: rawRunning, Applied: true},
{ClusterName: "member2", Status: rawSucceeded, Applied: true},
}
succeededObj, _ := helper.ToUnstructured(&corev1.Pod{Status: corev1.PodStatus{
ContainerStatuses: []corev1.ContainerStatus{containerStatusesRunning[0], newContainerStatusesSucceeded[0]},
Phase: corev1.PodRunning,
}})
pendingObj, _ := helper.ToUnstructured(&corev1.Pod{Status: corev1.PodStatus{
ContainerStatuses: []corev1.ContainerStatus{containerStatusesRunning[0]},
Phase: corev1.PodPending,
}})
tests := []struct {
name string
curObj *unstructured.Unstructured
aggregatedStatusItems []workv1alpha2.AggregatedStatusItem
expectedObj *unstructured.Unstructured
}{
{
name: "update initContainer status",
curObj: curObj,
aggregatedStatusItems: aggregatedInitContainerStatusItems1,
expectedObj: newInitContainerObj,
},
{
name: "update pod status",
curObj: curObj,
aggregatedStatusItems: aggregatedStatusItems1,
expectedObj: newObj,
},
{
name: "ignore update pod status as up to date",
curObj: newObj,
aggregatedStatusItems: aggregatedStatusItems1,
expectedObj: newObj,
},
{
name: "update pod status as one Pod failed",
curObj: newObj,
aggregatedStatusItems: aggregatedStatusItems2,
expectedObj: newObjFailed,
},
// More details please refer to: https://github.com/karmada-io/karmada/issues/2137
{
name: "failed pod status",
curObj: curObj,
aggregatedStatusItems: aggregatedStatusItemsFail, // Running + Failed => Failed
expectedObj: failObj,
},
{
name: "succeeded pod status",
curObj: curObj,
aggregatedStatusItems: aggregatedStatusItemsSucceeded, // Running + Succeeded => Running
expectedObj: succeededObj,
},
{
name: "pending pod status",
curObj: curObj,
aggregatedStatusItems: aggregatedStatusItemsPending, // Running + nil => Pending
expectedObj: pendingObj,
},
}
for _, tt := range tests {
actualObj, _ := aggregatePodStatus(tt.curObj, tt.aggregatedStatusItems)
assert.Equal(t, tt.expectedObj, actualObj)
}
}
func TestAggregatePVCStatus(t *testing.T) {
statusBoundMap := map[string]interface{}{
"phase": corev1.ClaimBound,
}
// Bound status
boundRaw1, _ := helper.BuildStatusRawExtension(statusBoundMap)
boundRaw2, _ := helper.BuildStatusRawExtension(statusBoundMap)
aggregatedStatusItems1 := []workv1alpha2.AggregatedStatusItem{
{ClusterName: "member1", Status: boundRaw1, Applied: true},
{ClusterName: "member2", Status: boundRaw2, Applied: true},
}
// Lost status
statusLostMap := map[string]interface{}{
"phase": corev1.ClaimLost,
}
lostRaw1, _ := helper.BuildStatusRawExtension(statusBoundMap)
lostRaw2, _ := helper.BuildStatusRawExtension(statusLostMap)
aggregatedStatusItems2 := []workv1alpha2.AggregatedStatusItem{
{ClusterName: "member1", Status: lostRaw1, Applied: true},
{ClusterName: "member2", Status: lostRaw2, Applied: true},
}
// Pending status
statusPendingMap := map[string]interface{}{
"phase": corev1.ClaimPending,
}
pendingRaw1, _ := helper.BuildStatusRawExtension(statusBoundMap)
pendingRaw2, _ := helper.BuildStatusRawExtension(statusPendingMap)
aggregatedStatusItems3 := []workv1alpha2.AggregatedStatusItem{
{ClusterName: "member1", Status: pendingRaw1, Applied: true},
{ClusterName: "member2", Status: pendingRaw2, Applied: true},
}
// test aggregatePersistentVolumeClaimStatus function
oldPVC := &corev1.PersistentVolumeClaim{}
oldObj, _ := helper.ToUnstructured(oldPVC)
boundNewPVC := &corev1.PersistentVolumeClaim{Status: corev1.PersistentVolumeClaimStatus{Phase: corev1.ClaimBound}}
newBoundPVCObj, _ := helper.ToUnstructured(boundNewPVC)
lostNewPVC := &corev1.PersistentVolumeClaim{Status: corev1.PersistentVolumeClaimStatus{Phase: corev1.ClaimLost}}
newLostPVCObj, _ := helper.ToUnstructured(lostNewPVC)
pendingNewPVC := &corev1.PersistentVolumeClaim{Status: corev1.PersistentVolumeClaimStatus{Phase: corev1.ClaimPending}}
newPendingPVCObj, _ := helper.ToUnstructured(pendingNewPVC)
tests := []struct {
name string
curObj *unstructured.Unstructured
aggregatedStatusItems []workv1alpha2.AggregatedStatusItem
expectedObj *unstructured.Unstructured
}{
{
name: "update pvc status",
curObj: oldObj,
aggregatedStatusItems: aggregatedStatusItems1,
expectedObj: newBoundPVCObj,
},
{
name: "update pvc status",
curObj: oldObj,
aggregatedStatusItems: aggregatedStatusItems2,
expectedObj: newLostPVCObj,
},
{
name: "update pvc status",
curObj: oldObj,
aggregatedStatusItems: aggregatedStatusItems3,
expectedObj: newPendingPVCObj,
},
{
name: "ignore update pvc status as up to date",
curObj: newBoundPVCObj,
aggregatedStatusItems: aggregatedStatusItems1,
expectedObj: newBoundPVCObj,
},
}
for _, tt := range tests {
actualObj, _ := aggregatePersistentVolumeClaimStatus(tt.curObj, tt.aggregatedStatusItems)
assert.Equal(t, tt.expectedObj, actualObj)
}
}
func TestAggregatePVStatus(t *testing.T) {
statusAvailableMap := map[string]interface{}{
"phase": corev1.VolumeAvailable,
}
// Available status
availableRaw1, _ := helper.BuildStatusRawExtension(statusAvailableMap)
availableRaw2, _ := helper.BuildStatusRawExtension(statusAvailableMap)
aggregatedStatusItems1 := []workv1alpha2.AggregatedStatusItem{
{ClusterName: "member1", Status: availableRaw1, Applied: true},
{ClusterName: "member2", Status: availableRaw2, Applied: true},
}
statusReleasedMap := map[string]interface{}{
"phase": corev1.VolumeReleased,
}
// Release status
releasedRaw1, _ := helper.BuildStatusRawExtension(statusReleasedMap)
releasedRaw2, _ := helper.BuildStatusRawExtension(statusReleasedMap)
aggregatedStatusItems2 := []workv1alpha2.AggregatedStatusItem{
{ClusterName: "member1", Status: releasedRaw1, Applied: true},
{ClusterName: "member2", Status: releasedRaw2, Applied: true},
}
statusBoundMap := map[string]interface{}{
"phase": corev1.VolumeBound,
}
// Bound status
boundRaw1, _ := helper.BuildStatusRawExtension(statusBoundMap)
boundRaw2, _ := helper.BuildStatusRawExtension(statusReleasedMap)
aggregatedStatusItems3 := []workv1alpha2.AggregatedStatusItem{
{ClusterName: "member1", Status: boundRaw1, Applied: true},
{ClusterName: "member2", Status: boundRaw2, Applied: true},
}
// Failed status
statusFailedMap := map[string]interface{}{
"phase": corev1.VolumeFailed,
}
failedRaw1, _ := helper.BuildStatusRawExtension(releasedRaw1)
failedRaw2, _ := helper.BuildStatusRawExtension(statusFailedMap)
aggregatedStatusItems4 := []workv1alpha2.AggregatedStatusItem{
{ClusterName: "member1", Status: failedRaw1, Applied: true},
{ClusterName: "member2", Status: failedRaw2, Applied: true},
}
// Pending status
statusPendingMap := map[string]interface{}{
"phase": corev1.VolumePending,
}
pendingRaw1, _ := helper.BuildStatusRawExtension(statusAvailableMap)
pendingRaw2, _ := helper.BuildStatusRawExtension(statusPendingMap)
aggregatedStatusItems5 := []workv1alpha2.AggregatedStatusItem{
{ClusterName: "member1", Status: pendingRaw1, Applied: true},
{ClusterName: "member2", Status: pendingRaw2, Applied: true},
}
// test aggregatePersistentVolumeStatus function
oldPVC := &corev1.PersistentVolume{}
oldObj, _ := helper.ToUnstructured(oldPVC)
availableNewPv := &corev1.PersistentVolume{Status: corev1.PersistentVolumeStatus{Phase: corev1.VolumeAvailable}}
newAvailablePvObj, _ := helper.ToUnstructured(availableNewPv)
boundNewPV := &corev1.PersistentVolume{Status: corev1.PersistentVolumeStatus{Phase: corev1.VolumeBound}}
newBoundPVObj, _ := helper.ToUnstructured(boundNewPV)
releaseNewPV := &corev1.PersistentVolume{Status: corev1.PersistentVolumeStatus{Phase: corev1.VolumeReleased}}
newReleasePVObj, _ := helper.ToUnstructured(releaseNewPV)
failedNewPV := &corev1.PersistentVolume{Status: corev1.PersistentVolumeStatus{Phase: corev1.VolumeFailed}}
newFailedPVObj, _ := helper.ToUnstructured(failedNewPV)
pendingNewPV := &corev1.PersistentVolume{Status: corev1.PersistentVolumeStatus{Phase: corev1.VolumePending}}
newPendingPVObj, _ := helper.ToUnstructured(pendingNewPV)
tests := []struct {
name string
curObj *unstructured.Unstructured
aggregatedStatusItems []workv1alpha2.AggregatedStatusItem
expectedObj *unstructured.Unstructured
}{
{
name: "update pvc status1",
curObj: oldObj,
aggregatedStatusItems: aggregatedStatusItems1,
expectedObj: newAvailablePvObj,
},
{
name: "update pvc status2",
curObj: oldObj,
aggregatedStatusItems: aggregatedStatusItems2,
expectedObj: newReleasePVObj,
},
{
name: "update pvc status3",
curObj: oldObj,
aggregatedStatusItems: aggregatedStatusItems3,
expectedObj: newBoundPVObj,
},
{
name: "update pvc status4",
curObj: oldObj,
aggregatedStatusItems: aggregatedStatusItems4,
expectedObj: newFailedPVObj,
},
{
name: "update pvc status5",
curObj: oldObj,
aggregatedStatusItems: aggregatedStatusItems5,
expectedObj: newPendingPVObj,
},
{
name: "ignore update pvc status as up to date",
curObj: newAvailablePvObj,
aggregatedStatusItems: aggregatedStatusItems1,
expectedObj: newAvailablePvObj,
},
}
for _, tt := range tests {
actualObj, _ := aggregatePersistentVolumeStatus(tt.curObj, tt.aggregatedStatusItems)
assert.Equal(t, tt.expectedObj, actualObj, tt.name)
}
}
func TestAggregatedPodDisruptionBudgetStatus(t *testing.T) {
currPdbObj, _ := helper.ToUnstructured(&policyv1.PodDisruptionBudget{
Status: policyv1.PodDisruptionBudgetStatus{
CurrentHealthy: 1,
DesiredHealthy: 1,
DisruptionsAllowed: 1,
ExpectedPods: 1,
},
})
expectedPdbObj, _ := helper.ToUnstructured(&policyv1.PodDisruptionBudget{
Status: policyv1.PodDisruptionBudgetStatus{
CurrentHealthy: 2,
DesiredHealthy: 2,
DisruptionsAllowed: 2,
ExpectedPods: 2,
},
})
healthyStatusRaw, _ := helper.BuildStatusRawExtension(map[string]interface{}{
"currentHealthy": 1,
"desiredHealthy": 1,
"disruptionsAllowed": 1,
"expectedPods": 1,
})
evictionTime := metav1.Now()
unHealthyStatusRaw, _ := helper.BuildStatusRawExtension(map[string]interface{}{
"currentHealthy": 0,
"desiredHealthy": 1,
"disruptionsAllowed": 0,
"expectedPods": 1,
"disruptedPods": map[string]metav1.Time{
"pod-1234": evictionTime,
},
})
expectedUnhealthyPdbObj, _ := helper.ToUnstructured(&policyv1.PodDisruptionBudget{
Status: policyv1.PodDisruptionBudgetStatus{
CurrentHealthy: 0,
DesiredHealthy: 2,
DisruptionsAllowed: 0,
ExpectedPods: 2,
DisruptedPods: map[string]metav1.Time{
"member1/pod-1234": evictionTime,
"member2/pod-1234": evictionTime,
},
},
})
aggregateStatusItems := []workv1alpha2.AggregatedStatusItem{
{ClusterName: "member1", Status: healthyStatusRaw, Applied: true},
{ClusterName: "member2", Status: healthyStatusRaw, Applied: true},
}
unhealthyAggregateStatusItems := []workv1alpha2.AggregatedStatusItem{
{ClusterName: "member1", Status: unHealthyStatusRaw, Applied: true},
{ClusterName: "member2", Status: unHealthyStatusRaw, Applied: true},
}
for _, tt := range []struct {
name string
curObj *unstructured.Unstructured
aggregatedStatusItems []workv1alpha2.AggregatedStatusItem
expectedObj *unstructured.Unstructured
}{
{
name: "update pdb status",
curObj: currPdbObj,
aggregatedStatusItems: aggregateStatusItems,
expectedObj: expectedPdbObj,
},
{
name: "update pdb status with disrupted pods",
curObj: currPdbObj,
aggregatedStatusItems: unhealthyAggregateStatusItems,
expectedObj: expectedUnhealthyPdbObj,
},
{
name: "ignore update pdb status as up to date",
curObj: expectedPdbObj,
aggregatedStatusItems: aggregateStatusItems,
expectedObj: expectedPdbObj,
},
} {
t.Run(tt.name, func(t *testing.T) {
actualObj, _ := aggregatePodDisruptionBudgetStatus(tt.curObj, tt.aggregatedStatusItems)
assert.Equal(t, tt.expectedObj, actualObj)
})
}
}
func Test_aggregateCronJobStatus(t *testing.T) {
currCronJobObj, _ := helper.ToUnstructured(&batchv1.CronJob{
Status: batchv1.CronJobStatus{
Active: []corev1.ObjectReference{},
LastScheduleTime: nil,
LastSuccessfulTime: nil,
},
})
cronjobStatusRaw1, _ := helper.BuildStatusRawExtension(map[string]interface{}{
"active": []corev1.ObjectReference{
{
APIVersion: "batch/v1",
Kind: "Job",
Name: "foo",
Namespace: "default",
ResourceVersion: "1",
UID: "1d5db04f-f2e8-4807-b6d4-7b78f402250d",
},
},
"lastScheduleTime": "2023-02-08T07:16:00Z",
"lastSuccessfulTime": nil,
})
cronjobStatusRaw2, _ := helper.BuildStatusRawExtension(map[string]interface{}{
"active": []corev1.ObjectReference{
{
APIVersion: "batch/v1",
Kind: "Job",
Name: "foo",
Namespace: "default",
ResourceVersion: "1",
UID: "1d5db04f-f2e8-4807-b6d4-7b78f402250d",
},
},
"lastScheduleTime": "2023-02-08T07:17:00Z",
"lastSuccessfulTime": "2023-02-08T07:17:00Z",
})
parse, _ := time.Parse("2006-01-02 15:04:05", "2023-02-08 07:17:00")
successfulTime := metav1.NewTime(parse)
expectedCronJobObj, _ := helper.ToUnstructured(&batchv1.CronJob{
Status: batchv1.CronJobStatus{
Active: []corev1.ObjectReference{
{
APIVersion: "batch/v1",
Kind: "Job",
Name: "foo",
Namespace: "default",
ResourceVersion: "1",
UID: "1d5db04f-f2e8-4807-b6d4-7b78f402250d",
},
{
APIVersion: "batch/v1",
Kind: "Job",
Name: "foo",
Namespace: "default",
ResourceVersion: "1",
UID: "1d5db04f-f2e8-4807-b6d4-7b78f402250d",
},
},
LastScheduleTime: &successfulTime,
LastSuccessfulTime: &successfulTime,
},
})
aggregateStatusItems := []workv1alpha2.AggregatedStatusItem{
{ClusterName: "member1", Status: cronjobStatusRaw1, Applied: true},
{ClusterName: "member2", Status: cronjobStatusRaw2, Applied: true},
}
for _, tt := range []struct {
name string
curObj *unstructured.Unstructured
aggregatedStatusItems []workv1alpha2.AggregatedStatusItem
expectedObj *unstructured.Unstructured
}{
{
name: "update cronjob status",
curObj: currCronJobObj,
expectedObj: expectedCronJobObj,
aggregatedStatusItems: aggregateStatusItems,
},
} {
t.Run(tt.name, func(t *testing.T) {
actualObj, _ := aggregateCronJobStatus(tt.curObj, tt.aggregatedStatusItems)
assert.Equal(t, tt.expectedObj, actualObj)
})
}
}
func Test_aggregateHorizontalPodAutoscalerStatus(t *testing.T) {
curHPA, _ := helper.ToUnstructured(&autoscalingv2.HorizontalPodAutoscaler{
Status: autoscalingv2.HorizontalPodAutoscalerStatus{
CurrentReplicas: 0,
DesiredReplicas: 0,
},
})
aggregatedStatusItem1, _ := helper.BuildStatusRawExtension(map[string]interface{}{
"currentReplicas": 2,
"desiredReplicas": 2,
})
aggregatedStatusItem2, _ := helper.BuildStatusRawExtension(map[string]interface{}{
"currentReplicas": 4,
"desiredReplicas": 4,
})
expectHPA, _ := helper.ToUnstructured(&autoscalingv2.HorizontalPodAutoscaler{
Status: autoscalingv2.HorizontalPodAutoscalerStatus{
CurrentReplicas: 6,
DesiredReplicas: 6,
},
})
type args struct {
object *unstructured.Unstructured
aggregatedStatusItems []workv1alpha2.AggregatedStatusItem
}
tests := []struct {
name string
args args
want *unstructured.Unstructured
wantErr bool
}{
{
name: "update hpa status",
args: args{
object: curHPA,
aggregatedStatusItems: []workv1alpha2.AggregatedStatusItem{
{ClusterName: "member1", Status: aggregatedStatusItem1, Applied: true},
{ClusterName: "member2", Status: aggregatedStatusItem2, Applied: true},
{ClusterName: "member3", Status: nil, Applied: true},
},
},
want: expectHPA,
wantErr: false,
},
{
name: "hpa status update to dates",
args: args{
object: expectHPA,
aggregatedStatusItems: []workv1alpha2.AggregatedStatusItem{
{ClusterName: "member1", Status: aggregatedStatusItem1, Applied: true},
{ClusterName: "member2", Status: aggregatedStatusItem2, Applied: true},
{ClusterName: "member3", Status: nil, Applied: true},
},
},
want: expectHPA,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := aggregateHorizontalPodAutoscalerStatus(tt.args.object, tt.args.aggregatedStatusItems)
if (err != nil) != tt.wantErr {
t.Errorf("Test_aggregateHorizontalPodAutoscalerStatus() err = %v, wantErr %v", err, tt.wantErr)
}
assert.Equalf(t, tt.want, got, "aggregateHorizontalPodAutoscalerStatus(%v, %v)", tt.args.object, tt.args.aggregatedStatusItems)
})
}
}