From e7ee3ae3d94f5ba54eb7d5303120ae67222e4a5c Mon Sep 17 00:00:00 2001 From: Stephen Heywood Date: Tue, 5 Sep 2023 11:25:09 +1200 Subject: [PATCH] Create e2e test for StorageClass endpoints e2e test validates the following 7 endpoints - createStorageV1StorageClass - deleteStorageV1CollectionStorageClass - deleteStorageV1StorageClass - listStorageV1StorageClass - patchStorageV1StorageClass - readStorageV1StorageClass - replaceStorageV1StorageClass --- test/e2e/storage/storageclass.go | 161 +++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 test/e2e/storage/storageclass.go diff --git a/test/e2e/storage/storageclass.go b/test/e2e/storage/storageclass.go new file mode 100644 index 00000000000..d318ee17f9c --- /dev/null +++ b/test/e2e/storage/storageclass.go @@ -0,0 +1,161 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package storage + +import ( + "context" + "fmt" + "time" + + storagev1 "k8s.io/api/storage/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/retry" + "k8s.io/kubernetes/test/e2e/framework" + "k8s.io/kubernetes/test/e2e/storage/utils" + admissionapi "k8s.io/pod-security-admission/api" + + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" +) + +var _ = utils.SIGDescribe("StorageClasses", func() { + + f := framework.NewDefaultFramework("csi-storageclass") + f.NamespacePodSecurityLevel = admissionapi.LevelBaseline + + ginkgo.Describe("CSI Conformance", func() { + ginkgo.It("should run through the lifecycle of a StorageClass", func(ctx context.Context) { + + scClient := f.ClientSet.StorageV1().StorageClasses() + var initialSC, replacementSC *storagev1.StorageClass + + initialSC = &storagev1.StorageClass{ + TypeMeta: metav1.TypeMeta{ + Kind: "StorageClass", + }, + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "e2e-", + }, + Provisioner: "e2e-fake-provisioner", + } + + ginkgo.By("Creating a StorageClass") + createdStorageClass, err := scClient.Create(ctx, initialSC, metav1.CreateOptions{}) + framework.ExpectNoError(err, "failed to create the requested StorageClass") + + ginkgo.By(fmt.Sprintf("Get StorageClass %q", createdStorageClass.Name)) + retrievedStorageClass, err := scClient.Get(ctx, createdStorageClass.Name, metav1.GetOptions{}) + framework.ExpectNoError(err, "failed to get StorageClass %q", createdStorageClass.Name) + + ginkgo.By(fmt.Sprintf("Patching the StorageClass %q", retrievedStorageClass.Name)) + payload := "{\"metadata\":{\"labels\":{\"" + retrievedStorageClass.Name + "\":\"patched\"}}}" + patchedStorageClass, err := scClient.Patch(ctx, retrievedStorageClass.Name, types.StrategicMergePatchType, []byte(payload), metav1.PatchOptions{}) + framework.ExpectNoError(err, "failed to patch StorageClass %q", retrievedStorageClass.Name) + gomega.Expect(patchedStorageClass.Labels).To(gomega.HaveKeyWithValue(patchedStorageClass.Name, "patched"), "checking that patched label has been applied") + + ginkgo.By(fmt.Sprintf("Delete StorageClass %q", patchedStorageClass.Name)) + err = scClient.Delete(ctx, patchedStorageClass.Name, metav1.DeleteOptions{}) + framework.ExpectNoError(err, "failed to delete StorageClass %q", patchedStorageClass.Name) + + ginkgo.By(fmt.Sprintf("Confirm deletion of StorageClass %q", patchedStorageClass.Name)) + + scSelector := labels.Set{patchedStorageClass.Name: "patched"}.AsSelector().String() + type state struct { + StorageClasses []storagev1.StorageClass + } + + err = framework.Gomega().Eventually(ctx, framework.HandleRetry(func(ctx context.Context) (*state, error) { + scList, err := scClient.List(ctx, metav1.ListOptions{LabelSelector: scSelector}) + if err != nil { + return nil, fmt.Errorf("failed to list StorageClass: %w", err) + } + return &state{ + StorageClasses: scList.Items, + }, nil + })).WithTimeout(30 * time.Second).Should(framework.MakeMatcher(func(s *state) (func() string, error) { + if len(s.StorageClasses) == 0 { + return nil, nil + } + return func() string { + return fmt.Sprintf("expected StorageClass to be deleted, found %q", s.StorageClasses[0].Name) + }, nil + })) + framework.ExpectNoError(err, "timeout while waiting to confirm StorageClass %q deletion", patchedStorageClass.Name) + + ginkgo.By("Create a replacement StorageClass") + + replacementSC = &storagev1.StorageClass{ + TypeMeta: metav1.TypeMeta{ + Kind: "StorageClass", + }, + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "e2e-v2-", + }, + Provisioner: "e2e-fake-provisioner", + } + + replacementStorageClass, err := scClient.Create(ctx, replacementSC, metav1.CreateOptions{}) + framework.ExpectNoError(err, "failed to create replacement StorageClass") + + ginkgo.By(fmt.Sprintf("Updating StorageClass %q", replacementStorageClass.Name)) + var updatedStorageClass *storagev1.StorageClass + + err = retry.RetryOnConflict(retry.DefaultRetry, func() error { + sc, err := scClient.Get(ctx, replacementStorageClass.Name, metav1.GetOptions{}) + framework.ExpectNoError(err, "unable to get Storage %q", replacementStorageClass.Name) + sc.Labels = map[string]string{replacementStorageClass.Name: "updated"} + updatedStorageClass, err = scClient.Update(ctx, sc, metav1.UpdateOptions{}) + + return err + }) + framework.ExpectNoError(err, "failed to update StorageClass %q", replacementStorageClass.Name) + gomega.Expect(updatedStorageClass.Labels).To(gomega.HaveKeyWithValue(replacementStorageClass.Name, "updated"), "checking that updated label has been applied") + + scSelector = labels.Set{replacementStorageClass.Name: "updated"}.AsSelector().String() + ginkgo.By(fmt.Sprintf("Listing all StorageClass with the labelSelector: %q", scSelector)) + scList, err := scClient.List(ctx, metav1.ListOptions{LabelSelector: scSelector}) + framework.ExpectNoError(err, "failed to list StorageClasses with the labelSelector: %q", scSelector) + gomega.Expect(scList.Items).To(gomega.HaveLen(1)) + + ginkgo.By(fmt.Sprintf("Deleting StorageClass %q via DeleteCollection", updatedStorageClass.Name)) + err = scClient.DeleteCollection(ctx, metav1.DeleteOptions{}, metav1.ListOptions{LabelSelector: scSelector}) + framework.ExpectNoError(err, "failed to delete StorageClass %q", updatedStorageClass.Name) + + ginkgo.By(fmt.Sprintf("Confirm deletion of StorageClass %q", updatedStorageClass.Name)) + + err = framework.Gomega().Eventually(ctx, framework.HandleRetry(func(ctx context.Context) (*state, error) { + scList, err := scClient.List(ctx, metav1.ListOptions{LabelSelector: scSelector}) + if err != nil { + return nil, fmt.Errorf("failed to list StorageClass: %w", err) + } + return &state{ + StorageClasses: scList.Items, + }, nil + })).WithTimeout(30 * time.Second).Should(framework.MakeMatcher(func(s *state) (func() string, error) { + if len(s.StorageClasses) == 0 { + return nil, nil + } + return func() string { + return fmt.Sprintf("expected StorageClass to be deleted, found %q", s.StorageClasses[0].Name) + }, nil + })) + framework.ExpectNoError(err, "timeout while waiting to confirm StorageClass %q deletion", updatedStorageClass.Name) + }) + }) +})