Skip to content

Commit 5499dfe

Browse files
committed
Volume clone
1 parent edd107e commit 5499dfe

File tree

6 files changed

+275
-6
lines changed

6 files changed

+275
-6
lines changed

pkg/cloud/cloud_interface.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,11 @@ type Cloud interface {
2626
AttachDisk(volumeID string, nodeID string) (err error)
2727
DetachDisk(volumeID string, nodeID string) (err error)
2828
ResizeDisk(volumeID string, reqSize int64) (newSize int64, err error)
29+
CloneDisk(sourceVolumeName string, cloneVolumeName string) (disk *Disk, err error)
2930
WaitForVolumeState(volumeID, state string) error
31+
WaitForCloneStatus(taskId string) error
3032
GetDiskByName(name string) (disk *Disk, err error)
33+
GetDiskByNamePrefix(namePrefix string) (disk *Disk, err error)
3134
GetDiskByID(volumeID string) (disk *Disk, err error)
3235
GetPVMInstanceByName(instanceName string) (instance *PVMInstance, err error)
3336
GetPVMInstanceByID(instanceID string) (instance *PVMInstance, err error)

pkg/cloud/mocks/mock_cloud.go

Lines changed: 44 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/cloud/powervs.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ const (
5151
type powerVSCloud struct {
5252
pvmInstancesClient *instance.IBMPIInstanceClient
5353
volClient *instance.IBMPIVolumeClient
54+
cloneVolumeClient *instance.IBMPICloneVolumeClient
5455
}
5556

5657
type PVMInstance struct {
@@ -96,10 +97,12 @@ func newPowerVSCloud(cloudInstanceID, zone string, debug bool) (Cloud, error) {
9697
backgroundContext := context.Background()
9798
volClient := instance.NewIBMPIVolumeClient(backgroundContext, piSession, cloudInstanceID)
9899
pvmInstancesClient := instance.NewIBMPIInstanceClient(backgroundContext, piSession, cloudInstanceID)
100+
cloneVolumeClient := instance.NewIBMPICloneVolumeClient(backgroundContext, piSession, cloudInstanceID)
99101

100102
return &powerVSCloud{
101103
pvmInstancesClient: pvmInstancesClient,
102104
volClient: volClient,
105+
cloneVolumeClient: cloneVolumeClient,
103106
}, nil
104107
}
105108

@@ -209,6 +212,36 @@ func (p *powerVSCloud) ResizeDisk(volumeID string, reqSize int64) (newSize int64
209212
return int64(*v.Size), nil
210213
}
211214

215+
func (p *powerVSCloud) CloneDisk(sourceVolumeID string, cloneVolumeName string) (disk *Disk, err error) {
216+
_, err = p.GetDiskByID(sourceVolumeID)
217+
if err != nil {
218+
return nil, err
219+
}
220+
cloneVolumeReq := &models.VolumesCloneAsyncRequest{
221+
Name: &cloneVolumeName,
222+
VolumeIDs: []string{sourceVolumeID},
223+
}
224+
cloneTaskRef, err := p.cloneVolumeClient.Create(cloneVolumeReq)
225+
if err != nil {
226+
return nil, err
227+
}
228+
cloneTaskId := cloneTaskRef.CloneTaskID
229+
err = p.WaitForCloneStatus(*cloneTaskId)
230+
if err != nil {
231+
return nil, err
232+
}
233+
clonedVolumeDetails, err := p.cloneVolumeClient.Get(*cloneTaskId)
234+
if err != nil {
235+
return nil, err
236+
}
237+
clonedVolumeID := clonedVolumeDetails.ClonedVolumes[0].ClonedVolumeID
238+
err = p.WaitForVolumeState(clonedVolumeID, VolumeAvailableState)
239+
if err != nil {
240+
return nil, err
241+
}
242+
return p.GetDiskByID(clonedVolumeID)
243+
}
244+
212245
func (p *powerVSCloud) WaitForVolumeState(volumeID, state string) error {
213246
ctx := context.Background()
214247
return wait.PollUntilContextTimeout(ctx, PollInterval, PollTimeout, true, func(ctx context.Context) (bool, error) {
@@ -221,6 +254,21 @@ func (p *powerVSCloud) WaitForVolumeState(volumeID, state string) error {
221254
})
222255
}
223256

257+
func (p *powerVSCloud) WaitForCloneStatus(cloneTaskId string) error {
258+
err := wait.PollImmediate(PollInterval, PollTimeout, func() (bool, error) {
259+
c, err := p.cloneVolumeClient.Get(cloneTaskId)
260+
if err != nil {
261+
return false, err
262+
}
263+
spew.Dump(*c)
264+
return *c.Status == "completed", nil
265+
})
266+
if err != nil {
267+
return err
268+
}
269+
return nil
270+
}
271+
224272
func (p *powerVSCloud) GetDiskByName(name string) (disk *Disk, err error) {
225273
vols, err := p.volClient.GetAll()
226274
if err != nil {
@@ -242,6 +290,27 @@ func (p *powerVSCloud) GetDiskByName(name string) (disk *Disk, err error) {
242290
return nil, ErrNotFound
243291
}
244292

293+
func (p *powerVSCloud) GetDiskByNamePrefix(namePrefix string) (disk *Disk, err error) {
294+
vols, err := p.volClient.GetAll()
295+
if err != nil {
296+
return nil, err
297+
}
298+
for _, v := range vols.Volumes {
299+
if strings.HasPrefix(*v.Name, namePrefix) {
300+
return &Disk{
301+
Name: *v.Name,
302+
DiskType: *v.DiskType,
303+
VolumeID: *v.VolumeID,
304+
WWN: strings.ToLower(*v.Wwn),
305+
Shareable: *v.Shareable,
306+
CapacityGiB: int64(*v.Size),
307+
}, nil
308+
}
309+
}
310+
311+
return nil, ErrNotFound
312+
}
313+
245314
func (p *powerVSCloud) GetDiskByID(volumeID string) (disk *Disk, err error) {
246315
v, err := p.volClient.Get(volumeID)
247316
if err != nil {

pkg/driver/controller.go

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ var (
4545
csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
4646
csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME,
4747
csi.ControllerServiceCapability_RPC_EXPAND_VOLUME,
48+
csi.ControllerServiceCapability_RPC_CLONE_VOLUME,
4849
}
4950
)
5051

@@ -173,6 +174,47 @@ func (d *controllerService) CreateVolume(ctx context.Context, req *csi.CreateVol
173174
VolumeType: volumeType,
174175
}
175176

177+
if req.GetVolumeContentSource() != nil {
178+
volumeSource := req.VolumeContentSource
179+
switch volumeSource.Type.(type) {
180+
case *csi.VolumeContentSource_Volume:
181+
diskDetails, _ := d.cloud.GetDiskByNamePrefix("clone-" + volName)
182+
if diskDetails != nil {
183+
err := verifyVolumeDetails(opts, diskDetails)
184+
if err != nil {
185+
return nil, err
186+
}
187+
return newCreateVolumeResponse(diskDetails, req.VolumeContentSource), nil
188+
}
189+
if srcVolume := volumeSource.GetVolume(); srcVolume != nil {
190+
srcVolumeID := srcVolume.GetVolumeId()
191+
diskDetails, err := d.cloud.GetDiskByID(srcVolumeID)
192+
if err != nil {
193+
return nil, status.Errorf(codes.Internal, "Could not get the source volume %q: %v", srcVolumeID, err)
194+
}
195+
if util.GiBToBytes(diskDetails.CapacityGiB) != volSizeBytes {
196+
return nil, status.Errorf(codes.Internal, "Cannot clone volume %v, source volume size is not equal to the clone volume", srcVolumeID)
197+
}
198+
err = verifyVolumeDetails(opts, diskDetails)
199+
if err != nil {
200+
return nil, err
201+
}
202+
diskFromSourceVolume, err := d.cloud.CloneDisk(srcVolumeID, volName)
203+
if err != nil {
204+
return nil, status.Errorf(codes.Internal, "Could not create volume %q: %v", volName, err)
205+
}
206+
207+
cloneDiskDetails, err := d.cloud.GetDiskByID(diskFromSourceVolume.VolumeID)
208+
if err != nil {
209+
return nil, status.Errorf(codes.Internal, "Could not create volume %q: %v", volName, err)
210+
}
211+
return newCreateVolumeResponse(cloneDiskDetails, req.VolumeContentSource), nil
212+
}
213+
default:
214+
return nil, status.Errorf(codes.InvalidArgument, "%v not a proper volume source", volumeSource)
215+
}
216+
}
217+
176218
// check if disk exists
177219
// disk exists only if previous createVolume request fails due to any network/tcp error
178220
diskDetails, _ := d.cloud.GetDiskByName(volName)
@@ -186,14 +228,14 @@ func (d *controllerService) CreateVolume(ctx context.Context, req *csi.CreateVol
186228
if err != nil {
187229
return nil, status.Errorf(codes.Internal, "Disk already exists and not in expected state")
188230
}
189-
return newCreateVolumeResponse(diskDetails), nil
231+
return newCreateVolumeResponse(diskDetails, req.VolumeContentSource), nil
190232
}
191233

192234
disk, err := d.cloud.CreateDisk(volName, opts)
193235
if err != nil {
194236
return nil, status.Errorf(codes.Internal, "Could not create volume %q: %v", volName, err)
195237
}
196-
return newCreateVolumeResponse(disk), nil
238+
return newCreateVolumeResponse(disk, req.VolumeContentSource), nil
197239
}
198240

199241
func (d *controllerService) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) {
@@ -455,9 +497,7 @@ func (d *controllerService) ListSnapshots(ctx context.Context, req *csi.ListSnap
455497
return nil, status.Error(codes.Unimplemented, "")
456498
}
457499

458-
func newCreateVolumeResponse(disk *cloud.Disk) *csi.CreateVolumeResponse {
459-
var src *csi.VolumeContentSource
460-
500+
func newCreateVolumeResponse(disk *cloud.Disk, src *csi.VolumeContentSource) *csi.CreateVolumeResponse {
461501
return &csi.CreateVolumeResponse{
462502
Volume: &csi.Volume{
463503
VolumeId: disk.VolumeID,

pkg/driver/controller_test.go

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@ import (
1919
"reflect"
2020
"testing"
2121

22-
csi "github.com/container-storage-interface/spec/lib/go/csi"
22+
"github.com/container-storage-interface/spec/lib/go/csi"
2323
"go.uber.org/mock/gomock"
2424
"google.golang.org/grpc/codes"
2525
"google.golang.org/grpc/status"
26+
2627
"sigs.k8s.io/ibm-powervs-block-csi-driver/pkg/cloud"
2728
mocks "sigs.k8s.io/ibm-powervs-block-csi-driver/pkg/cloud/mocks"
2829
"sigs.k8s.io/ibm-powervs-block-csi-driver/pkg/util"
@@ -95,7 +96,106 @@ func TestCreateVolume(t *testing.T) {
9596
}
9697
},
9798
},
99+
{
100+
name: "success normal with datasource PVC",
101+
testFunc: func(t *testing.T) {
102+
req := &csi.CreateVolumeRequest{
103+
Name: "clone-volume-name",
104+
CapacityRange: stdCapRange,
105+
VolumeCapabilities: stdVolCap,
106+
Parameters: stdParams,
107+
VolumeContentSource: &csi.VolumeContentSource{
108+
Type: &csi.VolumeContentSource_Volume{
109+
Volume: &csi.VolumeContentSource_VolumeSource{
110+
VolumeId: "test-volume-src-100",
111+
},
112+
},
113+
},
114+
}
115+
116+
ctx := context.Background()
117+
118+
mockDisk := &cloud.Disk{
119+
VolumeID: req.Name,
120+
CapacityGiB: util.BytesToGiB(stdVolSize),
121+
DiskType: cloud.DefaultVolumeType,
122+
}
123+
mockSrcDisk := &cloud.Disk{
124+
VolumeID: "test-volume-src-100",
125+
CapacityGiB: util.BytesToGiB(stdVolSize),
126+
DiskType: cloud.DefaultVolumeType,
127+
}
128+
129+
mockCtl := gomock.NewController(t)
130+
defer mockCtl.Finish()
131+
132+
mockCloud := mocks.NewMockCloud(mockCtl)
133+
mockCloud.EXPECT().GetDiskByNamePrefix(gomock.Eq("clone-"+req.Name)).Return(nil, nil)
134+
mockCloud.EXPECT().GetDiskByID(gomock.Eq(mockSrcDisk.VolumeID)).Return(mockSrcDisk, nil)
135+
mockCloud.EXPECT().CloneDisk(gomock.Eq(mockSrcDisk.VolumeID), gomock.Eq(req.Name)).Return(mockDisk, nil)
136+
mockCloud.EXPECT().GetDiskByID(gomock.Eq(mockDisk.VolumeID)).Return(mockDisk, nil)
137+
138+
powervsDriver := controllerService{
139+
cloud: mockCloud,
140+
driverOptions: &Options{},
141+
volumeLocks: util.NewVolumeLocks(),
142+
}
143+
144+
if _, err := powervsDriver.CreateVolume(ctx, req); err != nil {
145+
srvErr, ok := status.FromError(err)
146+
if !ok {
147+
t.Fatalf("Could not get error status code from error: %v", srvErr)
148+
}
149+
t.Fatalf("Unexpected error: %v", srvErr.Code())
150+
}
151+
},
152+
},
153+
{
154+
name: "Create PVC with Data source - volume already exists",
155+
testFunc: func(t *testing.T) {
156+
req := &csi.CreateVolumeRequest{
157+
Name: "clone-volume-name",
158+
CapacityRange: &csi.CapacityRange{RequiredBytes: stdVolSize},
159+
VolumeCapabilities: stdVolCap,
160+
Parameters: stdParams,
161+
VolumeContentSource: &csi.VolumeContentSource{
162+
Type: &csi.VolumeContentSource_Volume{
163+
Volume: &csi.VolumeContentSource_VolumeSource{
164+
VolumeId: "test-volume-src-100",
165+
},
166+
},
167+
},
168+
}
169+
170+
ctx := context.Background()
171+
172+
mockDisk := &cloud.Disk{
173+
VolumeID: req.Name,
174+
CapacityGiB: util.BytesToGiB(stdVolSize),
175+
DiskType: cloud.DefaultVolumeType,
176+
}
98177

178+
mockCtl := gomock.NewController(t)
179+
defer mockCtl.Finish()
180+
181+
mockCloud := mocks.NewMockCloud(mockCtl)
182+
mockCloud.EXPECT().GetDiskByNamePrefix(gomock.Eq("clone-"+req.Name)).Return(mockDisk, nil)
183+
184+
powervsDriver := controllerService{
185+
cloud: mockCloud,
186+
driverOptions: &Options{},
187+
volumeLocks: util.NewVolumeLocks(),
188+
}
189+
190+
if _, err := powervsDriver.CreateVolume(ctx, req); err != nil {
191+
srvErr, ok := status.FromError(err)
192+
if !ok {
193+
t.Fatalf("Could not get error status code from error: %v", srvErr)
194+
}
195+
t.Fatalf("Unexpected error: %v", srvErr.Code())
196+
}
197+
},
198+
},
99199
{
100200
name: "csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER",
101201
testFunc: func(t *testing.T) {

0 commit comments

Comments
 (0)