@@ -22,6 +22,7 @@ import (
2222	"encoding/binary" 
2323	"fmt" 
2424	"net/url" 
25+ 	"os/exec" 
2526	"strconv" 
2627	"strings" 
2728	"sync" 
@@ -32,6 +33,8 @@ import (
3233	"github.com/container-storage-interface/spec/lib/go/csi" 
3334	"github.com/pborman/uuid" 
3435	"github.com/rubiojr/go-vhd/vhd" 
36+ 	"google.golang.org/grpc/codes" 
37+ 	"google.golang.org/grpc/status" 
3538
3639	v1 "k8s.io/api/core/v1" 
3740	"k8s.io/apimachinery/pkg/api/errors" 
@@ -174,6 +177,9 @@ const (
174177	SnapshotID        =  "snapshot_id" 
175178
176179	FSGroupChangeNone  =  "None" 
180+ 
181+ 	waitForCopyInterval  =  5  *  time .Second 
182+ 	waitForCopyTimeout   =  3  *  time .Minute 
177183)
178184
179185var  (
@@ -209,6 +215,7 @@ type DriverOptions struct {
209215	SkipMatchingTagCacheExpireInMinutes     int 
210216	VolStatsCacheExpireInMinutes            int 
211217	PrintVolumeStatsCallLogs                bool 
218+ 	SasTokenExpirationMinutes               int 
212219}
213220
214221// Driver implements all interfaces of CSI drivers 
@@ -260,6 +267,8 @@ type Driver struct {
260267	resizeFileShareFailureCache  azcache.Resource 
261268	// a timed cache storing volume stats <volumeID, volumeStats> 
262269	volStatsCache  azcache.Resource 
270+ 	// sas expiry time for azcopy in volume clone 
271+ 	sasTokenExpirationMinutes  int 
263272}
264273
265274// NewDriver Creates a NewCSIDriver object. Assumes vendor version is equal to driver version & 
@@ -287,6 +296,7 @@ func NewDriver(options *DriverOptions) *Driver {
287296	driver .appendClosetimeoOption  =  options .AppendClosetimeoOption 
288297	driver .appendNoShareSockOption  =  options .AppendNoShareSockOption 
289298	driver .printVolumeStatsCallLogs  =  options .PrintVolumeStatsCallLogs 
299+ 	driver .sasTokenExpirationMinutes  =  options .SasTokenExpirationMinutes 
290300	driver .volLockMap  =  newLockMap ()
291301	driver .subnetLockMap  =  newLockMap ()
292302	driver .volumeLocks  =  newVolumeLocks ()
@@ -363,6 +373,7 @@ func (d *Driver) Run(endpoint, kubeconfig string, testBool bool) {
363373			csi .ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT ,
364374			csi .ControllerServiceCapability_RPC_EXPAND_VOLUME ,
365375			csi .ControllerServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER ,
376+ 			csi .ControllerServiceCapability_RPC_CLONE_VOLUME ,
366377		})
367378	d .AddVolumeCapabilityAccessModes ([]csi.VolumeCapability_AccessMode_Mode {
368379		csi .VolumeCapability_AccessMode_SINGLE_NODE_WRITER ,
@@ -889,6 +900,65 @@ func (d *Driver) ResizeFileShare(ctx context.Context, subsID, resourceGroup, acc
889900	})
890901}
891902
903+ // CopyFileShare copies a fileshare in the same storage account 
904+ func  (d  * Driver ) copyFileShare (ctx  context.Context , req  * csi.CreateVolumeRequest , accountKey  string , shareOptions  * fileclient.ShareOptions , storageEndpointSuffix  string ) error  {
905+ 	if  shareOptions .Protocol  ==  storage .EnabledProtocolsNFS  {
906+ 		return  fmt .Errorf ("protocol nfs is not supported for volume cloning" )
907+ 	}
908+ 	var  sourceVolumeID  string 
909+ 	if  req .GetVolumeContentSource () !=  nil  &&  req .GetVolumeContentSource ().GetVolume () !=  nil  {
910+ 		sourceVolumeID  =  req .GetVolumeContentSource ().GetVolume ().GetVolumeId ()
911+ 	}
912+ 	resourceGroupName , accountName , srcFileShareName , _ , _ , _ , err  :=  GetFileShareInfo (sourceVolumeID ) //nolint:dogsled 
913+ 	if  err  !=  nil  {
914+ 		return  status .Error (codes .NotFound , err .Error ())
915+ 	}
916+ 	dstFileShareName  :=  shareOptions .Name 
917+ 	if  srcFileShareName  ==  ""  ||  dstFileShareName  ==  ""  {
918+ 		return  fmt .Errorf ("srcFileShareName(%s) or dstFileShareName(%s) is empty" , srcFileShareName , dstFileShareName )
919+ 	}
920+ 
921+ 	klog .V (2 ).Infof ("generate sas token for account(%s)" , accountName )
922+ 	accountSasToken , genErr  :=  generateSASToken (accountName , accountKey , storageEndpointSuffix , d .sasTokenExpirationMinutes )
923+ 	if  genErr  !=  nil  {
924+ 		return  genErr 
925+ 	}
926+ 	klog .V (2 ).Infof ("begin to copy fileshare %s to %s" , srcFileShareName , dstFileShareName )
927+ 
928+ 	timeAfter  :=  time .After (waitForCopyTimeout )
929+ 	timeTick  :=  time .Tick (waitForCopyInterval )
930+ 	srcPath  :=  fmt .Sprintf ("https://%s.file.%s/%s%s" , accountName , storageEndpointSuffix , srcFileShareName , accountSasToken )
931+ 	dstPath  :=  fmt .Sprintf ("https://%s.file.%s/%s%s" , accountName , storageEndpointSuffix , dstFileShareName , accountSasToken )
932+ 
933+ 	jobState , percent , err  :=  getAzcopyJob (dstFileShareName )
934+ 	klog .V (2 ).Infof ("azcopy job status: %s, copy percent: %s%, error: %v" , jobState , percent , err )
935+ 	if  jobState  ==  AzcopyJobError  ||  jobState  ==  AzcopyJobCompleted  {
936+ 		return  err 
937+ 	}
938+ 	for  {
939+ 		select  {
940+ 		case  <- timeTick :
941+ 			jobState , percent , err  :=  getAzcopyJob (dstFileShareName )
942+ 			klog .V (2 ).Infof ("azcopy job status: %s, copy percent: %s%, error: %v" , jobState , percent , err )
943+ 			switch  jobState  {
944+ 			case  AzcopyJobError , AzcopyJobCompleted :
945+ 				return  err 
946+ 			case  AzcopyJobNotFound :
947+ 				klog .V (2 ).Infof ("copy fileshare %s to %s" , srcFileShareName , dstFileShareName )
948+ 				out , copyErr  :=  exec .Command ("azcopy" , "copy" , srcPath , dstPath , "--recursive" , "--check-length=false" ).CombinedOutput ()
949+ 				if  copyErr  !=  nil  {
950+ 					klog .Warningf ("CopyFileShare(%s, %s, %s) failed with error(%v): %v" , resourceGroupName , accountName , dstFileShareName , copyErr , string (out ))
951+ 				} else  {
952+ 					klog .V (2 ).Infof ("copied fileshare %s to %s successfully" , srcFileShareName , dstFileShareName )
953+ 				}
954+ 				return  copyErr 
955+ 			}
956+ 		case  <- timeAfter :
957+ 			return  fmt .Errorf ("timeout waiting for copy fileshare %s to %s succeed" , srcFileShareName , dstFileShareName )
958+ 		}
959+ 	}
960+ }
961+ 
892962// GetTotalAccountQuota returns the total quota in GB of all file shares in the storage account and the number of file shares 
893963func  (d  * Driver ) GetTotalAccountQuota (ctx  context.Context , subsID , resourceGroup , accountName  string ) (int32 , int32 , error ) {
894964	fileshares , err  :=  d .cloud .FileClient .WithSubscriptionID (subsID ).ListFileShare (ctx , resourceGroup , accountName , "" , "" )
0 commit comments