1
16
17 package scoring
18
19 import (
20 "context"
21 "fmt"
22 "strings"
23 "testing"
24 "time"
25
26 v1 "k8s.io/api/core/v1"
27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28 "k8s.io/apimachinery/pkg/runtime"
29 "k8s.io/apimachinery/pkg/util/intstr"
30 "k8s.io/apimachinery/pkg/util/wait"
31 utilfeature "k8s.io/apiserver/pkg/util/feature"
32 featuregatetesting "k8s.io/component-base/featuregate/testing"
33 configv1 "k8s.io/kube-scheduler/config/v1"
34 "k8s.io/kubernetes/pkg/features"
35 "k8s.io/kubernetes/pkg/scheduler"
36 configtesting "k8s.io/kubernetes/pkg/scheduler/apis/config/testing"
37 "k8s.io/kubernetes/pkg/scheduler/framework/plugins/imagelocality"
38 "k8s.io/kubernetes/pkg/scheduler/framework/plugins/interpodaffinity"
39 "k8s.io/kubernetes/pkg/scheduler/framework/plugins/nodeaffinity"
40 "k8s.io/kubernetes/pkg/scheduler/framework/plugins/noderesources"
41 "k8s.io/kubernetes/pkg/scheduler/framework/plugins/podtopologyspread"
42 st "k8s.io/kubernetes/pkg/scheduler/testing"
43 testutils "k8s.io/kubernetes/test/integration/util"
44 imageutils "k8s.io/kubernetes/test/utils/image"
45 "k8s.io/utils/pointer"
46 )
47
48
49 var (
50 runPausePod = testutils.RunPausePod
51 createAndWaitForNodesInCache = testutils.CreateAndWaitForNodesInCache
52 createNode = testutils.CreateNode
53 createNamespacesWithLabels = testutils.CreateNamespacesWithLabels
54 runPodWithContainers = testutils.RunPodWithContainers
55 initPausePod = testutils.InitPausePod
56 initPodWithContainers = testutils.InitPodWithContainers
57 podScheduledIn = testutils.PodScheduledIn
58 podUnschedulable = testutils.PodUnschedulable
59 )
60
61 var (
62 hardSpread = v1.DoNotSchedule
63 softSpread = v1.ScheduleAnyway
64 ignorePolicy = v1.NodeInclusionPolicyIgnore
65 honorPolicy = v1.NodeInclusionPolicyHonor
66 taints = []v1.Taint{{Key: v1.TaintNodeUnschedulable, Value: "", Effect: v1.TaintEffectPreferNoSchedule}}
67 )
68
69 const (
70 resourceGPU = "example.com/gpu"
71 pollInterval = 100 * time.Millisecond
72 )
73
74
75 func initTestSchedulerForPriorityTest(t *testing.T, preScorePluginName, scorePluginName string) *testutils.TestContext {
76 cc := configv1.KubeSchedulerConfiguration{
77 Profiles: []configv1.KubeSchedulerProfile{{
78 SchedulerName: pointer.String(v1.DefaultSchedulerName),
79 Plugins: &configv1.Plugins{
80 PreScore: configv1.PluginSet{
81 Disabled: []configv1.Plugin{
82 {Name: "*"},
83 },
84 },
85 Score: configv1.PluginSet{
86 Enabled: []configv1.Plugin{
87 {Name: scorePluginName, Weight: pointer.Int32(1)},
88 },
89 Disabled: []configv1.Plugin{
90 {Name: "*"},
91 },
92 },
93 },
94 }},
95 }
96 if preScorePluginName != "" {
97 cc.Profiles[0].Plugins.PreScore.Enabled = append(cc.Profiles[0].Plugins.PreScore.Enabled, configv1.Plugin{Name: preScorePluginName})
98 }
99 cfg := configtesting.V1ToInternalWithDefaults(t, cc)
100 testCtx := testutils.InitTestSchedulerWithOptions(
101 t,
102 testutils.InitTestAPIServer(t, strings.ToLower(scorePluginName), nil),
103 0,
104 scheduler.WithProfiles(cfg.Profiles...),
105 )
106 testutils.SyncSchedulerInformerFactory(testCtx)
107 go testCtx.Scheduler.Run(testCtx.Ctx)
108 return testCtx
109 }
110
111 func initTestSchedulerForNodeResourcesTest(t *testing.T) *testutils.TestContext {
112 cfg := configtesting.V1ToInternalWithDefaults(t, configv1.KubeSchedulerConfiguration{
113 Profiles: []configv1.KubeSchedulerProfile{
114 {
115 SchedulerName: pointer.String(v1.DefaultSchedulerName),
116 },
117 {
118 SchedulerName: pointer.String("gpu-binpacking-scheduler"),
119 PluginConfig: []configv1.PluginConfig{
120 {
121 Name: noderesources.Name,
122 Args: runtime.RawExtension{Object: &configv1.NodeResourcesFitArgs{
123 ScoringStrategy: &configv1.ScoringStrategy{
124 Type: configv1.MostAllocated,
125 Resources: []configv1.ResourceSpec{
126 {Name: string(v1.ResourceCPU), Weight: 1},
127 {Name: string(v1.ResourceMemory), Weight: 1},
128 {Name: resourceGPU, Weight: 2}},
129 },
130 }},
131 },
132 },
133 },
134 },
135 })
136 testCtx := testutils.InitTestSchedulerWithOptions(
137 t,
138 testutils.InitTestAPIServer(t, strings.ToLower(noderesources.Name), nil),
139 0,
140 scheduler.WithProfiles(cfg.Profiles...),
141 )
142 testutils.SyncSchedulerInformerFactory(testCtx)
143 go testCtx.Scheduler.Run(testCtx.Ctx)
144 return testCtx
145 }
146
147
148
149 func TestNodeResourcesScoring(t *testing.T) {
150 testCtx := initTestSchedulerForNodeResourcesTest(t)
151
152 _, err := createAndWaitForNodesInCache(testCtx, "testnode", st.MakeNode().Capacity(
153 map[v1.ResourceName]string{
154 v1.ResourceCPU: "8",
155 v1.ResourceMemory: "16G",
156 resourceGPU: "4",
157 }), 2)
158 if err != nil {
159 t.Fatal(err)
160 }
161 cpuBoundPod1, err := runPausePod(testCtx.ClientSet, st.MakePod().Namespace(testCtx.NS.Name).Name("cpubound1").Res(
162 map[v1.ResourceName]string{
163 v1.ResourceCPU: "2",
164 v1.ResourceMemory: "4G",
165 resourceGPU: "1",
166 },
167 ).Obj())
168 if err != nil {
169 t.Fatal(err)
170 }
171 gpuBoundPod1, err := runPausePod(testCtx.ClientSet, st.MakePod().Namespace(testCtx.NS.Name).Name("gpubound1").Res(
172 map[v1.ResourceName]string{
173 v1.ResourceCPU: "1",
174 v1.ResourceMemory: "2G",
175 resourceGPU: "2",
176 },
177 ).Obj())
178 if err != nil {
179 t.Fatal(err)
180 }
181 if cpuBoundPod1.Spec.NodeName == "" || gpuBoundPod1.Spec.NodeName == "" {
182 t.Fatalf("pods should have nodeName assigned, got %q and %q",
183 cpuBoundPod1.Spec.NodeName, gpuBoundPod1.Spec.NodeName)
184 }
185
186
187
188 if cpuBoundPod1.Spec.NodeName == gpuBoundPod1.Spec.NodeName {
189 t.Fatalf("pods should have landed on different nodes, both scheduled on %q",
190 cpuBoundPod1.Spec.NodeName)
191 }
192
193
194
195 cpuBoundPod2, err := runPausePod(testCtx.ClientSet, st.MakePod().Namespace(testCtx.NS.Name).Name("cpubound2").SchedulerName("gpu-binpacking-scheduler").Res(
196 map[v1.ResourceName]string{
197 v1.ResourceCPU: "2",
198 v1.ResourceMemory: "4G",
199 resourceGPU: "1",
200 },
201 ).Obj())
202 if err != nil {
203 t.Fatal(err)
204 }
205 if cpuBoundPod2.Spec.NodeName != gpuBoundPod1.Spec.NodeName {
206 t.Errorf("pods should have landed on the same node")
207 }
208 }
209
210
211
212 func TestNodeAffinityScoring(t *testing.T) {
213 testCtx := initTestSchedulerForPriorityTest(t, nodeaffinity.Name, nodeaffinity.Name)
214
215 _, err := createAndWaitForNodesInCache(testCtx, "testnode", st.MakeNode(), 4)
216 if err != nil {
217 t.Fatal(err)
218 }
219
220 labelKey := "kubernetes.io/node-topologyKey"
221 labelValue := "topologyvalue"
222 labeledNode, err := createNode(testCtx.ClientSet, st.MakeNode().Name("testnode-4").Label(labelKey, labelValue).Obj())
223 if err != nil {
224 t.Fatalf("Cannot create labeled node: %v", err)
225 }
226
227
228 podName := "pod-with-node-affinity"
229 pod, err := runPausePod(testCtx.ClientSet, initPausePod(&testutils.PausePodConfig{
230 Name: podName,
231 Namespace: testCtx.NS.Name,
232 Affinity: &v1.Affinity{
233 NodeAffinity: &v1.NodeAffinity{
234 PreferredDuringSchedulingIgnoredDuringExecution: []v1.PreferredSchedulingTerm{
235 {
236 Preference: v1.NodeSelectorTerm{
237 MatchExpressions: []v1.NodeSelectorRequirement{
238 {
239 Key: labelKey,
240 Operator: v1.NodeSelectorOpIn,
241 Values: []string{labelValue},
242 },
243 },
244 },
245 Weight: 20,
246 },
247 },
248 },
249 },
250 }))
251 if err != nil {
252 t.Fatalf("Error running pause pod: %v", err)
253 }
254 if pod.Spec.NodeName != labeledNode.Name {
255 t.Errorf("Pod %v got scheduled on an unexpected node: %v. Expected node: %v.", podName, pod.Spec.NodeName, labeledNode.Name)
256 } else {
257 t.Logf("Pod %v got successfully scheduled on node %v.", podName, pod.Spec.NodeName)
258 }
259 }
260
261
262
263 func TestPodAffinityScoring(t *testing.T) {
264 labelKey := "service"
265 labelValue := "S1"
266 topologyKey := "node-topologykey"
267 topologyValues := []string{}
268 for i := 0; i < 5; i++ {
269 topologyValues = append(topologyValues, fmt.Sprintf("topologyvalue%d", i))
270 }
271 tests := []struct {
272 name string
273 pod *testutils.PausePodConfig
274 existingPods []*testutils.PausePodConfig
275 nodes []*v1.Node
276
277 expectedNodeName []string
278 enableMatchLabelKeysInAffinity bool
279 }{
280 {
281 name: "pod affinity",
282 pod: &testutils.PausePodConfig{
283 Name: "pod1",
284 Namespace: "ns1",
285 Affinity: &v1.Affinity{
286 PodAffinity: &v1.PodAffinity{
287 PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{
288 {
289 PodAffinityTerm: v1.PodAffinityTerm{
290 LabelSelector: &metav1.LabelSelector{
291 MatchExpressions: []metav1.LabelSelectorRequirement{
292 {
293 Key: labelKey,
294 Operator: metav1.LabelSelectorOpIn,
295 Values: []string{labelValue, "S3"},
296 },
297 },
298 },
299 TopologyKey: topologyKey,
300 },
301 Weight: 50,
302 },
303 },
304 },
305 },
306 },
307 existingPods: []*testutils.PausePodConfig{
308 {
309 Name: "attractor-pod",
310 Namespace: "ns1",
311 Labels: map[string]string{labelKey: labelValue},
312 NodeName: "node1",
313 },
314 },
315 nodes: []*v1.Node{
316 st.MakeNode().Name("node1").Label(topologyKey, topologyValues[0]).Obj(),
317 st.MakeNode().Name("node2").Label(topologyKey, topologyValues[1]).Obj(),
318 st.MakeNode().Name("node3").Label(topologyKey, topologyValues[2]).Obj(),
319 st.MakeNode().Name("node4").Label(topologyKey, topologyValues[3]).Obj(),
320 st.MakeNode().Name("node5").Label(topologyKey, topologyValues[4]).Obj(),
321 st.MakeNode().Name("node6").Label(topologyKey, topologyValues[0]).Obj(),
322 st.MakeNode().Name("node7").Label(topologyKey, topologyValues[1]).Obj(),
323 st.MakeNode().Name("node8").Label(topologyKey, topologyValues[2]).Obj(),
324 st.MakeNode().Name("node9").Label(topologyKey, topologyValues[3]).Obj(),
325 st.MakeNode().Name("node10").Label(topologyKey, topologyValues[4]).Obj(),
326 st.MakeNode().Name("other-node1").Obj(),
327 st.MakeNode().Name("other-node2").Obj(),
328 },
329 expectedNodeName: []string{"node1", "node6"},
330 },
331 {
332 name: "pod affinity with namespace selector",
333 pod: &testutils.PausePodConfig{
334 Name: "pod1",
335 Namespace: "ns2",
336 Affinity: &v1.Affinity{
337 PodAffinity: &v1.PodAffinity{
338 PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{
339 {
340 PodAffinityTerm: v1.PodAffinityTerm{
341 NamespaceSelector: &metav1.LabelSelector{},
342 LabelSelector: &metav1.LabelSelector{
343 MatchExpressions: []metav1.LabelSelectorRequirement{
344 {
345 Key: labelKey,
346 Operator: metav1.LabelSelectorOpIn,
347 Values: []string{labelValue, "S3"},
348 },
349 },
350 },
351 TopologyKey: topologyKey,
352 },
353 Weight: 50,
354 },
355 },
356 },
357 },
358 },
359 existingPods: []*testutils.PausePodConfig{
360 {
361 Name: "attractor-pod",
362 Namespace: "ns1",
363 Labels: map[string]string{labelKey: labelValue},
364 NodeName: "node1",
365 },
366 },
367 nodes: []*v1.Node{
368 st.MakeNode().Name("node1").Label(topologyKey, topologyValues[0]).Obj(),
369 st.MakeNode().Name("node2").Label(topologyKey, topologyValues[1]).Obj(),
370 st.MakeNode().Name("node3").Label(topologyKey, topologyValues[2]).Obj(),
371 st.MakeNode().Name("node4").Label(topologyKey, topologyValues[3]).Obj(),
372 st.MakeNode().Name("node5").Label(topologyKey, topologyValues[4]).Obj(),
373 st.MakeNode().Name("node6").Label(topologyKey, topologyValues[0]).Obj(),
374 st.MakeNode().Name("node7").Label(topologyKey, topologyValues[1]).Obj(),
375 st.MakeNode().Name("node8").Label(topologyKey, topologyValues[2]).Obj(),
376 st.MakeNode().Name("node9").Label(topologyKey, topologyValues[3]).Obj(),
377 st.MakeNode().Name("node10").Label(topologyKey, topologyValues[4]).Obj(),
378 st.MakeNode().Name("other-node1").Obj(),
379 st.MakeNode().Name("other-node2").Obj(),
380 },
381 expectedNodeName: []string{"node1", "node6"},
382 },
383 {
384 name: "anti affinity: matchLabelKeys is merged into LabelSelector with In operator (feature flag: enabled)",
385 pod: &testutils.PausePodConfig{
386 Name: "incoming",
387 Namespace: "ns1",
388 Labels: map[string]string{"foo": "", "bar": "a"},
389 Affinity: &v1.Affinity{
390 PodAntiAffinity: &v1.PodAntiAffinity{
391 PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{
392 {
393 PodAffinityTerm: v1.PodAffinityTerm{
394 TopologyKey: topologyKey,
395 LabelSelector: &metav1.LabelSelector{
396 MatchExpressions: []metav1.LabelSelectorRequirement{
397 {
398 Key: "foo",
399 Operator: metav1.LabelSelectorOpExists,
400 },
401 },
402 },
403 MatchLabelKeys: []string{"bar"},
404 },
405 Weight: 50,
406 },
407 },
408 },
409 },
410 },
411 existingPods: []*testutils.PausePodConfig{
412
413
414 {
415 NodeName: "node1",
416 Name: "pod1",
417 Namespace: "ns1",
418 Labels: map[string]string{"foo": "", "bar": "fuga"},
419 },
420
421 {
422 NodeName: "node2",
423 Name: "pod2",
424 Namespace: "ns1",
425 Labels: map[string]string{"foo": "", "bar": "a"},
426 },
427 },
428 nodes: []*v1.Node{
429 st.MakeNode().Name("node1").Label(topologyKey, topologyValues[0]).Obj(),
430 st.MakeNode().Name("node2").Label(topologyKey, topologyValues[1]).Obj(),
431 },
432 expectedNodeName: []string{"node1"},
433 enableMatchLabelKeysInAffinity: true,
434 },
435 {
436 name: "anti affinity: mismatchLabelKeys is merged into LabelSelector with NotIn operator (feature flag: enabled)",
437 pod: &testutils.PausePodConfig{
438 Name: "incoming",
439 Namespace: "ns1",
440 Labels: map[string]string{"foo": "", "bar": "a"},
441 Affinity: &v1.Affinity{
442 PodAntiAffinity: &v1.PodAntiAffinity{
443 PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{
444 {
445 PodAffinityTerm: v1.PodAffinityTerm{
446 TopologyKey: topologyKey,
447 LabelSelector: &metav1.LabelSelector{
448 MatchExpressions: []metav1.LabelSelectorRequirement{
449 {
450 Key: "foo",
451 Operator: metav1.LabelSelectorOpExists,
452 },
453 },
454 },
455 MismatchLabelKeys: []string{"bar"},
456 },
457 Weight: 50,
458 },
459 },
460 },
461 },
462 },
463 existingPods: []*testutils.PausePodConfig{
464
465 {
466 NodeName: "node1",
467 Name: "pod1",
468 Namespace: "ns1",
469 Labels: map[string]string{"foo": "", "bar": "fuga"},
470 },
471
472
473 {
474 NodeName: "node2",
475 Name: "pod2",
476 Namespace: "ns1",
477 Labels: map[string]string{"foo": "", "bar": "a"},
478 },
479 },
480 nodes: []*v1.Node{
481 st.MakeNode().Name("node1").Label(topologyKey, topologyValues[0]).Obj(),
482 st.MakeNode().Name("node2").Label(topologyKey, topologyValues[1]).Obj(),
483 },
484 expectedNodeName: []string{"node2"},
485 enableMatchLabelKeysInAffinity: true,
486 },
487 {
488 name: "affinity: matchLabelKeys is merged into LabelSelector with In operator (feature flag: enabled)",
489 pod: &testutils.PausePodConfig{
490 Affinity: &v1.Affinity{
491 PodAffinity: &v1.PodAffinity{
492 PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{
493 {
494
495 PodAffinityTerm: v1.PodAffinityTerm{
496 TopologyKey: topologyKey,
497 LabelSelector: &metav1.LabelSelector{
498 MatchExpressions: []metav1.LabelSelectorRequirement{
499 {
500 Key: "foo",
501 Operator: metav1.LabelSelectorOpExists,
502 },
503 },
504 },
505 MatchLabelKeys: []string{"bar"},
506 },
507 Weight: 50,
508 },
509 {
510
511
512
513 PodAffinityTerm: v1.PodAffinityTerm{
514 TopologyKey: topologyKey,
515 LabelSelector: &metav1.LabelSelector{
516 MatchExpressions: []metav1.LabelSelectorRequirement{
517 {
518 Key: "bar",
519 Operator: metav1.LabelSelectorOpIn,
520 Values: []string{"hoge"},
521 },
522 },
523 },
524 },
525 Weight: 10,
526 },
527 },
528 },
529 },
530 Name: "incoming",
531 Namespace: "ns1",
532 Labels: map[string]string{"foo": "", "bar": "a"},
533 },
534 existingPods: []*testutils.PausePodConfig{
535 {
536 NodeName: "node1",
537 Name: "pod1",
538 Namespace: "ns1",
539 Labels: map[string]string{"foo": "", "bar": "hoge"},
540 },
541 {
542 NodeName: "node2",
543 Name: "pod2",
544 Namespace: "ns1",
545 Labels: map[string]string{"foo": "", "bar": "hoge"},
546 },
547 {
548 NodeName: "node3",
549 Name: "pod3",
550 Namespace: "ns1",
551 Labels: map[string]string{"foo": "", "bar": "a"},
552 },
553 },
554 enableMatchLabelKeysInAffinity: true,
555 nodes: []*v1.Node{
556 st.MakeNode().Name("node1").Label(topologyKey, topologyValues[0]).Obj(),
557 st.MakeNode().Name("node2").Label(topologyKey, topologyValues[1]).Obj(),
558 st.MakeNode().Name("node3").Label(topologyKey, topologyValues[2]).Obj(),
559 st.MakeNode().Name("node4").Label(topologyKey, topologyValues[0]).Obj(),
560 st.MakeNode().Name("node5").Label(topologyKey, topologyValues[1]).Obj(),
561 st.MakeNode().Name("node6").Label(topologyKey, topologyValues[2]).Obj(),
562 },
563 expectedNodeName: []string{"node3", "node6"},
564 },
565 {
566 name: "affinity: mismatchLabelKeys is merged into LabelSelector with NotIn operator (feature flag: enabled)",
567 pod: &testutils.PausePodConfig{
568 Affinity: &v1.Affinity{
569 PodAffinity: &v1.PodAffinity{
570 PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{
571 {
572
573 PodAffinityTerm: v1.PodAffinityTerm{
574 TopologyKey: topologyKey,
575 LabelSelector: &metav1.LabelSelector{
576 MatchExpressions: []metav1.LabelSelectorRequirement{
577 {
578 Key: "foo",
579 Operator: metav1.LabelSelectorOpExists,
580 },
581 },
582 },
583 MismatchLabelKeys: []string{"bar"},
584 },
585 Weight: 50,
586 },
587 {
588
589
590
591 PodAffinityTerm: v1.PodAffinityTerm{
592 TopologyKey: topologyKey,
593 LabelSelector: &metav1.LabelSelector{
594 MatchExpressions: []metav1.LabelSelectorRequirement{
595 {
596 Key: "bar",
597 Operator: metav1.LabelSelectorOpIn,
598 Values: []string{"hoge"},
599 },
600 },
601 },
602 },
603 Weight: 10,
604 },
605 },
606 },
607 },
608 Name: "incoming",
609 Namespace: "ns1",
610 Labels: map[string]string{"foo": "", "bar": "a"},
611 },
612 existingPods: []*testutils.PausePodConfig{
613 {
614 NodeName: "node1",
615 Name: "pod1",
616 Namespace: "ns1",
617 Labels: map[string]string{"foo": "", "bar": "a"},
618 },
619 {
620 NodeName: "node2",
621 Name: "pod2",
622 Namespace: "ns1",
623 Labels: map[string]string{"foo": "", "bar": "a"},
624 },
625 {
626 NodeName: "node3",
627 Name: "pod3",
628 Namespace: "ns1",
629 Labels: map[string]string{"foo": "", "bar": "hoge"},
630 },
631 },
632 enableMatchLabelKeysInAffinity: true,
633 nodes: []*v1.Node{
634 st.MakeNode().Name("node1").Label(topologyKey, topologyValues[0]).Obj(),
635 st.MakeNode().Name("node2").Label(topologyKey, topologyValues[1]).Obj(),
636 st.MakeNode().Name("node3").Label(topologyKey, topologyValues[2]).Obj(),
637 st.MakeNode().Name("node4").Label(topologyKey, topologyValues[0]).Obj(),
638 st.MakeNode().Name("node5").Label(topologyKey, topologyValues[1]).Obj(),
639 st.MakeNode().Name("node6").Label(topologyKey, topologyValues[2]).Obj(),
640 },
641 expectedNodeName: []string{"node3", "node6"},
642 },
643 }
644
645 for _, tt := range tests {
646 t.Run(tt.name, func(t *testing.T) {
647 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MatchLabelKeysInPodAffinity, tt.enableMatchLabelKeysInAffinity)()
648
649 testCtx := initTestSchedulerForPriorityTest(t, interpodaffinity.Name, interpodaffinity.Name)
650 if err := createNamespacesWithLabels(testCtx.ClientSet, []string{"ns1", "ns2"}, map[string]string{"team": "team1"}); err != nil {
651 t.Fatal(err)
652 }
653
654 for _, n := range tt.nodes {
655 if _, err := createNode(testCtx.ClientSet, n); err != nil {
656 t.Fatalf("failed to create node: %v", err)
657 }
658 }
659
660 for _, p := range tt.existingPods {
661 if _, err := runPausePod(testCtx.ClientSet, initPausePod(p)); err != nil {
662 t.Fatalf("failed to create existing pod: %v", err)
663 }
664 }
665
666 pod, err := runPausePod(testCtx.ClientSet, initPausePod(tt.pod))
667 if err != nil {
668 t.Fatalf("Error running pause pod: %v", err)
669 }
670
671 err = wait.PollUntilContextTimeout(testCtx.Ctx, pollInterval, wait.ForeverTestTimeout, false, podScheduledIn(testCtx.ClientSet, pod.Namespace, pod.Name, tt.expectedNodeName))
672 if err != nil {
673 t.Errorf("Error while trying to wait for a pod to be scheduled: %v", err)
674 }
675 })
676 }
677 }
678
679
680
681 func TestImageLocalityScoring(t *testing.T) {
682 testCtx := initTestSchedulerForPriorityTest(t, "", imagelocality.Name)
683
684
685
686
687 imageName := "fake-large-image:v1"
688 nodeWithLargeImage, err := createNode(
689 testCtx.ClientSet,
690 st.MakeNode().Name("testnode-large-image").Images(map[string]int64{imageName: 3000 * 1024 * 1024}).Obj(),
691 )
692 if err != nil {
693 t.Fatalf("cannot create node with a large image: %v", err)
694 }
695
696
697 _, err = createAndWaitForNodesInCache(testCtx, "testnode", st.MakeNode(), 10)
698 if err != nil {
699 t.Fatal(err)
700 }
701
702
703 podName := "pod-using-large-image"
704 pod, err := runPodWithContainers(testCtx.ClientSet, initPodWithContainers(testCtx.ClientSet, &testutils.PodWithContainersConfig{
705 Name: podName,
706 Namespace: testCtx.NS.Name,
707 Containers: makeContainersWithImages([]string{imageName}),
708 }))
709 if err != nil {
710 t.Fatalf("error running pod with images: %v", err)
711 }
712 if pod.Spec.NodeName != nodeWithLargeImage.Name {
713 t.Errorf("pod %v got scheduled on an unexpected node: %v. Expected node: %v.", podName, pod.Spec.NodeName, nodeWithLargeImage.Name)
714 } else {
715 t.Logf("pod %v got successfully scheduled on node %v.", podName, pod.Spec.NodeName)
716 }
717 }
718
719
720
721 func makeContainersWithImages(images []string) []v1.Container {
722 var containers []v1.Container
723 usedImages := make(map[string]struct{})
724
725 for _, image := range images {
726 if _, ok := usedImages[image]; !ok {
727 containers = append(containers, v1.Container{
728 Name: strings.Replace(image, ":", "-", -1) + "-container",
729 Image: image,
730 })
731 usedImages[image] = struct{}{}
732 }
733 }
734 return containers
735 }
736
737
738 func TestPodTopologySpreadScoring(t *testing.T) {
739 pause := imageutils.GetPauseImageName()
740 taint := v1.Taint{
741 Key: "k1",
742 Value: "v1",
743 Effect: v1.TaintEffectNoSchedule,
744 }
745
746
747 defaultNodes := []*v1.Node{
748 st.MakeNode().Name("node-0").Label("node", "node-0").Label("zone", "zone-0").Taints([]v1.Taint{taint}).Obj(),
749 st.MakeNode().Name("node-1").Label("node", "node-1").Label("zone", "zone-0").Obj(),
750 st.MakeNode().Name("node-2").Label("node", "node-2").Label("zone", "zone-1").Obj(),
751 st.MakeNode().Name("node-3").Label("node", "node-3").Label("zone", "zone-1").Obj(),
752 }
753
754 tests := []struct {
755 name string
756 incomingPod *v1.Pod
757 existingPods []*v1.Pod
758 fits bool
759 nodes []*v1.Node
760 want []string
761 enableNodeInclusionPolicy bool
762 enableMatchLabelKeys bool
763 }{
764
765
766 {
767 name: "place pod on a ~0~/1/2/3 cluster with MaxSkew=1, node-1 is the preferred fit",
768 incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).
769 SpreadConstraint(1, "node", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
770 Obj(),
771 existingPods: []*v1.Pod{
772 st.MakePod().Name("p1").Node("node-1").Label("foo", "").Container(pause).Obj(),
773 st.MakePod().Name("p2a").Node("node-2").Label("foo", "").Container(pause).Obj(),
774 st.MakePod().Name("p2b").Node("node-2").Label("foo", "").Container(pause).Obj(),
775 st.MakePod().Name("p3a").Node("node-3").Label("foo", "").Container(pause).Obj(),
776 st.MakePod().Name("p3b").Node("node-3").Label("foo", "").Container(pause).Obj(),
777 st.MakePod().Name("p3c").Node("node-3").Label("foo", "").Container(pause).Obj(),
778 },
779 fits: true,
780 nodes: defaultNodes,
781 want: []string{"node-1"},
782 },
783 {
784 name: "combined with hardSpread constraint on a ~4~/0/1/2 cluster",
785 incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).
786 SpreadConstraint(1, "node", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
787 SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
788 Obj(),
789 existingPods: []*v1.Pod{
790 st.MakePod().Name("p0a").Node("node-0").Label("foo", "").Container(pause).Obj(),
791 st.MakePod().Name("p0b").Node("node-0").Label("foo", "").Container(pause).Obj(),
792 st.MakePod().Name("p0c").Node("node-0").Label("foo", "").Container(pause).Obj(),
793 st.MakePod().Name("p0d").Node("node-0").Label("foo", "").Container(pause).Obj(),
794 st.MakePod().Name("p2").Node("node-2").Label("foo", "").Container(pause).Obj(),
795 st.MakePod().Name("p3a").Node("node-3").Label("foo", "").Container(pause).Obj(),
796 st.MakePod().Name("p3b").Node("node-3").Label("foo", "").Container(pause).Obj(),
797 },
798 fits: true,
799 nodes: defaultNodes,
800 want: []string{"node-2"},
801 },
802 {
803
804
805
806 name: "soft constraint with two node inclusion Constraints, zone: honor/ignore, node: honor/ignore",
807 incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).
808 NodeSelector(map[string]string{"foo": ""}).
809 SpreadConstraint(1, "zone", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
810 SpreadConstraint(1, "node", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
811 Obj(),
812 existingPods: []*v1.Pod{
813 st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(),
814 st.MakePod().Name("p2a").Node("node-2").Label("foo", "").Container(pause).Obj(),
815 st.MakePod().Name("p2b").Node("node-2").Label("foo", "").Container(pause).Obj(),
816 st.MakePod().Name("p4a").Node("node-4").Label("foo", "").Container(pause).Obj(),
817 },
818 fits: true,
819 nodes: []*v1.Node{
820 st.MakeNode().Name("node-1").Label("node", "node-1").Label("zone", "zone-1").Label("foo", "").Obj(),
821 st.MakeNode().Name("node-2").Label("node", "node-2").Label("zone", "zone-1").Label("foo", "").Taints(taints).Obj(),
822 st.MakeNode().Name("node-3").Label("node", "node-3").Label("zone", "zone-2").Label("foo", "").Obj(),
823 st.MakeNode().Name("node-4").Label("node", "node-4").Label("zone", "zone-2").Obj(),
824 },
825 want: []string{"node-3"},
826 enableNodeInclusionPolicy: true,
827 },
828 {
829
830
831
832 name: "soft constraint with two node inclusion Constraints, zone: ignore/ignore, node: honor/honor",
833 incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).
834 NodeSelector(map[string]string{"foo": ""}).
835 SpreadConstraint(1, "zone", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, &ignorePolicy, nil, nil).
836 SpreadConstraint(1, "node", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, &honorPolicy, nil).
837 Obj(),
838 existingPods: []*v1.Pod{
839 st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(),
840 st.MakePod().Name("p2a").Node("node-2").Label("foo", "").Container(pause).Obj(),
841 st.MakePod().Name("p2b").Node("node-2").Label("foo", "").Container(pause).Obj(),
842 st.MakePod().Name("p4a").Node("node-4").Label("foo", "").Container(pause).Obj(),
843 },
844 fits: true,
845 nodes: []*v1.Node{
846 st.MakeNode().Name("node-1").Label("node", "node-1").Label("zone", "zone-1").Label("foo", "").Obj(),
847 st.MakeNode().Name("node-2").Label("node", "node-2").Label("zone", "zone-1").Label("foo", "").Taints(taints).Obj(),
848 st.MakeNode().Name("node-3").Label("node", "node-3").Label("zone", "zone-2").Label("foo", "").Obj(),
849 st.MakeNode().Name("node-4").Label("node", "node-4").Label("zone", "zone-2").Obj(),
850 },
851 want: []string{"node-3"},
852 enableNodeInclusionPolicy: true,
853 },
854 {
855 name: "matchLabelKeys ignored when feature gate disabled, node-1 is the preferred fit",
856 incomingPod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").Container(pause).
857 SpreadConstraint(1, "node", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, []string{"bar"}).
858 Obj(),
859 existingPods: []*v1.Pod{
860 st.MakePod().Name("p1").Node("node-1").Label("foo", "").Label("bar", "").Container(pause).Obj(),
861 st.MakePod().Name("p2a").Node("node-2").Label("foo", "").Container(pause).Obj(),
862 st.MakePod().Name("p2b").Node("node-2").Label("foo", "").Container(pause).Obj(),
863 st.MakePod().Name("p3a").Node("node-3").Label("foo", "").Label("bar", "").Container(pause).Obj(),
864 st.MakePod().Name("p3b").Node("node-3").Label("foo", "").Label("bar", "").Container(pause).Obj(),
865 st.MakePod().Name("p3c").Node("node-3").Label("foo", "").Container(pause).Obj(),
866 },
867 fits: true,
868 nodes: defaultNodes,
869 want: []string{"node-1"},
870 enableMatchLabelKeys: false,
871 },
872 {
873 name: "matchLabelKeys ANDed with LabelSelector when LabelSelector isn't empty, node-2 is the preferred fit",
874 incomingPod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").Container(pause).
875 SpreadConstraint(1, "node", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, []string{"bar"}).
876 Obj(),
877 existingPods: []*v1.Pod{
878 st.MakePod().Name("p1").Node("node-1").Label("foo", "").Label("bar", "").Container(pause).Obj(),
879 st.MakePod().Name("p2a").Node("node-2").Label("foo", "").Container(pause).Obj(),
880 st.MakePod().Name("p2b").Node("node-2").Label("foo", "").Container(pause).Obj(),
881 st.MakePod().Name("p3a").Node("node-3").Label("foo", "").Label("bar", "").Container(pause).Obj(),
882 st.MakePod().Name("p3b").Node("node-3").Label("foo", "").Label("bar", "").Container(pause).Obj(),
883 st.MakePod().Name("p3c").Node("node-3").Label("foo", "").Container(pause).Obj(),
884 },
885 fits: true,
886 nodes: defaultNodes,
887 want: []string{"node-2"},
888 enableMatchLabelKeys: true,
889 },
890 {
891 name: "matchLabelKeys ANDed with LabelSelector when LabelSelector is empty, node-1 is the preferred fit",
892 incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).
893 SpreadConstraint(1, "node", softSpread, st.MakeLabelSelector().Obj(), nil, nil, nil, []string{"foo"}).
894 Obj(),
895 existingPods: []*v1.Pod{
896 st.MakePod().Name("p1").Node("node-1").Label("foo", "").Container(pause).Obj(),
897 st.MakePod().Name("p2a").Node("node-2").Label("foo", "").Container(pause).Obj(),
898 st.MakePod().Name("p2b").Node("node-2").Label("foo", "").Container(pause).Obj(),
899 st.MakePod().Name("p3a").Node("node-3").Label("foo", "").Container(pause).Obj(),
900 st.MakePod().Name("p3b").Node("node-3").Label("foo", "").Container(pause).Obj(),
901 st.MakePod().Name("p3c").Node("node-3").Label("foo", "").Container(pause).Obj(),
902 },
903 fits: true,
904 nodes: defaultNodes,
905 want: []string{"node-1"},
906 enableMatchLabelKeys: true,
907 },
908 }
909 for _, tt := range tests {
910 t.Run(tt.name, func(t *testing.T) {
911 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.NodeInclusionPolicyInPodTopologySpread, tt.enableNodeInclusionPolicy)()
912 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MatchLabelKeysInPodTopologySpread, tt.enableMatchLabelKeys)()
913
914 testCtx := initTestSchedulerForPriorityTest(t, podtopologyspread.Name, podtopologyspread.Name)
915 cs := testCtx.ClientSet
916 ns := testCtx.NS.Name
917
918 for i := range tt.nodes {
919 if _, err := createNode(cs, tt.nodes[i]); err != nil {
920 t.Fatalf("Cannot create node: %v", err)
921 }
922 }
923
924
925 for i := range tt.existingPods {
926 tt.existingPods[i].SetNamespace(ns)
927 }
928 tt.incomingPod.SetNamespace(ns)
929
930 allPods := append(tt.existingPods, tt.incomingPod)
931 defer testutils.CleanupPods(testCtx.Ctx, cs, t, allPods)
932 for _, pod := range tt.existingPods {
933 createdPod, err := cs.CoreV1().Pods(pod.Namespace).Create(testCtx.Ctx, pod, metav1.CreateOptions{})
934 if err != nil {
935 t.Fatalf("Test Failed: error while creating pod during test: %v", err)
936 }
937 err = wait.PollUntilContextTimeout(testCtx.Ctx, pollInterval, wait.ForeverTestTimeout, false,
938 testutils.PodScheduled(cs, createdPod.Namespace, createdPod.Name))
939 if err != nil {
940 t.Errorf("Test Failed: error while waiting for pod during test: %v", err)
941 }
942 }
943
944 testPod, err := cs.CoreV1().Pods(tt.incomingPod.Namespace).Create(testCtx.Ctx, tt.incomingPod, metav1.CreateOptions{})
945 if err != nil {
946 t.Fatalf("Test Failed: error while creating pod during test: %v", err)
947 }
948
949 if tt.fits {
950 err = wait.PollUntilContextTimeout(testCtx.Ctx, pollInterval, wait.ForeverTestTimeout, false,
951 podScheduledIn(cs, testPod.Namespace, testPod.Name, tt.want))
952 } else {
953 err = wait.PollUntilContextTimeout(testCtx.Ctx, pollInterval, wait.ForeverTestTimeout, false,
954 podUnschedulable(cs, testPod.Namespace, testPod.Name))
955 }
956 if err != nil {
957 t.Errorf("Test Failed: %v", err)
958 }
959 })
960 }
961 }
962
963
964
965
966 func TestDefaultPodTopologySpreadScoring(t *testing.T) {
967 testCtx := initTestSchedulerForPriorityTest(t, podtopologyspread.Name, podtopologyspread.Name)
968 cs := testCtx.ClientSet
969 ns := testCtx.NS.Name
970
971 zoneForNode := make(map[string]string)
972 for i := 0; i < 300; i++ {
973 nodeName := fmt.Sprintf("node-%d", i)
974 zone := fmt.Sprintf("zone-%d", i%3)
975 zoneForNode[nodeName] = zone
976 _, err := createNode(cs, st.MakeNode().Name(nodeName).Label(v1.LabelHostname, nodeName).Label(v1.LabelTopologyZone, zone).Obj())
977 if err != nil {
978 t.Fatalf("Cannot create node: %v", err)
979 }
980 }
981
982 serviceName := "test-service"
983 svc := &v1.Service{
984 ObjectMeta: metav1.ObjectMeta{
985 Name: serviceName,
986 Namespace: ns,
987 },
988 Spec: v1.ServiceSpec{
989 Selector: map[string]string{
990 "service": serviceName,
991 },
992 Ports: []v1.ServicePort{{
993 Port: 80,
994 TargetPort: intstr.FromInt32(80),
995 }},
996 },
997 }
998 _, err := cs.CoreV1().Services(ns).Create(testCtx.Ctx, svc, metav1.CreateOptions{})
999 if err != nil {
1000 t.Fatalf("Cannot create Service: %v", err)
1001 }
1002
1003 pause := imageutils.GetPauseImageName()
1004 totalPodCnt := 0
1005 for _, nPods := range []int{3, 9, 15} {
1006
1007 t.Run(fmt.Sprintf("%d-pods", totalPodCnt+nPods), func(t *testing.T) {
1008 for i := 0; i < nPods; i++ {
1009 p := st.MakePod().Name(fmt.Sprintf("p-%d", totalPodCnt)).Label("service", serviceName).Container(pause).Obj()
1010 _, err = cs.CoreV1().Pods(ns).Create(testCtx.Ctx, p, metav1.CreateOptions{})
1011 if err != nil {
1012 t.Fatalf("Cannot create Pod: %v", err)
1013 }
1014 totalPodCnt++
1015 }
1016 var pods []v1.Pod
1017
1018 err = wait.PollUntilContextTimeout(testCtx.Ctx, pollInterval, wait.ForeverTestTimeout, false, func(ctx context.Context) (bool, error) {
1019 podList, err := cs.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{})
1020 if err != nil {
1021 t.Fatalf("Cannot list pods to verify scheduling: %v", err)
1022 }
1023 for _, p := range podList.Items {
1024 if p.Spec.NodeName == "" {
1025 return false, nil
1026 }
1027 }
1028 pods = podList.Items
1029 return true, nil
1030 })
1031
1032 zoneCnts := make(map[string]int)
1033 for _, p := range pods {
1034 zoneCnts[zoneForNode[p.Spec.NodeName]]++
1035 }
1036 maxCnt := 0
1037 minCnt := len(pods)
1038 for _, c := range zoneCnts {
1039 if c > maxCnt {
1040 maxCnt = c
1041 }
1042 if c < minCnt {
1043 minCnt = c
1044 }
1045 }
1046 if skew := maxCnt - minCnt; skew != 0 {
1047 t.Errorf("Zone skew is %d, should be 0", skew)
1048 }
1049 })
1050 }
1051 }
1052
View as plain text