diff --git a/pkg/utils/webhook.go b/pkg/utils/webhook.go index d06b2caf9c2..c8ad05e922e 100644 --- a/pkg/utils/webhook.go +++ b/pkg/utils/webhook.go @@ -62,10 +62,24 @@ func InjectNodeSelectorTerms(requiredSchedulingTerms []corev1.NodeSelectorTerm, if len(pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms) == 0 { pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms = requiredSchedulingTerms } else { - for i := 0; i < len(requiredSchedulingTerms); i++ { - pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[0].MatchExpressions = - append(pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[0].MatchExpressions, requiredSchedulingTerms[i].MatchExpressions...) + existingTerms := pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms + combinedTerms := make([]corev1.NodeSelectorTerm, 0, len(existingTerms)*len(requiredSchedulingTerms)) + for i := 0; i < len(existingTerms); i++ { + if len(existingTerms[i].MatchExpressions) == 0 && len(existingTerms[i].MatchFields) == 0 { + continue + } + for j := 0; j < len(requiredSchedulingTerms); j++ { + if len(requiredSchedulingTerms[j].MatchExpressions) == 0 && len(requiredSchedulingTerms[j].MatchFields) == 0 { + continue + } + combinedTerm := corev1.NodeSelectorTerm{ + MatchExpressions: append(append([]corev1.NodeSelectorRequirement{}, existingTerms[i].MatchExpressions...), requiredSchedulingTerms[j].MatchExpressions...), + MatchFields: append(append([]corev1.NodeSelectorRequirement{}, existingTerms[i].MatchFields...), requiredSchedulingTerms[j].MatchFields...), + } + combinedTerms = append(combinedTerms, combinedTerm) + } } + pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms = combinedTerms } } diff --git a/pkg/utils/webhook_test.go b/pkg/utils/webhook_test.go index 6bba1d3099d..3ad92a28437 100644 --- a/pkg/utils/webhook_test.go +++ b/pkg/utils/webhook_test.go @@ -2,6 +2,7 @@ package utils import ( "math/rand" + "reflect" "testing" "time" @@ -73,7 +74,7 @@ func TestInjectNodeSelectorTerms(t *testing.T) { testCases := map[string]struct { nodeSelectorTermList []corev1.NodeSelectorTerm pod *corev1.Pod - expectLen int + expectTerms []corev1.NodeSelectorTerm }{ "test empty nodeSelectorTermList ": { nodeSelectorTermList: []corev1.NodeSelectorTerm{}, @@ -88,7 +89,7 @@ func TestInjectNodeSelectorTerms(t *testing.T) { }, }, }, - expectLen: 0, + expectTerms: []corev1.NodeSelectorTerm{}, }, "test no empty nodeSelectorTermList ": { nodeSelectorTermList: []corev1.NodeSelectorTerm{ @@ -104,7 +105,16 @@ func TestInjectNodeSelectorTerms(t *testing.T) { pod: &corev1.Pod{ Spec: corev1.PodSpec{}, }, - expectLen: 1, + expectTerms: []corev1.NodeSelectorTerm{ + { + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Operator: corev1.NodeSelectorOpIn, + Values: []string{"test-label-value"}, + }, + }, + }, + }, }, "test add no empty nodeSelectorTermList to pod which alredy have matchExpression": { nodeSelectorTermList: []corev1.NodeSelectorTerm{ @@ -138,21 +148,352 @@ func TestInjectNodeSelectorTerms(t *testing.T) { }, }, }, - expectLen: 2, + expectTerms: []corev1.NodeSelectorTerm{ + { + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "test", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"test-label-value2"}, + }, + { + Operator: corev1.NodeSelectorOpIn, + Values: []string{"test-label-value"}, + }, + }, + }, + }, + }, + "test cross product with existing and injected or terms": { + nodeSelectorTermList: []corev1.NodeSelectorTerm{ + { + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "disk", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"ssd"}, + }, + }, + }, + { + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "cpu", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"amd"}, + }, + }, + }, + }, + pod: &corev1.Pod{ + Spec: corev1.PodSpec{ + Affinity: &corev1.Affinity{ + NodeAffinity: &corev1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ + NodeSelectorTerms: []corev1.NodeSelectorTerm{ + { + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "zone", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"z1"}, + }, + }, + }, + { + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "region", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"r1"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + expectTerms: []corev1.NodeSelectorTerm{ + { + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "zone", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"z1"}, + }, + { + Key: "disk", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"ssd"}, + }, + }, + }, + { + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "zone", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"z1"}, + }, + { + Key: "cpu", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"amd"}, + }, + }, + }, + { + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "region", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"r1"}, + }, + { + Key: "disk", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"ssd"}, + }, + }, + }, + { + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "region", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"r1"}, + }, + { + Key: "cpu", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"amd"}, + }, + }, + }, + }, + }, + "test skip empty existing term when building cross product": { + nodeSelectorTermList: []corev1.NodeSelectorTerm{ + { + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "fluid.io/fuse", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"true"}, + }, + }, + }, + }, + pod: &corev1.Pod{ + Spec: corev1.PodSpec{ + Affinity: &corev1.Affinity{ + NodeAffinity: &corev1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ + NodeSelectorTerms: []corev1.NodeSelectorTerm{ + {}, + { + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "region", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"us-east-1"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + expectTerms: []corev1.NodeSelectorTerm{ + { + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "region", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"us-east-1"}, + }, + { + Key: "fluid.io/fuse", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"true"}, + }, + }, + }, + }, + }, + "test skip empty injected term when building cross product": { + nodeSelectorTermList: []corev1.NodeSelectorTerm{ + {}, + { + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "fluid.io/fuse", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"true"}, + }, + }, + }, + }, + pod: &corev1.Pod{ + Spec: corev1.PodSpec{ + Affinity: &corev1.Affinity{ + NodeAffinity: &corev1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ + NodeSelectorTerms: []corev1.NodeSelectorTerm{ + { + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "region", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"us-east-1"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + expectTerms: []corev1.NodeSelectorTerm{ + { + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "region", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"us-east-1"}, + }, + { + Key: "fluid.io/fuse", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"true"}, + }, + }, + }, + }, + }, + "test merge match fields when building cross product": { + nodeSelectorTermList: []corev1.NodeSelectorTerm{ + { + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "fluid.io/fuse", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"true"}, + }, + }, + MatchFields: []corev1.NodeSelectorRequirement{ + { + Key: "metadata.name", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"node-b"}, + }, + }, + }, + }, + pod: &corev1.Pod{ + Spec: corev1.PodSpec{ + Affinity: &corev1.Affinity{ + NodeAffinity: &corev1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ + NodeSelectorTerms: []corev1.NodeSelectorTerm{ + { + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "region", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"us-east-1"}, + }, + }, + MatchFields: []corev1.NodeSelectorRequirement{ + { + Key: "metadata.name", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"node-a"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + expectTerms: []corev1.NodeSelectorTerm{ + { + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "region", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"us-east-1"}, + }, + { + Key: "fluid.io/fuse", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"true"}, + }, + }, + MatchFields: []corev1.NodeSelectorRequirement{ + { + Key: "metadata.name", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"node-a"}, + }, + { + Key: "metadata.name", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"node-b"}, + }, + }, + }, + }, }, } for k, item := range testCases { InjectNodeSelectorTerms(item.nodeSelectorTermList, item.pod) - if k == "test empty nodeSelectorTermList " { - if len(item.pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms) != - item.expectLen { - t.Errorf("%s InjectNodeSelectorTerms failure, want:%v, got:%v", k, item.expectLen, item.pod.Spec.Affinity.NodeAffinity) + if item.pod.Spec.Affinity == nil || + item.pod.Spec.Affinity.NodeAffinity == nil || + item.pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution == nil { + if len(item.expectTerms) != 0 { + t.Errorf("%s InjectNodeSelectorTerms failure, want:%v, got:nil", k, item.expectTerms) } - } else { - if len(item.pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[0].MatchExpressions) != - item.expectLen { - t.Errorf("%s InjectNodeSelectorTerms failure, want:%v, got:%v", k, item.expectLen, item.pod.Spec.Affinity.NodeAffinity) + continue + } + + gotTerms := item.pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms + if len(gotTerms) != len(item.expectTerms) { + t.Errorf("%s InjectNodeSelectorTerms failure, want:%v, got:%v", k, item.expectTerms, gotTerms) + continue + } + + for i := range gotTerms { + if len(gotTerms[i].MatchExpressions) != len(item.expectTerms[i].MatchExpressions) { + t.Errorf("%s InjectNodeSelectorTerms failure, want:%v, got:%v", k, item.expectTerms, gotTerms) + break + } + + if len(gotTerms[i].MatchFields) != len(item.expectTerms[i].MatchFields) { + t.Errorf("%s InjectNodeSelectorTerms failure, want:%v, got:%v", k, item.expectTerms, gotTerms) + break + } + + for j := range gotTerms[i].MatchExpressions { + if !reflect.DeepEqual(gotTerms[i].MatchExpressions[j], item.expectTerms[i].MatchExpressions[j]) { + t.Errorf("%s InjectNodeSelectorTerms failure, want:%v, got:%v", k, item.expectTerms, gotTerms) + break + } + } + + for j := range gotTerms[i].MatchFields { + if !reflect.DeepEqual(gotTerms[i].MatchFields[j], item.expectTerms[i].MatchFields[j]) { + t.Errorf("%s InjectNodeSelectorTerms failure, want:%v, got:%v", k, item.expectTerms, gotTerms) + break + } } } } diff --git a/pkg/webhook/plugins/nodeaffinitywithcache/node_affinity_with_cache_test.go b/pkg/webhook/plugins/nodeaffinitywithcache/node_affinity_with_cache_test.go index 9ea704c411d..73331221f78 100644 --- a/pkg/webhook/plugins/nodeaffinitywithcache/node_affinity_with_cache_test.go +++ b/pkg/webhook/plugins/nodeaffinitywithcache/node_affinity_with_cache_test.go @@ -295,6 +295,113 @@ required: Expect(pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms).To(HaveLen(1)) }) + It("should combine existing required node affinity branches with injected cache locality terms", func() { + plugin, err := NewPlugin(client, customizedTieredLocality) + Expect(err).NotTo(HaveOccurred()) + + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + Labels: map[string]string{ + "fluid.io/dataset." + alluxioRuntime.Name + ".sched": "required", + }, + }, + Spec: corev1.PodSpec{ + Affinity: &corev1.Affinity{ + NodeAffinity: &corev1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ + NodeSelectorTerms: []corev1.NodeSelectorTerm{ + { + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "app.kubernetes.io/zone", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"zone-app-a"}, + }, + }, + }, + { + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "kubernetes.io/hostname", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"node-a"}, + }, + }, + }, + }, + }, + }, + }, + }, + } + + _, err = plugin.Mutate(pod, map[string]base.RuntimeInfoInterface{ + alluxioRuntime.Name: runtimeInfo, + }) + Expect(err).NotTo(HaveOccurred()) + + terms := pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms + Expect(terms).To(HaveLen(2)) + + Expect(terms[0].MatchExpressions).To(ContainElements( + corev1.NodeSelectorRequirement{Key: "app.kubernetes.io/zone", Operator: corev1.NodeSelectorOpIn, Values: []string{"zone-app-a"}}, + corev1.NodeSelectorRequirement{Key: runtimeInfo.GetCommonLabelName(), Operator: corev1.NodeSelectorOpIn, Values: []string{"true"}}, + )) + Expect(terms[1].MatchExpressions).To(ContainElements( + corev1.NodeSelectorRequirement{Key: "kubernetes.io/hostname", Operator: corev1.NodeSelectorOpIn, Values: []string{"node-a"}}, + corev1.NodeSelectorRequirement{Key: runtimeInfo.GetCommonLabelName(), Operator: corev1.NodeSelectorOpIn, Values: []string{"true"}}, + )) + }) + + It("should ignore empty existing required node affinity branches when injecting cache locality", func() { + plugin, err := NewPlugin(client, customizedTieredLocality) + Expect(err).NotTo(HaveOccurred()) + + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + Labels: map[string]string{ + "fluid.io/dataset." + alluxioRuntime.Name + ".sched": "required", + }, + }, + Spec: corev1.PodSpec{ + Affinity: &corev1.Affinity{ + NodeAffinity: &corev1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ + NodeSelectorTerms: []corev1.NodeSelectorTerm{ + {}, + { + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "kubernetes.io/hostname", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"node-a"}, + }, + }, + }, + }, + }, + }, + }, + }, + } + + _, err = plugin.Mutate(pod, map[string]base.RuntimeInfoInterface{ + alluxioRuntime.Name: runtimeInfo, + }) + Expect(err).NotTo(HaveOccurred()) + + terms := pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms + Expect(terms).To(HaveLen(1)) + Expect(terms[0].MatchExpressions).To(ContainElements( + corev1.NodeSelectorRequirement{Key: "kubernetes.io/hostname", Operator: corev1.NodeSelectorOpIn, Values: []string{"node-a"}}, + corev1.NodeSelectorRequirement{Key: runtimeInfo.GetCommonLabelName(), Operator: corev1.NodeSelectorOpIn, Values: []string{"true"}}, + )) + }) + It("should mutate pod with tiered locality", func() { plugin, err := NewPlugin(client, customizedTieredLocality) Expect(err).NotTo(HaveOccurred()) diff --git a/pkg/webhook/plugins/requirenodewithfuse/require_node_with_fuse_test.go b/pkg/webhook/plugins/requirenodewithfuse/require_node_with_fuse_test.go index 8eaadab9fe5..e02e130cec8 100644 --- a/pkg/webhook/plugins/requirenodewithfuse/require_node_with_fuse_test.go +++ b/pkg/webhook/plugins/requirenodewithfuse/require_node_with_fuse_test.go @@ -93,5 +93,124 @@ var _ = Describe("RequireNodeWithFuse Plugin", func() { _, err = plugin.Mutate(pod, map[string]base.RuntimeInfoInterface{"pvcName": nil}) Expect(err).To(HaveOccurred()) }) + + It("should inject node selector terms when runtimeInfo has fuse node selectors", func() { + plugin, err := NewPlugin(cl, "") + Expect(err).NotTo(HaveOccurred()) + + runtimeInfo, err := base.BuildRuntimeInfo("test", "fluid", "alluxio") + Expect(err).NotTo(HaveOccurred()) + runtimeInfo.SetFuseNodeSelector(map[string]string{"fluid.io/fuse": "true"}) + + shouldStop, err := plugin.Mutate(pod, map[string]base.RuntimeInfoInterface{"pvcName": runtimeInfo}) + Expect(err).NotTo(HaveOccurred()) + Expect(shouldStop).To(BeFalse()) + Expect(pod.Spec.Affinity).NotTo(BeNil()) + Expect(pod.Spec.Affinity.NodeAffinity).NotTo(BeNil()) + terms := pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms + Expect(terms).To(HaveLen(1)) + Expect(terms[0].MatchExpressions).To(HaveLen(1)) + Expect(terms[0].MatchExpressions[0].Key).To(Equal("fluid.io/fuse")) + Expect(terms[0].MatchExpressions[0].Operator).To(Equal(corev1.NodeSelectorOpIn)) + Expect(terms[0].MatchExpressions[0].Values).To(ConsistOf("true")) + }) + + It("should inject fuse match expression into every existing required node affinity branch", func() { + const fuseKey = "fluid.io/fuse" + const termAKey = "zone" + const termBKey = "region" + + pod.Spec.Affinity = &corev1.Affinity{ + NodeAffinity: &corev1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ + NodeSelectorTerms: []corev1.NodeSelectorTerm{ + { + MatchExpressions: []corev1.NodeSelectorRequirement{ + {Key: termAKey, Operator: corev1.NodeSelectorOpIn, Values: []string{"us-east-1a"}}, + }, + }, + { + MatchExpressions: []corev1.NodeSelectorRequirement{ + {Key: termBKey, Operator: corev1.NodeSelectorOpIn, Values: []string{"us-east-1"}}, + }, + }, + }, + }, + }, + } + + plugin, err := NewPlugin(cl, "") + Expect(err).NotTo(HaveOccurred()) + + runtimeInfo, err := base.BuildRuntimeInfo("test", "fluid", "alluxio") + Expect(err).NotTo(HaveOccurred()) + runtimeInfo.SetFuseNodeSelector(map[string]string{fuseKey: "true"}) + + shouldStop, err := plugin.Mutate(pod, map[string]base.RuntimeInfoInterface{"pvcName": runtimeInfo}) + Expect(err).NotTo(HaveOccurred()) + Expect(shouldStop).To(BeFalse()) + + terms := pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms + Expect(terms).To(HaveLen(2)) + Expect(terms[0].MatchExpressions).To(HaveLen(2)) + Expect(terms[0].MatchExpressions).To(ContainElement(corev1.NodeSelectorRequirement{ + Key: termAKey, + Operator: corev1.NodeSelectorOpIn, + Values: []string{"us-east-1a"}, + })) + Expect(terms[0].MatchExpressions).To(ContainElement(corev1.NodeSelectorRequirement{ + Key: fuseKey, + Operator: corev1.NodeSelectorOpIn, + Values: []string{"true"}, + })) + Expect(terms[1].MatchExpressions).To(HaveLen(2)) + Expect(terms[1].MatchExpressions).To(ContainElement(corev1.NodeSelectorRequirement{ + Key: termBKey, + Operator: corev1.NodeSelectorOpIn, + Values: []string{"us-east-1"}, + })) + Expect(terms[1].MatchExpressions).To(ContainElement(corev1.NodeSelectorRequirement{ + Key: fuseKey, + Operator: corev1.NodeSelectorOpIn, + Values: []string{"true"}, + })) + }) + + It("should ignore empty existing required node affinity branches when injecting fuse requirements", func() { + const fuseKey = "fluid.io/fuse" + + pod.Spec.Affinity = &corev1.Affinity{ + NodeAffinity: &corev1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ + NodeSelectorTerms: []corev1.NodeSelectorTerm{ + {}, + { + MatchExpressions: []corev1.NodeSelectorRequirement{ + {Key: "region", Operator: corev1.NodeSelectorOpIn, Values: []string{"us-east-1"}}, + }, + }, + }, + }, + }, + } + + plugin, err := NewPlugin(cl, "") + Expect(err).NotTo(HaveOccurred()) + + runtimeInfo, err := base.BuildRuntimeInfo("test", "fluid", "alluxio") + Expect(err).NotTo(HaveOccurred()) + runtimeInfo.SetFuseNodeSelector(map[string]string{fuseKey: "true"}) + + shouldStop, err := plugin.Mutate(pod, map[string]base.RuntimeInfoInterface{"pvcName": runtimeInfo}) + Expect(err).NotTo(HaveOccurred()) + Expect(shouldStop).To(BeFalse()) + + terms := pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms + Expect(terms).To(HaveLen(1)) + Expect(terms[0].MatchExpressions).To(ContainElements( + corev1.NodeSelectorRequirement{Key: "region", Operator: corev1.NodeSelectorOpIn, Values: []string{"us-east-1"}}, + corev1.NodeSelectorRequirement{Key: fuseKey, Operator: corev1.NodeSelectorOpIn, Values: []string{"true"}}, + )) + }) }) })