mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-27 21:47:07 +00:00
Volume node affinity enforcement
This commit is contained in:
parent
dc1e871828
commit
9aa82b6e7d
@ -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
|
// 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
|
// 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 {
|
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)
|
affinity, err := v1helper.GetStorageNodeAffinityFromAnnotation(pv.Annotations)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error getting storage node affinity: %v", err)
|
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
|
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.
|
// LoadPodFromFile will read, decode, and return a Pod from a file.
|
||||||
func LoadPodFromFile(filePath string) (*v1.Pod, error) {
|
func LoadPodFromFile(filePath string) (*v1.Pod, error) {
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
|
@ -37,7 +37,7 @@ var nodeLabels map[string]string = map[string]string{
|
|||||||
"test-key2": "test-value2",
|
"test-key2": "test-value2",
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckNodeAffinity(t *testing.T) {
|
func TestCheckAlphaNodeAffinity(t *testing.T) {
|
||||||
type affinityTest struct {
|
type affinityTest struct {
|
||||||
name string
|
name string
|
||||||
expectSuccess bool
|
expectSuccess bool
|
||||||
@ -48,12 +48,12 @@ func TestCheckNodeAffinity(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "valid-no-constraints",
|
name: "valid-no-constraints",
|
||||||
expectSuccess: true,
|
expectSuccess: true,
|
||||||
pv: testVolumeWithNodeAffinity(t, &v1.NodeAffinity{}),
|
pv: testVolumeWithAlphaNodeAffinity(t, &v1.NodeAffinity{}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "valid-constraints",
|
name: "valid-constraints",
|
||||||
expectSuccess: true,
|
expectSuccess: true,
|
||||||
pv: testVolumeWithNodeAffinity(t, &v1.NodeAffinity{
|
pv: testVolumeWithAlphaNodeAffinity(t, &v1.NodeAffinity{
|
||||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||||
{
|
{
|
||||||
@ -77,7 +77,7 @@ func TestCheckNodeAffinity(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "invalid-key",
|
name: "invalid-key",
|
||||||
expectSuccess: false,
|
expectSuccess: false,
|
||||||
pv: testVolumeWithNodeAffinity(t, &v1.NodeAffinity{
|
pv: testVolumeWithAlphaNodeAffinity(t, &v1.NodeAffinity{
|
||||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||||
{
|
{
|
||||||
@ -101,7 +101,7 @@ func TestCheckNodeAffinity(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "invalid-values",
|
name: "invalid-values",
|
||||||
expectSuccess: false,
|
expectSuccess: false,
|
||||||
pv: testVolumeWithNodeAffinity(t, &v1.NodeAffinity{
|
pv: testVolumeWithAlphaNodeAffinity(t, &v1.NodeAffinity{
|
||||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
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 := metav1.ObjectMeta{Name: "test-constraints"}
|
||||||
objMeta.Annotations = map[string]string{}
|
objMeta.Annotations = map[string]string{}
|
||||||
err := helper.StorageNodeAffinityToAlphaAnnotation(objMeta.Annotations, affinity)
|
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) {
|
func TestLoadPodFromFile(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
Loading…
Reference in New Issue
Block a user