Skip to content

Commit d37f066

Browse files
authored
Merge pull request #446 from Omar007/feature/multi-node-pv
[Feature] Allow additional selector terms to be defined in storage config
2 parents ad20e13 + 2729447 commit d37f066

File tree

7 files changed

+142
-195
lines changed

7 files changed

+142
-195
lines changed

helm/provisioner/templates/configmap.yaml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ data:
2626
{{- end }}
2727
{{- if .Values.useJobForCleaning }}
2828
useJobForCleaning: "yes"
29-
{{- end}}
29+
{{- end }}
3030
{{- if .Values.tolerations }}
3131
jobTolerations: | {{ toYaml .Values.tolerations | nindent 4 }}
3232
{{- end }}
@@ -35,7 +35,7 @@ data:
3535
{{- end }}
3636
{{- if .Values.minResyncPeriod }}
3737
minResyncPeriod: {{ .Values.minResyncPeriod | quote }}
38-
{{- end}}
38+
{{- end }}
3939
storageClassMap: |
4040
{{- range $classConfig := .Values.classes }}
4141
{{ $classConfig.name }}:
@@ -45,7 +45,7 @@ data:
4545
blockCleanerCommand:
4646
{{- range $val := $classConfig.blockCleanerCommand }}
4747
- {{ $val | quote }}
48-
{{- end}}
48+
{{- end }}
4949
{{- end }}
5050
{{- if $classConfig.volumeMode }}
5151
volumeMode: {{ $classConfig.volumeMode }}
@@ -56,4 +56,8 @@ data:
5656
{{- if $classConfig.namePattern }}
5757
namePattern: {{ $classConfig.namePattern | quote }}
5858
{{- end }}
59+
{{- if $classConfig.selector }}
60+
selector:
61+
{{- toYaml $classConfig.selector | nindent 8 }}
62+
{{- end }}
5963
{{- end }}

pkg/common/common.go

Lines changed: 16 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ import (
4444
"k8s.io/client-go/rest"
4545
"k8s.io/client-go/tools/clientcmd"
4646
"k8s.io/client-go/tools/record"
47-
volumeUtil "k8s.io/kubernetes/pkg/volume/util"
4847
"k8s.io/utils/mount"
4948
)
5049

@@ -141,6 +140,9 @@ type MountConfig struct {
141140
// NamePattern name pattern check
142141
// only discover file name matching pattern("*" by default)
143142
NamePattern string `json:"namePattern" yaml:"namePattern"`
143+
// Additional selector terms to set for node affinity in addition to the provisioner node name.
144+
// Useful for shared disks as affinity can not be changed after provisioning the PV.
145+
Selector []v1.NodeSelectorTerm `json:"selector" yaml:"selector"`
144146
}
145147

146148
// RuntimeConfig stores all the objects that the provisioner needs to run
@@ -495,44 +497,25 @@ func GetVolumeMode(volUtil util.VolumeUtil, fullPath string) (v1.PersistentVolum
495497
return "", fmt.Errorf("Block device check for %q failed: %s", fullPath, errblk)
496498
}
497499

498-
// NodeExists checks to see if a Node exists in the Indexer of a NodeLister.
499-
// It tries to get the node and if it fails, it uses the well known label
500-
// `kubernetes.io/hostname` to find the Node.
501-
func NodeExists(nodeLister corelisters.NodeLister, nodeName string) (bool, error) {
502-
_, err := nodeLister.Get(nodeName)
503-
if errors.IsNotFound(err) {
500+
// AnyNodeExists checks to see if a Node exists in the Indexer of a NodeLister.
501+
// If this fails, it uses the well known label `kubernetes.io/hostname` to find the Node.
502+
// It aborts early if an unexpected error occurs and it's uncertain if a node would exist or not.
503+
func AnyNodeExists(nodeLister corelisters.NodeLister, nodeNames []string) bool {
504+
for _, nodeName := range nodeNames {
505+
_, err := nodeLister.Get(nodeName)
506+
if err == nil || !errors.IsNotFound(err) {
507+
return true
508+
}
504509
req, err := labels.NewRequirement(NodeLabelKey, selection.Equals, []string{nodeName})
505510
if err != nil {
506-
return false, err
511+
return true
507512
}
508513
nodes, err := nodeLister.List(labels.NewSelector().Add(*req))
509-
if err != nil {
510-
return false, err
514+
if err != nil || len(nodes) > 0 {
515+
return true
511516
}
512-
return len(nodes) > 0, nil
513517
}
514-
return err == nil, err
515-
}
516-
517-
// NodeAttachedToLocalPV gets the name of the Node that a local PV has a NodeAffinity to.
518-
// It assumes that there should be only one matching Node for a local PV and that
519-
// the local PV follows the form:
520-
//
521-
// nodeAffinity:
522-
// required:
523-
// nodeSelectorTerms:
524-
// - matchExpressions:
525-
// - key: kubernetes.io/hostname
526-
// operator: In
527-
// values:
528-
// - <node1>
529-
func NodeAttachedToLocalPV(pv *v1.PersistentVolume) (string, bool) {
530-
nodeNames := volumeUtil.GetLocalPersistentVolumeNodeNames(pv)
531-
// We assume that there should only be one matching node.
532-
if nodeNames == nil || len(nodeNames) != 1 {
533-
return "", false
534-
}
535-
return nodeNames[0], true
518+
return false
536519
}
537520

538521
// IsLocalPVWithStorageClass checks that a PV is a local PV that belongs to any of the passed in StorageClasses.

pkg/common/common_test.go

Lines changed: 64 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,46 @@ func TestLoadProvisionerConfigs(t *testing.T) {
236236
},
237237
nil,
238238
},
239+
{
240+
map[string]string{"storageClassMap": `local-storage:
241+
hostDir: /mnt/disks
242+
mountDir: /mnt/disks
243+
selector:
244+
- matchExpressions:
245+
- key: "kubernetes.io/hostname"
246+
operator: "In"
247+
values:
248+
- otherNode1
249+
`,
250+
},
251+
ProvisionerConfiguration{
252+
StorageClassConfig: map[string]MountConfig{
253+
"local-storage": {
254+
HostDir: "/mnt/disks",
255+
MountDir: "/mnt/disks",
256+
BlockCleanerCommand: []string{"/scripts/quick_reset.sh"},
257+
VolumeMode: "Filesystem",
258+
NamePattern: "*",
259+
Selector: []v1.NodeSelectorTerm{
260+
{
261+
MatchExpressions: []v1.NodeSelectorRequirement{
262+
{
263+
Key: "kubernetes.io/hostname",
264+
Operator: v1.NodeSelectorOpIn,
265+
Values: []string{"otherNode1"},
266+
},
267+
},
268+
},
269+
},
270+
},
271+
},
272+
UseAlphaAPI: true,
273+
MinResyncPeriod: metav1.Duration{
274+
Duration: time.Hour + time.Minute*30,
275+
},
276+
},
277+
nil,
278+
},
239279
}
240280
for _, v := range testcases {
241281
for name, value := range v.data {
@@ -477,7 +517,7 @@ func TestGetVolumeMode(t *testing.T) {
477517
}
478518
}
479519

480-
func TestNodeExists(t *testing.T) {
520+
func TestAnyNodeExists(t *testing.T) {
481521
nodeName := "test-node"
482522
nodeWithName := &v1.Node{
483523
ObjectMeta: metav1.ObjectMeta{
@@ -495,21 +535,39 @@ func TestNodeExists(t *testing.T) {
495535
tests := []struct {
496536
nodeAdded *v1.Node
497537
// Required.
498-
nodeQueried *v1.Node
538+
nodeQueried []string
499539
expectedResult bool
500540
}{
501541
{
502542
nodeAdded: nodeWithName,
503-
nodeQueried: nodeWithName,
543+
nodeQueried: []string{nodeName},
504544
expectedResult: true,
505545
},
506546
{
507547
nodeAdded: nodeWithLabel,
508-
nodeQueried: nodeWithName,
548+
nodeQueried: []string{nodeName},
509549
expectedResult: true,
510550
},
511551
{
512-
nodeQueried: nodeWithName,
552+
nodeQueried: []string{nodeName},
553+
expectedResult: false,
554+
},
555+
{
556+
nodeAdded: nodeWithName,
557+
nodeQueried: []string{"other-node", nodeName},
558+
expectedResult: true,
559+
},
560+
{
561+
nodeAdded: nodeWithLabel,
562+
nodeQueried: []string{"other-node", nodeName},
563+
expectedResult: true,
564+
},
565+
{
566+
nodeQueried: []string{},
567+
expectedResult: false,
568+
},
569+
{
570+
nodeQueried: nil,
513571
expectedResult: false,
514572
},
515573
}
@@ -523,62 +581,13 @@ func TestNodeExists(t *testing.T) {
523581
nodeInformer.Informer().GetStore().Add(test.nodeAdded)
524582
}
525583

526-
exists, err := NodeExists(nodeInformer.Lister(), test.nodeQueried.Name)
527-
if err != nil {
528-
t.Errorf("Got unexpected error: %s", err.Error())
529-
}
584+
exists := AnyNodeExists(nodeInformer.Lister(), test.nodeQueried)
530585
if exists != test.expectedResult {
531586
t.Errorf("expected result: %t, actual: %t", test.expectedResult, exists)
532587
}
533588
}
534589
}
535590

536-
func TestNodeAttachedToLocalPV(t *testing.T) {
537-
nodeName := "testNodeName"
538-
539-
tests := []struct {
540-
name string
541-
pv *v1.PersistentVolume
542-
expectedNodeName string
543-
expectedStatus bool
544-
}{
545-
{
546-
name: "NodeAffinity will all necessary fields",
547-
pv: withNodeAffinity(pv(), []string{nodeName}, NodeLabelKey),
548-
expectedNodeName: nodeName,
549-
expectedStatus: true,
550-
},
551-
{
552-
name: "empty nodeNames array",
553-
pv: withNodeAffinity(pv(), []string{}, NodeLabelKey),
554-
expectedNodeName: "",
555-
expectedStatus: false,
556-
},
557-
{
558-
name: "multiple nodeNames",
559-
pv: withNodeAffinity(pv(), []string{nodeName, "newNode"}, NodeLabelKey),
560-
expectedNodeName: "",
561-
expectedStatus: false,
562-
},
563-
{
564-
name: "wrong node label key",
565-
pv: withNodeAffinity(pv(), []string{nodeName}, "wrongLabel"),
566-
expectedNodeName: "",
567-
expectedStatus: false,
568-
},
569-
}
570-
571-
for _, test := range tests {
572-
nodeName, ok := NodeAttachedToLocalPV(test.pv)
573-
if ok != test.expectedStatus {
574-
t.Errorf("test: %s, status: %t, expectedStaus: %t", test.name, ok, test.expectedStatus)
575-
}
576-
if nodeName != test.expectedNodeName {
577-
t.Errorf("test: %s, nodeName: %s, expectedNodeName: %s", test.name, nodeName, test.expectedNodeName)
578-
}
579-
}
580-
}
581-
582591
func TestIsLocalPVWithStorageClass(t *testing.T) {
583592
tests := []struct {
584593
name string

0 commit comments

Comments
 (0)