Skip to content

Commit 48eb56a

Browse files
RWX VMService VM testcases and utils automation
1 parent 61f0a34 commit 48eb56a

15 files changed

+2911
-3
lines changed

nimbus_pwd.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
VBLQ3qd2__JtEqoz

tests/e2e/constants/env_constants.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ const (
160160
EnsureAccessibilityMModeType = "ensureObjectAccessibility"
161161
EnvClusterFlavor = "CLUSTER_FLAVOR"
162162
EnvDiskSizeLarge = "LARGE_DISK_SIZE"
163+
EnvClusterName = "CLUSTER_NAME"
163164
EnvCSINamespace = "CSI_NAMESPACE"
164165
EnvContentLibraryUrl = "CONTENT_LIB_URL"
165166
EnvContentLibraryUrlSslThumbprint = "CONTENT_LIB_THUMBPRINT"

tests/e2e/constants/test_suite_level.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ const (
8181
Vc90 = "vc90"
8282
Vc80 = "vc80"
8383
Vc70 = "vc70"
84+
Vc901 = "vc901"
8485
Wldi = "wldi"
8586
VmServiceVm = "vmServiceVm"
8687
VcptocsiTest = "vcptocsiTest"

tests/e2e/constants/vsphere_constants.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const (
4242
EztAllocType = "Fully initialized"
4343
LztAllocType = "Reserve space"
4444
Nfs4FSType = "nfs4"
45+
Nfs4Keyword = "NFSv4.1"
4546
ObjOrItemNotFoundErr = "The object or item referred to could not be found"
4647
ProviderPrefix = "vsphere://"
4748
)

tests/e2e/csisnapshot/util.go

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package csisnapshot
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"strings"
23+
"time"
24+
25+
snapV1 "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1"
26+
snapclient "github.com/kubernetes-csi/external-snapshotter/client/v8/clientset/versioned"
27+
"github.com/onsi/ginkgo/v2"
28+
v1 "k8s.io/api/core/v1"
29+
"k8s.io/apimachinery/pkg/api/resource"
30+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31+
"k8s.io/apimachinery/pkg/util/wait"
32+
"k8s.io/client-go/tools/clientcmd"
33+
"k8s.io/kubernetes/test/e2e/framework"
34+
"sigs.k8s.io/vsphere-csi-driver/v3/tests/e2e/config"
35+
"sigs.k8s.io/vsphere-csi-driver/v3/tests/e2e/constants"
36+
"sigs.k8s.io/vsphere-csi-driver/v3/tests/e2e/env"
37+
"sigs.k8s.io/vsphere-csi-driver/v3/tests/e2e/vcutil"
38+
)
39+
40+
// waitForVolumeSnapshotContentReadyToUse waits for the volume's snapshot content to be in ReadyToUse
41+
func WaitForVolumeSnapshotContentReadyToUse(client snapclient.Clientset, ctx context.Context,
42+
name string) (*snapV1.VolumeSnapshotContent, error) {
43+
var volumeSnapshotContent *snapV1.VolumeSnapshotContent
44+
var err error
45+
46+
waitErr := wait.PollUntilContextTimeout(ctx, constants.Poll, constants.PollTimeout*2, true,
47+
func(ctx context.Context) (bool, error) {
48+
volumeSnapshotContent, err = client.SnapshotV1().VolumeSnapshotContents().Get(ctx, name, metav1.GetOptions{})
49+
framework.Logf("volumesnapshotcontent details: %v", volumeSnapshotContent)
50+
if err != nil {
51+
return false, fmt.Errorf("error fetching volumesnapshotcontent details : %v", err)
52+
}
53+
if volumeSnapshotContent.Status != nil && *volumeSnapshotContent.Status.ReadyToUse {
54+
framework.Logf("%s volume snapshotContent is in ready state", name)
55+
return true, nil
56+
}
57+
return false, nil
58+
})
59+
return volumeSnapshotContent, waitErr
60+
}
61+
62+
// waitForCNSSnapshotToBeCreated wait till the give snapshot is created in CNS
63+
func WaitForCNSSnapshotToBeCreated(vs *config.E2eTestConfig, volumeId string, snapshotId string) error {
64+
var err error
65+
waitErr := wait.PollUntilContextTimeout(context.Background(), constants.Poll, constants.PollTimeout*2, true,
66+
func(ctx context.Context) (bool, error) {
67+
err = vcutil.VerifySnapshotIsCreatedInCNS(vs, volumeId, snapshotId)
68+
if err != nil {
69+
if strings.Contains(err.Error(), "snapshot entry is not present in CNS") {
70+
return false, nil
71+
}
72+
return false, err
73+
}
74+
framework.Logf("Snapshot with ID: %v for volume with ID: %v is created in CNS now...", snapshotId, volumeId)
75+
return true, nil
76+
})
77+
return waitErr
78+
}
79+
80+
// getVolumeSnapshotSpec returns a spec for the volume snapshot
81+
func GetVolumeSnapshotSpec(namespace string, snapshotclassname string, pvcName string) *snapV1.VolumeSnapshot {
82+
var volumesnapshotSpec = &snapV1.VolumeSnapshot{
83+
TypeMeta: metav1.TypeMeta{
84+
Kind: "VolumeSnapshot",
85+
},
86+
ObjectMeta: metav1.ObjectMeta{
87+
GenerateName: "snapshot-",
88+
Namespace: namespace,
89+
},
90+
Spec: snapV1.VolumeSnapshotSpec{
91+
VolumeSnapshotClassName: &snapshotclassname,
92+
Source: snapV1.VolumeSnapshotSource{
93+
PersistentVolumeClaimName: &pvcName,
94+
},
95+
},
96+
}
97+
return volumesnapshotSpec
98+
}
99+
100+
// waitForVolumeSnapshotReadyToUse waits for the volume's snapshot to be in ReadyToUse
101+
func WaitForVolumeSnapshotReadyToUse(client snapclient.Clientset, ctx context.Context, namespace string,
102+
name string) (*snapV1.VolumeSnapshot, error) {
103+
var volumeSnapshot *snapV1.VolumeSnapshot
104+
var err error
105+
waitErr := wait.PollUntilContextTimeout(ctx, constants.Poll, constants.PollTimeout*2, true,
106+
func(ctx context.Context) (bool, error) {
107+
volumeSnapshot, err = client.SnapshotV1().VolumeSnapshots(namespace).Get(ctx, name, metav1.GetOptions{})
108+
if err != nil {
109+
return false, fmt.Errorf("error fetching volumesnapshot details : %v", err)
110+
}
111+
if volumeSnapshot.Status != nil && *volumeSnapshot.Status.ReadyToUse {
112+
return true, nil
113+
}
114+
return false, nil
115+
})
116+
return volumeSnapshot, waitErr
117+
}
118+
119+
// getVolumeSnapshotIdFromSnapshotHandle fetches VolumeSnapshotId From SnapshotHandle
120+
func GetVolumeSnapshotIdFromSnapshotHandle(ctx context.Context, vs *config.E2eTestConfig,
121+
snapshotContent *snapV1.VolumeSnapshotContent) (string, string, error) {
122+
var snapshotID string
123+
var snapshotHandle string
124+
var err error
125+
126+
if vs.TestInput.ClusterFlavor.VanillaCluster || vs.TestInput.ClusterFlavor.SupervisorCluster {
127+
snapshotHandle = *snapshotContent.Status.SnapshotHandle
128+
snapshotID = strings.Split(snapshotHandle, "+")[1]
129+
} else if vs.TestInput.ClusterFlavor.GuestCluster {
130+
snapshotHandle = *snapshotContent.Status.SnapshotHandle
131+
snapshotID, _, _, err = GetSnapshotHandleFromSupervisorCluster(ctx, snapshotHandle)
132+
if err != nil {
133+
return snapshotID, snapshotHandle, err
134+
}
135+
}
136+
return snapshotID, snapshotHandle, nil
137+
}
138+
139+
// getSnapshotHandleFromSupervisorCluster fetches the SnapshotHandle from Supervisor Cluster
140+
func GetSnapshotHandleFromSupervisorCluster(ctx context.Context,
141+
snapshothandle string) (string, string, string, error) {
142+
var snapc *snapclient.Clientset
143+
var err error
144+
if k8senv := env.GetAndExpectStringEnvVar("SUPERVISOR_CLUSTER_KUBE_CONFIG"); k8senv != "" {
145+
restConfig, err := clientcmd.BuildConfigFromFlags("", k8senv)
146+
if err != nil {
147+
return "", "", "", err
148+
}
149+
snapc, err = snapclient.NewForConfig(restConfig)
150+
if err != nil {
151+
return "", "", "", err
152+
}
153+
}
154+
155+
svNamespace := env.GetAndExpectStringEnvVar(constants.EnvSupervisorClusterNamespace)
156+
157+
volumeSnapshot, err := snapc.SnapshotV1().VolumeSnapshots(svNamespace).Get(ctx, snapshothandle,
158+
metav1.GetOptions{})
159+
if err != nil {
160+
return "", "", "", err
161+
}
162+
163+
snapshotContent, err := snapc.SnapshotV1().VolumeSnapshotContents().Get(ctx,
164+
*volumeSnapshot.Status.BoundVolumeSnapshotContentName, metav1.GetOptions{})
165+
if err != nil {
166+
return "", "", "", err
167+
}
168+
169+
svcSnapshotHandle := *snapshotContent.Status.SnapshotHandle
170+
snapshotID := strings.Split(svcSnapshotHandle, "+")[1]
171+
172+
svcVolumeSnapshotName := volumeSnapshot.Name
173+
174+
return snapshotID, svcSnapshotHandle, svcVolumeSnapshotName, nil
175+
}
176+
177+
// createDynamicVolumeSnapshot util creates dynamic volume snapshot for a volume
178+
func CreateDynamicVolumeSnapshot(ctx context.Context, vs *config.E2eTestConfig, namespace string,
179+
snapc *snapclient.Clientset, volumeSnapshotClass *snapV1.VolumeSnapshotClass,
180+
pvclaim *v1.PersistentVolumeClaim, volHandle string, diskSize string,
181+
performCnsQueryVolumeSnapshot bool) (*snapV1.VolumeSnapshot,
182+
*snapV1.VolumeSnapshotContent, bool, bool, string, string, error) {
183+
184+
volumeSnapshot, err := snapc.SnapshotV1().VolumeSnapshots(namespace).Create(ctx,
185+
GetVolumeSnapshotSpec(namespace, volumeSnapshotClass.Name, pvclaim.Name), metav1.CreateOptions{})
186+
if err != nil {
187+
return volumeSnapshot, nil, false, false, "", "", err
188+
}
189+
framework.Logf("Volume snapshot name is : %s", volumeSnapshot.Name)
190+
191+
ginkgo.By("Verify volume snapshot is created")
192+
volumeSnapshot, err = WaitForVolumeSnapshotReadyToUse(*snapc, ctx, namespace, volumeSnapshot.Name)
193+
if err != nil {
194+
return volumeSnapshot, nil, false, false, "", "", err
195+
}
196+
197+
snapshotCreated := true
198+
if volumeSnapshot.Status.RestoreSize.Cmp(resource.MustParse(diskSize)) != 0 {
199+
return volumeSnapshot, nil, false, false, "", "", fmt.Errorf("unexpected restore size")
200+
}
201+
202+
ginkgo.By("Verify volume snapshot content is created")
203+
snapshotContent, err := snapc.SnapshotV1().VolumeSnapshotContents().Get(ctx,
204+
*volumeSnapshot.Status.BoundVolumeSnapshotContentName, metav1.GetOptions{})
205+
if err != nil {
206+
return volumeSnapshot, snapshotContent, false, false, "", "", err
207+
}
208+
snapshotContentCreated := true
209+
snapshotContent, err = WaitForVolumeSnapshotContentReadyToUse(*snapc, ctx, snapshotContent.Name)
210+
if err != nil {
211+
return volumeSnapshot, snapshotContent, false, false, "", "",
212+
fmt.Errorf("volume snapshot content is not ready to use")
213+
}
214+
215+
framework.Logf("Get volume snapshot ID from snapshot handle")
216+
snapshotId, snapshotHandle, err := GetVolumeSnapshotIdFromSnapshotHandle(ctx, vs, snapshotContent)
217+
if err != nil {
218+
return volumeSnapshot, snapshotContent, false, false, snapshotId, "", err
219+
}
220+
221+
if performCnsQueryVolumeSnapshot {
222+
ginkgo.By("Query CNS and check the volume snapshot entry")
223+
err = WaitForCNSSnapshotToBeCreated(vs, volHandle, snapshotId)
224+
if err != nil {
225+
framework.Logf("no error")
226+
return volumeSnapshot, snapshotContent, false, false, snapshotId, "", err
227+
}
228+
}
229+
230+
return volumeSnapshot, snapshotContent, snapshotCreated, snapshotContentCreated, snapshotId, snapshotHandle, nil
231+
}
232+
233+
// createVolumeSnapshotClass creates VSC for a Vanilla cluster and
234+
// fetches VSC for a Guest or Supervisor Cluster
235+
func createVolumeSnapshotClass(ctx context.Context, snapc *snapclient.Clientset,
236+
deletionPolicy string) (*snapV1.VolumeSnapshotClass, error) {
237+
var volumeSnapshotClass *snapV1.VolumeSnapshotClass
238+
var err error
239+
if vanillaCluster {
240+
volumeSnapshotClass, err = snapc.SnapshotV1().VolumeSnapshotClasses().Create(ctx,
241+
GetVolumeSnapshotClassSpec(snapV1.DeletionPolicy(deletionPolicy), nil), metav1.CreateOptions{})
242+
if err != nil {
243+
return nil, err
244+
}
245+
} else if guestCluster || supervisorCluster {
246+
var volumeSnapshotClassName string
247+
if deletionPolicy == "Delete" {
248+
volumeSnapshotClassName = env.GetAndExpectStringEnvVar(constants.envVolSnapClassDel)
249+
} else {
250+
framework.Failf("%s volume snapshotclass is not supported"+
251+
" in Supervisor or Guest Cluster", deletionPolicy)
252+
}
253+
volumeSnapshotClass, err = GetVolumeSnapshotClass(ctx, snapc, volumeSnapshotClassName)
254+
if err != nil {
255+
return nil, err
256+
}
257+
}
258+
return volumeSnapshotClass, nil
259+
}
260+
261+
// deleteVolumeSnapshotWithPandoraWait deletes Volume Snapshot with Pandora wait for CNS to sync
262+
func deleteVolumeSnapshotWithPandoraWait(ctx context.Context, snapc *snapclient.Clientset,
263+
namespace string, snapshotName string, pandoraSyncWaitTime int) {
264+
err := snapc.SnapshotV1().VolumeSnapshots(namespace).Delete(ctx, snapshotName,
265+
metav1.DeleteOptions{})
266+
if !apierrors.IsNotFound(err) {
267+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
268+
}
269+
270+
ginkgo.By(fmt.Sprintf("Sleeping for %v seconds to allow CNS to sync with pandora", pandoraSyncWaitTime))
271+
time.Sleep(time.Duration(pandoraSyncWaitTime) * time.Second)
272+
}
273+
274+
/* deleteVolumeSnapshotContent deletes volume snapshot content explicitly on Guest cluster */
275+
func deleteVolumeSnapshotContent(ctx context.Context, updatedSnapshotContent *snapV1.VolumeSnapshotContent,
276+
snapc *snapclient.Clientset, pandoraSyncWaitTime int) error {
277+
278+
framework.Logf("Delete volume snapshot content")
279+
DeleteVolumeSnapshotContentWithPandoraWait(ctx, snapc, updatedSnapshotContent.Name, pandoraSyncWaitTime)
280+
281+
framework.Logf("Wait till the volume snapshot content is deleted")
282+
err := WaitForVolumeSnapshotContentToBeDeleted(*snapc, ctx, updatedSnapshotContent.Name)
283+
if err != nil {
284+
return err
285+
}
286+
return nil
287+
}
288+
289+
// waitForVolumeSnapshotContentToBeDeleted wait till the volume snapshot content is deleted
290+
func waitForVolumeSnapshotContentToBeDeleted(client snapclient.Clientset, ctx context.Context,
291+
name string) error {
292+
var err error
293+
waitErr := wait.PollUntilContextTimeout(ctx, constants.Poll, 2*constants.PollTimeout, true,
294+
func(ctx context.Context) (bool, error) {
295+
_, err = client.SnapshotV1().VolumeSnapshotContents().Get(ctx, name, metav1.GetOptions{})
296+
if err != nil {
297+
if apierrors.IsNotFound(err) {
298+
framework.Logf("VolumeSnapshotContent: %s is deleted", name)
299+
return true, nil
300+
} else {
301+
return false, fmt.Errorf("error fetching volumesnapshotcontent details : %v", err)
302+
}
303+
}
304+
return false, nil
305+
})
306+
return waitErr
307+
}

tests/e2e/e2e_common.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ const (
7474
envDiskSizeLarge = "LARGE_DISK_SIZE"
7575
envCSINamespace = "CSI_NAMESPACE"
7676
envContentLibraryUrl = "CONTENT_LIB_URL"
77+
envClusterName = "CLUSTER_NAME"
7778
envContentLibraryUrlSslThumbprint = "CONTENT_LIB_THUMBPRINT"
7879
envEsxHostIP = "ESX_TEST_HOST_IP"
7980
envFileServiceDisabledSharedDatastoreURL = "FILE_SERVICE_DISABLED_SHARED_VSPHERE_DATASTORE_URL"
@@ -161,6 +162,7 @@ const (
161162
kubeSystemNamespace = "kube-system"
162163
kubeletConfigYaml = "/var/lib/kubelet/config.yaml"
163164
nfs4FSType = "nfs4"
165+
nfs4Keyword = "NFSv4.1"
164166
mmStateChangeTimeout = 300 // int
165167
objOrItemNotFoundErr = "The object or item referred to could not be found"
166168
passorwdFilePath = "/etc/vmware/wcp/.storageUser"

tests/e2e/k8testutil/util.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7315,3 +7315,47 @@ func ListStoragePolicyUsages(ctx context.Context, c clientset.Interface, restCli
73157315

73167316
fmt.Println("All required storage policy usages are available.")
73177317
}
7318+
7319+
// createPVCAndQueryVolumeInCNS creates PVc with a given storage class on a given namespace
7320+
// and verifies cns metadata of that volume if verifyCNSVolume is set to true
7321+
func CreatePVCAndQueryVolumeInCNS(ctx context.Context, client clientset.Interface, vs *config.E2eTestConfig, namespace string,
7322+
pvclaimLabels map[string]string, accessMode v1.PersistentVolumeAccessMode,
7323+
ds string, storageclass *storagev1.StorageClass,
7324+
verifyCNSVolume bool) (*v1.PersistentVolumeClaim, []*v1.PersistentVolume, error) {
7325+
7326+
// Create PVC
7327+
pvclaim, err := CreatePVC(ctx, client, namespace, pvclaimLabels, ds, storageclass, accessMode)
7328+
if err != nil {
7329+
return pvclaim, nil, fmt.Errorf("failed to create PVC: %w", err)
7330+
}
7331+
7332+
// Wait for PVC to be bound to a PV
7333+
persistentvolumes, err := fpv.WaitForPVClaimBoundPhase(ctx, client,
7334+
[]*v1.PersistentVolumeClaim{pvclaim}, framework.ClaimProvisionTimeout*2)
7335+
if err != nil {
7336+
return pvclaim, persistentvolumes, fmt.Errorf("failed to wait for PVC to bind to a PV: %w", err)
7337+
}
7338+
7339+
// Get VolumeHandle from the PV
7340+
volHandle := persistentvolumes[0].Spec.CSI.VolumeHandle
7341+
if vs.TestInput.ClusterFlavor.GuestCluster {
7342+
volHandle = GetVolumeIDFromSupervisorCluster(volHandle)
7343+
}
7344+
if volHandle == "" {
7345+
return pvclaim, persistentvolumes, fmt.Errorf("volume handle is empty")
7346+
}
7347+
7348+
// Verify the volume in CNS if required
7349+
if verifyCNSVolume {
7350+
ginkgo.By(fmt.Sprintf("Invoking QueryCNSVolumeWithResult with VolumeID: %s", volHandle))
7351+
queryResult, err := vcutil.QueryCNSVolumeWithResult(vs, volHandle)
7352+
if err != nil {
7353+
return pvclaim, persistentvolumes, fmt.Errorf("failed to query CNS volume: %w", err)
7354+
}
7355+
if len(queryResult.Volumes) == 0 || queryResult.Volumes[0].VolumeId.Id != volHandle {
7356+
return pvclaim, persistentvolumes, fmt.Errorf("CNS query returned unexpected result")
7357+
}
7358+
}
7359+
7360+
return pvclaim, persistentvolumes, nil
7361+
}

0 commit comments

Comments
 (0)