/* 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 spreadconstraint import ( "k8s.io/utils/ptr" 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/scheduler/framework" ) // GroupClustersInfo indicate the cluster global view type GroupClustersInfo struct { Providers map[string]ProviderInfo Regions map[string]RegionInfo Zones map[string]ZoneInfo // Clusters from global view, sorted by cluster.Score descending. Clusters []ClusterDetailInfo calAvailableReplicasFunc func(clusters []*clusterv1alpha1.Cluster, spec *workv1alpha2.ResourceBindingSpec) []workv1alpha2.TargetCluster } // ProviderInfo indicate the provider information type ProviderInfo struct { Name string Score int64 // the highest score in all clusters of the provider AvailableReplicas int64 // Regions under this provider Regions map[string]struct{} // Zones under this provider Zones map[string]struct{} // Clusters under this provider, sorted by cluster.Score descending. Clusters []ClusterDetailInfo } // RegionInfo indicate the region information type RegionInfo struct { Name string Score int64 // the highest score in all clusters of the region AvailableReplicas int64 // Zones under this provider Zones map[string]struct{} // Clusters under this region, sorted by cluster.Score descending. Clusters []ClusterDetailInfo } // ZoneInfo indicate the zone information type ZoneInfo struct { Name string Score int64 // the highest score in all clusters of the zone AvailableReplicas int64 // Clusters under this zone, sorted by cluster.Score descending. Clusters []ClusterDetailInfo } // ClusterDetailInfo indicate the cluster information type ClusterDetailInfo struct { Name string Score int64 AvailableReplicas int64 Cluster *clusterv1alpha1.Cluster } // GroupClustersWithScore groups cluster base provider/region/zone/cluster func GroupClustersWithScore( clustersScore framework.ClusterScoreList, placement *policyv1alpha1.Placement, spec *workv1alpha2.ResourceBindingSpec, calAvailableReplicasFunc func(clusters []*clusterv1alpha1.Cluster, spec *workv1alpha2.ResourceBindingSpec) []workv1alpha2.TargetCluster, ) *GroupClustersInfo { if isTopologyIgnored(placement) { return groupClustersIgnoringTopology(clustersScore, spec, calAvailableReplicasFunc) } return groupClustersBasedTopology(clustersScore, spec, placement.SpreadConstraints, calAvailableReplicasFunc) } func groupClustersBasedTopology( clustersScore framework.ClusterScoreList, rbSpec *workv1alpha2.ResourceBindingSpec, spreadConstraints []policyv1alpha1.SpreadConstraint, calAvailableReplicasFunc func(clusters []*clusterv1alpha1.Cluster, spec *workv1alpha2.ResourceBindingSpec) []workv1alpha2.TargetCluster, ) *GroupClustersInfo { groupClustersInfo := &GroupClustersInfo{ Providers: make(map[string]ProviderInfo), Regions: make(map[string]RegionInfo), Zones: make(map[string]ZoneInfo), } groupClustersInfo.calAvailableReplicasFunc = calAvailableReplicasFunc groupClustersInfo.generateClustersInfo(clustersScore, rbSpec) groupClustersInfo.generateZoneInfo(spreadConstraints) groupClustersInfo.generateRegionInfo(spreadConstraints) groupClustersInfo.generateProviderInfo(spreadConstraints) return groupClustersInfo } func groupClustersIgnoringTopology( clustersScore framework.ClusterScoreList, rbSpec *workv1alpha2.ResourceBindingSpec, calAvailableReplicasFunc func(clusters []*clusterv1alpha1.Cluster, spec *workv1alpha2.ResourceBindingSpec) []workv1alpha2.TargetCluster, ) *GroupClustersInfo { groupClustersInfo := &GroupClustersInfo{} groupClustersInfo.calAvailableReplicasFunc = calAvailableReplicasFunc groupClustersInfo.generateClustersInfo(clustersScore, rbSpec) return groupClustersInfo } func (info *GroupClustersInfo) generateClustersInfo(clustersScore framework.ClusterScoreList, rbSpec *workv1alpha2.ResourceBindingSpec) { var clusters []*clusterv1alpha1.Cluster for _, clusterScore := range clustersScore { clusterInfo := ClusterDetailInfo{} clusterInfo.Name = clusterScore.Cluster.Name clusterInfo.Score = clusterScore.Score clusterInfo.Cluster = clusterScore.Cluster info.Clusters = append(info.Clusters, clusterInfo) clusters = append(clusters, clusterScore.Cluster) } clustersReplicas := info.calAvailableReplicasFunc(clusters, rbSpec) for i, clustersReplica := range clustersReplicas { info.Clusters[i].AvailableReplicas = int64(clustersReplica.Replicas) info.Clusters[i].AvailableReplicas += int64(rbSpec.AssignedReplicasForCluster(clustersReplica.Name)) } sortClusters(info.Clusters, func(i *ClusterDetailInfo, j *ClusterDetailInfo) *bool { if i.AvailableReplicas != j.AvailableReplicas { return ptr.To(i.AvailableReplicas > j.AvailableReplicas) } return nil }) } func (info *GroupClustersInfo) generateZoneInfo(spreadConstraints []policyv1alpha1.SpreadConstraint) { if !IsSpreadConstraintExisted(spreadConstraints, policyv1alpha1.SpreadByFieldZone) { return } for _, clusterInfo := range info.Clusters { zones := clusterInfo.Cluster.Spec.Zones if len(zones) == 0 { continue } for _, zone := range zones { zoneInfo, ok := info.Zones[zone] if !ok { zoneInfo = ZoneInfo{ Name: zone, Clusters: make([]ClusterDetailInfo, 0), } } zoneInfo.Clusters = append(zoneInfo.Clusters, clusterInfo) zoneInfo.AvailableReplicas += clusterInfo.AvailableReplicas info.Zones[zone] = zoneInfo } } for zone, zoneInfo := range info.Zones { zoneInfo.Score = zoneInfo.Clusters[0].Score info.Zones[zone] = zoneInfo } } func (info *GroupClustersInfo) generateRegionInfo(spreadConstraints []policyv1alpha1.SpreadConstraint) { if !IsSpreadConstraintExisted(spreadConstraints, policyv1alpha1.SpreadByFieldRegion) { return } for _, clusterInfo := range info.Clusters { region := clusterInfo.Cluster.Spec.Region if region == "" { continue } regionInfo, ok := info.Regions[region] if !ok { regionInfo = RegionInfo{ Name: region, Zones: make(map[string]struct{}), Clusters: make([]ClusterDetailInfo, 0), } } if clusterInfo.Cluster.Spec.Zone != "" { regionInfo.Zones[clusterInfo.Cluster.Spec.Zone] = struct{}{} } regionInfo.Clusters = append(regionInfo.Clusters, clusterInfo) regionInfo.AvailableReplicas += clusterInfo.AvailableReplicas info.Regions[region] = regionInfo } for region, regionInfo := range info.Regions { regionInfo.Score = regionInfo.Clusters[0].Score info.Regions[region] = regionInfo } } func (info *GroupClustersInfo) generateProviderInfo(spreadConstraints []policyv1alpha1.SpreadConstraint) { if !IsSpreadConstraintExisted(spreadConstraints, policyv1alpha1.SpreadByFieldProvider) { return } for _, clusterInfo := range info.Clusters { provider := clusterInfo.Cluster.Spec.Provider if provider == "" { continue } providerInfo, ok := info.Providers[provider] if !ok { providerInfo = ProviderInfo{ Name: provider, Regions: make(map[string]struct{}), Zones: make(map[string]struct{}), Clusters: make([]ClusterDetailInfo, 0), } } if clusterInfo.Cluster.Spec.Zone != "" { providerInfo.Zones[clusterInfo.Cluster.Spec.Zone] = struct{}{} } if clusterInfo.Cluster.Spec.Region != "" { providerInfo.Regions[clusterInfo.Cluster.Spec.Region] = struct{}{} } providerInfo.Clusters = append(providerInfo.Clusters, clusterInfo) providerInfo.AvailableReplicas += clusterInfo.AvailableReplicas info.Providers[provider] = providerInfo } for provider, providerInfo := range info.Providers { providerInfo.Score = providerInfo.Clusters[0].Score info.Providers[provider] = providerInfo } } func isTopologyIgnored(placement *policyv1alpha1.Placement) bool { spreadConstraints := placement.SpreadConstraints if len(spreadConstraints) == 0 || (len(spreadConstraints) == 1 && spreadConstraints[0].SpreadByField == policyv1alpha1.SpreadByFieldCluster) { return true } return shouldIgnoreSpreadConstraint(placement) }