package core import ( "context" "encoding/json" "fmt" "math" "time" "github.com/kr/pretty" "k8s.io/klog/v2" workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2" estimatorclient "github.com/karmada-io/karmada/pkg/estimator/client" "github.com/karmada-io/karmada/pkg/util" ) // SchedulingResultHelper is a helper to wrap the ResourceBinding and its target cluster result. type SchedulingResultHelper struct { *workv1alpha2.ResourceBinding TargetClusters []*TargetClusterWrapper } // NewSchedulingResultHelper returns a new SchedulingResultHelper based on ResourceBinding. func NewSchedulingResultHelper(binding *workv1alpha2.ResourceBinding) *SchedulingResultHelper { h := &SchedulingResultHelper{ResourceBinding: binding} readyReplicas := getReadyReplicas(binding) for i := range binding.Spec.Clusters { targetCluster := &binding.Spec.Clusters[i] targetClusterHelper := &TargetClusterWrapper{ ClusterName: targetCluster.Name, Spec: targetCluster.Replicas, } if ready, exist := readyReplicas[targetCluster.Name]; exist { targetClusterHelper.Ready = ready } else { targetClusterHelper.Ready = estimatorclient.UnauthenticReplica } h.TargetClusters = append(h.TargetClusters, targetClusterHelper) } return h } // FillUnschedulableReplicas will detect the unschedulable replicas of member cluster by calling // unschedulable replica estimators and fill the unschedulable field of TargetClusterWrapper. func (h *SchedulingResultHelper) FillUnschedulableReplicas(unschedulableThreshold time.Duration) { reference := &h.Spec.Resource undesiredClusters, undesiredClusterNames := h.GetUndesiredClusters() // Set the boundary. for i := range undesiredClusters { undesiredClusters[i].Unschedulable = math.MaxInt32 } // Get the minimum value of MaxAvailableReplicas in terms of all estimators. estimators := estimatorclient.GetUnschedulableReplicaEstimators() ctx := context.WithValue(context.TODO(), util.ContextKeyObject, fmt.Sprintf("kind=%s, name=%s/%s", reference.Kind, reference.Namespace, reference.Name)) for _, estimator := range estimators { res, err := estimator.GetUnschedulableReplicas(ctx, undesiredClusterNames, reference, unschedulableThreshold) if err != nil { klog.Errorf("Max cluster unschedulable replicas error: %v", err) continue } for i := range res { if res[i].Replicas == estimatorclient.UnauthenticReplica { continue } if undesiredClusters[i].ClusterName == res[i].Name && undesiredClusters[i].Unschedulable > res[i].Replicas { undesiredClusters[i].Unschedulable = res[i].Replicas } } } for i := range undesiredClusters { if undesiredClusters[i].Unschedulable == math.MaxInt32 { undesiredClusters[i].Unschedulable = 0 } } klog.V(4).Infof("Target undesired cluster of unschedulable replica result: %s", pretty.Sprint(undesiredClusters)) } // GetUndesiredClusters returns the cluster which of ready replicas are not reach the ready ones. func (h *SchedulingResultHelper) GetUndesiredClusters() ([]*TargetClusterWrapper, []string) { var clusters []*TargetClusterWrapper var names []string for _, cluster := range h.TargetClusters { if cluster.Ready < cluster.Spec { clusters = append(clusters, cluster) names = append(names, cluster.ClusterName) } } return clusters, names } // TargetClusterWrapper is a wrapper to wrap the target cluster name, spec replicas, // ready replicas and unschedulable replicas. type TargetClusterWrapper struct { ClusterName string Spec int32 Ready int32 Unschedulable int32 } func getReadyReplicas(binding *workv1alpha2.ResourceBinding) map[string]int32 { aggregatedStatus := binding.Status.AggregatedStatus res := make(map[string]int32, len(aggregatedStatus)) for i := range aggregatedStatus { item := aggregatedStatus[i] if item.Status == nil { continue } workloadStatus := make(map[string]interface{}) if err := json.Unmarshal(item.Status.Raw, &workloadStatus); err != nil { klog.ErrorS(err, "Failed to unmarshal workload status when get ready replicas", "ResourceBinding", klog.KObj(binding)) continue } readyReplicas := int32(0) // TODO(Garrybest): cooperate with custom resource interpreter if r, ok := workloadStatus[util.ReadyReplicasField]; ok { readyReplicas = int32(r.(float64)) res[item.ClusterName] = readyReplicas } } return res }