Skip to content

Commit 1f26dd2

Browse files
authored
Merge pull request #58 from j-griffith/add_clone_support
Implement Cloning for csi-hostpath driver
2 parents f4f3b14 + 959c467 commit 1f26dd2

File tree

3 files changed

+62
-0
lines changed

3 files changed

+62
-0
lines changed

examples/csi-clone.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
apiVersion: v1
2+
kind: PersistentVolumeClaim
3+
metadata:
4+
name: hp-pvc-clone
5+
spec:
6+
storageClassName: csi-hostpath-sc
7+
dataSource:
8+
name: src-hp-pvc
9+
kind: PersistentVolumeClaim
10+
apiGroup: ""
11+
accessModes:
12+
- ReadWriteOnce
13+
resources:
14+
requests:
15+
storage: 1Gi

pkg/hostpath/controllerserver.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ func NewControllerServer(ephemeral bool) *controllerServer {
6565
csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
6666
csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT,
6767
csi.ControllerServiceCapability_RPC_LIST_SNAPSHOTS,
68+
csi.ControllerServiceCapability_RPC_CLONE_VOLUME,
6869
}),
6970
}
7071
}
@@ -178,19 +179,47 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol
178179
snapshotId := contentSource.GetSnapshot().GetSnapshotId()
179180
snapshot, ok := hostPathVolumeSnapshots[snapshotId]
180181
if !ok {
182+
deleteHostpathVolume(volumeID)
181183
return nil, status.Errorf(codes.NotFound, "cannot find snapshot %v", snapshotId)
182184
}
183185
if snapshot.ReadyToUse != true {
186+
deleteHostpathVolume(volumeID)
184187
return nil, status.Errorf(codes.Internal, "Snapshot %v is not yet ready to use.", snapshotId)
185188
}
186189
snapshotPath := snapshot.Path
187190
args := []string{"zxvf", snapshotPath, "-C", path}
188191
executor := utilexec.New()
189192
out, err := executor.Command("tar", args...).CombinedOutput()
190193
if err != nil {
194+
deleteHostpathVolume(volumeID)
191195
return nil, status.Error(codes.Internal, fmt.Sprintf("failed pre-populate data for volume: %v: %s", err, out))
192196
}
193197
}
198+
if srcVolume := contentSource.GetVolume(); srcVolume != nil {
199+
srcVolumeID := srcVolume.GetVolumeId()
200+
hostPathVolume, ok := hostPathVolumes[srcVolumeID]
201+
if !ok {
202+
deleteHostpathVolume(volumeID)
203+
return nil, status.Error(codes.NotFound, "source volumeID does not exist, are source/destination in the same storage class?")
204+
}
205+
srcPath := hostPathVolume.VolPath
206+
isEmpty, err := hostPathIsEmpty(srcPath)
207+
if err != nil {
208+
deleteHostpathVolume(volumeID)
209+
return nil, status.Error(codes.Internal, fmt.Sprintf("failed verification check of source hostpath volume: %s: %v", srcVolumeID, err))
210+
}
211+
212+
// 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
213+
if !isEmpty {
214+
args := []string{"-a", srcPath + "/*", path + "/"}
215+
executor := utilexec.New()
216+
out, err := executor.Command("cp", args...).CombinedOutput()
217+
if err != nil {
218+
deleteHostpathVolume(volumeID)
219+
return nil, status.Error(codes.Internal, fmt.Sprintf("failed pre-populate data (clone) for volume: %s: %s", volumeID, out))
220+
}
221+
}
222+
}
194223
}
195224

196225
createVolumeResponse := &csi.CreateVolumeResponse{}

pkg/hostpath/hostpath.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package hostpath
1818

1919
import (
2020
"fmt"
21+
"io"
2122
"os"
2223

2324
"github.com/golang/glog"
@@ -169,10 +170,27 @@ func createHostpathVolume(volID, name string, cap int64, volAccessType accessTyp
169170

170171
// deleteVolume deletes the directory for the hostpath volume.
171172
func deleteHostpathVolume(volID string) error {
173+
glog.V(4).Infof("deleting hostpath volume: %s", volID)
172174
path := getVolumePath(volID)
173175
if err := os.RemoveAll(path); err != nil {
174176
return err
175177
}
176178
delete(hostPathVolumes, volID)
177179
return nil
178180
}
181+
182+
// hostPathIsEmpty is a simple check to determine if the specified hostpath directory
183+
// is empty or not.
184+
func hostPathIsEmpty(p string) (bool, error) {
185+
f, err := os.Open(p)
186+
if err != nil {
187+
return true, fmt.Errorf("unable to open hostpath volume, error: %v", err)
188+
}
189+
defer f.Close()
190+
191+
_, err = f.Readdir(1)
192+
if err == io.EOF {
193+
return true, nil
194+
}
195+
return false, err
196+
}

0 commit comments

Comments
 (0)