From 39033de1a2336764a1892a6ae5047f4cb3a40862 Mon Sep 17 00:00:00 2001 From: umagnus Date: Tue, 26 Sep 2023 08:21:59 +0000 Subject: [PATCH] smb restore from snapshot --- .../pvc-azurefile-snapshot-restored.yaml | 16 +++ pkg/azurefile/controllerserver.go | 75 ++++++++++- pkg/azurefile/controllerserver_test.go | 83 ++++++++++-- test/e2e/driver/driver.go | 1 - test/e2e/dynamic_provisioning_test.go | 119 ++++++++++++++++++ ...ally_provisioned_volume_snapshot_tester.go | 57 ++++++++- test/e2e/testsuites/specs.go | 3 + test/e2e/testsuites/testsuites.go | 28 +++++ test/sanity/run-test.sh | 4 +- 9 files changed, 368 insertions(+), 18 deletions(-) create mode 100644 deploy/example/snapshot/pvc-azurefile-snapshot-restored.yaml diff --git a/deploy/example/snapshot/pvc-azurefile-snapshot-restored.yaml b/deploy/example/snapshot/pvc-azurefile-snapshot-restored.yaml new file mode 100644 index 0000000000..2388e2ce27 --- /dev/null +++ b/deploy/example/snapshot/pvc-azurefile-snapshot-restored.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: pvc-azurefile-snapshot-restored +spec: + accessModes: + - ReadWriteMany + storageClassName: azurefile-csi + resources: + requests: + storage: 100Gi + dataSource: + name: azurefile-volume-snapshot + kind: VolumeSnapshot + apiGroup: snapshot.storage.k8s.io diff --git a/pkg/azurefile/controllerserver.go b/pkg/azurefile/controllerserver.go index 4621111458..1af0848a87 100644 --- a/pkg/azurefile/controllerserver.go +++ b/pkg/azurefile/controllerserver.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "net/url" + "os/exec" "strconv" "strings" "time" @@ -575,7 +576,7 @@ func (d *Driver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) if err != nil { return nil, status.Errorf(codes.Internal, "failed to GetStorageAccesskey on account(%s) rg(%s), error: %v", accountOptions.Name, accountOptions.ResourceGroup, err) } - if err := d.copyVolume(ctx, req, accountKeyCopy, shareOptions, storageEndpointSuffix); err != nil { + if err := d.copyVolume(ctx, accountName, req, accountKeyCopy, shareOptions, storageEndpointSuffix); err != nil { return nil, err } // storeAccountKey is not needed here since copy volume is only using SAS token @@ -725,11 +726,11 @@ func (d *Driver) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) return &csi.DeleteVolumeResponse{}, nil } -func (d *Driver) copyVolume(ctx context.Context, req *csi.CreateVolumeRequest, accountKey string, shareOptions *fileclient.ShareOptions, storageEndpointSuffix string) error { +func (d *Driver) copyVolume(ctx context.Context, accountName string, req *csi.CreateVolumeRequest, accountKey string, shareOptions *fileclient.ShareOptions, storageEndpointSuffix string) error { vs := req.VolumeContentSource switch vs.Type.(type) { case *csi.VolumeContentSource_Snapshot: - return status.Errorf(codes.InvalidArgument, "copy volume from volumeSnapshot is not supported") + return d.restoreSnapshot(ctx, accountName, req, accountKey, shareOptions, storageEndpointSuffix) case *csi.VolumeContentSource_Volume: return d.copyFileShare(ctx, req, accountKey, shareOptions, storageEndpointSuffix) default: @@ -1083,6 +1084,74 @@ func (d *Driver) ListSnapshots(ctx context.Context, req *csi.ListSnapshotsReques return nil, status.Error(codes.Unimplemented, "") } +// restoreSnapshot restores from a snapshot +func (d *Driver) restoreSnapshot(ctx context.Context, dstAccountName string, req *csi.CreateVolumeRequest, accountKey string, shareOptions *fileclient.ShareOptions, storageEndpointSuffix string) error { + if shareOptions.Protocol == storage.EnabledProtocolsNFS { + return fmt.Errorf("protocol nfs is not supported for snapshot restore") + } + var sourceSnapshotID string + if req.GetVolumeContentSource() != nil && req.GetVolumeContentSource().GetSnapshot() != nil { + sourceSnapshotID = req.GetVolumeContentSource().GetSnapshot().GetSnapshotId() + } + resourceGroupName, srcAccountName, srcFileShareName, _, _, _, err := GetFileShareInfo(sourceSnapshotID) //nolint:dogsled + if err != nil { + return status.Error(codes.NotFound, err.Error()) + } + snapshot, err := getSnapshot(sourceSnapshotID) + if err != nil { + return status.Error(codes.NotFound, err.Error()) + } + dstFileShareName := shareOptions.Name + if srcFileShareName == "" || dstFileShareName == "" { + return fmt.Errorf("srcFileShareName(%s) or dstFileShareName(%s) is empty", srcFileShareName, dstFileShareName) + } + + klog.V(2).Infof("generate sas token for src account(%s)", srcAccountName) + srcAccountSasToken, err := generateSASToken(srcAccountName, accountKey, storageEndpointSuffix, d.sasTokenExpirationMinutes) + if err != nil { + return err + } + klog.V(2).Infof("generate sas token for dst account(%s)", dstAccountName) + dstAccountSasToken, err := generateSASToken(dstAccountName, accountKey, storageEndpointSuffix, d.sasTokenExpirationMinutes) + if err != nil { + return err + } + + timeAfter := time.After(waitForCopyTimeout) + timeTick := time.Tick(waitForCopyInterval) + srcPath := fmt.Sprintf("https://%s.file.%s/%s%s&sharesnapshot=%s", srcAccountName, storageEndpointSuffix, srcFileShareName, srcAccountSasToken, snapshot) + dstPath := fmt.Sprintf("https://%s.file.%s/%s%s", dstAccountName, storageEndpointSuffix, dstFileShareName, dstAccountSasToken) + + jobState, percent, err := getAzcopyJob(dstFileShareName) + klog.V(2).Infof("azcopy job status: %s, copy percent: %s%%, error: %v", jobState, percent, err) + if jobState == AzcopyJobError || jobState == AzcopyJobCompleted { + return err + } + klog.V(2).Infof("begin to restore snapshot, copy fileshare %s(snapshot: %s) to %s", srcFileShareName, snapshot, dstFileShareName) + for { + select { + case <-timeTick: + jobState, percent, err := getAzcopyJob(dstFileShareName) + klog.V(2).Infof("azcopy job status: %s, copy percent: %s%%, error: %v", jobState, percent, err) + switch jobState { + case AzcopyJobError, AzcopyJobCompleted: + return err + case AzcopyJobNotFound: + klog.V(2).Infof("restore snapsnot, copy fileshare %s(snapshot: %s) to %s", srcFileShareName, snapshot, dstFileShareName) + out, copyErr := exec.Command("azcopy", "copy", srcPath, dstPath, "--recursive", "--check-length=false").CombinedOutput() + if copyErr != nil { + klog.Warningf("CopyFileShare(%s, %s, %s) failed with error(%v): %v", resourceGroupName, dstAccountName, dstFileShareName, copyErr, string(out)) + } else { + klog.V(2).Infof("restore snapshot done, copied fileshare %s(snapshot: %s) to %s successfully", srcFileShareName, snapshot, dstFileShareName) + } + return copyErr + } + case <-timeAfter: + return fmt.Errorf("timeout waiting for copy fileshare %s(snapshot: %s) to %s succeed", srcFileShareName, snapshot, dstFileShareName) + } + } +} + // ControllerExpandVolume controller expand volume func (d *Driver) ControllerExpandVolume(ctx context.Context, req *csi.ControllerExpandVolumeRequest) (*csi.ControllerExpandVolumeResponse, error) { volumeID := req.GetVolumeId() diff --git a/pkg/azurefile/controllerserver_test.go b/pkg/azurefile/controllerserver_test.go index b79a4e62f6..2b832ee7c2 100644 --- a/pkg/azurefile/controllerserver_test.go +++ b/pkg/azurefile/controllerserver_test.go @@ -23,12 +23,13 @@ import ( "net/http" "net/url" "reflect" - "sigs.k8s.io/cloud-provider-azure/pkg/azureclients/fileclient" "strings" "sync" "testing" "time" + "sigs.k8s.io/cloud-provider-azure/pkg/azureclients/fileclient" + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2022-07-01/network" "sigs.k8s.io/cloud-provider-azure/pkg/azureclients/subnetclient/mocksubnetclient" azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache" @@ -1619,7 +1620,40 @@ func TestCopyVolume(t *testing.T) { testFunc func(t *testing.T) }{ { - name: "copy volume from volumeSnapshot is not supported", + name: "restore volume from volumeSnapshot nfs is not supported", + testFunc: func(t *testing.T) { + allParam := map[string]string{} + + volumeSnapshotSource := &csi.VolumeContentSource_SnapshotSource{ + SnapshotId: "unit-test", + } + volumeContentSourceSnapshotSource := &csi.VolumeContentSource_Snapshot{ + Snapshot: volumeSnapshotSource, + } + volumecontensource := csi.VolumeContentSource{ + Type: volumeContentSourceSnapshotSource, + } + + req := &csi.CreateVolumeRequest{ + Name: "random-vol-name-valid-request", + VolumeCapabilities: stdVolCap, + CapacityRange: lessThanPremCapRange, + Parameters: allParam, + VolumeContentSource: &volumecontensource, + } + + d := NewFakeDriver() + ctx := context.Background() + + expectedErr := fmt.Errorf("protocol nfs is not supported for snapshot restore") + err := d.copyVolume(ctx, "", req, "", &fileclient.ShareOptions{Protocol: storage.EnabledProtocolsNFS}, "core.windows.net") + if !reflect.DeepEqual(err, expectedErr) { + t.Errorf("Unexpected error: %v", err) + } + }, + }, + { + name: "restore volume from volumeSnapshot not found", testFunc: func(t *testing.T) { allParam := map[string]string{} @@ -1644,8 +1678,41 @@ func TestCopyVolume(t *testing.T) { d := NewFakeDriver() ctx := context.Background() - expectedErr := status.Errorf(codes.InvalidArgument, "copy volume from volumeSnapshot is not supported") - err := d.copyVolume(ctx, req, "", nil, "core.windows.net") + expectedErr := status.Errorf(codes.NotFound, "error parsing volume id: \"unit-test\", should at least contain two #") + err := d.copyVolume(ctx, "", req, "", &fileclient.ShareOptions{Name: "dstFileshare"}, "core.windows.net") + if !reflect.DeepEqual(err, expectedErr) { + t.Errorf("Unexpected error: %v", err) + } + }, + }, + { + name: "restore volume from volumeSnapshot src fileshare is empty", + testFunc: func(t *testing.T) { + allParam := map[string]string{} + + volumeSnapshotSource := &csi.VolumeContentSource_SnapshotSource{ + SnapshotId: "rg#unit-test###", + } + volumeContentSourceSnapshotSource := &csi.VolumeContentSource_Snapshot{ + Snapshot: volumeSnapshotSource, + } + volumecontensource := csi.VolumeContentSource{ + Type: volumeContentSourceSnapshotSource, + } + + req := &csi.CreateVolumeRequest{ + Name: "random-vol-name-valid-request", + VolumeCapabilities: stdVolCap, + CapacityRange: lessThanPremCapRange, + Parameters: allParam, + VolumeContentSource: &volumecontensource, + } + + d := NewFakeDriver() + ctx := context.Background() + + expectedErr := fmt.Errorf("srcFileShareName() or dstFileShareName(dstFileshare) is empty") + err := d.copyVolume(ctx, "", req, "", &fileclient.ShareOptions{Name: "dstFileshare"}, "core.windows.net") if !reflect.DeepEqual(err, expectedErr) { t.Errorf("Unexpected error: %v", err) } @@ -1678,7 +1745,7 @@ func TestCopyVolume(t *testing.T) { ctx := context.Background() expectedErr := fmt.Errorf("protocol nfs is not supported for volume cloning") - err := d.copyVolume(ctx, req, "", &fileclient.ShareOptions{Protocol: storage.EnabledProtocolsNFS}, "core.windows.net") + err := d.copyVolume(ctx, "", req, "", &fileclient.ShareOptions{Protocol: storage.EnabledProtocolsNFS}, "core.windows.net") if !reflect.DeepEqual(err, expectedErr) { t.Errorf("Unexpected error: %v", err) } @@ -1711,7 +1778,7 @@ func TestCopyVolume(t *testing.T) { ctx := context.Background() expectedErr := status.Errorf(codes.NotFound, "error parsing volume id: \"unit-test\", should at least contain two #") - err := d.copyVolume(ctx, req, "", &fileclient.ShareOptions{Name: "dstFileshare"}, "core.windows.net") + err := d.copyVolume(ctx, "", req, "", &fileclient.ShareOptions{Name: "dstFileshare"}, "core.windows.net") if !reflect.DeepEqual(err, expectedErr) { t.Errorf("Unexpected error: %v", err) } @@ -1744,7 +1811,7 @@ func TestCopyVolume(t *testing.T) { ctx := context.Background() expectedErr := fmt.Errorf("srcFileShareName() or dstFileShareName(dstFileshare) is empty") - err := d.copyVolume(ctx, req, "", &fileclient.ShareOptions{Name: "dstFileshare"}, "core.windows.net") + err := d.copyVolume(ctx, "", req, "", &fileclient.ShareOptions{Name: "dstFileshare"}, "core.windows.net") if !reflect.DeepEqual(err, expectedErr) { t.Errorf("Unexpected error: %v", err) } @@ -1777,7 +1844,7 @@ func TestCopyVolume(t *testing.T) { ctx := context.Background() expectedErr := fmt.Errorf("srcFileShareName(fileshare) or dstFileShareName() is empty") - err := d.copyVolume(ctx, req, "", &fileclient.ShareOptions{}, "core.windows.net") + err := d.copyVolume(ctx, "", req, "", &fileclient.ShareOptions{}, "core.windows.net") if !reflect.DeepEqual(err, expectedErr) { t.Errorf("Unexpected error: %v", err) } diff --git a/test/e2e/driver/driver.go b/test/e2e/driver/driver.go index d48ffb2a36..4bd8427b81 100644 --- a/test/e2e/driver/driver.go +++ b/test/e2e/driver/driver.go @@ -90,7 +90,6 @@ func getVolumeSnapshotClass(generateName string, provisioner string) *snapshotv1 Kind: VolumeSnapshotClassKind, APIVersion: SnapshotAPIVersion, }, - ObjectMeta: metav1.ObjectMeta{ GenerateName: generateName, }, diff --git a/test/e2e/dynamic_provisioning_test.go b/test/e2e/dynamic_provisioning_test.go index bef075a957..60ef3c94a2 100644 --- a/test/e2e/dynamic_provisioning_test.go +++ b/test/e2e/dynamic_provisioning_test.go @@ -712,6 +712,8 @@ var _ = ginkgo.Describe("Dynamic Provisioning", func() { test := testsuites.DynamicallyProvisionedVolumeSnapshotTest{ CSIDriver: testDriver, Pod: pod, + ShouldOverwrite: false, + ShouldRestore: false, PodWithSnapshot: podWithSnapshot, StorageClassParameters: map[string]string{ "skuName": "Standard_LRS", @@ -1476,6 +1478,123 @@ var _ = ginkgo.Describe("Dynamic Provisioning", func() { } test.Run(ctx, cs, ns) }) + + ginkgo.It("should create a pod, write and read to it, take a volume snapshot, and create another pod from the snapshot [file.csi.azure.com]", func(ctx ginkgo.SpecContext) { + skipIfUsingInTreeVolumePlugin() + skipIfTestingInWindowsCluster() + + pod := testsuites.PodDetails{ + Cmd: "echo 'hello world' > /mnt/test-1/data && grep 'hello world' /mnt/test-1/data", + Volumes: []testsuites.VolumeDetails{ + { + ClaimSize: "100Gi", + VolumeMount: testsuites.VolumeMountDetails{ + NameGenerate: "test-volume-", + MountPathGenerate: "/mnt/test-", + }, + }, + }, + } + podWithSnapshot := testsuites.PodDetails{ + Cmd: "grep 'hello world' /mnt/test-1/data", + } + test := testsuites.DynamicallyProvisionedVolumeSnapshotTest{ + CSIDriver: testDriver, + Pod: pod, + ShouldOverwrite: false, + ShouldRestore: true, + PodWithSnapshot: podWithSnapshot, + StorageClassParameters: map[string]string{"skuName": "Standard_LRS"}, + } + test.Run(ctx, cs, snapshotrcs, ns) + }) + + ginkgo.It("should create a pod, write to its pv, take a volume snapshot, overwrite data in original pv, create another pod from the snapshot, and read unaltered original data from original pv[file.csi.azure.com]", func(ctx ginkgo.SpecContext) { + skipIfUsingInTreeVolumePlugin() + skipIfTestingInWindowsCluster() + + pod := testsuites.PodDetails{ + IsWindows: isWindowsCluster, + WinServerVer: winServerVer, + Cmd: "echo 'hello world' > /mnt/test-1/data", + Volumes: []testsuites.VolumeDetails{ + { + ClaimSize: "100Gi", + VolumeMount: testsuites.VolumeMountDetails{ + NameGenerate: "test-volume-", + MountPathGenerate: "/mnt/test-", + }, + }, + }, + } + + podOverwrite := testsuites.PodDetails{ + IsWindows: isWindowsCluster, + WinServerVer: winServerVer, + Cmd: "echo 'overwrite' > /mnt/test-1/data; sleep 3600", + } + + podWithSnapshot := testsuites.PodDetails{ + IsWindows: isWindowsCluster, + WinServerVer: winServerVer, + Cmd: "grep 'hello world' /mnt/test-1/data", + } + + test := testsuites.DynamicallyProvisionedVolumeSnapshotTest{ + CSIDriver: testDriver, + Pod: pod, + ShouldOverwrite: true, + ShouldRestore: true, + PodOverwrite: podOverwrite, + PodWithSnapshot: podWithSnapshot, + StorageClassParameters: map[string]string{"skuName": "Standard_LRS"}, + } + test.Run(ctx, cs, snapshotrcs, ns) + }) + + ginkgo.It("should create a pod, write to its pv, take a volume snapshot, overwrite data in original pv, create another pod from the snapshot, use another storage class, and read unaltered original data from original pv[file.csi.azure.com]", func(ctx ginkgo.SpecContext) { + skipIfUsingInTreeVolumePlugin() + skipIfTestingInWindowsCluster() + + pod := testsuites.PodDetails{ + IsWindows: isWindowsCluster, + WinServerVer: winServerVer, + Cmd: "echo 'hello world' > /mnt/test-1/data", + Volumes: []testsuites.VolumeDetails{ + { + ClaimSize: "100Gi", + VolumeMount: testsuites.VolumeMountDetails{ + NameGenerate: "test-volume-", + MountPathGenerate: "/mnt/test-", + }, + }, + }, + } + + podOverwrite := testsuites.PodDetails{ + IsWindows: isWindowsCluster, + WinServerVer: winServerVer, + Cmd: "echo 'overwrite' > /mnt/test-1/data; sleep 3600", + } + + podWithSnapshot := testsuites.PodDetails{ + IsWindows: isWindowsCluster, + WinServerVer: winServerVer, + Cmd: "grep 'hello world' /mnt/test-1/data", + } + + test := testsuites.DynamicallyProvisionedVolumeSnapshotTest{ + CSIDriver: testDriver, + Pod: pod, + ShouldOverwrite: true, + ShouldRestore: true, + PodOverwrite: podOverwrite, + PodWithSnapshot: podWithSnapshot, + StorageClassParameters: map[string]string{"skuName": "Standard_LRS"}, + SnapshotStorageClassParameters: map[string]string{"skuName": "Premium_LRS"}, + } + test.Run(ctx, cs, snapshotrcs, ns) + }) }) func restClient(group string, version string) (restclientset.Interface, error) { diff --git a/test/e2e/testsuites/dynamically_provisioned_volume_snapshot_tester.go b/test/e2e/testsuites/dynamically_provisioned_volume_snapshot_tester.go index ef4d5b6361..57dff61edd 100644 --- a/test/e2e/testsuites/dynamically_provisioned_volume_snapshot_tester.go +++ b/test/e2e/testsuites/dynamically_provisioned_volume_snapshot_tester.go @@ -18,6 +18,7 @@ package testsuites import ( "context" + "time" "sigs.k8s.io/azurefile-csi-driver/test/e2e/driver" @@ -33,10 +34,14 @@ import ( // Create a snapshot, validate whether it is ready to use. // This test only supports a single volume type DynamicallyProvisionedVolumeSnapshotTest struct { - CSIDriver driver.PVTestDriver - Pod PodDetails - PodWithSnapshot PodDetails - StorageClassParameters map[string]string + CSIDriver driver.PVTestDriver + Pod PodDetails + ShouldOverwrite bool + ShouldRestore bool + PodOverwrite PodDetails + PodWithSnapshot PodDetails + StorageClassParameters map[string]string + SnapshotStorageClassParameters map[string]string } func (t *DynamicallyProvisionedVolumeSnapshotTest) Run(ctx context.Context, client clientset.Interface, restclient restclientset.Interface, namespace *v1.Namespace) { @@ -53,15 +58,59 @@ func (t *DynamicallyProvisionedVolumeSnapshotTest) Run(ctx context.Context, clie defer tpod.Cleanup(ctx) ginkgo.By("checking that the pod's command exits with no error") tpod.WaitForSuccess(ctx) + ginkgo.By("sleep 10s to make sure the data is written to the disk") + time.Sleep(time.Millisecond * 10000) ginkgo.By("creating volume snapshot class") tvsc, cleanup := CreateVolumeSnapshotClass(ctx, restclient, namespace, t.CSIDriver) + if tvsc.volumeSnapshotClass.Parameters == nil { + tvsc.volumeSnapshotClass.Parameters = map[string]string{} + } defer cleanup() ginkgo.By("taking snapshots") snapshot := tvsc.CreateSnapshot(ctx, tpvc.persistentVolumeClaim) + + if t.ShouldOverwrite { + tpod = NewTestPod(client, namespace, t.PodOverwrite.Cmd, t.PodOverwrite.IsWindows, t.Pod.WinServerVer) + + tpod.SetupVolume(tpvc.persistentVolumeClaim, volume.VolumeMount.NameGenerate+"1", volume.VolumeMount.MountPathGenerate+"1", volume.VolumeMount.ReadOnly) + tpod.SetLabel(TestLabel) + ginkgo.By("deploying a new pod to overwrite pv data") + tpod.Create(ctx) + defer tpod.Cleanup(ctx) + ginkgo.By("checking that the pod is running") + tpod.WaitForRunning(ctx) + } + defer tvsc.DeleteSnapshot(ctx, snapshot) // If the field ReadyToUse is still false, there will be a timeout error. tvsc.ReadyToUse(ctx, snapshot) + + if t.ShouldRestore { + snapshotVolume := volume + snapshotVolume.DataSource = &DataSource{ + Kind: VolumeSnapshotKind, + Name: snapshot.Name, + } + snapshotVolume.ClaimSize = volume.ClaimSize + if t.SnapshotStorageClassParameters != nil { + // create the snapshot volume storageClass + tssc, tsscCleanup := t.Pod.Volumes[0].CreateStorageClass(ctx, client, namespace, t.CSIDriver, t.SnapshotStorageClassParameters) + defer tsscCleanup(ctx) + snapshotVolume.StorageClass = tssc.storageClass + } + t.PodWithSnapshot.Volumes = []VolumeDetails{snapshotVolume} + tPodWithSnapshot, tPodWithSnapshotCleanup := t.PodWithSnapshot.SetupWithDynamicVolumes(ctx, client, namespace, t.CSIDriver, t.StorageClassParameters) + for i := range tPodWithSnapshotCleanup { + defer tPodWithSnapshotCleanup[i](ctx) + } + + ginkgo.By("deploying a pod with a volume restored from the snapshot") + tPodWithSnapshot.Create(ctx) + defer tPodWithSnapshot.Cleanup(ctx) + ginkgo.By("checking that the pod's command exits with no error") + tPodWithSnapshot.WaitForSuccess(ctx) + } } diff --git a/test/e2e/testsuites/specs.go b/test/e2e/testsuites/specs.go index 26cb0ccbdc..48b042f7e8 100644 --- a/test/e2e/testsuites/specs.go +++ b/test/e2e/testsuites/specs.go @@ -236,6 +236,9 @@ func (volume *VolumeDetails) SetupDynamicPersistentVolumeClaim(ctx context.Conte Name: volume.DataSource.Name, Kind: volume.DataSource.Kind, } + if volume.DataSource.Kind == VolumeSnapshotKind { + dataSource.APIGroup = &SnapshotAPIGroup + } tpvc = NewTestPersistentVolumeClaimWithDataSource(client, namespace, volume.ClaimSize, volume.VolumeMode, &createdStorageClass, dataSource) } else { tpvc = NewTestPersistentVolumeClaim(client, namespace, volume.ClaimSize, volume.VolumeMode, &createdStorageClass) diff --git a/test/e2e/testsuites/testsuites.go b/test/e2e/testsuites/testsuites.go index e53efdbc46..1a729725c8 100644 --- a/test/e2e/testsuites/testsuites.go +++ b/test/e2e/testsuites/testsuites.go @@ -66,6 +66,26 @@ const ( pollLongTimeout = 5 * time.Minute pollTimeout = 10 * time.Minute pollForStringTimeout = 1 * time.Minute + + testLabelKey = "test-label-key" + testLabelValue = "test-label-value" + HostNameLabel = "kubernetes.io/hostname" +) + +var ( + TestLabel = map[string]string{ + testLabelKey: testLabelValue, + } + + TestPodAntiAffinity = v1.Affinity{ + PodAntiAffinity: &v1.PodAntiAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ + { + LabelSelector: &metav1.LabelSelector{MatchLabels: TestLabel}, + TopologyKey: HostNameLabel, + }}, + }, + } ) type TestStorageClass struct { @@ -839,6 +859,14 @@ func (t *TestPod) Logs(ctx context.Context) ([]byte, error) { return podLogs(ctx, t.client, t.pod.Name, t.namespace.Name) } +func (t *TestPod) SetAffinity(affinity *v1.Affinity) { + t.pod.Spec.Affinity = affinity +} + +func (t *TestPod) SetLabel(labels map[string]string) { + t.pod.ObjectMeta.Labels = labels +} + type TestSecret struct { client clientset.Interface secret *v1.Secret diff --git a/test/sanity/run-test.sh b/test/sanity/run-test.sh index d877088b36..1dfc4bdfab 100755 --- a/test/sanity/run-test.sh +++ b/test/sanity/run-test.sh @@ -54,7 +54,7 @@ sleep 1 echo 'Begin to run sanity test...' readonly CSI_SANITY_BIN='csi-sanity' -"$CSI_SANITY_BIN" --ginkgo.v --ginkgo.noColor --csi.endpoint="$endpoint" --ginkgo.skip='should fail when the volume source snapshot is not found|should work|should fail when the volume does not exist|should fail when the node does not exist|Node Service NodeGetCapabilities|should remove target path|should create volume from an existing source snapshot' +"$CSI_SANITY_BIN" --ginkgo.v --ginkgo.noColor --csi.endpoint="$endpoint" --ginkgo.skip='should fail when the volume source snapshot is not found|should work|should fail when the volume does not exist|should fail when the node does not exist|Node Service NodeGetCapabilities|should remove target path' testvolumeparameters='/tmp/vhd.yaml' cat > $testvolumeparameters << EOF @@ -62,4 +62,4 @@ fstype: ext4 EOF echo 'Begin to run sanity test for vhd disk feature...' -"$CSI_SANITY_BIN" --ginkgo.v --ginkgo.noColor --csi.endpoint="$endpoint" --csi.testvolumeparameters="$testvolumeparameters" --ginkgo.skip='should fail when the volume source snapshot is not found|should work|should fail when volume does not exist on the specified path|should fail when the volume does not exist|should fail when the node does not exist|should be idempotent|Node Service NodeGetCapabilities|should remove target path|should create volume from an existing source snapshot' +"$CSI_SANITY_BIN" --ginkgo.v --ginkgo.noColor --csi.endpoint="$endpoint" --csi.testvolumeparameters="$testvolumeparameters" --ginkgo.skip='should fail when the volume source snapshot is not found|should work|should fail when volume does not exist on the specified path|should fail when the volume does not exist|should fail when the node does not exist|should be idempotent|Node Service NodeGetCapabilities|should remove target path'