Skip to content

Commit b9c71cb

Browse files
committed
Enable multiple components estimating
Signed-off-by: RainbowMango <[email protected]>
1 parent ce66819 commit b9c71cb

File tree

3 files changed

+509
-11
lines changed

3 files changed

+509
-11
lines changed

pkg/scheduler/core/estimation.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
Copyright 2025 The Karmada Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package core
18+
19+
import (
20+
"context"
21+
22+
"k8s.io/klog/v2"
23+
24+
clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
25+
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
26+
workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
27+
estimatorclient "github.com/karmada-io/karmada/pkg/estimator/client"
28+
"github.com/karmada-io/karmada/pkg/util/names"
29+
)
30+
31+
// isMultiTemplateSchedulingApplicable checks if the given ResourceBindingSpec
32+
// meets the criteria for multi-template scheduling:
33+
// 1. The referenced resource holds multiple pod templates (multiple components).
34+
// 2. The placement configuration schedules the resource to exactly one cluster.
35+
// This is currently determined by checking if spread constraints is set and requires exactly one cluster.
36+
//
37+
// Returns true if both conditions are satisfied, false otherwise.
38+
// Note: We do not infer required cluster number from placement.clusterAffinity and
39+
// placement.clusterAffinities because it's impossible to determine without cluster metadata
40+
// whether the affinity rule matches exactly one cluster in the current environment, and the
41+
// only reliable way is spread constraints.
42+
func isMultiTemplateSchedulingApplicable(spec *workv1alpha2.ResourceBindingSpec) bool {
43+
if spec == nil {
44+
return false
45+
}
46+
47+
if len(spec.Components) < 2 {
48+
return false
49+
}
50+
51+
// Check if placement targets exactly one cluster
52+
if spec.Placement == nil {
53+
return false
54+
}
55+
for i := range spec.Placement.SpreadConstraints {
56+
if spec.Placement.SpreadConstraints[i].SpreadByField == policyv1alpha1.SpreadByFieldCluster &&
57+
spec.Placement.SpreadConstraints[i].MinGroups == 1 &&
58+
spec.Placement.SpreadConstraints[i].MaxGroups == 1 {
59+
return true
60+
}
61+
}
62+
63+
return false
64+
}
65+
66+
// calculateMultiTemplateAvailableSets calculates available sets for multi-template scheduling.
67+
// It uses MaxAvailableComponentSets to estimate capacity for workloads with multiple pod templates.
68+
func calculateMultiTemplateAvailableSets(ctx context.Context, estimator estimatorclient.ReplicaEstimator, name string, clusters []*clusterv1alpha1.Cluster, spec *workv1alpha2.ResourceBindingSpec, availableTargetClusters []workv1alpha2.TargetCluster) ([]workv1alpha2.TargetCluster, error) {
69+
req := estimatorclient.ComponentSetEstimationRequest{
70+
Clusters: clusters,
71+
Components: spec.Components,
72+
Namespace: spec.Resource.Namespace,
73+
}
74+
75+
namespacedKey := names.NamespacedKey(spec.Resource.Namespace, spec.Resource.Name)
76+
resp, err := estimator.MaxAvailableComponentSets(ctx, req)
77+
if err != nil {
78+
klog.Errorf("Failed to calculate available component set with estimator(%s) for workload(%s, kind=%s, %s): %v",
79+
name, spec.Resource.APIVersion, spec.Resource.Kind, namespacedKey, err)
80+
return availableTargetClusters, err
81+
}
82+
83+
for i := range resp {
84+
if resp[i].Sets == estimatorclient.UnauthenticReplica {
85+
continue
86+
}
87+
88+
// Should find the corresponding cluster by name, not by assuming the order is the same as input,
89+
// even if the order is the same, it's not guaranteed.
90+
for j := range availableTargetClusters {
91+
if availableTargetClusters[j].Name == resp[i].Name {
92+
if availableTargetClusters[j].Replicas > resp[i].Sets {
93+
availableTargetClusters[j].Replicas = resp[i].Sets
94+
break
95+
}
96+
}
97+
}
98+
}
99+
100+
return availableTargetClusters, nil
101+
}

0 commit comments

Comments
 (0)