Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions api/v1beta2/resourcepool_func.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,12 +159,22 @@ func (r *ResourcePool) CalculateAvailableResources() {
available := corev1.ResourceList{}

for res, qt := range r.Status.Allocation.Hard {
// Copy qt before subtracting: ranging over the map yields live
// references, so mutating it in place would corrupt Hard.
remaining := qt.DeepCopy()
Comment thread
oliverbaehler marked this conversation as resolved.
Outdated

amount, exists := r.Status.Allocation.Claimed[res]
if exists {
qt.Sub(amount)
remaining.Sub(amount)
Comment on lines 167 to +169
}

// A pool can never offer a negative amount of resources, so clamp
// at zero. This also keeps Hard intact for downstream math.
if remaining.Sign() < 0 {
remaining = resource.MustParse("0")
}
Comment thread
AruneshDwivedi marked this conversation as resolved.

available[res] = qt
available[res] = remaining
}

r.Status.Allocation.Available = available
Expand Down
38 changes: 38 additions & 0 deletions api/v1beta2/resourcepool_func_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,44 @@ func TestCalculateResources(t *testing.T) {
assert.Equal(t, 0, (&actualAvailable).Cmp(resource.MustParse("1")))
}

func TestCalculateResources_OverSubscriptionDoesNotGoNegative(t *testing.T) {
pool := &capsulev1beta2.ResourcePool{
Status: capsulev1beta2.ResourcePoolStatus{
Allocation: capsulev1beta2.ResourcePoolQuotaStatus{
Hard: corev1.ResourceList{
corev1.ResourceLimitsCPU: resource.MustParse("10"),
},
},
Claims: capsulev1beta2.ResourcePoolNamespaceClaimsStatus{
"ns": {
&capsulev1beta2.ResourcePoolClaimsItem{
Claims: corev1.ResourceList{
corev1.ResourceLimitsCPU: resource.MustParse("55"),
},
},
},
},
},
}

pool.CalculateClaimedResources()

// Even when claims exceed the hard limit, available must clamp at zero
// and Hard must stay intact (regression: subtracting in place corrupted
// both Hard and Available, producing negative values — see issue #1977).
actualAvailable := pool.Status.Allocation.Available[corev1.ResourceLimitsCPU]
assert.Equal(t, 0, (&actualAvailable).Cmp(resource.MustParse("0")))

actualHard := pool.Status.Allocation.Hard[corev1.ResourceLimitsCPU]
assert.Equal(t, 0, (&actualHard).Cmp(resource.MustParse("10")))

// With zero available, a new oversized claim must be rejected.
errs := pool.CanClaimFromPool(corev1.ResourceList{
corev1.ResourceLimitsCPU: resource.MustParse("55"),
})
assert.Len(t, errs, 1)
}

func TestCanClaimFromPool(t *testing.T) {
pool := &capsulev1beta2.ResourcePool{
Status: capsulev1beta2.ResourcePoolStatus{
Expand Down
Loading