Skip to content

Commit

Permalink
add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
carlory committed Apr 18, 2024
1 parent e6a8f9c commit 5a31a46
Show file tree
Hide file tree
Showing 2 changed files with 278 additions and 0 deletions.
79 changes: 79 additions & 0 deletions pkg/quota/v1/evaluator/core/persistent_volume_claims_test.go
Expand Up @@ -20,6 +20,7 @@ import (
"reflect"
"testing"

"github.com/google/go-cmp/cmp"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -30,6 +31,7 @@ import (
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/features"
"k8s.io/utils/ptr"
)

func testVolumeClaim(name string, namespace string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim {
Expand All @@ -39,6 +41,83 @@ func testVolumeClaim(name string, namespace string, spec core.PersistentVolumeCl
}
}

func TestPersistentVolumeClaimEvaluatorMatchingScopes(t *testing.T) {
evaluator := NewPersistentVolumeClaimEvaluator(nil)
testCases := map[string]struct {
claim *core.PersistentVolumeClaim
selectors []corev1.ScopedResourceSelectorRequirement
wantSelectors []corev1.ScopedResourceSelectorRequirement
}{
"VolumeAttributesClass": {
claim: &core.PersistentVolumeClaim{
Spec: core.PersistentVolumeClaimSpec{
VolumeAttributesClassName: ptr.To("class1"),
},
},
selectors: []corev1.ScopedResourceSelectorRequirement{
{ScopeName: corev1.ResourceQuotaScopeVolumeAttributesClass, Operator: corev1.ScopeSelectorOpIn, Values: []string{"class1"}},
},
wantSelectors: []corev1.ScopedResourceSelectorRequirement{
{ScopeName: corev1.ResourceQuotaScopeVolumeAttributesClass, Operator: corev1.ScopeSelectorOpIn, Values: []string{"class1"}},
},
},
"VolumeAttributesClassWithTarget": {
claim: &core.PersistentVolumeClaim{
Spec: core.PersistentVolumeClaimSpec{
VolumeAttributesClassName: ptr.To("class1"),
},
Status: core.PersistentVolumeClaimStatus{
CurrentVolumeAttributesClassName: ptr.To("class2"),
},
},
selectors: []corev1.ScopedResourceSelectorRequirement{
{ScopeName: corev1.ResourceQuotaScopeVolumeAttributesClass, Operator: corev1.ScopeSelectorOpIn, Values: []string{"class1"}},
{ScopeName: corev1.ResourceQuotaScopeVolumeAttributesClass, Operator: corev1.ScopeSelectorOpIn, Values: []string{"class2"}},
},
wantSelectors: []corev1.ScopedResourceSelectorRequirement{
{ScopeName: corev1.ResourceQuotaScopeVolumeAttributesClass, Operator: corev1.ScopeSelectorOpIn, Values: []string{"class1"}},
{ScopeName: corev1.ResourceQuotaScopeVolumeAttributesClass, Operator: corev1.ScopeSelectorOpIn, Values: []string{"class2"}},
},
},
"VolumeAttributesClassWithModityStatus": {
claim: &core.PersistentVolumeClaim{
Spec: core.PersistentVolumeClaimSpec{
VolumeAttributesClassName: ptr.To("class1"),
},
Status: core.PersistentVolumeClaimStatus{
CurrentVolumeAttributesClassName: ptr.To("class2"),
ModifyVolumeStatus: &core.ModifyVolumeStatus{
TargetVolumeAttributesClassName: "class3",
},
},
},
selectors: []corev1.ScopedResourceSelectorRequirement{
{ScopeName: corev1.ResourceQuotaScopeVolumeAttributesClass, Operator: corev1.ScopeSelectorOpIn, Values: []string{"class1"}},
{ScopeName: corev1.ResourceQuotaScopeVolumeAttributesClass, Operator: corev1.ScopeSelectorOpIn, Values: []string{"class2"}},
{ScopeName: corev1.ResourceQuotaScopeVolumeAttributesClass, Operator: corev1.ScopeSelectorOpIn, Values: []string{"class3"}},
},
wantSelectors: []corev1.ScopedResourceSelectorRequirement{
{ScopeName: corev1.ResourceQuotaScopeVolumeAttributesClass, Operator: corev1.ScopeSelectorOpIn, Values: []string{"class1"}},
{ScopeName: corev1.ResourceQuotaScopeVolumeAttributesClass, Operator: corev1.ScopeSelectorOpIn, Values: []string{"class2"}},
{ScopeName: corev1.ResourceQuotaScopeVolumeAttributesClass, Operator: corev1.ScopeSelectorOpIn, Values: []string{"class3"}},
},
},
}

for testName, testCase := range testCases {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, true)()
t.Run(testName, func(t *testing.T) {
gotSelectors, err := evaluator.MatchingScopes(testCase.claim, testCase.selectors)
if err != nil {
t.Error(err)
}
if diff := cmp.Diff(testCase.wantSelectors, gotSelectors); diff != "" {
t.Errorf("%v: unexpected diff (-want, +got):\n%s", testName, diff)
}
})
}
}

func TestPersistentVolumeClaimEvaluatorUsage(t *testing.T) {
classGold := "gold"
validClaim := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
Expand Down
199 changes: 199 additions & 0 deletions test/e2e/apimachinery/resource_quota.go
Expand Up @@ -45,13 +45,15 @@ import (
"k8s.io/client-go/tools/cache"
watchtools "k8s.io/client-go/tools/watch"
"k8s.io/client-go/util/retry"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/quota/v1/evaluator/core"
"k8s.io/kubernetes/test/e2e/feature"
"k8s.io/kubernetes/test/e2e/framework"
"k8s.io/kubernetes/test/utils/crd"
imageutils "k8s.io/kubernetes/test/utils/image"
admissionapi "k8s.io/pod-security-admission/api"
"k8s.io/utils/pointer"
"k8s.io/utils/ptr"

"github.com/onsi/ginkgo/v2"
"github.com/onsi/gomega"
Expand All @@ -64,6 +66,7 @@ const (
)

var classGold = "gold"
var classSilver = "silver"
var extendedResourceName = "example.com/dongle"

var _ = SIGDescribe("ResourceQuota", func() {
Expand Down Expand Up @@ -1219,6 +1222,183 @@ var _ = SIGDescribe("ResourceQuota", func() {
})
})

var _ = SIGDescribe("ResourceQuota", feature.VolumeAttributesClass, framework.WithFeatureGate(features.VolumeAttributesClass), func() {
f := framework.NewDefaultFramework("resourcequota-volumeattributesclass")
f.NamespacePodSecurityLevel = admissionapi.LevelBaseline

ginkgo.It("should verify ResourceQuota's volume attributes class scope (quota set to pvc count: 1) against a pvc with same volume attributes class.", func(ctx context.Context) {
hard := v1.ResourceList{}
hard[v1.ResourcePersistentVolumeClaims] = resource.MustParse("1")

ginkgo.By("Creating a ResourceQuota with volume attributes class scope")
quota, err := createResourceQuota(ctx, f.ClientSet, f.Namespace.Name, newTestResourceQuotaWithScopeForVolumeAttributesClass("quota-volumeattributesclass", hard, v1.ScopeSelectorOpIn, []string{classGold}))
framework.ExpectNoError(err)

ginkgo.By("Ensuring ResourceQuota status is calculated")
usedResources := v1.ResourceList{}
usedResources[v1.ResourcePersistentVolumeClaims] = resource.MustParse("0")
err = waitForResourceQuota(ctx, f.ClientSet, f.Namespace.Name, quota.Name, usedResources)
framework.ExpectNoError(err)

ginkgo.By("Creating a pvc with volume attributes class")
pvc1 := newTestPersistentVolumeClaimForQuota("test-claim-1")
pvc1.Spec.StorageClassName = ptr.To("")
pvc1.Spec.VolumeAttributesClassName = &classGold
pvc1, err = f.ClientSet.CoreV1().PersistentVolumeClaims(f.Namespace.Name).Create(ctx, pvc1, metav1.CreateOptions{})
framework.ExpectNoError(err)

ginkgo.By("Ensuring resource quota with volume attributes class scope captures the pvc usage")
usedResources[v1.ResourcePersistentVolumeClaims] = resource.MustParse("1")
err = waitForResourceQuota(ctx, f.ClientSet, f.Namespace.Name, quota.Name, usedResources)
framework.ExpectNoError(err)

ginkgo.By("Updating the pvc modify volume status with volume attributes class")
pvc1.Status.ModifyVolumeStatus = &v1.ModifyVolumeStatus{
TargetVolumeAttributesClassName: classGold,
Status: v1.PersistentVolumeClaimModifyVolumePending,
}
pvc1, err = f.ClientSet.CoreV1().PersistentVolumeClaims(f.Namespace.Name).UpdateStatus(ctx, pvc1, metav1.UpdateOptions{})
framework.ExpectNoError(err)

ginkgo.By("Ensuring resource quota not changed after updating the pvc status")
gomega.Consistently(ctx,
framework.GetObject(f.ClientSet.CoreV1().ResourceQuotas(f.Namespace.Name).Get, quota.Name, metav1.GetOptions{})).
WithPolling(5 * time.Second).
WithTimeout(30 * time.Second).
Should(gomega.HaveField("Status.Used", gomega.Equal(usedResources)))

ginkgo.By("Updating the pvc status with volume attributes class")
pvc1.Status.ModifyVolumeStatus = nil
pvc1.Status.CurrentVolumeAttributesClassName = &classGold
pvc1, err = f.ClientSet.CoreV1().PersistentVolumeClaims(f.Namespace.Name).UpdateStatus(ctx, pvc1, metav1.UpdateOptions{})
framework.ExpectNoError(err)

ginkgo.By("Ensuring resource quota not changed after updating the pvc status")
gomega.Consistently(ctx,
framework.GetObject(f.ClientSet.CoreV1().ResourceQuotas(f.Namespace.Name).Get, quota.Name, metav1.GetOptions{})).
WithPolling(5 * time.Second).
WithTimeout(30 * time.Second).
Should(gomega.HaveField("Status.Used", gomega.Equal(usedResources)))

ginkgo.By("Deleting the pvc")
err = f.ClientSet.CoreV1().PersistentVolumeClaims(f.Namespace.Name).Delete(ctx, pvc1.Name, metav1.DeleteOptions{})
framework.ExpectNoError(err)

ginkgo.By("Ensuring resource quota status released the pvc usage")
usedResources[v1.ResourcePersistentVolumeClaims] = resource.MustParse("0")
err = waitForResourceQuota(ctx, f.ClientSet, f.Namespace.Name, quota.Name, usedResources)
framework.ExpectNoError(err)
})

ginkgo.It("should verify ResourceQuota's volume attributes class scope (quota set to pvc count: 1) against a pvc with different volume attributes class.", func(ctx context.Context) {
hard := v1.ResourceList{}
hard[v1.ResourcePersistentVolumeClaims] = resource.MustParse("1")

ginkgo.By("Creating 2 ResourceQuotas with volume attributes class scope")
quotaGold, err := createResourceQuota(ctx, f.ClientSet, f.Namespace.Name, newTestResourceQuotaWithScopeForVolumeAttributesClass("quota-volumeattributesclass-gold", hard, v1.ScopeSelectorOpIn, []string{classGold}))
framework.ExpectNoError(err)
quotaSilver, err := createResourceQuota(ctx, f.ClientSet, f.Namespace.Name, newTestResourceQuotaWithScopeForVolumeAttributesClass("quota-volumeattributesclass-silver", hard, v1.ScopeSelectorOpIn, []string{classSilver}))
framework.ExpectNoError(err)

ginkgo.By("Ensuring all ResourceQuotas status is calculated")
usedResources := v1.ResourceList{}
usedResources[v1.ResourcePersistentVolumeClaims] = resource.MustParse("0")
err = waitForResourceQuota(ctx, f.ClientSet, f.Namespace.Name, quotaGold.Name, usedResources)
framework.ExpectNoError(err)
err = waitForResourceQuota(ctx, f.ClientSet, f.Namespace.Name, quotaSilver.Name, usedResources)
framework.ExpectNoError(err)

ginkgo.By("Creating a pvc with gold volume attributes class")
pvc1 := newTestPersistentVolumeClaimForQuota("test-claim-1")
pvc1.Spec.StorageClassName = ptr.To("")
pvc1.Spec.VolumeAttributesClassName = &classGold
pvc1, err = f.ClientSet.CoreV1().PersistentVolumeClaims(f.Namespace.Name).Create(ctx, pvc1, metav1.CreateOptions{})
framework.ExpectNoError(err)

ginkgo.By("Ensuring first resource quota with volume attributes class scope captures the pvc usage")
usedResources[v1.ResourcePersistentVolumeClaims] = resource.MustParse("1")
err = waitForResourceQuota(ctx, f.ClientSet, f.Namespace.Name, quotaGold.Name, usedResources)
framework.ExpectNoError(err)

ginkgo.By("Updating the pvc status with volume attributes class")
pvc1.Status.ModifyVolumeStatus = nil
pvc1.Status.CurrentVolumeAttributesClassName = &classGold
pvc1, err = f.ClientSet.CoreV1().PersistentVolumeClaims(f.Namespace.Name).UpdateStatus(ctx, pvc1, metav1.UpdateOptions{})
framework.ExpectNoError(err)

ginkgo.By("Updating the pvc with silver volume attributes class")
pvc1.Spec.VolumeAttributesClassName = &classSilver
pvc1, err = f.ClientSet.CoreV1().PersistentVolumeClaims(f.Namespace.Name).Update(ctx, pvc1, metav1.UpdateOptions{})
framework.ExpectNoError(err)

ginkgo.By("Ensuring resource quota not changed after updating the pvc status and spec")
gomega.Consistently(ctx,
framework.GetObject(f.ClientSet.CoreV1().ResourceQuotas(f.Namespace.Name).Get, quotaGold.Name, metav1.GetOptions{})).
WithPolling(5 * time.Second).
WithTimeout(30 * time.Second).
Should(gomega.HaveField("Status.Used", gomega.Equal(usedResources)))

ginkgo.By("Ensuring 2nd resource quota with volume attributes class scope captures the pvc usage")
usedResources[v1.ResourcePersistentVolumeClaims] = resource.MustParse("1")
err = waitForResourceQuota(ctx, f.ClientSet, f.Namespace.Name, quotaSilver.Name, usedResources)
framework.ExpectNoError(err)

ginkgo.By("Replacing the pvc status with silver volume attributes class")
pvc1.Status.ModifyVolumeStatus = nil
pvc1.Status.CurrentVolumeAttributesClassName = &classSilver
_, err = f.ClientSet.CoreV1().PersistentVolumeClaims(f.Namespace.Name).Update(ctx, pvc1, metav1.UpdateOptions{})
framework.ExpectNoError(err)

ginkgo.By("Ensuring first resource quota status released the pvc usage")
usedResources[v1.ResourcePersistentVolumeClaims] = resource.MustParse("0")
err = waitForResourceQuota(ctx, f.ClientSet, f.Namespace.Name, quotaGold.Name, usedResources)
framework.ExpectNoError(err)
})

ginkgo.It("should verify ResourceQuota's volume attributes class scope (quota set to pvc count: 1) against 2 pvcs with same volume attributes class.", func(ctx context.Context) {
hard := v1.ResourceList{}
hard[v1.ResourcePersistentVolumeClaims] = resource.MustParse("1")

ginkgo.By("Creating a ResourceQuota with volume attributes class scope")
quota, err := createResourceQuota(ctx, f.ClientSet, f.Namespace.Name, newTestResourceQuotaWithScopeForVolumeAttributesClass("quota-volumeattributesclass", hard, v1.ScopeSelectorOpIn, []string{classGold}))
framework.ExpectNoError(err)

ginkgo.By("Ensuring ResourceQuota status is calculated")
usedResources := v1.ResourceList{}
usedResources[v1.ResourcePersistentVolumeClaims] = resource.MustParse("0")
err = waitForResourceQuota(ctx, f.ClientSet, f.Namespace.Name, quota.Name, usedResources)
framework.ExpectNoError(err)

ginkgo.By("Creating a pvc with volume attributes class")
pvc1 := newTestPersistentVolumeClaimForQuota("test-claim-1")
pvc1.Spec.StorageClassName = ptr.To("")
pvc1.Spec.VolumeAttributesClassName = &classGold
pvc1, err = f.ClientSet.CoreV1().PersistentVolumeClaims(f.Namespace.Name).Create(ctx, pvc1, metav1.CreateOptions{})
framework.ExpectNoError(err)

ginkgo.By("Ensuring resource quota with volume attributes class scope captures the pvc usage")
usedResources[v1.ResourcePersistentVolumeClaims] = resource.MustParse("1")
err = waitForResourceQuota(ctx, f.ClientSet, f.Namespace.Name, quota.Name, usedResources)
framework.ExpectNoError(err)

ginkgo.By("Creating 2nd pod with priority class should fail")
pvc2 := newTestPersistentVolumeClaimForQuota("test-claim-2")
pvc2.Spec.StorageClassName = ptr.To("")
pvc2.Spec.VolumeAttributesClassName = &classGold
_, err = f.ClientSet.CoreV1().PersistentVolumeClaims(f.Namespace.Name).Create(ctx, pvc2, metav1.CreateOptions{})
gomega.Expect(err).To(gomega.MatchError(apierrors.IsForbidden, "expect a forbidden error when creating a PVC that exceeds quota"))

ginkgo.By("Deleting first pvc")
err = f.ClientSet.CoreV1().PersistentVolumeClaims(f.Namespace.Name).Delete(ctx, pvc1.Name, metav1.DeleteOptions{})
framework.ExpectNoError(err)

ginkgo.By("Ensuring resource quota status released the pvc usage")
usedResources[v1.ResourcePersistentVolumeClaims] = resource.MustParse("0")
err = waitForResourceQuota(ctx, f.ClientSet, f.Namespace.Name, quota.Name, usedResources)
framework.ExpectNoError(err)
})
})

var _ = SIGDescribe("ResourceQuota", feature.ScopeSelectors, func() {
f := framework.NewDefaultFramework("scope-selectors")
f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
Expand Down Expand Up @@ -1878,6 +2058,25 @@ func newTestResourceQuotaWithScopeForPriorityClass(name string, hard v1.Resource
}
}

// newTestResourceQuotaWithScopeForVolumeAttributesClass returns a quota
// that enforces default constraints for testing with ResourceQuotaScopeVolumeAttributesClass scope
func newTestResourceQuotaWithScopeForVolumeAttributesClass(name string, hard v1.ResourceList, op v1.ScopeSelectorOperator, values []string) *v1.ResourceQuota {
return &v1.ResourceQuota{
ObjectMeta: metav1.ObjectMeta{Name: name},
Spec: v1.ResourceQuotaSpec{Hard: hard,
ScopeSelector: &v1.ScopeSelector{
MatchExpressions: []v1.ScopedResourceSelectorRequirement{
{
ScopeName: v1.ResourceQuotaScopeVolumeAttributesClass,
Operator: op,
Values: values,
},
},
},
},
}
}

// newTestResourceQuota returns a quota that enforces default constraints for testing
func newTestResourceQuota(name string) *v1.ResourceQuota {
hard := v1.ResourceList{}
Expand Down

0 comments on commit 5a31a46

Please sign in to comment.