Files
capsule/api/v1beta2/resourcepool_func_test.go
Oliver Bähler c7237f802b feat(api): add resourcepools and claims (#1333)
* feat: functional appsets

* feat(api): add resourcepools api

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: fix gomod

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: correct webhooks

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: fix harpoon image

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: improve e2e

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: add labels to e2e test

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: fix status handling

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: fix racing conditions

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: make values compatible

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: fix custom resources test

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: correct metrics

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

---------

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2025-05-22 09:07:13 +02:00

293 lines
7.3 KiB
Go

package v1beta2
import (
"testing"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"github.com/projectcapsule/capsule/pkg/api"
"github.com/projectcapsule/capsule/pkg/meta"
"github.com/stretchr/testify/assert"
)
func TestGetClaimFromStatus(t *testing.T) {
ns := "test-namespace"
testUID := types.UID("test-uid")
otherUID := types.UID("wrong-uid")
claim := &ResourcePoolClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "claim-a",
Namespace: ns,
UID: testUID,
},
}
pool := &ResourcePool{
Status: ResourcePoolStatus{
Claims: ResourcePoolNamespaceClaimsStatus{
ns: {
&ResourcePoolClaimsItem{
StatusNameUID: api.StatusNameUID{
UID: testUID,
},
Claims: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("500m"),
corev1.ResourceMemory: resource.MustParse("256Mi"),
},
},
},
},
},
}
t.Run("returns matching claim", func(t *testing.T) {
found := pool.GetClaimFromStatus(claim)
assert.NotNil(t, found)
assert.Equal(t, testUID, found.UID)
})
t.Run("returns nil if UID doesn't match", func(t *testing.T) {
claimWrongUID := *claim
claimWrongUID.UID = otherUID
found := pool.GetClaimFromStatus(&claimWrongUID)
assert.Nil(t, found)
})
t.Run("returns nil if namespace has no claims", func(t *testing.T) {
claimWrongNS := *claim
claimWrongNS.Namespace = "other-ns"
found := pool.GetClaimFromStatus(&claimWrongNS)
assert.Nil(t, found)
})
}
func makeResourceList(cpu, memory string) corev1.ResourceList {
return corev1.ResourceList{
corev1.ResourceLimitsCPU: resource.MustParse(cpu),
corev1.ResourceLimitsMemory: resource.MustParse(memory),
}
}
func makeClaim(name, ns string, uid types.UID, res corev1.ResourceList) *ResourcePoolClaim {
return &ResourcePoolClaim{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: ns,
UID: uid,
},
Spec: ResourcePoolClaimSpec{
ResourceClaims: res,
},
}
}
func TestAssignNamespaces(t *testing.T) {
pool := &ResourcePool{}
namespaces := []corev1.Namespace{
{ObjectMeta: metav1.ObjectMeta{Name: "active-ns"}, Status: corev1.NamespaceStatus{Phase: corev1.NamespaceActive}},
{ObjectMeta: metav1.ObjectMeta{Name: "terminating-ns", DeletionTimestamp: &metav1.Time{}}, Status: corev1.NamespaceStatus{Phase: corev1.NamespaceTerminating}},
}
pool.AssignNamespaces(namespaces)
assert.Equal(t, uint(1), pool.Status.NamespaceSize)
assert.Equal(t, []string{"active-ns"}, pool.Status.Namespaces)
}
func TestAssignClaims(t *testing.T) {
pool := &ResourcePool{
Status: ResourcePoolStatus{
Claims: ResourcePoolNamespaceClaimsStatus{
"ns": {
&ResourcePoolClaimsItem{},
&ResourcePoolClaimsItem{},
},
},
},
}
pool.AssignClaims()
assert.Equal(t, uint(2), pool.Status.ClaimSize)
}
func TestAddRemoveClaimToStatus(t *testing.T) {
pool := &ResourcePool{}
claim := makeClaim("claim-1", "ns", "uid-1", makeResourceList("1", "1Gi"))
pool.AddClaimToStatus(claim)
stored := pool.GetClaimFromStatus(claim)
assert.NotNil(t, stored)
assert.Equal(t, api.Name("claim-1"), stored.Name)
pool.RemoveClaimFromStatus(claim)
assert.Nil(t, pool.GetClaimFromStatus(claim))
}
func TestCalculateResources(t *testing.T) {
pool := &ResourcePool{
Status: ResourcePoolStatus{
Allocation: ResourcePoolQuotaStatus{
Hard: corev1.ResourceList{
corev1.ResourceLimitsCPU: resource.MustParse("2"),
},
},
Claims: ResourcePoolNamespaceClaimsStatus{
"ns": {
&ResourcePoolClaimsItem{
Claims: corev1.ResourceList{
corev1.ResourceLimitsCPU: resource.MustParse("1"),
},
},
},
},
},
}
pool.CalculateClaimedResources()
actualClaimed := pool.Status.Allocation.Claimed[corev1.ResourceLimitsCPU]
actualAvailable := pool.Status.Allocation.Available[corev1.ResourceLimitsCPU]
assert.Equal(t, 0, (&actualClaimed).Cmp(resource.MustParse("1")))
assert.Equal(t, 0, (&actualAvailable).Cmp(resource.MustParse("1")))
}
func TestCanClaimFromPool(t *testing.T) {
pool := &ResourcePool{
Status: ResourcePoolStatus{
Allocation: ResourcePoolQuotaStatus{
Hard: corev1.ResourceList{
corev1.ResourceLimitsMemory: resource.MustParse("1Gi"),
},
Claimed: corev1.ResourceList{
corev1.ResourceLimitsMemory: resource.MustParse("512Mi"),
},
},
},
}
errs := pool.CanClaimFromPool(corev1.ResourceList{
corev1.ResourceLimitsMemory: resource.MustParse("1Gi"),
})
assert.Len(t, errs, 1)
errs = pool.CanClaimFromPool(corev1.ResourceList{
corev1.ResourceLimitsMemory: resource.MustParse("500Mi"),
})
assert.Len(t, errs, 0)
}
func TestGetResourceQuotaHardResources(t *testing.T) {
pool := &ResourcePool{
Spec: ResourcePoolSpec{
Defaults: corev1.ResourceList{
corev1.ResourceLimitsCPU: resource.MustParse("1"),
},
},
Status: ResourcePoolStatus{
Claims: ResourcePoolNamespaceClaimsStatus{
"ns": {
&ResourcePoolClaimsItem{
Claims: corev1.ResourceList{
corev1.ResourceLimitsCPU: resource.MustParse("1"),
},
},
},
},
},
}
res := pool.GetResourceQuotaHardResources("ns")
actual := res[corev1.ResourceLimitsCPU]
assert.Equal(t, 0, (&actual).Cmp(resource.MustParse("2")))
}
func TestGetNamespaceClaims(t *testing.T) {
pool := &ResourcePool{
Status: ResourcePoolStatus{
Claims: ResourcePoolNamespaceClaimsStatus{
"ns": {
&ResourcePoolClaimsItem{
StatusNameUID: api.StatusNameUID{UID: "uid1"},
Claims: corev1.ResourceList{
corev1.ResourceLimitsCPU: resource.MustParse("1"),
},
},
},
},
},
}
claims, res := pool.GetNamespaceClaims("ns")
assert.Contains(t, claims, "uid1")
actual := res[corev1.ResourceLimitsCPU]
assert.Equal(t, 0, (&actual).Cmp(resource.MustParse("1")))
}
func TestGetClaimedByNamespaceClaims(t *testing.T) {
pool := &ResourcePool{
Status: ResourcePoolStatus{
Claims: ResourcePoolNamespaceClaimsStatus{
"ns1": {
&ResourcePoolClaimsItem{
Claims: makeResourceList("1", "1Gi"),
},
},
},
},
}
result := pool.GetClaimedByNamespaceClaims()
actualCPU := result["ns1"][corev1.ResourceLimitsCPU]
actualMem := result["ns1"][corev1.ResourceLimitsMemory]
assert.Equal(t, 0, (&actualCPU).Cmp(resource.MustParse("1")))
assert.Equal(t, 0, (&actualMem).Cmp(resource.MustParse("1Gi")))
}
func TestIsBoundToResourcePool_2(t *testing.T) {
t.Run("bound to resource pool (Assigned=True)", func(t *testing.T) {
claim := &ResourcePoolClaim{
Status: ResourcePoolClaimStatus{
Condition: metav1.Condition{
Type: meta.BoundCondition,
Status: metav1.ConditionTrue,
},
},
}
assert.Equal(t, true, claim.IsBoundToResourcePool())
})
t.Run("not bound - wrong condition type", func(t *testing.T) {
claim := &ResourcePoolClaim{
Status: ResourcePoolClaimStatus{
Condition: metav1.Condition{
Type: "Other",
Status: metav1.ConditionTrue,
},
},
}
assert.Equal(t, false, claim.IsBoundToResourcePool())
})
t.Run("not bound - condition not true", func(t *testing.T) {
claim := &ResourcePoolClaim{
Status: ResourcePoolClaimStatus{
Condition: metav1.Condition{
Type: meta.BoundCondition,
Status: metav1.ConditionFalse,
},
},
}
assert.Equal(t, false, claim.IsBoundToResourcePool())
})
}