From c19f47226e52882b0174d49e67c9c62a65b7aefb Mon Sep 17 00:00:00 2001 From: ShravaniVangur Date: Thu, 27 Mar 2025 22:35:29 +0530 Subject: [PATCH] e2e: test for PVC with volumeBindingMode on helm installation Test PVC binding with WaitForFirstConsumer in Helm installation. Signed-off-by: ShravaniVangur --- e2e/cephfs.go | 20 ++++++++++++++++++++ e2e/cephfs_helper.go | 45 ++++++++++++++++++++++++++++++++++++++++---- e2e/pvc.go | 9 +++++++++ e2e/utils.go | 29 ++++++++++++++++++++++++++++ 4 files changed, 99 insertions(+), 4 deletions(-) diff --git a/e2e/cephfs.go b/e2e/cephfs.go index da6a293c6..5a64fa8e7 100644 --- a/e2e/cephfs.go +++ b/e2e/cephfs.go @@ -360,6 +360,26 @@ var _ = Describe(cephfsType, func() { }) } + By("verify PVC and App Binding with volumeBindingMode:WaitForFirstConsumer", func() { + err := createCephfsStorageClassWaitForFirstConsumer(f.ClientSet, f, true, nil) + if err != nil { + framework.Failf("failed to create CephFS storageclass: %v", err) + } + + err = validatePVCAndAppWaitForFirstConsumer(pvcPath, appPath, f) + if err != nil { + framework.Failf("failed to validate CephFS pvc and application binding: %v", err) + } + + validateSubvolumeCount(f, 0, fileSystemName, subvolumegroup) + validateOmapCount(f, 0, cephfsType, metadataPool, volumesType) + + err = deleteResource(cephFSExamplePath + "storageclass.yaml") + if err != nil { + framework.Failf("failed to delete CephFS storageclass: %v", err) + } + }) + By("verify mountOptions support", func() { err := createCephfsStorageClass(f.ClientSet, f, true, nil) if err != nil { diff --git a/e2e/cephfs_helper.go b/e2e/cephfs_helper.go index fc44ae4c7..fb7863cb8 100644 --- a/e2e/cephfs_helper.go +++ b/e2e/cephfs_helper.go @@ -26,6 +26,7 @@ import ( snapapi "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" v1 "k8s.io/api/core/v1" + scv1 "k8s.io/api/storage/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes" @@ -67,6 +68,40 @@ func createCephfsStorageClass( return err } + err = updateStorageClassParameters(&sc, params, enablePool, f) + if err != nil { + return err + } + + return createStorageClass(c, &sc) +} + +func createCephfsStorageClassWaitForFirstConsumer(c kubernetes.Interface, f *framework.Framework, + enablePool bool, + params map[string]string) error { + scPath := fmt.Sprintf("%s/%s", cephFSExamplePath, "storageclass.yaml") + sc, err := getStorageClass(scPath) + if err != nil { + return err + } + + err = updateStorageClassParameters(&sc, params, enablePool, f) + if err != nil { + return err + } + + // Set the volume binding mode to WaitForFirstConsumer + value := scv1.VolumeBindingWaitForFirstConsumer + sc.VolumeBindingMode = &value + + return createStorageClass(c, &sc) +} + +func updateStorageClassParameters(sc *scv1.StorageClass, params map[string]string, enablePool bool, f *framework.Framework) error { + if sc == nil { + return fmt.Errorf("StorageClass is nil") + } + sc.Parameters["fsName"] = fileSystemName sc.Parameters["csi.storage.k8s.io/provisioner-secret-namespace"] = cephCSINamespace sc.Parameters["csi.storage.k8s.io/provisioner-secret-name"] = cephFSProvisionerSecretName @@ -93,18 +128,20 @@ func createCephfsStorageClass( // fetch and set fsID from the cluster if not set in params if _, found := params["clusterID"]; !found { - var fsID string - fsID, err = getClusterID(f) + fsID, err := getClusterID(f) if err != nil { return fmt.Errorf("failed to get clusterID: %w", err) } sc.Parameters["clusterID"] = fsID } - timeout := time.Duration(deployTimeout) * time.Minute + return nil +} +func createStorageClass(c kubernetes.Interface, sc *scv1.StorageClass) error { + timeout := time.Duration(deployTimeout) * time.Minute return wait.PollUntilContextTimeout(context.TODO(), poll, timeout, true, func(ctx context.Context) (bool, error) { - _, err = c.StorageV1().StorageClasses().Create(ctx, &sc, metav1.CreateOptions{}) + _, err := c.StorageV1().StorageClasses().Create(ctx, sc, metav1.CreateOptions{}) if err != nil { framework.Logf("error creating StorageClass %q: %v", sc.Name, err) if isRetryableAPIError(err) { diff --git a/e2e/pvc.go b/e2e/pvc.go index 932d66b33..46bf198b3 100644 --- a/e2e/pvc.go +++ b/e2e/pvc.go @@ -130,6 +130,15 @@ func createPVCAndPV(c kubernetes.Interface, pvc *v1.PersistentVolumeClaim, pv *v return err } +func createPVC(c kubernetes.Interface, pvc *v1.PersistentVolumeClaim) error { + _, err := c.CoreV1().PersistentVolumeClaims(pvc.Namespace).Create(context.TODO(), pvc, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("failed to create pvc: %w", err) + } + + return nil +} + func deletePVCAndPV(c kubernetes.Interface, pvc *v1.PersistentVolumeClaim, pv *v1.PersistentVolume, t int) error { ctx := context.TODO() err := c.CoreV1().PersistentVolumeClaims(pvc.Namespace).Delete(ctx, pvc.Name, metav1.DeleteOptions{}) diff --git a/e2e/utils.go b/e2e/utils.go index 3782f6bae..b580e9fd9 100644 --- a/e2e/utils.go +++ b/e2e/utils.go @@ -557,6 +557,35 @@ func validatePVCAndAppBinding(pvcPath, appPath string, f *framework.Framework) e return err } +// validatePVCAndAppWaitForFirstConsumer creates a Pending PVC, starts an app, and verifies it to become Bound. +func validatePVCAndAppWaitForFirstConsumer(pvcPath, appPath string, f *framework.Framework) error { + pvc, err := loadPVC(pvcPath) + if err != nil { + return err + } + pvc.Namespace = f.UniqueName + + app, err := loadApp(appPath) + if err != nil { + return err + } + app.Namespace = f.UniqueName + + err = createPVC(f.ClientSet, pvc) + if err != nil { + return err + } + + err = createApp(f.ClientSet, app, deployTimeout) + if err != nil { + return err + } + + err = deletePVCAndApp("", f, pvc, app) + + return err +} + func getMountType(selector, mountPath string, f *framework.Framework) (string, error) { opt := metav1.ListOptions{ LabelSelector: selector,