@@ -18,6 +18,7 @@ package nfs
18
18
19
19
import (
20
20
"fmt"
21
+ "io/fs"
21
22
"os"
22
23
"os/exec"
23
24
"path/filepath"
@@ -29,6 +30,7 @@ import (
29
30
"golang.org/x/net/context"
30
31
"google.golang.org/grpc/codes"
31
32
"google.golang.org/grpc/status"
33
+ "google.golang.org/protobuf/types/known/timestamppb"
32
34
33
35
"k8s.io/klog/v2"
34
36
)
@@ -59,6 +61,35 @@ type nfsVolume struct {
59
61
onDelete string
60
62
}
61
63
64
+ // nfsSnapshot is an internal representation of a volume snapshot
65
+ // created by the provisioner.
66
+ type nfsSnapshot struct {
67
+ // Snapshot id.
68
+ id string
69
+ // Address of the NFS server.
70
+ // Matches paramServer.
71
+ server string
72
+ // Base directory of the NFS server to create snapshots under
73
+ // Matches paramShare.
74
+ baseDir string
75
+ // Snapshot name.
76
+ uuid string
77
+ // Source volume.
78
+ src string
79
+ }
80
+
81
+ func (snap nfsSnapshot ) archiveSubPath () string {
82
+ return snap .uuid
83
+ }
84
+
85
+ func (snap nfsSnapshot ) archiveName () string {
86
+ return fmt .Sprintf ("%v.tar.gz" , snap .src )
87
+ }
88
+
89
+ func (snap nfsSnapshot ) archivePath () string {
90
+ return filepath .Join (snap .archiveSubPath (), snap .archiveName ())
91
+ }
92
+
62
93
// Ordering of elements in the CSI volume id.
63
94
// ID is of the form {server}/{baseDir}/{subDir}.
64
95
// TODO: This volume id format limits baseDir and
@@ -74,6 +105,19 @@ const (
74
105
totalIDElements // Always last
75
106
)
76
107
108
+ // Ordering of elements in the CSI snapshot id.
109
+ // ID is of the form {server}/{baseDir}/{snapName}/{srcVolumeName}.
110
+ // Adding a new element should always go at the end
111
+ // before totalSnapIDElements
112
+ const (
113
+ idSnapServer = iota
114
+ idSnapBaseDir
115
+ idSnapUUID
116
+ idSnapArchivePath
117
+ idSnapArchiveName
118
+ totalIDSnapElements // Always last
119
+ )
120
+
77
121
// CreateVolume create a volume
78
122
func (cs * ControllerServer ) CreateVolume (ctx context.Context , req * csi.CreateVolumeRequest ) (* csi.CreateVolumeResponse , error ) {
79
123
name := req .GetName ()
@@ -263,11 +307,115 @@ func (cs *ControllerServer) ControllerGetCapabilities(ctx context.Context, req *
263
307
}
264
308
265
309
func (cs * ControllerServer ) CreateSnapshot (ctx context.Context , req * csi.CreateSnapshotRequest ) (* csi.CreateSnapshotResponse , error ) {
266
- return nil , status .Error (codes .Unimplemented , "" )
310
+ if len (req .GetName ()) == 0 {
311
+ return nil , status .Error (codes .InvalidArgument , "CreateSnapshot name must be provided" )
312
+ }
313
+ if len (req .GetSourceVolumeId ()) == 0 {
314
+ return nil , status .Error (codes .InvalidArgument , "CreateSnapshot source volume ID must be provided" )
315
+ }
316
+
317
+ srcVol , err := getNfsVolFromID (req .GetSourceVolumeId ())
318
+ if err != nil {
319
+ return nil , status .Errorf (codes .NotFound , "failed to create source volume: %v" , err )
320
+ }
321
+ snapshot , err := newNFSSnapshot (req .GetName (), req .GetParameters (), srcVol )
322
+ if err != nil {
323
+ return nil , status .Errorf (codes .NotFound , "failed to create nfsSnapshot: %v" , err )
324
+ }
325
+ snapVol := volumeFromSnapshot (snapshot )
326
+ if err = cs .internalMount (ctx , snapVol , nil , nil ); err != nil {
327
+ return nil , status .Errorf (codes .Internal , "failed to mount snapshot nfs server: %v" , err )
328
+ }
329
+ defer func () {
330
+ if err = cs .internalUnmount (ctx , snapVol ); err != nil {
331
+ klog .Warningf ("failed to unmount snapshot nfs server: %v" , err )
332
+ }
333
+ }()
334
+ snapInternalVolPath := filepath .Join (getInternalVolumePath (cs .Driver .workingMountDir , snapVol ), snapshot .archiveSubPath ())
335
+ if err = os .MkdirAll (snapInternalVolPath , 0777 ); err != nil {
336
+ return nil , status .Errorf (codes .Internal , "failed to make subdirectory: %v" , err )
337
+ }
338
+ if err := validateSnapshot (snapInternalVolPath , snapshot ); err != nil {
339
+ return nil , err
340
+ }
341
+
342
+ if err = cs .internalMount (ctx , srcVol , nil , nil ); err != nil {
343
+ return nil , status .Errorf (codes .Internal , "failed to mount src nfs server: %v" , err )
344
+ }
345
+ defer func () {
346
+ if err = cs .internalUnmount (ctx , srcVol ); err != nil {
347
+ klog .Warningf ("failed to unmount src nfs server: %v" , err )
348
+ }
349
+ }()
350
+
351
+ srcPath := getInternalVolumePath (cs .Driver .workingMountDir , srcVol )
352
+ dstPath := filepath .Join (snapInternalVolPath , snapshot .archiveName ())
353
+ klog .V (2 ).Infof ("archiving %v -> %v" , srcPath , dstPath )
354
+ out , err := exec .Command ("tar" , "-C" , srcPath , "-czvf" , dstPath , "." ).CombinedOutput ()
355
+ if err != nil {
356
+ return nil , status .Errorf (codes .Internal , "failed to create archive for snapshot: %v: %v" , err , string (out ))
357
+ }
358
+ klog .V (2 ).Infof ("archived %s -> %s" , srcPath , dstPath )
359
+
360
+ var snapshotSize int64
361
+ fi , err := os .Stat (dstPath )
362
+ if err != nil {
363
+ klog .Warningf ("failed to determine snapshot size: %v" , err )
364
+ } else {
365
+ snapshotSize = fi .Size ()
366
+ }
367
+ return & csi.CreateSnapshotResponse {
368
+ Snapshot : & csi.Snapshot {
369
+ SnapshotId : snapshot .id ,
370
+ SourceVolumeId : srcVol .id ,
371
+ SizeBytes : snapshotSize ,
372
+ CreationTime : timestamppb .Now (),
373
+ ReadyToUse : true ,
374
+ },
375
+ }, nil
267
376
}
268
377
269
378
func (cs * ControllerServer ) DeleteSnapshot (ctx context.Context , req * csi.DeleteSnapshotRequest ) (* csi.DeleteSnapshotResponse , error ) {
270
- return nil , status .Error (codes .Unimplemented , "" )
379
+ if len (req .GetSnapshotId ()) == 0 {
380
+ return nil , status .Error (codes .InvalidArgument , "Snapshot ID is required for deletion" )
381
+ }
382
+ snap , err := getNfsSnapFromID (req .GetSnapshotId ())
383
+ if err != nil {
384
+ // An invalid ID should be treated as doesn't exist
385
+ klog .Warningf ("failed to get nfs snapshot for id %v deletion: %v" , req .GetSnapshotId (), err )
386
+ return & csi.DeleteSnapshotResponse {}, nil
387
+ }
388
+
389
+ var volCap * csi.VolumeCapability
390
+ mountOptions := getMountOptions (req .GetSecrets ())
391
+ if mountOptions != "" {
392
+ klog .V (2 ).Infof ("DeleteSnapshot: found mountOptions(%s) for snapshot(%s)" , mountOptions , req .GetSnapshotId ())
393
+ volCap = & csi.VolumeCapability {
394
+ AccessType : & csi.VolumeCapability_Mount {
395
+ Mount : & csi.VolumeCapability_MountVolume {
396
+ MountFlags : []string {mountOptions },
397
+ },
398
+ },
399
+ }
400
+ }
401
+ vol := volumeFromSnapshot (snap )
402
+ if err = cs .internalMount (ctx , vol , nil , volCap ); err != nil {
403
+ return nil , status .Errorf (codes .Internal , "failed to mount nfs server for snapshot deletion: %v" , err )
404
+ }
405
+ defer func () {
406
+ if err = cs .internalUnmount (ctx , vol ); err != nil {
407
+ klog .Warningf ("failed to unmount nfs server after snapshot deletion: %v" , err )
408
+ }
409
+ }()
410
+
411
+ // delete snapshot archive
412
+ internalVolumePath := filepath .Join (getInternalVolumePath (cs .Driver .workingMountDir , vol ), snap .archiveSubPath ())
413
+ klog .V (2 ).Infof ("Removing snapshot archive at %v" , internalVolumePath )
414
+ if err = os .RemoveAll (internalVolumePath ); err != nil {
415
+ return nil , status .Errorf (codes .Internal , "failed to delete subdirectory: %v" , err .Error ())
416
+ }
417
+
418
+ return & csi.DeleteSnapshotResponse {}, nil
271
419
}
272
420
273
421
func (cs * ControllerServer ) ListSnapshots (ctx context.Context , req * csi.ListSnapshotsRequest ) (* csi.ListSnapshotsResponse , error ) {
@@ -325,6 +473,46 @@ func (cs *ControllerServer) internalUnmount(ctx context.Context, vol *nfsVolume)
325
473
return err
326
474
}
327
475
476
+ func (cs * ControllerServer ) copyFromSnapshot (ctx context.Context , req * csi.CreateVolumeRequest , dstVol * nfsVolume ) error {
477
+ snap , err := getNfsSnapFromID (req .VolumeContentSource .GetSnapshot ().GetSnapshotId ())
478
+ if err != nil {
479
+ return status .Error (codes .NotFound , err .Error ())
480
+ }
481
+ snapVol := volumeFromSnapshot (snap )
482
+
483
+ var volCap * csi.VolumeCapability
484
+ if len (req .GetVolumeCapabilities ()) > 0 {
485
+ volCap = req .GetVolumeCapabilities ()[0 ]
486
+ }
487
+
488
+ if err = cs .internalMount (ctx , snapVol , nil , volCap ); err != nil {
489
+ return status .Errorf (codes .Internal , "failed to mount src nfs server for snapshot volume copy: %v" , err )
490
+ }
491
+ defer func () {
492
+ if err = cs .internalUnmount (ctx , snapVol ); err != nil {
493
+ klog .Warningf ("failed to unmount src nfs server after snapshot volume copy: %v" , err )
494
+ }
495
+ }()
496
+ if err = cs .internalMount (ctx , dstVol , nil , volCap ); err != nil {
497
+ return status .Errorf (codes .Internal , "failed to mount dst nfs server for snapshot volume copy: %v" , err )
498
+ }
499
+ defer func () {
500
+ if err = cs .internalUnmount (ctx , dstVol ); err != nil {
501
+ klog .Warningf ("failed to unmount dst nfs server after snapshot volume copy: %v" , err )
502
+ }
503
+ }()
504
+
505
+ // untar snapshot archive to dst path
506
+ snapPath := filepath .Join (getInternalVolumePath (cs .Driver .workingMountDir , snapVol ), snap .archivePath ())
507
+ dstPath := getInternalVolumePath (cs .Driver .workingMountDir , dstVol )
508
+ klog .V (2 ).Infof ("copy volume from snapshot %v -> %v" , snapPath , dstPath )
509
+ out , err := exec .Command ("tar" , "-xzvf" , snapPath , "-C" , dstPath ).CombinedOutput ()
510
+ if err != nil {
511
+ return status .Errorf (codes .Internal , "failed to copy volume for snapshot: %v: %v" , err , string (out ))
512
+ }
513
+ return nil
514
+ }
515
+
328
516
func (cs * ControllerServer ) copyFromVolume (ctx context.Context , req * csi.CreateVolumeRequest , dstVol * nfsVolume ) error {
329
517
srcVol , err := getNfsVolFromID (req .GetVolumeContentSource ().GetVolume ().GetVolumeId ())
330
518
if err != nil {
@@ -340,26 +528,26 @@ func (cs *ControllerServer) copyFromVolume(ctx context.Context, req *csi.CreateV
340
528
volCap = req .GetVolumeCapabilities ()[0 ]
341
529
}
342
530
if err = cs .internalMount (ctx , srcVol , nil , volCap ); err != nil {
343
- return status .Errorf (codes .Internal , "failed to mount src nfs server: %v" , err . Error () )
531
+ return status .Errorf (codes .Internal , "failed to mount src nfs server: %v" , err )
344
532
}
345
533
defer func () {
346
534
if err = cs .internalUnmount (ctx , srcVol ); err != nil {
347
- klog .Warningf ("failed to unmount nfs server: %v" , err . Error () )
535
+ klog .Warningf ("failed to unmount nfs server: %v" , err )
348
536
}
349
537
}()
350
538
if err = cs .internalMount (ctx , dstVol , nil , volCap ); err != nil {
351
- return status .Errorf (codes .Internal , "failed to mount dst nfs server: %v" , err . Error () )
539
+ return status .Errorf (codes .Internal , "failed to mount dst nfs server: %v" , err )
352
540
}
353
541
defer func () {
354
542
if err = cs .internalUnmount (ctx , dstVol ); err != nil {
355
- klog .Warningf ("failed to unmount dst nfs server: %v" , err . Error () )
543
+ klog .Warningf ("failed to unmount dst nfs server: %v" , err )
356
544
}
357
545
}()
358
546
359
547
// recursive 'cp' with '-a' to handle symlinks
360
548
out , err := exec .Command ("cp" , "-a" , srcPath , dstPath ).CombinedOutput ()
361
549
if err != nil {
362
- return status .Error (codes .Internal , fmt . Sprintf ( " %v: %v" , err , string (out ) ))
550
+ return status .Errorf (codes .Internal , "failed to copy volume %v: %v" , err , string (out ))
363
551
}
364
552
klog .V (2 ).Infof ("copied %s -> %s" , srcPath , dstPath )
365
553
return nil
@@ -369,14 +557,48 @@ func (cs *ControllerServer) copyVolume(ctx context.Context, req *csi.CreateVolum
369
557
vs := req .VolumeContentSource
370
558
switch vs .Type .(type ) {
371
559
case * csi.VolumeContentSource_Snapshot :
372
- return status . Error ( codes . Unimplemented , "Currently only volume copy from another volume is supported" )
560
+ return cs . copyFromSnapshot ( ctx , req , vol )
373
561
case * csi.VolumeContentSource_Volume :
374
562
return cs .copyFromVolume (ctx , req , vol )
375
563
default :
376
564
return status .Errorf (codes .InvalidArgument , "%v not a proper volume source" , vs )
377
565
}
378
566
}
379
567
568
+ // newNFSSnapshot Convert VolumeSnapshot parameters to a nfsSnapshot
569
+ func newNFSSnapshot (name string , params map [string ]string , vol * nfsVolume ) (* nfsSnapshot , error ) {
570
+ server := vol .server
571
+ baseDir := vol .baseDir
572
+ for k , v := range params {
573
+ switch strings .ToLower (k ) {
574
+ case paramServer :
575
+ server = v
576
+ case paramShare :
577
+ baseDir = v
578
+ }
579
+ }
580
+
581
+ if server == "" {
582
+ return nil , fmt .Errorf ("%v is a required parameter" , paramServer )
583
+ }
584
+ snapshot := & nfsSnapshot {
585
+ server : server ,
586
+ baseDir : baseDir ,
587
+ uuid : name ,
588
+ }
589
+ if vol .subDir != "" {
590
+ snapshot .src = vol .subDir
591
+ }
592
+ if vol .uuid != "" {
593
+ snapshot .src = vol .uuid
594
+ }
595
+ if snapshot .src == "" {
596
+ return nil , fmt .Errorf ("missing required source volume name" )
597
+ }
598
+ snapshot .id = getSnapshotIDFromNfsSnapshot (snapshot )
599
+ return snapshot , nil
600
+ }
601
+
380
602
// newNFSVolume Convert VolumeCreate parameters to an nfsVolume
381
603
func newNFSVolume (name string , size int64 , params map [string ]string , defaultOnDeletePolicy string ) (* nfsVolume , error ) {
382
604
var server , baseDir , subDir , onDelete string
@@ -470,6 +692,17 @@ func getVolumeIDFromNfsVol(vol *nfsVolume) string {
470
692
return strings .Join (idElements , separator )
471
693
}
472
694
695
+ // Given a nfsSnapshot, return a CSI snapshot id.
696
+ func getSnapshotIDFromNfsSnapshot (snap * nfsSnapshot ) string {
697
+ idElements := make ([]string , totalIDSnapElements )
698
+ idElements [idSnapServer ] = strings .Trim (snap .server , "/" )
699
+ idElements [idSnapBaseDir ] = strings .Trim (snap .baseDir , "/" )
700
+ idElements [idSnapUUID ] = snap .uuid
701
+ idElements [idSnapArchivePath ] = snap .uuid
702
+ idElements [idSnapArchiveName ] = snap .src
703
+ return strings .Join (idElements , separator )
704
+ }
705
+
473
706
// Given a CSI volume id, return a nfsVolume
474
707
// sample volume Id:
475
708
//
@@ -513,6 +746,25 @@ func getNfsVolFromID(id string) (*nfsVolume, error) {
513
746
}, nil
514
747
}
515
748
749
+ // Given a CSI snapshot ID, return a nfsSnapshot
750
+ // sample snapshot ID:
751
+ //
752
+ // nfs-server.default.svc.cluster.local#share#snapshot-016f784f-56f4-44d1-9041-5f59e82dbce1#snapshot-016f784f-56f4-44d1-9041-5f59e82dbce1#pvc-4bcbf944-b6f7-4bd0-b50f-3c3dd00efc64
753
+ func getNfsSnapFromID (id string ) (* nfsSnapshot , error ) {
754
+ segments := strings .Split (id , separator )
755
+ if len (segments ) == totalIDSnapElements {
756
+ return & nfsSnapshot {
757
+ id : id ,
758
+ server : segments [idSnapServer ],
759
+ baseDir : segments [idSnapBaseDir ],
760
+ src : segments [idSnapArchiveName ],
761
+ uuid : segments [idSnapUUID ],
762
+ }, nil
763
+ }
764
+
765
+ return & nfsSnapshot {}, fmt .Errorf ("failed to create nfsSnapshot from snapshot ID" )
766
+ }
767
+
516
768
// isValidVolumeCapabilities validates the given VolumeCapability array is valid
517
769
func isValidVolumeCapabilities (volCaps []* csi.VolumeCapability ) error {
518
770
if len (volCaps ) == 0 {
@@ -525,3 +777,32 @@ func isValidVolumeCapabilities(volCaps []*csi.VolumeCapability) error {
525
777
}
526
778
return nil
527
779
}
780
+
781
+ // Validate snapshot after internal mount
782
+ func validateSnapshot (snapInternalVolPath string , snap * nfsSnapshot ) error {
783
+ return filepath .WalkDir (snapInternalVolPath , func (path string , d fs.DirEntry , err error ) error {
784
+ if path == snapInternalVolPath {
785
+ // skip root
786
+ return nil
787
+ }
788
+ if err != nil {
789
+ return err
790
+ }
791
+ if d .Name () != snap .archiveName () {
792
+ // there should be just one archive in the snapshot path and archive name should match
793
+ return status .Errorf (codes .AlreadyExists , "snapshot with the same name but different source volume ID already exists: found %q, desired %q" , d .Name (), snap .archiveName ())
794
+ }
795
+ return nil
796
+ })
797
+ }
798
+
799
+ // Volume for snapshot internal mount/unmount
800
+ func volumeFromSnapshot (snap * nfsSnapshot ) * nfsVolume {
801
+ return & nfsVolume {
802
+ id : snap .id ,
803
+ server : snap .server ,
804
+ baseDir : snap .baseDir ,
805
+ subDir : snap .baseDir ,
806
+ uuid : snap .uuid ,
807
+ }
808
+ }
0 commit comments