Skip to content

Commit 1996d79

Browse files
committed
util & test added for restore snapshot
1 parent 9eefea3 commit 1996d79

File tree

8 files changed

+596
-0
lines changed

8 files changed

+596
-0
lines changed

tests/e2e/constants/kubernetes.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ const (
138138
StorageQuotaWebhookPrefix = "storage-quota-webhook"
139139
DevopsKubeConf = "DEV_OPS_USER_KUBECONFIG"
140140
QuotaSupportedVCVersion = "9.0.0"
141+
Latebinding = "-latebinding"
141142
)
142143

143144
// For busybox pod image

tests/e2e/csisnapshot/util.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/onsi/ginkgo/v2"
2828
"github.com/onsi/gomega"
2929
v1 "k8s.io/api/core/v1"
30+
storagev1 "k8s.io/api/storage/v1"
3031
apierrors "k8s.io/apimachinery/pkg/api/errors"
3132
"k8s.io/apimachinery/pkg/api/resource"
3233
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -389,3 +390,79 @@ func WaitForVolumeSnapshotContentToBeDeleted(client snapclient.Clientset, ctx co
389390
})
390391
return waitErr
391392
}
393+
394+
// getPersistentVolumeClaimSpecWithDatasource return the PersistentVolumeClaim
395+
// spec with specified storage class.
396+
func GetPersistentVolumeClaimSpecWithDatasource(namespace string, ds string, storageclass *storagev1.StorageClass,
397+
pvclaimlabels map[string]string, accessMode v1.PersistentVolumeAccessMode,
398+
datasourceName string, snapshotapigroup string) *v1.PersistentVolumeClaim {
399+
disksize := constants.DiskSize
400+
if ds != "" {
401+
disksize = ds
402+
}
403+
if accessMode == "" {
404+
// If accessMode is not specified, set the default accessMode.
405+
accessMode = v1.ReadWriteOnce
406+
}
407+
claim := &v1.PersistentVolumeClaim{
408+
ObjectMeta: metav1.ObjectMeta{
409+
GenerateName: "pvc-",
410+
Namespace: namespace,
411+
},
412+
Spec: v1.PersistentVolumeClaimSpec{
413+
AccessModes: []v1.PersistentVolumeAccessMode{
414+
accessMode,
415+
},
416+
Resources: v1.VolumeResourceRequirements{
417+
Requests: v1.ResourceList{
418+
v1.ResourceName(v1.ResourceStorage): resource.MustParse(disksize),
419+
},
420+
},
421+
StorageClassName: &(storageclass.Name),
422+
DataSource: &v1.TypedLocalObjectReference{
423+
APIGroup: &snapshotapigroup,
424+
Kind: "VolumeSnapshot",
425+
Name: datasourceName,
426+
},
427+
},
428+
}
429+
430+
if pvclaimlabels != nil {
431+
claim.Labels = pvclaimlabels
432+
}
433+
434+
return claim
435+
}
436+
437+
// DeleteVolumeSnapshotWithPollWait request deletion of Volume Snapshot and waits until it is deleted
438+
func DeleteVolumeSnapshotWithPollWait(ctx context.Context, snapc *snapclient.Clientset,
439+
namespace string, name string) {
440+
441+
err := snapc.SnapshotV1().VolumeSnapshots(namespace).Delete(ctx, name, metav1.DeleteOptions{})
442+
if !apierrors.IsNotFound(err) {
443+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
444+
}
445+
446+
err = WaitForVolumeSnapshotToBeDeleted(ctx, snapc, namespace, name)
447+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
448+
}
449+
450+
// WaitForVolumeSnapshotToBeDeleted wait till the volume snapshot is deleted
451+
func WaitForVolumeSnapshotToBeDeleted(ctx context.Context, client *snapclient.Clientset,
452+
namespace string, name string) error {
453+
454+
waitErr := wait.PollUntilContextTimeout(ctx, constants.Poll, 2*constants.PollTimeout, true,
455+
func(ctx context.Context) (bool, error) {
456+
_, err := client.SnapshotV1().VolumeSnapshots(namespace).Get(ctx, name, metav1.GetOptions{})
457+
if err != nil {
458+
if apierrors.IsNotFound(err) {
459+
framework.Logf("VolumeSnapshot: %s is deleted", name)
460+
return true, nil
461+
} else {
462+
return false, fmt.Errorf("error fetching volumesnapshot details : %v", err)
463+
}
464+
}
465+
return false, nil
466+
})
467+
return waitErr
468+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package restore_snapshot
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/onsi/ginkgo/v2"
8+
"github.com/onsi/gomega"
9+
"k8s.io/kubernetes/test/e2e/framework"
10+
fpv "k8s.io/kubernetes/test/e2e/framework/pv"
11+
admissionapi "k8s.io/pod-security-admission/api"
12+
"sigs.k8s.io/vsphere-csi-driver/v3/tests/e2e/constants"
13+
"sigs.k8s.io/vsphere-csi-driver/v3/tests/e2e/csisnapshot"
14+
"sigs.k8s.io/vsphere-csi-driver/v3/tests/e2e/vcutil"
15+
)
16+
17+
var _ bool = ginkgo.Describe("[restore-snapshot-other-ds] restore Snapshot on different Datastore-Basic", func() {
18+
19+
f := framework.NewDefaultFramework("restore-snapshot")
20+
f.NamespacePodSecurityEnforceLevel = admissionapi.LevelPrivileged
21+
var (
22+
preSetupData *PreSetupTest
23+
)
24+
ginkgo.Context("Snapshot restore on diferent datastores", func() {
25+
sharedDatastoreType := "VSAN"
26+
// Generate entries dynamically from the JSON file at test construction time.
27+
var entries []ginkgo.TableEntry
28+
testCases, _ := LoadRestoreMatrix(sharedDatastoreType)
29+
for _, tc := range testCases {
30+
entries = append(entries, ginkgo.Entry(fmt.Sprintf("%s → %v", tc.SourceSC, tc.TargetSCs), tc))
31+
}
32+
33+
ginkgo.DescribeTableSubtree("Restore-Snapshot-On-Different-Datastore-Basic-Test",
34+
func(tc RestoreMatrixEntry) {
35+
36+
ginkgo.BeforeEach(func() {
37+
ginkgo.By("In BeforeEach")
38+
preSetupData = PreSetup(f, tc.SourceSC)
39+
})
40+
41+
ginkgo.AfterEach(func() {
42+
ctx, cancel := context.WithCancel(context.Background())
43+
defer cancel()
44+
ginkgo.By("Cleaning up preSetup PVC,Snapshot,SnapshotClass")
45+
if preSetupData != nil && preSetupData.VolumeSnapshot != nil {
46+
csisnapshot.DeleteVolumeSnapshotWithPandoraWait(ctx, preSetupData.SnapC, namespace, volumeSnapshot.Name, pandoraSyncWaitTime)
47+
}
48+
if preSetupData != nil && preSetupData.PVC != nil {
49+
err := fpv.DeletePersistentVolumeClaim(ctx, client, preSetupData.PVC.Name, namespace)
50+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
51+
err = vcutil.WaitForCNSVolumeToBeDeleted(e2eTestConfig, preSetupData.VolHandle)
52+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
53+
}
54+
55+
})
56+
57+
/*
58+
CreatePvcFromSnapshotOnTargetDs
59+
Steps:
60+
1. Create a PVC on a SC having a datastore say ds1 in storage compatible list.
61+
2. Create a snapshot from the volume
62+
3. Create a PVC from a snapshot by passing a storage class such that it has a common host as of datastore where
63+
the snapshot is created
64+
4. Verify the PVC gets created in the datastore passed in step#3
65+
5. Run Volume usability test (attach - detach - relocate - data integrity)
66+
6. Run cleanup.
67+
*/
68+
ginkgo.It("[csi-supervisor] Restore snapshot on a different datastore", ginkgo.Label(constants.P0,
69+
constants.VmServiceVm, constants.Block, constants.Wcp, constants.Vc901), func() {
70+
ctx, cancel := context.WithCancel(context.Background())
71+
defer cancel()
72+
73+
ginkgo.By(fmt.Sprintf("Restoring PVC from snapshot: %s → %s", tc.SourceSC, tc.TargetSCs))
74+
VerifyVolumeRestoreOperationOnDifferentDatastore(ctx, e2eTestConfig, client, preSetupData.Namespace,
75+
tc.TargetSCs, preSetupData.VolumeSnapshot, constants.DiskSize, true)
76+
77+
})
78+
79+
},
80+
entries,
81+
)
82+
})
83+
84+
})
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package restore_snapshot
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"os"
7+
"strconv"
8+
"strings"
9+
"time"
10+
11+
snapV1 "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1"
12+
snapclient "github.com/kubernetes-csi/external-snapshotter/client/v8/clientset/versioned"
13+
"github.com/onsi/ginkgo/v2"
14+
"github.com/onsi/gomega"
15+
v1 "k8s.io/api/core/v1"
16+
storagev1 "k8s.io/api/storage/v1"
17+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
18+
clientset "k8s.io/client-go/kubernetes"
19+
"k8s.io/kubernetes/test/e2e/framework"
20+
fpv "k8s.io/kubernetes/test/e2e/framework/pv"
21+
"sigs.k8s.io/vsphere-csi-driver/v3/tests/e2e/bootstrap"
22+
"sigs.k8s.io/vsphere-csi-driver/v3/tests/e2e/config"
23+
"sigs.k8s.io/vsphere-csi-driver/v3/tests/e2e/constants"
24+
"sigs.k8s.io/vsphere-csi-driver/v3/tests/e2e/csisnapshot"
25+
"sigs.k8s.io/vsphere-csi-driver/v3/tests/e2e/k8testutil"
26+
"sigs.k8s.io/vsphere-csi-driver/v3/tests/e2e/vcutil"
27+
)
28+
29+
var (
30+
ctx context.Context
31+
cancel context.CancelFunc
32+
e2eTestConfig *config.E2eTestConfig
33+
client clientset.Interface
34+
volumeSnapshot *snapV1.VolumeSnapshot
35+
namespace string
36+
snapc *snapclient.Clientset
37+
pandoraSyncWaitTime int
38+
)
39+
40+
type PreSetupTest struct {
41+
Namespace string
42+
StorageClass *storagev1.StorageClass
43+
PVC *v1.PersistentVolumeClaim
44+
VolumeSnapshot *snapV1.VolumeSnapshot
45+
VolHandle string
46+
SnapC *snapclient.Clientset
47+
}
48+
49+
func PreSetup(f *framework.Framework, storagePolicyName string) *PreSetupTest {
50+
ctx, cancel = context.WithCancel(context.Background())
51+
defer cancel()
52+
client = f.ClientSet
53+
e2eTestConfig = bootstrap.Bootstrap()
54+
namespace = vcutil.GetNamespaceToRunTests(f, e2eTestConfig)
55+
56+
scParameters := make(map[string]string)
57+
storagePolicyName = strings.ToLower(strings.ReplaceAll(storagePolicyName, " ", "-"))
58+
profileID := vcutil.GetSpbmPolicyID(storagePolicyName, e2eTestConfig)
59+
scParameters[constants.ScParamStoragePolicyID] = profileID
60+
61+
labelsMap := map[string]string{"app": "test"}
62+
63+
ginkgo.By("Create storage class")
64+
storageclass, err := k8testutil.CreateStorageClass(client, e2eTestConfig, scParameters, nil, "", "", false, storagePolicyName)
65+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
66+
defer func() {
67+
ginkgo.By("Delete Storage Class")
68+
err = client.StorageV1().StorageClasses().Delete(ctx, storageclass.Name, *metav1.NewDeleteOptions(0))
69+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
70+
}()
71+
72+
ginkgo.By("Create PVC")
73+
pvclaim, persistentVolumes, err := k8testutil.CreatePVCAndQueryVolumeInCNS(ctx, client, e2eTestConfig, namespace, labelsMap, "", constants.DiskSize, storageclass, true)
74+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
75+
76+
volHandle := persistentVolumes[0].Spec.CSI.VolumeHandle
77+
if e2eTestConfig.TestInput.ClusterFlavor.GuestCluster {
78+
volHandle = k8testutil.GetVolumeIDFromSupervisorCluster(volHandle)
79+
}
80+
gomega.Expect(volHandle).NotTo(gomega.BeEmpty())
81+
ginkgo.DeferCleanup(func() {
82+
cleanupCtx, cleanupCancel := context.WithTimeout(context.Background(), time.Minute)
83+
defer cleanupCancel()
84+
err := fpv.DeletePersistentVolumeClaim(cleanupCtx, client, pvclaim.Name, namespace)
85+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
86+
err = vcutil.WaitForCNSVolumeToBeDeleted(e2eTestConfig, volHandle)
87+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
88+
})
89+
// reading fullsync wait time
90+
if os.Getenv(constants.EnvPandoraSyncWaitTime) != "" {
91+
pandoraSyncWaitTime, err = strconv.Atoi(os.Getenv(constants.EnvPandoraSyncWaitTime))
92+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
93+
} else {
94+
pandoraSyncWaitTime = constants.DefaultPandoraSyncWaitTime
95+
}
96+
var snapc *snapclient.Clientset
97+
restConfig := k8testutil.GetRestConfigClient(e2eTestConfig)
98+
snapc, err = snapclient.NewForConfig(restConfig)
99+
100+
ginkgo.By("Create volume snapshot class")
101+
volumeSnapshotClass, err := csisnapshot.CreateVolumeSnapshotClass(ctx, e2eTestConfig, snapc, constants.DeletionPolicy)
102+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
103+
104+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
105+
ginkgo.By("Create a dynamic volume snapshot")
106+
volumeSnapshot, _, _, _, _, _, err = csisnapshot.CreateDynamicVolumeSnapshot(
107+
ctx, e2eTestConfig, namespace, snapc, volumeSnapshotClass, pvclaim, volHandle, constants.DiskSize, true)
108+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
109+
110+
return &PreSetupTest{
111+
Namespace: namespace,
112+
StorageClass: storageclass,
113+
PVC: pvclaim,
114+
VolumeSnapshot: volumeSnapshot,
115+
VolHandle: volHandle,
116+
SnapC: snapc,
117+
}
118+
}
119+
120+
type RestoreMatrixEntry struct {
121+
SourceSC string `json:"sourceSC"`
122+
TargetSCs []string `json:"targetSCs"`
123+
}
124+
125+
func LoadRestoreMatrix(sharedDatastoreType string) ([]RestoreMatrixEntry, error) {
126+
data, err := os.ReadFile("restoreMatrix-OSA.json")
127+
if sharedDatastoreType == "VSAN2" {
128+
data, err = os.ReadFile("restoreMatrix-ESA.json")
129+
if err != nil {
130+
return nil, err
131+
}
132+
}
133+
134+
var matrix []RestoreMatrixEntry
135+
err = json.Unmarshal(data, &matrix)
136+
return matrix, err
137+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[
2+
{
3+
"sourceSC": "vsan2-thin-policy",
4+
"targetSCs": ["vmfs-thin-policy", "vmfs-ezt-policy", "vmfs-lzt-policy", "nfs-policy"]
5+
},
6+
{
7+
"sourceSC": "vsan2-thick-policy",
8+
"targetSCs": ["vmfs-thin-policy", "vmfs-ezt-policy", "vmfs-lzt-policy", "nfs-policy"]
9+
},
10+
{
11+
"sourceSC": "vsan2-50-res-policy",
12+
"targetSCs": ["vmfs-thin-policy", "vmfs-ezt-policy", "vmfs-lzt-policy", "nfs-policy"]
13+
},
14+
{
15+
"sourceSC": "vmfs-thin-policy",
16+
"targetSCs": ["vsan2-thin-policy", "vsan2-thick-policy", "vsan2-50-res-policy", "nfs-policy"]
17+
},
18+
{
19+
"sourceSC": "vmfs-ezt-policy",
20+
"targetSCs": ["vsan2-thin-policy", "vsan2-thick-policy", "vsan2-50-res-policy", "nfs-policy"]
21+
},
22+
{
23+
"sourceSC": "vmfs-lzt-policy",
24+
"targetSCs": ["vsan2-thin-policy", "vsan2-thick-policy", "vsan2-50-res-policy", "nfs-policy"]
25+
},
26+
{
27+
"sourceSC": "nfs-policy",
28+
"targetSCs": ["vsan2-thin-policy", "vsan2-thick-policy", "vsan2-50-res-policy", "vmfs-thin-policy", "vmfs-ezt-policy", "vmfs-lzt-policy"]
29+
}
30+
]
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[
2+
{
3+
"sourceSC": "vsan-thin-policy",
4+
"targetSCs": ["vmfs-thin-policy", "vmfs-ezt-policy", "vmfs-lzt-policy", "nfs-policy"]
5+
},
6+
{
7+
"sourceSC": "vsan-thick-policy",
8+
"targetSCs": ["vmfs-thin-policy", "vmfs-ezt-policy", "vmfs-lzt-policy", "nfs-policy"]
9+
},
10+
{
11+
"sourceSC": "vsan-50-res-policy",
12+
"targetSCs": ["vmfs-thin-policy", "vmfs-ezt-policy", "vmfs-lzt-policy", "nfs-policy"]
13+
},
14+
{
15+
"sourceSC": "vmfs-thin-policy",
16+
"targetSCs": ["vsan-thin-policy", "vsan-thick-policy", "vsan-50-res-policy", "nfs-policy"]
17+
},
18+
{
19+
"sourceSC": "vmfs-ezt-policy",
20+
"targetSCs": ["vsan-thin-policy", "vsan-thick-policy", "vsan-50-res-policy", "nfs-policy"]
21+
},
22+
{
23+
"sourceSC": "vmfs-lzt-policy",
24+
"targetSCs": ["vsan-thin-policy", "vsan-thick-policy", "vsan-50-res-policy", "nfs-policy"]
25+
},
26+
{
27+
"sourceSC": "nfs-policy",
28+
"targetSCs": ["vsan-thin-policy", "vsan-thick-policy", "vsan-50-res-policy", "vmfs-thin-policy", "vmfs-ezt-policy", "vmfs-lzt-policy"]
29+
}
30+
]

0 commit comments

Comments
 (0)