Skip to content

Commit 6da4f57

Browse files
committed
Implement e2e test for Retroactive StorageClass Assignment feature
Signed-off-by: torredil <[email protected]>
1 parent dc481fe commit 6da4f57

File tree

1 file changed

+101
-121
lines changed

1 file changed

+101
-121
lines changed

test/e2e/storage/pvc_storageclass.go

Lines changed: 101 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -18,172 +18,152 @@ package storage
1818

1919
import (
2020
"context"
21-
"fmt"
2221
"time"
2322

23+
"k8s.io/klog/v2"
24+
2425
"github.com/onsi/ginkgo/v2"
2526
"github.com/onsi/gomega"
26-
2727
v1 "k8s.io/api/core/v1"
2828
storagev1 "k8s.io/api/storage/v1"
2929
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30-
"k8s.io/apimachinery/pkg/util/wait"
3130
clientset "k8s.io/client-go/kubernetes"
3231
storageutil "k8s.io/kubernetes/pkg/apis/storage/util"
3332
"k8s.io/kubernetes/test/e2e/framework"
3433
e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
35-
"k8s.io/kubernetes/test/e2e/storage/testsuites"
3634
"k8s.io/kubernetes/test/e2e/storage/utils"
3735
admissionapi "k8s.io/pod-security-admission/api"
3836
)
3937

40-
var _ = utils.SIGDescribe("Persistent Volume Claim and StorageClass", func() {
41-
f := framework.NewDefaultFramework("pvc-retroactive-storageclass")
38+
var _ = utils.SIGDescribe("Retroactive StorageClass Assignment", func() {
39+
f := framework.NewDefaultFramework("retroactive-storageclass")
4240
f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
4341

4442
var (
4543
client clientset.Interface
4644
namespace string
47-
prefixPVC string
48-
prefixSC string
49-
t testsuites.StorageClassTest
50-
pvc *v1.PersistentVolumeClaim
51-
err error
5245
)
5346

5447
ginkgo.BeforeEach(func() {
5548
client = f.ClientSet
5649
namespace = f.Namespace.Name
57-
prefixPVC = "retro-pvc-"
58-
prefixSC = "retro"
59-
t = testsuites.StorageClassTest{
60-
Timeouts: f.Timeouts,
61-
ClaimSize: "1Gi",
62-
}
6350
})
6451

65-
f.Describe("Retroactive StorageClass assignment", framework.WithSerial(), framework.WithDisruptive(), func() {
66-
ginkgo.It("should assign default SC to PVCs that have no SC set", func(ctx context.Context) {
67-
68-
// Temporarily set all default storage classes as non-default
69-
restoreClasses := temporarilyUnsetDefaultClasses(ctx, client)
70-
defer restoreClasses()
71-
72-
// Create PVC with nil SC
73-
pvcObj := e2epv.MakePersistentVolumeClaim(e2epv.PersistentVolumeClaimConfig{
74-
NamePrefix: prefixPVC,
75-
ClaimSize: t.ClaimSize,
76-
VolumeMode: &t.VolumeMode,
77-
}, namespace)
78-
pvc, err = client.CoreV1().PersistentVolumeClaims(pvcObj.Namespace).Create(ctx, pvcObj, metav1.CreateOptions{})
79-
framework.ExpectNoError(err, "Error creating PVC")
80-
defer func(pvc *v1.PersistentVolumeClaim) {
81-
// Remove test PVC
82-
err := client.CoreV1().PersistentVolumeClaims(pvc.Namespace).Delete(ctx, pvc.Name, metav1.DeleteOptions{})
83-
framework.ExpectNoError(err, "Error cleaning up PVC")
84-
}(pvc)
85-
86-
// Create custom default SC
87-
storageClass := testsuites.SetupStorageClass(ctx, client, makeStorageClass(prefixSC))
88-
89-
// Wait for PVC to get updated with the new default SC
90-
pvc, err = waitForPVCStorageClass(ctx, client, namespace, pvc.Name, storageClass.Name, f.Timeouts.ClaimBound)
91-
framework.ExpectNoError(err, "Error updating PVC with the correct storage class")
92-
93-
// Create PV with specific class
94-
pv := e2epv.MakePersistentVolume(e2epv.PersistentVolumeConfig{
95-
NamePrefix: "pv-",
96-
StorageClassName: storageClass.Name,
97-
VolumeMode: pvc.Spec.VolumeMode,
98-
PVSource: v1.PersistentVolumeSource{
99-
HostPath: &v1.HostPathVolumeSource{
100-
Path: "/tmp/test",
101-
},
102-
},
103-
})
104-
_, err = e2epv.CreatePV(ctx, client, f.Timeouts, pv)
105-
framework.ExpectNoError(err, "Error creating pv %v", err)
106-
ginkgo.DeferCleanup(e2epv.DeletePersistentVolume, client, pv.Name)
107-
108-
// Verify the PVC is bound and has the new default SC
109-
claimNames := []string{pvc.Name}
110-
err = e2epv.WaitForPersistentVolumeClaimsPhase(ctx, v1.ClaimBound, client, namespace, claimNames, 2*time.Second /* Poll */, t.Timeouts.ClaimProvisionShort, false)
111-
framework.ExpectNoError(err)
112-
updatedPVC, err := client.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, pvc.Name, metav1.GetOptions{})
113-
framework.ExpectNoError(err)
114-
gomega.Expect(*updatedPVC.Spec.StorageClassName).To(gomega.Equal(storageClass.Name), "Expected PVC %v to have StorageClass %v, but it has StorageClass %v instead", updatedPVC.Name, prefixSC, updatedPVC.Spec.StorageClassName)
115-
framework.Logf("Success - PersistentVolumeClaim %s got updated retroactively with StorageClass %v", updatedPVC.Name, storageClass.Name)
52+
f.It("should assign default StorageClass to PVCs retroactively", f.WithDisruptive(), f.WithSerial(), func(ctx context.Context) {
53+
defaultSCs, err := getDefaultStorageClasses(ctx, client)
54+
framework.ExpectNoError(err, "Failed to get default StorageClasses")
55+
56+
ginkgo.DeferCleanup(func(cleanupContext context.Context) {
57+
// Restore existing default StorageClasses at the end of the test
58+
for _, sc := range defaultSCs {
59+
setStorageClassDefault(cleanupContext, client, sc.Name, "true")
60+
}
61+
})
62+
63+
// Unset all default StorageClasses
64+
for _, sc := range defaultSCs {
65+
klog.InfoS("Unsetting default StorageClass", "StorageClass", sc.Name)
66+
setStorageClassDefault(ctx, client, sc.Name, "false")
67+
}
68+
69+
// Ensure no default StorageClasses exist
70+
if len(defaultSCs) > 0 {
71+
ensureNoDefaultStorageClasses(ctx, client)
72+
}
73+
74+
// Create a PVC with nil StorageClass
75+
pvc := createPVC(ctx, client, namespace)
76+
ginkgo.DeferCleanup(func(cleanupContext context.Context) {
77+
err := client.CoreV1().PersistentVolumeClaims(namespace).Delete(ctx, pvc.Name, *metav1.NewDeleteOptions(0))
78+
framework.ExpectNoError(err, "Error deleting PVC")
11679
})
80+
81+
// Create a default StorageClass
82+
sc := createDefaultStorageClass(ctx, client)
83+
ginkgo.DeferCleanup(func(cleanupContext context.Context) {
84+
deleteStorageClass(cleanupContext, client, sc.Name)
85+
})
86+
87+
// Verify that the PVC is assigned the default StorageClass
88+
gomega.Eventually(ctx, framework.GetObject(client.CoreV1().PersistentVolumeClaims(pvc.Namespace).Get, pvc.Name, metav1.GetOptions{})).
89+
WithPolling(framework.Poll).
90+
WithTimeout(2*time.Minute).
91+
Should(gomega.HaveField("Spec.StorageClassName", gomega.Equal(&sc.Name)),
92+
"failed to wait for PVC to have the storageclass %s", sc.Name)
11793
})
11894
})
11995

120-
func makeStorageClass(prefixSC string) *storagev1.StorageClass {
121-
return &storagev1.StorageClass{
122-
TypeMeta: metav1.TypeMeta{
123-
Kind: "StorageClass",
124-
},
125-
ObjectMeta: metav1.ObjectMeta{
126-
GenerateName: prefixSC,
127-
Annotations: map[string]string{
128-
storageutil.IsDefaultStorageClassAnnotation: "true",
129-
},
130-
},
131-
Provisioner: "fake-1",
96+
func getDefaultStorageClasses(ctx context.Context, client clientset.Interface) ([]storagev1.StorageClass, error) {
97+
scList, err := client.StorageV1().StorageClasses().List(ctx, metav1.ListOptions{})
98+
if err != nil {
99+
return nil, err
132100
}
133-
}
134-
135-
func temporarilyUnsetDefaultClasses(ctx context.Context, client clientset.Interface) func() {
136-
classes, err := client.StorageV1().StorageClasses().List(ctx, metav1.ListOptions{})
137-
framework.ExpectNoError(err)
138-
139-
changedClasses := make(map[string]bool)
140101

141-
for _, sc := range classes.Items {
102+
var defaultSCs []storagev1.StorageClass
103+
for _, sc := range scList.Items {
142104
if sc.Annotations[storageutil.IsDefaultStorageClassAnnotation] == "true" {
143-
changedClasses[sc.GetName()] = true
144-
sc.Annotations[storageutil.IsDefaultStorageClassAnnotation] = "false"
145-
_, err := client.StorageV1().StorageClasses().Update(ctx, &sc, metav1.UpdateOptions{})
146-
framework.ExpectNoError(err)
105+
defaultSCs = append(defaultSCs, sc)
147106
}
148107
}
108+
return defaultSCs, nil
109+
}
149110

150-
return func() {
151-
classes, err = client.StorageV1().StorageClasses().List(ctx, metav1.ListOptions{})
152-
framework.ExpectNoError(err)
153-
for _, sc := range classes.Items {
154-
if _, found := changedClasses[sc.GetName()]; found {
155-
sc.Annotations[storageutil.IsDefaultStorageClassAnnotation] = "true"
156-
_, err := client.StorageV1().StorageClasses().Update(ctx, &sc, metav1.UpdateOptions{})
157-
framework.ExpectNoError(err)
158-
}
159-
}
111+
func setStorageClassDefault(ctx context.Context, client clientset.Interface, scName string, isDefault string) {
112+
sc, err := client.StorageV1().StorageClasses().Get(ctx, scName, metav1.GetOptions{})
113+
framework.ExpectNoError(err, "Error getting StorageClass")
114+
115+
if sc.Annotations == nil {
116+
sc.Annotations = make(map[string]string)
160117
}
118+
sc.Annotations[storageutil.IsDefaultStorageClassAnnotation] = isDefault
161119

120+
_, err = client.StorageV1().StorageClasses().Update(ctx, sc, metav1.UpdateOptions{})
121+
framework.ExpectNoError(err, "Error updating StorageClass")
162122
}
163123

164-
func waitForPVCStorageClass(ctx context.Context, c clientset.Interface, namespace, pvcName, scName string, timeout time.Duration) (*v1.PersistentVolumeClaim, error) {
165-
var watchedPVC *v1.PersistentVolumeClaim
166-
167-
err := wait.PollUntilContextTimeout(ctx, 1*time.Second, timeout, false, func(ctx context.Context) (bool, error) {
168-
var err error
169-
watchedPVC, err = c.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, pvcName, metav1.GetOptions{})
124+
func ensureNoDefaultStorageClasses(ctx context.Context, client clientset.Interface) {
125+
gomega.Eventually(ctx, func() (int, error) {
126+
defaultSCs, err := getDefaultStorageClasses(ctx, client)
170127
if err != nil {
171-
return true, err
128+
return 0, err
172129
}
130+
return len(defaultSCs), nil
131+
}).WithPolling(framework.Poll).WithTimeout(2*time.Minute).
132+
Should(gomega.BeZero(), "Expected no default StorageClasses")
133+
}
173134

174-
if watchedPVC.Spec.StorageClassName == nil {
175-
return false, nil // Poll until PVC has correct SC
176-
}
177-
if watchedPVC.Spec.StorageClassName != nil && *watchedPVC.Spec.StorageClassName != scName {
178-
framework.Logf("PersistentVolumeClaim %s has unexpected StorageClass %v, expected StorageClass is: %v", watchedPVC.Name, *watchedPVC.Spec.StorageClassName, scName)
179-
return false, nil // Log unexpected SC and continue poll (something might be changing default SC)
180-
}
181-
return true, nil // Correct SC name found on PVC
182-
})
135+
func createPVC(ctx context.Context, client clientset.Interface, namespace string) *v1.PersistentVolumeClaim {
136+
ginkgo.By("Creating a PVC")
183137

184-
if err != nil {
185-
return watchedPVC, fmt.Errorf("error waiting for claim %s to have StorageClass set to %s: %w", pvcName, scName, err)
138+
c := e2epv.PersistentVolumeClaimConfig{
139+
Name: "test-pvc",
140+
}
141+
142+
pvcObj := e2epv.MakePersistentVolumeClaim(c, namespace)
143+
pvc, err := client.CoreV1().PersistentVolumeClaims(pvcObj.Namespace).Create(ctx, pvcObj, metav1.CreateOptions{})
144+
framework.ExpectNoError(err, "Error creating PVC")
145+
146+
return pvc
147+
}
148+
149+
func createDefaultStorageClass(ctx context.Context, client clientset.Interface) *storagev1.StorageClass {
150+
ginkgo.By("Creating a default StorageClass")
151+
152+
c := &storagev1.StorageClass{
153+
TypeMeta: metav1.TypeMeta{
154+
Kind: "StorageClass",
155+
},
156+
ObjectMeta: metav1.ObjectMeta{
157+
Name: "test-default-sc",
158+
Annotations: map[string]string{
159+
storageutil.IsDefaultStorageClassAnnotation: "true",
160+
},
161+
},
162+
Provisioner: "fake-1",
186163
}
187164

188-
return watchedPVC, nil
165+
sc, err := client.StorageV1().StorageClasses().Create(ctx, c, metav1.CreateOptions{})
166+
framework.ExpectNoError(err, "Error creating StorageClass")
167+
168+
return sc
189169
}

0 commit comments

Comments
 (0)