Skip to content

Commit cd50d48

Browse files
authored
Merge pull request #426 from wozniakjan/volume_to_volume_copy
feat: volume clone from source volume
2 parents af29ce4 + 784da6f commit cd50d48

File tree

5 files changed

+85
-0
lines changed

5 files changed

+85
-0
lines changed

deploy/example/pvc-volume-clone.yaml

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-nfs-clone
6+
namespace: default
7+
spec:
8+
accessModes:
9+
- ReadWriteMany
10+
resources:
11+
requests:
12+
storage: 10Gi
13+
storageClassName: nfs-csi
14+
dataSource:
15+
kind: PersistentVolumeClaim
16+
name: pvc-nfs-dynamic

pkg/nfs/controllerserver.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package nfs
1919
import (
2020
"fmt"
2121
"os"
22+
"os/exec"
2223
"path/filepath"
2324
"regexp"
2425
"strconv"
@@ -143,12 +144,19 @@ func (cs *ControllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol
143144
}
144145
}
145146

147+
if req.GetVolumeContentSource() != nil {
148+
if err := cs.copyVolume(ctx, req, nfsVol); err != nil {
149+
return nil, err
150+
}
151+
}
152+
146153
setKeyValueInMap(parameters, paramSubDir, nfsVol.subDir)
147154
return &csi.CreateVolumeResponse{
148155
Volume: &csi.Volume{
149156
VolumeId: nfsVol.id,
150157
CapacityBytes: 0, // by setting it to zero, Provisioner will use PVC requested size as PV size
151158
VolumeContext: parameters,
159+
ContentSource: req.GetVolumeContentSource(),
152160
},
153161
}, nil
154162
}
@@ -307,6 +315,58 @@ func (cs *ControllerServer) internalUnmount(ctx context.Context, vol *nfsVolume)
307315
return err
308316
}
309317

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+
310370
// newNFSVolume Convert VolumeCreate parameters to an nfsVolume
311371
func newNFSVolume(name string, size int64, params map[string]string) (*nfsVolume, error) {
312372
var server, baseDir, subDir string

pkg/nfs/controllerserver_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,13 @@ func TestControllerGetCapabilities(t *testing.T) {
325325
},
326326
},
327327
},
328+
{
329+
Type: &csi.ControllerServiceCapability_Rpc{
330+
Rpc: &csi.ControllerServiceCapability_RPC{
331+
Type: csi.ControllerServiceCapability_RPC_CLONE_VOLUME,
332+
},
333+
},
334+
},
328335
},
329336
},
330337
expectedErr: nil,

pkg/nfs/nfs.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ func NewDriver(options *DriverOptions) *Driver {
8484
n.AddControllerServiceCapabilities([]csi.ControllerServiceCapability_RPC_Type{
8585
csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
8686
csi.ControllerServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
87+
csi.ControllerServiceCapability_RPC_CLONE_VOLUME,
8788
})
8889

8990
n.AddNodeServiceCapabilities([]csi.NodeServiceCapability_RPC_Type{

test/external-e2e/testdriver.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ DriverInfo:
1212
multipods: true
1313
RWX: true
1414
fsGroup: true
15+
pvcDataSource: true
1516
InlineVolumes:
1617
- Attributes:
1718
server: nfs-server.default.svc.cluster.local

0 commit comments

Comments
 (0)