Skip to content

Commit c0f7b74

Browse files
committed
fix bug where volume cloning topology requirements are ignored when chosing the location of the volume
1 parent 0e7a44f commit c0f7b74

File tree

2 files changed

+66
-10
lines changed

2 files changed

+66
-10
lines changed

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

+65-9
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import (
3232
"k8s.io/apimachinery/pkg/util/uuid"
3333
"k8s.io/client-go/util/flowcontrol"
3434
"k8s.io/klog/v2"
35-
35+
"k8s.io/utils/strings/slices"
3636
"sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/pkg/common"
3737
gce "sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/pkg/gce-cloud-provider/compute"
3838
)
@@ -98,6 +98,12 @@ type workItem struct {
9898
unpublishReq *csi.ControllerUnpublishVolumeRequest
9999
}
100100

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+
101107
var _ csi.ControllerServer = &GCEControllerServer{}
102108

103109
const (
@@ -142,6 +148,29 @@ func isDiskReady(disk *gce.CloudDisk) (bool, error) {
142148
return false, nil
143149
}
144150

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+
145174
func (gceCS *GCEControllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) {
146175
var err error
147176
// Validate arguments
@@ -177,12 +206,21 @@ func (gceCS *GCEControllerServer) CreateVolume(ctx context.Context, req *csi.Cre
177206
if multiWriter {
178207
gceAPIVersion = gce.GCEAPIVersionBeta
179208
}
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+
180218
// Determine the zone or zones+region of the disk
181219
var zones []string
182220
var volKey *meta.Key
183221
switch params.ReplicationType {
184222
case replicationTypeNone:
185-
zones, err = pickZones(ctx, gceCS, req.GetAccessibilityRequirements(), 1)
223+
zones, err = pickZones(ctx, gceCS, req.GetAccessibilityRequirements(), 1, locationTopReq)
186224
if err != nil {
187225
return nil, status.Errorf(codes.InvalidArgument, "CreateVolume failed to pick zones for disk: %v", err.Error())
188226
}
@@ -192,7 +230,7 @@ func (gceCS *GCEControllerServer) CreateVolume(ctx context.Context, req *csi.Cre
192230
volKey = meta.ZonalKey(name, zones[0])
193231

194232
case replicationTypeRegionalPD:
195-
zones, err = pickZones(ctx, gceCS, req.GetAccessibilityRequirements(), 2)
233+
zones, err = pickZones(ctx, gceCS, req.GetAccessibilityRequirements(), 2, locationTopReq)
196234
if err != nil {
197235
return nil, status.Errorf(codes.InvalidArgument, "CreateVolume failed to pick zones for disk: %v", err.Error())
198236
}
@@ -1358,7 +1396,7 @@ func diskIsAttachedAndCompatible(deviceName string, instance *compute.Instance,
13581396
return false, nil
13591397
}
13601398

1361-
func pickZonesFromTopology(top *csi.TopologyRequirement, numZones int) ([]string, error) {
1399+
func pickZonesFromTopology(top *csi.TopologyRequirement, numZones int, locationTopReq *locationRequirements) ([]string, error) {
13621400
reqZones, err := getZonesFromTopology(top.GetRequisite())
13631401
if err != nil {
13641402
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
13681406
return nil, fmt.Errorf("could not get zones from preferred topology: %w", err)
13691407
}
13701408

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+
13711414
if numZones <= len(prefZones) {
13721415
return prefZones[0:numZones], nil
13731416
} else {
@@ -1426,16 +1469,16 @@ func getZoneFromSegment(seg map[string]string) (string, error) {
14261469
return zone, nil
14271470
}
14281471

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) {
14301473
var zones []string
14311474
var err error
14321475
if top != nil {
1433-
zones, err = pickZonesFromTopology(top, numZones)
1476+
zones, err = pickZonesFromTopology(top, numZones, locationTopReq)
14341477
if err != nil {
14351478
return nil, fmt.Errorf("failed to pick zones from topology: %w", err)
14361479
}
14371480
} else {
1438-
zones, err = getDefaultZonesInRegion(ctx, gceCS, []string{gceCS.CloudProvider.GetDefaultZone()}, numZones)
1481+
zones, err = getDefaultZonesInRegion(ctx, gceCS, []string{gceCS.CloudProvider.GetDefaultZone()}, numZones, locationTopReq)
14391482
if err != nil {
14401483
return nil, fmt.Errorf("failed to get default %v zones in region: %w", numZones, err)
14411484
}
@@ -1445,16 +1488,29 @@ func pickZones(ctx context.Context, gceCS *GCEControllerServer, top *csi.Topolog
14451488
return zones, nil
14461489
}
14471490

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) {
14491492
region, err := common.GetRegionFromZones(existingZones)
14501493
if err != nil {
14511494
return nil, fmt.Errorf("failed to get region from zones: %w", err)
14521495
}
1453-
needToGet := numZones - len(existingZones)
1496+
14541497
totZones, err := gceCS.CloudProvider.ListZones(ctx, region)
14551498
if err != nil {
14561499
return nil, fmt.Errorf("failed to list zones from cloud provider: %w", err)
14571500
}
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)
14581514
remainingZones := sets.NewString(totZones...).Difference(sets.NewString(existingZones...))
14591515
l := remainingZones.List()
14601516
if len(l) < needToGet {

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -1924,7 +1924,7 @@ func TestPickZonesFromTopology(t *testing.T) {
19241924
}
19251925
for _, tc := range testCases {
19261926
t.Logf("test case: %s", tc.name)
1927-
gotZones, err := pickZonesFromTopology(tc.top, tc.numZones)
1927+
gotZones, err := pickZonesFromTopology(tc.top, tc.numZones, nil)
19281928
if err != nil && !tc.expErr {
19291929
t.Errorf("Did not expect error but got: %v", err)
19301930
}

0 commit comments

Comments
 (0)