diff --git a/controllers/lvmcluster_controller.go b/controllers/lvmcluster_controller.go index c2c8fcee8..b454a48ed 100644 --- a/controllers/lvmcluster_controller.go +++ b/controllers/lvmcluster_controller.go @@ -155,10 +155,10 @@ func (r *LVMClusterReconciler) reconcile(ctx context.Context, instance *lvmv1alp } if lvsExist { waitForLVRemoval := time.Second * 10 - err := fmt.Errorf("found PVCs provisioned by topolvm, waiting %s for their deletion: %w", waitForLVRemoval, err) + err := fmt.Errorf("found PVCs provisioned by topolvm, waiting %s for their deletion", waitForLVRemoval) r.WarningEvent(ctx, instance, EventReasonErrorDeletionPending, err) // check every 10 seconds if there are still PVCs present - return ctrl.Result{RequeueAfter: waitForLVRemoval}, err + return ctrl.Result{RequeueAfter: waitForLVRemoval}, nil } logger.Info("processing LVMCluster deletion") diff --git a/test/e2e/aws_disk.go b/test/e2e/aws_disk_test.go similarity index 98% rename from test/e2e/aws_disk.go rename to test/e2e/aws_disk_test.go index 9249196b0..ffaa0852f 100644 --- a/test/e2e/aws_disk.go +++ b/test/e2e/aws_disk_test.go @@ -22,12 +22,15 @@ import ( "strings" "time" + . "github.com/onsi/ginkgo/v2" + + "github.com/go-logr/logr" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/ec2" - "github.com/go-logr/logr" - "github.com/onsi/ginkgo/v2" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" @@ -211,7 +214,7 @@ func getEC2Client(ctx context.Context, region string) (*ec2.EC2, error) { Region: aws.String(region), Credentials: credentials.NewStaticCredentials(string(id), string(key), ""), Logger: aws.LoggerFunc(func(args ...interface{}) { - ginkgo.GinkgoLogr.Info(fmt.Sprint(args), "source", "aws") + GinkgoLogr.Info(fmt.Sprint(args), "source", "aws") }), }) if err != nil { diff --git a/test/e2e/config.go b/test/e2e/config_test.go similarity index 99% rename from test/e2e/config.go rename to test/e2e/config_test.go index d515b6c7a..9ebe1f344 100644 --- a/test/e2e/config.go +++ b/test/e2e/config_test.go @@ -24,9 +24,10 @@ import ( snapapi "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1" configv1 "github.com/openshift/api/config/v1" secv1 "github.com/openshift/api/security/v1" - lvmv1 "github.com/openshift/lvm-operator/api/v1alpha1" + operatorv1 "github.com/operator-framework/api/pkg/operators/v1" operatorv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" utilruntime "k8s.io/apimachinery/pkg/util/runtime" @@ -34,6 +35,8 @@ import ( "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" crclient "sigs.k8s.io/controller-runtime/pkg/client" + + lvmv1 "github.com/openshift/lvm-operator/api/v1alpha1" ) const ( diff --git a/test/e2e/disk_setup.go b/test/e2e/disk_setup_test.go similarity index 85% rename from test/e2e/disk_setup.go rename to test/e2e/disk_setup_test.go index e04568c09..23cf1894f 100644 --- a/test/e2e/disk_setup.go +++ b/test/e2e/disk_setup_test.go @@ -27,13 +27,11 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) -func diskSetup(ctx context.Context) error { +func diskSetup(ctx context.Context) { // get nodes By(fmt.Sprintf("getting all worker nodes by label %s", labelNodeRoleWorker)) nodeList := &corev1.NodeList{} - if err := crClient.List(ctx, nodeList, client.HasLabels{labelNodeRoleWorker}); err != nil { - return fmt.Errorf("could not list worker nodes nodes for Disk setup: %w", err) - } + Expect(crClient.List(ctx, nodeList, client.HasLabels{labelNodeRoleWorker})).To(Succeed()) By("getting AWS region info from the first Node spec") nodeInfo, err := getAWSNodeInfo(nodeList.Items[0]) @@ -51,8 +49,6 @@ func diskSetup(ctx context.Context) error { // create and attach volumes By("creating and attaching Disks") Expect(NewAWSDiskManager(ec2, GinkgoLogr).CreateAndAttachAWSVolumes(ctx, nodeEnv)).To(Succeed()) - - return nil } func getNodeEnvironmentFromNodeList(nodeList *corev1.NodeList) ([]NodeDisks, error) { @@ -76,13 +72,11 @@ func getNodeEnvironmentFromNodeList(nodeList *corev1.NodeList) ([]NodeDisks, err return nodeEnv, nil } -func diskRemoval(ctx context.Context) error { +func diskTeardown(ctx context.Context) { // get nodes By(fmt.Sprintf("getting all worker nodes by label %s", labelNodeRoleWorker)) nodeList := &corev1.NodeList{} - if err := crClient.List(ctx, nodeList, client.HasLabels{labelNodeRoleWorker}); err != nil { - return fmt.Errorf("could not list worker nodes nodes for Disk setup: %w", err) - } + Expect(crClient.List(ctx, nodeList, client.HasLabels{labelNodeRoleWorker})).To(Succeed()) By("getting AWS region info from the first Node spec") nodeInfo, err := getAWSNodeInfo(nodeList.Items[0]) @@ -96,6 +90,4 @@ func diskRemoval(ctx context.Context) error { // cleaning Disk By("cleaning up Disks") Expect(NewAWSDiskManager(ec2, GinkgoLogr).cleanupAWSDisks(ctx)).To(Succeed()) - - return err } diff --git a/test/e2e/ephemeral_check.go b/test/e2e/ephemeral_check.go deleted file mode 100644 index 5c3ab16b8..000000000 --- a/test/e2e/ephemeral_check.go +++ /dev/null @@ -1,352 +0,0 @@ -/* -Copyright © 2023 Red Hat, Inc. - -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 e2e - -import ( - "context" - _ "embed" - "fmt" - - snapapi "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - k8sv1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -var ( - //go:embed testdata/ephemeral_tests/pod-ephemeral-volume-device.yaml - podEphemeralBlockYAMLTemplate string - - //go:embed testdata/ephemeral_tests/pod-ephemeral-volume-mount.yaml - podEphemeralFSYAMLTemplate string - - //go:embed testdata/ephemeral_tests/ephemeral-volume-snapshot.yaml - ephemeralVolumeSnapshotYAMLTemplate string - - //go:embed testdata/ephemeral_tests/ephemeral-clone.yaml - ephemeralPvcCloneYAMLTemplate string - - //go:embed testdata/ephemeral_tests/ephemeral-snapshot-restore.yaml - ephemeralPvcSnapshotRestoreYAMLTemplate string - - //go:embed testdata/ephemeral_tests/pod-volume-mount-template.yaml - podFSYAMLTemplate string - - //go:embed testdata/ephemeral_tests/pod-volume-device-template.yaml - podBlockYAMLTemplate string -) - -func ephemeralTest() { - Describe("Ephemeral Volume Tests", func() { - var ( - pvc = &k8sv1.PersistentVolumeClaim{} - ephemeralPod *k8sv1.Pod - snapshot *snapapi.VolumeSnapshot - clonePvc *k8sv1.PersistentVolumeClaim - clonePod *k8sv1.Pod - restorePvc *k8sv1.PersistentVolumeClaim - restorePod *k8sv1.Pod - err error - skipSnapshotOps = false - ) - - Context("Create ephemeral pod and volume", func() { - Context("Tests ephemeral volume operations for VolumeMode=Filesystem", Ordered, func() { - It("Creation of ephemeral Pod binding a PVC from LVMS", func(ctx SpecContext) { - By("Creating an ephemeral pod") - podVolumeMountYaml := fmt.Sprintf(podEphemeralFSYAMLTemplate, "ephemeral-filepod", testNamespace, storageClassName) - ephemeralPod, err = getPod(podVolumeMountYaml) - Expect(err).To(BeNil()) - Expect(crClient.Create(ctx, ephemeralPod)).To(Succeed()) - - By("PVC should be bound") - Eventually(func(ctx context.Context) error { - if err := crClient.Get(ctx, types.NamespacedName{Name: "ephemeral-filepod-generic-ephemeral-volume", Namespace: testNamespace}, pvc); err != nil { - return err - } - if pvc.Status.Phase != k8sv1.ClaimBound { - return fmt.Errorf("pvc is not bound yet: %s", pvc.Status.Phase) - } - return nil - }, timeout, interval).WithContext(ctx).Should(Succeed()) - - By("Pod should be running") - Eventually(func(ctx context.Context) bool { - err = crClient.Get(ctx, types.NamespacedName{Name: ephemeralPod.Name, Namespace: testNamespace}, ephemeralPod) - return err == nil && ephemeralPod.Status.Phase == k8sv1.PodRunning - }, timeout, interval).WithContext(ctx).Should(BeTrue()) - - By("Writing data to the Pod") - Expect(contentTester.WriteDataInPod(ctx, ephemeralPod, "TESTDATA", ContentModeFile)).To(Succeed()) - }) - - It("Testing Snapshot Operations", func(ctx SpecContext) { - By("Creating a Snapshot of the pvc") - snapshotYaml := fmt.Sprintf(ephemeralVolumeSnapshotYAMLTemplate, "ephemeralfilepvc-snapshot", testNamespace, snapshotClass, "ephemeral-filepod-generic-ephemeral-volume") - snapshot, err = getVolumeSnapshot(snapshotYaml) - Expect(err).To(BeNil()) - err = crClient.Create(ctx, snapshot) - if meta.IsNoMatchError(err) { - skipSnapshotOps = true - Skip("Skipping Testing of Snapshot Operations due to lack of volume snapshot support") - } - Expect(err).To(BeNil()) - - By("Verifying that the Snapshot is ready") - Eventually(func() bool { - err := crClient.Get(ctx, types.NamespacedName{Name: snapshot.Name, Namespace: snapshot.Namespace}, snapshot) - return err == nil && snapshot.Status != nil && *snapshot.Status.ReadyToUse - }, timeout, interval).Should(BeTrue()) - - By("Creating a clone of the pvc") - pvcCloneYaml := fmt.Sprintf(ephemeralPvcCloneYAMLTemplate, "ephemeralfilepvc-clone", testNamespace, "Filesystem", storageClassName, "ephemeral-filepod-generic-ephemeral-volume") - clonePvc, err = getPVC(pvcCloneYaml) - Expect(err).To(BeNil()) - Expect(crClient.Create(ctx, clonePvc)).To(Succeed()) - - By("Creating a pod consuming the clone of the pvc") - podVolumeMountYaml := fmt.Sprintf(podFSYAMLTemplate, "clone-ephemeralfilepod", testNamespace, "ephemeralfilepvc-clone") - clonePod, err = getPod(podVolumeMountYaml) - Expect(err).To(BeNil()) - Expect(crClient.Create(ctx, clonePod)).To(Succeed()) - - By("Having a bound claim in the pvc") - Eventually(func(ctx context.Context) error { - if err := crClient.Get(ctx, client.ObjectKeyFromObject(clonePvc), clonePvc); err != nil { - return err - } - if clonePvc.Status.Phase != k8sv1.ClaimBound { - return fmt.Errorf("pvc is not bound yet: %s", clonePvc.Status.Phase) - } - return nil - }, timeout, interval).WithContext(ctx).Should(Succeed()) - - By("Reading data from the cloned data in the Pod") - clonedData := "" - Eventually(func(ctx context.Context) error { - clonedData, err = contentTester.GetDataInPod(ctx, clonePod, ContentModeFile) - return err - }).WithContext(ctx).Should(Succeed()) - Expect(clonedData).To(Equal("TESTDATA")) - - By("Restore Snapshot for pvc") - pvcRestoreYaml := fmt.Sprintf(ephemeralPvcSnapshotRestoreYAMLTemplate, "ephemeralfilepvc-restore", testNamespace, "Filesystem", storageClassName, "ephemeralfilepvc-snapshot") - restorePvc, err = getPVC(pvcRestoreYaml) - Expect(err).To(BeNil()) - Expect(crClient.Create(ctx, restorePvc)).To(Succeed()) - - By("Creating a pod consuming the restored snapshot data") - podVolumeMountYaml = fmt.Sprintf(podFSYAMLTemplate, "restore-ephemeralfilepod", testNamespace, "ephemeralfilepvc-restore") - restorePod, err = getPod(podVolumeMountYaml) - Expect(err).To(BeNil()) - Expect(crClient.Create(ctx, restorePod)).To(Succeed()) - - By("Having the restored data pvc be bound") - Eventually(func(ctx context.Context) error { - if err := crClient.Get(ctx, client.ObjectKeyFromObject(restorePvc), restorePvc); err != nil { - return err - } - if restorePvc.Status.Phase != k8sv1.ClaimBound { - return fmt.Errorf("pvc is not bound yet: %s", restorePvc.Status.Phase) - } - return nil - }, timeout, interval).WithContext(ctx).Should(Succeed()) - - By("Reading data from the restored data in the Pod") - restoredData := "" - Eventually(func(ctx context.Context) error { - restoredData, err = contentTester.GetDataInPod(ctx, restorePod, ContentModeFile) - return err - }, timeout, interval).WithContext(ctx).Should(Succeed()) - Expect(restoredData).To(Equal("TESTDATA")) - }) - - It("Cleaning up ephemeral volume operations for VolumeMode=Filesystem", func(ctx SpecContext) { - if !skipSnapshotOps { - By(fmt.Sprintf("Deleting %s", clonePod.Name)) - Expect(crClient.Delete(ctx, clonePod)).To(Succeed()) - - By(fmt.Sprintf("Deleting Clone PVC %s", clonePvc.Name)) - Expect(crClient.Delete(ctx, clonePvc)).To(Succeed()) - - By(fmt.Sprintf("Deleting Pod %s", restorePod.Name)) - Expect(crClient.Delete(ctx, restorePod)).To(Succeed()) - - By(fmt.Sprintf("Deleting Snapshot PVC %s", restorePvc.Name)) - Expect(crClient.Delete(ctx, restorePvc)).To(Succeed()) - - By(fmt.Sprintf("Deleting VolumeSnapshot %s", snapshot.Name)) - Expect(crClient.Delete(ctx, snapshot)).To(Succeed()) - } - - By("Deleting Pod") - Expect(crClient.Delete(ctx, ephemeralPod)).To(Succeed()) - - By("Confirming that ephemeral volume is automatically deleted") - Eventually(func(ctx context.Context) bool { - err := crClient.Get(ctx, types.NamespacedName{Name: "ephemeral-filepod-generic-ephemeral-volume", Namespace: testNamespace}, pvc) - return err != nil && k8serrors.IsNotFound(err) - }, timeout, interval).WithContext(ctx).Should(BeTrue()) - }) - }) - - Context("Tests PVC operations for VolumeMode=Block", Ordered, func() { - It("Creation of ephemeral Pod binding a PVC from LVMS", func(ctx SpecContext) { - By("Creating an ephemeral pod") - podVolumeBlockYaml := fmt.Sprintf(podEphemeralBlockYAMLTemplate, "ephemeral-blockpod", testNamespace, storageClassName) - ephemeralPod, err = getPod(podVolumeBlockYaml) - Expect(err).To(BeNil()) - Expect(crClient.Create(ctx, ephemeralPod)).To(Succeed()) - - By("PVC should be bound") - Eventually(func(ctx context.Context) error { - if err := crClient.Get(ctx, types.NamespacedName{Name: "ephemeral-blockpod-generic-ephemeral-volume", Namespace: testNamespace}, pvc); err != nil { - return err - } - if pvc.Status.Phase != k8sv1.ClaimBound { - return fmt.Errorf("pvc is not bound yet: %s", pvc.Status.Phase) - } - return nil - }, timeout, interval).WithContext(ctx).Should(Succeed()) - - By("Pod should be running") - Eventually(func(ctx context.Context) bool { - err = crClient.Get(ctx, types.NamespacedName{Name: ephemeralPod.Name, Namespace: testNamespace}, ephemeralPod) - return err == nil && ephemeralPod.Status.Phase == k8sv1.PodRunning - }, timeout, interval).WithContext(ctx).Should(BeTrue()) - - By("Writing data to the Pod") - Expect(contentTester.WriteDataInPod(ctx, ephemeralPod, "TESTDATA", ContentModeBlock)).To(Succeed()) - }) - - It("Testing Snapshot Operations", func(ctx SpecContext) { - By("Creating a Snapshot of the pvc") - snapshotYaml := fmt.Sprintf(ephemeralVolumeSnapshotYAMLTemplate, "ephemeralblockpvc-snapshot", testNamespace, snapshotClass, "ephemeral-blockpod-generic-ephemeral-volume") - snapshot, err = getVolumeSnapshot(snapshotYaml) - Expect(err).To(BeNil()) - err = crClient.Create(ctx, snapshot) - if meta.IsNoMatchError(err) { - skipSnapshotOps = true - Skip("Skipping Testing of Snapshot Operations due to lack of volume snapshot support") - } - Expect(err).To(BeNil()) - - By("Verifying that the Snapshot is ready") - Eventually(func(ctx context.Context) bool { - err := crClient.Get(ctx, types.NamespacedName{Name: snapshot.Name, Namespace: snapshot.Namespace}, snapshot) - return err == nil && snapshot.Status != nil && *snapshot.Status.ReadyToUse - }, timeout, interval).WithContext(ctx).Should(BeTrue()) - - By("Creating a clone of the pvc") - pvcCloneYaml := fmt.Sprintf(ephemeralPvcCloneYAMLTemplate, "ephemeralblockpvc-clone", testNamespace, "Block", storageClassName, "ephemeral-blockpod-generic-ephemeral-volume") - clonePvc, err = getPVC(pvcCloneYaml) - Expect(err).To(BeNil()) - Expect(crClient.Create(ctx, clonePvc)).To(BeNil()) - - By("Creating a pod consuming the clone of the pvc") - podVolumeBlockYaml := fmt.Sprintf(podBlockYAMLTemplate, "clone-ephemeralblockpod", testNamespace, "ephemeralblockpvc-clone") - clonePod, err = getPod(podVolumeBlockYaml) - Expect(err).To(BeNil()) - Expect(crClient.Create(ctx, clonePod)).To(BeNil()) - - By("Having a bound claim in the pvc") - Eventually(func(ctx context.Context) error { - if err := crClient.Get(ctx, client.ObjectKeyFromObject(clonePvc), clonePvc); err != nil { - return err - } - if clonePvc.Status.Phase != k8sv1.ClaimBound { - return fmt.Errorf("pvc is not bound yet: %s", clonePvc.Status.Phase) - } - return nil - }, timeout, interval).WithContext(ctx).Should(Succeed()) - - By("Reading data from the cloned data in the Pod") - clonedData := "" - Eventually(func(ctx context.Context) error { - clonedData, err = contentTester.GetDataInPod(ctx, clonePod, ContentModeBlock) - return err - }).WithContext(ctx).Should(Succeed()) - Expect(clonedData).To(Equal("TESTDATA")) - - By("Restore Snapshot for pvc") - pvcRestoreYaml := fmt.Sprintf(ephemeralPvcSnapshotRestoreYAMLTemplate, "ephemeralblockpvc-restore", testNamespace, "Block", storageClassName, "ephemeralblockpvc-snapshot") - restorePvc, err = getPVC(pvcRestoreYaml) - Expect(err).To(BeNil()) - Expect(crClient.Create(ctx, restorePvc)).To(Succeed()) - - By("Creating a pod consuming the restored snapshot data") - podVolumeBlockYaml = fmt.Sprintf(podBlockYAMLTemplate, "restore-ephemeralblockpod", testNamespace, "ephemeralblockpvc-restore") - restorePod, err = getPod(podVolumeBlockYaml) - Expect(err).To(BeNil()) - Expect(crClient.Create(ctx, restorePod)).To(Succeed()) - - By("Having the restored data pvc be bound") - Eventually(func(ctx context.Context) error { - if err := crClient.Get(ctx, client.ObjectKeyFromObject(restorePvc), restorePvc); err != nil { - return err - } - if restorePvc.Status.Phase != k8sv1.ClaimBound { - return fmt.Errorf("pvc is not bound yet: %s", restorePvc.Status.Phase) - } - return nil - }, timeout, interval).WithContext(ctx).Should(Succeed()) - - By("Reading data from the restored data in the Pod") - restoredData := "" - Eventually(func(ctx context.Context) error { - restoredData, err = contentTester.GetDataInPod(ctx, restorePod, ContentModeBlock) - return err - }, timeout, interval).WithContext(ctx).Should(Succeed()) - Expect(restoredData).To(Equal("TESTDATA")) - }) - - It("Cleaning up ephemeral volume operations for VolumeMode=Filesystem", func(ctx SpecContext) { - if !skipSnapshotOps { - By(fmt.Sprintf("Deleting %s", clonePod.Name)) - Expect(crClient.Delete(ctx, clonePod)).To(Succeed()) - - By(fmt.Sprintf("Deleting Clone PVC %s", clonePvc.Name)) - Expect(crClient.Delete(ctx, clonePvc)).To(Succeed()) - - By(fmt.Sprintf("Deleting Pod %s", restorePod.Name)) - Expect(crClient.Delete(ctx, restorePod)).To(Succeed()) - - By(fmt.Sprintf("Deleting Snapshot PVC %s", restorePvc.Name)) - Expect(crClient.Delete(ctx, restorePvc)).To(Succeed()) - - By(fmt.Sprintf("Deleting VolumeSnapshot %s", snapshot.Name)) - Expect(crClient.Delete(ctx, snapshot)).To(Succeed()) - } - - By("Deleting Pod") - Expect(crClient.Delete(ctx, ephemeralPod)).To(Succeed()) - - By("Confirming that ephemeral volume is automatically deleted") - Eventually(func(ctx context.Context) bool { - err := crClient.Get(ctx, types.NamespacedName{Name: "ephemeral-blockpod-generic-ephemeral-volume", Namespace: testNamespace}, pvc) - return err != nil && k8serrors.IsNotFound(err) - }, timeout, interval).WithContext(ctx).Should(BeTrue()) - }) - }) - - }) - }) -} diff --git a/test/e2e/helper.go b/test/e2e/helper.go deleted file mode 100644 index bf2c1ce6a..000000000 --- a/test/e2e/helper.go +++ /dev/null @@ -1,103 +0,0 @@ -/* -Copyright © 2023 Red Hat, Inc. - -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 e2e - -import ( - "context" - "fmt" - "time" - - "github.com/onsi/gomega" - k8sv1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - utilwait "k8s.io/apimachinery/pkg/util/wait" -) - -// createNamespace creates a namespace in the cluster, ignoring if it already exists. -func createNamespace(ctx context.Context, namespace string) error { - label := make(map[string]string) - // label required for monitoring this namespace - label["openshift.io/cluster-monitoring"] = "true" - label["pod-security.kubernetes.io/enforce"] = "privileged" - label["security.openshift.io/scc.podSecurityLabelSync"] = "false" - - annotations := make(map[string]string) - // annotation required for workload partitioning - annotations["workload.openshift.io/allowed"] = "management" - - ns := &k8sv1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: namespace, - Annotations: annotations, - Labels: label, - }, - } - err := crClient.Create(ctx, ns) - if err != nil && !errors.IsAlreadyExists(err) { - return err - } - return nil -} - -// deleteNamespaceAndWait deletes a namespace and waits on it to terminate. -func deleteNamespaceAndWait(ctx context.Context, namespace string) error { - label := make(map[string]string) - // label required for monitoring this namespace - label["openshift.io/cluster-monitoring"] = "true" - label["pod-security.kubernetes.io/enforce"] = "baseline" - label["security.openshift.io/scc.podSecurityLabelSync"] = "false" - - annotations := make(map[string]string) - // annotation required for workload partitioning - annotations["workload.openshift.io/allowed"] = "management" - - ns := &k8sv1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: namespace, - Annotations: annotations, - Labels: label, - }, - } - err := crClient.Delete(ctx, ns) - gomega.Expect(err).To(gomega.BeNil()) - - lastReason := "" - timeout := 600 * time.Second - interval := 10 * time.Second - - // wait for namespace to terminate - err = utilwait.PollUntilContextTimeout(ctx, interval, timeout, true, func(ctx context.Context) (done bool, err error) { - err = crClient.Get(ctx, types.NamespacedName{Name: namespace, Namespace: namespace}, ns) - if err != nil && !errors.IsNotFound(err) { - lastReason = fmt.Sprintf("Error talking to k8s apiserver: %v", err) - return false, nil - } - if err == nil { - lastReason = "Waiting on namespace to be deleted" - return false, nil - } - - return true, nil - }) - - if err != nil { - return fmt.Errorf("%v: %s", err, lastReason) - } - return nil -} diff --git a/test/e2e/helper_test.go b/test/e2e/helper_test.go new file mode 100644 index 000000000..d41ecc630 --- /dev/null +++ b/test/e2e/helper_test.go @@ -0,0 +1,200 @@ +/* +Copyright © 2023 Red Hat, Inc. + +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 e2e + +import ( + "context" + "fmt" + "sync" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/meta" + + k8sv1 "k8s.io/api/core/v1" + storagev1 "k8s.io/api/storage/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" + + "github.com/openshift/lvm-operator/api/v1alpha1" +) + +const ( + DefaultTestLVMCluster string = "rh-lvmcluster" +) + +// beforeTestSuiteSetup is the function called to initialize the test environment. +func beforeTestSuiteSetup(ctx context.Context) { + if diskInstall { + By("Creating Disk for e2e tests") + diskSetup(ctx) + } + + if lvmOperatorInstall { + By("BeforeTestSuite: deploying LVM Operator") + deployLVMWithOLM(ctx, lvmCatalogSourceImage, lvmSubscriptionChannel) + } +} + +func VerifyLVMSSetup(ctx context.Context, cluster *v1alpha1.LVMCluster) { + GinkgoHelper() + validateLVMCluster(ctx, cluster) + validateCSIDriver(ctx) + validateTopolvmController(ctx) + validateVGManager(ctx) + validateLVMVolumeGroup(ctx) + validateTopolvmNode(ctx) + validateStorageClass(ctx) + validateVolumeSnapshotClass(ctx) +} + +func GetStorageClass(ctx context.Context, name types.NamespacedName) *storagev1.StorageClass { + GinkgoHelper() + By(fmt.Sprintf("retrieving the Storage Class %q", name)) + // Make sure the storage class was configured properly + sc := storagev1.StorageClass{} + Eventually(func(ctx SpecContext) error { + return crClient.Get(ctx, name, &sc) + }, timeout, interval).WithContext(ctx).Should(Succeed()) + return &sc +} + +func CreateResource(ctx context.Context, obj client.Object) { + GinkgoHelper() + gvk, _ := apiutil.GVKForObject(obj, crClient.Scheme()) + var key string + if obj.GetNamespace() == "" { + key = obj.GetName() + } else { + key = client.ObjectKeyFromObject(obj).String() + } + By(fmt.Sprintf("Creating %s %q", gvk.Kind, key)) + Expect(crClient.Create(ctx, obj)).To(Succeed()) +} + +func DeleteResource(ctx context.Context, obj client.Object) { + GinkgoHelper() + gvk, _ := apiutil.GVKForObject(obj, crClient.Scheme()) + var key string + if obj.GetNamespace() == "" { + key = obj.GetName() + } else { + key = client.ObjectKeyFromObject(obj).String() + } + By(fmt.Sprintf("Deleting %s %q", gvk.Kind, key)) + err := crClient.Delete(ctx, obj) + if k8serrors.IsNotFound(err) { + return + } + if meta.IsNoMatchError(err) { + GinkgoLogr.Info("deletion was requested for resource that is not supported on the cluster", + "gvk", gvk, "obj", client.ObjectKeyFromObject(obj)) + return + } + Expect(err).ToNot(HaveOccurred()) + Eventually(func(ctx context.Context) error { + return crClient.Get(ctx, client.ObjectKeyFromObject(obj), obj) + }, timeout, interval).WithContext(ctx).Should(Satisfy(k8serrors.IsNotFound)) +} + +func lvmNamespaceCleanup(ctx context.Context) { + DeleteResource(ctx, &k8sv1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: testNamespace}}) +} + +// afterTestSuiteCleanup is the function called to tear down the test environment. +func afterTestSuiteCleanup(ctx context.Context) { + if lvmOperatorUninstall { + By("AfterTestSuite: uninstalling LVM Operator") + uninstallLVM(ctx, lvmCatalogSourceImage, lvmSubscriptionChannel) + } + + if diskInstall { + By("Cleaning up disk") + diskTeardown(ctx) + } +} + +func GetDefaultTestLVMClusterTemplate() *v1alpha1.LVMCluster { + lvmClusterRes := &v1alpha1.LVMCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: DefaultTestLVMCluster, + Namespace: installNamespace, + }, + Spec: v1alpha1.LVMClusterSpec{ + Storage: v1alpha1.Storage{ + DeviceClasses: []v1alpha1.DeviceClass{ + { + Name: "vg1", + Default: true, + ThinPoolConfig: &v1alpha1.ThinPoolConfig{ + Name: "mytp1", + SizePercent: 90, + OverprovisionRatio: 5, + }, + }, + }, + }, + }, + } + return lvmClusterRes +} + +// createNamespace creates a namespace in the cluster, ignoring if it already exists. +func createNamespace(ctx context.Context, namespace string) { + label := make(map[string]string) + // label required for monitoring this namespace + label["openshift.io/cluster-monitoring"] = "true" + label["pod-security.kubernetes.io/enforce"] = "privileged" + label["security.openshift.io/scc.podSecurityLabelSync"] = "false" + + annotations := make(map[string]string) + // annotation required for workload partitioning + annotations["workload.openshift.io/allowed"] = "management" + + ns := &k8sv1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + Annotations: annotations, + Labels: label, + }, + } + CreateResource(ctx, ns) +} + +// DeleteResources in concurrent rows with sequential elements in each row +// for 3 rows with 3 objects each there will be 3 goroutines running deletions for +// 3 elements each +func DeleteResources(objRows [][]client.Object) func(ctx SpecContext) { + GinkgoHelper() + return func(ctx SpecContext) { + wg := sync.WaitGroup{} + for _, objs := range objRows { + wg.Add(1) + go func(ctx context.Context, objs []client.Object) { + defer wg.Done() + defer GinkgoRecover() + for _, obj := range objs { + DeleteResource(ctx, obj) + } + }(ctx, objs) + } + wg.Wait() + } +} diff --git a/test/e2e/lvm_cluster_test.go b/test/e2e/lvm_cluster_test.go new file mode 100644 index 000000000..63fab8264 --- /dev/null +++ b/test/e2e/lvm_cluster_test.go @@ -0,0 +1,77 @@ +/* +Copyright © 2023 Red Hat, Inc. + +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 e2e + +import ( + "fmt" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "k8s.io/apimachinery/pkg/types" + + "github.com/openshift/lvm-operator/api/v1alpha1" +) + +func lvmClusterTest() { + var cluster *v1alpha1.LVMCluster + BeforeEach(func(ctx SpecContext) { + cluster = GetDefaultTestLVMClusterTemplate() + }) + AfterEach(func(ctx SpecContext) { + DeleteResource(ctx, cluster) + }) + + Describe("Filesystem Type", Serial, func() { + + It("should default to xfs", func(ctx SpecContext) { + CreateResource(ctx, cluster) + VerifyLVMSSetup(ctx, cluster) + + By("Verifying that the default FS type is set to XFS on the StorageClass") + sc := GetStorageClass(ctx, types.NamespacedName{Name: storageClassName, Namespace: installNamespace}) + Expect(sc.Parameters["csi.storage.k8s.io/fstype"]).To(Equal(string(v1alpha1.FilesystemTypeXFS))) + }) + + DescribeTable("fstype", func(ctx SpecContext, fsType v1alpha1.DeviceFilesystemType) { + By(fmt.Sprintf("modifying cluster template to have file system %s by default", fsType)) + cluster.Spec.Storage.DeviceClasses[0].FilesystemType = fsType + + CreateResource(ctx, cluster) + VerifyLVMSSetup(ctx, cluster) + + By("Verifying the correct fstype Parameter") + sc := GetStorageClass(ctx, types.NamespacedName{Name: storageClassName, Namespace: installNamespace}) + Expect(sc.Parameters["csi.storage.k8s.io/fstype"]).To(Equal(string(fsType))) + }, + Entry("xfs", v1alpha1.FilesystemTypeXFS), + Entry("ext4", v1alpha1.FilesystemTypeExt4), + ) + }) + + Describe("Storage Class", Serial, func() { + It("should become ready without a default storageclass", func(ctx SpecContext) { + // set default to false + for _, dc := range cluster.Spec.Storage.DeviceClasses { + dc.Default = false + } + + CreateResource(ctx, cluster) + VerifyLVMSSetup(ctx, cluster) + }) + }) +} diff --git a/test/e2e/lvm_pvc_test.go b/test/e2e/lvm_pvc_test.go new file mode 100644 index 000000000..cd3aaf100 --- /dev/null +++ b/test/e2e/lvm_pvc_test.go @@ -0,0 +1,293 @@ +/* +Copyright © 2023 Red Hat, Inc. + +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 e2e + +import ( + "context" + _ "embed" + "fmt" + "strings" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/meta" + + snapapi "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1" + + k8sv1 "k8s.io/api/core/v1" + "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" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" + + "github.com/openshift/lvm-operator/api/v1alpha1" +) + +const ( + PodImageForPVCTests = "public.ecr.aws/docker/library/busybox:1.36" + DevicePathForPVCTests = "/dev/xda" + MountPathForPVCTests = "/test1" + VolumeNameForPVCTests = "vol1" + ContainerNameForPVCTests = "pause" +) + +var ( + PodCommandForPVCTests = []string{"sh", "-c", "tail -f /dev/null"} +) + +type pvcType string + +const ( + pvcTypeEphemeral pvcType = "ephemeral" + pvcTypeStatic pvcType = "static" +) + +func pvcTest() { + var cluster *v1alpha1.LVMCluster + BeforeAll(func(ctx SpecContext) { + cluster = GetDefaultTestLVMClusterTemplate() + CreateResource(ctx, cluster) + VerifyLVMSSetup(ctx, cluster) + DeferCleanup(func(ctx SpecContext) { + DeleteResource(ctx, cluster) + }) + }) + + volumeModes := []k8sv1.PersistentVolumeMode{ + k8sv1.PersistentVolumeBlock, + k8sv1.PersistentVolumeFilesystem, + } + + pvcTypes := []pvcType{ + pvcTypeEphemeral, + pvcTypeStatic, + } + + for _, pvMode := range volumeModes { + Context(fmt.Sprintf("PersistentVolumeMode: %s", string(pvMode)), func() { + for _, pvcType := range pvcTypes { + Context(fmt.Sprintf("PVC Type: %s", pvcType), func() { + pvcTestsForMode(pvMode, pvcType) + }) + } + }) + } +} + +func pvcTestsForMode(volumeMode k8sv1.PersistentVolumeMode, pvcType pvcType) { + var contentMode ContentMode + switch volumeMode { + case k8sv1.PersistentVolumeBlock: + contentMode = ContentModeBlock + case k8sv1.PersistentVolumeFilesystem: + contentMode = ContentModeFile + } + + var pod *k8sv1.Pod + var pvc *k8sv1.PersistentVolumeClaim + switch pvcType { + case pvcTypeStatic: + pvc = generatePVC(volumeMode) + pod = generatePodConsumingPVC(pvc) + case pvcTypeEphemeral: + pod = generatePodWithEphemeralVolume(volumeMode) + // recreates locally what will be created as an ephemeral volume + pvc = &k8sv1.PersistentVolumeClaim{} + pvc.SetName(fmt.Sprintf("%s-%s", pod.GetName(), pod.Spec.Volumes[0].Name)) + pvc.SetNamespace(pod.GetNamespace()) + pvc.Spec.VolumeMode = &volumeMode + } + + clonePVC := generatePVCCloneFromPVC(pvc) + clonePod := generatePodConsumingPVC(clonePVC) + + snapshot := generateVolumeSnapshot(pvc, snapshotClass) + snapshotPVC := generatePVCFromSnapshot(volumeMode, snapshot) + snapshotPod := generatePodConsumingPVC(snapshotPVC) + + AfterAll(DeleteResources([][]client.Object{ + {snapshotPod, snapshotPVC, snapshot}, + {clonePod, clonePVC}, + {pod, pvc}, + })) + + expectedData := "TESTDATA" + It("PVC and Pod", func(ctx SpecContext) { + if pvcType == pvcTypeStatic { + CreateResource(ctx, pvc) + } + CreateResource(ctx, pod) + validatePodIsRunning(ctx, client.ObjectKeyFromObject(pod)) + validatePVCIsBound(ctx, client.ObjectKeyFromObject(pvc)) + + Expect(contentTester.WriteDataInPod(ctx, pod, expectedData, contentMode)).To(Succeed()) + validatePodData(ctx, pod, expectedData, contentMode) + }) + + It("Cloning", func(ctx SpecContext) { + CreateResource(ctx, clonePVC) + CreateResource(ctx, clonePod) + + validatePodIsRunning(ctx, client.ObjectKeyFromObject(clonePod)) + validatePVCIsBound(ctx, client.ObjectKeyFromObject(clonePVC)) + validatePodData(ctx, clonePod, expectedData, contentMode) + }) + + It("Snapshots", func(ctx SpecContext) { + createVolumeSnapshotFromPVCOrSkipIfUnsupported(ctx, snapshot) + validateSnapshotReadyToUse(ctx, client.ObjectKeyFromObject(snapshot)) + + CreateResource(ctx, snapshotPVC) + CreateResource(ctx, snapshotPod) + + validatePodIsRunning(ctx, client.ObjectKeyFromObject(snapshotPod)) + validatePVCIsBound(ctx, client.ObjectKeyFromObject(snapshotPVC)) + validatePodData(ctx, snapshotPod, expectedData, contentMode) + }) +} + +func generatePVCSpec(mode k8sv1.PersistentVolumeMode) k8sv1.PersistentVolumeClaimSpec { + return k8sv1.PersistentVolumeClaimSpec{ + VolumeMode: ptr.To(mode), + AccessModes: []k8sv1.PersistentVolumeAccessMode{k8sv1.ReadWriteOnce}, + Resources: k8sv1.ResourceRequirements{Requests: map[k8sv1.ResourceName]resource.Quantity{ + k8sv1.ResourceStorage: resource.MustParse("1Gi"), + }}, + StorageClassName: ptr.To(storageClassName), + } +} + +func generatePVC(mode k8sv1.PersistentVolumeMode) *k8sv1.PersistentVolumeClaim { + return &k8sv1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: strings.ToLower(string(mode)), + Namespace: testNamespace, + }, + Spec: generatePVCSpec(mode), + } +} + +func generatePVCCloneFromPVC(pvc *k8sv1.PersistentVolumeClaim) *k8sv1.PersistentVolumeClaim { + clone := generatePVC(*pvc.Spec.VolumeMode) + gvk, _ := apiutil.GVKForObject(pvc, crClient.Scheme()) + clone.SetName(fmt.Sprintf("%s-clone", pvc.GetName())) + clone.Spec.DataSource = &k8sv1.TypedLocalObjectReference{ + Kind: gvk.Kind, + Name: pvc.GetName(), + } + return clone +} + +func generatePVCFromSnapshot(mode k8sv1.PersistentVolumeMode, snapshot *snapapi.VolumeSnapshot) *k8sv1.PersistentVolumeClaim { + pvc := &k8sv1.PersistentVolumeClaim{} + pvc.SetName(snapshot.GetName()) + pvc.SetNamespace(snapshot.GetNamespace()) + pvc.Spec = generatePVCSpec(mode) + gvk, _ := apiutil.GVKForObject(snapshot, crClient.Scheme()) + pvc.Spec.DataSource = &k8sv1.TypedLocalObjectReference{ + Kind: gvk.Kind, + APIGroup: ptr.To(gvk.Group), + Name: snapshot.GetName(), + } + return pvc +} + +func generateContainer(mode k8sv1.PersistentVolumeMode) k8sv1.Container { + container := k8sv1.Container{ + Name: ContainerNameForPVCTests, + Command: PodCommandForPVCTests, + Image: PodImageForPVCTests, + SecurityContext: &k8sv1.SecurityContext{ + RunAsNonRoot: ptr.To(true), + SeccompProfile: &k8sv1.SeccompProfile{ + Type: k8sv1.SeccompProfileTypeRuntimeDefault, + }, + Capabilities: &k8sv1.Capabilities{Drop: []k8sv1.Capability{"ALL"}}, + }, + } + switch mode { + case k8sv1.PersistentVolumeBlock: + container.VolumeDevices = []k8sv1.VolumeDevice{{Name: VolumeNameForPVCTests, DevicePath: DevicePathForPVCTests}} + case k8sv1.PersistentVolumeFilesystem: + container.VolumeMounts = []k8sv1.VolumeMount{{Name: VolumeNameForPVCTests, MountPath: MountPathForPVCTests}} + } + return container +} + +func generatePodConsumingPVC(pvc *k8sv1.PersistentVolumeClaim) *k8sv1.Pod { + return &k8sv1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-consumer", pvc.GetName()), + Namespace: testNamespace, + DeletionGracePeriodSeconds: ptr.To(int64(1)), + }, + Spec: k8sv1.PodSpec{ + Containers: []k8sv1.Container{generateContainer(*pvc.Spec.VolumeMode)}, + Volumes: []k8sv1.Volume{{ + Name: VolumeNameForPVCTests, + VolumeSource: k8sv1.VolumeSource{ + PersistentVolumeClaim: &k8sv1.PersistentVolumeClaimVolumeSource{ClaimName: pvc.GetName()}, + }, + }}, + }, + } +} + +func generatePodWithEphemeralVolume(mode k8sv1.PersistentVolumeMode) *k8sv1.Pod { + return &k8sv1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-ephemeral", strings.ToLower(string(mode))), + Namespace: testNamespace, + DeletionGracePeriodSeconds: ptr.To(int64(1)), + }, + Spec: k8sv1.PodSpec{ + Containers: []k8sv1.Container{generateContainer(mode)}, + Volumes: []k8sv1.Volume{{ + Name: VolumeNameForPVCTests, + VolumeSource: k8sv1.VolumeSource{ + Ephemeral: &k8sv1.EphemeralVolumeSource{VolumeClaimTemplate: &k8sv1.PersistentVolumeClaimTemplate{ + Spec: generatePVCSpec(mode)}, + }, + }, + }}, + }, + } +} + +func generateVolumeSnapshot(pvc *k8sv1.PersistentVolumeClaim, snapshotClassName string) *snapapi.VolumeSnapshot { + return &snapapi.VolumeSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-snapshot", pvc.GetName()), + Namespace: testNamespace, + }, + Spec: snapapi.VolumeSnapshotSpec{ + Source: snapapi.VolumeSnapshotSource{PersistentVolumeClaimName: ptr.To(pvc.GetName())}, + VolumeSnapshotClassName: ptr.To(snapshotClassName), + }, + } +} + +func createVolumeSnapshotFromPVCOrSkipIfUnsupported(ctx context.Context, snapshot *snapapi.VolumeSnapshot) { + GinkgoHelper() + By(fmt.Sprintf("Creating VolumeSnapshot %q", snapshot.GetName())) + err := crClient.Create(ctx, snapshot) + if meta.IsNoMatchError(err) { + Skip("Skipping Testing of VolumeSnapshot Operations due to lack of volume snapshot support") + } + Expect(err).ToNot(HaveOccurred(), "PVC should be created successfully") +} diff --git a/test/e2e/lvm_suite_test.go b/test/e2e/lvm_suite_test.go index 496532121..ea2b01211 100644 --- a/test/e2e/lvm_suite_test.go +++ b/test/e2e/lvm_suite_test.go @@ -20,7 +20,9 @@ import ( "context" "flag" "testing" - "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" "go.uber.org/zap" "go.uber.org/zap/zapcore" @@ -28,10 +30,6 @@ import ( "github.com/go-logr/zapr" "sigs.k8s.io/controller-runtime/pkg/log" ctrlZap "sigs.k8s.io/controller-runtime/pkg/log/zap" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/openshift/lvm-operator/api/v1alpha1" ) func TestLvm(t *testing.T) { @@ -52,7 +50,7 @@ var _ = BeforeSuite(func(ctx context.Context) { // Configure the disk and install the operator beforeTestSuiteSetup(ctx) - lvmNamespaceSetup(ctx) + createNamespace(ctx, testNamespace) }) var _ = AfterSuite(func(ctx context.Context) { @@ -62,26 +60,5 @@ var _ = AfterSuite(func(ctx context.Context) { var _ = Describe("LVM Operator e2e tests", func() { Describe("LVM Cluster Configuration", Serial, lvmClusterTest) - - Describe("LVM Operator", Ordered, func() { - // Ordered to give the BeforeAll/AfterAll functionality to achieve common setup - var clusterConfig *v1alpha1.LVMCluster - - BeforeAll(func(ctx SpecContext) { - clusterConfig = generateLVMCluster() - lvmClusterSetup(clusterConfig, ctx) - By("Verifying the cluster is ready") - Eventually(clusterReadyCheck(clusterConfig), timeout, 300*time.Millisecond).WithContext(ctx).Should(Succeed()) - }) - - Describe("Functional Tests", func() { - Context("LVMCluster reconciliation", validateResources) - Context("PVC tests", pvcTest) - Context("Ephemeral volume tests", ephemeralTest) - }) - - AfterAll(func(ctx SpecContext) { - lvmClusterCleanup(clusterConfig, ctx) - }) - }) + Describe("PVC", Serial, Ordered, pvcTest) }) diff --git a/test/e2e/lvmcluster.go b/test/e2e/lvmcluster.go deleted file mode 100644 index 90b674e0e..000000000 --- a/test/e2e/lvmcluster.go +++ /dev/null @@ -1,210 +0,0 @@ -/* -Copyright © 2023 Red Hat, Inc. - -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 e2e - -import ( - "context" - "errors" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/openshift/lvm-operator/api/v1alpha1" - storagev1 "k8s.io/api/storage/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - utilwait "k8s.io/apimachinery/pkg/util/wait" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const ( - lvmClusterName string = "rh-lvmcluster" -) - -func generateLVMCluster() *v1alpha1.LVMCluster { - lvmClusterRes := &v1alpha1.LVMCluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: lvmClusterName, - Namespace: installNamespace, - }, - Spec: v1alpha1.LVMClusterSpec{ - Storage: v1alpha1.Storage{ - DeviceClasses: []v1alpha1.DeviceClass{ - { - Name: "vg1", - Default: true, - ThinPoolConfig: &v1alpha1.ThinPoolConfig{ - Name: "mytp1", - SizePercent: 90, - OverprovisionRatio: 5, - }, - }, - }, - }, - }, - } - return lvmClusterRes -} - -// startLVMCluster creates a sample CR. -func startLVMCluster(clusterConfig *v1alpha1.LVMCluster, ctx context.Context) error { - return crClient.Create(ctx, clusterConfig) -} - -// deleteLVMCluster deletes a sample CR. -func deleteLVMCluster(clusterConfig *v1alpha1.LVMCluster, ctx context.Context) error { - err := crClient.Delete(ctx, clusterConfig) - if err != nil { - return err - } - - timeout := 600 * time.Second - interval := 10 * time.Second - - // wait for LVMCluster to be deleted - err = utilwait.PollUntilContextTimeout(ctx, interval, timeout, true, func(ctx context.Context) (done bool, err error) { - err = crClient.Get(ctx, types.NamespacedName{Name: clusterConfig.Name, Namespace: installNamespace}, clusterConfig) - if err != nil && k8serrors.IsNotFound(err) { - return true, nil - } - if err == nil { - return false, nil - } - return true, nil - }) - - return err -} - -func lvmClusterTest() { - Describe("Filesystem Type", Serial, func() { - - var clusterConfig *v1alpha1.LVMCluster - - AfterEach(func(ctx SpecContext) { - // Delete the cluster - lvmClusterCleanup(clusterConfig, ctx) - }) - - It("should default to xfs", func(ctx SpecContext) { - clusterConfig = generateLVMCluster() // Do not specify a fstype - - By("Setting up the cluster with the default fstype") - lvmClusterSetup(clusterConfig, ctx) - - By("Verifying the cluster is ready") - Eventually(clusterReadyCheck(clusterConfig), timeout, 300*time.Millisecond).WithContext(ctx).Should(Succeed()) - - By("Checking that the Storage Class is created normally") - // Make sure the storage class was configured properly - sc := storagev1.StorageClass{} - - By("Verifying the StorageClass exists") - Eventually(func(ctx SpecContext) error { - return crClient.Get(ctx, types.NamespacedName{Name: storageClassName, Namespace: installNamespace}, &sc) - }, timeout, interval).WithContext(ctx).Should(Succeed()) - - By("Verifying that the default FS type is set to XFS on the StorageClass") - - Expect(sc.Parameters["csi.storage.k8s.io/fstype"]).To(Equal(string(v1alpha1.FilesystemTypeXFS))) - }) - - It("should be xfs if specified", func(ctx SpecContext) { - clusterConfig = generateLVMCluster() - clusterConfig.Spec.Storage.DeviceClasses[0].FilesystemType = v1alpha1.FilesystemTypeXFS - - By("Setting up the cluster with xfs fstype") - lvmClusterSetup(clusterConfig, ctx) - - By("Verifying the cluster is ready") - Eventually(clusterReadyCheck(clusterConfig), timeout, 300*time.Millisecond).WithContext(ctx).Should(Succeed()) - - // Make sure the storage class was configured properly - sc := storagev1.StorageClass{} - - By("Verifying the StorageClass exists") - Eventually(func(ctx SpecContext) error { - return crClient.Get(ctx, types.NamespacedName{Name: storageClassName, Namespace: installNamespace}, &sc) - }, timeout, interval).WithContext(ctx).Should(Succeed()) - - By("Verifying the correct fstype Parameter") - Expect(sc.Parameters["csi.storage.k8s.io/fstype"]).To(Equal(string(v1alpha1.FilesystemTypeXFS))) - }) - - It("should be ext4 if specified", func(ctx SpecContext) { - clusterConfig = generateLVMCluster() - clusterConfig.Spec.Storage.DeviceClasses[0].FilesystemType = v1alpha1.FilesystemTypeExt4 - - By("Setting up the cluster with the ext4 fstype") - lvmClusterSetup(clusterConfig, ctx) - - By("Verifying the cluster is ready") - Eventually(clusterReadyCheck(clusterConfig), timeout, 300*time.Millisecond).WithContext(ctx).Should(Succeed()) - - // Make sure the storage class was configured properly - sc := storagev1.StorageClass{} - - By("Verifying the StorageClass exists") - Eventually(func(ctx SpecContext) error { - return crClient.Get(ctx, types.NamespacedName{Name: storageClassName, Namespace: installNamespace}, &sc) - }, timeout, interval).WithContext(ctx).Should(Succeed()) - - By("Verifying the correct fstype Parameter") - Expect(sc.Parameters["csi.storage.k8s.io/fstype"]).To(Equal(string(v1alpha1.FilesystemTypeExt4))) - }) - }) - - Describe("Storage Class", Serial, func() { - - var clusterConfig *v1alpha1.LVMCluster - - AfterEach(func(ctx SpecContext) { - // Delete the cluster - lvmClusterCleanup(clusterConfig, ctx) - }) - - It("should become ready without a default storageclass", func(ctx SpecContext) { - clusterConfig = generateLVMCluster() // Do not specify a fstype - - // set default to false - for _, dc := range clusterConfig.Spec.Storage.DeviceClasses { - dc.Default = false - } - - By("Setting up the cluster with the default fstype") - lvmClusterSetup(clusterConfig, ctx) - - By("Verifying the cluster is ready") - Eventually(clusterReadyCheck(clusterConfig), timeout, 300*time.Millisecond).WithContext(ctx).Should(Succeed()) - }) - }) -} - -func clusterReadyCheck(clusterConfig *v1alpha1.LVMCluster) func(ctx context.Context) error { - return func(ctx context.Context) error { - currentCluster := clusterConfig - err := crClient.Get(ctx, client.ObjectKeyFromObject(clusterConfig), currentCluster) - if err != nil { - return err - } - if currentCluster.Status.State == v1alpha1.LVMStatusReady { - return nil - } - return errors.New("cluster is not ready yet") - } -} diff --git a/test/e2e/pod_runner.go b/test/e2e/pod_runner_test.go similarity index 97% rename from test/e2e/pod_runner.go rename to test/e2e/pod_runner_test.go index bd3340368..6f4573c20 100644 --- a/test/e2e/pod_runner.go +++ b/test/e2e/pod_runner_test.go @@ -9,7 +9,8 @@ import ( "net/http" "strings" - "github.com/onsi/gomega" + . "github.com/onsi/gomega" + k8sv1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes" @@ -21,8 +22,8 @@ import ( type ContentMode string const ( - ContentModeFile = "file" - ContentModeBlock = "block" + ContentModeFile ContentMode = "file" + ContentModeBlock ContentMode = "block" ) // PodRunner is a helper to run commands in pods via remote execution. similar to kubectl exec @@ -129,7 +130,7 @@ func (t *PodRunner) ExecCommandInFirstPodContainer(ctx context.Context, pod *k8s // WriteDataInPod writes the data to pod. func (t *PodRunner) WriteDataInPod(ctx context.Context, pod *k8sv1.Pod, content string, mode ContentMode) error { - gomega.Expect(pod.Spec.Containers).NotTo(gomega.BeEmpty()) + Expect(pod.Spec.Containers).NotTo(BeEmpty()) var filePath string if mode == "file" { filePath = pod.Spec.Containers[0].VolumeMounts[0].MountPath + "/test" diff --git a/test/e2e/pvc_check.go b/test/e2e/pvc_check.go deleted file mode 100644 index 1e9542698..000000000 --- a/test/e2e/pvc_check.go +++ /dev/null @@ -1,360 +0,0 @@ -/* -Copyright © 2023 Red Hat, Inc. - -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 e2e - -import ( - "context" - _ "embed" - "fmt" - - snapapi "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - k8sv1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -//go:embed testdata/pvc_tests/pvc-template.yaml -var pvcYAMLTemplate string - -//go:embed testdata/pvc_tests/pod-volume-mount-template.yaml -var podVolumeFSYAMLTemplate string - -//go:embed testdata/pvc_tests/pod-volume-device-template.yaml -var podVolumeBlockYAMLTemplate string - -//go:embed testdata/pvc_tests/volume-snapshot-template.yaml -var volumeSnapshotYAMLTemplate string - -//go:embed testdata/pvc_tests/pvc-clone-template.yaml -var pvcCloneYAMLTemplate string - -//go:embed testdata/pvc_tests/pvc-snapshot-restore-template.yaml -var pvcSnapshotRestoreYAMLTemplate string - -func pvcTest() { - - Describe("PVC Tests", func() { - var pvc *k8sv1.PersistentVolumeClaim - var pod *k8sv1.Pod - var snapshot *snapapi.VolumeSnapshot - var clonePvc *k8sv1.PersistentVolumeClaim - var clonePod *k8sv1.Pod - var restorePvc *k8sv1.PersistentVolumeClaim - var restorePod *k8sv1.Pod - var err error - - var skipSnapshotOps bool - - Context("create pvc, pod, snapshots, clones", Ordered, func() { - Context("Tests PVC operations for VolumeMode=Filesystem", Ordered, func() { - It("Creation of Pod binding a PVC from LVMS", func(ctx SpecContext) { - By("Creating a pvc") - filePvcYaml := fmt.Sprintf(pvcYAMLTemplate, "lvmfilepvc", testNamespace, "Filesystem", storageClassName) - pvc, err = getPVC(filePvcYaml) - Expect(err).To(BeNil()) - Expect(crClient.Create(ctx, pvc)).To(Succeed()) - - By("Creating a pod") - podVolumeMountYaml := fmt.Sprintf(podVolumeFSYAMLTemplate, "lvmfilepod", testNamespace, "lvmfilepvc") - pod, err = getPod(podVolumeMountYaml) - Expect(err).To(BeNil()) - Expect(crClient.Create(ctx, pod)).To(Succeed()) - - By("Verifying that the PVC(file system) is bound and the Pod is running") - Eventually(func(ctx context.Context) error { - if err := crClient.Get(ctx, types.NamespacedName{Name: pvc.Name, Namespace: pvc.Namespace}, pvc); err != nil { - return err - } - if pvc.Status.Phase != k8sv1.ClaimBound { - return fmt.Errorf("pvc is not bound yet: %s", pvc.Status.Phase) - } - return nil - }, timeout, interval).WithContext(ctx).Should(Succeed()) - - By("Pod should be running") - Eventually(func(ctx context.Context) bool { - err = crClient.Get(ctx, types.NamespacedName{Name: pod.Name, Namespace: pod.Namespace}, pod) - return err == nil && pod.Status.Phase == k8sv1.PodRunning - }, timeout, interval).WithContext(ctx).Should(BeTrue()) - - By("Writing data to the Pod") - Expect(contentTester.WriteDataInPod(ctx, pod, "TESTDATA", ContentModeFile)).To(Succeed()) - }) - - It("Testing Snapshot Operations", func(ctx SpecContext) { - By("Creating a Snapshot of the file-pvc") - snapshotYaml := fmt.Sprintf(volumeSnapshotYAMLTemplate, "lvmfilepvc-snapshot", testNamespace, snapshotClass, "lvmfilepvc") - snapshot, err = getVolumeSnapshot(snapshotYaml) - Expect(err).To(BeNil()) - err = crClient.Create(ctx, snapshot) - if meta.IsNoMatchError(err) { - skipSnapshotOps = true - Skip("Skipping Testing of Snapshot Operations due to lack of volume snapshot support") - } - Expect(err).To(BeNil()) - - By("Verifying that the Snapshot is ready") - Eventually(func(ctx context.Context) bool { - err := crClient.Get(ctx, types.NamespacedName{Name: snapshot.Name, Namespace: snapshot.Namespace}, snapshot) - if err == nil && snapshot.Status != nil && snapshot.Status.ReadyToUse != nil { - return *snapshot.Status.ReadyToUse - } - return false - }, timeout, interval).WithContext(ctx).Should(BeTrue()) - - By("Creating a clone of the filesystem pvc") - pvcCloneYaml := fmt.Sprintf(pvcCloneYAMLTemplate, "lvmfilepvc-clone", testNamespace, "Filesystem", storageClassName, "lvmfilepvc") - clonePvc, err = getPVC(pvcCloneYaml) - Expect(err).To(BeNil()) - Expect(crClient.Create(ctx, clonePvc)).To(Succeed()) - - By("Creating a pod consuming the clone") - podVolumeMountYaml := fmt.Sprintf(podVolumeFSYAMLTemplate, "clone-lvmfilepod", testNamespace, "lvmfilepvc-clone") - clonePod, err = getPod(podVolumeMountYaml) - Expect(err).To(BeNil()) - Expect(crClient.Create(ctx, clonePod)).To(Succeed()) - - By("Having a bound claim in the pvc") - Eventually(func(ctx context.Context) error { - if err := crClient.Get(ctx, client.ObjectKeyFromObject(clonePvc), clonePvc); err != nil { - return err - } - if clonePvc.Status.Phase != k8sv1.ClaimBound { - return fmt.Errorf("pvc is not bound yet: %s", clonePvc.Status.Phase) - } - return nil - }, timeout, interval).WithContext(ctx).Should(Succeed()) - - By("Reading data from the cloned data in the Pod") - clonedData := "" - Eventually(func(ctx context.Context) error { - clonedData, err = contentTester.GetDataInPod(ctx, clonePod, ContentModeFile) - return err - }).WithContext(ctx).Should(Succeed()) - Expect(clonedData).To(Equal("TESTDATA")) - - By("Restore Snapshot for file-pvc") - pvcRestoreYaml := fmt.Sprintf(pvcSnapshotRestoreYAMLTemplate, "lvmfilepvc-restore", testNamespace, "Filesystem", storageClassName, "lvmfilepvc-snapshot") - restorePvc, err = getPVC(pvcRestoreYaml) - Expect(err).To(BeNil()) - Expect(crClient.Create(ctx, restorePvc)).To(Succeed()) - - By("Creating a pod consuming the restored snapshot of the pvc") - podVolumeMountYaml = fmt.Sprintf(podVolumeFSYAMLTemplate, "restore-lvmfilepod", testNamespace, "lvmfilepvc-restore") - restorePod, err = getPod(podVolumeMountYaml) - Expect(err).To(BeNil()) - Expect(crClient.Create(ctx, restorePod)).To(Succeed()) - - By("Having the restored data pvc be bound") - Eventually(func(ctx context.Context) error { - if err := crClient.Get(ctx, client.ObjectKeyFromObject(restorePvc), restorePvc); err != nil { - return err - } - if restorePvc.Status.Phase != k8sv1.ClaimBound { - return fmt.Errorf("pvc is not bound yet: %s", restorePvc.Status.Phase) - } - return nil - }, timeout, interval).WithContext(ctx).Should(Succeed()) - - By("Reading data from the restored data in the Pod") - restoredData := "" - Eventually(func(ctx context.Context) error { - restoredData, err = contentTester.GetDataInPod(ctx, restorePod, ContentModeFile) - return err - }, timeout, interval).WithContext(ctx).Should(Succeed()) - Expect(restoredData).To(Equal("TESTDATA")) - }) - - It("Cleaning up for VolumeMode=Filesystem", func(ctx SpecContext) { - if !skipSnapshotOps { - By(fmt.Sprintf("Deleting %s", clonePod.Name)) - Expect(crClient.Delete(ctx, clonePod)).To(Succeed()) - - By(fmt.Sprintf("Deleting Clone PVC %s", clonePvc.Name)) - Expect(crClient.Delete(ctx, clonePvc)).To(Succeed()) - - By(fmt.Sprintf("Deleting Pod %s", restorePod.Name)) - Expect(crClient.Delete(ctx, restorePod)).To(Succeed()) - - By(fmt.Sprintf("Deleting Snapshot PVC %s", restorePvc.Name)) - Expect(crClient.Delete(ctx, restorePvc)).To(Succeed()) - - By(fmt.Sprintf("Deleting VolumeSnapshot %s", snapshot.Name)) - Expect(crClient.Delete(ctx, snapshot)).To(Succeed()) - } - - By(fmt.Sprintf("Deleting Pod %s", pod.Name)) - Expect(crClient.Delete(ctx, pod)).To(Succeed()) - - By(fmt.Sprintf("Deleting PVC %s", pvc.Name)) - Expect(crClient.Delete(ctx, pvc)).To(Succeed()) - }) - }) - - Context("Tests PVC operations for VolumeMode=Block", Ordered, func() { - It("Creation of Pod binding a PVC from LVMS", func(ctx SpecContext) { - By("Creating a pvc") - blockPvcYaml := fmt.Sprintf(pvcYAMLTemplate, "lvmblockpvc", testNamespace, "Block", storageClassName) - pvc, err = getPVC(blockPvcYaml) - Expect(err).To(BeNil()) - Expect(crClient.Create(ctx, pvc)).To(Succeed()) - - By("Creating a pod") - podVolumeBlockYaml := fmt.Sprintf(podVolumeBlockYAMLTemplate, "lvmblockpod", testNamespace, "lvmblockpvc") - pod, err = getPod(podVolumeBlockYaml) - Expect(err).To(BeNil()) - Expect(crClient.Create(ctx, pod)).To(Succeed()) - - By("Verifying that the PVC(block) is bound and the Pod is running") - Eventually(func(ctx context.Context) error { - if err := crClient.Get(ctx, client.ObjectKeyFromObject(pvc), pvc); err != nil { - return err - } - if pvc.Status.Phase != k8sv1.ClaimBound { - return fmt.Errorf("pvc is not bound yet: %s", pvc.Status.Phase) - } - return nil - }, timeout, interval).WithContext(ctx).Should(Succeed()) - - By("Pod should be running") - Eventually(func(ctx context.Context) bool { - err = crClient.Get(ctx, types.NamespacedName{Name: pod.Name, Namespace: pod.Namespace}, pod) - return err == nil && pod.Status.Phase == k8sv1.PodRunning - }, timeout, interval).WithContext(ctx).Should(BeTrue()) - - By("Writing data to the Pod") - Expect(contentTester.WriteDataInPod(ctx, pod, "TESTDATA", ContentModeBlock)).To(Succeed()) - }) - - It("Testing Snapshot Operations", func(ctx SpecContext) { - By("Creating a Snapshot of the block-pvc") - snapshotYaml := fmt.Sprintf(volumeSnapshotYAMLTemplate, "lvmblockpvc-snapshot", testNamespace, snapshotClass, "lvmblockpvc") - snapshot, err = getVolumeSnapshot(snapshotYaml) - Expect(err).To(BeNil()) - err = crClient.Create(ctx, snapshot) - if meta.IsNoMatchError(err) { - skipSnapshotOps = true - Skip("Skipping Testing of Snapshot Operations due to lack of volume snapshot support") - } - Expect(err).To(BeNil()) - - By("Verifying that the Snapshot is ready") - Eventually(func(ctx context.Context) bool { - err := crClient.Get(ctx, types.NamespacedName{Name: snapshot.Name, Namespace: snapshot.Namespace}, snapshot) - if err == nil && snapshot.Status != nil && snapshot.Status.ReadyToUse != nil { - return *snapshot.Status.ReadyToUse - } - return false - }, timeout, interval).WithContext(ctx).Should(BeTrue()) - - By("Creating a clone of the block-pvc") - pvcCloneYaml := fmt.Sprintf(pvcCloneYAMLTemplate, "lvmblockpvc-clone", testNamespace, "Block", storageClassName, "lvmblockpvc") - clonePvc, err = getPVC(pvcCloneYaml) - Expect(err).To(BeNil()) - err = crClient.Create(ctx, clonePvc) - Expect(err).To(BeNil()) - - By("Creating a pod consuming the clone of the pvc") - podVolumeBlockYaml := fmt.Sprintf(podVolumeBlockYAMLTemplate, "clone-lvmblockpod", testNamespace, "lvmblockpvc-clone") - clonePod, err = getPod(podVolumeBlockYaml) - Expect(err).To(BeNil()) - Expect(crClient.Create(ctx, clonePod)).To(Succeed()) - - By("Having a bound claim in the pvc") - Eventually(func(ctx context.Context) error { - if err := crClient.Get(ctx, client.ObjectKeyFromObject(clonePvc), clonePvc); err != nil { - return err - } - if clonePvc.Status.Phase != k8sv1.ClaimBound { - return fmt.Errorf("pvc is not bound yet: %s", clonePvc.Status.Phase) - } - return nil - }, timeout, interval).WithContext(ctx).Should(Succeed()) - - By("Reading data from the cloned data in the Pod") - clonedData := "" - Eventually(func(ctx context.Context) error { - clonedData, err = contentTester.GetDataInPod(ctx, clonePod, ContentModeBlock) - return err - }).WithContext(ctx).Should(Succeed()) - Expect(clonedData).To(Equal("TESTDATA")) - - By("Restore Snapshot for block-pvc") - pvcRestoreYaml := fmt.Sprintf(pvcSnapshotRestoreYAMLTemplate, "lvmblockpvc-restore", testNamespace, "Block", storageClassName, "lvmblockpvc-snapshot") - restorePvc, err = getPVC(pvcRestoreYaml) - Expect(err).To(BeNil()) - err = crClient.Create(ctx, restorePvc) - Expect(err).To(BeNil()) - - By("Creating a pod consuming the restored snapshot data") - podVolumeBlockYaml = fmt.Sprintf(podVolumeBlockYAMLTemplate, "restore-lvmblockpod", testNamespace, "lvmblockpvc-restore") - restorePod, err = getPod(podVolumeBlockYaml) - Expect(err).To(BeNil()) - Expect(crClient.Create(ctx, restorePod)).To(Succeed()) - - By("Having the restored data pvc be bound") - Eventually(func(ctx context.Context) error { - if err := crClient.Get(ctx, client.ObjectKeyFromObject(restorePvc), restorePvc); err != nil { - return err - } - if restorePvc.Status.Phase != k8sv1.ClaimBound { - return fmt.Errorf("pvc is not bound yet: %s", restorePvc.Status.Phase) - } - return nil - }, timeout, interval).WithContext(ctx).Should(Succeed()) - - By("Reading data from the restored data in the Pod") - restoredData := "" - Eventually(func(ctx context.Context) error { - restoredData, err = contentTester.GetDataInPod(ctx, restorePod, ContentModeBlock) - return err - }, timeout, interval).WithContext(ctx).Should(Succeed()) - Expect(restoredData).To(Equal("TESTDATA")) - }) - - It("Cleaning up for VolumeMode=Block", func(ctx SpecContext) { - if !skipSnapshotOps { - By(fmt.Sprintf("Deleting %s", clonePod.Name)) - Expect(crClient.Delete(ctx, clonePod)).To(Succeed()) - - By(fmt.Sprintf("Deleting Clone PVC %s", clonePvc.Name)) - Expect(crClient.Delete(ctx, clonePvc)).To(Succeed()) - - By(fmt.Sprintf("Deleting Pod %s", restorePod.Name)) - Expect(crClient.Delete(ctx, restorePod)).To(Succeed()) - - By(fmt.Sprintf("Deleting Snapshot PVC %s", restorePvc.Name)) - Expect(crClient.Delete(ctx, restorePvc)).To(Succeed()) - - By(fmt.Sprintf("Deleting VolumeSnapshot %s", snapshot.Name)) - Expect(crClient.Delete(ctx, snapshot)).To(Succeed()) - } - - By(fmt.Sprintf("Deleting Pod %s", pod.Name)) - Expect(crClient.Delete(ctx, pod)).To(Succeed()) - - By(fmt.Sprintf("Deleting PVC %s", pvc.Name)) - Expect(crClient.Delete(ctx, pvc)).To(Succeed()) - }) - }) - - }) - }) -} diff --git a/test/e2e/setup_teardown.go b/test/e2e/setup_teardown.go deleted file mode 100644 index 8e94d44ae..000000000 --- a/test/e2e/setup_teardown.go +++ /dev/null @@ -1,79 +0,0 @@ -/* -Copyright © 2023 Red Hat, Inc. - -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 e2e - -import ( - "context" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/openshift/lvm-operator/api/v1alpha1" -) - -// beforeTestSuiteSetup is the function called to initialize the test environment. -func beforeTestSuiteSetup(ctx context.Context) { - - if diskInstall { - By("Creating Disk for e2e tests") - Expect(diskSetup(ctx)).To(Succeed()) - } - - if lvmOperatorInstall { - By("BeforeTestSuite: deploying LVM Operator") - Expect(deployLVMWithOLM(ctx, lvmCatalogSourceImage, lvmSubscriptionChannel)).To(Succeed()) - } -} - -func lvmClusterSetup(clusterConfig *v1alpha1.LVMCluster, ctx context.Context) { - By("Starting LVM Cluster") - err := startLVMCluster(clusterConfig, ctx) - Expect(err).To(BeNil()) -} - -func lvmNamespaceSetup(ctx context.Context) { - By("Creating Namespace " + testNamespace) - err := createNamespace(ctx, testNamespace) - Expect(err).To(BeNil()) -} - -func lvmNamespaceCleanup(ctx context.Context) { - By("Deleting Namespace " + testNamespace) - err := deleteNamespaceAndWait(ctx, testNamespace) - Expect(err).To(BeNil()) -} - -func lvmClusterCleanup(clusterConfig *v1alpha1.LVMCluster, ctx context.Context) { - By("Deleting default LVM Cluster") - err := deleteLVMCluster(clusterConfig, ctx) - Expect(err).To(BeNil()) -} - -// afterTestSuiteCleanup is the function called to tear down the test environment. -func afterTestSuiteCleanup(ctx context.Context) { - - if lvmOperatorUninstall { - By("AfterTestSuite: uninstalling LVM Operator") - err := uninstallLVM(ctx, lvmCatalogSourceImage, lvmSubscriptionChannel) - Expect(err).To(BeNil(), "error uninstalling the LVM Operator: %v", err) - } - - if diskInstall { - By("Cleaning up disk") - err := diskRemoval(ctx) - Expect(err).To(BeNil()) - } -} diff --git a/test/e2e/subscription.go b/test/e2e/subscription_test.go similarity index 64% rename from test/e2e/subscription.go rename to test/e2e/subscription_test.go index 2887a4ee8..fe83394ed 100644 --- a/test/e2e/subscription.go +++ b/test/e2e/subscription_test.go @@ -18,11 +18,14 @@ package e2e import ( "context" - "fmt" "time" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + v1 "github.com/operator-framework/api/pkg/operators/v1" "github.com/operator-framework/api/pkg/operators/v1alpha1" + appsv1 "k8s.io/api/apps/v1" k8sv1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -30,7 +33,6 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" - utilwait "k8s.io/apimachinery/pkg/util/wait" crclient "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -117,31 +119,19 @@ func generateClusterObjects(lvmCatalogImage string, subscriptionChannel string) } // waitForLVMCatalogSource waits for LVM catalog source. -func waitForLVMCatalogSource(ctx context.Context) error { - timeout := 300 * time.Second - interval := 10 * time.Second - lastReason := "" - +func waitForLVMCatalogSource(ctx context.Context) bool { labelSelector, err := labels.Parse("olm.catalogSource in (lvms-catalogsource)") - if err != nil { - return err - } - err = utilwait.PollUntilContextTimeout(ctx, interval, timeout, true, func(ctx context.Context) (done bool, err error) { + Expect(err).ToNot(HaveOccurred()) + return Eventually(func(g Gomega, ctx context.Context) { pods := &k8sv1.PodList{} - err = crClient.List(ctx, pods, &crclient.ListOptions{ + g.Expect(crClient.List(ctx, pods, &crclient.ListOptions{ LabelSelector: labelSelector, Namespace: installNamespace, - }) - if err != nil { - lastReason = fmt.Sprintf("error talking to k8s apiserver: %v", err) - return false, nil - } + })).To(Succeed()) + + g.Expect(pods.Items).ToNot(BeEmpty(), "waiting on lvms catalog source pod to be created") - if len(pods.Items) == 0 { - lastReason = "waiting on lvms catalog source pod to be created" - return false, nil - } isReady := false for _, pod := range pods.Items { for _, condition := range pod.Status.Conditions { @@ -151,39 +141,19 @@ func waitForLVMCatalogSource(ctx context.Context) error { } } } - if !isReady { - lastReason = "waiting on lvms catalog source pod to reach ready state" - return false, nil - } - - // if we get here, then all deployments are created and available - return true, nil - }) + g.Expect(isReady).To(BeTrue()) - if err != nil { - return fmt.Errorf("%v: %s", err, lastReason) - } - - return nil + }).WithTimeout(300 * time.Second).WithPolling(10 * time.Second).WithContext(ctx).Should(Succeed()) } // waitForLVMOperator waits for the lvm-operator to come online. -func waitForLVMOperator(ctx context.Context) error { +func waitForLVMOperator(ctx context.Context) bool { deployments := []string{"lvms-operator"} - timeout := 1000 * time.Second - interval := 10 * time.Second - - lastReason := "" - - err := utilwait.PollUntilContextTimeout(ctx, interval, timeout, true, func(ctx context.Context) (done bool, err error) { + return Eventually(func(g Gomega, ctx context.Context) { for _, name := range deployments { deployment := &appsv1.Deployment{} - err = crClient.Get(ctx, types.NamespacedName{Name: name, Namespace: installNamespace}, deployment) - if err != nil { - lastReason = fmt.Sprintf("waiting on deployment %s to be created", name) - return false, nil - } + g.Expect(crClient.Get(ctx, types.NamespacedName{Name: name, Namespace: installNamespace}, deployment)).To(Succeed()) isAvailable := false for _, condition := range deployment.Status.Conditions { @@ -193,55 +163,37 @@ func waitForLVMOperator(ctx context.Context) error { } } - if !isAvailable { - lastReason = fmt.Sprintf("waiting on deployment %s to become available", name) - return false, nil - } + g.Expect(isAvailable).To(BeTrue()) } - - // if we get here, then all deployments are created and available - return true, nil - }) - - if err != nil { - return fmt.Errorf("%v: %s", err, lastReason) - } - - return nil + }).WithTimeout(1000 * time.Second).WithPolling(10 * time.Second).WithContext(ctx).Should(Succeed()) } // deployClusterObjects deploys the cluster objects. -func deployClusterObjects(ctx context.Context, co *clusterObjects) error { +func deployClusterObjects(ctx context.Context, co *clusterObjects) { for _, namespace := range co.namespaces { - err := createNamespace(ctx, namespace.Name) - if err != nil { - return err - } + createNamespace(ctx, namespace.Name) } for _, operatorGroup := range co.operatorGroups { operatorGroup := operatorGroup operatorGroups := &v1.OperatorGroupList{} - err := crClient.List(ctx, operatorGroups, &crclient.ListOptions{ + Expect(crClient.List(ctx, operatorGroups, &crclient.ListOptions{ Namespace: installNamespace, - }) - if err != nil { - return err - } - if len(operatorGroups.Items) > 1 { - // There should be only one operatorgroup in a namespace. - // The system is already misconfigured - error out. - return fmt.Errorf("more than one operatorgroup detected in namespace %v - aborting", operatorGroup.Namespace) - } + })).To(Succeed()) + + // There should be only one operatorgroup in a namespace. + // The system is already misconfigured - error out. + Expect(operatorGroups.Items).To(HaveLen(1), "more than one operatorgroup per namespace is not allowed") + if len(operatorGroups.Items) > 0 { // There should be only one operatorgroup in a namespace. // Skip this one, so we don't make the system bad. continue } - err = crClient.Create(ctx, &operatorGroup) + err := crClient.Create(ctx, &operatorGroup) if err != nil && !errors.IsAlreadyExists(err) { - return err + Expect(err).ToNot(HaveOccurred()) } } @@ -249,94 +201,52 @@ func deployClusterObjects(ctx context.Context, co *clusterObjects) error { catalogSource := catalogSource err := crClient.Create(ctx, &catalogSource) if err != nil && !errors.IsAlreadyExists(err) { - return err + Expect(err).ToNot(HaveOccurred()) } } // Wait for catalog source before posting subscription - err := waitForLVMCatalogSource(ctx) - if err != nil { - return err - } + waitForLVMCatalogSource(ctx) for _, subscription := range co.subscriptions { subscription := subscription err := crClient.Create(ctx, &subscription) if err != nil && !errors.IsAlreadyExists(err) { - return err + Expect(err).ToNot(HaveOccurred()) } } // Wait on lvm-operator to come online. - err = waitForLVMOperator(ctx) - if err != nil { - return err - } - - return nil + waitForLVMOperator(ctx) } // deployLVMWithOLM deploys lvm operator via an olm subscription. -func deployLVMWithOLM(ctx context.Context, lvmCatalogImage string, subscriptionChannel string) error { - - if lvmCatalogImage == "" { - return fmt.Errorf("catalog registry images not supplied") - } - +func deployLVMWithOLM(ctx context.Context, lvmCatalogImage string, subscriptionChannel string) { + Expect(lvmCatalogImage).ToNot(BeEmpty(), "catalog registry images must be supplied") co := generateClusterObjects(lvmCatalogImage, subscriptionChannel) - err := deployClusterObjects(ctx, co) - if err != nil { - return err - } - - return nil + deployClusterObjects(ctx, co) } -// deleteClusterObjects deletes remaining operator manifests. -func deleteClusterObjects(ctx context.Context, co *clusterObjects) error { +// uninstallLVM uninstalls lvm operator. +func uninstallLVM(ctx context.Context, lvmCatalogImage string, subscriptionChannel string) { + GinkgoHelper() - for _, operatorGroup := range co.operatorGroups { - operatorgroup := operatorGroup - err := crClient.Delete(ctx, &operatorgroup) - if err != nil && !errors.IsNotFound(err) { - return err - } + // Delete remaining operator manifests + co := generateClusterObjects(lvmCatalogImage, subscriptionChannel) + for _, operatorGroup := range co.operatorGroups { + DeleteResource(ctx, &operatorGroup) } for _, catalogSource := range co.catalogSources { - catalogsource := catalogSource - err := crClient.Delete(ctx, &catalogsource) - if err != nil && !errors.IsNotFound(err) { - return err - } + DeleteResource(ctx, &catalogSource) } for _, subscription := range co.subscriptions { - subs := subscription - err := crClient.Delete(ctx, &subs) - if err != nil && !errors.IsNotFound(err) { - return err - } + DeleteResource(ctx, &subscription) } - return nil -} - -// uninstallLVM uninstalls lvm operator. -func uninstallLVM(ctx context.Context, lvmCatalogImage string, subscriptionChannel string) error { - // Delete remaining operator manifests - co := generateClusterObjects(lvmCatalogImage, subscriptionChannel) - err := deleteClusterObjects(ctx, co) - if err != nil { - return err - } for _, namespace := range co.namespaces { - err = deleteNamespaceAndWait(ctx, namespace.Name) - if err != nil { - return err - } + DeleteResource(ctx, &namespace) } - - return nil } diff --git a/test/e2e/testdata/ephemeral_tests/ephemeral-clone.yaml b/test/e2e/testdata/ephemeral_tests/ephemeral-clone.yaml deleted file mode 100644 index e63116082..000000000 --- a/test/e2e/testdata/ephemeral_tests/ephemeral-clone.yaml +++ /dev/null @@ -1,16 +0,0 @@ -kind: PersistentVolumeClaim -apiVersion: v1 -metadata: - name: %s - namespace: %s -spec: - volumeMode: %s - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi - storageClassName: %s - dataSource: - name: %s - kind: PersistentVolumeClaim diff --git a/test/e2e/testdata/ephemeral_tests/ephemeral-snapshot-restore.yaml b/test/e2e/testdata/ephemeral_tests/ephemeral-snapshot-restore.yaml deleted file mode 100644 index 900c9d868..000000000 --- a/test/e2e/testdata/ephemeral_tests/ephemeral-snapshot-restore.yaml +++ /dev/null @@ -1,17 +0,0 @@ -kind: PersistentVolumeClaim -apiVersion: v1 -metadata: - name: %s - namespace: %s -spec: - accessModes: - - ReadWriteOnce - volumeMode: %s - resources: - requests: - storage: 1Gi - storageClassName: %s - dataSource: - name: %s - kind: VolumeSnapshot - apiGroup: snapshot.storage.k8s.io diff --git a/test/e2e/testdata/ephemeral_tests/ephemeral-volume-snapshot.yaml b/test/e2e/testdata/ephemeral_tests/ephemeral-volume-snapshot.yaml deleted file mode 100644 index 300f56eb3..000000000 --- a/test/e2e/testdata/ephemeral_tests/ephemeral-volume-snapshot.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: snapshot.storage.k8s.io/v1 -kind: VolumeSnapshot -metadata: - name: %s - namespace: %s -spec: - volumeSnapshotClassName: %s - source: - persistentVolumeClaimName: %s diff --git a/test/e2e/testdata/ephemeral_tests/pod-ephemeral-volume-device.yaml b/test/e2e/testdata/ephemeral_tests/pod-ephemeral-volume-device.yaml deleted file mode 100644 index 809b9dd39..000000000 --- a/test/e2e/testdata/ephemeral_tests/pod-ephemeral-volume-device.yaml +++ /dev/null @@ -1,35 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: %s - namespace: %s -spec: - containers: - - name: pause - securityContext: - runAsNonRoot: true - seccompProfile: - type: RuntimeDefault - capabilities: - drop: - - "ALL" - image: public.ecr.aws/docker/library/busybox:1.36 - command: ["sh", "-c", "tail -f /dev/null"] - volumeDevices: - - devicePath: /dev/xda - name: generic-ephemeral-volume - volumes: - - name: generic-ephemeral-volume - ephemeral: - volumeClaimTemplate: - metadata: - labels: - type: ephemeral-volume - spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi - storageClassName: %s - volumeMode: Block diff --git a/test/e2e/testdata/ephemeral_tests/pod-ephemeral-volume-mount.yaml b/test/e2e/testdata/ephemeral_tests/pod-ephemeral-volume-mount.yaml deleted file mode 100644 index 39ba9b861..000000000 --- a/test/e2e/testdata/ephemeral_tests/pod-ephemeral-volume-mount.yaml +++ /dev/null @@ -1,34 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: %s - namespace: %s -spec: - containers: - - name: pause - securityContext: - runAsNonRoot: true - seccompProfile: - type: RuntimeDefault - capabilities: - drop: - - "ALL" - image: public.ecr.aws/docker/library/busybox:1.36 - command: ["sh", "-c", "tail -f /dev/null"] - volumeMounts: - - mountPath: /test1 - name: generic-ephemeral-volume - volumes: - - name: generic-ephemeral-volume - ephemeral: - volumeClaimTemplate: - metadata: - labels: - type: ephemeral-volume - spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi - storageClassName: %s diff --git a/test/e2e/testdata/ephemeral_tests/pod-volume-device-template.yaml b/test/e2e/testdata/ephemeral_tests/pod-volume-device-template.yaml deleted file mode 100644 index 781a57d25..000000000 --- a/test/e2e/testdata/ephemeral_tests/pod-volume-device-template.yaml +++ /dev/null @@ -1,24 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: %s - namespace: %s -spec: - containers: - - name: pause - securityContext: - runAsNonRoot: true - seccompProfile: - type: RuntimeDefault - capabilities: - drop: - - "ALL" - image: public.ecr.aws/docker/library/busybox:1.36 - command: ["sh", "-c", "tail -f /dev/null"] - volumeDevices: - - devicePath: /dev/xda - name: my-volume - volumes: - - name: my-volume - persistentVolumeClaim: - claimName: %s diff --git a/test/e2e/testdata/ephemeral_tests/pod-volume-mount-template.yaml b/test/e2e/testdata/ephemeral_tests/pod-volume-mount-template.yaml deleted file mode 100644 index ce07b7b82..000000000 --- a/test/e2e/testdata/ephemeral_tests/pod-volume-mount-template.yaml +++ /dev/null @@ -1,24 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: %s - namespace: %s -spec: - containers: - - name: pause - securityContext: - runAsNonRoot: true - seccompProfile: - type: RuntimeDefault - capabilities: - drop: - - "ALL" - image: public.ecr.aws/docker/library/busybox:1.36 - command: ["sh", "-c", "tail -f /dev/null"] - volumeMounts: - - mountPath: /test1 - name: my-volume - volumes: - - name: my-volume - persistentVolumeClaim: - claimName: %s diff --git a/test/e2e/testdata/pvc_tests/pod-volume-device-template.yaml b/test/e2e/testdata/pvc_tests/pod-volume-device-template.yaml deleted file mode 100644 index 781a57d25..000000000 --- a/test/e2e/testdata/pvc_tests/pod-volume-device-template.yaml +++ /dev/null @@ -1,24 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: %s - namespace: %s -spec: - containers: - - name: pause - securityContext: - runAsNonRoot: true - seccompProfile: - type: RuntimeDefault - capabilities: - drop: - - "ALL" - image: public.ecr.aws/docker/library/busybox:1.36 - command: ["sh", "-c", "tail -f /dev/null"] - volumeDevices: - - devicePath: /dev/xda - name: my-volume - volumes: - - name: my-volume - persistentVolumeClaim: - claimName: %s diff --git a/test/e2e/testdata/pvc_tests/pod-volume-mount-template.yaml b/test/e2e/testdata/pvc_tests/pod-volume-mount-template.yaml deleted file mode 100644 index ce07b7b82..000000000 --- a/test/e2e/testdata/pvc_tests/pod-volume-mount-template.yaml +++ /dev/null @@ -1,24 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: %s - namespace: %s -spec: - containers: - - name: pause - securityContext: - runAsNonRoot: true - seccompProfile: - type: RuntimeDefault - capabilities: - drop: - - "ALL" - image: public.ecr.aws/docker/library/busybox:1.36 - command: ["sh", "-c", "tail -f /dev/null"] - volumeMounts: - - mountPath: /test1 - name: my-volume - volumes: - - name: my-volume - persistentVolumeClaim: - claimName: %s diff --git a/test/e2e/testdata/pvc_tests/pvc-clone-template.yaml b/test/e2e/testdata/pvc_tests/pvc-clone-template.yaml deleted file mode 100644 index e63116082..000000000 --- a/test/e2e/testdata/pvc_tests/pvc-clone-template.yaml +++ /dev/null @@ -1,16 +0,0 @@ -kind: PersistentVolumeClaim -apiVersion: v1 -metadata: - name: %s - namespace: %s -spec: - volumeMode: %s - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi - storageClassName: %s - dataSource: - name: %s - kind: PersistentVolumeClaim diff --git a/test/e2e/testdata/pvc_tests/pvc-snapshot-restore-template.yaml b/test/e2e/testdata/pvc_tests/pvc-snapshot-restore-template.yaml deleted file mode 100644 index 900c9d868..000000000 --- a/test/e2e/testdata/pvc_tests/pvc-snapshot-restore-template.yaml +++ /dev/null @@ -1,17 +0,0 @@ -kind: PersistentVolumeClaim -apiVersion: v1 -metadata: - name: %s - namespace: %s -spec: - accessModes: - - ReadWriteOnce - volumeMode: %s - resources: - requests: - storage: 1Gi - storageClassName: %s - dataSource: - name: %s - kind: VolumeSnapshot - apiGroup: snapshot.storage.k8s.io diff --git a/test/e2e/testdata/pvc_tests/pvc-template.yaml b/test/e2e/testdata/pvc_tests/pvc-template.yaml deleted file mode 100644 index b5a527732..000000000 --- a/test/e2e/testdata/pvc_tests/pvc-template.yaml +++ /dev/null @@ -1,13 +0,0 @@ -kind: PersistentVolumeClaim -apiVersion: v1 -metadata: - name: %s - namespace: %s -spec: - volumeMode: %s - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi - storageClassName: %s diff --git a/test/e2e/testdata/pvc_tests/volume-snapshot-template.yaml b/test/e2e/testdata/pvc_tests/volume-snapshot-template.yaml deleted file mode 100644 index 300f56eb3..000000000 --- a/test/e2e/testdata/pvc_tests/volume-snapshot-template.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: snapshot.storage.k8s.io/v1 -kind: VolumeSnapshot -metadata: - name: %s - namespace: %s -spec: - volumeSnapshotClassName: %s - source: - persistentVolumeClaimName: %s diff --git a/test/e2e/testdata/pvc_tests/volume-snapshot.yaml b/test/e2e/testdata/pvc_tests/volume-snapshot.yaml deleted file mode 100644 index 304e4cdde..000000000 --- a/test/e2e/testdata/pvc_tests/volume-snapshot.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: snapshot.storage.k8s.io/v1 -kind: VolumeSnapshot -metadata: - name: lvfilepvc-snapshot - namespace: lvm-endtoendtest -spec: - volumeSnapshotClassName: lvms-vg1 - source: - persistentVolumeClaimName: lvmfilepvc diff --git a/test/e2e/utils.go b/test/e2e/utils.go deleted file mode 100644 index 47601026a..000000000 --- a/test/e2e/utils.go +++ /dev/null @@ -1,49 +0,0 @@ -/* -Copyright © 2023 Red Hat, Inc. - -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 e2e - -import ( - snapapi "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1" - k8sv1 "k8s.io/api/core/v1" -) - -func getPVC(pvcYAML string) (*k8sv1.PersistentVolumeClaim, error) { - obj, _, err := deserializer.Decode([]byte(pvcYAML), nil, nil) - if err != nil { - return nil, err - } - pvc := obj.(*k8sv1.PersistentVolumeClaim) - return pvc, nil -} - -func getPod(podYAML string) (*k8sv1.Pod, error) { - obj, _, err := deserializer.Decode([]byte(podYAML), nil, nil) - if err != nil { - return nil, err - } - pod := obj.(*k8sv1.Pod) - return pod, nil -} - -func getVolumeSnapshot(volumeSnapshotYAML string) (*snapapi.VolumeSnapshot, error) { - obj, _, err := deserializer.Decode([]byte(volumeSnapshotYAML), nil, nil) - if err != nil { - return nil, err - } - volumeSnapshot := obj.(*snapapi.VolumeSnapshot) - return volumeSnapshot, nil -} diff --git a/test/e2e/validation.go b/test/e2e/validation_test.go similarity index 59% rename from test/e2e/validation.go rename to test/e2e/validation_test.go index 07fc07b86..b507d56fd 100644 --- a/test/e2e/validation.go +++ b/test/e2e/validation_test.go @@ -22,19 +22,23 @@ import ( "time" . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/meta" snapapi "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1" - . "github.com/onsi/gomega" - lvmv1alpha1 "github.com/openshift/lvm-operator/api/v1alpha1" + appsv1 "k8s.io/api/apps/v1" + k8sv1 "k8s.io/api/core/v1" storagev1 "k8s.io/api/storage/v1" - "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/openshift/lvm-operator/api/v1alpha1" ) const ( timeout = time.Minute * 2 - interval = time.Second * 3 + interval = time.Millisecond * 300 lvmVolumeGroupName = "vg1" storageClassName = "lvms-vg1" volumeSnapshotClassName = "lvms-vg1" @@ -44,16 +48,35 @@ const ( vgManagerDaemonsetName = "vg-manager" ) +func validateLVMCluster(ctx context.Context, cluster *v1alpha1.LVMCluster) bool { + GinkgoHelper() + checkClusterIsReady := func(ctx context.Context) error { + currentCluster := cluster + err := crClient.Get(ctx, client.ObjectKeyFromObject(cluster), currentCluster) + if err != nil { + return err + } + if currentCluster.Status.State == v1alpha1.LVMStatusReady { + return nil + } + return fmt.Errorf("cluster is not ready: %v", currentCluster.Status) + } + By("validating the LVMCluster") + return Eventually(checkClusterIsReady, timeout, interval).WithContext(ctx).Should(Succeed()) +} + // function to validate LVMVolume group. -func validateLVMvg(ctx context.Context) bool { +func validateLVMVolumeGroup(ctx context.Context) bool { + GinkgoHelper() By("validating the LVMVolumeGroup") return Eventually(func(ctx context.Context) error { - return crClient.Get(ctx, types.NamespacedName{Name: lvmVolumeGroupName, Namespace: installNamespace}, &lvmv1alpha1.LVMVolumeGroup{}) + return crClient.Get(ctx, types.NamespacedName{Name: lvmVolumeGroupName, Namespace: installNamespace}, &v1alpha1.LVMVolumeGroup{}) }, timeout, interval).WithContext(ctx).Should(Succeed()) } // function to validate storage class. func validateStorageClass(ctx context.Context) bool { + GinkgoHelper() By("validating the StorageClass") return Eventually(func() error { return crClient.Get(ctx, types.NamespacedName{Name: storageClassName, Namespace: installNamespace}, &storagev1.StorageClass{}) @@ -62,11 +85,12 @@ func validateStorageClass(ctx context.Context) bool { // function to validate volume snapshot class. func validateVolumeSnapshotClass(ctx context.Context) bool { + GinkgoHelper() By("validating the VolumeSnapshotClass") return Eventually(func(ctx context.Context) error { err := crClient.Get(ctx, types.NamespacedName{Name: volumeSnapshotClassName}, &snapapi.VolumeSnapshotClass{}) if meta.IsNoMatchError(err) { - By("VolumeSnapshotClass is ignored since VolumeSnapshotClasses are not supported in the given Cluster") + GinkgoLogr.Info("VolumeSnapshotClass is ignored since VolumeSnapshotClasses are not supported in the given Cluster") return nil } return err @@ -75,6 +99,7 @@ func validateVolumeSnapshotClass(ctx context.Context) bool { // function to validate CSI Driver. func validateCSIDriver(ctx context.Context) bool { + GinkgoHelper() By("validating the CSIDriver") return Eventually(func(ctx context.Context) error { return crClient.Get(ctx, types.NamespacedName{Name: csiDriverName, Namespace: installNamespace}, &storagev1.CSIDriver{}) @@ -89,12 +114,14 @@ func validateTopolvmNode(ctx context.Context) bool { // function to validate vg manager resource. func validateVGManager(ctx context.Context) bool { + GinkgoHelper() By("validating the vg-manager DaemonSet") return validateDaemonSet(ctx, types.NamespacedName{Name: vgManagerDaemonsetName, Namespace: installNamespace}) } // function to validate TopoLVM Deployment. func validateTopolvmController(ctx context.Context) bool { + GinkgoHelper() By("validating the TopoLVM controller deployment") name := types.NamespacedName{Name: topolvmCtrlDeploymentName, Namespace: installNamespace} return Eventually(func(ctx context.Context) error { @@ -111,6 +138,7 @@ func validateTopolvmController(ctx context.Context) bool { } func validateDaemonSet(ctx context.Context, name types.NamespacedName) bool { + GinkgoHelper() return Eventually(func(ctx context.Context) error { ds := &appsv1.DaemonSet{} if err := crClient.Get(ctx, name, ds); err != nil { @@ -124,30 +152,51 @@ func validateDaemonSet(ctx context.Context, name types.NamespacedName) bool { }, timeout, interval).WithContext(ctx).Should(Succeed()) } -// Validate all the resources created by LVMO. -func validateResources() { - Describe("Validate LVMCluster reconciliation", func() { - It("Should check that LVMO resources have been created", func(ctx SpecContext) { - By("Checking that CSIDriver has been created") - Expect(validateCSIDriver(ctx)).To(BeTrue()) - - By("Checking that the topolvm-controller deployment has been created") - Expect(validateTopolvmController(ctx)).To(BeTrue()) - - By("Checking that the vg-manager daemonset has been created") - Expect(validateVGManager(ctx)).To(BeTrue()) - - By("Checking that the LVMVolumeGroup has been created") - Expect(validateLVMvg(ctx)).To(BeTrue()) +func validatePVCIsBound(ctx context.Context, name types.NamespacedName) bool { + GinkgoHelper() + By(fmt.Sprintf("validating the PVC %q", name)) + return Eventually(func(ctx context.Context) error { + pvc := &k8sv1.PersistentVolumeClaim{} + if err := crClient.Get(ctx, name, pvc); err != nil { + return err + } + if pvc.Status.Phase != k8sv1.ClaimBound { + return fmt.Errorf("pvc is not bound yet: %s", pvc.Status.Phase) + } + return nil + }, timeout, interval).WithContext(ctx).Should(Succeed(), "pvc should be bound") +} - By("Checking that the topolvm-node daemonset has been created") - Expect(validateTopolvmNode(ctx)).To(BeTrue()) +func validatePodIsRunning(ctx context.Context, name types.NamespacedName) bool { + GinkgoHelper() + By(fmt.Sprintf("validating the Pod %q", name)) + return Eventually(func(ctx context.Context) bool { + pod := &k8sv1.Pod{} + err := crClient.Get(ctx, name, pod) + return err == nil && pod.Status.Phase == k8sv1.PodRunning + }, timeout, interval).WithContext(ctx).Should(BeTrue(), "pod should be running") +} - By("Checking that the StorageClass has been created") - Expect(validateStorageClass(ctx)).To(BeTrue()) +func validateSnapshotReadyToUse(ctx context.Context, name types.NamespacedName) bool { + GinkgoHelper() + By(fmt.Sprintf("validating the VolumeSnapshot %q", name)) + return Eventually(func(ctx context.Context) bool { + snapshot := &snapapi.VolumeSnapshot{} + err := crClient.Get(ctx, name, snapshot) + if err == nil && snapshot.Status != nil && snapshot.Status.ReadyToUse != nil { + return *snapshot.Status.ReadyToUse + } + return false + }, timeout, interval).WithContext(ctx).Should(BeTrue()) +} - By("Checking that the VolumeSnapshotClass has been created") - Expect(validateVolumeSnapshotClass(ctx)).To(BeTrue()) - }) - }) +func validatePodData(ctx context.Context, pod *k8sv1.Pod, expectedData string, contentMode ContentMode) bool { + var actualData string + By(fmt.Sprintf("validating the Data written in Pod %q", client.ObjectKeyFromObject(pod))) + Eventually(func(ctx context.Context) error { + var err error + actualData, err = contentTester.GetDataInPod(ctx, pod, contentMode) + return err + }).WithContext(ctx).Should(Succeed()) + return Expect(actualData).To(Equal(expectedData)) }