Skip to content

Commit 8dea010

Browse files
committed
Introduce new flag - strict-topology
With the current implementation, In delayed binding case, CSI driver is offered with all nodes topology that are matched with 'selected node' topology keys in CreateVolumeRequest.AccessibilityRequirements. So this allows the driver to select any node from the passed preferred list to create volume. But this results in scheduling failure when the volume created on a node other than Kubernetes selected node. To address this, introduced new flag "--strict-topology', when set, in case of delayed binding, the driver is offered with only selected node topology, so that driver has to create the volume on this node.
1 parent 967b7a3 commit 8dea010

File tree

5 files changed

+58
-17
lines changed

5 files changed

+58
-17
lines changed

cmd/csi-provisioner/csi-provisioner.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ var (
6262

6363
enableLeaderElection = flag.Bool("enable-leader-election", false, "Enables leader election. If leader election is enabled, additional RBAC rules are required. Please refer to the Kubernetes CSI documentation for instructions on setting up these RBAC rules.")
6464
leaderElectionType = flag.String("leader-election-type", "endpoints", "the type of leader election, options are 'endpoints' (default) or 'leases' (strongly recommended). The 'endpoints' option is deprecated in favor of 'leases'.")
65+
strictTopology = flag.Bool("strict-topology", false, "Passes only selected node topology to CreateVolume Request, unlike default behavior of passing all nodes that match with topology keys of the selected node.")
6566

6667
featureGates map[string]bool
6768
provisionController *controller.ProvisionController
@@ -178,7 +179,7 @@ func main() {
178179

179180
// Create the provisioner: it implements the Provisioner interface expected by
180181
// the controller
181-
csiProvisioner := ctrl.NewCSIProvisioner(clientset, *operationTimeout, identity, *volumeNamePrefix, *volumeNameUUIDLength, grpcClient, snapClient, provisionerName, pluginCapabilities, controllerCapabilities, supportsMigrationFromInTreePluginName)
182+
csiProvisioner := ctrl.NewCSIProvisioner(clientset, *operationTimeout, identity, *volumeNamePrefix, *volumeNameUUIDLength, grpcClient, snapClient, provisionerName, pluginCapabilities, controllerCapabilities, supportsMigrationFromInTreePluginName, *strictTopology)
182183
provisionController = controller.NewProvisionController(
183184
clientset,
184185
provisionerName,

pkg/controller/controller.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ type csiProvisioner struct {
161161
pluginCapabilities connection.PluginCapabilitySet
162162
controllerCapabilities connection.ControllerCapabilitySet
163163
supportsMigrationFromInTreePluginName string
164+
strictTopology bool
164165
}
165166

166167
var _ controller.Provisioner = &csiProvisioner{}
@@ -216,7 +217,8 @@ func NewCSIProvisioner(client kubernetes.Interface,
216217
driverName string,
217218
pluginCapabilities connection.PluginCapabilitySet,
218219
controllerCapabilities connection.ControllerCapabilitySet,
219-
supportsMigrationFromInTreePluginName string) controller.Provisioner {
220+
supportsMigrationFromInTreePluginName string,
221+
strictTopology bool) controller.Provisioner {
220222

221223
csiClient := csi.NewControllerClient(grpcClient)
222224
provisioner := &csiProvisioner{
@@ -232,6 +234,7 @@ func NewCSIProvisioner(client kubernetes.Interface,
232234
pluginCapabilities: pluginCapabilities,
233235
controllerCapabilities: controllerCapabilities,
234236
supportsMigrationFromInTreePluginName: supportsMigrationFromInTreePluginName,
237+
strictTopology: strictTopology,
235238
}
236239
return provisioner
237240
}
@@ -432,7 +435,8 @@ func (p *csiProvisioner) Provision(options controller.ProvisionOptions) (*v1.Per
432435
p.driverName,
433436
options.PVC.Name,
434437
options.StorageClass.AllowedTopologies,
435-
options.SelectedNode)
438+
options.SelectedNode,
439+
p.strictTopology)
436440
if err != nil {
437441
return nil, fmt.Errorf("error generating accessibility requirements: %v", err)
438442
}

pkg/controller/controller_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ func TestCreateDriverReturnsInvalidCapacityDuringProvision(t *testing.T) {
392392
defer driver.Stop()
393393

394394
pluginCaps, controllerCaps := provisionCapabilities()
395-
csiProvisioner := NewCSIProvisioner(nil, 5*time.Second, "test-provisioner", "test", 5, csiConn.conn, nil, driverName, pluginCaps, controllerCaps, "")
395+
csiProvisioner := NewCSIProvisioner(nil, 5*time.Second, "test-provisioner", "test", 5, csiConn.conn, nil, driverName, pluginCaps, controllerCaps, "", false)
396396

397397
// Requested PVC with requestedBytes storage
398398
deletePolicy := v1.PersistentVolumeReclaimDelete
@@ -1287,7 +1287,7 @@ func runProvisionTest(t *testing.T, k string, tc provisioningTestcase, requested
12871287
}
12881288

12891289
pluginCaps, controllerCaps := provisionCapabilities()
1290-
csiProvisioner := NewCSIProvisioner(clientSet, 5*time.Second, "test-provisioner", "test", 5, csiConn.conn, nil, driverName, pluginCaps, controllerCaps, "")
1290+
csiProvisioner := NewCSIProvisioner(clientSet, 5*time.Second, "test-provisioner", "test", 5, csiConn.conn, nil, driverName, pluginCaps, controllerCaps, "", false)
12911291

12921292
out := &csi.CreateVolumeResponse{
12931293
Volume: &csi.Volume{
@@ -1650,7 +1650,7 @@ func TestProvisionFromSnapshot(t *testing.T) {
16501650
})
16511651

16521652
pluginCaps, controllerCaps := provisionFromSnapshotCapabilities()
1653-
csiProvisioner := NewCSIProvisioner(clientSet, 5*time.Second, "test-provisioner", "test", 5, csiConn.conn, client, driverName, pluginCaps, controllerCaps, "")
1653+
csiProvisioner := NewCSIProvisioner(clientSet, 5*time.Second, "test-provisioner", "test", 5, csiConn.conn, client, driverName, pluginCaps, controllerCaps, "", false)
16541654

16551655
out := &csi.CreateVolumeResponse{
16561656
Volume: &csi.Volume{
@@ -1801,7 +1801,7 @@ func TestProvisionWithTopologyEnabled(t *testing.T) {
18011801
}
18021802

18031803
clientSet := fakeclientset.NewSimpleClientset(nodes, nodeInfos)
1804-
csiProvisioner := NewCSIProvisioner(clientSet, 5*time.Second, "test-provisioner", "test", 5, csiConn.conn, nil, driverName, pluginCaps, controllerCaps, "")
1804+
csiProvisioner := NewCSIProvisioner(clientSet, 5*time.Second, "test-provisioner", "test", 5, csiConn.conn, nil, driverName, pluginCaps, controllerCaps, "", false)
18051805

18061806
pv, err := csiProvisioner.Provision(controller.ProvisionOptions{
18071807
StorageClass: &storagev1.StorageClass{},
@@ -1855,7 +1855,7 @@ func TestProvisionWithTopologyDisabled(t *testing.T) {
18551855

18561856
clientSet := fakeclientset.NewSimpleClientset()
18571857
pluginCaps, controllerCaps := provisionWithTopologyCapabilities()
1858-
csiProvisioner := NewCSIProvisioner(clientSet, 5*time.Second, "test-provisioner", "test", 5, csiConn.conn, nil, driverName, pluginCaps, controllerCaps, "")
1858+
csiProvisioner := NewCSIProvisioner(clientSet, 5*time.Second, "test-provisioner", "test", 5, csiConn.conn, nil, driverName, pluginCaps, controllerCaps, "", false)
18591859

18601860
out := &csi.CreateVolumeResponse{
18611861
Volume: &csi.Volume{
@@ -1902,7 +1902,7 @@ func TestProvisionWithMountOptions(t *testing.T) {
19021902

19031903
clientSet := fakeclientset.NewSimpleClientset()
19041904
pluginCaps, controllerCaps := provisionCapabilities()
1905-
csiProvisioner := NewCSIProvisioner(clientSet, 5*time.Second, "test-provisioner", "test", 5, csiConn.conn, nil, driverName, pluginCaps, controllerCaps, "")
1905+
csiProvisioner := NewCSIProvisioner(clientSet, 5*time.Second, "test-provisioner", "test", 5, csiConn.conn, nil, driverName, pluginCaps, controllerCaps, "", false)
19061906

19071907
out := &csi.CreateVolumeResponse{
19081908
Volume: &csi.Volume{
@@ -2076,7 +2076,7 @@ func runDeleteTest(t *testing.T, k string, tc deleteTestcase) {
20762076
}
20772077

20782078
pluginCaps, controllerCaps := provisionCapabilities()
2079-
csiProvisioner := NewCSIProvisioner(clientSet, 5*time.Second, "test-provisioner", "test", 5, csiConn.conn, nil, driverName, pluginCaps, controllerCaps, "")
2079+
csiProvisioner := NewCSIProvisioner(clientSet, 5*time.Second, "test-provisioner", "test", 5, csiConn.conn, nil, driverName, pluginCaps, controllerCaps, "", false)
20802080

20812081
err = csiProvisioner.Delete(tc.persistentVolume)
20822082
if tc.expectErr && err == nil {

pkg/controller/topology.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,8 @@ func GenerateAccessibilityRequirements(
139139
driverName string,
140140
pvcName string,
141141
allowedTopologies []v1.TopologySelectorTerm,
142-
selectedNode *v1.Node) (*csi.TopologyRequirement, error) {
142+
selectedNode *v1.Node,
143+
strictTopology bool) (*csi.TopologyRequirement, error) {
143144
requirement := &csi.TopologyRequirement{}
144145

145146
var (
@@ -163,11 +164,21 @@ func GenerateAccessibilityRequirements(
163164
// 2. Generate CSI Requisite Terms
164165
var requisiteTerms []topologyTerm
165166
if len(allowedTopologies) == 0 {
166-
// Aggregate existing topologies in nodes across the entire cluster.
167-
var err error
168-
requisiteTerms, err = aggregateTopologies(kubeClient, driverName, selectedCSINode)
169-
if err != nil {
170-
return nil, err
167+
if strictTopology {
168+
// Only pass topology of selected node.
169+
topologyKeys := getTopologyKeys(selectedCSINode, driverName)
170+
term, isMissingKey := getTopologyFromNode(selectedNode, topologyKeys)
171+
if isMissingKey {
172+
return nil, fmt.Errorf("topology labels from selected node %v does not match topology keys from CSINode %v", selectedNode.Labels, topologyKeys)
173+
}
174+
requisiteTerms = append(requisiteTerms, term)
175+
} else {
176+
// Aggregate existing topologies in nodes across the entire cluster.
177+
var err error
178+
requisiteTerms, err = aggregateTopologies(kubeClient, driverName, selectedCSINode)
179+
if err != nil {
180+
return nil, err
181+
}
171182
}
172183
} else {
173184
// Distribute out one of the OR layers in allowedTopologies
@@ -200,6 +211,8 @@ func GenerateAccessibilityRequirements(
200211
hash, index := getPVCNameHashAndIndexOffset(pvcName)
201212
i := (hash + index) % uint32(len(requisiteTerms))
202213
preferredTerms = sortAndShift(requisiteTerms, nil, i)
214+
} else if strictTopology {
215+
preferredTerms = requisiteTerms
203216
} else {
204217
// Delayed binding, use topology from that node to populate preferredTerms
205218
topologyKeys := getTopologyKeys(selectedCSINode, driverName)

pkg/controller/topology_test.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,7 @@ func TestStatefulSetSpreading(t *testing.T) {
399399
tc.pvcName,
400400
tc.allowedTopologies,
401401
nil,
402+
false, /* strictTopology */
402403
)
403404

404405
if err != nil {
@@ -785,7 +786,9 @@ func TestAllowedTopologies(t *testing.T) {
785786
"test-driver", /* driverName */
786787
"testpvc",
787788
tc.allowedTopologies,
788-
nil /* selectedNode */)
789+
nil, /* selectedNode */
790+
false, /* strictTopology */
791+
)
789792

790793
if err != nil {
791794
t.Errorf("expected no error but got: %v", err)
@@ -808,6 +811,7 @@ func TestTopologyAggregation(t *testing.T) {
808811
nodeLabels []map[string]string
809812
topologyKeys []map[string][]string
810813
hasSelectedNode bool // if set, the first map in nodeLabels is for the selected node.
814+
strictTopology bool
811815
preBetaNode bool // use a node before 1.14
812816
expectedRequisite []*csi.Topology
813817
expectError bool
@@ -876,6 +880,23 @@ func TestTopologyAggregation(t *testing.T) {
876880
{Segments: map[string]string{"com.example.csi/zone": "zone2"}},
877881
},
878882
},
883+
"selected node; different values across cluster with strict topology": {
884+
hasSelectedNode: true,
885+
strictTopology: true,
886+
nodeLabels: []map[string]string{
887+
{"com.example.csi/zone": "zone1"},
888+
{"com.example.csi/zone": "zone2"},
889+
{"com.example.csi/zone": "zone2"},
890+
},
891+
topologyKeys: []map[string][]string{
892+
{testDriverName: []string{"com.example.csi/zone"}},
893+
{testDriverName: []string{"com.example.csi/zone"}},
894+
{testDriverName: []string{"com.example.csi/zone"}},
895+
},
896+
expectedRequisite: []*csi.Topology{
897+
{Segments: map[string]string{"com.example.csi/zone": "zone1"}},
898+
},
899+
},
879900
//"different keys across cluster": {
880901
// nodeLabels: []map[string]string{
881902
// { "com.example.csi/zone": "zone1" },
@@ -1061,6 +1082,7 @@ func TestTopologyAggregation(t *testing.T) {
10611082
"testpvc",
10621083
nil, /* allowedTopologies */
10631084
selectedNode,
1085+
tc.strictTopology,
10641086
)
10651087

10661088
if tc.expectError {
@@ -1278,6 +1300,7 @@ func TestPreferredTopologies(t *testing.T) {
12781300
"testpvc",
12791301
tc.allowedTopologies,
12801302
selectedNode,
1303+
false, /* strictTopology */
12811304
)
12821305

12831306
if tc.expectError {

0 commit comments

Comments
 (0)