package scheduler import ( "encoding/json" "errors" "testing" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilerrors "k8s.io/apimachinery/pkg/util/errors" policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1" workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2" "github.com/karmada-io/karmada/pkg/scheduler/framework" ) func Test_needConsideredPlacementChanged(t *testing.T) { type args struct { placement policyv1alpha1.Placement appliedPlacement *policyv1alpha1.Placement schedulerObservingAffinityName string } tests := []struct { name string args args want bool }{ { name: "nil appliedPlacement", args: args{ placement: policyv1alpha1.Placement{ ClusterAffinity: &policyv1alpha1.ClusterAffinity{ClusterNames: []string{"m1"}}, }, appliedPlacement: nil, schedulerObservingAffinityName: "", }, want: true, }, { name: "same placement and appliedPlacement", args: args{ placement: policyv1alpha1.Placement{ ClusterAffinity: &policyv1alpha1.ClusterAffinity{ClusterNames: []string{"m1"}}, ClusterTolerations: []corev1.Toleration{ {Key: "foo", Operator: corev1.TolerationOpEqual, Value: "foo", Effect: corev1.TaintEffectNoSchedule}, }, SpreadConstraints: []policyv1alpha1.SpreadConstraint{ {MinGroups: 1, MaxGroups: 1, SpreadByField: policyv1alpha1.SpreadByFieldCluster}, }, ReplicaScheduling: &policyv1alpha1.ReplicaSchedulingStrategy{ ReplicaSchedulingType: policyv1alpha1.ReplicaSchedulingTypeDuplicated, }, }, appliedPlacement: &policyv1alpha1.Placement{ ClusterAffinity: &policyv1alpha1.ClusterAffinity{ClusterNames: []string{"m1"}}, ClusterTolerations: []corev1.Toleration{ {Key: "foo", Operator: corev1.TolerationOpEqual, Value: "foo", Effect: corev1.TaintEffectNoSchedule}, }, SpreadConstraints: []policyv1alpha1.SpreadConstraint{ {MinGroups: 1, MaxGroups: 1, SpreadByField: policyv1alpha1.SpreadByFieldCluster}, }, ReplicaScheduling: &policyv1alpha1.ReplicaSchedulingStrategy{ ReplicaSchedulingType: policyv1alpha1.ReplicaSchedulingTypeDuplicated, }, }, schedulerObservingAffinityName: "", }, want: false, }, { name: "different ClusterAffinity field in placement and appliedPlacement", args: args{ placement: policyv1alpha1.Placement{ ClusterAffinity: nil, ClusterTolerations: []corev1.Toleration{ {Key: "foo", Operator: corev1.TolerationOpEqual, Value: "foo", Effect: corev1.TaintEffectNoSchedule}, }, SpreadConstraints: []policyv1alpha1.SpreadConstraint{ {MinGroups: 1, MaxGroups: 1, SpreadByField: policyv1alpha1.SpreadByFieldCluster}, }, ReplicaScheduling: &policyv1alpha1.ReplicaSchedulingStrategy{ ReplicaSchedulingType: policyv1alpha1.ReplicaSchedulingTypeDuplicated, }, }, appliedPlacement: &policyv1alpha1.Placement{ ClusterAffinity: &policyv1alpha1.ClusterAffinity{ClusterNames: []string{"m2"}}, ClusterTolerations: []corev1.Toleration{ {Key: "foo", Operator: corev1.TolerationOpEqual, Value: "foo", Effect: corev1.TaintEffectNoSchedule}, }, SpreadConstraints: []policyv1alpha1.SpreadConstraint{ {MinGroups: 1, MaxGroups: 1, SpreadByField: policyv1alpha1.SpreadByFieldCluster}, }, ReplicaScheduling: &policyv1alpha1.ReplicaSchedulingStrategy{ ReplicaSchedulingType: policyv1alpha1.ReplicaSchedulingTypeDuplicated, }, }, schedulerObservingAffinityName: "", }, want: true, }, { name: "different ClusterTolerations field in placement and appliedPlacement", args: args{ placement: policyv1alpha1.Placement{ ClusterAffinity: &policyv1alpha1.ClusterAffinity{ClusterNames: []string{"m1"}}, ClusterTolerations: []corev1.Toleration{ {Key: "foo", Operator: corev1.TolerationOpEqual, Value: "foo", Effect: corev1.TaintEffectNoSchedule}, }, SpreadConstraints: []policyv1alpha1.SpreadConstraint{ {MinGroups: 1, MaxGroups: 1, SpreadByField: policyv1alpha1.SpreadByFieldCluster}, }, ReplicaScheduling: &policyv1alpha1.ReplicaSchedulingStrategy{ ReplicaSchedulingType: policyv1alpha1.ReplicaSchedulingTypeDuplicated, }, }, appliedPlacement: &policyv1alpha1.Placement{ ClusterAffinity: &policyv1alpha1.ClusterAffinity{ClusterNames: []string{"m1"}}, ClusterTolerations: []corev1.Toleration{ {Key: "foo", Operator: corev1.TolerationOpExists, Value: "foo", Effect: corev1.TaintEffectNoSchedule}, }, SpreadConstraints: []policyv1alpha1.SpreadConstraint{ {MinGroups: 1, MaxGroups: 1, SpreadByField: policyv1alpha1.SpreadByFieldCluster}, }, ReplicaScheduling: &policyv1alpha1.ReplicaSchedulingStrategy{ ReplicaSchedulingType: policyv1alpha1.ReplicaSchedulingTypeDuplicated, }, }, schedulerObservingAffinityName: "", }, want: true, }, { name: "different SpreadConstraints field in placement and appliedPlacement", args: args{ placement: policyv1alpha1.Placement{ ClusterAffinity: &policyv1alpha1.ClusterAffinity{ClusterNames: []string{"m1"}}, ClusterTolerations: []corev1.Toleration{ {Key: "foo", Operator: corev1.TolerationOpEqual, Value: "foo", Effect: corev1.TaintEffectNoSchedule}, }, SpreadConstraints: []policyv1alpha1.SpreadConstraint{ {MinGroups: 1, MaxGroups: 1, SpreadByField: policyv1alpha1.SpreadByFieldCluster}, }, ReplicaScheduling: &policyv1alpha1.ReplicaSchedulingStrategy{ ReplicaSchedulingType: policyv1alpha1.ReplicaSchedulingTypeDuplicated, }, }, appliedPlacement: &policyv1alpha1.Placement{ ClusterAffinity: &policyv1alpha1.ClusterAffinity{ClusterNames: []string{"m1"}}, ClusterTolerations: []corev1.Toleration{ {Key: "foo", Operator: corev1.TolerationOpEqual, Value: "foo", Effect: corev1.TaintEffectNoSchedule}, }, SpreadConstraints: []policyv1alpha1.SpreadConstraint{ {MinGroups: 1, MaxGroups: 2, SpreadByField: policyv1alpha1.SpreadByFieldCluster}, }, ReplicaScheduling: &policyv1alpha1.ReplicaSchedulingStrategy{ ReplicaSchedulingType: policyv1alpha1.ReplicaSchedulingTypeDuplicated, }, }, schedulerObservingAffinityName: "", }, want: true, }, { name: "different ReplicaScheduling field in placement and appliedPlacement", args: args{ placement: policyv1alpha1.Placement{ ClusterAffinity: &policyv1alpha1.ClusterAffinity{ClusterNames: []string{"m1"}}, ClusterTolerations: []corev1.Toleration{ {Key: "foo", Operator: corev1.TolerationOpEqual, Value: "foo", Effect: corev1.TaintEffectNoSchedule}, }, SpreadConstraints: []policyv1alpha1.SpreadConstraint{ {MinGroups: 1, MaxGroups: 1, SpreadByField: policyv1alpha1.SpreadByFieldCluster}, }, ReplicaScheduling: &policyv1alpha1.ReplicaSchedulingStrategy{ ReplicaSchedulingType: policyv1alpha1.ReplicaSchedulingTypeDuplicated, }, }, appliedPlacement: &policyv1alpha1.Placement{ ClusterAffinity: &policyv1alpha1.ClusterAffinity{ClusterNames: []string{"m1"}}, ClusterTolerations: []corev1.Toleration{ {Key: "foo", Operator: corev1.TolerationOpEqual, Value: "foo", Effect: corev1.TaintEffectNoSchedule}, }, SpreadConstraints: []policyv1alpha1.SpreadConstraint{ {MinGroups: 1, MaxGroups: 1, SpreadByField: policyv1alpha1.SpreadByFieldCluster}, }, ReplicaScheduling: &policyv1alpha1.ReplicaSchedulingStrategy{ ReplicaSchedulingType: policyv1alpha1.ReplicaSchedulingTypeDivided, }, }, schedulerObservingAffinityName: "", }, want: true, }, { name: "different ClusterAffinities field with empty schedulerObservingAffinityName", args: args{ placement: policyv1alpha1.Placement{ ClusterAffinities: []policyv1alpha1.ClusterAffinityTerm{ { AffinityName: "group1", ClusterAffinity: policyv1alpha1.ClusterAffinity{ClusterNames: []string{"m1"}}, }, }, }, appliedPlacement: &policyv1alpha1.Placement{}, schedulerObservingAffinityName: "", }, want: true, }, { name: "update the target ClusterAffinityTerm", args: args{ placement: policyv1alpha1.Placement{ ClusterAffinities: []policyv1alpha1.ClusterAffinityTerm{ { AffinityName: "group1", ClusterAffinity: policyv1alpha1.ClusterAffinity{ClusterNames: []string{"m1", "m2"}}, }, }, }, appliedPlacement: &policyv1alpha1.Placement{ ClusterAffinities: []policyv1alpha1.ClusterAffinityTerm{ { AffinityName: "group1", ClusterAffinity: policyv1alpha1.ClusterAffinity{ClusterNames: []string{"m1"}}, }, }, }, schedulerObservingAffinityName: "group1", }, want: true, }, { name: "delete the target ClusterAffinityTerm", args: args{ placement: policyv1alpha1.Placement{ ClusterAffinities: []policyv1alpha1.ClusterAffinityTerm{ { AffinityName: "group2", ClusterAffinity: policyv1alpha1.ClusterAffinity{ClusterNames: []string{"m2"}}, }, }, }, appliedPlacement: &policyv1alpha1.Placement{ ClusterAffinities: []policyv1alpha1.ClusterAffinityTerm{ { AffinityName: "group1", ClusterAffinity: policyv1alpha1.ClusterAffinity{ClusterNames: []string{"m1"}}, }, { AffinityName: "group2", ClusterAffinity: policyv1alpha1.ClusterAffinity{ClusterNames: []string{"m2"}}, }, }, }, schedulerObservingAffinityName: "group1", }, want: true, }, { name: "delete the whole ClusterAffinities", args: args{ placement: policyv1alpha1.Placement{}, appliedPlacement: &policyv1alpha1.Placement{ ClusterAffinities: []policyv1alpha1.ClusterAffinityTerm{ { AffinityName: "group1", ClusterAffinity: policyv1alpha1.ClusterAffinity{ClusterNames: []string{"m1"}}, }, }, }, schedulerObservingAffinityName: "group1", }, want: true, }, { name: "add a new ClusterAffinityTerm and don't update the target ClusterAffinityTerm ", args: args{ placement: policyv1alpha1.Placement{ ClusterAffinities: []policyv1alpha1.ClusterAffinityTerm{ { AffinityName: "group1", ClusterAffinity: policyv1alpha1.ClusterAffinity{ClusterNames: []string{"m1"}}, }, { AffinityName: "group2", ClusterAffinity: policyv1alpha1.ClusterAffinity{ClusterNames: []string{"m1", "m2"}}, }, }, }, appliedPlacement: &policyv1alpha1.Placement{ ClusterAffinities: []policyv1alpha1.ClusterAffinityTerm{ { AffinityName: "group1", ClusterAffinity: policyv1alpha1.ClusterAffinity{ClusterNames: []string{"m1"}}, }, }, }, schedulerObservingAffinityName: "group1", }, want: false, }, { name: "update a ClusterAffinityTerm and don't update the target ClusterAffinityTerm ", args: args{ placement: policyv1alpha1.Placement{ ClusterAffinities: []policyv1alpha1.ClusterAffinityTerm{ { AffinityName: "group1", ClusterAffinity: policyv1alpha1.ClusterAffinity{ClusterNames: []string{"m1"}}, }, { AffinityName: "group2", ClusterAffinity: policyv1alpha1.ClusterAffinity{ClusterNames: []string{"m1", "m2"}}, }, }, }, appliedPlacement: &policyv1alpha1.Placement{ ClusterAffinities: []policyv1alpha1.ClusterAffinityTerm{ { AffinityName: "group1", ClusterAffinity: policyv1alpha1.ClusterAffinity{ClusterNames: []string{"m1"}}, }, { AffinityName: "group2", ClusterAffinity: policyv1alpha1.ClusterAffinity{ClusterNames: []string{"m2"}}, }, }, }, schedulerObservingAffinityName: "group1", }, want: false, }, { name: "delete a ClusterAffinityTerm and don't update the target ClusterAffinityTerm ", args: args{ placement: policyv1alpha1.Placement{ ClusterAffinities: []policyv1alpha1.ClusterAffinityTerm{ { AffinityName: "group1", ClusterAffinity: policyv1alpha1.ClusterAffinity{ClusterNames: []string{"m1"}}, }, }, }, appliedPlacement: &policyv1alpha1.Placement{ ClusterAffinities: []policyv1alpha1.ClusterAffinityTerm{ { AffinityName: "group1", ClusterAffinity: policyv1alpha1.ClusterAffinity{ClusterNames: []string{"m1"}}, }, { AffinityName: "group2", ClusterAffinity: policyv1alpha1.ClusterAffinity{ClusterNames: []string{"m2"}}, }, }, }, schedulerObservingAffinityName: "group1", }, want: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var appliedPlacementStr string if tt.args.appliedPlacement == nil { appliedPlacementStr = "" } else { placementBytes, err := json.Marshal(*tt.args.appliedPlacement) if err != nil { t.Errorf("jsom marshal failed: %v", err) } appliedPlacementStr = string(placementBytes) } if got := placementChanged(tt.args.placement, appliedPlacementStr, tt.args.schedulerObservingAffinityName); got != tt.want { t.Errorf("placementChanged() = %v, want %v", got, tt.want) } }) } } func Test_getAffinityIndex(t *testing.T) { type args struct { affinities []policyv1alpha1.ClusterAffinityTerm observedName string } tests := []struct { name string args args want int }{ { name: "empty observedName", args: args{ affinities: []policyv1alpha1.ClusterAffinityTerm{ {AffinityName: "group1"}, {AffinityName: "group2"}, {AffinityName: "group3"}, }, observedName: "", }, want: 0, }, { name: "observedName can not find in affinities", args: args{ affinities: []policyv1alpha1.ClusterAffinityTerm{ {AffinityName: "group1"}, {AffinityName: "group2"}, {AffinityName: "group3"}, }, observedName: "group0", }, want: 0, }, { name: "observedName can find in affinities", args: args{ affinities: []policyv1alpha1.ClusterAffinityTerm{ {AffinityName: "group1"}, {AffinityName: "group2"}, {AffinityName: "group3"}, }, observedName: "group3", }, want: 2, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := getAffinityIndex(tt.args.affinities, tt.args.observedName); got != tt.want { t.Errorf("getAffinityIndex() = %v, want %v", got, tt.want) } }) } } func Test_getConditionByError(t *testing.T) { tests := []struct { name string err error expectedCondition metav1.Condition ignoreErr bool }{ { name: "no error", err: nil, expectedCondition: metav1.Condition{Type: workv1alpha2.Scheduled, Reason: workv1alpha2.BindingReasonSuccess, Status: metav1.ConditionTrue}, ignoreErr: true, }, { name: "failed to schedule", err: utilerrors.NewAggregate([]error{errors.New("")}), expectedCondition: metav1.Condition{Type: workv1alpha2.Scheduled, Reason: workv1alpha2.BindingReasonSchedulerError, Status: metav1.ConditionFalse}, ignoreErr: false, }, { name: "no cluster fit", err: &framework.FitError{}, expectedCondition: metav1.Condition{Type: workv1alpha2.Scheduled, Reason: workv1alpha2.BindingReasonNoClusterFit, Status: metav1.ConditionFalse}, ignoreErr: true, }, { name: "aggregated fit error", err: utilerrors.NewAggregate([]error{&framework.FitError{}, errors.New("")}), expectedCondition: metav1.Condition{Type: workv1alpha2.Scheduled, Reason: workv1alpha2.BindingReasonNoClusterFit, Status: metav1.ConditionFalse}, ignoreErr: false, }, { name: "unschedulable error", err: &framework.UnschedulableError{}, expectedCondition: metav1.Condition{Type: workv1alpha2.Scheduled, Reason: workv1alpha2.BindingReasonUnschedulable, Status: metav1.ConditionFalse}, ignoreErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { condition, ignoreErr := getConditionByError(tt.err) if condition.Type != tt.expectedCondition.Type || condition.Reason != tt.expectedCondition.Reason || condition.Status != tt.expectedCondition.Status { t.Errorf("expected condition: (%s, %s, %s), but got (%s, %s, %s)", tt.expectedCondition.Type, tt.expectedCondition.Reason, tt.expectedCondition.Status, condition.Type, condition.Reason, condition.Status) } if ignoreErr != tt.ignoreErr { t.Errorf("expect to ignore error: %v. but got: %v", tt.ignoreErr, ignoreErr) } }) } }