k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/integration/scheduler/filters/filters_test.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package filters 18 19 import ( 20 "context" 21 "fmt" 22 "testing" 23 "time" 24 25 "github.com/google/go-cmp/cmp" 26 v1 "k8s.io/api/core/v1" 27 apierrors "k8s.io/apimachinery/pkg/api/errors" 28 "k8s.io/apimachinery/pkg/api/resource" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/util/wait" 31 utilfeature "k8s.io/apiserver/pkg/util/feature" 32 "k8s.io/client-go/kubernetes" 33 featuregatetesting "k8s.io/component-base/featuregate/testing" 34 "k8s.io/component-helpers/storage/volume" 35 "k8s.io/kubernetes/pkg/features" 36 st "k8s.io/kubernetes/pkg/scheduler/testing" 37 testutils "k8s.io/kubernetes/test/integration/util" 38 imageutils "k8s.io/kubernetes/test/utils/image" 39 "k8s.io/kubernetes/test/utils/ktesting" 40 "k8s.io/utils/pointer" 41 ) 42 43 var ( 44 createAndWaitForNodesInCache = testutils.CreateAndWaitForNodesInCache 45 createNamespacesWithLabels = testutils.CreateNamespacesWithLabels 46 createNode = testutils.CreateNode 47 updateNode = testutils.UpdateNode 48 createPausePod = testutils.CreatePausePod 49 deletePod = testutils.DeletePod 50 getPod = testutils.GetPod 51 initPausePod = testutils.InitPausePod 52 initTest = testutils.InitTestSchedulerWithNS 53 podScheduledIn = testutils.PodScheduledIn 54 podUnschedulable = testutils.PodUnschedulable 55 waitForPodUnschedulable = testutils.WaitForPodUnschedulable 56 ) 57 58 // This file tests the scheduler predicates functionality. 59 60 const pollInterval = 100 * time.Millisecond 61 62 var ( 63 ignorePolicy = v1.NodeInclusionPolicyIgnore 64 honorPolicy = v1.NodeInclusionPolicyHonor 65 taints = []v1.Taint{{Key: v1.TaintNodeUnschedulable, Value: "", Effect: v1.TaintEffectNoSchedule}} 66 ) 67 68 // TestInterPodAffinity verifies that scheduler's inter pod affinity and 69 // anti-affinity predicate functions works correctly. 70 func TestInterPodAffinity(t *testing.T) { 71 podLabel := map[string]string{"service": "securityscan"} 72 podLabel2 := map[string]string{"security": "S1"} 73 74 defaultNS := "ns1" 75 76 tests := []struct { 77 name string 78 pod *v1.Pod 79 pods []*v1.Pod 80 fits bool 81 enableMatchLabelKeysInAffinity bool 82 errorType string 83 }{ 84 { 85 name: "validates that a pod with an invalid podAffinity is rejected because of the LabelSelectorRequirement is invalid", 86 pod: &v1.Pod{ 87 ObjectMeta: metav1.ObjectMeta{ 88 Name: "fakename", 89 Labels: podLabel2, 90 }, 91 Spec: v1.PodSpec{ 92 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 93 Affinity: &v1.Affinity{ 94 PodAffinity: &v1.PodAffinity{ 95 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 96 { 97 LabelSelector: &metav1.LabelSelector{ 98 MatchExpressions: []metav1.LabelSelectorRequirement{ 99 { 100 Key: "security", 101 Operator: metav1.LabelSelectorOpDoesNotExist, 102 }, 103 }, 104 }, 105 TopologyKey: "region", 106 }, 107 }, 108 }, 109 }, 110 }, 111 }, 112 fits: false, 113 errorType: "invalidPod", 114 }, 115 { 116 name: "validates that Inter-pod-Affinity is respected if not matching", 117 pod: &v1.Pod{ 118 ObjectMeta: metav1.ObjectMeta{ 119 Name: "fakename", 120 Labels: podLabel2, 121 }, 122 Spec: v1.PodSpec{ 123 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 124 Affinity: &v1.Affinity{ 125 PodAffinity: &v1.PodAffinity{ 126 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 127 { 128 LabelSelector: &metav1.LabelSelector{ 129 MatchExpressions: []metav1.LabelSelectorRequirement{ 130 { 131 Key: "security", 132 Operator: metav1.LabelSelectorOpIn, 133 Values: []string{"securityscan"}, 134 }, 135 }, 136 }, 137 TopologyKey: "region", 138 }, 139 }, 140 }, 141 }, 142 }, 143 }, 144 fits: false, 145 }, 146 { 147 name: "validates that InterPodAffinity is respected if matching. requiredDuringSchedulingIgnoredDuringExecution in PodAffinity using In operator that matches the existing pod", 148 pod: &v1.Pod{ 149 ObjectMeta: metav1.ObjectMeta{ 150 Name: "fakename", 151 Labels: podLabel2, 152 }, 153 Spec: v1.PodSpec{ 154 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 155 Affinity: &v1.Affinity{ 156 PodAffinity: &v1.PodAffinity{ 157 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 158 { 159 LabelSelector: &metav1.LabelSelector{ 160 MatchExpressions: []metav1.LabelSelectorRequirement{ 161 { 162 Key: "service", 163 Operator: metav1.LabelSelectorOpIn, 164 Values: []string{"securityscan", "value2"}, 165 }, 166 }, 167 }, 168 TopologyKey: "region", 169 }, 170 }, 171 }, 172 }, 173 }, 174 }, 175 pods: []*v1.Pod{{ 176 ObjectMeta: metav1.ObjectMeta{ 177 Name: "fakename2", 178 Labels: podLabel, 179 }, 180 Spec: v1.PodSpec{ 181 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 182 NodeName: "testnode-0", 183 }, 184 }, 185 }, 186 fits: true, 187 }, 188 { 189 name: "validates that InterPodAffinity is respected if matching. requiredDuringSchedulingIgnoredDuringExecution in PodAffinity using not in operator in labelSelector that matches the existing pod", 190 pod: &v1.Pod{ 191 ObjectMeta: metav1.ObjectMeta{ 192 Name: "fakename", 193 Labels: podLabel2, 194 }, 195 Spec: v1.PodSpec{ 196 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 197 Affinity: &v1.Affinity{ 198 PodAffinity: &v1.PodAffinity{ 199 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 200 { 201 LabelSelector: &metav1.LabelSelector{ 202 MatchExpressions: []metav1.LabelSelectorRequirement{ 203 { 204 Key: "service", 205 Operator: metav1.LabelSelectorOpNotIn, 206 Values: []string{"securityscan3", "value3"}, 207 }, 208 }, 209 }, 210 TopologyKey: "region", 211 }, 212 }, 213 }, 214 }, 215 }, 216 }, 217 pods: []*v1.Pod{{Spec: v1.PodSpec{ 218 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 219 NodeName: "testnode-0"}, 220 ObjectMeta: metav1.ObjectMeta{ 221 Name: "fakename2", 222 Labels: podLabel}}}, 223 fits: true, 224 }, 225 { 226 name: "validates that inter-pod-affinity is respected when pods have different Namespaces", 227 pod: &v1.Pod{ 228 ObjectMeta: metav1.ObjectMeta{ 229 Name: "fakename", 230 Labels: podLabel2, 231 }, 232 Spec: v1.PodSpec{ 233 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 234 Affinity: &v1.Affinity{ 235 PodAffinity: &v1.PodAffinity{ 236 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 237 { 238 LabelSelector: &metav1.LabelSelector{ 239 MatchExpressions: []metav1.LabelSelectorRequirement{ 240 { 241 Key: "service", 242 Operator: metav1.LabelSelectorOpIn, 243 Values: []string{"securityscan", "value2"}, 244 }, 245 }, 246 }, 247 TopologyKey: "region", 248 Namespaces: []string{"diff-namespace"}, 249 }, 250 }, 251 }, 252 }, 253 }, 254 }, 255 pods: []*v1.Pod{{Spec: v1.PodSpec{ 256 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 257 NodeName: "testnode-0"}, 258 ObjectMeta: metav1.ObjectMeta{ 259 Name: "fakename2", 260 Labels: podLabel, Namespace: "ns2"}}}, 261 fits: false, 262 }, 263 { 264 name: "Doesn't satisfy the PodAffinity because of unmatching labelSelector with the existing pod", 265 pod: &v1.Pod{ 266 ObjectMeta: metav1.ObjectMeta{ 267 Name: "fakename", 268 Labels: podLabel, 269 }, 270 Spec: v1.PodSpec{ 271 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 272 Affinity: &v1.Affinity{ 273 PodAffinity: &v1.PodAffinity{ 274 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 275 { 276 LabelSelector: &metav1.LabelSelector{ 277 MatchExpressions: []metav1.LabelSelectorRequirement{ 278 { 279 Key: "service", 280 Operator: metav1.LabelSelectorOpIn, 281 Values: []string{"antivirusscan", "value2"}, 282 }, 283 }, 284 }, 285 TopologyKey: "region", 286 }, 287 }, 288 }, 289 }, 290 }, 291 }, 292 pods: []*v1.Pod{{Spec: v1.PodSpec{ 293 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 294 NodeName: "testnode-0"}, ObjectMeta: metav1.ObjectMeta{ 295 Name: "fakename2", 296 Labels: podLabel}}}, 297 fits: false, 298 }, 299 { 300 name: "validates that InterPodAffinity is respected if matching with multiple affinities in multiple RequiredDuringSchedulingIgnoredDuringExecution ", 301 pod: &v1.Pod{ 302 ObjectMeta: metav1.ObjectMeta{ 303 Name: "fakename", 304 Labels: podLabel2, 305 }, 306 Spec: v1.PodSpec{ 307 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 308 Affinity: &v1.Affinity{ 309 PodAffinity: &v1.PodAffinity{ 310 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 311 { 312 LabelSelector: &metav1.LabelSelector{ 313 MatchExpressions: []metav1.LabelSelectorRequirement{ 314 { 315 Key: "service", 316 Operator: metav1.LabelSelectorOpExists, 317 }, { 318 Key: "wrongkey", 319 Operator: metav1.LabelSelectorOpDoesNotExist, 320 }, 321 }, 322 }, 323 TopologyKey: "region", 324 }, { 325 LabelSelector: &metav1.LabelSelector{ 326 MatchExpressions: []metav1.LabelSelectorRequirement{ 327 { 328 Key: "service", 329 Operator: metav1.LabelSelectorOpIn, 330 Values: []string{"securityscan"}, 331 }, { 332 Key: "service", 333 Operator: metav1.LabelSelectorOpNotIn, 334 Values: []string{"WrongValue"}, 335 }, 336 }, 337 }, 338 TopologyKey: "region", 339 }, 340 }, 341 }, 342 }, 343 }, 344 }, 345 pods: []*v1.Pod{{Spec: v1.PodSpec{ 346 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 347 NodeName: "testnode-0"}, ObjectMeta: metav1.ObjectMeta{ 348 Name: "fakename2", 349 Labels: podLabel}}}, 350 fits: true, 351 }, 352 { 353 name: "The labelSelector requirements(items of matchExpressions) are ANDed, the pod cannot schedule onto the node because one of the matchExpression items doesn't match.", 354 pod: &v1.Pod{ 355 ObjectMeta: metav1.ObjectMeta{ 356 Labels: podLabel2, 357 Name: "fakename", 358 }, 359 Spec: v1.PodSpec{ 360 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 361 Affinity: &v1.Affinity{ 362 PodAffinity: &v1.PodAffinity{ 363 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 364 { 365 LabelSelector: &metav1.LabelSelector{ 366 MatchExpressions: []metav1.LabelSelectorRequirement{ 367 { 368 Key: "service", 369 Operator: metav1.LabelSelectorOpExists, 370 }, { 371 Key: "wrongkey", 372 Operator: metav1.LabelSelectorOpDoesNotExist, 373 }, 374 }, 375 }, 376 TopologyKey: "region", 377 }, { 378 LabelSelector: &metav1.LabelSelector{ 379 MatchExpressions: []metav1.LabelSelectorRequirement{ 380 { 381 Key: "service", 382 Operator: metav1.LabelSelectorOpIn, 383 Values: []string{"securityscan2"}, 384 }, { 385 Key: "service", 386 Operator: metav1.LabelSelectorOpNotIn, 387 Values: []string{"WrongValue"}, 388 }, 389 }, 390 }, 391 TopologyKey: "region", 392 }, 393 }, 394 }, 395 }, 396 }, 397 }, 398 pods: []*v1.Pod{{Spec: v1.PodSpec{ 399 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 400 NodeName: "testnode-0"}, ObjectMeta: metav1.ObjectMeta{ 401 Name: "fakename2", 402 Labels: podLabel}}}, 403 fits: false, 404 }, 405 { 406 name: "validates that InterPod Affinity and AntiAffinity is respected if matching", 407 pod: &v1.Pod{ 408 ObjectMeta: metav1.ObjectMeta{ 409 Name: "fakename", 410 Labels: podLabel2, 411 }, 412 Spec: v1.PodSpec{ 413 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 414 Affinity: &v1.Affinity{ 415 PodAffinity: &v1.PodAffinity{ 416 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 417 { 418 LabelSelector: &metav1.LabelSelector{ 419 MatchExpressions: []metav1.LabelSelectorRequirement{ 420 { 421 Key: "service", 422 Operator: metav1.LabelSelectorOpIn, 423 Values: []string{"securityscan", "value2"}, 424 }, 425 }, 426 }, 427 TopologyKey: "region", 428 }, 429 }, 430 }, 431 PodAntiAffinity: &v1.PodAntiAffinity{ 432 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 433 { 434 LabelSelector: &metav1.LabelSelector{ 435 MatchExpressions: []metav1.LabelSelectorRequirement{ 436 { 437 Key: "service", 438 Operator: metav1.LabelSelectorOpIn, 439 Values: []string{"antivirusscan", "value2"}, 440 }, 441 }, 442 }, 443 TopologyKey: "node", 444 }, 445 }, 446 }, 447 }, 448 }, 449 }, 450 pods: []*v1.Pod{{Spec: v1.PodSpec{ 451 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 452 NodeName: "testnode-0"}, ObjectMeta: metav1.ObjectMeta{ 453 Name: "fakename2", 454 Labels: podLabel}}}, 455 fits: true, 456 }, 457 { 458 name: "satisfies the PodAffinity and PodAntiAffinity and PodAntiAffinity symmetry with the existing pod", 459 pod: &v1.Pod{ 460 ObjectMeta: metav1.ObjectMeta{ 461 Name: "fakename", 462 Labels: podLabel2, 463 }, 464 Spec: v1.PodSpec{ 465 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 466 Affinity: &v1.Affinity{ 467 PodAffinity: &v1.PodAffinity{ 468 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 469 { 470 LabelSelector: &metav1.LabelSelector{ 471 MatchExpressions: []metav1.LabelSelectorRequirement{ 472 { 473 Key: "service", 474 Operator: metav1.LabelSelectorOpIn, 475 Values: []string{"securityscan", "value2"}, 476 }, 477 }, 478 }, 479 TopologyKey: "region", 480 }, 481 }, 482 }, 483 PodAntiAffinity: &v1.PodAntiAffinity{ 484 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 485 { 486 LabelSelector: &metav1.LabelSelector{ 487 MatchExpressions: []metav1.LabelSelectorRequirement{ 488 { 489 Key: "service", 490 Operator: metav1.LabelSelectorOpIn, 491 Values: []string{"antivirusscan", "value2"}, 492 }, 493 }, 494 }, 495 TopologyKey: "node", 496 }, 497 }, 498 }, 499 }, 500 }, 501 }, 502 pods: []*v1.Pod{ 503 { 504 Spec: v1.PodSpec{ 505 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 506 NodeName: "testnode-0", 507 Affinity: &v1.Affinity{ 508 PodAntiAffinity: &v1.PodAntiAffinity{ 509 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 510 { 511 LabelSelector: &metav1.LabelSelector{ 512 MatchExpressions: []metav1.LabelSelectorRequirement{ 513 { 514 Key: "service", 515 Operator: metav1.LabelSelectorOpIn, 516 Values: []string{"antivirusscan", "value2"}, 517 }, 518 }, 519 }, 520 TopologyKey: "node", 521 }, 522 }, 523 }, 524 }, 525 }, 526 ObjectMeta: metav1.ObjectMeta{ 527 Name: "fakename2", 528 Labels: podLabel}, 529 }, 530 }, 531 fits: true, 532 }, 533 { 534 name: "satisfies the PodAffinity but doesn't satisfies the PodAntiAffinity with the existing pod", 535 pod: &v1.Pod{ 536 ObjectMeta: metav1.ObjectMeta{ 537 Name: "fakename", 538 Labels: podLabel2, 539 }, 540 Spec: v1.PodSpec{ 541 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 542 Affinity: &v1.Affinity{ 543 PodAffinity: &v1.PodAffinity{ 544 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 545 { 546 LabelSelector: &metav1.LabelSelector{ 547 MatchExpressions: []metav1.LabelSelectorRequirement{ 548 { 549 Key: "service", 550 Operator: metav1.LabelSelectorOpIn, 551 Values: []string{"securityscan", "value2"}, 552 }, 553 }, 554 }, 555 TopologyKey: "region", 556 }, 557 }, 558 }, 559 PodAntiAffinity: &v1.PodAntiAffinity{ 560 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 561 { 562 LabelSelector: &metav1.LabelSelector{ 563 MatchExpressions: []metav1.LabelSelectorRequirement{ 564 { 565 Key: "service", 566 Operator: metav1.LabelSelectorOpIn, 567 Values: []string{"securityscan", "value2"}, 568 }, 569 }, 570 }, 571 TopologyKey: "zone", 572 }, 573 }, 574 }, 575 }, 576 }, 577 }, 578 pods: []*v1.Pod{{Spec: v1.PodSpec{ 579 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 580 NodeName: "testnode-0"}, ObjectMeta: metav1.ObjectMeta{ 581 Name: "fakename2", 582 Labels: podLabel}}}, 583 fits: false, 584 }, 585 { 586 name: "satisfies the PodAffinity and PodAntiAffinity but doesn't satisfies PodAntiAffinity symmetry with the existing pod", 587 pod: &v1.Pod{ 588 ObjectMeta: metav1.ObjectMeta{ 589 Name: "fakename", 590 Labels: podLabel, 591 }, 592 Spec: v1.PodSpec{ 593 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 594 Affinity: &v1.Affinity{ 595 PodAffinity: &v1.PodAffinity{ 596 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 597 { 598 LabelSelector: &metav1.LabelSelector{ 599 MatchExpressions: []metav1.LabelSelectorRequirement{ 600 { 601 Key: "service", 602 Operator: metav1.LabelSelectorOpIn, 603 Values: []string{"securityscan", "value2"}, 604 }, 605 }, 606 }, 607 TopologyKey: "region", 608 }, 609 }, 610 }, 611 PodAntiAffinity: &v1.PodAntiAffinity{ 612 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 613 { 614 LabelSelector: &metav1.LabelSelector{ 615 MatchExpressions: []metav1.LabelSelectorRequirement{ 616 { 617 Key: "service", 618 Operator: metav1.LabelSelectorOpIn, 619 Values: []string{"antivirusscan", "value2"}, 620 }, 621 }, 622 }, 623 TopologyKey: "node", 624 }, 625 }, 626 }, 627 }, 628 }, 629 }, 630 pods: []*v1.Pod{ 631 { 632 Spec: v1.PodSpec{ 633 NodeName: "testnode-0", 634 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 635 Affinity: &v1.Affinity{ 636 PodAntiAffinity: &v1.PodAntiAffinity{ 637 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 638 { 639 LabelSelector: &metav1.LabelSelector{ 640 MatchExpressions: []metav1.LabelSelectorRequirement{ 641 { 642 Key: "service", 643 Operator: metav1.LabelSelectorOpIn, 644 Values: []string{"securityscan", "value3"}, 645 }, 646 }, 647 }, 648 TopologyKey: "zone", 649 }, 650 }, 651 }, 652 }, 653 }, 654 ObjectMeta: metav1.ObjectMeta{ 655 Name: "fakename2", 656 Labels: podLabel}, 657 }, 658 }, 659 fits: false, 660 }, 661 { 662 name: "pod matches its own Label in PodAffinity and that matches the existing pod Labels", 663 pod: &v1.Pod{ 664 ObjectMeta: metav1.ObjectMeta{ 665 Name: "fakename", 666 Labels: podLabel, 667 }, 668 Spec: v1.PodSpec{ 669 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 670 Affinity: &v1.Affinity{ 671 PodAffinity: &v1.PodAffinity{ 672 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 673 { 674 LabelSelector: &metav1.LabelSelector{ 675 MatchExpressions: []metav1.LabelSelectorRequirement{ 676 { 677 Key: "service", 678 Operator: metav1.LabelSelectorOpNotIn, 679 Values: []string{"securityscan", "value2"}, 680 }, 681 }, 682 }, 683 TopologyKey: "region", 684 }, 685 }, 686 }, 687 }, 688 }, 689 }, 690 pods: []*v1.Pod{{Spec: v1.PodSpec{ 691 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 692 NodeName: "machine2"}, ObjectMeta: metav1.ObjectMeta{ 693 Name: "fakename2", 694 Labels: podLabel}}}, 695 fits: false, 696 }, 697 { 698 name: "Verify that PodAntiAffinity of an existing pod is respected when PodAntiAffinity symmetry is not satisfied with the existing pod", 699 pod: &v1.Pod{ 700 ObjectMeta: metav1.ObjectMeta{ 701 Name: "fakename", 702 Labels: podLabel, 703 }, 704 Spec: v1.PodSpec{Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}}, 705 }, 706 pods: []*v1.Pod{ 707 { 708 Spec: v1.PodSpec{NodeName: "testnode-0", 709 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 710 Affinity: &v1.Affinity{ 711 PodAntiAffinity: &v1.PodAntiAffinity{ 712 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 713 { 714 LabelSelector: &metav1.LabelSelector{ 715 MatchExpressions: []metav1.LabelSelectorRequirement{ 716 { 717 Key: "service", 718 Operator: metav1.LabelSelectorOpIn, 719 Values: []string{"securityscan", "value2"}, 720 }, 721 }, 722 }, 723 TopologyKey: "zone", 724 }, 725 }, 726 }, 727 }, 728 }, 729 ObjectMeta: metav1.ObjectMeta{ 730 Name: "fakename2", 731 Labels: podLabel}, 732 }, 733 }, 734 fits: false, 735 }, 736 { 737 name: "Verify that PodAntiAffinity from existing pod is respected when pod statisfies PodAntiAffinity symmetry with the existing pod", 738 pod: &v1.Pod{ 739 ObjectMeta: metav1.ObjectMeta{ 740 Name: "fake-name", 741 Labels: podLabel, 742 }, 743 Spec: v1.PodSpec{Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}}, 744 }, 745 pods: []*v1.Pod{ 746 { 747 Spec: v1.PodSpec{NodeName: "testnode-0", 748 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 749 Affinity: &v1.Affinity{ 750 PodAntiAffinity: &v1.PodAntiAffinity{ 751 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 752 { 753 LabelSelector: &metav1.LabelSelector{ 754 MatchExpressions: []metav1.LabelSelectorRequirement{ 755 { 756 Key: "service", 757 Operator: metav1.LabelSelectorOpNotIn, 758 Values: []string{"securityscan", "value2"}, 759 }, 760 }, 761 }, 762 TopologyKey: "zone", 763 }, 764 }, 765 }, 766 }, 767 }, 768 ObjectMeta: metav1.ObjectMeta{ 769 Name: "fake-name2", 770 Labels: podLabel}, 771 }, 772 }, 773 fits: true, 774 }, 775 { 776 name: "nodes[0] and nodes[1] have same topologyKey and label value. nodes[0] has an existing pod that matches the inter pod affinity rule. The new pod can not be scheduled onto either of the two nodes.", 777 pod: &v1.Pod{ 778 ObjectMeta: metav1.ObjectMeta{Name: "fake-name2"}, 779 Spec: v1.PodSpec{ 780 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 781 NodeSelector: map[string]string{"region": "r1"}, 782 Affinity: &v1.Affinity{ 783 PodAntiAffinity: &v1.PodAntiAffinity{ 784 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 785 { 786 LabelSelector: &metav1.LabelSelector{ 787 MatchExpressions: []metav1.LabelSelectorRequirement{ 788 { 789 Key: "foo", 790 Operator: metav1.LabelSelectorOpIn, 791 Values: []string{"abc"}, 792 }, 793 }, 794 }, 795 TopologyKey: "region", 796 }, 797 }, 798 }, 799 }, 800 }, 801 }, 802 pods: []*v1.Pod{ 803 {Spec: v1.PodSpec{ 804 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 805 NodeName: "testnode-0"}, ObjectMeta: metav1.ObjectMeta{Name: "fakename", Labels: map[string]string{"foo": "abc"}}}, 806 }, 807 fits: false, 808 }, 809 { 810 name: "anti affinity: matchLabelKeys is merged into LabelSelector with In operator (feature flag: enabled)", 811 pod: &v1.Pod{ 812 Spec: v1.PodSpec{ 813 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 814 Affinity: &v1.Affinity{ 815 PodAntiAffinity: &v1.PodAntiAffinity{ 816 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 817 { 818 TopologyKey: "zone", 819 LabelSelector: &metav1.LabelSelector{ 820 MatchExpressions: []metav1.LabelSelectorRequirement{ 821 { 822 Key: "foo", 823 Operator: metav1.LabelSelectorOpExists, 824 }, 825 }, 826 }, 827 MatchLabelKeys: []string{"bar"}, 828 }, 829 }, 830 }, 831 }, 832 }, 833 ObjectMeta: metav1.ObjectMeta{ 834 Name: "incoming", 835 Labels: map[string]string{"foo": "", "bar": "a"}, 836 }, 837 }, 838 pods: []*v1.Pod{ 839 // It matches the incoming Pod's anti affinity's labelSelector. 840 // BUT, the matchLabelKeys make the existing Pod's anti affinity's labelSelector not match with this label. 841 { 842 Spec: v1.PodSpec{ 843 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 844 NodeName: "testnode-0", 845 }, 846 ObjectMeta: metav1.ObjectMeta{Name: "pod1", Labels: map[string]string{"foo": "", "bar": "fuga"}}, 847 }, 848 // It matches the incoming Pod's anti affinity's labelSelector. 849 // BUT, the matchLabelKeys make the existing Pod's anti affinity's labelSelector not match with this label. 850 { 851 Spec: v1.PodSpec{ 852 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 853 NodeName: "testnode-0", 854 }, 855 ObjectMeta: metav1.ObjectMeta{Name: "pod2", Labels: map[string]string{"foo": "", "bar": "hoge"}}, 856 }, 857 }, 858 enableMatchLabelKeysInAffinity: true, 859 fits: true, 860 }, 861 { 862 name: "anti affinity: mismatchLabelKeys is merged into LabelSelector with NotIn operator (feature flag: enabled)", 863 pod: &v1.Pod{ 864 Spec: v1.PodSpec{ 865 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 866 Affinity: &v1.Affinity{ 867 PodAntiAffinity: &v1.PodAntiAffinity{ 868 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 869 { 870 TopologyKey: "zone", 871 LabelSelector: &metav1.LabelSelector{ 872 MatchExpressions: []metav1.LabelSelectorRequirement{ 873 { 874 Key: "foo", 875 Operator: metav1.LabelSelectorOpExists, 876 }, 877 }, 878 }, 879 MismatchLabelKeys: []string{"bar"}, 880 }, 881 }, 882 }, 883 }, 884 }, 885 ObjectMeta: metav1.ObjectMeta{ 886 Name: "incoming", 887 Labels: map[string]string{"foo": "", "bar": "a"}, 888 }, 889 }, 890 pods: []*v1.Pod{ 891 // It matches the incoming Pod's anti affinity's labelSelector. 892 // BUT, the mismatchLabelKeys make the existing Pod's anti affinity's labelSelector not match with this label. 893 { 894 Spec: v1.PodSpec{ 895 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 896 NodeName: "testnode-0", 897 }, 898 ObjectMeta: metav1.ObjectMeta{Name: "pod1", Labels: map[string]string{"foo": "", "bar": "a"}}, 899 }, 900 // It matches the incoming Pod's anti affinity's labelSelector. 901 // BUT, the mismatchLabelKeys make the existing Pod's anti affinity's labelSelector not match with this label. 902 { 903 Spec: v1.PodSpec{ 904 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 905 NodeName: "testnode-0", 906 }, 907 ObjectMeta: metav1.ObjectMeta{Name: "pod2", Labels: map[string]string{"foo": "", "bar": "a"}}, 908 }, 909 }, 910 enableMatchLabelKeysInAffinity: true, 911 fits: true, 912 }, 913 { 914 name: "affinity: matchLabelKeys is merged into LabelSelector with In operator (feature flag: enabled)", 915 pod: &v1.Pod{ 916 Spec: v1.PodSpec{ 917 Containers: []v1.Container{ 918 { 919 Name: "container", 920 Image: imageutils.GetPauseImageName(), 921 Resources: v1.ResourceRequirements{ 922 Requests: v1.ResourceList{ 923 v1.ResourceMemory: resource.MustParse("1G"), 924 }, 925 }, 926 }, 927 }, 928 Affinity: &v1.Affinity{ 929 PodAffinity: &v1.PodAffinity{ 930 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 931 { 932 TopologyKey: "node", 933 LabelSelector: &metav1.LabelSelector{ 934 MatchExpressions: []metav1.LabelSelectorRequirement{ 935 { 936 Key: "foo", 937 Operator: metav1.LabelSelectorOpExists, 938 }, 939 }, 940 }, 941 MatchLabelKeys: []string{"bar"}, 942 }, 943 }, 944 }, 945 }, 946 }, 947 ObjectMeta: metav1.ObjectMeta{ 948 Name: "incoming", 949 Labels: map[string]string{"foo": "", "bar": "a"}, 950 }, 951 }, 952 pods: []*v1.Pod{ 953 { 954 // It matches the incoming affinity. But, it uses all resources on nodes[1]. 955 // So, the incoming Pod can no longer get scheduled on nodes[1]. 956 Spec: v1.PodSpec{ 957 Containers: []v1.Container{ 958 { 959 Name: "container", 960 Image: imageutils.GetPauseImageName(), 961 Resources: v1.ResourceRequirements{ 962 Requests: v1.ResourceList{ 963 v1.ResourceMemory: resource.MustParse("1G"), 964 }, 965 }, 966 }, 967 }, 968 NodeName: "anothernode-0", 969 }, 970 ObjectMeta: metav1.ObjectMeta{Name: "pod1", Labels: map[string]string{"foo": "", "bar": "a"}}, 971 }, 972 { 973 // It doesn't match the incoming affinity due to matchLabelKeys. 974 Spec: v1.PodSpec{ 975 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 976 NodeName: "testnode-0", 977 }, 978 ObjectMeta: metav1.ObjectMeta{Name: "pod2", Labels: map[string]string{"foo": "", "bar": "hoge"}}, 979 }, 980 { 981 // It doesn't match the incoming affinity due to matchLabelKeys. 982 Spec: v1.PodSpec{ 983 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 984 NodeName: "testnode-0", 985 }, 986 ObjectMeta: metav1.ObjectMeta{Name: "pod3", Labels: map[string]string{"foo": "", "bar": "fuga"}}, 987 }, 988 }, 989 enableMatchLabelKeysInAffinity: true, 990 fits: false, 991 }, 992 { 993 name: "affinity: mismatchLabelKeys is merged into LabelSelector with NotIn operator (feature flag: enabled)", 994 pod: &v1.Pod{ 995 Spec: v1.PodSpec{ 996 Containers: []v1.Container{ 997 { 998 Name: "container", 999 Image: imageutils.GetPauseImageName(), 1000 Resources: v1.ResourceRequirements{ 1001 Requests: v1.ResourceList{ 1002 v1.ResourceMemory: resource.MustParse("1G"), 1003 }, 1004 }, 1005 }, 1006 }, 1007 Affinity: &v1.Affinity{ 1008 PodAffinity: &v1.PodAffinity{ 1009 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 1010 { 1011 TopologyKey: "node", 1012 LabelSelector: &metav1.LabelSelector{ 1013 MatchExpressions: []metav1.LabelSelectorRequirement{ 1014 { 1015 Key: "foo", 1016 Operator: metav1.LabelSelectorOpExists, 1017 }, 1018 }, 1019 }, 1020 MismatchLabelKeys: []string{"bar"}, 1021 }, 1022 }, 1023 }, 1024 }, 1025 }, 1026 ObjectMeta: metav1.ObjectMeta{ 1027 Name: "incoming", 1028 Labels: map[string]string{"foo": "", "bar": "a"}, 1029 }, 1030 }, 1031 pods: []*v1.Pod{ 1032 { 1033 // It matches the incoming affinity. But, it uses all resources on nodes[1]. 1034 // So, the incoming Pod can no longer get scheduled on nodes[1]. 1035 Spec: v1.PodSpec{ 1036 Containers: []v1.Container{ 1037 { 1038 Name: "container", 1039 Image: imageutils.GetPauseImageName(), 1040 Resources: v1.ResourceRequirements{ 1041 Requests: v1.ResourceList{ 1042 v1.ResourceMemory: resource.MustParse("1G"), 1043 }, 1044 }, 1045 }, 1046 }, 1047 NodeName: "anothernode-0", 1048 }, 1049 ObjectMeta: metav1.ObjectMeta{Name: "pod1", Labels: map[string]string{"foo": "", "bar": "fuga"}}, 1050 }, 1051 { 1052 // It doesn't match the incoming affinity due to mismatchLabelKeys. 1053 Spec: v1.PodSpec{ 1054 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 1055 NodeName: "testnode-0", 1056 }, 1057 ObjectMeta: metav1.ObjectMeta{Name: "pod2", Labels: map[string]string{"foo": "", "bar": "a"}}, 1058 }, 1059 { 1060 // It doesn't match the incoming affinity due to mismatchLabelKeys. 1061 Spec: v1.PodSpec{ 1062 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 1063 NodeName: "testnode-0", 1064 }, 1065 ObjectMeta: metav1.ObjectMeta{Name: "pod3", Labels: map[string]string{"foo": "", "bar": "a"}}, 1066 }, 1067 }, 1068 enableMatchLabelKeysInAffinity: true, 1069 fits: false, 1070 }, 1071 } 1072 1073 for _, test := range tests { 1074 t.Run(test.name, func(t *testing.T) { 1075 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MatchLabelKeysInPodAffinity, test.enableMatchLabelKeysInAffinity) 1076 _, ctx := ktesting.NewTestContext(t) 1077 1078 testCtx := initTest(t, "") 1079 cs := testCtx.ClientSet 1080 1081 if _, err := createNode(cs, st.MakeNode().Name("testnode-0").Label("region", "r1").Label("zone", "z11").Label("node", "n1").Capacity( 1082 map[v1.ResourceName]string{ 1083 v1.ResourceMemory: "1G", 1084 }, 1085 ).Obj()); err != nil { 1086 t.Fatalf("failed to create node: %v", err) 1087 } 1088 1089 // another test node has the same "region" and "zone" labels as testnode, but has a different "node" label. 1090 if _, err := createNode(cs, st.MakeNode().Name("anothernode-0").Label("region", "r1").Label("zone", "z11").Label("node", "n2").Capacity( 1091 map[v1.ResourceName]string{ 1092 v1.ResourceMemory: "1G", 1093 }, 1094 ).Obj()); err != nil { 1095 t.Fatalf("failed to create node: %v", err) 1096 } 1097 1098 if err := createNamespacesWithLabels(cs, []string{"ns1", "ns2"}, map[string]string{"team": "team1"}); err != nil { 1099 t.Fatal(err) 1100 } 1101 if err := createNamespacesWithLabels(cs, []string{"ns3"}, map[string]string{"team": "team2"}); err != nil { 1102 t.Fatal(err) 1103 } 1104 1105 for _, pod := range test.pods { 1106 if pod.Namespace == "" { 1107 pod.Namespace = defaultNS 1108 } 1109 createdPod, err := cs.CoreV1().Pods(pod.Namespace).Create(ctx, pod, metav1.CreateOptions{}) 1110 if err != nil { 1111 t.Fatalf("Error while creating pod: %v", err) 1112 } 1113 err = wait.PollUntilContextTimeout(ctx, pollInterval, wait.ForeverTestTimeout, false, 1114 testutils.PodScheduled(cs, createdPod.Namespace, createdPod.Name)) 1115 if err != nil { 1116 t.Errorf("Error while creating pod: %v", err) 1117 } 1118 } 1119 if test.pod.Namespace == "" { 1120 test.pod.Namespace = defaultNS 1121 } 1122 1123 testPod, err := cs.CoreV1().Pods(test.pod.Namespace).Create(ctx, test.pod, metav1.CreateOptions{}) 1124 if err != nil { 1125 if !(test.errorType == "invalidPod" && apierrors.IsInvalid(err)) { 1126 t.Fatalf("Error while creating pod: %v", err) 1127 } 1128 } 1129 1130 if test.fits { 1131 err = wait.PollUntilContextTimeout(ctx, pollInterval, wait.ForeverTestTimeout, false, 1132 testutils.PodScheduled(cs, testPod.Namespace, testPod.Name)) 1133 } else { 1134 err = wait.PollUntilContextTimeout(ctx, pollInterval, wait.ForeverTestTimeout, false, 1135 podUnschedulable(cs, testPod.Namespace, testPod.Name)) 1136 } 1137 if err != nil { 1138 t.Errorf("Error while trying to fit a pod: %v", err) 1139 return 1140 } 1141 1142 err = cs.CoreV1().Pods(test.pod.Namespace).Delete(ctx, test.pod.Name, *metav1.NewDeleteOptions(0)) 1143 if err != nil { 1144 t.Errorf("Error while deleting pod: %v", err) 1145 } 1146 err = wait.PollUntilContextTimeout(ctx, pollInterval, wait.ForeverTestTimeout, true, 1147 testutils.PodDeleted(ctx, cs, testCtx.NS.Name, test.pod.Name)) 1148 if err != nil { 1149 t.Errorf("Error while waiting for pod to get deleted: %v", err) 1150 } 1151 for _, pod := range test.pods { 1152 err = cs.CoreV1().Pods(pod.Namespace).Delete(ctx, pod.Name, *metav1.NewDeleteOptions(0)) 1153 if err != nil { 1154 t.Errorf("Error while deleting pod: %v", err) 1155 } 1156 err = wait.PollUntilContextTimeout(ctx, pollInterval, wait.ForeverTestTimeout, true, 1157 testutils.PodDeleted(ctx, cs, pod.Namespace, pod.Name)) 1158 if err != nil { 1159 t.Errorf("Error while waiting for pod to get deleted: %v", err) 1160 } 1161 } 1162 }) 1163 } 1164 } 1165 1166 // TestInterPodAffinityWithNamespaceSelector verifies that inter pod affinity with NamespaceSelector works as expected. 1167 func TestInterPodAffinityWithNamespaceSelector(t *testing.T) { 1168 podLabel := map[string]string{"service": "securityscan"} 1169 tests := []struct { 1170 name string 1171 pod *v1.Pod 1172 existingPod *v1.Pod 1173 fits bool 1174 errorType string 1175 }{ 1176 { 1177 name: "MatchingNamespaces", 1178 pod: &v1.Pod{ 1179 ObjectMeta: metav1.ObjectMeta{ 1180 Name: "pod-ns-selector", 1181 }, 1182 Spec: v1.PodSpec{ 1183 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 1184 Affinity: &v1.Affinity{ 1185 PodAffinity: &v1.PodAffinity{ 1186 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 1187 { 1188 LabelSelector: &metav1.LabelSelector{ 1189 MatchExpressions: []metav1.LabelSelectorRequirement{ 1190 { 1191 Key: "service", 1192 Operator: metav1.LabelSelectorOpIn, 1193 Values: []string{"securityscan"}, 1194 }, 1195 }, 1196 }, 1197 NamespaceSelector: &metav1.LabelSelector{ 1198 MatchExpressions: []metav1.LabelSelectorRequirement{ 1199 { 1200 Key: "team", 1201 Operator: metav1.LabelSelectorOpIn, 1202 Values: []string{"team1"}, 1203 }, 1204 }, 1205 }, 1206 TopologyKey: "region", 1207 }, 1208 }, 1209 }, 1210 }, 1211 }, 1212 }, 1213 existingPod: &v1.Pod{ 1214 ObjectMeta: metav1.ObjectMeta{ 1215 Name: "fakename2", 1216 Labels: podLabel, 1217 Namespace: "ns2", 1218 }, 1219 Spec: v1.PodSpec{ 1220 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 1221 }, 1222 }, 1223 fits: true, 1224 }, 1225 { 1226 name: "MismatchingNamespaces", 1227 pod: &v1.Pod{ 1228 ObjectMeta: metav1.ObjectMeta{ 1229 Name: "pod-ns-selector", 1230 }, 1231 Spec: v1.PodSpec{ 1232 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 1233 Affinity: &v1.Affinity{ 1234 PodAffinity: &v1.PodAffinity{ 1235 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 1236 { 1237 LabelSelector: &metav1.LabelSelector{ 1238 MatchExpressions: []metav1.LabelSelectorRequirement{ 1239 { 1240 Key: "service", 1241 Operator: metav1.LabelSelectorOpIn, 1242 Values: []string{"securityscan"}, 1243 }, 1244 }, 1245 }, 1246 NamespaceSelector: &metav1.LabelSelector{ 1247 MatchExpressions: []metav1.LabelSelectorRequirement{ 1248 { 1249 Key: "team", 1250 Operator: metav1.LabelSelectorOpIn, 1251 Values: []string{"team1"}, 1252 }, 1253 }, 1254 }, 1255 TopologyKey: "region", 1256 }, 1257 }, 1258 }, 1259 }, 1260 }, 1261 }, 1262 existingPod: &v1.Pod{ 1263 ObjectMeta: metav1.ObjectMeta{ 1264 Name: "fakename2", 1265 Labels: podLabel, 1266 Namespace: "ns3", 1267 }, 1268 Spec: v1.PodSpec{ 1269 Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}, 1270 }, 1271 }, 1272 fits: false, 1273 }, 1274 } 1275 1276 for _, test := range tests { 1277 t.Run(test.name, func(t *testing.T) { 1278 testCtx := initTest(t, "") 1279 1280 // Add a few nodes with labels 1281 nodes, err := createAndWaitForNodesInCache(testCtx, "testnode", st.MakeNode().Label("region", "r1").Label("zone", "z11"), 2) 1282 if err != nil { 1283 t.Fatal(err) 1284 } 1285 test.existingPod.Spec.NodeName = nodes[0].Name 1286 1287 cs := testCtx.ClientSet 1288 1289 if err := createNamespacesWithLabels(cs, []string{"ns1", "ns2"}, map[string]string{"team": "team1"}); err != nil { 1290 t.Fatal(err) 1291 } 1292 if err := createNamespacesWithLabels(cs, []string{"ns3"}, map[string]string{"team": "team2"}); err != nil { 1293 t.Fatal(err) 1294 } 1295 defaultNS := "ns1" 1296 1297 createdPod, err := cs.CoreV1().Pods(test.existingPod.Namespace).Create(testCtx.Ctx, test.existingPod, metav1.CreateOptions{}) 1298 if err != nil { 1299 t.Fatalf("Error while creating pod: %v", err) 1300 } 1301 err = wait.PollUntilContextTimeout(testCtx.Ctx, pollInterval, wait.ForeverTestTimeout, false, 1302 testutils.PodScheduled(cs, createdPod.Namespace, createdPod.Name)) 1303 if err != nil { 1304 t.Errorf("Error while creating pod: %v", err) 1305 } 1306 1307 if test.pod.Namespace == "" { 1308 test.pod.Namespace = defaultNS 1309 } 1310 1311 testPod, err := cs.CoreV1().Pods(test.pod.Namespace).Create(testCtx.Ctx, test.pod, metav1.CreateOptions{}) 1312 if err != nil { 1313 if !(test.errorType == "invalidPod" && apierrors.IsInvalid(err)) { 1314 t.Fatalf("Error while creating pod: %v", err) 1315 } 1316 } 1317 1318 if test.fits { 1319 err = wait.PollUntilContextTimeout(testCtx.Ctx, pollInterval, wait.ForeverTestTimeout, false, 1320 testutils.PodScheduled(cs, testPod.Namespace, testPod.Name)) 1321 } else { 1322 err = wait.PollUntilContextTimeout(testCtx.Ctx, pollInterval, wait.ForeverTestTimeout, false, 1323 podUnschedulable(cs, testPod.Namespace, testPod.Name)) 1324 } 1325 if err != nil { 1326 t.Errorf("Error while trying to fit a pod: %v", err) 1327 } 1328 err = cs.CoreV1().Pods(test.pod.Namespace).Delete(testCtx.Ctx, test.pod.Name, *metav1.NewDeleteOptions(0)) 1329 if err != nil { 1330 t.Errorf("Error while deleting pod: %v", err) 1331 } 1332 err = wait.PollUntilContextTimeout(testCtx.Ctx, pollInterval, wait.ForeverTestTimeout, true, 1333 testutils.PodDeleted(testCtx.Ctx, cs, testCtx.NS.Name, test.pod.Name)) 1334 if err != nil { 1335 t.Errorf("Error while waiting for pod to get deleted: %v", err) 1336 } 1337 err = cs.CoreV1().Pods(test.existingPod.Namespace).Delete(testCtx.Ctx, test.existingPod.Name, *metav1.NewDeleteOptions(0)) 1338 if err != nil { 1339 t.Errorf("Error while deleting pod: %v", err) 1340 } 1341 err = wait.PollUntilContextTimeout(testCtx.Ctx, pollInterval, wait.ForeverTestTimeout, true, 1342 testutils.PodDeleted(testCtx.Ctx, cs, test.existingPod.Namespace, test.existingPod.Name)) 1343 if err != nil { 1344 t.Errorf("Error while waiting for pod to get deleted: %v", err) 1345 } 1346 }) 1347 } 1348 } 1349 1350 // TestPodTopologySpreadFilter verifies that EvenPodsSpread predicate functions well. 1351 func TestPodTopologySpreadFilter(t *testing.T) { 1352 pause := imageutils.GetPauseImageName() 1353 // default nodes with labels "zone: zone-{0,1}" and "node: <node name>". 1354 defaultNodes := []*v1.Node{ 1355 st.MakeNode().Name("node-0").Label("node", "node-0").Label("zone", "zone-0").Obj(), 1356 st.MakeNode().Name("node-1").Label("node", "node-1").Label("zone", "zone-0").Obj(), 1357 st.MakeNode().Name("node-2").Label("node", "node-2").Label("zone", "zone-1").Obj(), 1358 st.MakeNode().Name("node-3").Label("node", "node-3").Label("zone", "zone-1").Obj(), 1359 } 1360 1361 tests := []struct { 1362 name string 1363 incomingPod *v1.Pod 1364 existingPods []*v1.Pod 1365 fits bool 1366 nodes []*v1.Node 1367 candidateNodes []string // nodes expected to schedule onto 1368 enableNodeInclusionPolicy bool 1369 enableMatchLabelKeys bool 1370 }{ 1371 // note: naming starts at index 0 1372 { 1373 name: "place pod on a 1/1/0/1 cluster with MaxSkew=1, node-2 is the only fit", 1374 incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). 1375 SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil). 1376 Obj(), 1377 existingPods: []*v1.Pod{ 1378 st.MakePod().Name("p0").Node("node-0").Label("foo", "").Container(pause).Obj(), 1379 st.MakePod().Name("p1").Node("node-1").Label("foo", "").Container(pause).Obj(), 1380 st.MakePod().Name("p3").Node("node-3").Label("foo", "").Container(pause).Obj(), 1381 }, 1382 fits: true, 1383 nodes: defaultNodes, 1384 candidateNodes: []string{"node-2"}, 1385 }, 1386 { 1387 name: "place pod on a 2/0/0/1 cluster with MaxSkew=2, node-{1,2,3} are good fits", 1388 incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). 1389 SpreadConstraint(2, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil). 1390 Obj(), 1391 existingPods: []*v1.Pod{ 1392 st.MakePod().Name("p0a").Node("node-0").Label("foo", "").Container(pause).Obj(), 1393 st.MakePod().Name("p0b").Node("node-0").Label("foo", "").Container(pause).Obj(), 1394 st.MakePod().Name("p3").Node("node-3").Label("foo", "").Container(pause).Obj(), 1395 }, 1396 fits: true, 1397 nodes: defaultNodes, 1398 candidateNodes: []string{"node-1", "node-2", "node-3"}, 1399 }, 1400 { 1401 name: "pod is required to be placed on zone0, so only node-1 fits", 1402 incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). 1403 NodeAffinityIn("zone", []string{"zone-0"}). 1404 SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil). 1405 Obj(), 1406 existingPods: []*v1.Pod{ 1407 st.MakePod().Name("p0").Node("node-0").Label("foo", "").Container(pause).Obj(), 1408 st.MakePod().Name("p3").Node("node-3").Label("foo", "").Container(pause).Obj(), 1409 }, 1410 fits: true, 1411 nodes: defaultNodes, 1412 candidateNodes: []string{"node-1"}, 1413 }, 1414 { 1415 name: "two constraints: pod can only be placed to zone-1/node-2", 1416 incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). 1417 SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil). 1418 SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil). 1419 Obj(), 1420 existingPods: []*v1.Pod{ 1421 st.MakePod().Name("p0").Node("node-0").Label("foo", "").Container(pause).Obj(), 1422 st.MakePod().Name("p1").Node("node-1").Label("foo", "").Container(pause).Obj(), 1423 st.MakePod().Name("p3a").Node("node-3").Label("foo", "").Container(pause).Obj(), 1424 st.MakePod().Name("p3b").Node("node-3").Label("foo", "").Container(pause).Obj(), 1425 }, 1426 fits: true, 1427 nodes: defaultNodes, 1428 candidateNodes: []string{"node-2"}, 1429 }, 1430 { 1431 name: "pod cannot be placed onto any node", 1432 incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). 1433 NodeAffinityNotIn("node", []string{"node-0"}). // mock a 3-node cluster 1434 SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil). 1435 SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil). 1436 Obj(), 1437 existingPods: []*v1.Pod{ 1438 st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(), 1439 st.MakePod().Name("p1b").Node("node-1").Label("foo", "").Container(pause).Obj(), 1440 st.MakePod().Name("p2a").Node("node-2").Label("foo", "").Container(pause).Obj(), 1441 st.MakePod().Name("p2b").Node("node-2").Label("foo", "").Container(pause).Obj(), 1442 st.MakePod().Name("p3").Node("node-3").Label("foo", "").Container(pause).Obj(), 1443 }, 1444 fits: false, 1445 nodes: defaultNodes, 1446 }, 1447 { 1448 name: "high priority pod can preempt others", 1449 incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).Priority(100). 1450 NodeAffinityNotIn("node", []string{"node-0"}). // mock a 3-node cluster 1451 SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil). 1452 SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil). 1453 Obj(), 1454 existingPods: []*v1.Pod{ 1455 st.MakePod().ZeroTerminationGracePeriod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(), 1456 st.MakePod().ZeroTerminationGracePeriod().Name("p1b").Node("node-1").Label("foo", "").Container(pause).Obj(), 1457 st.MakePod().ZeroTerminationGracePeriod().Name("p2a").Node("node-2").Label("foo", "").Container(pause).Obj(), 1458 st.MakePod().ZeroTerminationGracePeriod().Name("p2b").Node("node-2").Label("foo", "").Container(pause).Obj(), 1459 st.MakePod().ZeroTerminationGracePeriod().Name("p3").Node("node-3").Label("foo", "").Container(pause).Obj(), 1460 }, 1461 fits: true, 1462 nodes: defaultNodes, 1463 candidateNodes: []string{"node-1", "node-2", "node-3"}, 1464 }, 1465 { 1466 name: "pods spread across nodes as 2/2/1, maxSkew is 2, and the number of domains < minDomains, then the third node fits", 1467 incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). 1468 NodeAffinityNotIn("node", []string{"node-0"}). // mock a 3-node cluster 1469 SpreadConstraint( 1470 2, 1471 "node", 1472 hardSpread, 1473 st.MakeLabelSelector().Exists("foo").Obj(), 1474 pointer.Int32(4), // larger than the number of domains (= 3) 1475 nil, 1476 nil, 1477 nil, 1478 ). 1479 Obj(), 1480 existingPods: []*v1.Pod{ 1481 st.MakePod().ZeroTerminationGracePeriod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(), 1482 st.MakePod().ZeroTerminationGracePeriod().Name("p1b").Node("node-1").Label("foo", "").Container(pause).Obj(), 1483 st.MakePod().ZeroTerminationGracePeriod().Name("p2a").Node("node-2").Label("foo", "").Container(pause).Obj(), 1484 st.MakePod().ZeroTerminationGracePeriod().Name("p2b").Node("node-2").Label("foo", "").Container(pause).Obj(), 1485 st.MakePod().ZeroTerminationGracePeriod().Name("p3").Node("node-3").Label("foo", "").Container(pause).Obj(), 1486 }, 1487 fits: true, 1488 nodes: defaultNodes, 1489 candidateNodes: []string{"node-3"}, 1490 }, 1491 { 1492 name: "pods spread across nodes as 2/2/1, maxSkew is 2, and the number of domains > minDomains, then the all nodes fit", 1493 incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). 1494 NodeAffinityNotIn("node", []string{"node-0"}). // mock a 3-node cluster 1495 SpreadConstraint( 1496 2, 1497 "node", 1498 hardSpread, 1499 st.MakeLabelSelector().Exists("foo").Obj(), 1500 pointer.Int32(2), // smaller than the number of domains (= 3) 1501 nil, 1502 nil, 1503 nil, 1504 ). 1505 Obj(), 1506 existingPods: []*v1.Pod{ 1507 st.MakePod().ZeroTerminationGracePeriod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(), 1508 st.MakePod().ZeroTerminationGracePeriod().Name("p1b").Node("node-1").Label("foo", "").Container(pause).Obj(), 1509 st.MakePod().ZeroTerminationGracePeriod().Name("p2a").Node("node-2").Label("foo", "").Container(pause).Obj(), 1510 st.MakePod().ZeroTerminationGracePeriod().Name("p2b").Node("node-2").Label("foo", "").Container(pause).Obj(), 1511 st.MakePod().ZeroTerminationGracePeriod().Name("p3").Node("node-3").Label("foo", "").Container(pause).Obj(), 1512 }, 1513 fits: true, 1514 nodes: defaultNodes, 1515 candidateNodes: []string{"node-1", "node-2", "node-3"}, 1516 }, 1517 { 1518 name: "pods spread across zone as 2/1, maxSkew is 2 and the number of domains < minDomains, then the third and fourth nodes fit", 1519 incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). 1520 SpreadConstraint( 1521 2, 1522 "zone", 1523 v1.DoNotSchedule, 1524 st.MakeLabelSelector().Exists("foo").Obj(), 1525 pointer.Int32(3), // larger than the number of domains(2) 1526 nil, 1527 nil, 1528 nil, 1529 ).Obj(), 1530 existingPods: []*v1.Pod{ 1531 st.MakePod().Name("p1a").Node("node-0").Label("foo", "").Container(pause).Obj(), 1532 st.MakePod().Name("p2a").Node("node-1").Label("foo", "").Container(pause).Obj(), 1533 st.MakePod().Name("p3a").Node("node-2").Label("foo", "").Container(pause).Obj(), 1534 }, 1535 fits: true, 1536 nodes: defaultNodes, 1537 candidateNodes: []string{"node-2", "node-3"}, 1538 }, 1539 { 1540 name: "pods spread across zone as 2/1, maxSkew is 2 and the number of domains < minDomains, then the all nodes fit", 1541 incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). 1542 SpreadConstraint( 1543 2, 1544 "zone", 1545 v1.DoNotSchedule, 1546 st.MakeLabelSelector().Exists("foo").Obj(), 1547 pointer.Int32(1), // smaller than the number of domains(2) 1548 nil, 1549 nil, 1550 nil, 1551 ).Obj(), 1552 existingPods: []*v1.Pod{ 1553 st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(), 1554 st.MakePod().Name("p2a").Node("node-2").Label("foo", "").Container(pause).Obj(), 1555 st.MakePod().Name("p3a").Node("node-3").Label("foo", "").Container(pause).Obj(), 1556 }, 1557 fits: true, 1558 nodes: defaultNodes, 1559 candidateNodes: []string{"node-0", "node-1", "node-2", "node-3"}, 1560 }, 1561 { 1562 name: "NodeAffinityPolicy honored with labelSelectors, pods spread across zone as 2/1", 1563 incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). 1564 NodeSelector(map[string]string{"foo": ""}). 1565 SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil). 1566 Obj(), 1567 existingPods: []*v1.Pod{ 1568 st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(), 1569 st.MakePod().Name("p2a").Node("node-2").Label("foo", "").Container(pause).Obj(), 1570 st.MakePod().Name("p3a").Node("node-3").Label("foo", "").Container(pause).Obj(), 1571 st.MakePod().Name("p4a").Node("node-4").Label("foo", "").Container(pause).Obj(), 1572 }, 1573 fits: true, 1574 nodes: []*v1.Node{ 1575 st.MakeNode().Name("node-1").Label("node", "node-1").Label("zone", "zone-1").Label("foo", "").Obj(), 1576 st.MakeNode().Name("node-2").Label("node", "node-2").Label("zone", "zone-1").Label("foo", "").Obj(), 1577 st.MakeNode().Name("node-3").Label("node", "node-3").Label("zone", "zone-2").Obj(), 1578 st.MakeNode().Name("node-4").Label("node", "node-4").Label("zone", "zone-2").Label("foo", "").Obj(), 1579 }, 1580 candidateNodes: []string{"node-4"}, // node-3 is filtered out by NodeAffinity plugin 1581 enableNodeInclusionPolicy: true, 1582 }, 1583 { 1584 name: "NodeAffinityPolicy ignored with nodeAffinity, pods spread across zone as 1/~2~", 1585 incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). 1586 NodeAffinityIn("foo", []string{""}). 1587 SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, &ignorePolicy, nil, nil). 1588 Obj(), 1589 existingPods: []*v1.Pod{ 1590 st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(), 1591 st.MakePod().Name("p3a").Node("node-3").Label("foo", "").Container(pause).Obj(), 1592 st.MakePod().Name("p4a").Node("node-4").Label("foo", "").Container(pause).Obj(), 1593 }, 1594 fits: true, 1595 nodes: []*v1.Node{ 1596 st.MakeNode().Name("node-1").Label("node", "node-1").Label("zone", "zone-1").Label("foo", "").Obj(), 1597 st.MakeNode().Name("node-2").Label("node", "node-2").Label("zone", "zone-1").Label("foo", "").Obj(), 1598 st.MakeNode().Name("node-3").Label("node", "node-3").Label("zone", "zone-2").Obj(), 1599 st.MakeNode().Name("node-4").Label("node", "node-4").Label("zone", "zone-2").Label("foo", "").Obj(), 1600 }, 1601 candidateNodes: []string{"node-1", "node-2"}, 1602 enableNodeInclusionPolicy: true, 1603 }, 1604 { 1605 name: "NodeTaintsPolicy honored, pods spread across zone as 2/1", 1606 incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). 1607 SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, &honorPolicy, nil). 1608 Obj(), 1609 existingPods: []*v1.Pod{ 1610 st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(), 1611 st.MakePod().Name("p2a").Node("node-2").Label("foo", "").Container(pause).Obj(), 1612 st.MakePod().Name("p3a").Node("node-3").Label("foo", "").Container(pause).Obj(), 1613 st.MakePod().Name("p4a").Node("node-4").Label("foo", "").Container(pause).Obj(), 1614 }, 1615 fits: true, 1616 nodes: []*v1.Node{ 1617 st.MakeNode().Name("node-1").Label("node", "node-1").Label("zone", "zone-1").Label("foo", "").Obj(), 1618 st.MakeNode().Name("node-2").Label("node", "node-2").Label("zone", "zone-1").Label("foo", "").Obj(), 1619 st.MakeNode().Name("node-3").Label("node", "node-3").Label("zone", "zone-2").Taints(taints).Obj(), 1620 st.MakeNode().Name("node-4").Label("node", "node-4").Label("zone", "zone-2").Label("foo", "").Obj(), 1621 }, 1622 candidateNodes: []string{"node-4"}, // node-3 is filtered out by TaintToleration plugin 1623 enableNodeInclusionPolicy: true, 1624 }, 1625 { 1626 name: "NodeTaintsPolicy ignored, pods spread across zone as 2/2", 1627 incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). 1628 SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil). 1629 Obj(), 1630 existingPods: []*v1.Pod{ 1631 st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(), 1632 st.MakePod().Name("p2a").Node("node-2").Label("foo", "").Container(pause).Obj(), 1633 st.MakePod().Name("p3a").Node("node-3").Label("foo", "").Container(pause).Obj(), 1634 st.MakePod().Name("p4a").Node("node-4").Label("foo", "").Container(pause).Obj(), 1635 }, 1636 fits: true, 1637 nodes: []*v1.Node{ 1638 st.MakeNode().Name("node-1").Label("node", "node-1").Label("zone", "zone-1").Label("foo", "").Obj(), 1639 st.MakeNode().Name("node-2").Label("node", "node-2").Label("zone", "zone-1").Label("foo", "").Obj(), 1640 st.MakeNode().Name("node-3").Label("node", "node-3").Label("zone", "zone-2").Taints(taints).Obj(), 1641 st.MakeNode().Name("node-4").Label("node", "node-4").Label("zone", "zone-2").Label("foo", "").Obj(), 1642 }, 1643 candidateNodes: []string{"node-1", "node-2", "node-4"}, // node-3 is filtered out by TaintToleration plugin 1644 enableNodeInclusionPolicy: true, 1645 }, 1646 { 1647 // 1. to fulfil "zone" constraint, pods spread across zones as 2/1 1648 // 2. to fulfil "node" constraint, pods spread across zones as 1/1/~0~/1 1649 // intersection of (1) and (2) returns node-4 as node-3 is filtered out by NodeAffinity plugin. 1650 name: "two node inclusion Constraints, zone: honor/ignore, node: honor/ignore", 1651 incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). 1652 NodeSelector(map[string]string{"foo": ""}). 1653 SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil). 1654 SpreadConstraint(1, "node", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil). 1655 Obj(), 1656 existingPods: []*v1.Pod{ 1657 st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(), 1658 st.MakePod().Name("p2a").Node("node-2").Label("foo", "").Container(pause).Obj(), 1659 st.MakePod().Name("p3a").Node("node-3").Label("foo", "").Container(pause).Obj(), 1660 st.MakePod().Name("p4a").Node("node-4").Label("foo", "").Container(pause).Obj(), 1661 }, 1662 fits: true, 1663 nodes: []*v1.Node{ 1664 st.MakeNode().Name("node-1").Label("node", "node-1").Label("zone", "zone-1").Label("foo", "").Obj(), 1665 st.MakeNode().Name("node-2").Label("node", "node-2").Label("zone", "zone-1").Label("foo", "").Taints(taints).Obj(), 1666 st.MakeNode().Name("node-3").Label("node", "node-3").Label("zone", "zone-2").Obj(), 1667 st.MakeNode().Name("node-4").Label("node", "node-4").Label("zone", "zone-2").Label("foo", "").Obj(), 1668 }, 1669 candidateNodes: []string{"node-4"}, 1670 enableNodeInclusionPolicy: true, 1671 }, 1672 { 1673 // 1. to fulfil "zone" constraint, pods spread across zones as 2/1 1674 // 2. to fulfil "node" constraint, pods spread across zones as 1/1/~0~/1 1675 // intersection of (1) and (2) returns node-4 as node-3 is filtered out by NodeAffinity plugin 1676 name: "feature gate disabled, two node inclusion Constraints, zone: honor/ignore, node: honor/ignore", 1677 incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). 1678 NodeSelector(map[string]string{"foo": ""}). 1679 SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil). 1680 SpreadConstraint(1, "node", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil). 1681 Obj(), 1682 existingPods: []*v1.Pod{ 1683 st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(), 1684 st.MakePod().Name("p2a").Node("node-2").Label("foo", "").Container(pause).Obj(), 1685 st.MakePod().Name("p3a").Node("node-3").Label("foo", "").Container(pause).Obj(), 1686 st.MakePod().Name("p4a").Node("node-4").Label("foo", "").Container(pause).Obj(), 1687 }, 1688 fits: true, 1689 nodes: []*v1.Node{ 1690 st.MakeNode().Name("node-1").Label("node", "node-1").Label("zone", "zone-1").Label("foo", "").Obj(), 1691 st.MakeNode().Name("node-2").Label("node", "node-2").Label("zone", "zone-1").Label("foo", "").Taints(taints).Obj(), 1692 st.MakeNode().Name("node-3").Label("node", "node-3").Label("zone", "zone-2").Obj(), 1693 st.MakeNode().Name("node-4").Label("node", "node-4").Label("zone", "zone-2").Label("foo", "").Obj(), 1694 }, 1695 candidateNodes: []string{"node-4"}, 1696 enableNodeInclusionPolicy: false, 1697 }, 1698 { 1699 // 1. to fulfil "zone" constraint, pods spread across zones as 2/2 1700 // 2. to fulfil "node" constraint, pods spread across zones as 1/~0~/~0~/1 1701 // intersection of (1) and (2) returns node-1 and node-4 as node-2, node-3 are filtered out by plugins 1702 name: "two node inclusion Constraints, zone: ignore/ignore, node: honor/honor", 1703 incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). 1704 NodeSelector(map[string]string{"foo": ""}). 1705 SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, &ignorePolicy, nil, nil). 1706 SpreadConstraint(1, "node", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, &honorPolicy, nil). 1707 Obj(), 1708 existingPods: []*v1.Pod{ 1709 st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(), 1710 st.MakePod().Name("p2a").Node("node-2").Label("foo", "").Container(pause).Obj(), 1711 st.MakePod().Name("p3a").Node("node-3").Label("foo", "").Container(pause).Obj(), 1712 st.MakePod().Name("p4a").Node("node-4").Label("foo", "").Container(pause).Obj(), 1713 }, 1714 fits: true, 1715 nodes: []*v1.Node{ 1716 st.MakeNode().Name("node-1").Label("node", "node-1").Label("zone", "zone-1").Label("foo", "").Obj(), 1717 st.MakeNode().Name("node-2").Label("node", "node-2").Label("zone", "zone-1").Label("foo", "").Taints(taints).Obj(), 1718 st.MakeNode().Name("node-3").Label("node", "node-3").Label("zone", "zone-2").Obj(), 1719 st.MakeNode().Name("node-4").Label("node", "node-4").Label("zone", "zone-2").Label("foo", "").Obj(), 1720 }, 1721 candidateNodes: []string{"node-1", "node-4"}, 1722 enableNodeInclusionPolicy: true, 1723 }, 1724 { 1725 name: "matchLabelKeys ignored when feature gate disabled, pods spread across zone as 2/1", 1726 incomingPod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").Container(pause). 1727 SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, []string{"bar"}). 1728 Obj(), 1729 existingPods: []*v1.Pod{ 1730 st.MakePod().Name("p1a").Node("node-0").Label("foo", "").Container(pause).Obj(), 1731 st.MakePod().Name("p2a").Node("node-1").Label("foo", "").Container(pause).Obj(), 1732 st.MakePod().Name("p3a").Node("node-2").Label("foo", "").Label("bar", "").Container(pause).Obj(), 1733 }, 1734 fits: true, 1735 nodes: defaultNodes, 1736 candidateNodes: []string{"node-2", "node-3"}, 1737 enableMatchLabelKeys: false, 1738 }, 1739 { 1740 name: "matchLabelKeys ANDed with LabelSelector when LabelSelector isn't empty, pods spread across zone as 0/1", 1741 incomingPod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").Container(pause). 1742 SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, []string{"bar"}). 1743 Obj(), 1744 existingPods: []*v1.Pod{ 1745 st.MakePod().Name("p1a").Node("node-0").Label("foo", "").Container(pause).Obj(), 1746 st.MakePod().Name("p2a").Node("node-1").Label("foo", "").Container(pause).Obj(), 1747 st.MakePod().Name("p3a").Node("node-2").Label("foo", "").Label("bar", "").Container(pause).Obj(), 1748 }, 1749 fits: true, 1750 nodes: defaultNodes, 1751 candidateNodes: []string{"node-0", "node-1"}, 1752 enableMatchLabelKeys: true, 1753 }, 1754 { 1755 name: "matchLabelKeys ANDed with LabelSelector when LabelSelector is empty, pods spread across zone as 2/1", 1756 incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). 1757 SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Obj(), nil, nil, nil, []string{"foo"}). 1758 Obj(), 1759 existingPods: []*v1.Pod{ 1760 st.MakePod().Name("p1a").Node("node-0").Label("foo", "").Container(pause).Obj(), 1761 st.MakePod().Name("p2a").Node("node-1").Label("foo", "").Container(pause).Obj(), 1762 st.MakePod().Name("p3a").Node("node-2").Label("foo", "").Container(pause).Obj(), 1763 }, 1764 fits: true, 1765 nodes: defaultNodes, 1766 candidateNodes: []string{"node-2", "node-3"}, 1767 enableMatchLabelKeys: true, 1768 }, 1769 } 1770 for _, tt := range tests { 1771 t.Run(tt.name, func(t *testing.T) { 1772 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.NodeInclusionPolicyInPodTopologySpread, tt.enableNodeInclusionPolicy) 1773 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MatchLabelKeysInPodTopologySpread, tt.enableMatchLabelKeys) 1774 1775 testCtx := initTest(t, "pts-predicate") 1776 cs := testCtx.ClientSet 1777 ns := testCtx.NS.Name 1778 1779 for i := range tt.nodes { 1780 if _, err := createNode(cs, tt.nodes[i]); err != nil { 1781 t.Fatalf("Cannot create node: %v", err) 1782 } 1783 } 1784 1785 // set namespace to pods 1786 for i := range tt.existingPods { 1787 tt.existingPods[i].SetNamespace(ns) 1788 } 1789 tt.incomingPod.SetNamespace(ns) 1790 1791 allPods := append(tt.existingPods, tt.incomingPod) 1792 defer testutils.CleanupPods(testCtx.Ctx, cs, t, allPods) 1793 1794 for _, pod := range tt.existingPods { 1795 createdPod, err := cs.CoreV1().Pods(pod.Namespace).Create(testCtx.Ctx, pod, metav1.CreateOptions{}) 1796 if err != nil { 1797 t.Fatalf("Error while creating pod during test: %v", err) 1798 } 1799 err = wait.PollUntilContextTimeout(testCtx.Ctx, pollInterval, wait.ForeverTestTimeout, false, 1800 testutils.PodScheduled(cs, createdPod.Namespace, createdPod.Name)) 1801 if err != nil { 1802 t.Errorf("Error while waiting for pod during test: %v", err) 1803 } 1804 } 1805 testPod, err := cs.CoreV1().Pods(tt.incomingPod.Namespace).Create(testCtx.Ctx, tt.incomingPod, metav1.CreateOptions{}) 1806 if err != nil { 1807 t.Fatalf("Error while creating pod during test: %v", err) 1808 } 1809 1810 if tt.fits { 1811 err = wait.PollUntilContextTimeout(testCtx.Ctx, pollInterval, wait.ForeverTestTimeout, false, 1812 podScheduledIn(cs, testPod.Namespace, testPod.Name, tt.candidateNodes)) 1813 } else { 1814 err = wait.PollUntilContextTimeout(testCtx.Ctx, pollInterval, wait.ForeverTestTimeout, false, 1815 podUnschedulable(cs, testPod.Namespace, testPod.Name)) 1816 } 1817 if err != nil { 1818 t.Errorf("Test Failed: %v", err) 1819 } 1820 }) 1821 } 1822 } 1823 1824 var ( 1825 hardSpread = v1.DoNotSchedule 1826 ) 1827 1828 func TestUnschedulablePodBecomesSchedulable(t *testing.T) { 1829 tests := []struct { 1830 name string 1831 init func(kubernetes.Interface, string) error 1832 pod *testutils.PausePodConfig 1833 update func(kubernetes.Interface, string) error 1834 }{ 1835 { 1836 name: "node gets added", 1837 pod: &testutils.PausePodConfig{ 1838 Name: "pod-1", 1839 }, 1840 update: func(cs kubernetes.Interface, _ string) error { 1841 _, err := createNode(cs, st.MakeNode().Name("node-added").Obj()) 1842 if err != nil { 1843 return fmt.Errorf("cannot create node: %v", err) 1844 } 1845 return nil 1846 }, 1847 }, 1848 { 1849 name: "node gets taint removed", 1850 init: func(cs kubernetes.Interface, _ string) error { 1851 node, err := createNode(cs, st.MakeNode().Name("node-tainted").Obj()) 1852 if err != nil { 1853 return fmt.Errorf("cannot create node: %v", err) 1854 } 1855 taint := v1.Taint{Key: "test", Value: "test", Effect: v1.TaintEffectNoSchedule} 1856 if err := testutils.AddTaintToNode(cs, node.Name, taint); err != nil { 1857 return fmt.Errorf("cannot add taint to node: %v", err) 1858 } 1859 return nil 1860 }, 1861 pod: &testutils.PausePodConfig{ 1862 Name: "pod-1", 1863 }, 1864 update: func(cs kubernetes.Interface, _ string) error { 1865 taint := v1.Taint{Key: "test", Value: "test", Effect: v1.TaintEffectNoSchedule} 1866 if err := testutils.RemoveTaintOffNode(cs, "node-tainted", taint); err != nil { 1867 return fmt.Errorf("cannot remove taint off node: %v", err) 1868 } 1869 return nil 1870 }, 1871 }, 1872 { 1873 name: "other pod gets deleted", 1874 init: func(cs kubernetes.Interface, ns string) error { 1875 nodeObject := st.MakeNode().Name("node-scheduler-integration-test").Capacity(map[v1.ResourceName]string{v1.ResourcePods: "1"}).Obj() 1876 _, err := createNode(cs, nodeObject) 1877 if err != nil { 1878 return fmt.Errorf("cannot create node: %v", err) 1879 } 1880 _, err = createPausePod(cs, initPausePod(&testutils.PausePodConfig{Name: "pod-to-be-deleted", Namespace: ns})) 1881 if err != nil { 1882 return fmt.Errorf("cannot create pod: %v", err) 1883 } 1884 return nil 1885 }, 1886 pod: &testutils.PausePodConfig{ 1887 Name: "pod-1", 1888 }, 1889 update: func(cs kubernetes.Interface, ns string) error { 1890 if err := deletePod(cs, "pod-to-be-deleted", ns); err != nil { 1891 return fmt.Errorf("cannot delete pod: %v", err) 1892 } 1893 return nil 1894 }, 1895 }, 1896 { 1897 name: "pod with pod-affinity gets added", 1898 init: func(cs kubernetes.Interface, _ string) error { 1899 _, err := createNode(cs, st.MakeNode().Name("node-1").Label("region", "test").Obj()) 1900 if err != nil { 1901 return fmt.Errorf("cannot create node: %v", err) 1902 } 1903 return nil 1904 }, 1905 pod: &testutils.PausePodConfig{ 1906 Name: "pod-1", 1907 Affinity: &v1.Affinity{ 1908 PodAffinity: &v1.PodAffinity{ 1909 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 1910 { 1911 LabelSelector: &metav1.LabelSelector{ 1912 MatchLabels: map[string]string{ 1913 "pod-with-affinity": "true", 1914 }, 1915 }, 1916 TopologyKey: "region", 1917 }, 1918 }, 1919 }, 1920 }, 1921 }, 1922 update: func(cs kubernetes.Interface, ns string) error { 1923 podConfig := &testutils.PausePodConfig{ 1924 Name: "pod-with-affinity", 1925 Namespace: ns, 1926 Labels: map[string]string{ 1927 "pod-with-affinity": "true", 1928 }, 1929 } 1930 if _, err := createPausePod(cs, initPausePod(podConfig)); err != nil { 1931 return fmt.Errorf("cannot create pod: %v", err) 1932 } 1933 return nil 1934 }, 1935 }, 1936 { 1937 name: "scheduled pod gets updated to match affinity", 1938 init: func(cs kubernetes.Interface, ns string) error { 1939 _, err := createNode(cs, st.MakeNode().Name("node-1").Label("region", "test").Obj()) 1940 if err != nil { 1941 return fmt.Errorf("cannot create node: %v", err) 1942 } 1943 if _, err := createPausePod(cs, initPausePod(&testutils.PausePodConfig{Name: "pod-to-be-updated", Namespace: ns})); err != nil { 1944 return fmt.Errorf("cannot create pod: %v", err) 1945 } 1946 return nil 1947 }, 1948 pod: &testutils.PausePodConfig{ 1949 Name: "pod-1", 1950 Affinity: &v1.Affinity{ 1951 PodAffinity: &v1.PodAffinity{ 1952 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 1953 { 1954 LabelSelector: &metav1.LabelSelector{ 1955 MatchLabels: map[string]string{ 1956 "pod-with-affinity": "true", 1957 }, 1958 }, 1959 TopologyKey: "region", 1960 }, 1961 }, 1962 }, 1963 }, 1964 }, 1965 update: func(cs kubernetes.Interface, ns string) error { 1966 pod, err := getPod(cs, "pod-to-be-updated", ns) 1967 if err != nil { 1968 return fmt.Errorf("cannot get pod: %v", err) 1969 } 1970 pod.Labels = map[string]string{"pod-with-affinity": "true"} 1971 if _, err := cs.CoreV1().Pods(pod.Namespace).Update(context.TODO(), pod, metav1.UpdateOptions{}); err != nil { 1972 return fmt.Errorf("cannot update pod: %v", err) 1973 } 1974 return nil 1975 }, 1976 }, 1977 { 1978 name: "scheduled pod uses read-write-once-pod pvc", 1979 init: func(cs kubernetes.Interface, ns string) error { 1980 _, err := createNode(cs, st.MakeNode().Name("node").Obj()) 1981 if err != nil { 1982 return fmt.Errorf("cannot create node: %v", err) 1983 } 1984 1985 storage := v1.VolumeResourceRequirements{Requests: v1.ResourceList{v1.ResourceStorage: resource.MustParse("1Mi")}} 1986 volType := v1.HostPathDirectoryOrCreate 1987 pv, err := testutils.CreatePV(cs, st.MakePersistentVolume(). 1988 Name("pv-with-read-write-once-pod"). 1989 AccessModes([]v1.PersistentVolumeAccessMode{v1.ReadWriteOncePod}). 1990 Capacity(storage.Requests). 1991 HostPathVolumeSource(&v1.HostPathVolumeSource{Path: "/mnt", Type: &volType}). 1992 Obj()) 1993 if err != nil { 1994 return fmt.Errorf("cannot create pv: %v", err) 1995 } 1996 pvc, err := testutils.CreatePVC(cs, st.MakePersistentVolumeClaim(). 1997 Name("pvc-with-read-write-once-pod"). 1998 Namespace(ns). 1999 // Annotation and volume name required for PVC to be considered bound. 2000 Annotation(volume.AnnBindCompleted, "true"). 2001 VolumeName(pv.Name). 2002 AccessModes([]v1.PersistentVolumeAccessMode{v1.ReadWriteOncePod}). 2003 Resources(storage). 2004 Obj()) 2005 if err != nil { 2006 return fmt.Errorf("cannot create pvc: %v", err) 2007 } 2008 2009 pod := initPausePod(&testutils.PausePodConfig{ 2010 Name: "pod-to-be-deleted", 2011 Namespace: ns, 2012 Volumes: []v1.Volume{{ 2013 Name: "volume", 2014 VolumeSource: v1.VolumeSource{ 2015 PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ 2016 ClaimName: pvc.Name, 2017 }, 2018 }, 2019 }}, 2020 }) 2021 if _, err := createPausePod(cs, pod); err != nil { 2022 return fmt.Errorf("cannot create pod: %v", err) 2023 } 2024 return nil 2025 }, 2026 pod: &testutils.PausePodConfig{ 2027 Name: "pod-to-take-over-pvc", 2028 Volumes: []v1.Volume{{ 2029 Name: "volume", 2030 VolumeSource: v1.VolumeSource{ 2031 PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ 2032 ClaimName: "pvc-with-read-write-once-pod", 2033 }, 2034 }, 2035 }}, 2036 }, 2037 update: func(cs kubernetes.Interface, ns string) error { 2038 return deletePod(cs, "pod-to-be-deleted", ns) 2039 }, 2040 }, 2041 { 2042 name: "pod with pvc has node-affinity to non-existent/illegal nodes", 2043 init: func(cs kubernetes.Interface, ns string) error { 2044 storage := v1.VolumeResourceRequirements{Requests: v1.ResourceList{v1.ResourceStorage: resource.MustParse("1Mi")}} 2045 volType := v1.HostPathDirectoryOrCreate 2046 pv, err := testutils.CreatePV(cs, st.MakePersistentVolume(). 2047 Name("pv-has-non-existent-nodes"). 2048 AccessModes([]v1.PersistentVolumeAccessMode{v1.ReadWriteOncePod}). 2049 Capacity(storage.Requests). 2050 HostPathVolumeSource(&v1.HostPathVolumeSource{Path: "/tmp", Type: &volType}). 2051 NodeAffinityIn("kubernetes.io/hostname", []string{"node-available", "non-existing"}). // one node exist, one doesn't 2052 Obj()) 2053 if err != nil { 2054 return fmt.Errorf("cannot create pv: %w", err) 2055 } 2056 _, err = testutils.CreatePVC(cs, st.MakePersistentVolumeClaim(). 2057 Name("pvc-has-non-existent-nodes"). 2058 Namespace(ns). 2059 Annotation(volume.AnnBindCompleted, "true"). 2060 VolumeName(pv.Name). 2061 AccessModes([]v1.PersistentVolumeAccessMode{v1.ReadWriteOncePod}). 2062 Resources(storage). 2063 Obj()) 2064 if err != nil { 2065 return fmt.Errorf("cannot create pvc: %w", err) 2066 } 2067 return nil 2068 }, 2069 pod: &testutils.PausePodConfig{ 2070 Name: "pod-with-pvc-has-non-existent-nodes", 2071 Volumes: []v1.Volume{{ 2072 Name: "volume", 2073 VolumeSource: v1.VolumeSource{ 2074 PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ 2075 ClaimName: "pvc-has-non-existent-nodes", 2076 }, 2077 }, 2078 }}, 2079 }, 2080 update: func(cs kubernetes.Interface, ns string) error { 2081 _, err := createNode(cs, st.MakeNode().Label("kubernetes.io/hostname", "node-available").Name("node-available").Obj()) 2082 if err != nil { 2083 return fmt.Errorf("cannot create node: %w", err) 2084 } 2085 return nil 2086 }, 2087 }, 2088 { 2089 name: "pod with pvc got scheduled after node updated it's label", 2090 init: func(cs kubernetes.Interface, ns string) error { 2091 _, err := createNode(cs, st.MakeNode().Label("foo", "foo").Name("node-foo").Obj()) 2092 if err != nil { 2093 return fmt.Errorf("cannot create node: %w", err) 2094 } 2095 storage := v1.VolumeResourceRequirements{Requests: v1.ResourceList{v1.ResourceStorage: resource.MustParse("1Mi")}} 2096 volType := v1.HostPathDirectoryOrCreate 2097 pv, err := testutils.CreatePV(cs, st.MakePersistentVolume(). 2098 Name("pv-foo"). 2099 AccessModes([]v1.PersistentVolumeAccessMode{v1.ReadWriteOncePod}). 2100 Capacity(storage.Requests). 2101 HostPathVolumeSource(&v1.HostPathVolumeSource{Path: "/tmp", Type: &volType}). 2102 NodeAffinityIn("foo", []string{"bar"}). 2103 Obj()) 2104 if err != nil { 2105 return fmt.Errorf("cannot create pv: %w", err) 2106 } 2107 _, err = testutils.CreatePVC(cs, st.MakePersistentVolumeClaim(). 2108 Name("pvc-foo"). 2109 Namespace(ns). 2110 Annotation(volume.AnnBindCompleted, "true"). 2111 VolumeName(pv.Name). 2112 AccessModes([]v1.PersistentVolumeAccessMode{v1.ReadWriteOncePod}). 2113 Resources(storage). 2114 Obj()) 2115 if err != nil { 2116 return fmt.Errorf("cannot create pvc: %w", err) 2117 } 2118 return nil 2119 }, 2120 pod: &testutils.PausePodConfig{ 2121 Name: "pod-with-pvc-foo", 2122 Volumes: []v1.Volume{{ 2123 Name: "volume", 2124 VolumeSource: v1.VolumeSource{ 2125 PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ 2126 ClaimName: "pvc-foo", 2127 }, 2128 }, 2129 }}, 2130 }, 2131 update: func(cs kubernetes.Interface, ns string) error { 2132 _, err := updateNode(cs, &v1.Node{ 2133 ObjectMeta: metav1.ObjectMeta{ 2134 Name: "node-foo", 2135 Labels: map[string]string{ 2136 "foo": "bar", 2137 }, 2138 }, 2139 }) 2140 if err != nil { 2141 return fmt.Errorf("cannot update node: %w", err) 2142 } 2143 return nil 2144 }, 2145 }, 2146 } 2147 for _, tt := range tests { 2148 t.Run(tt.name, func(t *testing.T) { 2149 testCtx := initTest(t, "scheduler-informer") 2150 2151 if tt.init != nil { 2152 if err := tt.init(testCtx.ClientSet, testCtx.NS.Name); err != nil { 2153 t.Fatal(err) 2154 } 2155 } 2156 tt.pod.Namespace = testCtx.NS.Name 2157 pod, err := createPausePod(testCtx.ClientSet, initPausePod(tt.pod)) 2158 if err != nil { 2159 t.Fatal(err) 2160 } 2161 if err := waitForPodUnschedulable(testCtx.ClientSet, pod); err != nil { 2162 t.Errorf("Pod %v got scheduled: %v", pod.Name, err) 2163 } 2164 if err := tt.update(testCtx.ClientSet, testCtx.NS.Name); err != nil { 2165 t.Fatal(err) 2166 } 2167 if err := testutils.WaitForPodToSchedule(testCtx.ClientSet, pod); err != nil { 2168 t.Errorf("Pod %v was not scheduled: %v", pod.Name, err) 2169 } 2170 // Make sure pending queue is empty. 2171 pendingPods, s := testCtx.Scheduler.SchedulingQueue.PendingPods() 2172 if len(pendingPods) != 0 { 2173 t.Errorf("pending pods queue is not empty, size is: %d, summary is: %s", len(pendingPods), s) 2174 } 2175 }) 2176 } 2177 } 2178 2179 // TestPodAffinityMatchLabelKeyEnablement tests the Pod is correctly mutated by MatchLabelKeysInPodAffinity feature, 2180 // even if turing the feature gate enabled or disabled. 2181 func TestPodAffinityMatchLabelKeyEnablement(t *testing.T) { 2182 // enable the feature gate 2183 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MatchLabelKeysInPodAffinity, true) 2184 testCtx := initTest(t, "matchlabelkey") 2185 2186 pod := &v1.Pod{ 2187 ObjectMeta: metav1.ObjectMeta{ 2188 GenerateName: "test", 2189 Namespace: testCtx.NS.Name, 2190 Labels: map[string]string{"foo": "", "bar": "a"}, 2191 }, 2192 Spec: v1.PodSpec{ 2193 Containers: []v1.Container{ 2194 { 2195 Name: "container", 2196 Image: imageutils.GetPauseImageName(), 2197 Resources: v1.ResourceRequirements{ 2198 Requests: v1.ResourceList{ 2199 v1.ResourceMemory: resource.MustParse("1G"), 2200 }, 2201 }, 2202 }, 2203 }, 2204 Affinity: &v1.Affinity{ 2205 PodAffinity: &v1.PodAffinity{ 2206 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 2207 { 2208 TopologyKey: "node", 2209 LabelSelector: &metav1.LabelSelector{ 2210 MatchExpressions: []metav1.LabelSelectorRequirement{ 2211 { 2212 Key: "foo", 2213 Operator: metav1.LabelSelectorOpExists, 2214 }, 2215 }, 2216 }, 2217 MatchLabelKeys: []string{"bar"}, 2218 }, 2219 }, 2220 }, 2221 }, 2222 }, 2223 } 2224 expectedLabelSelector := &metav1.LabelSelector{ 2225 MatchExpressions: []metav1.LabelSelectorRequirement{ 2226 { 2227 Key: "foo", 2228 Operator: metav1.LabelSelectorOpExists, 2229 }, 2230 { 2231 Key: "bar", 2232 Operator: metav1.LabelSelectorOpIn, 2233 Values: []string{"a"}, 2234 }, 2235 }, 2236 } 2237 2238 p1, err := testCtx.ClientSet.CoreV1().Pods(testCtx.NS.Name).Create(testCtx.Ctx, pod, metav1.CreateOptions{}) 2239 if err != nil { 2240 t.Fatalf("Error while creating pod during test: %v", err) 2241 } 2242 2243 // check the pod has the expected label selector. 2244 gotpod, err := testCtx.ClientSet.CoreV1().Pods(testCtx.NS.Name).Get(testCtx.Ctx, p1.Name, metav1.GetOptions{}) 2245 if err != nil { 2246 t.Fatalf("Error while getting pod during test: %v", err) 2247 } 2248 2249 // the label selector should be changed from the original one because the feature gate is enabled. 2250 if d := cmp.Diff(gotpod.Spec.Affinity.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution[0].LabelSelector, expectedLabelSelector); d != "" { 2251 t.Fatalf("Pod %v has wrong label selector: diff = \n%v", p1.Name, d) 2252 } 2253 2254 // disable the feature gate. 2255 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MatchLabelKeysInPodAffinity, false) 2256 2257 p2, err := testCtx.ClientSet.CoreV1().Pods(testCtx.NS.Name).Create(testCtx.Ctx, pod, metav1.CreateOptions{}) 2258 if err != nil { 2259 t.Fatalf("Error while creating pod during test: %v", err) 2260 } 2261 2262 // check the pod has the expected label selector. 2263 gotpod, err = testCtx.ClientSet.CoreV1().Pods(testCtx.NS.Name).Get(testCtx.Ctx, p2.Name, metav1.GetOptions{}) 2264 if err != nil { 2265 t.Fatalf("Error while getting pod during test: %v", err) 2266 } 2267 2268 // the label selector should be the same as the original one because the feature gate is disabled. 2269 if d := cmp.Diff(gotpod.Spec.Affinity.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution[0].LabelSelector, pod.Spec.Affinity.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution[0].LabelSelector); d != "" { 2270 t.Fatalf("Pod %v has wrong label selector: diff = \n%v", p2.Name, d) 2271 } 2272 2273 // check the pod, which was created when the feature gate is enabled, still has the expected label selector. 2274 gotpod, err = testCtx.ClientSet.CoreV1().Pods(testCtx.NS.Name).Get(testCtx.Ctx, p1.Name, metav1.GetOptions{}) 2275 if err != nil { 2276 t.Fatalf("Error while getting pod during test: %v", err) 2277 } 2278 2279 // the label selector should be changed from the original one because the feature gate is enabled. 2280 if d := cmp.Diff(gotpod.Spec.Affinity.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution[0].LabelSelector, expectedLabelSelector); d != "" { 2281 t.Fatalf("Pod %v has wrong label selector: diff = \n%v", p1.Name, d) 2282 } 2283 2284 // Again, enable the feature gate. 2285 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MatchLabelKeysInPodAffinity, true) 2286 2287 p3, err := testCtx.ClientSet.CoreV1().Pods(testCtx.NS.Name).Create(testCtx.Ctx, pod, metav1.CreateOptions{}) 2288 if err != nil { 2289 t.Fatalf("Error while creating pod during test: %v", err) 2290 } 2291 2292 // check the pod has the expected label selector. 2293 gotpod, err = testCtx.ClientSet.CoreV1().Pods(testCtx.NS.Name).Get(testCtx.Ctx, p3.Name, metav1.GetOptions{}) 2294 if err != nil { 2295 t.Fatalf("Error while getting pod during test: %v", err) 2296 } 2297 2298 // the label selector should be changed from the original one because the feature gate is enabled. 2299 if d := cmp.Diff(gotpod.Spec.Affinity.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution[0].LabelSelector, expectedLabelSelector); d != "" { 2300 t.Fatalf("Pod %v has wrong label selector: diff = \n%v", p1.Name, d) 2301 } 2302 2303 // check the pod has the expected label selector. 2304 gotpod, err = testCtx.ClientSet.CoreV1().Pods(testCtx.NS.Name).Get(testCtx.Ctx, p2.Name, metav1.GetOptions{}) 2305 if err != nil { 2306 t.Fatalf("Error while getting pod during test: %v", err) 2307 } 2308 2309 // the label selector shouldn't get changed because the feature gate was disabled at its creation. 2310 // Even if the feature gate is enabled now, matchLabelKeys don't get applied to the pod. 2311 // (it's only handled when the pod is created) 2312 if d := cmp.Diff(gotpod.Spec.Affinity.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution[0].LabelSelector, pod.Spec.Affinity.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution[0].LabelSelector); d != "" { 2313 t.Fatalf("Pod %v has wrong label selector: diff = \n%v", p2.Name, d) 2314 } 2315 2316 }