From e78aee99654d571bb2d5deb3be7d5935de396be1 Mon Sep 17 00:00:00 2001 From: Andi Li Date: Tue, 14 Jul 2020 22:29:07 +0000 Subject: [PATCH] Add pre provisioned snapshot tests Make pre provisioned snapshots using CSI driver by 1. Take a dynamic snapshot with retain policy 2. Delete the dynamic snapshot and content 3. Create a preprovisioned snapshot with snapshotHandle This commit adds a preprovisiond test pattern, all snapshots made using create snapshot resource become prepv snapshots. All exisitng test cases now run again with prepv snapshots. --- test/e2e/storage/external/external.go | 4 ++ test/e2e/storage/testpatterns/testpattern.go | 4 ++ test/e2e/storage/testsuites/base.go | 59 ++++++++++++++++ test/e2e/storage/testsuites/snapshottable.go | 73 ++++++++++++++++++-- 4 files changed, 133 insertions(+), 7 deletions(-) diff --git a/test/e2e/storage/external/external.go b/test/e2e/storage/external/external.go index b21a6f88094..ac0eb515a22 100644 --- a/test/e2e/storage/external/external.go +++ b/test/e2e/storage/external/external.go @@ -255,6 +255,10 @@ func (d *driverDefinition) SkipUnsupportedTest(pattern testpatterns.TestPattern) if d.SnapshotClass.FromName || d.SnapshotClass.FromFile != "" || d.SnapshotClass.FromExistingClassName != "" { supported = true } + case testpatterns.PreprovisionedCreatedSnapshot: + if d.SnapshotClass.FromName || d.SnapshotClass.FromFile != "" || d.SnapshotClass.FromExistingClassName != "" { + supported = true + } } if !supported { e2eskipper.Skipf("Driver %q does not support snapshot type %q - skipping", d.DriverInfo.Name, pattern.SnapshotType) diff --git a/test/e2e/storage/testpatterns/testpattern.go b/test/e2e/storage/testpatterns/testpattern.go index cd971b94fd7..f6057999e16 100644 --- a/test/e2e/storage/testpatterns/testpattern.go +++ b/test/e2e/storage/testpatterns/testpattern.go @@ -70,6 +70,10 @@ var ( RetainSnapshot TestSnapshotDeletionPolicy = "Retain" ) +func (t TestSnapshotDeletionPolicy) String() string { + return string(t) +} + // TestPattern represents a combination of parameters to be tested in a TestSuite type TestPattern struct { Name string // Name of TestPattern diff --git a/test/e2e/storage/testsuites/base.go b/test/e2e/storage/testsuites/base.go index 7519d0a0806..079d07a9b2d 100644 --- a/test/e2e/storage/testsuites/base.go +++ b/test/e2e/storage/testsuites/base.go @@ -18,6 +18,7 @@ package testsuites import ( "context" + "crypto/sha1" "flag" "fmt" "math" @@ -571,6 +572,64 @@ func getSnapshot(claimName string, ns, snapshotClassName string) *unstructured.U return snapshot } +func getPreProvisionedSnapshot(snapshotContentName, ns, snapshotHandle string) *unstructured.Unstructured { + snapshot := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "kind": "VolumeSnapshot", + "apiVersion": snapshotAPIVersion, + "metadata": map[string]interface{}{ + "name": getPreProvisionedSnapshotName(snapshotHandle), + "namespace": ns, + }, + "spec": map[string]interface{}{ + "source": map[string]interface{}{ + "volumeSnapshotContentName": snapshotContentName, + }, + }, + }, + } + + return snapshot +} +func getPreProvisionedSnapshotContent(snapshotName, snapshotNamespace, snapshotHandle, deletionPolicy, csiDriverName, volumeSnapshotClassName string) *unstructured.Unstructured { + snapshotContent := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "kind": "VolumeSnapshotContent", + "apiVersion": snapshotAPIVersion, + "metadata": map[string]interface{}{ + "name": getPreProvisionedSnapshotContentName(snapshotHandle), + }, + "spec": map[string]interface{}{ + "source": map[string]interface{}{ + "snapshotHandle": snapshotHandle, + }, + "volumeSnapshotRef": map[string]interface{}{ + "name": snapshotName, + "namespace": snapshotNamespace, + }, + "driver": csiDriverName, + "deletionPolicy": deletionPolicy, + "volumeSnapshotClassName": volumeSnapshotClassName, + }, + }, + } + + return snapshotContent +} + +func genShortHash(s string) string { + h := sha1.New() + h.Write([]byte(s)) + bs := h.Sum(nil) + return fmt.Sprintf("%x", bs)[:7] +} +func getPreProvisionedSnapshotContentName(snapshotHandle string) string { + return fmt.Sprintf("pre-provisioned-snapcontent-%s", genShortHash(snapshotHandle)) +} + +func getPreProvisionedSnapshotName(snapshotHandle string) string { + return fmt.Sprintf("pre-provisioned-snapshot-%s", genShortHash(snapshotHandle)) +} // StartPodLogs begins capturing log output and events from current // and future pods running in the namespace of the framework. That diff --git a/test/e2e/storage/testsuites/snapshottable.go b/test/e2e/storage/testsuites/snapshottable.go index 15f4584d59f..59cac10d035 100644 --- a/test/e2e/storage/testsuites/snapshottable.go +++ b/test/e2e/storage/testsuites/snapshottable.go @@ -75,6 +75,8 @@ func InitSnapshottableTestSuite() TestSuite { TestPatterns: []testpatterns.TestPattern{ testpatterns.DynamicSnapshotDelete, testpatterns.DynamicSnapshotRetain, + testpatterns.PreprovisionedSnapshotDelete, + testpatterns.PreprovisionedSnapshotRetain, }, SupportedSizeRange: e2evolume.SizeRange{ Min: "1Mi", @@ -94,7 +96,6 @@ func (s *snapshottableTestSuite) SkipRedundantSuite(driver TestDriver, pattern t func (s *snapshottableTestSuite) DefineTests(driver TestDriver, pattern testpatterns.TestPattern) { ginkgo.BeforeEach(func() { // Check preconditions. - framework.ExpectEqual(pattern.SnapshotType, testpatterns.DynamicCreatedSnapshot) dInfo := driver.GetDriverInfo() ok := false sDriver, ok = driver.(SnapshottableTestDriver) @@ -355,6 +356,10 @@ type SnapshotResource struct { // different test pattern snapshot provisioning and deletion policy func CreateSnapshotResource(sDriver SnapshottableTestDriver, config *PerTestConfig, pattern testpatterns.TestPattern, pvcName string, pvcNamespace string) *SnapshotResource { var err error + if pattern.SnapshotType != testpatterns.DynamicCreatedSnapshot || pattern.SnapshotType != testpatterns.PreprovisionedCreatedSnapshot { + err = fmt.Errorf("SnapshotType must be set to either DynamicCreatedSnapshot or PreprovisionedCreatedSnapshot") + framework.ExpectNoError(err) + } r := SnapshotResource{ Config: config, Pattern: pattern, @@ -366,7 +371,7 @@ func CreateSnapshotResource(sDriver SnapshottableTestDriver, config *PerTestConf if r.Vsclass == nil { framework.Failf("Failed to get snapshot class based on test config") } - r.Vsclass.Object["deletionPolicy"] = pattern.SnapshotDeletionPolicy + r.Vsclass.Object["deletionPolicy"] = pattern.SnapshotDeletionPolicy.String() r.Vsclass, err = dc.Resource(SnapshotClassGVR).Create(context.TODO(), r.Vsclass, metav1.CreateOptions{}) framework.ExpectNoError(err) @@ -402,14 +407,68 @@ func CreateSnapshotResource(sDriver SnapshottableTestDriver, config *PerTestConf // to pre-provision the snapshot as we normally would using their API. // We instead dynamically take a snapshot and create another snapshot using // the first snapshot's snapshot handle. - ginkgo.Skip("Preprovisioned test not implemented") - ginkgo.By("taking a snapshot with deletion policy retain") - ginkgo.By("recording the volume handle and status.snapshotHandle") + ginkgo.By("taking a dynamic snapshot") + r.Vs = getSnapshot(pvcName, pvcNamespace, r.Vsclass.GetName()) + r.Vs, err = dc.Resource(SnapshotGVR).Namespace(r.Vs.GetNamespace()).Create(context.TODO(), r.Vs, metav1.CreateOptions{}) + framework.ExpectNoError(err) + + err = WaitForSnapshotReady(dc, r.Vs.GetNamespace(), r.Vs.GetName(), framework.Poll, framework.SnapshotCreateTimeout) + framework.ExpectNoError(err) + + r.Vs, err = dc.Resource(SnapshotGVR).Namespace(r.Vs.GetNamespace()).Get(context.TODO(), r.Vs.GetName(), metav1.GetOptions{}) + + snapshotStatus := r.Vs.Object["status"].(map[string]interface{}) + snapshotContentName := snapshotStatus["boundVolumeSnapshotContentName"].(string) + framework.Logf("received snapshotStatus %v", snapshotStatus) + framework.Logf("snapshotContentName %s", snapshotContentName) + framework.ExpectNoError(err) + + ginkgo.By("updating the snapshot content deletion policy to retain") + r.Vscontent, err = dc.Resource(SnapshotContentGVR).Get(context.TODO(), snapshotContentName, metav1.GetOptions{}) + framework.ExpectNoError(err) + r.Vscontent.Object["spec"].(map[string]interface{})["deletionPolicy"] = "Retain" + + r.Vscontent, err = dc.Resource(SnapshotContentGVR).Update(context.TODO(), r.Vscontent, metav1.UpdateOptions{}) + framework.ExpectNoError(err) + + ginkgo.By("recording the volume handle and snapshotHandle") + snapshotHandle := r.Vscontent.Object["status"].(map[string]interface{})["snapshotHandle"].(string) + framework.Logf("Recording snapshot handle: %s", snapshotHandle) + csiDriverName := r.Vsclass.Object["driver"].(string) + ginkgo.By("deleting the snapshot and snapshot content") // TODO: test what happens when I have two snapshot content that refer to the same content + err = dc.Resource(SnapshotGVR).Namespace(r.Vs.GetNamespace()).Delete(context.TODO(), r.Vs.GetName(), metav1.DeleteOptions{}) + framework.ExpectNoError(err) + + ginkgo.By("checking the Snapshot has been deleted") + err = utils.WaitForNamespacedGVRDeletion(dc, SnapshotGVR, r.Vs.GetName(), r.Vs.GetNamespace(), framework.Poll, framework.SnapshotDeleteTimeout) + framework.ExpectNoError(err) + + err = dc.Resource(SnapshotContentGVR).Delete(context.TODO(), r.Vscontent.GetName(), metav1.DeleteOptions{}) + framework.ExpectNoError(err) + + ginkgo.By("checking the Snapshot content has been deleted") + err = utils.WaitForGVRDeletion(dc, SnapshotContentGVR, r.Vscontent.GetName(), framework.Poll, framework.SnapshotDeleteTimeout) + framework.ExpectNoError(err) + ginkgo.By("creating a snapshot content with the snapshot handle") + r.Vscontent = getPreProvisionedSnapshotContent(getPreProvisionedSnapshotName(snapshotHandle), pvcNamespace, snapshotHandle, pattern.SnapshotDeletionPolicy.String(), csiDriverName, r.Vsclass.GetName()) + r.Vscontent, err = dc.Resource(SnapshotContentGVR).Create(context.TODO(), r.Vscontent, metav1.CreateOptions{}) + framework.ExpectNoError(err) + ginkgo.By("creating a snapshot with that snapshot content") - default: - err = fmt.Errorf("SnapshotType must be set to either DynamicCreatedSnapshot or PreprovisionedCreatedSnapshot") + r.Vs = getPreProvisionedSnapshot(getPreProvisionedSnapshotContentName(snapshotHandle), pvcNamespace, snapshotHandle) + r.Vs, err = dc.Resource(SnapshotGVR).Namespace(r.Vs.GetNamespace()).Create(context.TODO(), r.Vs, metav1.CreateOptions{}) + framework.ExpectNoError(err) + + err = WaitForSnapshotReady(dc, r.Vs.GetNamespace(), r.Vs.GetName(), framework.Poll, framework.SnapshotCreateTimeout) + framework.ExpectNoError(err) + + ginkgo.By("getting the snapshot and snapshot content") + r.Vs, err = dc.Resource(SnapshotGVR).Namespace(r.Vs.GetNamespace()).Get(context.TODO(), r.Vs.GetName(), metav1.GetOptions{}) + framework.ExpectNoError(err) + + r.Vscontent, err = dc.Resource(SnapshotContentGVR).Get(context.TODO(), r.Vscontent.GetName(), metav1.GetOptions{}) framework.ExpectNoError(err) } return &r