Skip to content

Commit e7ecc93

Browse files
authored
Merge pull request #6701 from liaolecheng/feature/enhance-quota
Enhance the quota statistics
2 parents 3b47683 + 7e9dd8d commit e7ecc93

File tree

6 files changed

+406
-81
lines changed

6 files changed

+406
-81
lines changed

pkg/controllers/federatedresourcequota/federated_resource_quota_enforcement_controller.go

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ func (c *QuotaEnforcementController) collectQuotaStatus(quota *policyv1alpha1.Fe
266266
func calculateUsedWithResourceBinding(resourceBindings []workv1alpha2.ResourceBinding, overall corev1.ResourceList) corev1.ResourceList {
267267
overallUsed := corev1.ResourceList{}
268268
for _, binding := range resourceBindings {
269-
usage := calculateResourceUsage(&binding)
269+
usage := helper.CalculateResourceUsage(&binding)
270270
for name, quantity := range usage {
271271
existing, found := overallUsed[name]
272272
if found {
@@ -280,38 +280,6 @@ func calculateUsedWithResourceBinding(resourceBindings []workv1alpha2.ResourceBi
280280
return filterResourceListByOverall(overallUsed, overall)
281281
}
282282

283-
// calculateResourceUsage calculates the total resource usage based on the ResourceBinding.
284-
func calculateResourceUsage(rb *workv1alpha2.ResourceBinding) corev1.ResourceList {
285-
if rb == nil || rb.Spec.ReplicaRequirements == nil || len(rb.Spec.ReplicaRequirements.ResourceRequest) == 0 || len(rb.Spec.Clusters) == 0 {
286-
return corev1.ResourceList{}
287-
}
288-
289-
totalReplicas := int32(0)
290-
for _, cluster := range rb.Spec.Clusters {
291-
totalReplicas += cluster.Replicas
292-
}
293-
294-
if totalReplicas == 0 {
295-
return corev1.ResourceList{}
296-
}
297-
298-
usage := corev1.ResourceList{}
299-
replicaCount := int64(totalReplicas)
300-
301-
for resourceName, quantityPerReplica := range rb.Spec.ReplicaRequirements.ResourceRequest {
302-
if quantityPerReplica.IsZero() {
303-
continue
304-
}
305-
306-
totalQuantity := quantityPerReplica.DeepCopy()
307-
totalQuantity.Mul(replicaCount)
308-
309-
usage[resourceName] = totalQuantity
310-
}
311-
312-
return usage
313-
}
314-
315283
// Filters source ResourceList using the keys provided by reference
316284
func filterResourceListByOverall(source, reference corev1.ResourceList) corev1.ResourceList {
317285
filteredUsed := corev1.ResourceList{}

pkg/controllers/federatedresourcequota/federated_resource_quota_enforcement_controller_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,30 @@ func TestCalculateUsedWithResourceBinding(t *testing.T) {
143143
corev1.ResourceMemory: resource.MustParse("256Mi"),
144144
},
145145
},
146+
{
147+
name: "binding with complex components",
148+
bindings: []workv1alpha2.ResourceBinding{
149+
makeBindingWithComponents(
150+
[]workv1alpha2.TargetCluster{
151+
{Name: "cluster2", Replicas: 7},
152+
{Name: "cluster3", Replicas: 7},
153+
},
154+
[]workv1alpha2.ComponentRequirements{
155+
makeComponentWithReplicas(2, "100m", "128Mi"),
156+
makeComponentWithReplicas(0, "50m", "64Mi"),
157+
makeComponentWithReplicas(3, "25m", ""),
158+
makeComponentWithReplicas(2, "", "32Mi"),
159+
},
160+
),
161+
},
162+
overall: makeResourceRequest("1", "2Gi"),
163+
expected: corev1.ResourceList{
164+
// CPU: (100m*2 + 25m*3)*2 = 550m
165+
// Memory: (128Mi*2 + 32Mi*2)*2 = 640Mi
166+
corev1.ResourceCPU: resource.MustParse("550m"),
167+
corev1.ResourceMemory: resource.MustParse("640Mi"),
168+
},
169+
},
146170
{
147171
name: "empty binding list",
148172
bindings: []workv1alpha2.ResourceBinding{},
@@ -176,6 +200,24 @@ func makeBinding(cpu string, memory string, clusters []workv1alpha2.TargetCluste
176200
}
177201
}
178202

203+
func makeBindingWithComponents(clusters []workv1alpha2.TargetCluster, components []workv1alpha2.ComponentRequirements) workv1alpha2.ResourceBinding {
204+
return workv1alpha2.ResourceBinding{
205+
Spec: workv1alpha2.ResourceBindingSpec{
206+
Clusters: clusters,
207+
Components: components,
208+
},
209+
}
210+
}
211+
212+
func makeComponentWithReplicas(replicas int32, cpu, memory string) workv1alpha2.ComponentRequirements {
213+
return workv1alpha2.ComponentRequirements{
214+
Replicas: replicas,
215+
ReplicaRequirements: &workv1alpha2.ReplicaRequirements{
216+
ResourceRequest: makeResourceRequest(cpu, memory),
217+
},
218+
}
219+
}
220+
179221
func makeResourceRequest(cpu string, memory string) map[corev1.ResourceName]resource.Quantity {
180222
if cpu != "" && memory != "" {
181223
return map[corev1.ResourceName]resource.Quantity{

pkg/util/helper/binding.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,3 +488,94 @@ func ConstructObjectReference(rs policyv1alpha1.ResourceSelector) workv1alpha2.O
488488
Name: rs.Name,
489489
}
490490
}
491+
492+
// CalculateResourceUsage calculates the total resource usage based on the ResourceBinding.
493+
func CalculateResourceUsage(rb *workv1alpha2.ResourceBinding) corev1.ResourceList {
494+
if rb == nil {
495+
return corev1.ResourceList{}
496+
}
497+
498+
usage := corev1.ResourceList{}
499+
500+
// if Components is set, calculate the resource usage based on Components.
501+
if len(rb.Spec.Components) > 0 {
502+
clusterCount := int64(len(rb.Spec.Clusters))
503+
if clusterCount == 0 {
504+
return corev1.ResourceList{}
505+
}
506+
507+
resourceRequestPerSet := aggregateComponentResources(rb.Spec.Components)
508+
509+
for resourceName, quantityPerCluster := range resourceRequestPerSet {
510+
if quantityPerCluster.IsZero() {
511+
continue
512+
}
513+
514+
totalQuantity := quantityPerCluster.DeepCopy()
515+
totalQuantity.Mul(clusterCount)
516+
517+
usage[resourceName] = totalQuantity
518+
}
519+
520+
return usage
521+
}
522+
523+
// if Components is not set, calculate the resource usage based on ReplicaRequirements.
524+
if rb.Spec.ReplicaRequirements != nil && len(rb.Spec.ReplicaRequirements.ResourceRequest) > 0 {
525+
totalReplicas := int32(0)
526+
for _, cluster := range rb.Spec.Clusters {
527+
totalReplicas += cluster.Replicas
528+
}
529+
if totalReplicas == 0 {
530+
return corev1.ResourceList{}
531+
}
532+
replicaCount := int64(totalReplicas)
533+
534+
for resourceName, quantityPerReplica := range rb.Spec.ReplicaRequirements.ResourceRequest {
535+
if quantityPerReplica.IsZero() {
536+
continue
537+
}
538+
539+
totalQuantity := quantityPerReplica.DeepCopy()
540+
totalQuantity.Mul(replicaCount)
541+
542+
usage[resourceName] = totalQuantity
543+
}
544+
545+
return usage
546+
}
547+
548+
return usage
549+
}
550+
551+
func aggregateComponentResources(components []workv1alpha2.ComponentRequirements) corev1.ResourceList {
552+
aggregatedResources := corev1.ResourceList{}
553+
for _, component := range components {
554+
if component.ReplicaRequirements == nil || len(component.ReplicaRequirements.ResourceRequest) == 0 {
555+
continue
556+
}
557+
558+
componentReplicas := component.Replicas
559+
if componentReplicas == 0 {
560+
continue
561+
}
562+
563+
for resourceName, quantity := range component.ReplicaRequirements.ResourceRequest {
564+
if quantity.IsZero() {
565+
continue
566+
}
567+
568+
totalComponentQuantity := quantity.DeepCopy()
569+
totalComponentQuantity.Mul(int64(componentReplicas))
570+
571+
existing, found := aggregatedResources[resourceName]
572+
if found {
573+
existing.Add(totalComponentQuantity)
574+
aggregatedResources[resourceName] = existing
575+
} else {
576+
aggregatedResources[resourceName] = totalComponentQuantity
577+
}
578+
}
579+
}
580+
return aggregatedResources
581+
}

pkg/util/helper/binding_test.go

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"sort"
2323
"testing"
2424

25+
"github.com/stretchr/testify/assert"
2526
corev1 "k8s.io/api/core/v1"
2627
"k8s.io/apimachinery/pkg/api/meta"
2728
"k8s.io/apimachinery/pkg/api/resource"
@@ -1257,3 +1258,144 @@ func TestConstructClusterWideKey(t *testing.T) {
12571258
})
12581259
}
12591260
}
1261+
1262+
func compareResourceLists(t *testing.T, expected, actual corev1.ResourceList) {
1263+
for resourceName, expectedQuantity := range expected {
1264+
actualQuantity, exists := actual[resourceName]
1265+
assert.True(t, exists, "Resource %s is missing in actual result", resourceName)
1266+
assert.Equal(t, expectedQuantity.Value(), actualQuantity.Value(), "Resource %s value mismatch", resourceName)
1267+
assert.Equal(t, expectedQuantity.Format, actualQuantity.Format, "Resource %s format mismatch", resourceName)
1268+
}
1269+
assert.Equal(t, len(expected), len(actual), "Resource list length mismatch")
1270+
}
1271+
1272+
func TestCalculateResourceUsage(t *testing.T) {
1273+
tests := []struct {
1274+
name string
1275+
rb *workv1alpha2.ResourceBinding
1276+
expected corev1.ResourceList
1277+
}{
1278+
{
1279+
name: "Calculate usage with components",
1280+
rb: &workv1alpha2.ResourceBinding{
1281+
Spec: workv1alpha2.ResourceBindingSpec{
1282+
Clusters: []workv1alpha2.TargetCluster{
1283+
{Name: "cluster1"},
1284+
{Name: "cluster2"},
1285+
},
1286+
Components: []workv1alpha2.ComponentRequirements{
1287+
{
1288+
Replicas: 2,
1289+
ReplicaRequirements: &workv1alpha2.ReplicaRequirements{
1290+
ResourceRequest: corev1.ResourceList{
1291+
corev1.ResourceCPU: resource.MustParse("500m"),
1292+
corev1.ResourceMemory: resource.MustParse("1Gi"),
1293+
},
1294+
},
1295+
},
1296+
},
1297+
},
1298+
},
1299+
expected: corev1.ResourceList{
1300+
corev1.ResourceCPU: resource.MustParse("2"),
1301+
corev1.ResourceMemory: resource.MustParse("4Gi"),
1302+
},
1303+
},
1304+
{
1305+
name: "Calculate usage with replica requirements",
1306+
rb: &workv1alpha2.ResourceBinding{
1307+
Spec: workv1alpha2.ResourceBindingSpec{
1308+
Clusters: []workv1alpha2.TargetCluster{
1309+
{Name: "cluster1", Replicas: 3},
1310+
{Name: "cluster2", Replicas: 2},
1311+
},
1312+
ReplicaRequirements: &workv1alpha2.ReplicaRequirements{
1313+
ResourceRequest: corev1.ResourceList{
1314+
corev1.ResourceCPU: resource.MustParse("1"),
1315+
corev1.ResourceMemory: resource.MustParse("2Gi"),
1316+
},
1317+
},
1318+
},
1319+
},
1320+
expected: corev1.ResourceList{
1321+
corev1.ResourceCPU: resource.MustParse("5"),
1322+
corev1.ResourceMemory: resource.MustParse("10Gi"),
1323+
},
1324+
},
1325+
{
1326+
name: "Nil ResourceBinding",
1327+
rb: nil,
1328+
expected: corev1.ResourceList{},
1329+
},
1330+
}
1331+
1332+
for _, tt := range tests {
1333+
t.Run(tt.name, func(t *testing.T) {
1334+
result := CalculateResourceUsage(tt.rb)
1335+
compareResourceLists(t, tt.expected, result)
1336+
})
1337+
}
1338+
}
1339+
1340+
func TestAggregateComponentResources(t *testing.T) {
1341+
tests := []struct {
1342+
name string
1343+
components []workv1alpha2.ComponentRequirements
1344+
expected corev1.ResourceList
1345+
}{
1346+
{
1347+
name: "Aggregate resources from components",
1348+
components: []workv1alpha2.ComponentRequirements{
1349+
{
1350+
Replicas: 2,
1351+
ReplicaRequirements: &workv1alpha2.ReplicaRequirements{
1352+
ResourceRequest: corev1.ResourceList{
1353+
corev1.ResourceCPU: resource.MustParse("500m"),
1354+
corev1.ResourceMemory: resource.MustParse("1Gi"),
1355+
},
1356+
},
1357+
},
1358+
{
1359+
Replicas: 3,
1360+
ReplicaRequirements: &workv1alpha2.ReplicaRequirements{
1361+
ResourceRequest: corev1.ResourceList{
1362+
corev1.ResourceCPU: resource.MustParse("1"),
1363+
corev1.ResourceMemory: resource.MustParse("2Gi"),
1364+
},
1365+
},
1366+
},
1367+
},
1368+
expected: corev1.ResourceList{
1369+
corev1.ResourceCPU: resource.MustParse("4"),
1370+
corev1.ResourceMemory: resource.MustParse("8Gi"),
1371+
},
1372+
},
1373+
{
1374+
name: "No components",
1375+
components: []workv1alpha2.ComponentRequirements{
1376+
{
1377+
Replicas: 0,
1378+
ReplicaRequirements: &workv1alpha2.ReplicaRequirements{
1379+
ResourceRequest: corev1.ResourceList{
1380+
corev1.ResourceCPU: resource.MustParse("500m"),
1381+
corev1.ResourceMemory: resource.MustParse("1Gi"),
1382+
},
1383+
},
1384+
},
1385+
},
1386+
expected: corev1.ResourceList{},
1387+
},
1388+
{
1389+
name: "Empty components",
1390+
components: []workv1alpha2.ComponentRequirements{},
1391+
expected: corev1.ResourceList{},
1392+
},
1393+
}
1394+
1395+
for _, tt := range tests {
1396+
t.Run(tt.name, func(t *testing.T) {
1397+
result := aggregateComponentResources(tt.components)
1398+
compareResourceLists(t, tt.expected, result)
1399+
})
1400+
}
1401+
}

0 commit comments

Comments
 (0)