From bfe7fe469716c3938bbce391e6f9ea8c59af0139 Mon Sep 17 00:00:00 2001 From: Jan Wozniak Date: Thu, 9 Mar 2023 15:53:21 +0100 Subject: [PATCH 1/3] feat: volume clone from source volume --- deploy/example/pvc-volume-clone.yaml | 16 ++++++++ pkg/nfs/controllerserver.go | 60 ++++++++++++++++++++++++++++ pkg/nfs/controllerserver_test.go | 7 ++++ pkg/nfs/nfs.go | 1 + 4 files changed, 84 insertions(+) create mode 100644 deploy/example/pvc-volume-clone.yaml diff --git a/deploy/example/pvc-volume-clone.yaml b/deploy/example/pvc-volume-clone.yaml new file mode 100644 index 000000000..5b1575f34 --- /dev/null +++ b/deploy/example/pvc-volume-clone.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: pvc-nfs-clone + namespace: default +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 10Gi + storageClassName: nfs-csi + dataSource: + kind: PersistentVolumeClaim + name: pvc-nfs-dynamic diff --git a/pkg/nfs/controllerserver.go b/pkg/nfs/controllerserver.go index cf8f44d42..d43dca078 100644 --- a/pkg/nfs/controllerserver.go +++ b/pkg/nfs/controllerserver.go @@ -19,6 +19,7 @@ package nfs import ( "fmt" "os" + "os/exec" "path/filepath" "regexp" "strconv" @@ -143,12 +144,19 @@ func (cs *ControllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol } } + if req.GetVolumeContentSource() != nil { + if err := cs.copyVolume(ctx, req, nfsVol); err != nil { + return nil, err + } + } + setKeyValueInMap(parameters, paramSubDir, nfsVol.subDir) return &csi.CreateVolumeResponse{ Volume: &csi.Volume{ VolumeId: nfsVol.id, CapacityBytes: 0, // by setting it to zero, Provisioner will use PVC requested size as PV size VolumeContext: parameters, + ContentSource: req.GetVolumeContentSource(), }, }, nil } @@ -307,6 +315,58 @@ func (cs *ControllerServer) internalUnmount(ctx context.Context, vol *nfsVolume) return err } +func (cs *ControllerServer) copyFromVolume(ctx context.Context, req *csi.CreateVolumeRequest, dstVol *nfsVolume) error { + srcVol, err := getNfsVolFromID(req.GetVolumeContentSource().GetVolume().GetVolumeId()) + if err != nil { + return status.Error(codes.NotFound, err.Error()) + } + srcPath := getInternalVolumePath(cs.Driver.workingMountDir, srcVol) + dstPath := getInternalVolumePath(cs.Driver.workingMountDir, dstVol) + klog.V(2).Infof("copy volume from volume %v -> %v", srcPath, dstPath) + + var volCap *csi.VolumeCapability + if len(req.GetVolumeCapabilities()) > 0 { + volCap = req.GetVolumeCapabilities()[0] + } + if err = cs.internalMount(ctx, srcVol, nil, volCap); err != nil { + return status.Errorf(codes.Internal, "failed to mount src nfs server: %v", err.Error()) + } + defer func() { + if err = cs.internalUnmount(ctx, srcVol); err != nil { + klog.Warningf("failed to unmount nfs server: %v", err.Error()) + } + }() + if err = cs.internalMount(ctx, dstVol, nil, volCap); err != nil { + return status.Errorf(codes.Internal, "failed to mount dst nfs server: %v", err.Error()) + } + defer func() { + if err = cs.internalUnmount(ctx, dstVol); err != nil { + klog.Warningf("failed to unmount dst nfs server: %v", err.Error()) + } + }() + + // recursive 'cp' with '-a' to handle symlinks. Note that the source path must include trailing '/.', + // which is the reason why 'filepath.Join()' is not used as it would perform path cleaning + out, err := exec.Command("cp", "-a", fmt.Sprintf("%v%v.", srcPath, filepath.Separator), dstPath).CombinedOutput() + klog.V(2).Infof("copied %s -> %s output: %v", srcPath, dstPath, string(out)) + if err != nil { + return status.Error(codes.Internal, err.Error()) + } + return nil +} + +func (cs *ControllerServer) copyVolume(ctx context.Context, req *csi.CreateVolumeRequest, vol *nfsVolume) error { + vs := req.VolumeContentSource + switch vs.Type.(type) { + case *csi.VolumeContentSource_Snapshot: + return status.Error(codes.Unimplemented, "Currently only volume copy from another volume is supported") + case *csi.VolumeContentSource_Volume: + return cs.copyFromVolume(ctx, req, vol) + default: + return status.Errorf(codes.InvalidArgument, "%v not a proper volume source", vs) + } +} + // newNFSVolume Convert VolumeCreate parameters to an nfsVolume func newNFSVolume(name string, size int64, params map[string]string) (*nfsVolume, error) { var server, baseDir, subDir string diff --git a/pkg/nfs/controllerserver_test.go b/pkg/nfs/controllerserver_test.go index 9f3c8c871..692539795 100644 --- a/pkg/nfs/controllerserver_test.go +++ b/pkg/nfs/controllerserver_test.go @@ -325,6 +325,13 @@ func TestControllerGetCapabilities(t *testing.T) { }, }, }, + { + Type: &csi.ControllerServiceCapability_Rpc{ + Rpc: &csi.ControllerServiceCapability_RPC{ + Type: csi.ControllerServiceCapability_RPC_CLONE_VOLUME, + }, + }, + }, }, }, expectedErr: nil, diff --git a/pkg/nfs/nfs.go b/pkg/nfs/nfs.go index 039aea070..921a0fd14 100644 --- a/pkg/nfs/nfs.go +++ b/pkg/nfs/nfs.go @@ -84,6 +84,7 @@ func NewDriver(options *DriverOptions) *Driver { n.AddControllerServiceCapabilities([]csi.ControllerServiceCapability_RPC_Type{ csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME, csi.ControllerServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER, + csi.ControllerServiceCapability_RPC_CLONE_VOLUME, }) n.AddNodeServiceCapabilities([]csi.NodeServiceCapability_RPC_Type{ From 61a6301ff9b145a3e39c42eb3fbcd5776329c3a2 Mon Sep 17 00:00:00 2001 From: Jan Wozniak Date: Mon, 13 Mar 2023 11:19:34 +0100 Subject: [PATCH 2/3] test: execute datasource e2e tests --- test/external-e2e/testdriver.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/test/external-e2e/testdriver.yaml b/test/external-e2e/testdriver.yaml index 406d4c9dd..b63de8d90 100644 --- a/test/external-e2e/testdriver.yaml +++ b/test/external-e2e/testdriver.yaml @@ -12,6 +12,7 @@ DriverInfo: multipods: true RWX: true fsGroup: true + pvcDataSource: true InlineVolumes: - Attributes: server: nfs-server.default.svc.cluster.local From 784da6fa801d9aaa486c6fd133c428c78adbac06 Mon Sep 17 00:00:00 2001 From: Jan Wozniak Date: Tue, 14 Mar 2023 17:06:40 +0100 Subject: [PATCH 3/3] feat: volume copy src dir fix --- pkg/nfs/controllerserver.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/nfs/controllerserver.go b/pkg/nfs/controllerserver.go index d43dca078..65a74a78c 100644 --- a/pkg/nfs/controllerserver.go +++ b/pkg/nfs/controllerserver.go @@ -320,7 +320,8 @@ func (cs *ControllerServer) copyFromVolume(ctx context.Context, req *csi.CreateV if err != nil { return status.Error(codes.NotFound, err.Error()) } - srcPath := getInternalVolumePath(cs.Driver.workingMountDir, srcVol) + // Note that the source path must include trailing '/.', can't use 'filepath.Join()' as it performs path cleaning + srcPath := fmt.Sprintf("%v/.", getInternalVolumePath(cs.Driver.workingMountDir, srcVol)) dstPath := getInternalVolumePath(cs.Driver.workingMountDir, dstVol) klog.V(2).Infof("copy volume from volume %v -> %v", srcPath, dstPath) @@ -345,13 +346,12 @@ func (cs *ControllerServer) copyFromVolume(ctx context.Context, req *csi.CreateV } }() - // recursive 'cp' with '-a' to handle symlinks. Note that the source path must include trailing '/.', - // which is the reason why 'filepath.Join()' is not used as it would perform path cleaning - out, err := exec.Command("cp", "-a", fmt.Sprintf("%v%v.", srcPath, filepath.Separator), dstPath).CombinedOutput() - klog.V(2).Infof("copied %s -> %s output: %v", srcPath, dstPath, string(out)) + // recursive 'cp' with '-a' to handle symlinks + out, err := exec.Command("cp", "-a", srcPath, dstPath).CombinedOutput() if err != nil { - return status.Error(codes.Internal, err.Error()) + return status.Error(codes.Internal, fmt.Sprintf("%v: %v", err, string(out))) } + klog.V(2).Infof("copied %s -> %s", srcPath, dstPath) return nil }