diff --git a/pkg/volume/util/util.go b/pkg/volume/util/util.go index 88b82f1f5f5..b9d8629d659 100644 --- a/pkg/volume/util/util.go +++ b/pkg/volume/util/util.go @@ -237,6 +237,13 @@ func GetClassForVolume(kubeClient clientset.Interface, pv *v1.PersistentVolume) // CheckNodeAffinity looks at the PV node affinity, and checks if the node has the same corresponding labels // This ensures that we don't mount a volume that doesn't belong to this node func CheckNodeAffinity(pv *v1.PersistentVolume, nodeLabels map[string]string) error { + if err := checkAlphaNodeAffinity(pv, nodeLabels); err != nil { + return err + } + return checkVolumeNodeAffinity(pv, nodeLabels) +} + +func checkAlphaNodeAffinity(pv *v1.PersistentVolume, nodeLabels map[string]string) error { affinity, err := v1helper.GetStorageNodeAffinityFromAnnotation(pv.Annotations) if err != nil { return fmt.Errorf("Error getting storage node affinity: %v", err) @@ -261,6 +268,27 @@ func CheckNodeAffinity(pv *v1.PersistentVolume, nodeLabels map[string]string) er return nil } +func checkVolumeNodeAffinity(pv *v1.PersistentVolume, nodeLabels map[string]string) error { + if pv.Spec.NodeAffinity == nil { + return nil + } + + if pv.Spec.NodeAffinity.Required != nil { + terms := pv.Spec.NodeAffinity.Required.NodeSelectorTerms + glog.V(10).Infof("Match for Required node selector terms %+v", terms) + for _, term := range terms { + selector, err := v1helper.NodeSelectorRequirementsAsSelector(term.MatchExpressions) + if err != nil { + return fmt.Errorf("Failed to parse MatchExpressions: %v", err) + } + if !selector.Matches(labels.Set(nodeLabels)) { + return fmt.Errorf("NodeSelectorTerm %+v does not match node labels", term.MatchExpressions) + } + } + } + return nil +} + // LoadPodFromFile will read, decode, and return a Pod from a file. func LoadPodFromFile(filePath string) (*v1.Pod, error) { if filePath == "" { diff --git a/pkg/volume/util/util_test.go b/pkg/volume/util/util_test.go index 6dcc125eb59..b9da3df96e4 100644 --- a/pkg/volume/util/util_test.go +++ b/pkg/volume/util/util_test.go @@ -37,7 +37,7 @@ var nodeLabels map[string]string = map[string]string{ "test-key2": "test-value2", } -func TestCheckNodeAffinity(t *testing.T) { +func TestCheckAlphaNodeAffinity(t *testing.T) { type affinityTest struct { name string expectSuccess bool @@ -48,12 +48,12 @@ func TestCheckNodeAffinity(t *testing.T) { { name: "valid-no-constraints", expectSuccess: true, - pv: testVolumeWithNodeAffinity(t, &v1.NodeAffinity{}), + pv: testVolumeWithAlphaNodeAffinity(t, &v1.NodeAffinity{}), }, { name: "valid-constraints", expectSuccess: true, - pv: testVolumeWithNodeAffinity(t, &v1.NodeAffinity{ + pv: testVolumeWithAlphaNodeAffinity(t, &v1.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ NodeSelectorTerms: []v1.NodeSelectorTerm{ { @@ -77,7 +77,7 @@ func TestCheckNodeAffinity(t *testing.T) { { name: "invalid-key", expectSuccess: false, - pv: testVolumeWithNodeAffinity(t, &v1.NodeAffinity{ + pv: testVolumeWithAlphaNodeAffinity(t, &v1.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ NodeSelectorTerms: []v1.NodeSelectorTerm{ { @@ -101,7 +101,7 @@ func TestCheckNodeAffinity(t *testing.T) { { name: "invalid-values", expectSuccess: false, - pv: testVolumeWithNodeAffinity(t, &v1.NodeAffinity{ + pv: testVolumeWithAlphaNodeAffinity(t, &v1.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ NodeSelectorTerms: []v1.NodeSelectorTerm{ { @@ -136,7 +136,111 @@ func TestCheckNodeAffinity(t *testing.T) { } } -func testVolumeWithNodeAffinity(t *testing.T, affinity *v1.NodeAffinity) *v1.PersistentVolume { +func TestCheckVolumeNodeAffinity(t *testing.T) { + type affinityTest struct { + name string + expectSuccess bool + pv *v1.PersistentVolume + } + + cases := []affinityTest{ + { + name: "valid-nil", + expectSuccess: true, + pv: testVolumeWithNodeAffinity(t, nil), + }, + { + name: "valid-no-constraints", + expectSuccess: true, + pv: testVolumeWithNodeAffinity(t, &v1.VolumeNodeAffinity{}), + }, + { + name: "valid-constraints", + expectSuccess: true, + pv: testVolumeWithNodeAffinity(t, &v1.VolumeNodeAffinity{ + Required: &v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "test-key1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"test-value1", "test-value3"}, + }, + { + Key: "test-key2", + Operator: v1.NodeSelectorOpIn, + Values: []string{"test-value0", "test-value2"}, + }, + }, + }, + }, + }, + }), + }, + { + name: "invalid-key", + expectSuccess: false, + pv: testVolumeWithNodeAffinity(t, &v1.VolumeNodeAffinity{ + Required: &v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "test-key1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"test-value1", "test-value3"}, + }, + { + Key: "test-key3", + Operator: v1.NodeSelectorOpIn, + Values: []string{"test-value0", "test-value2"}, + }, + }, + }, + }, + }, + }), + }, + { + name: "invalid-values", + expectSuccess: false, + pv: testVolumeWithNodeAffinity(t, &v1.VolumeNodeAffinity{ + Required: &v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "test-key1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"test-value3", "test-value4"}, + }, + { + Key: "test-key2", + Operator: v1.NodeSelectorOpIn, + Values: []string{"test-value0", "test-value2"}, + }, + }, + }, + }, + }, + }), + }, + } + + for _, c := range cases { + err := CheckNodeAffinity(c.pv, nodeLabels) + + if err != nil && c.expectSuccess { + t.Errorf("CheckTopology %v returned error: %v", c.name, err) + } + if err == nil && !c.expectSuccess { + t.Errorf("CheckTopology %v returned success, expected error", c.name) + } + } +} + +func testVolumeWithAlphaNodeAffinity(t *testing.T, affinity *v1.NodeAffinity) *v1.PersistentVolume { objMeta := metav1.ObjectMeta{Name: "test-constraints"} objMeta.Annotations = map[string]string{} err := helper.StorageNodeAffinityToAlphaAnnotation(objMeta.Annotations, affinity) @@ -149,6 +253,16 @@ func testVolumeWithNodeAffinity(t *testing.T, affinity *v1.NodeAffinity) *v1.Per } } +func testVolumeWithNodeAffinity(t *testing.T, affinity *v1.VolumeNodeAffinity) *v1.PersistentVolume { + objMeta := metav1.ObjectMeta{Name: "test-constraints"} + return &v1.PersistentVolume{ + ObjectMeta: objMeta, + Spec: v1.PersistentVolumeSpec{ + NodeAffinity: affinity, + }, + } +} + func TestLoadPodFromFile(t *testing.T) { tests := []struct { name string