package core import ( "fmt" "sort" clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1" 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/util" "github.com/karmada-io/karmada/pkg/util/helper" ) // TargetClustersList is a slice of TargetCluster that implements sort.Interface to sort by Value. type TargetClustersList []workv1alpha2.TargetCluster func (a TargetClustersList) Len() int { return len(a) } func (a TargetClustersList) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a TargetClustersList) Less(i, j int) bool { return a[i].Replicas > a[j].Replicas } func getStaticWeightInfoList(clusters []*clusterv1alpha1.Cluster, weightList []policyv1alpha1.StaticClusterWeight) helper.ClusterWeightInfoList { list := make(helper.ClusterWeightInfoList, 0) for _, cluster := range clusters { var weight int64 for _, staticWeightRule := range weightList { if util.ClusterMatches(cluster, staticWeightRule.TargetCluster) { weight = util.MaxInt64(weight, staticWeightRule.Weight) } } if weight > 0 { list = append(list, helper.ClusterWeightInfo{ ClusterName: cluster.Name, Weight: weight, }) } } if list.GetWeightSum() == 0 { for _, cluster := range clusters { list = append(list, helper.ClusterWeightInfo{ ClusterName: cluster.Name, Weight: 1, }) } } return list } // dynamicDivideReplicas assigns a total number of replicas to the selected clusters by preference according to the resource. func dynamicDivideReplicas(state *assignState) ([]workv1alpha2.TargetCluster, error) { if state.availableReplicas < state.targetReplicas { return nil, fmt.Errorf("clusters resources are not enough to schedule, max %d replicas are support", state.availableReplicas) } switch state.strategyType { case AggregatedStrategy: state.availableClusters = state.resortAvailableClusters() var sum int32 for i := range state.availableClusters { if sum += state.availableClusters[i].Replicas; sum >= state.targetReplicas { state.availableClusters = state.availableClusters[:i+1] break } } fallthrough case DynamicWeightStrategy: // Set the availableClusters as the weight, scheduledClusters as init result, target as the dispenser object. // After dispensing, the target cluster will be the combination of init result and weighted result for target replicas. return helper.SpreadReplicasByTargetClusters(state.targetReplicas, state.availableClusters, state.scheduledClusters), nil default: // should never happen return nil, fmt.Errorf("undefined strategy type: %s", state.strategyType) } } func dynamicScaleDown(state *assignState) ([]workv1alpha2.TargetCluster, error) { // The previous scheduling result will be the weight reference of scaling down. // In other words, we scale down the replicas proportionally by their scheduled replicas. // Now: // 1. targetReplicas is set to desired replicas. // 2. availableClusters is set to the former schedule result. // 3. scheduledClusters and assignedReplicas are not set, which implicates we consider this action as a first schedule. state.targetReplicas = state.spec.Replicas state.scheduledClusters = nil state.buildAvailableClusters(func(_ []*clusterv1alpha1.Cluster, spec *workv1alpha2.ResourceBindingSpec) []workv1alpha2.TargetCluster { availableClusters := make(TargetClustersList, len(spec.Clusters)) copy(availableClusters, spec.Clusters) sort.Sort(availableClusters) return availableClusters }) return dynamicDivideReplicas(state) } func dynamicScaleUp(state *assignState) ([]workv1alpha2.TargetCluster, error) { // Target is the extra ones. state.targetReplicas = state.spec.Replicas - state.assignedReplicas state.buildAvailableClusters(func(clusters []*clusterv1alpha1.Cluster, spec *workv1alpha2.ResourceBindingSpec) []workv1alpha2.TargetCluster { clusterAvailableReplicas := calAvailableReplicas(clusters, spec) sort.Sort(TargetClustersList(clusterAvailableReplicas)) return clusterAvailableReplicas }) return dynamicDivideReplicas(state) }