From 01820ff7c27584d303429946c845265e8eb54140 Mon Sep 17 00:00:00 2001 From: googs1025 Date: Wed, 5 Mar 2025 09:27:11 +0800 Subject: [PATCH] chore(scheduler): add filter integration tests for missing part plugins: NodeAffinity plugin Signed-off-by: googs1025 --- pkg/scheduler/testing/wrappers.go | 8 + .../scheduler/filters/filters_test.go | 211 ++++++++++++++++++ 2 files changed, 219 insertions(+) diff --git a/pkg/scheduler/testing/wrappers.go b/pkg/scheduler/testing/wrappers.go index 258e3dae989..53687810b1a 100644 --- a/pkg/scheduler/testing/wrappers.go +++ b/pkg/scheduler/testing/wrappers.go @@ -340,6 +340,14 @@ func (p *PodWrapper) Resources(resources v1.ResourceRequirements) *PodWrapper { return p } +func (p *PodWrapper) NodeAffinity(nodeAffinity *v1.NodeAffinity) *PodWrapper { + if p.Spec.Affinity == nil { + p.Spec.Affinity = &v1.Affinity{} + } + p.Spec.Affinity.NodeAffinity = nodeAffinity + return p +} + // OwnerReference updates the owning controller of the pod. func (p *PodWrapper) OwnerReference(name string, gvk schema.GroupVersionKind) *PodWrapper { p.OwnerReferences = []metav1.OwnerReference{ diff --git a/test/integration/scheduler/filters/filters_test.go b/test/integration/scheduler/filters/filters_test.go index 5961ec37c52..80289982691 100644 --- a/test/integration/scheduler/filters/filters_test.go +++ b/test/integration/scheduler/filters/filters_test.go @@ -2726,3 +2726,214 @@ func TestNodeResourcesFilter(t *testing.T) { }) } } + +func TestNodeAffinityFilter(t *testing.T) { + pause := imageutils.GetPauseImageName() + tests := []struct { + name string + nodes []*v1.Node + incomingPod *v1.Pod + expectedNode string + }{ + { + name: "Pod fits when node matches label using In operator", + nodes: []*v1.Node{ + st.MakeNode().Name("node-ssd").Label("disktype", "ssd").Obj(), + st.MakeNode().Name("node-hdd").Label("disktype", "hdd").Obj(), + }, + incomingPod: st.MakePod().Name("affinity-fitting-pod"). + NodeAffinity(&v1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "disktype", + Operator: v1.NodeSelectorOpIn, + Values: []string{"ssd"}, + }, + }, + }, + }, + }, + }).Container(pause). + Obj(), + expectedNode: "node-ssd", + }, + { + name: "Pod not fit when node not match label using In operator", + nodes: []*v1.Node{ + st.MakeNode().Name("node-ssd").Label("disktype", "ssd").Obj(), + }, + incomingPod: st.MakePod().Name("affinity-non-fitting-pod"). + NodeAffinity(&v1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "disktype", + Operator: v1.NodeSelectorOpIn, + Values: []string{"hdd"}, + }, + }, + }, + }, + }, + }). + Container(pause). + Obj(), + expectedNode: "", + }, + { + name: "Pod not fit when node matches label using NotIn operator", + nodes: []*v1.Node{ + st.MakeNode().Name("node-hdd-1").Label("disktype", "hdd").Obj(), + st.MakeNode().Name("node-hdd-2").Label("disktype", "hdd").Obj(), + }, + incomingPod: st.MakePod().Name("pod-avoid-hdd"). + NodeAffinity(&v1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "disktype", + Operator: v1.NodeSelectorOpNotIn, + Values: []string{"hdd"}, + }, + }, + }, + }, + }, + }).Container(pause). + Obj(), + expectedNode: "", + }, + { + name: "Pod fits when node matches label using Exists operator", + nodes: []*v1.Node{ + st.MakeNode().Name("node-with-disktype").Label("disktype", "ssd").Obj(), + st.MakeNode().Name("node-without-disktype").Obj(), + }, + incomingPod: st.MakePod().Name("pod-requires-any-disktype"). + NodeAffinity(&v1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "disktype", + Operator: v1.NodeSelectorOpExists, + }, + }, + }, + }, + }, + }).Container(pause). + Obj(), + expectedNode: "node-with-disktype", + }, + { + name: "Pod not fit when node matches label using DoesNotExist operator", + nodes: []*v1.Node{ + st.MakeNode().Name("node-with-disktype-1").Label("disktype", "ssd").Obj(), + st.MakeNode().Name("node-with-disktype-2").Label("disktype", "ssd").Obj(), + }, + incomingPod: st.MakePod().Name("pod-avoids-disktype"). + NodeAffinity(&v1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "disktype", + Operator: v1.NodeSelectorOpDoesNotExist, + }, + }, + }, + }, + }, + }).Container(pause). + Obj(), + expectedNode: "", + }, + { + name: "Pod fits both label and metadata.name using MatchExpressions and MatchFields", + nodes: []*v1.Node{ + st.MakeNode().Name("affinity-matching-node-1").Label("disktype", "ssd").Obj(), + st.MakeNode().Name("affinity-matching-node-2").Obj(), + }, + incomingPod: st.MakePod().Name("affinity-fitting-pod"). + NodeAffinity(&v1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "disktype", + Operator: v1.NodeSelectorOpIn, + Values: []string{"ssd"}, + }, + }, + MatchFields: []v1.NodeSelectorRequirement{ + { + Key: "metadata.name", + Operator: v1.NodeSelectorOpIn, + Values: []string{"affinity-matching-node-1"}, + }, + }, + }, + }, + }, + }).Container(pause). + Obj(), + expectedNode: "affinity-matching-node-1", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testCtx := initTest(t, "nodeaffinity-filter") + cs := testCtx.ClientSet + ns := testCtx.NS.Name + + // Create node. + for _, node := range tt.nodes { + if _, err := cs.CoreV1().Nodes().Create(testCtx.Ctx, node, metav1.CreateOptions{}); err != nil { + t.Fatalf("Failed to create node: %v", err) + } + } + + // Set namespace for pods and create them. + tt.incomingPod.SetNamespace(ns) + defer testutils.CleanupPods(testCtx.Ctx, cs, t, []*v1.Pod{tt.incomingPod}) + + // Attempt to schedule the incoming pod. + testPod, err := cs.CoreV1().Pods(tt.incomingPod.Namespace).Create(testCtx.Ctx, tt.incomingPod, metav1.CreateOptions{}) + if err != nil { + t.Fatalf("Failed to create pod during test: %v", err) + } + + if tt.expectedNode != "" { + err = wait.PollUntilContextTimeout(testCtx.Ctx, pollInterval, wait.ForeverTestTimeout, false, + podScheduled(cs, testPod.Namespace, testPod.Name)) + if err != nil { + t.Errorf("Test Failed: Expected pod %s/%s to be scheduled but got error: %v", testPod.Namespace, testPod.Name, err) + } + pod, err := cs.CoreV1().Pods(testPod.Namespace).Get(testCtx.Ctx, testPod.Name, metav1.GetOptions{}) + if err != nil { + t.Fatalf("Failed to get pod after scheduling: %v", err) + } + if pod.Spec.NodeName != tt.expectedNode { + t.Errorf("Expected pod to be scheduled to %q, but was scheduled to %q", tt.expectedNode, pod.Spec.NodeName) + } + } else { + err = wait.PollUntilContextTimeout(testCtx.Ctx, pollInterval, wait.ForeverTestTimeout, false, + podUnschedulable(cs, testPod.Namespace, testPod.Name)) + if err != nil { + t.Errorf("Test Failed: Expected pod %s/%s to be unschedulable but got error: %v", testPod.Namespace, testPod.Name, err) + } + } + }) + } +}