Skip to content

Commit 84823d8

Browse files
authored
fix: fix Capacity Value Requirements (#1237)
2 parents 8822bfe + da8f7fc commit 84823d8

File tree

5 files changed

+80
-44
lines changed

5 files changed

+80
-44
lines changed

pkg/propertychecker/azure/checker.go

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ const (
2727
// The value is constrained to a reasonable upper bound of 200 for most production workloads.
2828
// Upper bound is enforced after adjusting for operator semantics.
2929
maxVMInstanceCapacity = 200
30+
31+
// minVMInstanceCapacity defines the minimum allowed VM instance capacity for SKU capacity requirements.
32+
// The Azure vmSizeRecommender API requires capacity values greater than 0, so minimum is set to 1.
33+
// Lower bound is enforced after adjusting for operator semantics.
34+
minVMInstanceCapacity = 1
3035
)
3136

3237
// PropertyChecker provides Azure-specific property validation for member clusters.
@@ -48,8 +53,8 @@ func NewPropertyChecker(vmSizeRecommenderClient compute.AttributeBasedVMSizeReco
4853

4954
// CheckIfMeetSKUCapacityRequirement validates whether a member cluster can meet the specified
5055
// SKU capacity requirement. It extracts the required SKU and capacity from the property selector
51-
// requirement and checks to determine if the cluster's Azure subscription
52-
// and location can provision the requested VM instances.
56+
// requirement and checks whether the cluster's Azure subscription and location can provision
57+
// the requested VM instances.
5358
//
5459
// The cluster must have both Azure location and subscription ID labels configured.
5560
// Returns true if the SKU capacity requirement can be met, false otherwise.
@@ -75,14 +80,14 @@ func (s *PropertyChecker) CheckIfMeetSKUCapacityRequirement(
7580
}
7681

7782
// Request VM size recommendations to validate SKU availability and capacity.
78-
// The capacity is checked by ensuring the current allocatable capacity is greater than the requested capacity.
83+
// The capacity is checked by ensuring the current allocatable capacity is greater than or equal to the requested capacity.
7984
klog.V(2).Infof("Checking SKU %s with capacity %d in cluster %s", sku, capacity, cluster.Name)
8085
request := &computev1.GenerateAttributeBasedRecommendationsRequest{
8186
SubscriptionId: subID,
8287
Location: location,
8388
RegularPriorityProfile: &computev1.RegularPriorityProfile{
8489
CapacityUnitType: computev1.CapacityUnitType_CAPACITY_UNIT_TYPE_VM_INSTANCE_COUNT,
85-
TargetCapacity: capacity, // CurrentAllocatableCapacity > RequestedCapacity
90+
TargetCapacity: capacity, // CurrentAllocatableCapacity >= RequestedCapacity
8691
},
8792
ResourceProperties: &computev1.ResourceProperties{
8893
VmAttributes: &computev1.VMAttributes{
@@ -116,9 +121,8 @@ func (s *PropertyChecker) CheckIfMeetSKUCapacityRequirement(
116121
// extractCapacityRequirements extracts the capacity value from a PropertySelectorRequirement.
117122
// This function is specifically designed for Azure SKU capacity properties that follow the pattern:
118123
// "kubernetes.azure.com/vm-sizes/{sku}/capacity"
119-
// Returns the capacity if the requirement is valid;
120-
// The capacity will be updated based on the configured operator as VMSizeRecommender API
121-
// checks if the current allocatableCapacity > the requested capacity.
124+
// Returns the capacity value adjusted for the operator semantics, as the VMSizeRecommender API
125+
// checks whether current allocatable capacity is greater than or equal to the requested capacity.
122126
func extractCapacityRequirements(req placementv1beta1.PropertySelectorRequirement) (uint32, error) {
123127
if req.Operator != placementv1beta1.PropertySelectorGreaterThan && req.Operator != placementv1beta1.PropertySelectorGreaterThanOrEqualTo {
124128
return 0, fmt.Errorf("unsupported operator %q for SKU capacity property, only GreaterThan (Gt) and GreaterThanOrEqualTo (Ge) are supported", req.Operator)
@@ -149,18 +153,24 @@ func validateCapacity(value string, operator placementv1beta1.PropertySelectorOp
149153

150154
capacity := uint32(valueUint) // capacity is >= 0 since it's parsed as uint.
151155

152-
if operator == placementv1beta1.PropertySelectorGreaterThanOrEqualTo && capacity > 0 {
153-
capacity -= 1
156+
// Adjust capacity for VMSizeRecommender API semantics.
157+
// The API requires requested capacity >= 1.
158+
// If operator is GreaterThan (Gt) , increment the requested value by 1.
159+
// Otherwise, use the requested value as-is.
160+
// These adjustments ensure the API interprets the request correctly
161+
if operator == placementv1beta1.PropertySelectorGreaterThan {
162+
capacity += 1
154163
}
155164

156165
// Validate against maximum allowed capacity (exceed maxVMInstanceCapacity).
157-
if capacity >= maxVMInstanceCapacity {
166+
if capacity > maxVMInstanceCapacity {
158167
return 0, fmt.Errorf("capacity value %d exceeds maximum allowed value of %d", capacity, maxVMInstanceCapacity)
159168
}
160169

161-
// A capacity of zero is only valid for GreaterThan operator.
162-
if capacity == 0 && operator != placementv1beta1.PropertySelectorGreaterThan {
163-
return 0, fmt.Errorf("capacity value cannot be zero for operator %q", operator)
170+
// Ensure capacity meets minimum requirements (minVMInstanceCapacity) after operator adjustment.
171+
// The VMSizeRecommender API requires capacity > 0.
172+
if capacity < minVMInstanceCapacity {
173+
return 0, fmt.Errorf("capacity value %d is below minimum allowed value of %d", capacity, minVMInstanceCapacity)
164174
}
165175

166176
return capacity, nil

pkg/propertychecker/azure/checker_test.go

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@ func TestValidateCapacity(t *testing.T) {
3333
name: "valid capacity value for GreaterThan operator",
3434
value: "10",
3535
operator: placementv1beta1.PropertySelectorGreaterThan,
36-
wantValue: 10,
36+
wantValue: 11,
3737
wantError: false,
3838
},
3939
{
4040
name: "valid capacity value for GreaterThanOrEqualTo operator",
4141
value: "50",
4242
operator: placementv1beta1.PropertySelectorGreaterThanOrEqualTo,
43-
wantValue: 49,
43+
wantValue: 50,
4444
wantError: false,
4545
},
4646
{
@@ -72,11 +72,11 @@ func TestValidateCapacity(t *testing.T) {
7272
errorSubstring: "invalid capacity value",
7373
},
7474
{
75-
name: "unsupported operator for capacity of zero",
75+
name: "invalid value of zero for capacity",
7676
value: "0",
7777
operator: placementv1beta1.PropertySelectorGreaterThanOrEqualTo,
7878
wantError: true,
79-
errorSubstring: "capacity value cannot be zero for operator",
79+
errorSubstring: "is below minimum allowed value",
8080
},
8181
{
8282
name: "capacity equal to max limit with GreaterThan operator",
@@ -85,18 +85,11 @@ func TestValidateCapacity(t *testing.T) {
8585
wantError: true,
8686
errorSubstring: "exceeds maximum allowed value",
8787
},
88-
{
89-
name: "supported operator with capacity of zero",
90-
value: "0",
91-
operator: placementv1beta1.PropertySelectorGreaterThan,
92-
wantValue: 0,
93-
wantError: false,
94-
},
9588
{
9689
name: "capacity equal to max limit with supported operator",
9790
value: "200",
9891
operator: placementv1beta1.PropertySelectorGreaterThanOrEqualTo,
99-
wantValue: 199,
92+
wantValue: 200,
10093
wantError: false,
10194
},
10295
{
@@ -107,12 +100,19 @@ func TestValidateCapacity(t *testing.T) {
107100
errorSubstring: "exceeds maximum allowed value",
108101
},
109102
{
110-
name: "capacity at minimum limit",
111-
value: "1",
103+
name: "capacity at minimum limit (Gt)",
104+
value: "0",
112105
operator: placementv1beta1.PropertySelectorGreaterThan,
113106
wantValue: 1,
114107
wantError: false,
115108
},
109+
{
110+
name: "capacity at minimum limit (Ge)",
111+
value: "1",
112+
operator: placementv1beta1.PropertySelectorGreaterThanOrEqualTo,
113+
wantValue: 1,
114+
wantError: false,
115+
},
116116
}
117117
for _, tt := range tests {
118118
t.Run(tt.name, func(t *testing.T) {
@@ -158,7 +158,7 @@ func TestExtractCapacityRequirements(t *testing.T) {
158158
Values: []string{"4"},
159159
},
160160
wantSKU: "Standard_D4s_v3",
161-
wantCapacityValue: 4,
161+
wantCapacityValue: 5,
162162
wantError: false,
163163
},
164164
{
@@ -172,6 +172,17 @@ func TestExtractCapacityRequirements(t *testing.T) {
172172
wantError: true,
173173
errorSubstring: "exceeds maximum allowed value of 200",
174174
},
175+
{
176+
name: "invalid Azure SKU capacity property below min limit (Ge)",
177+
req: placementv1beta1.PropertySelectorRequirement{
178+
Name: fmt.Sprintf(azure.CapacityPerSKUPropertyTmpl, "Standard_B2ms"),
179+
Operator: placementv1beta1.PropertySelectorGreaterThanOrEqualTo,
180+
Values: []string{"0"},
181+
},
182+
wantSKU: "Standard_B2ms",
183+
wantError: true,
184+
errorSubstring: "is below minimum allowed value",
185+
},
175186
{
176187
name: "invalid Azure SKU capacity property with decimal",
177188
req: placementv1beta1.PropertySelectorRequirement{
@@ -281,7 +292,7 @@ func TestExtractCapacityRequirements(t *testing.T) {
281292
}
282293

283294
func TestCheckIfMeetSKUCapacityRequirement(t *testing.T) {
284-
// Prepare test data
295+
// Prepare test data.
285296
validSKU := "Standard_D2s_v3"
286297
validPropertySelectorRequirement := placementv1beta1.PropertySelectorRequirement{
287298
Name: fmt.Sprintf(azure.CapacityPerSKUPropertyTmpl, validSKU),
@@ -312,7 +323,7 @@ func TestCheckIfMeetSKUCapacityRequirement(t *testing.T) {
312323
name: "valid capacity request",
313324
cluster: cluster,
314325
sku: validSKU,
315-
targetCapacity: 3,
326+
targetCapacity: 4,
316327
req: validPropertySelectorRequirement,
317328
mockStatusCode: http.StatusOK,
318329
wantAvailable: true,
@@ -322,7 +333,7 @@ func TestCheckIfMeetSKUCapacityRequirement(t *testing.T) {
322333
name: "unavailable SKU request",
323334
cluster: cluster,
324335
sku: "Standard_D2s_v4",
325-
targetCapacity: 1,
336+
targetCapacity: 2,
326337
req: placementv1beta1.PropertySelectorRequirement{
327338
Name: fmt.Sprintf(azure.CapacityPerSKUPropertyTmpl, "Standard_D2s_v4"),
328339
Operator: placementv1beta1.PropertySelectorGreaterThanOrEqualTo,
@@ -381,7 +392,7 @@ func TestCheckIfMeetSKUCapacityRequirement(t *testing.T) {
381392
name: "Azure API returns error",
382393
cluster: cluster,
383394
sku: validSKU,
384-
targetCapacity: 3,
395+
targetCapacity: 4,
385396
req: validPropertySelectorRequirement,
386397
mockStatusCode: http.StatusInternalServerError,
387398
wantError: true,
@@ -402,7 +413,7 @@ func TestCheckIfMeetSKUCapacityRequirement(t *testing.T) {
402413
errorSubstring: "unsupported operator \"Eq\" for SKU capacity property, only GreaterThan (Gt) and GreaterThanOrEqualTo (Ge) are supported",
403414
},
404415
{
405-
name: "unsupported operator in requirement",
416+
name: "too low value in requirement",
406417
cluster: cluster,
407418
sku: validSKU,
408419
targetCapacity: 0,
@@ -411,15 +422,14 @@ func TestCheckIfMeetSKUCapacityRequirement(t *testing.T) {
411422
Operator: placementv1beta1.PropertySelectorGreaterThanOrEqualTo,
412423
Values: []string{"0"},
413424
},
414-
mockStatusCode: http.StatusOK,
415425
wantError: true,
416-
errorSubstring: "capacity value cannot be zero for operator",
426+
errorSubstring: "is below minimum allowed value",
417427
},
418428
{
419429
name: "cases-insensitive request - available SKU",
420430
cluster: cluster,
421431
sku: "STANDARD_D2S_V3",
422-
targetCapacity: 1,
432+
targetCapacity: 2,
423433
req: placementv1beta1.PropertySelectorRequirement{
424434
Name: fmt.Sprintf(azure.CapacityPerSKUPropertyTmpl, "STANDARD_D2S_V3"),
425435
Operator: placementv1beta1.PropertySelectorGreaterThanOrEqualTo,

pkg/scheduler/framework/plugins/clusteraffinity/filtering_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -831,7 +831,7 @@ func TestFilter_PropertyChecker(t *testing.T) {
831831
},
832832
},
833833
vmSize: "Standard_D2s_v3",
834-
targetCapacity: 1,
834+
targetCapacity: 2,
835835
},
836836
{
837837
name: "single cluster capacity based term, not matched",
@@ -878,7 +878,7 @@ func TestFilter_PropertyChecker(t *testing.T) {
878878
},
879879
},
880880
vmSize: "Standard_B2ms",
881-
targetCapacity: 3,
881+
targetCapacity: 4,
882882
wantStatus: framework.NewNonErrorStatus(framework.ClusterUnschedulable, p.Name(), "cluster does not match with any of the required cluster affinity terms"),
883883
},
884884
}

pkg/scheduler/framework/plugins/clusteraffinity/types_azure_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ func TestMatchPropertiesInPropertyChecker(t *testing.T) {
124124
Values: []string{"2"},
125125
},
126126
vmSize: "Standard_D2s_v3",
127-
targetCapacity: 2,
127+
targetCapacity: 3,
128128
wantHandled: true,
129129
wantAvailable: true,
130130
},
@@ -137,7 +137,7 @@ func TestMatchPropertiesInPropertyChecker(t *testing.T) {
137137
Values: []string{"2"},
138138
},
139139
vmSize: "NonExistentSKU",
140-
targetCapacity: 1,
140+
targetCapacity: 2,
141141
wantHandled: true,
142142
wantAvailable: false,
143143
},

pkg/scheduler/framework/plugins/clusteraffinity/types_test.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1356,7 +1356,23 @@ func TestClusterRequirementMatches_WithPropertyChecker(t *testing.T) {
13561356
},
13571357
cluster: cluster,
13581358
sku: "Standard_D2s_v3",
1359-
targetCapacity: 0,
1359+
targetCapacity: 1,
1360+
want: true,
1361+
},
1362+
{
1363+
name: "op >=, matched (min limit)",
1364+
matchExpression: []placementv1beta1.PropertySelectorRequirement{
1365+
{
1366+
Name: validPropertyName,
1367+
Operator: placementv1beta1.PropertySelectorGreaterThanOrEqualTo,
1368+
Values: []string{
1369+
"1",
1370+
},
1371+
},
1372+
},
1373+
cluster: cluster,
1374+
sku: "Standard_D2s_v3",
1375+
targetCapacity: 1,
13601376
want: true,
13611377
},
13621378
{
@@ -1372,7 +1388,7 @@ func TestClusterRequirementMatches_WithPropertyChecker(t *testing.T) {
13721388
},
13731389
cluster: cluster,
13741390
sku: "Standard_D4s_v3",
1375-
targetCapacity: 8,
1391+
targetCapacity: 9,
13761392
},
13771393
{
13781394
name: "op >=, matched (max limit)",
@@ -1387,7 +1403,7 @@ func TestClusterRequirementMatches_WithPropertyChecker(t *testing.T) {
13871403
},
13881404
cluster: cluster,
13891405
sku: "Standard_D2s_v3",
1390-
targetCapacity: 199,
1406+
targetCapacity: 200,
13911407
want: true,
13921408
},
13931409
{
@@ -1403,7 +1419,7 @@ func TestClusterRequirementMatches_WithPropertyChecker(t *testing.T) {
14031419
},
14041420
cluster: cluster,
14051421
sku: "Standard_D4s_v3",
1406-
targetCapacity: 79,
1422+
targetCapacity: 80,
14071423
},
14081424
}
14091425

0 commit comments

Comments
 (0)