Skip to content

Commit 19d670f

Browse files
committed
added volume cloning
1 parent 41bba8a commit 19d670f

File tree

6 files changed

+200
-21
lines changed

6 files changed

+200
-21
lines changed

pkg/gce-cloud-provider/compute/cloud-disk.go

+11
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,17 @@ func (d *CloudDisk) GetSnapshotId() string {
177177
}
178178
}
179179

180+
func (d *CloudDisk) GetDiskId() string {
181+
switch {
182+
case d.disk != nil:
183+
return d.disk.SourceDiskId
184+
case d.betaDisk != nil:
185+
return d.betaDisk.SourceDiskId
186+
default:
187+
return ""
188+
}
189+
}
190+
180191
func (d *CloudDisk) GetKMSKeyName() string {
181192
switch {
182193
case d.disk != nil:

pkg/gce-cloud-provider/compute/fake-gce.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ func (cloud *FakeCloudProvider) ValidateExistingDisk(ctx context.Context, resp *
220220
return ValidateDiskParameters(resp, params)
221221
}
222222

223-
func (cloud *FakeCloudProvider) InsertDisk(ctx context.Context, project string, volKey *meta.Key, params common.DiskParameters, capBytes int64, capacityRange *csi.CapacityRange, replicaZones []string, snapshotID string, multiWriter bool) error {
223+
func (cloud *FakeCloudProvider) InsertDisk(ctx context.Context, project string, volKey *meta.Key, params common.DiskParameters, capBytes int64, capacityRange *csi.CapacityRange, replicaZones []string, snapshotID string, volumeContentSourceVolumeID string, multiWriter bool) error {
224224
if disk, ok := cloud.disks[volKey.Name]; ok {
225225
err := cloud.ValidateExistingDisk(ctx, disk, params,
226226
int64(capacityRange.GetRequiredBytes()),
@@ -237,6 +237,7 @@ func (cloud *FakeCloudProvider) InsertDisk(ctx context.Context, project string,
237237
Description: "Disk created by GCE-PD CSI Driver",
238238
Type: cloud.GetDiskTypeURI(project, volKey, params.DiskType),
239239
SourceSnapshotId: snapshotID,
240+
SourceDiskId: volumeContentSourceVolumeID,
240241
Status: cloud.mockDiskStatus,
241242
Labels: params.Labels,
242243
}

pkg/gce-cloud-provider/compute/gce-compute.go

+12-4
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ type GCECompute interface {
5656
GetDisk(ctx context.Context, project string, volumeKey *meta.Key, gceAPIVersion GCEAPIVersion) (*CloudDisk, error)
5757
RepairUnderspecifiedVolumeKey(ctx context.Context, project string, volumeKey *meta.Key) (string, *meta.Key, error)
5858
ValidateExistingDisk(ctx context.Context, disk *CloudDisk, params common.DiskParameters, reqBytes, limBytes int64, multiWriter bool) error
59-
InsertDisk(ctx context.Context, project string, volKey *meta.Key, params common.DiskParameters, capBytes int64, capacityRange *csi.CapacityRange, replicaZones []string, snapshotID string, multiWriter bool) error
59+
InsertDisk(ctx context.Context, project string, volKey *meta.Key, params common.DiskParameters, capBytes int64, capacityRange *csi.CapacityRange, replicaZones []string, snapshotID string, volumeContentSourceVolumeID string, multiWriter bool) error
6060
DeleteDisk(ctx context.Context, project string, volumeKey *meta.Key) error
6161
AttachDisk(ctx context.Context, project string, volKey *meta.Key, readWrite, diskType, instanceZone, instanceName string) error
6262
DetachDisk(ctx context.Context, project, deviceName, instanceZone, instanceName string) error
@@ -315,7 +315,7 @@ func ValidateDiskParameters(disk *CloudDisk, params common.DiskParameters) error
315315
return nil
316316
}
317317

318-
func (cloud *CloudProvider) InsertDisk(ctx context.Context, project string, volKey *meta.Key, params common.DiskParameters, capBytes int64, capacityRange *csi.CapacityRange, replicaZones []string, snapshotID string, multiWriter bool) error {
318+
func (cloud *CloudProvider) InsertDisk(ctx context.Context, project string, volKey *meta.Key, params common.DiskParameters, capBytes int64, capacityRange *csi.CapacityRange, replicaZones []string, snapshotID string, volumeContentSourceVolumeID string, multiWriter bool) error {
319319
klog.V(5).Infof("Inserting disk %v", volKey)
320320

321321
description, err := encodeDiskTags(params.Tags)
@@ -328,12 +328,12 @@ func (cloud *CloudProvider) InsertDisk(ctx context.Context, project string, volK
328328
if description == "" {
329329
description = "Disk created by GCE-PD CSI Driver"
330330
}
331-
return cloud.insertZonalDisk(ctx, project, volKey, params, capBytes, capacityRange, snapshotID, description, multiWriter)
331+
return cloud.insertZonalDisk(ctx, project, volKey, params, capBytes, capacityRange, snapshotID, volumeContentSourceVolumeID, description, multiWriter)
332332
case meta.Regional:
333333
if description == "" {
334334
description = "Regional disk created by GCE-PD CSI Driver"
335335
}
336-
return cloud.insertRegionalDisk(ctx, project, volKey, params, capBytes, capacityRange, replicaZones, snapshotID, description, multiWriter)
336+
return cloud.insertRegionalDisk(ctx, project, volKey, params, capBytes, capacityRange, replicaZones, snapshotID, volumeContentSourceVolumeID, description, multiWriter)
337337
default:
338338
return fmt.Errorf("could not insert disk, key was neither zonal nor regional, instead got: %v", volKey.String())
339339
}
@@ -377,6 +377,7 @@ func (cloud *CloudProvider) insertRegionalDisk(
377377
capacityRange *csi.CapacityRange,
378378
replicaZones []string,
379379
snapshotID string,
380+
volumeContentSourceVolumeID string,
380381
description string,
381382
multiWriter bool) error {
382383
var (
@@ -399,6 +400,9 @@ func (cloud *CloudProvider) insertRegionalDisk(
399400
if snapshotID != "" {
400401
diskToCreate.SourceSnapshot = snapshotID
401402
}
403+
if volumeContentSourceVolumeID != "" {
404+
diskToCreate.SourceDisk = volumeContentSourceVolumeID
405+
}
402406
if len(replicaZones) != 0 {
403407
diskToCreate.ReplicaZones = replicaZones
404408
}
@@ -472,6 +476,7 @@ func (cloud *CloudProvider) insertZonalDisk(
472476
capBytes int64,
473477
capacityRange *csi.CapacityRange,
474478
snapshotID string,
479+
volumeContentSourceVolumeID string,
475480
description string,
476481
multiWriter bool) error {
477482
var (
@@ -495,6 +500,9 @@ func (cloud *CloudProvider) insertZonalDisk(
495500
if snapshotID != "" {
496501
diskToCreate.SourceSnapshot = snapshotID
497502
}
503+
if volumeContentSourceVolumeID != "" {
504+
diskToCreate.SourceDisk = volumeContentSourceVolumeID
505+
}
498506

499507
if params.DiskEncryptionKMSKey != "" {
500508
diskToCreate.DiskEncryptionKey = &computev1.CustomerEncryptionKey{

pkg/gce-pd-csi-driver/controller.go

+65-15
Original file line numberDiff line numberDiff line change
@@ -190,10 +190,10 @@ func (gceCS *GCEControllerServer) CreateVolume(ctx context.Context, req *csi.Cre
190190
}
191191

192192
snapshotID := ""
193+
volumeContentSourceVolumeID := ""
193194
content := req.GetVolumeContentSource()
194195
if content != nil {
195196
if content.GetSnapshot() != nil {
196-
// TODO(#161): Add support for Volume Source (cloning) introduced in CSI v1.0.0
197197
snapshotID = content.GetSnapshot().GetSnapshotId()
198198

199199
// Verify that snapshot exists
@@ -204,24 +204,60 @@ func (gceCS *GCEControllerServer) CreateVolume(ctx context.Context, req *csi.Cre
204204
return nil, status.Errorf(codes.NotFound, "CreateVolume source snapshot %s does not exist", snapshotID)
205205
}
206206
}
207-
}
208207

208+
if content.GetVolume() != nil {
209+
volumeContentSourceVolumeID = content.GetVolume().GetVolumeId()
210+
211+
// Verify that the source VolumeID is in the correct format.
212+
project, volKey, err := common.VolumeIDToKey(volumeContentSourceVolumeID)
213+
if err != nil {
214+
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("CreateVolume source volume ID is invalid: %v", err))
215+
}
216+
// Verify that the volume in VolumeContentSource exists.
217+
diskFromSourceVolume, err := gceCS.CloudProvider.GetDisk(ctx, project, volKey, gceAPIVersion)
218+
if err != nil {
219+
if gce.IsGCEError(err, "notFound") {
220+
return nil, status.Errorf(codes.NotFound, "CreateVolume source volume %s does not exist", volumeContentSourceVolumeID)
221+
} else {
222+
return nil, status.Error(codes.Internal, fmt.Sprintf("CreateVolume unknown get disk error when validating: %v", err))
223+
}
224+
}
225+
226+
// Verify the zone, region, and disk type of the clone must be the same as that of the source disk.
227+
if err := gce.ValidateDiskParameters(diskFromSourceVolume, params); err != nil {
228+
return nil, status.Errorf(codes.InvalidArgument, `CreateVolume source volume parameters do not match CreateVolumeRequest Parameters: %v`, err)
229+
}
230+
231+
// Verify the source disk is ready.
232+
if err == nil {
233+
ready, err := isDiskReady(diskFromSourceVolume)
234+
if err != nil {
235+
return nil, status.Error(codes.Internal, fmt.Sprintf("CreateVolume disk from source volume %v had error checking ready status: %v", volKey, err))
236+
}
237+
if !ready {
238+
return nil, status.Error(codes.Internal, fmt.Sprintf("CreateVolume disk from source volume %v is not ready", volKey))
239+
}
240+
}
241+
//TODO does this go inside of err == nil?
242+
243+
}
244+
}
209245
// Create the disk
210246
var disk *gce.CloudDisk
211247
switch params.ReplicationType {
212248
case replicationTypeNone:
213249
if len(zones) != 1 {
214250
return nil, status.Error(codes.Internal, fmt.Sprintf("CreateVolume failed to get a single zone for creating zonal disk, instead got: %v", zones))
215251
}
216-
disk, err = createSingleZoneDisk(ctx, gceCS.CloudProvider, name, zones, params, capacityRange, capBytes, snapshotID, multiWriter)
252+
disk, err = createSingleZoneDisk(ctx, gceCS.CloudProvider, name, zones, params, capacityRange, capBytes, snapshotID, volumeContentSourceVolumeID, multiWriter)
217253
if err != nil {
218254
return nil, status.Error(codes.Internal, fmt.Sprintf("CreateVolume failed to create single zonal disk %#v: %v", name, err))
219255
}
220256
case replicationTypeRegionalPD:
221257
if len(zones) != 2 {
222258
return nil, status.Errorf(codes.Internal, fmt.Sprintf("CreateVolume failed to get a 2 zones for creating regional disk, instead got: %v", zones))
223259
}
224-
disk, err = createRegionalDisk(ctx, gceCS.CloudProvider, name, zones, params, capacityRange, capBytes, snapshotID, multiWriter)
260+
disk, err = createRegionalDisk(ctx, gceCS.CloudProvider, name, zones, params, capacityRange, capBytes, snapshotID, volumeContentSourceVolumeID, multiWriter)
225261
if err != nil {
226262
return nil, status.Error(codes.Internal, fmt.Sprintf("CreateVolume failed to create regional disk %#v: %v", name, err))
227263
}
@@ -1053,15 +1089,29 @@ func generateCreateVolumeResponse(disk *gce.CloudDisk, zones []string) *csi.Crea
10531089
},
10541090
}
10551091
snapshotID := disk.GetSnapshotId()
1056-
if snapshotID != "" {
1057-
source := &csi.VolumeContentSource{
1058-
Type: &csi.VolumeContentSource_Snapshot{
1059-
Snapshot: &csi.VolumeContentSource_SnapshotSource{
1060-
SnapshotId: snapshotID,
1092+
diskID := disk.GetDiskId()
1093+
if diskID != "" || snapshotID != "" {
1094+
contentSource := &csi.VolumeContentSource{}
1095+
if snapshotID != "" {
1096+
contentSource = &csi.VolumeContentSource{
1097+
Type: &csi.VolumeContentSource_Snapshot{
1098+
Snapshot: &csi.VolumeContentSource_SnapshotSource{
1099+
SnapshotId: snapshotID,
1100+
},
10611101
},
1062-
},
1102+
}
1103+
}
1104+
diskID := disk.GetDiskId()
1105+
if diskID != "" {
1106+
contentSource = &csi.VolumeContentSource{
1107+
Type: &csi.VolumeContentSource_Volume{
1108+
Volume: &csi.VolumeContentSource_VolumeSource{
1109+
VolumeId: diskID,
1110+
},
1111+
},
1112+
}
10631113
}
1064-
createResp.Volume.ContentSource = source
1114+
createResp.Volume.ContentSource = contentSource
10651115
}
10661116
return createResp
10671117
}
@@ -1072,7 +1122,7 @@ func cleanSelfLink(selfLink string) string {
10721122
return strings.TrimPrefix(temp, gce.GCEComputeAlphaAPIEndpoint)
10731123
}
10741124

1075-
func createRegionalDisk(ctx context.Context, cloudProvider gce.GCECompute, name string, zones []string, params common.DiskParameters, capacityRange *csi.CapacityRange, capBytes int64, snapshotID string, multiWriter bool) (*gce.CloudDisk, error) {
1125+
func createRegionalDisk(ctx context.Context, cloudProvider gce.GCECompute, name string, zones []string, params common.DiskParameters, capacityRange *csi.CapacityRange, capBytes int64, snapshotID string, volumeContentSourceVolumeID string, multiWriter bool) (*gce.CloudDisk, error) {
10761126
project := cloudProvider.GetDefaultProject()
10771127
region, err := common.GetRegionFromZones(zones)
10781128
if err != nil {
@@ -1085,7 +1135,7 @@ func createRegionalDisk(ctx context.Context, cloudProvider gce.GCECompute, name
10851135
fullyQualifiedReplicaZones, cloudProvider.GetReplicaZoneURI(project, replicaZone))
10861136
}
10871137

1088-
err = cloudProvider.InsertDisk(ctx, project, meta.RegionalKey(name, region), params, capBytes, capacityRange, fullyQualifiedReplicaZones, snapshotID, multiWriter)
1138+
err = cloudProvider.InsertDisk(ctx, project, meta.RegionalKey(name, region), params, capBytes, capacityRange, fullyQualifiedReplicaZones, snapshotID, volumeContentSourceVolumeID, multiWriter)
10891139
if err != nil {
10901140
return nil, fmt.Errorf("failed to insert regional disk: %v", err)
10911141
}
@@ -1102,13 +1152,13 @@ func createRegionalDisk(ctx context.Context, cloudProvider gce.GCECompute, name
11021152
return disk, nil
11031153
}
11041154

1105-
func createSingleZoneDisk(ctx context.Context, cloudProvider gce.GCECompute, name string, zones []string, params common.DiskParameters, capacityRange *csi.CapacityRange, capBytes int64, snapshotID string, multiWriter bool) (*gce.CloudDisk, error) {
1155+
func createSingleZoneDisk(ctx context.Context, cloudProvider gce.GCECompute, name string, zones []string, params common.DiskParameters, capacityRange *csi.CapacityRange, capBytes int64, snapshotID string, volumeContentSourceVolumeID string, multiWriter bool) (*gce.CloudDisk, error) {
11061156
project := cloudProvider.GetDefaultProject()
11071157
if len(zones) != 1 {
11081158
return nil, fmt.Errorf("got wrong number of zones for zonal create volume: %v", len(zones))
11091159
}
11101160
diskZone := zones[0]
1111-
err := cloudProvider.InsertDisk(ctx, project, meta.ZonalKey(name, diskZone), params, capBytes, capacityRange, nil, snapshotID, multiWriter)
1161+
err := cloudProvider.InsertDisk(ctx, project, meta.ZonalKey(name, diskZone), params, capBytes, capacityRange, nil, snapshotID, volumeContentSourceVolumeID, multiWriter)
11121162
if err != nil {
11131163
return nil, fmt.Errorf("failed to insert zonal disk: %v", err)
11141164
}

pkg/gce-pd-csi-driver/controller_test.go

+109-1
Original file line numberDiff line numberDiff line change
@@ -824,7 +824,7 @@ func TestListVolumeArgs(t *testing.T) {
824824
}
825825
}
826826

827-
func TestCreateVolumeWithVolumeSource(t *testing.T) {
827+
func TestCreateVolumeWithVolumeSourceFromSnapshot(t *testing.T) {
828828
// Define test cases
829829
testCases := []struct {
830830
name string
@@ -900,6 +900,114 @@ func TestCreateVolumeWithVolumeSource(t *testing.T) {
900900
}
901901
}
902902

903+
func TestCreateVolumeWithVolumeSourceFromVolume(t *testing.T) {
904+
testSourceVolumeName := "test-volume-source-name"
905+
testVolumeSourceID := fmt.Sprintf("projects/%s/zones/%s/disks/%s", project, zone, testSourceVolumeName)
906+
testVolumeSourceIDDifferentZone := fmt.Sprintf("projects/%s/zones/%s/disks/%s", project, "different-zone", testSourceVolumeName)
907+
// Define test cases
908+
testCases := []struct {
909+
name string
910+
project string
911+
volKey *meta.Key
912+
volumeOnCloud bool
913+
expErrCode codes.Code
914+
sourceVolumeID string
915+
parameters map[string]string
916+
}{
917+
{
918+
name: "success with data source of volume type",
919+
project: "test-project",
920+
volKey: meta.ZonalKey("my-disk", zone),
921+
volumeOnCloud: true,
922+
sourceVolumeID: testVolumeSourceID,
923+
},
924+
{
925+
name: "fail with data source of volume type that doesn't exist",
926+
project: "test-project",
927+
volKey: meta.ZonalKey("my-disk", zone),
928+
volumeOnCloud: false,
929+
expErrCode: codes.NotFound,
930+
sourceVolumeID: testVolumeSourceID,
931+
},
932+
{
933+
name: "fail with data source of volume type with invalid volume id format",
934+
project: "test-project",
935+
volKey: meta.ZonalKey("my-disk", zone),
936+
volumeOnCloud: false,
937+
expErrCode: codes.InvalidArgument,
938+
sourceVolumeID: testVolumeSourceID + "invalid/format",
939+
},
940+
{
941+
name: "fail with data source of volume type with invalid disk parameters",
942+
project: "test-project",
943+
volKey: meta.ZonalKey("my-disk", zone),
944+
volumeOnCloud: true,
945+
expErrCode: codes.InvalidArgument,
946+
sourceVolumeID: testVolumeSourceIDDifferentZone,
947+
parameters: map[string]string{
948+
common.ParameterKeyType: "different-type",
949+
},
950+
},
951+
}
952+
953+
for _, tc := range testCases {
954+
t.Logf("test case: %s", tc.name)
955+
gceDriver := initGCEDriver(t, nil)
956+
957+
req := &csi.CreateVolumeRequest{
958+
Name: name,
959+
CapacityRange: stdCapRange,
960+
VolumeCapabilities: stdVolCaps,
961+
Parameters: stdParams,
962+
VolumeContentSource: &csi.VolumeContentSource{
963+
Type: &csi.VolumeContentSource_Volume{
964+
Volume: &csi.VolumeContentSource_VolumeSource{
965+
VolumeId: tc.sourceVolumeID,
966+
},
967+
},
968+
},
969+
}
970+
971+
sourceVolumeRequest := &csi.CreateVolumeRequest{
972+
Name: testSourceVolumeName,
973+
CapacityRange: stdCapRange,
974+
VolumeCapabilities: stdVolCaps,
975+
Parameters: stdParams,
976+
}
977+
if tc.parameters != nil {
978+
sourceVolumeRequest.Parameters = tc.parameters
979+
}
980+
981+
if tc.volumeOnCloud {
982+
// Create the source volume.
983+
gceDriver.cs.CreateVolume(context.Background(), sourceVolumeRequest)
984+
}
985+
resp, err := gceDriver.cs.CreateVolume(context.Background(), req)
986+
t.Logf("response: %v err: %v", resp, err)
987+
if err != nil {
988+
serverError, ok := status.FromError(err)
989+
if !ok {
990+
t.Fatalf("Could not get error status code from err: %v", serverError)
991+
}
992+
if serverError.Code() != tc.expErrCode {
993+
t.Fatalf("Expected error code: %v, got: %v. err : %v", tc.expErrCode, serverError.Code(), err)
994+
}
995+
continue
996+
}
997+
if tc.expErrCode != codes.OK {
998+
t.Fatalf("Expected error: %v, got no error", tc.expErrCode)
999+
}
1000+
1001+
// Make sure the response has the source volume.
1002+
sourceVolume := resp.GetVolume()
1003+
t.Logf("response has source volume: %v ", sourceVolume)
1004+
if sourceVolume.ContentSource == nil || sourceVolume.ContentSource.Type == nil ||
1005+
sourceVolume.ContentSource.GetVolume() == nil || sourceVolume.ContentSource.GetVolume().VolumeId == "" {
1006+
t.Fatalf("Expected volume content source to have volume ID, got none")
1007+
}
1008+
}
1009+
}
1010+
9031011
func TestCreateVolumeRandomRequisiteTopology(t *testing.T) {
9041012
req := &csi.CreateVolumeRequest{
9051013
Name: "test-name",

pkg/gce-pd-csi-driver/gce-pd-driver.go

+1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ func (gceDriver *GCEDriver) SetupGCEDriver(name, vendorVersion string, extraVolu
6767
csi.ControllerServiceCapability_RPC_EXPAND_VOLUME,
6868
csi.ControllerServiceCapability_RPC_LIST_VOLUMES,
6969
csi.ControllerServiceCapability_RPC_LIST_VOLUMES_PUBLISHED_NODES,
70+
csi.ControllerServiceCapability_RPC_CLONE_VOLUME,
7071
}
7172
gceDriver.AddControllerServiceCapabilities(csc)
7273
ns := []csi.NodeServiceCapability_RPC_Type{

0 commit comments

Comments
 (0)