Skip to content

Commit 63b1b7d

Browse files
committed
smb restore from snapshot
1 parent 8e21965 commit 63b1b7d

File tree

17 files changed

+912
-238
lines changed

17 files changed

+912
-238
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
apiVersion: v1
3+
kind: PersistentVolumeClaim
4+
metadata:
5+
name: pvc-azurefile-snapshot-restored
6+
spec:
7+
accessModes:
8+
- ReadWriteMany
9+
storageClassName: azurefile-csi
10+
resources:
11+
requests:
12+
storage: 100Gi
13+
dataSource:
14+
name: azurefile-volume-snapshot
15+
kind: VolumeSnapshot
16+
apiGroup: snapshot.storage.k8s.io
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// /*
2+
// Copyright 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+
// */

hack/update-mock.sh

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/bin/bash
2+
3+
# Copyright 2020 The Kubernetes Authors.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
set -euo pipefail
18+
19+
REPO_ROOT=$(realpath $(dirname ${BASH_SOURCE})/..)
20+
COPYRIGHT_FILE="${REPO_ROOT}/hack/boilerplate/boilerplate.go.txt"
21+
22+
if ! type mockgen &> /dev/null; then
23+
echo "mockgen not exist, install it"
24+
go install github.com/golang/mock/[email protected]
25+
fi
26+
27+
echo "Updating mocks for util.go"
28+
mockgen -copyright_file=$COPYRIGHT_FILE -source=pkg/util/util.go -package=util -destination=pkg/util/util_mock.go

pkg/azurefile/azurefile.go

Lines changed: 6 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
"encoding/binary"
2323
"fmt"
2424
"net/url"
25-
"os/exec"
2625
"strconv"
2726
"strings"
2827
"sync"
@@ -46,6 +45,7 @@ import (
4645

4746
csicommon "sigs.k8s.io/azurefile-csi-driver/pkg/csi-common"
4847
"sigs.k8s.io/azurefile-csi-driver/pkg/mounter"
48+
fileutil "sigs.k8s.io/azurefile-csi-driver/pkg/util"
4949
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/fileclient"
5050
azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
5151
azure "sigs.k8s.io/cloud-provider-azure/pkg/provider"
@@ -269,6 +269,8 @@ type Driver struct {
269269
volStatsCache azcache.Resource
270270
// sas expiry time for azcopy in volume clone
271271
sasTokenExpirationMinutes int
272+
// azcopyFunc for provide exec mock for ut
273+
azcopyFunc *fileutil.AzcopyFunc
272274
}
273275

274276
// NewDriver Creates a NewCSIDriver object. Assumes vendor version is equal to driver version &
@@ -300,6 +302,7 @@ func NewDriver(options *DriverOptions) *Driver {
300302
driver.volLockMap = newLockMap()
301303
driver.subnetLockMap = newLockMap()
302304
driver.volumeLocks = newVolumeLocks()
305+
driver.azcopyFunc = &fileutil.AzcopyFunc{}
303306

304307
var err error
305308
getter := func(key string) (interface{}, error) { return nil, nil }
@@ -917,45 +920,16 @@ func (d *Driver) copyFileShare(ctx context.Context, req *csi.CreateVolumeRequest
917920
return fmt.Errorf("srcFileShareName(%s) or dstFileShareName(%s) is empty", srcFileShareName, dstFileShareName)
918921
}
919922

920-
klog.V(2).Infof("generate sas token for account(%s)", accountName)
923+
klog.V(2).Infof("generate sas token for resource group(%s) account(%s)", resourceGroupName, accountName)
921924
accountSasToken, genErr := generateSASToken(accountName, accountKey, storageEndpointSuffix, d.sasTokenExpirationMinutes)
922925
if genErr != nil {
923926
return genErr
924927
}
925928

926-
timeAfter := time.After(waitForCopyTimeout)
927-
timeTick := time.Tick(waitForCopyInterval)
928929
srcPath := fmt.Sprintf("https://%s.file.%s/%s%s", accountName, storageEndpointSuffix, srcFileShareName, accountSasToken)
929930
dstPath := fmt.Sprintf("https://%s.file.%s/%s%s", accountName, storageEndpointSuffix, dstFileShareName, accountSasToken)
930931

931-
jobState, percent, err := getAzcopyJob(dstFileShareName)
932-
klog.V(2).Infof("azcopy job status: %s, copy percent: %s%%, error: %v", jobState, percent, err)
933-
if jobState == AzcopyJobError || jobState == AzcopyJobCompleted {
934-
return err
935-
}
936-
klog.V(2).Infof("begin to copy fileshare %s to %s", srcFileShareName, dstFileShareName)
937-
for {
938-
select {
939-
case <-timeTick:
940-
jobState, percent, err := getAzcopyJob(dstFileShareName)
941-
klog.V(2).Infof("azcopy job status: %s, copy percent: %s%%, error: %v", jobState, percent, err)
942-
switch jobState {
943-
case AzcopyJobError, AzcopyJobCompleted:
944-
return err
945-
case AzcopyJobNotFound:
946-
klog.V(2).Infof("copy fileshare %s to %s", srcFileShareName, dstFileShareName)
947-
out, copyErr := exec.Command("azcopy", "copy", srcPath, dstPath, "--recursive", "--check-length=false").CombinedOutput()
948-
if copyErr != nil {
949-
klog.Warningf("CopyFileShare(%s, %s, %s) failed with error(%v): %v", resourceGroupName, accountName, dstFileShareName, copyErr, string(out))
950-
} else {
951-
klog.V(2).Infof("copied fileshare %s to %s successfully", srcFileShareName, dstFileShareName)
952-
}
953-
return copyErr
954-
}
955-
case <-timeAfter:
956-
return fmt.Errorf("timeout waiting for copy fileshare %s to %s succeed", srcFileShareName, dstFileShareName)
957-
}
958-
}
932+
return d.copyFileShareByAzcopy(srcFileShareName, dstFileShareName, srcPath, dstPath, "")
959933
}
960934

961935
// GetTotalAccountQuota returns the total quota in GB of all file shares in the storage account and the number of file shares

pkg/azurefile/controllerserver.go

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ import (
2020
"context"
2121
"fmt"
2222
"net/url"
23+
"os/exec"
2324
"strconv"
2425
"strings"
2526
"time"
2627

28+
"sigs.k8s.io/azurefile-csi-driver/pkg/util"
2729
volumehelper "sigs.k8s.io/azurefile-csi-driver/pkg/util"
2830

2931
"github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/sas"
@@ -108,7 +110,8 @@ func (d *Driver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest)
108110
if acquired := d.volumeLocks.TryAcquire(volName); !acquired {
109111
// logging the job status if it's volume cloning
110112
if req.GetVolumeContentSource() != nil {
111-
jobState, percent, err := getAzcopyJob(volName)
113+
aFunc := &util.AzcopyFunc{}
114+
jobState, percent, err := aFunc.GetAzcopyJob(volName)
112115
klog.V(2).Infof("azcopy job status: %s, copy percent: %s%%, error: %v", jobState, percent, err)
113116
}
114117
return nil, status.Errorf(codes.Aborted, volumeOperationAlreadyExistsFmt, volName)
@@ -575,7 +578,7 @@ func (d *Driver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest)
575578
if err != nil {
576579
return nil, status.Errorf(codes.Internal, "failed to GetStorageAccesskey on account(%s) rg(%s), error: %v", accountOptions.Name, accountOptions.ResourceGroup, err)
577580
}
578-
if err := d.copyVolume(ctx, req, accountKeyCopy, shareOptions, storageEndpointSuffix); err != nil {
581+
if err := d.copyVolume(ctx, accountName, req, accountKeyCopy, shareOptions, storageEndpointSuffix); err != nil {
579582
return nil, err
580583
}
581584
// storeAccountKey is not needed here since copy volume is only using SAS token
@@ -725,11 +728,11 @@ func (d *Driver) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest)
725728
return &csi.DeleteVolumeResponse{}, nil
726729
}
727730

728-
func (d *Driver) copyVolume(ctx context.Context, req *csi.CreateVolumeRequest, accountKey string, shareOptions *fileclient.ShareOptions, storageEndpointSuffix string) error {
731+
func (d *Driver) copyVolume(ctx context.Context, accountName string, req *csi.CreateVolumeRequest, accountKey string, shareOptions *fileclient.ShareOptions, storageEndpointSuffix string) error {
729732
vs := req.VolumeContentSource
730733
switch vs.Type.(type) {
731734
case *csi.VolumeContentSource_Snapshot:
732-
return status.Errorf(codes.InvalidArgument, "copy volume from volumeSnapshot is not supported")
735+
return d.restoreSnapshot(ctx, accountName, req, accountKey, shareOptions, storageEndpointSuffix)
733736
case *csi.VolumeContentSource_Volume:
734737
return d.copyFileShare(ctx, req, accountKey, shareOptions, storageEndpointSuffix)
735738
default:
@@ -1083,6 +1086,91 @@ func (d *Driver) ListSnapshots(ctx context.Context, req *csi.ListSnapshotsReques
10831086
return nil, status.Error(codes.Unimplemented, "")
10841087
}
10851088

1089+
// restoreSnapshot restores from a snapshot
1090+
func (d *Driver) restoreSnapshot(ctx context.Context, dstAccountName string, req *csi.CreateVolumeRequest, accountKey string, shareOptions *fileclient.ShareOptions, storageEndpointSuffix string) error {
1091+
if shareOptions.Protocol == storage.EnabledProtocolsNFS {
1092+
return fmt.Errorf("protocol nfs is not supported for snapshot restore")
1093+
}
1094+
var sourceSnapshotID string
1095+
if req.GetVolumeContentSource() != nil && req.GetVolumeContentSource().GetSnapshot() != nil {
1096+
sourceSnapshotID = req.GetVolumeContentSource().GetSnapshot().GetSnapshotId()
1097+
}
1098+
resourceGroupName, srcAccountName, srcFileShareName, _, _, _, err := GetFileShareInfo(sourceSnapshotID) //nolint:dogsled
1099+
if err != nil {
1100+
return status.Error(codes.NotFound, err.Error())
1101+
}
1102+
snapshot, err := getSnapshot(sourceSnapshotID)
1103+
if err != nil {
1104+
return status.Error(codes.NotFound, err.Error())
1105+
}
1106+
dstFileShareName := shareOptions.Name
1107+
if srcFileShareName == "" || dstFileShareName == "" {
1108+
return fmt.Errorf("srcFileShareName(%s) or dstFileShareName(%s) is empty", srcFileShareName, dstFileShareName)
1109+
}
1110+
1111+
srcAccountKey := accountKey
1112+
if srcAccountName != dstAccountName {
1113+
srcAccountKey, err = d.cloud.GetStorageAccesskey(ctx, d.cloud.SubscriptionID, srcAccountName, resourceGroupName, true)
1114+
if err != nil {
1115+
return fmt.Errorf("get src accountkey get err: %v", err)
1116+
}
1117+
}
1118+
1119+
klog.V(2).Infof("generate sas token for resource group(%s) src account(%s)", resourceGroupName, srcAccountName)
1120+
srcAccountSasToken, err := generateSASToken(srcAccountName, srcAccountKey, storageEndpointSuffix, d.sasTokenExpirationMinutes)
1121+
if err != nil {
1122+
return err
1123+
}
1124+
klog.V(2).Infof("generate sas token for resource group(%s) dst account(%s)", resourceGroupName, dstAccountName)
1125+
dstAccountSasToken, err := generateSASToken(dstAccountName, accountKey, storageEndpointSuffix, d.sasTokenExpirationMinutes)
1126+
if err != nil {
1127+
return err
1128+
}
1129+
1130+
srcPath := fmt.Sprintf("https://%s.file.%s/%s%s&sharesnapshot=%s", srcAccountName, storageEndpointSuffix, srcFileShareName, srcAccountSasToken, snapshot)
1131+
dstPath := fmt.Sprintf("https://%s.file.%s/%s%s", dstAccountName, storageEndpointSuffix, dstFileShareName, dstAccountSasToken)
1132+
1133+
return d.copyFileShareByAzcopy(srcFileShareName, dstFileShareName, srcPath, dstPath, snapshot)
1134+
}
1135+
1136+
func (d *Driver) copyFileShareByAzcopy(srcFileShareName, dstFileShareName, srcPath, dstPath, snapshot string) error {
1137+
timeAfter := time.After(waitForCopyTimeout)
1138+
timeTick := time.Tick(waitForCopyInterval)
1139+
if snapshot != "" {
1140+
srcFileShareName = fmt.Sprintf("%s(snapshot: %s)", srcFileShareName, snapshot)
1141+
}
1142+
1143+
aFunc := d.azcopyFunc
1144+
jobState, percent, err := aFunc.GetAzcopyJob(dstFileShareName)
1145+
klog.V(2).Infof("azcopy job status: %s, copy percent: %s%%, error: %v", jobState, percent, err)
1146+
if jobState == util.AzcopyJobError || jobState == util.AzcopyJobCompleted {
1147+
return err
1148+
}
1149+
klog.V(2).Infof("begin to copy fileshare %s to %s", srcFileShareName, dstFileShareName)
1150+
for {
1151+
select {
1152+
case <-timeTick:
1153+
jobState, percent, err := aFunc.GetAzcopyJob(dstFileShareName)
1154+
klog.V(2).Infof("azcopy job status: %s, copy percent: %s%%, error: %v", jobState, percent, err)
1155+
switch jobState {
1156+
case util.AzcopyJobError, util.AzcopyJobCompleted:
1157+
return err
1158+
case util.AzcopyJobNotFound:
1159+
klog.V(2).Infof("copy fileshare %s to %s", srcFileShareName, dstFileShareName)
1160+
out, copyErr := exec.Command("azcopy", "copy", srcPath, dstPath, "--recursive", "--check-length=false").CombinedOutput()
1161+
if copyErr != nil {
1162+
klog.Warningf("CopyFileShare(%s) failed with error(%v): %v", dstFileShareName, copyErr, string(out))
1163+
} else {
1164+
klog.V(2).Infof("copied fileshare %s to %s successfully", srcFileShareName, dstFileShareName)
1165+
}
1166+
return copyErr
1167+
}
1168+
case <-timeAfter:
1169+
return fmt.Errorf("timeout waiting for copy fileshare %s to %s succeed", srcFileShareName, dstFileShareName)
1170+
}
1171+
}
1172+
}
1173+
10861174
// ControllerExpandVolume controller expand volume
10871175
func (d *Driver) ControllerExpandVolume(ctx context.Context, req *csi.ControllerExpandVolumeRequest) (*csi.ControllerExpandVolumeResponse, error) {
10881176
volumeID := req.GetVolumeId()

0 commit comments

Comments
 (0)