Files
capsule/e2e/pool_resourcepoolclaim_test.go
T

1011 lines
33 KiB
Go

// Copyright 2020-2023 Project Capsule Authors.
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"context"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
"github.com/projectcapsule/capsule/pkg/api/meta"
"github.com/projectcapsule/capsule/pkg/api/rbac"
"github.com/projectcapsule/capsule/pkg/runtime/selectors"
)
var _ = Describe("ResourcePoolClaim Tests", Ordered, Label("resourcepool", "claim"), func() {
_ = &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "e2e-test-claims-1",
Labels: map[string]string{
"env": "e2e",
},
},
Spec: capsulev1beta2.TenantSpec{
Owners: rbac.OwnerListSpec{
{
CoreOwnerSpec: rbac.CoreOwnerSpec{
UserSpec: rbac.UserSpec{
Name: "wind-user",
Kind: "User",
},
},
},
},
},
}
JustAfterEach(func() {
Eventually(func() error {
poolList := &capsulev1beta2.TenantList{}
labelSelector := client.MatchingLabels{"e2e-resourcepoolclaims": "test"}
if err := k8sClient.List(context.TODO(), poolList, labelSelector); err != nil {
return err
}
for _, pool := range poolList.Items {
EventuallyDeletion(&pool)
}
return nil
}, "30s", "5s").Should(Succeed())
Eventually(func() error {
poolList := &capsulev1beta2.ResourcePoolList{}
labelSelector := client.MatchingLabels{"e2e-resourcepoolclaims": "test"}
if err := k8sClient.List(context.TODO(), poolList, labelSelector); err != nil {
return err
}
for _, pool := range poolList.Items {
EventuallyDeletion(&pool)
}
return nil
}, "30s", "5s").Should(Succeed())
Eventually(func() error {
poolList := &corev1.NamespaceList{}
labelSelector := client.MatchingLabels{"e2e-resourcepoolclaims": "test"}
if err := k8sClient.List(context.TODO(), poolList, labelSelector); err != nil {
return err
}
for _, pool := range poolList.Items {
EventuallyDeletion(&pool)
}
return nil
}, "30s", "5s").Should(Succeed())
})
It("Claim to Pool Assignment", func() {
pool1 := &capsulev1beta2.ResourcePool{
ObjectMeta: metav1.ObjectMeta{
Name: "test-binding-claims",
Labels: map[string]string{
"e2e-resourcepoolclaims": "test",
},
},
Spec: capsulev1beta2.ResourcePoolSpec{
Selectors: []selectors.NamespaceSelector{
{
LabelSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"e2e.capsule.dev/test-suite": "claims-bindings",
},
},
},
{
LabelSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"e2e.capsule.dev/test-suite": "claims-bindings-2",
},
},
},
},
Quota: corev1.ResourceQuotaSpec{
Hard: corev1.ResourceList{
corev1.ResourceLimitsCPU: resource.MustParse("2"),
corev1.ResourceLimitsMemory: resource.MustParse("2Gi"),
corev1.ResourceRequestsCPU: resource.MustParse("2"),
corev1.ResourceRequestsMemory: resource.MustParse("2Gi"),
},
},
},
}
claim1 := &capsulev1beta2.ResourcePoolClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "assign-pool-claim-1",
Namespace: "ns-1-pool-assign",
},
Spec: capsulev1beta2.ResourcePoolClaimSpec{
Pool: "test-binding-claims",
ResourceClaims: corev1.ResourceList{
corev1.ResourceLimitsCPU: resource.MustParse("0"),
corev1.ResourceLimitsMemory: resource.MustParse("0"),
corev1.ResourceRequestsCPU: resource.MustParse("0"),
corev1.ResourceRequestsMemory: resource.MustParse("0"),
},
},
}
claim2 := &capsulev1beta2.ResourcePoolClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "assign-pool-claim-2",
Namespace: "ns-2-pool-assign",
},
Spec: capsulev1beta2.ResourcePoolClaimSpec{
Pool: "test-binding-claims",
ResourceClaims: corev1.ResourceList{
corev1.ResourceLimitsCPU: resource.MustParse("0"),
corev1.ResourceLimitsMemory: resource.MustParse("0"),
corev1.ResourceRequestsCPU: resource.MustParse("0"),
corev1.ResourceRequestsMemory: resource.MustParse("0"),
},
},
}
By("Create the ResourcePool", func() {
EventuallyCreation(func() error {
pool1.ResourceVersion = ""
return k8sClient.Create(context.TODO(), pool1)
}).Should(Succeed(), "Failed to create ResourcePool %s", pool1)
})
By("Get Applied revision", func() {
err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: pool1.Name}, pool1)
Expect(err).Should(Succeed())
})
By("Create Namespaces, which are selected by the pool", func() {
ns1 := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "ns-1-pool-assign",
Labels: map[string]string{
"e2e-resourcepoolclaims": "test",
"e2e.capsule.dev/test-suite": "claims-bindings",
},
},
}
err := k8sClient.Create(context.TODO(), ns1)
Expect(err).Should(Succeed())
ns2 := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "ns-2-pool-assign",
Labels: map[string]string{
"e2e-resourcepoolclaims": "test",
"e2e.capsule.dev/test-suite": "claims-bindings-2",
},
},
}
err = k8sClient.Create(context.TODO(), ns2)
Expect(err).Should(Succeed())
ns3 := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "ns-3-pool-assign",
Labels: map[string]string{
"e2e-resourcepoolclaims": "test",
"e2e.capsule.dev/test-suite": "something-else",
},
},
}
err = k8sClient.Create(context.TODO(), ns3)
Expect(err).Should(Succeed())
})
By("Verify Namespaces are shown as allowed targets", func() {
expectedNamespaces := []string{"ns-1-pool-assign", "ns-2-pool-assign"}
Eventually(func(g Gomega) {
stat := &capsulev1beta2.ResourcePool{}
err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: pool1.Name}, stat)
g.Expect(err).Should(Succeed())
g.Expect(stat.Status.Namespaces).To(Equal(expectedNamespaces))
g.Expect(stat.Status.NamespaceSize).To(Equal(uint(2)))
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
})
By("Create a first claim and verify binding", func() {
err := k8sClient.Create(context.TODO(), claim1)
Expect(err).Should(Succeed(), "Failed to create Claim %s", claim1)
isSuccessfullyBoundAndUnsedToPool(pool1, claim1)
})
By("Create a second claim and verify binding", func() {
err := k8sClient.Create(context.TODO(), claim2)
Expect(err).Should(Succeed(), "Failed to create Claim %s", claim2)
isSuccessfullyBoundAndUnsedToPool(pool1, claim2)
})
By("Create a third claim and verify error", func() {
claim := &capsulev1beta2.ResourcePoolClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "assign-pool-claim-3",
Namespace: "ns-3-pool-assign",
},
Spec: capsulev1beta2.ResourcePoolClaimSpec{
Pool: "test-binding-claims",
ResourceClaims: corev1.ResourceList{
corev1.ResourceLimitsCPU: resource.MustParse("0"),
corev1.ResourceLimitsMemory: resource.MustParse("0"),
corev1.ResourceRequestsCPU: resource.MustParse("0"),
corev1.ResourceRequestsMemory: resource.MustParse("0"),
},
},
}
err := k8sClient.Create(context.TODO(), claim)
Expect(err).Should(Succeed(), "Failed to create Claim %s", claim)
Eventually(func(g Gomega) {
stat := &capsulev1beta2.ResourcePoolClaim{}
err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)
g.Expect(err).Should(Succeed())
expectedPool := meta.LocalRFC1123ObjectReferenceWithUID{}
g.Expect(stat.Status.Pool).To(Equal(expectedPool), "expected pool name to be empty")
g.Expect(len(stat.Status.Conditions)).To(Equal(1), "expected single condition")
g.Expect(len(stat.OwnerReferences)).To(Equal(0), "expected no ownerreferences")
assigned := stat.Status.Conditions.GetConditionByType(meta.ReadyCondition)
g.Expect(assigned.Status).To(Equal(metav1.ConditionFalse), "failed to verify condition status")
g.Expect(assigned.Type).To(Equal(meta.ReadyCondition), "failed to verify condition type")
g.Expect(assigned.Reason).To(Equal(meta.FailedReason), "failed to verify condition reason")
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
})
})
It("Admission (Validation) - Patch Guard", Label("skip-on-openshift"), func() {
pool := &capsulev1beta2.ResourcePool{
ObjectMeta: metav1.ObjectMeta{
Name: "test-admission-claims",
Labels: map[string]string{
"e2e-resourcepoolclaims": "test",
},
},
Spec: capsulev1beta2.ResourcePoolSpec{
Config: capsulev1beta2.ResourcePoolSpecConfiguration{
DeleteBoundResources: ptr.To(false),
},
Selectors: []selectors.NamespaceSelector{
{
LabelSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"e2e.capsule.dev/test-suite": "admission-guards",
},
},
},
},
Quota: corev1.ResourceQuotaSpec{
Hard: corev1.ResourceList{
corev1.ResourceLimitsCPU: resource.MustParse("2"),
corev1.ResourceLimitsMemory: resource.MustParse("2Gi"),
corev1.ResourceRequestsCPU: resource.MustParse("2"),
corev1.ResourceRequestsMemory: resource.MustParse("2Gi"),
},
},
},
}
claim := &capsulev1beta2.ResourcePoolClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "admission-pool-claim-1",
Namespace: "ns-1-pool-admission",
},
Spec: capsulev1beta2.ResourcePoolClaimSpec{
Pool: pool.GetName(),
ResourceClaims: corev1.ResourceList{
corev1.ResourceLimitsCPU: resource.MustParse("1"),
corev1.ResourceLimitsMemory: resource.MustParse("1Gi"),
corev1.ResourceRequestsCPU: resource.MustParse("1"),
corev1.ResourceRequestsMemory: resource.MustParse("1Gi"),
},
},
}
By("Create the Claim", func() {
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: claim.Namespace,
Labels: map[string]string{
"e2e-resourcepoolclaims": "test",
"e2e.capsule.dev/test-suite": "admission-guards",
},
},
}
EventuallyCreation(func() error {
ns.ResourceVersion = ""
return k8sClient.Create(context.TODO(), ns)
}).Should(Succeed(), "Failed to create %s", ns)
EventuallyCreation(func() error {
claim.ResourceVersion = ""
return k8sClient.Create(context.TODO(), claim)
}).Should(Succeed(), "Failed to create %s", claim)
})
By("Create the ResourcePool", func() {
EventuallyCreation(func() error {
pool.ResourceVersion = ""
return k8sClient.Create(context.TODO(), pool)
}).Should(Succeed(), "Failed to create %s", pool)
})
By("Get Applied revision", func() {
err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: pool.Name}, pool)
Expect(err).Should(Succeed())
})
By("Bind a claim", func() {
Eventually(func(g Gomega) {
stat := &capsulev1beta2.ResourcePoolClaim{}
err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)
g.Expect(err).Should(Succeed())
expectedPool := meta.LocalRFC1123ObjectReferenceWithUID{
Name: meta.RFC1123Name(pool.Name),
UID: pool.GetUID(),
}
g.Expect(stat.Status.Pool).To(Equal(expectedPool), "expected pool name to match")
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
isBoundAndUnusedCondition(claim)
})
By("Create a pod with resource requests/limits", func() {
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "claim-pod",
Namespace: claim.Namespace,
Labels: map[string]string{
"e2e": "claim-pod",
},
},
Spec: corev1.PodSpec{
SecurityContext: nobodyPodSecurityContext(),
// optional: helps schedule quickly, avoid restarts
RestartPolicy: corev1.RestartPolicyNever,
Containers: []corev1.Container{
{
Name: "pause",
Image: "registry.k8s.io/pause:3.9",
SecurityContext: restrictedContainerSecurityContext(),
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("10m"),
corev1.ResourceMemory: resource.MustParse("16Mi"),
},
Limits: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("20m"),
corev1.ResourceMemory: resource.MustParse("32Mi"),
},
},
},
},
},
}
Expect(k8sClient.Create(context.TODO(), pod)).To(Succeed())
})
By("Verify the claim is used and cant be deleted", func() {
Eventually(func() error {
stat := &capsulev1beta2.ResourcePoolClaim{}
err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)
Expect(err).Should(Succeed())
isBoundAndUsedCondition(stat)
return k8sClient.Delete(context.TODO(), stat)
}, defaultTimeoutInterval, defaultPollInterval).ShouldNot(Succeed())
})
By("Error on patching resources for claim (Increase)", func() {
Eventually(func() error {
stat := &capsulev1beta2.ResourcePoolClaim{}
err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)
Expect(err).Should(Succeed())
stat.Spec.ResourceClaims = corev1.ResourceList{
corev1.ResourceLimitsCPU: resource.MustParse("2"),
corev1.ResourceLimitsMemory: resource.MustParse("2Gi"),
corev1.ResourceRequestsCPU: resource.MustParse("2"),
corev1.ResourceRequestsMemory: resource.MustParse("2Gi"),
}
return k8sClient.Update(context.TODO(), stat)
}, defaultTimeoutInterval, defaultPollInterval).ShouldNot(Succeed())
})
By("Error on patching resources for claim (Decrease)", func() {
Eventually(func() error {
stat := &capsulev1beta2.ResourcePoolClaim{}
err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)
Expect(err).Should(Succeed())
stat.Spec.ResourceClaims = corev1.ResourceList{
corev1.ResourceLimitsCPU: resource.MustParse("0"),
corev1.ResourceLimitsMemory: resource.MustParse("0Gi"),
corev1.ResourceRequestsCPU: resource.MustParse("0"),
corev1.ResourceRequestsMemory: resource.MustParse("0Gi"),
}
return k8sClient.Update(context.TODO(), stat)
}, defaultTimeoutInterval, defaultPollInterval).ShouldNot(Succeed())
})
By("Error on patching pool name", func() {
Eventually(func() error {
stat := &capsulev1beta2.ResourcePoolClaim{}
err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)
Expect(err).Should(Succeed())
stat.Spec.Pool = "some-random-pool"
return k8sClient.Update(context.TODO(), stat)
}, defaultTimeoutInterval, defaultPollInterval).ShouldNot(Succeed())
})
By("Make the claim unused", func() {
key := client.ObjectKey{Name: "claim-pod", Namespace: claim.Namespace}
pod := &corev1.Pod{}
err := k8sClient.Get(context.TODO(), key, pod)
Expect(err).To(Succeed(), "pod must exist before deleting")
Expect(k8sClient.Delete(context.TODO(), pod, &client.DeleteOptions{
GracePeriodSeconds: ptr.To(int64(0)),
})).To(Succeed())
Eventually(func() bool {
p := &corev1.Pod{}
err := k8sClient.Get(
context.TODO(),
key,
p,
)
return apierrors.IsNotFound(err)
}, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue())
})
By("Bind a claim", func() {
Eventually(func(g Gomega) {
stat := &capsulev1beta2.ResourcePoolClaim{}
err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)
g.Expect(err).Should(Succeed())
expectedPool := meta.LocalRFC1123ObjectReferenceWithUID{
Name: meta.RFC1123Name(pool.Name),
UID: pool.GetUID(),
}
isBoundAndUnusedCondition(stat)
g.Expect(stat.Status.Pool).To(Equal(expectedPool), "expected pool name to match")
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
})
By("Allow on patching resources for claim (Increase)", func() {
Eventually(func() error {
stat := &capsulev1beta2.ResourcePoolClaim{}
err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)
Expect(err).Should(Succeed())
stat.Spec.ResourceClaims = corev1.ResourceList{
corev1.ResourceLimitsCPU: resource.MustParse("2"),
corev1.ResourceLimitsMemory: resource.MustParse("2Gi"),
corev1.ResourceRequestsCPU: resource.MustParse("2"),
corev1.ResourceRequestsMemory: resource.MustParse("2Gi"),
}
return k8sClient.Update(context.TODO(), stat)
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
})
By("Allow on patching resources for claim (Decrease)", func() {
Eventually(func() error {
stat := &capsulev1beta2.ResourcePoolClaim{}
if err := k8sClient.Get(
context.TODO(),
client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace},
stat,
); err != nil {
return err
}
stat.Spec.ResourceClaims = corev1.ResourceList{
corev1.ResourceLimitsCPU: resource.MustParse("0"),
corev1.ResourceLimitsMemory: resource.MustParse("0Gi"),
corev1.ResourceRequestsCPU: resource.MustParse("0"),
corev1.ResourceRequestsMemory: resource.MustParse("0Gi"),
}
return k8sClient.Update(context.TODO(), stat)
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
})
By("Allow on patching pool name", func() {
Eventually(func() error {
stat := &capsulev1beta2.ResourcePoolClaim{}
err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)
Expect(err).Should(Succeed())
stat.Spec.Pool = "some-random-pool"
return k8sClient.Update(context.TODO(), stat)
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
})
By("Delete Pool", func() {
EventuallyDeletion(pool)
})
By("Verify claim is no longer bound", func() {
isUnassignedCondition(claim)
})
By("Allow patching resources for claim (Increase)", func() {
Eventually(func() error {
stat := &capsulev1beta2.ResourcePoolClaim{}
err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)
Expect(err).Should(Succeed())
stat.Spec.ResourceClaims = corev1.ResourceList{
corev1.ResourceLimitsCPU: resource.MustParse("2"),
corev1.ResourceLimitsMemory: resource.MustParse("2Gi"),
corev1.ResourceRequestsCPU: resource.MustParse("2"),
corev1.ResourceRequestsMemory: resource.MustParse("2Gi"),
}
return k8sClient.Update(context.TODO(), stat)
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
})
By("Allow patching resources for claim (Decrease)", func() {
Eventually(func() error {
stat := &capsulev1beta2.ResourcePoolClaim{}
err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)
Expect(err).Should(Succeed())
stat.Spec.ResourceClaims = corev1.ResourceList{
corev1.ResourceLimitsCPU: resource.MustParse("0"),
corev1.ResourceLimitsMemory: resource.MustParse("0Gi"),
corev1.ResourceRequestsCPU: resource.MustParse("0"),
corev1.ResourceRequestsMemory: resource.MustParse("0Gi"),
}
return k8sClient.Update(context.TODO(), stat)
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
})
By("Allow patching pool name", func() {
Eventually(func() error {
stat := &capsulev1beta2.ResourcePoolClaim{}
err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)
Expect(err).Should(Succeed())
stat.Spec.Pool = "some-random-pool"
return k8sClient.Update(context.TODO(), stat)
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
})
})
It("Admission (Mutation) - Release Bound Claim", Label("skip-on-openshift"), func() {
pool := &capsulev1beta2.ResourcePool{
ObjectMeta: metav1.ObjectMeta{
Name: "test-release-bound-claim",
Labels: map[string]string{
"e2e-resourcepoolclaims": "test",
},
},
Spec: capsulev1beta2.ResourcePoolSpec{
Config: capsulev1beta2.ResourcePoolSpecConfiguration{
DeleteBoundResources: ptr.To(false),
},
Selectors: []selectors.NamespaceSelector{
{
LabelSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"e2e.capsule.dev/test-suite": "release-bound-claim",
},
},
},
},
Quota: corev1.ResourceQuotaSpec{
Hard: corev1.ResourceList{
corev1.ResourceLimitsCPU: resource.MustParse("2"),
corev1.ResourceLimitsMemory: resource.MustParse("2Gi"),
corev1.ResourceRequestsCPU: resource.MustParse("2"),
corev1.ResourceRequestsMemory: resource.MustParse("2Gi"),
},
},
},
}
claim := &capsulev1beta2.ResourcePoolClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "release-bound-claim",
Namespace: "ns-release-bound-claim",
},
Spec: capsulev1beta2.ResourcePoolClaimSpec{
Pool: pool.GetName(),
ResourceClaims: corev1.ResourceList{
corev1.ResourceLimitsCPU: resource.MustParse("1"),
corev1.ResourceLimitsMemory: resource.MustParse("1Gi"),
corev1.ResourceRequestsCPU: resource.MustParse("1"),
corev1.ResourceRequestsMemory: resource.MustParse("1Gi"),
},
},
}
By("Create the ResourcePool and namespace", func() {
EventuallyCreation(func() error {
pool.ResourceVersion = ""
return k8sClient.Create(context.TODO(), pool)
}).Should(Succeed(), "Failed to create %s", pool)
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: claim.Namespace,
Labels: map[string]string{
"e2e-resourcepoolclaims": "test",
"e2e.capsule.dev/test-suite": "release-bound-claim",
},
},
}
EventuallyCreation(func() error {
ns.ResourceVersion = ""
return k8sClient.Create(context.TODO(), ns)
}).Should(Succeed(), "Failed to create %s", ns)
})
By("Bind the claim to the pool", func() {
EventuallyCreation(func() error {
claim.ResourceVersion = ""
return k8sClient.Create(context.TODO(), claim)
}).Should(Succeed(), "Failed to create %s", claim)
isSuccessfullyBoundAndUnsedToPool(pool, claim)
})
By("Release the bound claim with the release annotation", func() {
Eventually(func() error {
stat := &capsulev1beta2.ResourcePoolClaim{}
if err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat); err != nil {
return err
}
if stat.Annotations == nil {
stat.Annotations = map[string]string{}
}
stat.Annotations[meta.ReleaseAnnotation] = meta.ReleaseAnnotationTrigger
return k8sClient.Update(context.TODO(), stat)
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
})
By("Verify the claim is disassociated and the release annotation was consumed", func() {
Eventually(func(g Gomega) {
stat := &capsulev1beta2.ResourcePoolClaim{}
g.Expect(k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)).To(Succeed())
bound := stat.Status.Conditions.GetConditionByType(meta.BoundCondition)
g.Expect(bound).ToNot(BeNil(), "Bound condition should not be nil")
g.Expect(bound.Status).To(Equal(metav1.ConditionFalse), "Bound condition status should be False")
g.Expect(stat.Annotations).NotTo(HaveKey(meta.ReleaseAnnotation))
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
})
By("Allow updating the released claim resources while the pool still exists", func() {
Eventually(func() error {
stat := &capsulev1beta2.ResourcePoolClaim{}
if err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat); err != nil {
return err
}
stat.Spec.ResourceClaims = corev1.ResourceList{
corev1.ResourceLimitsCPU: resource.MustParse("2"),
corev1.ResourceLimitsMemory: resource.MustParse("2Gi"),
corev1.ResourceRequestsCPU: resource.MustParse("2"),
corev1.ResourceRequestsMemory: resource.MustParse("2Gi"),
}
return k8sClient.Update(context.TODO(), stat)
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
})
})
It("Admission (Mutation) - Auto Pool Assign", Label("skip-on-openshift"), func() {
pool1 := &capsulev1beta2.ResourcePool{
ObjectMeta: metav1.ObjectMeta{
Name: "test-auto-assign-1",
Labels: map[string]string{
"e2e-resourcepoolclaims": "test",
},
},
Spec: capsulev1beta2.ResourcePoolSpec{
Config: capsulev1beta2.ResourcePoolSpecConfiguration{
DeleteBoundResources: ptr.To(false),
},
Selectors: []selectors.NamespaceSelector{
{
LabelSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"e2e.capsule.dev/test-suite": "admission-auto-assign",
},
},
},
},
Quota: corev1.ResourceQuotaSpec{
Hard: corev1.ResourceList{
corev1.ResourceLimitsCPU: resource.MustParse("2"),
corev1.ResourceRequestsCPU: resource.MustParse("2"),
},
},
},
}
pool2 := &capsulev1beta2.ResourcePool{
ObjectMeta: metav1.ObjectMeta{
Name: "test-auto-assign-2",
Labels: map[string]string{
"e2e-resourcepoolclaims": "test",
},
},
Spec: capsulev1beta2.ResourcePoolSpec{
Config: capsulev1beta2.ResourcePoolSpecConfiguration{
DeleteBoundResources: ptr.To(false),
},
Selectors: []selectors.NamespaceSelector{
{
LabelSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"e2e.capsule.dev/test-suite": "admission-auto-assign",
},
},
},
},
Quota: corev1.ResourceQuotaSpec{
Hard: corev1.ResourceList{
corev1.ResourceLimitsMemory: resource.MustParse("2"),
corev1.ResourceRequestsMemory: resource.MustParse("2"),
},
},
},
}
By("Create the ResourcePools", func() {
EventuallyCreation(func() error {
pool1.ResourceVersion = ""
return k8sClient.Create(context.TODO(), pool1)
}).Should(Succeed(), "Failed to create %s", pool1)
EventuallyCreation(func() error {
pool2.ResourceVersion = ""
return k8sClient.Create(context.TODO(), pool2)
}).Should(Succeed(), "Failed to create %s", pool2)
})
By("Auto Assign Claim (CPU)", func() {
claim := &capsulev1beta2.ResourcePoolClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "auto-assign-1",
Namespace: "ns-1-pool-assign",
},
Spec: capsulev1beta2.ResourcePoolClaimSpec{
ResourceClaims: corev1.ResourceList{
corev1.ResourceLimitsCPU: resource.MustParse("1"),
corev1.ResourceRequestsCPU: resource.MustParse("1"),
},
},
}
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: claim.Namespace,
Labels: map[string]string{
"e2e-resourcepoolclaims": "test",
"e2e.capsule.dev/test-suite": "admission-auto-assign",
},
},
}
EventuallyCreation(func() error {
return k8sClient.Create(context.TODO(), ns)
}).Should(Succeed())
ExpectNamespaceInResourcePoolEventually(pool1.Name, ns.Name)
EventuallyCreation(func() error {
claim.ResourceVersion = ""
return k8sClient.Create(context.TODO(), claim)
}).Should(Succeed(), "Failed to create Claim %s/%s", claim.Namespace, claim.Name)
Eventually(func(g Gomega) {
stat := &capsulev1beta2.ResourcePoolClaim{}
g.Expect(k8sClient.Get(
context.TODO(),
client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace},
stat,
)).To(Succeed())
g.Expect(stat.Spec.Pool).To(Equal(pool1.Name), "expected pool name to match")
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
})
By("Auto Assign Claim (Memory)", func() {
claim := &capsulev1beta2.ResourcePoolClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "auto-assign-1",
Namespace: "ns-2-pool-assign",
},
Spec: capsulev1beta2.ResourcePoolClaimSpec{
ResourceClaims: corev1.ResourceList{
corev1.ResourceLimitsMemory: resource.MustParse("1"),
corev1.ResourceRequestsMemory: resource.MustParse("1"),
},
},
}
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: claim.Namespace,
Labels: map[string]string{
"e2e-resourcepoolclaims": "test",
"e2e.capsule.dev/test-suite": "admission-auto-assign",
},
},
}
err := k8sClient.Create(context.TODO(), ns)
Expect(err).Should(Succeed())
ExpectNamespaceInResourcePoolEventually(pool2.Name, ns.Name)
err = k8sClient.Create(context.TODO(), claim)
Expect(err).Should(Succeed(), "Failed to create Claim %s", claim)
Eventually(func(g Gomega) {
stat := &capsulev1beta2.ResourcePoolClaim{}
err = k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)
g.Expect(err).Should(Succeed())
g.Expect(stat.Spec.Pool).To(Equal(pool2.Name), "expected pool name to match")
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
})
By("No Default available (Storage)", func() {
claim := &capsulev1beta2.ResourcePoolClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "auto-assign-3",
Namespace: "ns-3-pool-assign",
},
Spec: capsulev1beta2.ResourcePoolClaimSpec{
ResourceClaims: corev1.ResourceList{
corev1.ResourceRequestsStorage: resource.MustParse("1"),
},
},
}
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: claim.Namespace,
Labels: map[string]string{
"e2e-resourcepoolclaims": "test",
"e2e.capsule.dev/test-suite": "admission-auto-assign",
},
},
}
EventuallyCreation(func() error {
ns.ResourceVersion = ""
return k8sClient.Create(context.TODO(), ns)
}).Should(Succeed(), "Failed to create %s", ns)
ExpectNamespaceInResourcePoolEventually(pool1.Name, ns.Name)
EventuallyCreation(func() error {
claim.ResourceVersion = ""
return k8sClient.Create(context.TODO(), claim)
}).Should(Succeed(), "Failed to create %s", claim)
Eventually(func(g Gomega) {
stat := &capsulev1beta2.ResourcePoolClaim{}
err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)
g.Expect(err).Should(Succeed())
g.Expect(stat.Spec.Pool).To(Equal(""), "expected pool name to match")
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
})
})
})
func isUnassignedCondition(claim *capsulev1beta2.ResourcePoolClaim) {
Eventually(func(g Gomega) {
cl := &capsulev1beta2.ResourcePoolClaim{}
err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, cl)
g.Expect(err).Should(Succeed())
assigned := cl.Status.Conditions.GetConditionByType(meta.ReadyCondition)
g.Expect(assigned).NotTo(BeNil(), "Ready condition should be present")
g.Expect(assigned.Status).To(Equal(metav1.ConditionFalse), "failed to verify condition status")
g.Expect(assigned.Type).To(Equal(meta.ReadyCondition), "failed to verify condition type")
g.Expect(assigned.Reason).To(Equal(meta.FailedReason), "failed to verify condition reason")
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
}
func isBoundAndUnusedCondition(claim *capsulev1beta2.ResourcePoolClaim) {
Eventually(func(g Gomega) {
cl := &capsulev1beta2.ResourcePoolClaim{}
err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, cl)
g.Expect(err).Should(Succeed())
bound := cl.Status.Conditions.GetConditionByType(meta.BoundCondition)
g.Expect(bound).NotTo(BeNil(), "Bound condition should be present")
g.Expect(bound.Type).To(Equal(meta.BoundCondition), "failed to verify condition type")
g.Expect(bound.Reason).To(Equal(meta.UnusedReason), "failed to verify condition reason")
g.Expect(bound.Status).To(Equal(metav1.ConditionFalse), "failed to verify condition status")
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
}
func isBoundAndUsedCondition(claim *capsulev1beta2.ResourcePoolClaim) {
Eventually(func(g Gomega) {
cl := &capsulev1beta2.ResourcePoolClaim{}
err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, cl)
g.Expect(err).Should(Succeed())
bound := cl.Status.Conditions.GetConditionByType(meta.BoundCondition)
g.Expect(bound).NotTo(BeNil(), "Bound condition should be present")
g.Expect(bound.Status).To(Equal(metav1.ConditionTrue), "failed to verify condition status")
g.Expect(bound.Type).To(Equal(meta.BoundCondition), "failed to verify condition type")
g.Expect(bound.Reason).To(Equal(meta.InUseReason), "failed to verify condition reason")
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
}