karmada/pkg/scheduler/helper_test.go

507 lines
17 KiB
Go

/*
Copyright 2023 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 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)
}
})
}
}