@@ -19,6 +19,7 @@ package nfs
19
19
import (
20
20
"fmt"
21
21
"os"
22
+ "os/exec"
22
23
"path/filepath"
23
24
"regexp"
24
25
"strconv"
@@ -143,12 +144,19 @@ func (cs *ControllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol
143
144
}
144
145
}
145
146
147
+ if req .GetVolumeContentSource () != nil {
148
+ if err := cs .copyVolume (ctx , req , nfsVol ); err != nil {
149
+ return nil , err
150
+ }
151
+ }
152
+
146
153
setKeyValueInMap (parameters , paramSubDir , nfsVol .subDir )
147
154
return & csi.CreateVolumeResponse {
148
155
Volume : & csi.Volume {
149
156
VolumeId : nfsVol .id ,
150
157
CapacityBytes : 0 , // by setting it to zero, Provisioner will use PVC requested size as PV size
151
158
VolumeContext : parameters ,
159
+ ContentSource : req .GetVolumeContentSource (),
152
160
},
153
161
}, nil
154
162
}
@@ -307,6 +315,58 @@ func (cs *ControllerServer) internalUnmount(ctx context.Context, vol *nfsVolume)
307
315
return err
308
316
}
309
317
318
+ func (cs * ControllerServer ) copyFromVolume (ctx context.Context , req * csi.CreateVolumeRequest , dstVol * nfsVolume ) error {
319
+ srcVol , err := getNfsVolFromID (req .GetVolumeContentSource ().GetVolume ().GetVolumeId ())
320
+ if err != nil {
321
+ return status .Error (codes .NotFound , err .Error ())
322
+ }
323
+ // Note that the source path must include trailing '/.', can't use 'filepath.Join()' as it performs path cleaning
324
+ srcPath := fmt .Sprintf ("%v/." , getInternalVolumePath (cs .Driver .workingMountDir , srcVol ))
325
+ dstPath := getInternalVolumePath (cs .Driver .workingMountDir , dstVol )
326
+ klog .V (2 ).Infof ("copy volume from volume %v -> %v" , srcPath , dstPath )
327
+
328
+ var volCap * csi.VolumeCapability
329
+ if len (req .GetVolumeCapabilities ()) > 0 {
330
+ volCap = req .GetVolumeCapabilities ()[0 ]
331
+ }
332
+ if err = cs .internalMount (ctx , srcVol , nil , volCap ); err != nil {
333
+ return status .Errorf (codes .Internal , "failed to mount src nfs server: %v" , err .Error ())
334
+ }
335
+ defer func () {
336
+ if err = cs .internalUnmount (ctx , srcVol ); err != nil {
337
+ klog .Warningf ("failed to unmount nfs server: %v" , err .Error ())
338
+ }
339
+ }()
340
+ if err = cs .internalMount (ctx , dstVol , nil , volCap ); err != nil {
341
+ return status .Errorf (codes .Internal , "failed to mount dst nfs server: %v" , err .Error ())
342
+ }
343
+ defer func () {
344
+ if err = cs .internalUnmount (ctx , dstVol ); err != nil {
345
+ klog .Warningf ("failed to unmount dst nfs server: %v" , err .Error ())
346
+ }
347
+ }()
348
+
349
+ // recursive 'cp' with '-a' to handle symlinks
350
+ out , err := exec .Command ("cp" , "-a" , srcPath , dstPath ).CombinedOutput ()
351
+ if err != nil {
352
+ return status .Error (codes .Internal , fmt .Sprintf ("%v: %v" , err , string (out )))
353
+ }
354
+ klog .V (2 ).Infof ("copied %s -> %s" , srcPath , dstPath )
355
+ return nil
356
+ }
357
+
358
+ func (cs * ControllerServer ) copyVolume (ctx context.Context , req * csi.CreateVolumeRequest , vol * nfsVolume ) error {
359
+ vs := req .VolumeContentSource
360
+ switch vs .Type .(type ) {
361
+ case * csi.VolumeContentSource_Snapshot :
362
+ return status .Error (codes .Unimplemented , "Currently only volume copy from another volume is supported" )
363
+ case * csi.VolumeContentSource_Volume :
364
+ return cs .copyFromVolume (ctx , req , vol )
365
+ default :
366
+ return status .Errorf (codes .InvalidArgument , "%v not a proper volume source" , vs )
367
+ }
368
+ }
369
+
310
370
// newNFSVolume Convert VolumeCreate parameters to an nfsVolume
311
371
func newNFSVolume (name string , size int64 , params map [string ]string ) (* nfsVolume , error ) {
312
372
var server , baseDir , subDir string
0 commit comments