@@ -33,6 +33,7 @@ import (
33
33
"k8s.io/apimachinery/pkg/util/uuid"
34
34
"k8s.io/client-go/util/flowcontrol"
35
35
"k8s.io/klog/v2"
36
+ "k8s.io/utils/strings/slices"
36
37
37
38
"sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/pkg/common"
38
39
gce "sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/pkg/gce-cloud-provider/compute"
@@ -99,6 +100,14 @@ type workItem struct {
99
100
unpublishReq * csi.ControllerUnpublishVolumeRequest
100
101
}
101
102
103
+ // locationRequirements are additional location topology requirements that must be respected when creating a volume.
104
+ type locationRequirements struct {
105
+ srcVolRegion string
106
+ srcVolZone string
107
+ srcReplicationType string
108
+ cloneReplicationType string
109
+ }
110
+
102
111
var _ csi.ControllerServer = & GCEControllerServer {}
103
112
104
113
const (
@@ -143,6 +152,44 @@ func isDiskReady(disk *gce.CloudDisk) (bool, error) {
143
152
return false , nil
144
153
}
145
154
155
+ // cloningLocationRequirements returns additional location requirements to be applied to the given create volume requests topology.
156
+ // If the CreateVolumeRequest will use volume cloning, location requirements in compliance with the volume cloning limitations
157
+ // will be returned: https://cloud.google.com/kubernetes-engine/docs/how-to/persistent-volumes/volume-cloning#limitations.
158
+ func cloningLocationRequirements (req * csi.CreateVolumeRequest , cloneReplicationType string ) (* locationRequirements , error ) {
159
+ if ! useVolumeCloning (req ) {
160
+ return nil , nil
161
+ }
162
+ // If we are using volume cloning, this will be set.
163
+ volSrc := req .VolumeContentSource .GetVolume ()
164
+ volSrcVolID := volSrc .GetVolumeId ()
165
+
166
+ _ , sourceVolKey , err := common .VolumeIDToKey (volSrcVolID )
167
+ if err != nil {
168
+ return nil , fmt .Errorf ("volume ID is invalid: %w" , err )
169
+ }
170
+
171
+ isZonalSrcVol := sourceVolKey .Type () == meta .Zonal
172
+ if isZonalSrcVol {
173
+ region , err := common .GetRegionFromZones ([]string {sourceVolKey .Zone })
174
+ if err != nil {
175
+ return nil , fmt .Errorf ("failed to get region from zones: %w" , err )
176
+ }
177
+ sourceVolKey .Region = region
178
+ }
179
+
180
+ srcReplicationType := replicationTypeNone
181
+ if ! isZonalSrcVol {
182
+ srcReplicationType = replicationTypeRegionalPD
183
+ }
184
+
185
+ return & locationRequirements {srcVolZone : sourceVolKey .Zone , srcVolRegion : sourceVolKey .Region , srcReplicationType : srcReplicationType , cloneReplicationType : cloneReplicationType }, nil
186
+ }
187
+
188
+ // useVolumeCloning returns true if the create volume request should be created with volume cloning.
189
+ func useVolumeCloning (req * csi.CreateVolumeRequest ) bool {
190
+ return req .VolumeContentSource != nil && req .VolumeContentSource .GetVolume () != nil
191
+ }
192
+
146
193
func (gceCS * GCEControllerServer ) CreateVolume (ctx context.Context , req * csi.CreateVolumeRequest ) (* csi.CreateVolumeResponse , error ) {
147
194
var err error
148
195
// Validate arguments
@@ -178,12 +225,21 @@ func (gceCS *GCEControllerServer) CreateVolume(ctx context.Context, req *csi.Cre
178
225
if multiWriter {
179
226
gceAPIVersion = gce .GCEAPIVersionBeta
180
227
}
228
+
229
+ var locationTopReq * locationRequirements
230
+ if useVolumeCloning (req ) {
231
+ locationTopReq , err = cloningLocationRequirements (req , params .ReplicationType )
232
+ if err != nil {
233
+ return nil , status .Errorf (codes .InvalidArgument , "failed to get location requirements: %v" , err .Error ())
234
+ }
235
+ }
236
+
181
237
// Determine the zone or zones+region of the disk
182
238
var zones []string
183
239
var volKey * meta.Key
184
240
switch params .ReplicationType {
185
241
case replicationTypeNone :
186
- zones , err = pickZones (ctx , gceCS , req .GetAccessibilityRequirements (), 1 )
242
+ zones , err = pickZones (ctx , gceCS , req .GetAccessibilityRequirements (), 1 , locationTopReq )
187
243
if err != nil {
188
244
return nil , status .Errorf (codes .InvalidArgument , "CreateVolume failed to pick zones for disk: %v" , err .Error ())
189
245
}
@@ -193,7 +249,7 @@ func (gceCS *GCEControllerServer) CreateVolume(ctx context.Context, req *csi.Cre
193
249
volKey = meta .ZonalKey (name , zones [0 ])
194
250
195
251
case replicationTypeRegionalPD :
196
- zones , err = pickZones (ctx , gceCS , req .GetAccessibilityRequirements (), 2 )
252
+ zones , err = pickZones (ctx , gceCS , req .GetAccessibilityRequirements (), 2 , locationTopReq )
197
253
if err != nil {
198
254
return nil , status .Errorf (codes .InvalidArgument , "CreateVolume failed to pick zones for disk: %v" , err .Error ())
199
255
}
@@ -1374,7 +1430,29 @@ func diskIsAttachedAndCompatible(deviceName string, instance *compute.Instance,
1374
1430
return false , nil
1375
1431
}
1376
1432
1377
- func pickZonesFromTopology (top * csi.TopologyRequirement , numZones int ) ([]string , error ) {
1433
+ // pickZonesInRegion will remove any zones that are not in the given region.
1434
+ func pickZonesInRegion (region string , zones []string ) []string {
1435
+ refinedZones := []string {}
1436
+ for _ , zone := range zones {
1437
+ if strings .Contains (zone , region ) {
1438
+ refinedZones = append (refinedZones , zone )
1439
+ }
1440
+ }
1441
+ return refinedZones
1442
+ }
1443
+
1444
+ func prependZone (zone string , zones []string ) []string {
1445
+ newZones := []string {zone }
1446
+ for i := 0 ; i < len (zones ); i ++ {
1447
+ // Do not add a zone if it is equal to the zone that is already prepended to newZones.
1448
+ if zones [i ] != zone {
1449
+ newZones = append (newZones , zones [i ])
1450
+ }
1451
+ }
1452
+ return newZones
1453
+ }
1454
+
1455
+ func pickZonesFromTopology (top * csi.TopologyRequirement , numZones int , locationTopReq * locationRequirements ) ([]string , error ) {
1378
1456
reqZones , err := getZonesFromTopology (top .GetRequisite ())
1379
1457
if err != nil {
1380
1458
return nil , fmt .Errorf ("could not get zones from requisite topology: %w" , err )
@@ -1384,6 +1462,39 @@ func pickZonesFromTopology(top *csi.TopologyRequirement, numZones int) ([]string
1384
1462
return nil , fmt .Errorf ("could not get zones from preferred topology: %w" , err )
1385
1463
}
1386
1464
1465
+ if locationTopReq != nil {
1466
+ srcVolZone := locationTopReq .srcVolZone
1467
+ switch locationTopReq .cloneReplicationType {
1468
+ // For zonal -> zonal cloning, the source disk zone must match the destination disk zone.
1469
+ case replicationTypeNone :
1470
+ // If the source volume zone is not in the topology requirement, we return an error.
1471
+ if ! slices .Contains (prefZones , srcVolZone ) && ! slices .Contains (reqZones , srcVolZone ) {
1472
+ volumeCloningReq := fmt .Sprintf ("clone zone must match source disk zone: %s" , srcVolZone )
1473
+ return nil , fmt .Errorf ("failed to find zone from topology %v: %s" , top , volumeCloningReq )
1474
+ }
1475
+ return []string {srcVolZone }, nil
1476
+ // For zonal or regional -> regional disk cloning, the source disk region must match the destination disk region.
1477
+ case replicationTypeRegionalPD :
1478
+ srcVolRegion := locationTopReq .srcVolRegion
1479
+ prefZones = pickZonesInRegion (srcVolRegion , prefZones )
1480
+ reqZones = pickZonesInRegion (srcVolRegion , reqZones )
1481
+
1482
+ if len (prefZones ) == 0 && len (reqZones ) == 0 {
1483
+ volumeCloningReq := fmt .Sprintf ("clone zone must reside in source disk region %s" , srcVolRegion )
1484
+ return nil , fmt .Errorf ("failed to find zone from topology %v: %s" , top , volumeCloningReq )
1485
+ }
1486
+
1487
+ // For zonal -> regional disk cloning, one of the replicated zones must match the source zone.
1488
+ if locationTopReq .srcReplicationType == replicationTypeNone {
1489
+ if ! slices .Contains (prefZones , srcVolZone ) && ! slices .Contains (reqZones , srcVolZone ) {
1490
+ volumeCloningReq := fmt .Sprintf ("one of the replica zones of the clone must match the source disk zone: %s" , srcVolZone )
1491
+ return nil , fmt .Errorf ("failed to find zone from topology %v: %s" , top , volumeCloningReq )
1492
+ }
1493
+ prefZones = prependZone (srcVolZone , prefZones )
1494
+ }
1495
+ }
1496
+ }
1497
+
1387
1498
if numZones <= len (prefZones ) {
1388
1499
return prefZones [0 :numZones ], nil
1389
1500
} else {
@@ -1442,16 +1553,25 @@ func getZoneFromSegment(seg map[string]string) (string, error) {
1442
1553
return zone , nil
1443
1554
}
1444
1555
1445
- func pickZones (ctx context.Context , gceCS * GCEControllerServer , top * csi.TopologyRequirement , numZones int ) ([]string , error ) {
1556
+ func pickZones (ctx context.Context , gceCS * GCEControllerServer , top * csi.TopologyRequirement , numZones int , locationTopReq * locationRequirements ) ([]string , error ) {
1446
1557
var zones []string
1447
1558
var err error
1448
1559
if top != nil {
1449
- zones , err = pickZonesFromTopology (top , numZones )
1560
+ zones , err = pickZonesFromTopology (top , numZones , locationTopReq )
1450
1561
if err != nil {
1451
1562
return nil , fmt .Errorf ("failed to pick zones from topology: %w" , err )
1452
1563
}
1453
1564
} else {
1454
- zones , err = getDefaultZonesInRegion (ctx , gceCS , []string {gceCS .CloudProvider .GetDefaultZone ()}, numZones )
1565
+ existingZones := []string {gceCS .CloudProvider .GetDefaultZone ()}
1566
+ // We set existingZones to the source volume zone so that for zonal -> zonal cloning, the clone is provisioned
1567
+ // in the same zone as the source volume, and for zonal -> regional, one of the replicated zones will always
1568
+ // be the zone of the source volume. For regional -> regional cloning, the srcVolZone will not be set, so we
1569
+ // just use the default zone.
1570
+ if locationTopReq != nil && locationTopReq .srcReplicationType == replicationTypeNone {
1571
+ existingZones = []string {locationTopReq .srcVolZone }
1572
+ }
1573
+ // If topology is nil, then the Immediate binding mode was used without setting allowedTopologies in the storageclass.
1574
+ zones , err = getDefaultZonesInRegion (ctx , gceCS , existingZones , numZones )
1455
1575
if err != nil {
1456
1576
return nil , fmt .Errorf ("failed to get default %v zones in region: %w" , numZones , err )
1457
1577
}
0 commit comments