Skip to content

Commit

Permalink
smb restore from snapshot
Browse files Browse the repository at this point in the history
  • Loading branch information
umagnus committed Oct 10, 2023
1 parent 8e21965 commit 39033de
Show file tree
Hide file tree
Showing 9 changed files with 368 additions and 18 deletions.
16 changes: 16 additions & 0 deletions deploy/example/snapshot/pvc-azurefile-snapshot-restored.yaml
Original file line number Diff line number Diff line change
@@ -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
75 changes: 72 additions & 3 deletions pkg/azurefile/controllerserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"fmt"
"net/url"
"os/exec"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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()
Expand Down
83 changes: 75 additions & 8 deletions pkg/azurefile/controllerserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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{}

Expand All @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down
1 change: 0 additions & 1 deletion test/e2e/driver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ func getVolumeSnapshotClass(generateName string, provisioner string) *snapshotv1
Kind: VolumeSnapshotClassKind,
APIVersion: SnapshotAPIVersion,
},

ObjectMeta: metav1.ObjectMeta{
GenerateName: generateName,
},
Expand Down
Loading

0 comments on commit 39033de

Please sign in to comment.