From 959c467ffa2be83159c8908de9f945b563fe6d70 Mon Sep 17 00:00:00 2001 From: j-griffith Date: Mon, 10 Jun 2019 15:31:54 -0600 Subject: [PATCH] Implement Cloning for csi-hostpath driver This PR adds support for cloning of hostpath volumes. --- examples/csi-clone.yaml | 15 +++++++++++++++ pkg/hostpath/controllerserver.go | 29 +++++++++++++++++++++++++++++ pkg/hostpath/hostpath.go | 18 ++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 examples/csi-clone.yaml diff --git a/examples/csi-clone.yaml b/examples/csi-clone.yaml new file mode 100644 index 000000000..fa0964484 --- /dev/null +++ b/examples/csi-clone.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: hp-pvc-clone +spec: + storageClassName: csi-hostpath-sc + dataSource: + name: src-hp-pvc + kind: PersistentVolumeClaim + apiGroup: "" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi diff --git a/pkg/hostpath/controllerserver.go b/pkg/hostpath/controllerserver.go index 17755dfe7..5fbe65526 100644 --- a/pkg/hostpath/controllerserver.go +++ b/pkg/hostpath/controllerserver.go @@ -65,6 +65,7 @@ func NewControllerServer(ephemeral bool) *controllerServer { csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME, csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT, csi.ControllerServiceCapability_RPC_LIST_SNAPSHOTS, + csi.ControllerServiceCapability_RPC_CLONE_VOLUME, }), } } @@ -178,9 +179,11 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol snapshotId := contentSource.GetSnapshot().GetSnapshotId() snapshot, ok := hostPathVolumeSnapshots[snapshotId] if !ok { + deleteHostpathVolume(volumeID) return nil, status.Errorf(codes.NotFound, "cannot find snapshot %v", snapshotId) } if snapshot.ReadyToUse != true { + deleteHostpathVolume(volumeID) return nil, status.Errorf(codes.Internal, "Snapshot %v is not yet ready to use.", snapshotId) } snapshotPath := snapshot.Path @@ -188,9 +191,35 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol executor := utilexec.New() out, err := executor.Command("tar", args...).CombinedOutput() if err != nil { + deleteHostpathVolume(volumeID) return nil, status.Error(codes.Internal, fmt.Sprintf("failed pre-populate data for volume: %v: %s", err, out)) } } + if srcVolume := contentSource.GetVolume(); srcVolume != nil { + srcVolumeID := srcVolume.GetVolumeId() + hostPathVolume, ok := hostPathVolumes[srcVolumeID] + if !ok { + deleteHostpathVolume(volumeID) + return nil, status.Error(codes.NotFound, "source volumeID does not exist, are source/destination in the same storage class?") + } + srcPath := hostPathVolume.VolPath + isEmpty, err := hostPathIsEmpty(srcPath) + if err != nil { + deleteHostpathVolume(volumeID) + return nil, status.Error(codes.Internal, fmt.Sprintf("failed verification check of source hostpath volume: %s: %v", srcVolumeID, err)) + } + + // If the source hostpath volume is empty it's a noop and we just move along, otherwise the cp call will fail with a a file stat error DNE + if !isEmpty { + args := []string{"-a", srcPath + "/*", path + "/"} + executor := utilexec.New() + out, err := executor.Command("cp", args...).CombinedOutput() + if err != nil { + deleteHostpathVolume(volumeID) + return nil, status.Error(codes.Internal, fmt.Sprintf("failed pre-populate data (clone) for volume: %s: %s", volumeID, out)) + } + } + } } createVolumeResponse := &csi.CreateVolumeResponse{} diff --git a/pkg/hostpath/hostpath.go b/pkg/hostpath/hostpath.go index bea7950ab..725e00129 100644 --- a/pkg/hostpath/hostpath.go +++ b/pkg/hostpath/hostpath.go @@ -18,6 +18,7 @@ package hostpath import ( "fmt" + "io" "os" "github.com/golang/glog" @@ -169,6 +170,7 @@ func createHostpathVolume(volID, name string, cap int64, volAccessType accessTyp // deleteVolume deletes the directory for the hostpath volume. func deleteHostpathVolume(volID string) error { + glog.V(4).Infof("deleting hostpath volume: %s", volID) path := getVolumePath(volID) if err := os.RemoveAll(path); err != nil { return err @@ -176,3 +178,19 @@ func deleteHostpathVolume(volID string) error { delete(hostPathVolumes, volID) return nil } + +// hostPathIsEmpty is a simple check to determine if the specified hostpath directory +// is empty or not. +func hostPathIsEmpty(p string) (bool, error) { + f, err := os.Open(p) + if err != nil { + return true, fmt.Errorf("unable to open hostpath volume, error: %v", err) + } + defer f.Close() + + _, err = f.Readdir(1) + if err == io.EOF { + return true, nil + } + return false, err +}