@@ -32,7 +32,7 @@ import (
32
32
"k8s.io/apimachinery/pkg/util/uuid"
33
33
"k8s.io/client-go/util/flowcontrol"
34
34
"k8s.io/klog/v2"
35
-
35
+ "k8s.io/utils/strings/slices"
36
36
"sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/pkg/common"
37
37
gce "sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/pkg/gce-cloud-provider/compute"
38
38
)
@@ -98,6 +98,12 @@ type workItem struct {
98
98
unpublishReq * csi.ControllerUnpublishVolumeRequest
99
99
}
100
100
101
+ // locationRequirements are additional location topology requirements that must be respected when creating a volume.
102
+ type locationRequirements struct {
103
+ region string
104
+ zone string
105
+ }
106
+
101
107
var _ csi.ControllerServer = & GCEControllerServer {}
102
108
103
109
const (
@@ -142,6 +148,29 @@ func isDiskReady(disk *gce.CloudDisk) (bool, error) {
142
148
return false , nil
143
149
}
144
150
151
+ // cloningLocationRequirements returns additional location requirements to be applied to the given create volume requests topology.
152
+ // If the CreateVolumeRequest will use volume cloning, location requirements in compliance with the volume cloning limitations
153
+ // will be returned: https://cloud.google.com/kubernetes-engine/docs/how-to/persistent-volumes/volume-cloning#limitations
154
+ func cloningLocationRequirements (req * csi.CreateVolumeRequest , replicationType string ) (* locationRequirements , error ) {
155
+ if ! useVolumeCloning (req ) {
156
+ return nil , nil
157
+ }
158
+ // If we are using volume cloning, this will be set.
159
+ volSrc := req .VolumeContentSource .GetVolume ()
160
+ volSrcVolID := volSrc .GetVolumeId ()
161
+
162
+ _ , sourceVolKey , err := common .VolumeIDToKey (volSrcVolID )
163
+ if err != nil {
164
+ return nil , err
165
+ }
166
+ return & locationRequirements {zone : sourceVolKey .Zone , region : sourceVolKey .Region }, nil
167
+ }
168
+
169
+ // useVolumeCloning returns true if the create volume request should be created with volume cloning.
170
+ func useVolumeCloning (req * csi.CreateVolumeRequest ) bool {
171
+ return req .VolumeContentSource != nil && req .VolumeContentSource .GetVolume () != nil
172
+ }
173
+
145
174
func (gceCS * GCEControllerServer ) CreateVolume (ctx context.Context , req * csi.CreateVolumeRequest ) (* csi.CreateVolumeResponse , error ) {
146
175
var err error
147
176
// Validate arguments
@@ -177,12 +206,21 @@ func (gceCS *GCEControllerServer) CreateVolume(ctx context.Context, req *csi.Cre
177
206
if multiWriter {
178
207
gceAPIVersion = gce .GCEAPIVersionBeta
179
208
}
209
+
210
+ var locationTopReq * locationRequirements
211
+ if useVolumeCloning (req ) {
212
+ locationTopReq , err = cloningLocationRequirements (req , params .ReplicationType )
213
+ if err != nil {
214
+ return nil , status .Errorf (codes .Internal , "failed to get location topology requirements for volume cloning: %v" , err )
215
+ }
216
+ }
217
+
180
218
// Determine the zone or zones+region of the disk
181
219
var zones []string
182
220
var volKey * meta.Key
183
221
switch params .ReplicationType {
184
222
case replicationTypeNone :
185
- zones , err = pickZones (ctx , gceCS , req .GetAccessibilityRequirements (), 1 )
223
+ zones , err = pickZones (ctx , gceCS , req .GetAccessibilityRequirements (), 1 , locationTopReq )
186
224
if err != nil {
187
225
return nil , status .Errorf (codes .InvalidArgument , "CreateVolume failed to pick zones for disk: %v" , err .Error ())
188
226
}
@@ -192,7 +230,7 @@ func (gceCS *GCEControllerServer) CreateVolume(ctx context.Context, req *csi.Cre
192
230
volKey = meta .ZonalKey (name , zones [0 ])
193
231
194
232
case replicationTypeRegionalPD :
195
- zones , err = pickZones (ctx , gceCS , req .GetAccessibilityRequirements (), 2 )
233
+ zones , err = pickZones (ctx , gceCS , req .GetAccessibilityRequirements (), 2 , locationTopReq )
196
234
if err != nil {
197
235
return nil , status .Errorf (codes .InvalidArgument , "CreateVolume failed to pick zones for disk: %v" , err .Error ())
198
236
}
@@ -1358,7 +1396,7 @@ func diskIsAttachedAndCompatible(deviceName string, instance *compute.Instance,
1358
1396
return false , nil
1359
1397
}
1360
1398
1361
- func pickZonesFromTopology (top * csi.TopologyRequirement , numZones int ) ([]string , error ) {
1399
+ func pickZonesFromTopology (top * csi.TopologyRequirement , numZones int , locationTopReq * locationRequirements ) ([]string , error ) {
1362
1400
reqZones , err := getZonesFromTopology (top .GetRequisite ())
1363
1401
if err != nil {
1364
1402
return nil , fmt .Errorf ("could not get zones from requisite topology: %w" , err )
@@ -1368,6 +1406,11 @@ func pickZonesFromTopology(top *csi.TopologyRequirement, numZones int) ([]string
1368
1406
return nil , fmt .Errorf ("could not get zones from preferred topology: %w" , err )
1369
1407
}
1370
1408
1409
+ // For volumeCloning, only 1 zone can be chosen. It doesn't matter if it comes from Requisite or Preferred.
1410
+ if locationTopReq != nil && slices .Contains (prefZones , locationTopReq .zone ) || slices .Contains (reqZones , locationTopReq .zone ) {
1411
+ return []string {locationTopReq .zone }, nil
1412
+ }
1413
+
1371
1414
if numZones <= len (prefZones ) {
1372
1415
return prefZones [0 :numZones ], nil
1373
1416
} else {
@@ -1426,16 +1469,16 @@ func getZoneFromSegment(seg map[string]string) (string, error) {
1426
1469
return zone , nil
1427
1470
}
1428
1471
1429
- func pickZones (ctx context.Context , gceCS * GCEControllerServer , top * csi.TopologyRequirement , numZones int ) ([]string , error ) {
1472
+ func pickZones (ctx context.Context , gceCS * GCEControllerServer , top * csi.TopologyRequirement , numZones int , locationTopReq * locationRequirements ) ([]string , error ) {
1430
1473
var zones []string
1431
1474
var err error
1432
1475
if top != nil {
1433
- zones , err = pickZonesFromTopology (top , numZones )
1476
+ zones , err = pickZonesFromTopology (top , numZones , locationTopReq )
1434
1477
if err != nil {
1435
1478
return nil , fmt .Errorf ("failed to pick zones from topology: %w" , err )
1436
1479
}
1437
1480
} else {
1438
- zones , err = getDefaultZonesInRegion (ctx , gceCS , []string {gceCS .CloudProvider .GetDefaultZone ()}, numZones )
1481
+ zones , err = getDefaultZonesInRegion (ctx , gceCS , []string {gceCS .CloudProvider .GetDefaultZone ()}, numZones , locationTopReq )
1439
1482
if err != nil {
1440
1483
return nil , fmt .Errorf ("failed to get default %v zones in region: %w" , numZones , err )
1441
1484
}
@@ -1445,16 +1488,29 @@ func pickZones(ctx context.Context, gceCS *GCEControllerServer, top *csi.Topolog
1445
1488
return zones , nil
1446
1489
}
1447
1490
1448
- func getDefaultZonesInRegion (ctx context.Context , gceCS * GCEControllerServer , existingZones []string , numZones int ) ([]string , error ) {
1491
+ func getDefaultZonesInRegion (ctx context.Context , gceCS * GCEControllerServer , existingZones []string , numZones int , locationTopReq * locationRequirements ) ([]string , error ) {
1449
1492
region , err := common .GetRegionFromZones (existingZones )
1450
1493
if err != nil {
1451
1494
return nil , fmt .Errorf ("failed to get region from zones: %w" , err )
1452
1495
}
1453
- needToGet := numZones - len ( existingZones )
1496
+
1454
1497
totZones , err := gceCS .CloudProvider .ListZones (ctx , region )
1455
1498
if err != nil {
1456
1499
return nil , fmt .Errorf ("failed to list zones from cloud provider: %w" , err )
1457
1500
}
1501
+
1502
+ // For volumeCloning, only 1 zone can be chosen. It doesn't matter if it comes from Requisite or Preferred.
1503
+ if locationTopReq != nil {
1504
+ if region != locationTopReq .region {
1505
+ return nil , fmt .Errorf ("default region is not suitable for volume cloning" )
1506
+ }
1507
+ if ! slices .Contains (totZones , locationTopReq .zone ) {
1508
+ return nil , fmt .Errorf ("failed to get zone in region %s suitable for volume cloning. Zone must match" , region )
1509
+ }
1510
+ return []string {locationTopReq .zone }, nil
1511
+ }
1512
+
1513
+ needToGet := numZones - len (existingZones )
1458
1514
remainingZones := sets .NewString (totZones ... ).Difference (sets .NewString (existingZones ... ))
1459
1515
l := remainingZones .List ()
1460
1516
if len (l ) < needToGet {
0 commit comments