k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/e2e/scheduling/predicates.go (about) 1 /* 2 Copyright 2015 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 scheduling 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "time" 24 25 v1 "k8s.io/api/core/v1" 26 nodev1 "k8s.io/api/node/v1" 27 "k8s.io/apimachinery/pkg/api/resource" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/types" 30 "k8s.io/apimachinery/pkg/util/intstr" 31 "k8s.io/apimachinery/pkg/util/sets" 32 "k8s.io/apimachinery/pkg/util/strategicpatch" 33 "k8s.io/apimachinery/pkg/util/uuid" 34 utilversion "k8s.io/apimachinery/pkg/util/version" 35 clientset "k8s.io/client-go/kubernetes" 36 podutil "k8s.io/kubernetes/pkg/api/v1/pod" 37 "k8s.io/kubernetes/test/e2e/feature" 38 "k8s.io/kubernetes/test/e2e/framework" 39 e2enode "k8s.io/kubernetes/test/e2e/framework/node" 40 e2eruntimeclass "k8s.io/kubernetes/test/e2e/framework/node/runtimeclass" 41 e2epod "k8s.io/kubernetes/test/e2e/framework/pod" 42 e2epv "k8s.io/kubernetes/test/e2e/framework/pv" 43 e2erc "k8s.io/kubernetes/test/e2e/framework/rc" 44 e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper" 45 testutils "k8s.io/kubernetes/test/utils" 46 imageutils "k8s.io/kubernetes/test/utils/image" 47 admissionapi "k8s.io/pod-security-admission/api" 48 49 "github.com/onsi/ginkgo/v2" 50 "github.com/onsi/gomega" 51 52 // ensure libs have a chance to initialize 53 _ "github.com/stretchr/testify/assert" 54 ) 55 56 const ( 57 maxNumberOfPods int64 = 10 58 defaultTimeout = 3 * time.Minute 59 ) 60 61 var localStorageVersion = utilversion.MustParseSemantic("v1.8.0-beta.0") 62 63 // variable populated in BeforeEach, never modified afterwards 64 var workerNodes = sets.Set[string]{} 65 66 type pausePodConfig struct { 67 Name string 68 Namespace string 69 Finalizers []string 70 Affinity *v1.Affinity 71 Annotations, Labels, NodeSelector map[string]string 72 Resources *v1.ResourceRequirements 73 RuntimeClassHandler *string 74 Tolerations []v1.Toleration 75 NodeName string 76 Ports []v1.ContainerPort 77 OwnerReferences []metav1.OwnerReference 78 PriorityClassName string 79 DeletionGracePeriodSeconds *int64 80 TopologySpreadConstraints []v1.TopologySpreadConstraint 81 SchedulingGates []v1.PodSchedulingGate 82 } 83 84 var _ = SIGDescribe("SchedulerPredicates", framework.WithSerial(), func() { 85 var cs clientset.Interface 86 var nodeList *v1.NodeList 87 var RCName string 88 var ns string 89 f := framework.NewDefaultFramework("sched-pred") 90 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged 91 92 ginkgo.AfterEach(func(ctx context.Context) { 93 rc, err := cs.CoreV1().ReplicationControllers(ns).Get(ctx, RCName, metav1.GetOptions{}) 94 if err == nil && *(rc.Spec.Replicas) != 0 { 95 ginkgo.By("Cleaning up the replication controller") 96 err := e2erc.DeleteRCAndWaitForGC(ctx, f.ClientSet, ns, RCName) 97 framework.ExpectNoError(err) 98 } 99 }) 100 101 ginkgo.BeforeEach(func(ctx context.Context) { 102 cs = f.ClientSet 103 ns = f.Namespace.Name 104 nodeList = &v1.NodeList{} 105 var err error 106 107 e2enode.AllNodesReady(ctx, cs, time.Minute) 108 109 nodeList, err = e2enode.GetReadySchedulableNodes(ctx, cs) 110 if err != nil { 111 framework.Logf("Unexpected error occurred: %v", err) 112 } 113 framework.ExpectNoErrorWithOffset(0, err) 114 for _, n := range nodeList.Items { 115 workerNodes.Insert(n.Name) 116 } 117 118 err = framework.CheckTestingNSDeletedExcept(ctx, cs, ns) 119 framework.ExpectNoError(err) 120 121 for _, node := range nodeList.Items { 122 framework.Logf("\nLogging pods the apiserver thinks is on node %v before test", node.Name) 123 printAllPodsOnNode(ctx, cs, node.Name) 124 } 125 126 }) 127 128 // This test verifies we don't allow scheduling of pods in a way that sum of local ephemeral storage resource requests of pods is greater than machines capacity. 129 // It assumes that cluster add-on pods stay stable and cannot be run in parallel with any other test that touches Nodes or Pods. 130 // It is so because we need to have precise control on what's running in the cluster. 131 f.It("validates local ephemeral storage resource limits of pods that are allowed to run", feature.LocalStorageCapacityIsolation, func(ctx context.Context) { 132 133 e2eskipper.SkipUnlessServerVersionGTE(localStorageVersion, f.ClientSet.Discovery()) 134 135 nodeMaxAllocatable := int64(0) 136 137 nodeToAllocatableMap := make(map[string]int64) 138 for _, node := range nodeList.Items { 139 allocatable, found := node.Status.Allocatable[v1.ResourceEphemeralStorage] 140 if !found { 141 framework.Failf("node.Status.Allocatable %v does not contain entry %v", node.Status.Allocatable, v1.ResourceEphemeralStorage) 142 } 143 nodeToAllocatableMap[node.Name] = allocatable.Value() 144 if nodeMaxAllocatable < allocatable.Value() { 145 nodeMaxAllocatable = allocatable.Value() 146 } 147 } 148 WaitForStableCluster(cs, workerNodes) 149 150 pods, err := cs.CoreV1().Pods(metav1.NamespaceAll).List(ctx, metav1.ListOptions{}) 151 framework.ExpectNoError(err) 152 for _, pod := range pods.Items { 153 _, found := nodeToAllocatableMap[pod.Spec.NodeName] 154 if found && pod.Status.Phase != v1.PodSucceeded && pod.Status.Phase != v1.PodFailed { 155 framework.Logf("Pod %v requesting local ephemeral resource =%v on Node %v", pod.Name, getRequestedStorageEphemeralStorage(pod), pod.Spec.NodeName) 156 nodeToAllocatableMap[pod.Spec.NodeName] -= getRequestedStorageEphemeralStorage(pod) 157 } 158 } 159 160 var podsNeededForSaturation int 161 ephemeralStoragePerPod := nodeMaxAllocatable / maxNumberOfPods 162 163 framework.Logf("Using pod capacity: %v", ephemeralStoragePerPod) 164 for name, leftAllocatable := range nodeToAllocatableMap { 165 framework.Logf("Node: %v has local ephemeral resource allocatable: %v", name, leftAllocatable) 166 podsNeededForSaturation += (int)(leftAllocatable / ephemeralStoragePerPod) 167 } 168 169 ginkgo.By(fmt.Sprintf("Starting additional %v Pods to fully saturate the cluster local ephemeral resource and trying to start another one", podsNeededForSaturation)) 170 171 // As the pods are distributed randomly among nodes, 172 // it can easily happen that all nodes are saturated 173 // and there is no need to create additional pods. 174 // StartPods requires at least one pod to replicate. 175 if podsNeededForSaturation > 0 { 176 framework.ExpectNoError(testutils.StartPods(cs, podsNeededForSaturation, ns, "overcommit", 177 *initPausePod(f, pausePodConfig{ 178 Name: "", 179 Labels: map[string]string{"name": ""}, 180 Resources: &v1.ResourceRequirements{ 181 Limits: v1.ResourceList{ 182 v1.ResourceEphemeralStorage: *resource.NewQuantity(ephemeralStoragePerPod, "DecimalSI"), 183 }, 184 Requests: v1.ResourceList{ 185 v1.ResourceEphemeralStorage: *resource.NewQuantity(ephemeralStoragePerPod, "DecimalSI"), 186 }, 187 }, 188 }), true, framework.Logf)) 189 } 190 podName := "additional-pod" 191 conf := pausePodConfig{ 192 Name: podName, 193 Labels: map[string]string{"name": "additional"}, 194 Resources: &v1.ResourceRequirements{ 195 Limits: v1.ResourceList{ 196 v1.ResourceEphemeralStorage: *resource.NewQuantity(ephemeralStoragePerPod, "DecimalSI"), 197 }, 198 Requests: v1.ResourceList{ 199 v1.ResourceEphemeralStorage: *resource.NewQuantity(ephemeralStoragePerPod, "DecimalSI"), 200 }, 201 }, 202 } 203 WaitForSchedulerAfterAction(ctx, f, createPausePodAction(f, conf), ns, podName, false) 204 verifyResult(ctx, cs, podsNeededForSaturation, 1, ns) 205 }) 206 207 // This test verifies we don't allow scheduling of pods in a way that sum of limits + 208 // associated overhead is greater than machine's capacity. 209 // It assumes that cluster add-on pods stay stable and cannot be run in parallel 210 // with any other test that touches Nodes or Pods. 211 // Because of this we need to have precise control on what's running in the cluster. 212 // Test scenario: 213 // 1. Find the first ready node on the system, and add a fake resource for test 214 // 2. Create one with affinity to the particular node that uses 70% of the fake resource. 215 // 3. Wait for the pod to be scheduled. 216 // 4. Create another pod with affinity to the particular node that needs 20% of the fake resource and 217 // an overhead set as 25% of the fake resource. 218 // 5. Make sure this additional pod is not scheduled. 219 220 ginkgo.Context("validates pod overhead is considered along with resource limits of pods that are allowed to run", func() { 221 var testNodeName string 222 var handler string 223 var beardsecond v1.ResourceName = "example.com/beardsecond" 224 225 ginkgo.BeforeEach(func(ctx context.Context) { 226 WaitForStableCluster(cs, workerNodes) 227 ginkgo.By("Add RuntimeClass and fake resource") 228 229 // find a node which can run a pod: 230 testNodeName = GetNodeThatCanRunPod(ctx, f) 231 232 // Get node object: 233 node, err := cs.CoreV1().Nodes().Get(ctx, testNodeName, metav1.GetOptions{}) 234 framework.ExpectNoError(err, "unable to get node object for node %v", testNodeName) 235 236 // update Node API object with a fake resource 237 nodeCopy := node.DeepCopy() 238 nodeCopy.ResourceVersion = "0" 239 240 nodeCopy.Status.Capacity[beardsecond] = resource.MustParse("1000") 241 _, err = cs.CoreV1().Nodes().UpdateStatus(ctx, nodeCopy, metav1.UpdateOptions{}) 242 framework.ExpectNoError(err, "unable to apply fake resource to %v", testNodeName) 243 244 // Register a runtimeClass with overhead set as 25% of the available beard-seconds 245 handler = e2eruntimeclass.PreconfiguredRuntimeClassHandler 246 247 rc := &nodev1.RuntimeClass{ 248 ObjectMeta: metav1.ObjectMeta{Name: handler}, 249 Handler: handler, 250 Overhead: &nodev1.Overhead{ 251 PodFixed: v1.ResourceList{ 252 beardsecond: resource.MustParse("250"), 253 }, 254 }, 255 } 256 _, err = cs.NodeV1().RuntimeClasses().Create(ctx, rc, metav1.CreateOptions{}) 257 framework.ExpectNoError(err, "failed to create RuntimeClass resource") 258 }) 259 260 ginkgo.AfterEach(func(ctx context.Context) { 261 ginkgo.By("Remove fake resource and RuntimeClass") 262 // remove fake resource: 263 if testNodeName != "" { 264 // Get node object: 265 node, err := cs.CoreV1().Nodes().Get(ctx, testNodeName, metav1.GetOptions{}) 266 framework.ExpectNoError(err, "unable to get node object for node %v", testNodeName) 267 268 nodeCopy := node.DeepCopy() 269 // force it to update 270 nodeCopy.ResourceVersion = "0" 271 delete(nodeCopy.Status.Capacity, beardsecond) 272 _, err = cs.CoreV1().Nodes().UpdateStatus(ctx, nodeCopy, metav1.UpdateOptions{}) 273 framework.ExpectNoError(err, "unable to update node %v", testNodeName) 274 } 275 276 // remove RuntimeClass 277 _ = cs.NodeV1().RuntimeClasses().Delete(ctx, e2eruntimeclass.PreconfiguredRuntimeClassHandler, metav1.DeleteOptions{}) 278 }) 279 280 ginkgo.It("verify pod overhead is accounted for", func(ctx context.Context) { 281 if testNodeName == "" { 282 framework.Fail("unable to find a node which can run a pod") 283 } 284 285 ginkgo.By("Starting Pod to consume most of the node's resource.") 286 287 // Create pod which requires 70% of the available beard-seconds. 288 fillerPod := createPausePod(ctx, f, pausePodConfig{ 289 Name: "filler-pod-" + string(uuid.NewUUID()), 290 Resources: &v1.ResourceRequirements{ 291 Requests: v1.ResourceList{beardsecond: resource.MustParse("700")}, 292 Limits: v1.ResourceList{beardsecond: resource.MustParse("700")}, 293 }, 294 }) 295 296 // Wait for filler pod to schedule. 297 framework.ExpectNoError(e2epod.WaitForPodRunningInNamespace(ctx, cs, fillerPod)) 298 299 ginkgo.By("Creating another pod that requires unavailable amount of resources.") 300 // Create another pod that requires 20% of available beard-seconds, but utilizes the RuntimeClass 301 // which defines a pod overhead that requires an additional 25%. 302 // This pod should remain pending as at least 70% of beard-second in 303 // the node are already consumed. 304 podName := "additional-pod" + string(uuid.NewUUID()) 305 conf := pausePodConfig{ 306 RuntimeClassHandler: &handler, 307 Name: podName, 308 Labels: map[string]string{"name": "additional"}, 309 Resources: &v1.ResourceRequirements{ 310 Limits: v1.ResourceList{beardsecond: resource.MustParse("200")}, 311 }, 312 } 313 314 WaitForSchedulerAfterAction(ctx, f, createPausePodAction(f, conf), ns, podName, false) 315 verifyResult(ctx, cs, 1, 1, ns) 316 }) 317 }) 318 319 // This test verifies we don't allow scheduling of pods in a way that sum of 320 // resource requests of pods is greater than machines capacity. 321 // It assumes that cluster add-on pods stay stable and cannot be run in parallel 322 // with any other test that touches Nodes or Pods. 323 // It is so because we need to have precise control on what's running in the cluster. 324 // Test scenario: 325 // 1. Find the amount CPU resources on each node. 326 // 2. Create one pod with affinity to each node that uses 70% of the node CPU. 327 // 3. Wait for the pods to be scheduled. 328 // 4. Create another pod with no affinity to any node that need 50% of the largest node CPU. 329 // 5. Make sure this additional pod is not scheduled. 330 /* 331 Release: v1.9 332 Testname: Scheduler, resource limits 333 Description: Scheduling Pods MUST fail if the resource requests exceed Machine capacity. 334 */ 335 framework.ConformanceIt("validates resource limits of pods that are allowed to run", func(ctx context.Context) { 336 WaitForStableCluster(cs, workerNodes) 337 nodeMaxAllocatable := int64(0) 338 nodeToAllocatableMap := make(map[string]int64) 339 for _, node := range nodeList.Items { 340 nodeReady := false 341 for _, condition := range node.Status.Conditions { 342 if condition.Type == v1.NodeReady && condition.Status == v1.ConditionTrue { 343 nodeReady = true 344 break 345 } 346 } 347 if !nodeReady { 348 continue 349 } 350 // Apply node label to each node 351 e2enode.AddOrUpdateLabelOnNode(cs, node.Name, "node", node.Name) 352 e2enode.ExpectNodeHasLabel(ctx, cs, node.Name, "node", node.Name) 353 // Find allocatable amount of CPU. 354 allocatable, found := node.Status.Allocatable[v1.ResourceCPU] 355 if !found { 356 framework.Failf("node.Status.Allocatable %v does not contain entry %v", node.Status.Allocatable, v1.ResourceCPU) 357 } 358 nodeToAllocatableMap[node.Name] = allocatable.MilliValue() 359 if nodeMaxAllocatable < allocatable.MilliValue() { 360 nodeMaxAllocatable = allocatable.MilliValue() 361 } 362 } 363 // Clean up added labels after this test. 364 defer func() { 365 for nodeName := range nodeToAllocatableMap { 366 e2enode.RemoveLabelOffNode(cs, nodeName, "node") 367 } 368 }() 369 370 pods, err := cs.CoreV1().Pods(metav1.NamespaceAll).List(ctx, metav1.ListOptions{}) 371 framework.ExpectNoError(err) 372 for _, pod := range pods.Items { 373 _, found := nodeToAllocatableMap[pod.Spec.NodeName] 374 if found && pod.Status.Phase != v1.PodSucceeded && pod.Status.Phase != v1.PodFailed { 375 framework.Logf("Pod %v requesting resource cpu=%vm on Node %v", pod.Name, getRequestedCPU(pod), pod.Spec.NodeName) 376 nodeToAllocatableMap[pod.Spec.NodeName] -= getRequestedCPU(pod) 377 } 378 } 379 380 ginkgo.By("Starting Pods to consume most of the cluster CPU.") 381 // Create one pod per node that requires 70% of the node remaining CPU. 382 fillerPods := []*v1.Pod{} 383 for nodeName, cpu := range nodeToAllocatableMap { 384 requestedCPU := cpu * 7 / 10 385 framework.Logf("Creating a pod which consumes cpu=%vm on Node %v", requestedCPU, nodeName) 386 fillerPods = append(fillerPods, createPausePod(ctx, f, pausePodConfig{ 387 Name: "filler-pod-" + string(uuid.NewUUID()), 388 Resources: &v1.ResourceRequirements{ 389 Limits: v1.ResourceList{ 390 v1.ResourceCPU: *resource.NewMilliQuantity(requestedCPU, "DecimalSI"), 391 }, 392 Requests: v1.ResourceList{ 393 v1.ResourceCPU: *resource.NewMilliQuantity(requestedCPU, "DecimalSI"), 394 }, 395 }, 396 Affinity: &v1.Affinity{ 397 NodeAffinity: &v1.NodeAffinity{ 398 RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ 399 NodeSelectorTerms: []v1.NodeSelectorTerm{ 400 { 401 MatchExpressions: []v1.NodeSelectorRequirement{ 402 { 403 Key: "node", 404 Operator: v1.NodeSelectorOpIn, 405 Values: []string{nodeName}, 406 }, 407 }, 408 }, 409 }, 410 }, 411 }, 412 }, 413 })) 414 } 415 // Wait for filler pods to schedule. 416 for _, pod := range fillerPods { 417 framework.ExpectNoError(e2epod.WaitForPodRunningInNamespace(ctx, cs, pod)) 418 } 419 ginkgo.By("Creating another pod that requires unavailable amount of CPU.") 420 // Create another pod that requires 50% of the largest node CPU resources. 421 // This pod should remain pending as at least 70% of CPU of other nodes in 422 // the cluster are already consumed. 423 podName := "additional-pod" 424 conf := pausePodConfig{ 425 Name: podName, 426 Labels: map[string]string{"name": "additional"}, 427 Resources: &v1.ResourceRequirements{ 428 Limits: v1.ResourceList{ 429 v1.ResourceCPU: *resource.NewMilliQuantity(nodeMaxAllocatable*5/10, "DecimalSI"), 430 }, 431 Requests: v1.ResourceList{ 432 v1.ResourceCPU: *resource.NewMilliQuantity(nodeMaxAllocatable*5/10, "DecimalSI"), 433 }, 434 }, 435 } 436 WaitForSchedulerAfterAction(ctx, f, createPausePodAction(f, conf), ns, podName, false) 437 verifyResult(ctx, cs, len(fillerPods), 1, ns) 438 }) 439 440 // Test Nodes does not have any label, hence it should be impossible to schedule Pod with 441 // nonempty Selector set. 442 /* 443 Release: v1.9 444 Testname: Scheduler, node selector not matching 445 Description: Create a Pod with a NodeSelector set to a value that does not match a node in the cluster. Since there are no nodes matching the criteria the Pod MUST not be scheduled. 446 */ 447 framework.ConformanceIt("validates that NodeSelector is respected if not matching", func(ctx context.Context) { 448 ginkgo.By("Trying to schedule Pod with nonempty NodeSelector.") 449 podName := "restricted-pod" 450 451 WaitForStableCluster(cs, workerNodes) 452 453 conf := pausePodConfig{ 454 Name: podName, 455 Labels: map[string]string{"name": "restricted"}, 456 NodeSelector: map[string]string{ 457 "label": "nonempty", 458 }, 459 } 460 461 WaitForSchedulerAfterAction(ctx, f, createPausePodAction(f, conf), ns, podName, false) 462 verifyResult(ctx, cs, 0, 1, ns) 463 }) 464 465 /* 466 Release: v1.9 467 Testname: Scheduler, node selector matching 468 Description: Create a label on the node {k: v}. Then create a Pod with a NodeSelector set to {k: v}. Check to see if the Pod is scheduled. When the NodeSelector matches then Pod MUST be scheduled on that node. 469 */ 470 framework.ConformanceIt("validates that NodeSelector is respected if matching", func(ctx context.Context) { 471 nodeName := GetNodeThatCanRunPod(ctx, f) 472 473 ginkgo.By("Trying to apply a random label on the found node.") 474 k := fmt.Sprintf("kubernetes.io/e2e-%s", string(uuid.NewUUID())) 475 v := "42" 476 e2enode.AddOrUpdateLabelOnNode(cs, nodeName, k, v) 477 e2enode.ExpectNodeHasLabel(ctx, cs, nodeName, k, v) 478 defer e2enode.RemoveLabelOffNode(cs, nodeName, k) 479 480 ginkgo.By("Trying to relaunch the pod, now with labels.") 481 labelPodName := "with-labels" 482 createPausePod(ctx, f, pausePodConfig{ 483 Name: labelPodName, 484 NodeSelector: map[string]string{ 485 k: v, 486 }, 487 }) 488 489 // check that pod got scheduled. We intentionally DO NOT check that the 490 // pod is running because this will create a race condition with the 491 // kubelet and the scheduler: the scheduler might have scheduled a pod 492 // already when the kubelet does not know about its new label yet. The 493 // kubelet will then refuse to launch the pod. 494 framework.ExpectNoError(e2epod.WaitForPodNotPending(ctx, cs, ns, labelPodName)) 495 labelPod, err := cs.CoreV1().Pods(ns).Get(ctx, labelPodName, metav1.GetOptions{}) 496 framework.ExpectNoError(err) 497 gomega.Expect(labelPod.Spec.NodeName).To(gomega.Equal(nodeName)) 498 }) 499 500 // Test Nodes does not have any label, hence it should be impossible to schedule Pod with 501 // non-nil NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution. 502 ginkgo.It("validates that NodeAffinity is respected if not matching", func(ctx context.Context) { 503 ginkgo.By("Trying to schedule Pod with nonempty NodeSelector.") 504 podName := "restricted-pod" 505 506 WaitForStableCluster(cs, workerNodes) 507 508 conf := pausePodConfig{ 509 Name: podName, 510 Affinity: &v1.Affinity{ 511 NodeAffinity: &v1.NodeAffinity{ 512 RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ 513 NodeSelectorTerms: []v1.NodeSelectorTerm{ 514 { 515 MatchExpressions: []v1.NodeSelectorRequirement{ 516 { 517 Key: "foo", 518 Operator: v1.NodeSelectorOpIn, 519 Values: []string{"bar", "value2"}, 520 }, 521 }, 522 }, { 523 MatchExpressions: []v1.NodeSelectorRequirement{ 524 { 525 Key: "diffkey", 526 Operator: v1.NodeSelectorOpIn, 527 Values: []string{"wrong", "value2"}, 528 }, 529 }, 530 }, 531 }, 532 }, 533 }, 534 }, 535 Labels: map[string]string{"name": "restricted"}, 536 } 537 WaitForSchedulerAfterAction(ctx, f, createPausePodAction(f, conf), ns, podName, false) 538 verifyResult(ctx, cs, 0, 1, ns) 539 }) 540 541 // Keep the same steps with the test on NodeSelector, 542 // but specify Affinity in Pod.Spec.Affinity, instead of NodeSelector. 543 ginkgo.It("validates that required NodeAffinity setting is respected if matching", func(ctx context.Context) { 544 nodeName := GetNodeThatCanRunPod(ctx, f) 545 546 ginkgo.By("Trying to apply a random label on the found node.") 547 k := fmt.Sprintf("kubernetes.io/e2e-%s", string(uuid.NewUUID())) 548 v := "42" 549 e2enode.AddOrUpdateLabelOnNode(cs, nodeName, k, v) 550 e2enode.ExpectNodeHasLabel(ctx, cs, nodeName, k, v) 551 defer e2enode.RemoveLabelOffNode(cs, nodeName, k) 552 553 ginkgo.By("Trying to relaunch the pod, now with labels.") 554 labelPodName := "with-labels" 555 createPausePod(ctx, f, pausePodConfig{ 556 Name: labelPodName, 557 Affinity: &v1.Affinity{ 558 NodeAffinity: &v1.NodeAffinity{ 559 RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ 560 NodeSelectorTerms: []v1.NodeSelectorTerm{ 561 { 562 MatchExpressions: []v1.NodeSelectorRequirement{ 563 { 564 Key: k, 565 Operator: v1.NodeSelectorOpIn, 566 Values: []string{v}, 567 }, 568 }, 569 }, 570 }, 571 }, 572 }, 573 }, 574 }) 575 576 // check that pod got scheduled. We intentionally DO NOT check that the 577 // pod is running because this will create a race condition with the 578 // kubelet and the scheduler: the scheduler might have scheduled a pod 579 // already when the kubelet does not know about its new label yet. The 580 // kubelet will then refuse to launch the pod. 581 framework.ExpectNoError(e2epod.WaitForPodNotPending(ctx, cs, ns, labelPodName)) 582 labelPod, err := cs.CoreV1().Pods(ns).Get(ctx, labelPodName, metav1.GetOptions{}) 583 framework.ExpectNoError(err) 584 gomega.Expect(labelPod.Spec.NodeName).To(gomega.Equal(nodeName)) 585 }) 586 587 // 1. Run a pod to get an available node, then delete the pod 588 // 2. Taint the node with a random taint 589 // 3. Try to relaunch the pod with tolerations tolerate the taints on node, 590 // and the pod's nodeName specified to the name of node found in step 1 591 ginkgo.It("validates that taints-tolerations is respected if matching", func(ctx context.Context) { 592 nodeName := getNodeThatCanRunPodWithoutToleration(ctx, f) 593 594 ginkgo.By("Trying to apply a random taint on the found node.") 595 testTaint := v1.Taint{ 596 Key: fmt.Sprintf("kubernetes.io/e2e-taint-key-%s", string(uuid.NewUUID())), 597 Value: "testing-taint-value", 598 Effect: v1.TaintEffectNoSchedule, 599 } 600 e2enode.AddOrUpdateTaintOnNode(ctx, cs, nodeName, testTaint) 601 e2enode.ExpectNodeHasTaint(ctx, cs, nodeName, &testTaint) 602 ginkgo.DeferCleanup(e2enode.RemoveTaintOffNode, cs, nodeName, testTaint) 603 604 ginkgo.By("Trying to apply a random label on the found node.") 605 labelKey := fmt.Sprintf("kubernetes.io/e2e-label-key-%s", string(uuid.NewUUID())) 606 labelValue := "testing-label-value" 607 e2enode.AddOrUpdateLabelOnNode(cs, nodeName, labelKey, labelValue) 608 e2enode.ExpectNodeHasLabel(ctx, cs, nodeName, labelKey, labelValue) 609 defer e2enode.RemoveLabelOffNode(cs, nodeName, labelKey) 610 611 ginkgo.By("Trying to relaunch the pod, now with tolerations.") 612 tolerationPodName := "with-tolerations" 613 createPausePod(ctx, f, pausePodConfig{ 614 Name: tolerationPodName, 615 Tolerations: []v1.Toleration{{Key: testTaint.Key, Value: testTaint.Value, Effect: testTaint.Effect}}, 616 NodeSelector: map[string]string{labelKey: labelValue}, 617 }) 618 619 // check that pod got scheduled. We intentionally DO NOT check that the 620 // pod is running because this will create a race condition with the 621 // kubelet and the scheduler: the scheduler might have scheduled a pod 622 // already when the kubelet does not know about its new taint yet. The 623 // kubelet will then refuse to launch the pod. 624 framework.ExpectNoError(e2epod.WaitForPodNotPending(ctx, cs, ns, tolerationPodName)) 625 deployedPod, err := cs.CoreV1().Pods(ns).Get(ctx, tolerationPodName, metav1.GetOptions{}) 626 framework.ExpectNoError(err) 627 gomega.Expect(deployedPod.Spec.NodeName).To(gomega.Equal(nodeName)) 628 }) 629 630 // 1. Run a pod to get an available node, then delete the pod 631 // 2. Taint the node with a random taint 632 // 3. Try to relaunch the pod still no tolerations, 633 // and the pod's nodeName specified to the name of node found in step 1 634 ginkgo.It("validates that taints-tolerations is respected if not matching", func(ctx context.Context) { 635 nodeName := getNodeThatCanRunPodWithoutToleration(ctx, f) 636 637 ginkgo.By("Trying to apply a random taint on the found node.") 638 testTaint := v1.Taint{ 639 Key: fmt.Sprintf("kubernetes.io/e2e-taint-key-%s", string(uuid.NewUUID())), 640 Value: "testing-taint-value", 641 Effect: v1.TaintEffectNoSchedule, 642 } 643 e2enode.AddOrUpdateTaintOnNode(ctx, cs, nodeName, testTaint) 644 e2enode.ExpectNodeHasTaint(ctx, cs, nodeName, &testTaint) 645 ginkgo.DeferCleanup(e2enode.RemoveTaintOffNode, cs, nodeName, testTaint) 646 647 ginkgo.By("Trying to apply a random label on the found node.") 648 labelKey := fmt.Sprintf("kubernetes.io/e2e-label-key-%s", string(uuid.NewUUID())) 649 labelValue := "testing-label-value" 650 e2enode.AddOrUpdateLabelOnNode(cs, nodeName, labelKey, labelValue) 651 e2enode.ExpectNodeHasLabel(ctx, cs, nodeName, labelKey, labelValue) 652 defer e2enode.RemoveLabelOffNode(cs, nodeName, labelKey) 653 654 ginkgo.By("Trying to relaunch the pod, still no tolerations.") 655 podNameNoTolerations := "still-no-tolerations" 656 conf := pausePodConfig{ 657 Name: podNameNoTolerations, 658 NodeSelector: map[string]string{labelKey: labelValue}, 659 } 660 661 WaitForSchedulerAfterAction(ctx, f, createPausePodAction(f, conf), ns, podNameNoTolerations, false) 662 verifyResult(ctx, cs, 0, 1, ns) 663 664 ginkgo.By("Removing taint off the node") 665 WaitForSchedulerAfterAction(ctx, f, removeTaintFromNodeAction(cs, nodeName, testTaint), ns, podNameNoTolerations, true) 666 verifyResult(ctx, cs, 1, 0, ns) 667 }) 668 669 ginkgo.It("validates that there is no conflict between pods with same hostPort but different hostIP and protocol", func(ctx context.Context) { 670 671 nodeName := GetNodeThatCanRunPod(ctx, f) 672 localhost := "127.0.0.1" 673 if framework.TestContext.ClusterIsIPv6() { 674 localhost = "::1" 675 } 676 hostIP := getNodeHostIP(ctx, f, nodeName) 677 678 // use nodeSelector to make sure the testing pods get assigned on the same node to explicitly verify there exists conflict or not 679 ginkgo.By("Trying to apply a random label on the found node.") 680 k := fmt.Sprintf("kubernetes.io/e2e-%s", string(uuid.NewUUID())) 681 v := "90" 682 683 nodeSelector := make(map[string]string) 684 nodeSelector[k] = v 685 686 e2enode.AddOrUpdateLabelOnNode(cs, nodeName, k, v) 687 e2enode.ExpectNodeHasLabel(ctx, cs, nodeName, k, v) 688 defer e2enode.RemoveLabelOffNode(cs, nodeName, k) 689 690 port := int32(54321) 691 ginkgo.By(fmt.Sprintf("Trying to create a pod(pod1) with hostport %v and hostIP %s and expect scheduled", port, localhost)) 692 createHostPortPodOnNode(ctx, f, "pod1", ns, localhost, port, v1.ProtocolTCP, nodeSelector, true) 693 694 ginkgo.By(fmt.Sprintf("Trying to create another pod(pod2) with hostport %v but hostIP %s on the node which pod1 resides and expect scheduled", port, hostIP)) 695 createHostPortPodOnNode(ctx, f, "pod2", ns, hostIP, port, v1.ProtocolTCP, nodeSelector, true) 696 697 ginkgo.By(fmt.Sprintf("Trying to create a third pod(pod3) with hostport %v, hostIP %s but use UDP protocol on the node which pod2 resides", port, hostIP)) 698 createHostPortPodOnNode(ctx, f, "pod3", ns, hostIP, port, v1.ProtocolUDP, nodeSelector, true) 699 700 }) 701 702 /* 703 Release: v1.16 704 Testname: Scheduling, HostPort and Protocol match, HostIPs different but one is default HostIP (0.0.0.0) 705 Description: Pods with the same HostPort and Protocol, but different HostIPs, MUST NOT schedule to the 706 same node if one of those IPs is the default HostIP of 0.0.0.0, which represents all IPs on the host. 707 */ 708 framework.ConformanceIt("validates that there exists conflict between pods with same hostPort and protocol but one using 0.0.0.0 hostIP", func(ctx context.Context) { 709 nodeName := GetNodeThatCanRunPod(ctx, f) 710 hostIP := getNodeHostIP(ctx, f, nodeName) 711 // use nodeSelector to make sure the testing pods get assigned on the same node to explicitly verify there exists conflict or not 712 ginkgo.By("Trying to apply a random label on the found node.") 713 k := fmt.Sprintf("kubernetes.io/e2e-%s", string(uuid.NewUUID())) 714 v := "95" 715 716 nodeSelector := make(map[string]string) 717 nodeSelector[k] = v 718 719 e2enode.AddOrUpdateLabelOnNode(cs, nodeName, k, v) 720 e2enode.ExpectNodeHasLabel(ctx, cs, nodeName, k, v) 721 defer e2enode.RemoveLabelOffNode(cs, nodeName, k) 722 723 port := int32(54322) 724 ginkgo.By(fmt.Sprintf("Trying to create a pod(pod4) with hostport %v and hostIP 0.0.0.0(empty string here) and expect scheduled", port)) 725 createHostPortPodOnNode(ctx, f, "pod4", ns, "", port, v1.ProtocolTCP, nodeSelector, true) 726 727 ginkgo.By(fmt.Sprintf("Trying to create another pod(pod5) with hostport %v but hostIP %s on the node which pod4 resides and expect not scheduled", port, hostIP)) 728 createHostPortPodOnNode(ctx, f, "pod5", ns, hostIP, port, v1.ProtocolTCP, nodeSelector, false) 729 }) 730 731 ginkgo.Context("PodTopologySpread Filtering", func() { 732 var nodeNames []string 733 topologyKey := "kubernetes.io/e2e-pts-filter" 734 735 ginkgo.BeforeEach(func(ctx context.Context) { 736 if len(nodeList.Items) < 2 { 737 ginkgo.Skip("At least 2 nodes are required to run the test") 738 } 739 ginkgo.By("Trying to get 2 available nodes which can run pod") 740 nodeNames = Get2NodesThatCanRunPod(ctx, f) 741 ginkgo.By(fmt.Sprintf("Apply dedicated topologyKey %v for this test on the 2 nodes.", topologyKey)) 742 for _, nodeName := range nodeNames { 743 e2enode.AddOrUpdateLabelOnNode(cs, nodeName, topologyKey, nodeName) 744 } 745 }) 746 ginkgo.AfterEach(func() { 747 for _, nodeName := range nodeNames { 748 e2enode.RemoveLabelOffNode(cs, nodeName, topologyKey) 749 } 750 }) 751 752 ginkgo.It("validates 4 pods with MaxSkew=1 are evenly distributed into 2 nodes", func(ctx context.Context) { 753 podLabel := "e2e-pts-filter" 754 replicas := 4 755 rsConfig := pauseRSConfig{ 756 Replicas: int32(replicas), 757 PodConfig: pausePodConfig{ 758 Name: podLabel, 759 Namespace: ns, 760 Labels: map[string]string{podLabel: ""}, 761 Affinity: &v1.Affinity{ 762 NodeAffinity: &v1.NodeAffinity{ 763 RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ 764 NodeSelectorTerms: []v1.NodeSelectorTerm{ 765 { 766 MatchExpressions: []v1.NodeSelectorRequirement{ 767 { 768 Key: topologyKey, 769 Operator: v1.NodeSelectorOpIn, 770 Values: nodeNames, 771 }, 772 }, 773 }, 774 }, 775 }, 776 }, 777 }, 778 TopologySpreadConstraints: []v1.TopologySpreadConstraint{ 779 { 780 MaxSkew: 1, 781 TopologyKey: topologyKey, 782 WhenUnsatisfiable: v1.DoNotSchedule, 783 LabelSelector: &metav1.LabelSelector{ 784 MatchExpressions: []metav1.LabelSelectorRequirement{ 785 { 786 Key: podLabel, 787 Operator: metav1.LabelSelectorOpExists, 788 }, 789 }, 790 }, 791 }, 792 }, 793 }, 794 } 795 runPauseRS(ctx, f, rsConfig) 796 podList, err := cs.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{}) 797 framework.ExpectNoError(err) 798 numInNode1, numInNode2 := 0, 0 799 for _, pod := range podList.Items { 800 if pod.Spec.NodeName == nodeNames[0] { 801 numInNode1++ 802 } else if pod.Spec.NodeName == nodeNames[1] { 803 numInNode2++ 804 } 805 } 806 expected := replicas / len(nodeNames) 807 gomega.Expect(numInNode1).To(gomega.Equal(expected), fmt.Sprintf("Pods are not distributed as expected on node %q", nodeNames[0])) 808 gomega.Expect(numInNode2).To(gomega.Equal(expected), fmt.Sprintf("Pods are not distributed as expected on node %q", nodeNames[1])) 809 }) 810 }) 811 812 ginkgo.It("validates Pods with non-empty schedulingGates are blocked on scheduling", func(ctx context.Context) { 813 podLabel := "e2e-scheduling-gates" 814 replicas := 3 815 ginkgo.By(fmt.Sprintf("Creating a ReplicaSet with replicas=%v, carrying scheduling gates [foo bar]", replicas)) 816 rsConfig := pauseRSConfig{ 817 Replicas: int32(replicas), 818 PodConfig: pausePodConfig{ 819 Name: podLabel, 820 Namespace: ns, 821 Labels: map[string]string{podLabel: ""}, 822 SchedulingGates: []v1.PodSchedulingGate{ 823 {Name: "foo"}, 824 {Name: "bar"}, 825 }, 826 }, 827 } 828 createPauseRS(ctx, f, rsConfig) 829 830 ginkgo.By("Expect all pods stay in pending state") 831 podList, err := e2epod.WaitForNumberOfPods(ctx, cs, ns, replicas, time.Minute) 832 framework.ExpectNoError(err) 833 framework.ExpectNoError(e2epod.WaitForPodsSchedulingGated(ctx, cs, ns, replicas, time.Minute)) 834 835 ginkgo.By("Remove one scheduling gate") 836 want := []v1.PodSchedulingGate{{Name: "bar"}} 837 var pods []*v1.Pod 838 for _, pod := range podList.Items { 839 clone := pod.DeepCopy() 840 clone.Spec.SchedulingGates = want 841 live, err := patchPod(cs, &pod, clone) 842 framework.ExpectNoError(err) 843 pods = append(pods, live) 844 } 845 846 ginkgo.By("Expect all pods carry one scheduling gate and are still in pending state") 847 framework.ExpectNoError(e2epod.WaitForPodsWithSchedulingGates(ctx, cs, ns, replicas, time.Minute, want)) 848 framework.ExpectNoError(e2epod.WaitForPodsSchedulingGated(ctx, cs, ns, replicas, time.Minute)) 849 850 ginkgo.By("Remove the remaining scheduling gates") 851 for _, pod := range pods { 852 clone := pod.DeepCopy() 853 clone.Spec.SchedulingGates = nil 854 _, err := patchPod(cs, pod, clone) 855 framework.ExpectNoError(err) 856 } 857 858 ginkgo.By("Expect all pods are scheduled and running") 859 framework.ExpectNoError(e2epod.WaitForPodsRunning(ctx, cs, ns, replicas, time.Minute)) 860 }) 861 862 // Regression test for an extended scenario for https://issues.k8s.io/123465 863 ginkgo.It("when PVC has node-affinity to non-existent/illegal nodes, the pod should be scheduled normally if suitable nodes exist", func(ctx context.Context) { 864 nodeName := GetNodeThatCanRunPod(ctx, f) 865 nonExistentNodeName1 := string(uuid.NewUUID()) 866 nonExistentNodeName2 := string(uuid.NewUUID()) 867 hostLabel := "kubernetes.io/hostname" 868 localPath := "/tmp" 869 podName := "bind-pv-with-non-existent-nodes" 870 pvcName := "pvc-" + string(uuid.NewUUID()) 871 _, pvc, err := e2epv.CreatePVPVC(ctx, cs, f.Timeouts, e2epv.PersistentVolumeConfig{ 872 PVSource: v1.PersistentVolumeSource{ 873 Local: &v1.LocalVolumeSource{ 874 Path: localPath, 875 }, 876 }, 877 Prebind: &v1.PersistentVolumeClaim{ 878 ObjectMeta: metav1.ObjectMeta{Name: pvcName, Namespace: ns}, 879 }, 880 NodeAffinity: &v1.VolumeNodeAffinity{ 881 Required: &v1.NodeSelector{ 882 NodeSelectorTerms: []v1.NodeSelectorTerm{ 883 { 884 MatchExpressions: []v1.NodeSelectorRequirement{ 885 { 886 Key: hostLabel, 887 Operator: v1.NodeSelectorOpIn, 888 // add non-existent nodes to the list 889 Values: []string{nodeName, nonExistentNodeName1, nonExistentNodeName2}, 890 }, 891 }, 892 }, 893 }, 894 }, 895 }, 896 }, e2epv.PersistentVolumeClaimConfig{ 897 Name: pvcName, 898 }, ns, true) 899 framework.ExpectNoError(err) 900 bindPvPod := &v1.Pod{ 901 ObjectMeta: metav1.ObjectMeta{ 902 Name: podName, 903 }, 904 Spec: v1.PodSpec{ 905 Containers: []v1.Container{ 906 { 907 Name: "pause", 908 Image: imageutils.GetE2EImage(imageutils.Pause), 909 VolumeMounts: []v1.VolumeMount{ 910 { 911 Name: "data", 912 MountPath: "/tmp", 913 }, 914 }, 915 }, 916 }, 917 Volumes: []v1.Volume{ 918 { 919 Name: "data", 920 VolumeSource: v1.VolumeSource{ 921 PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ 922 ClaimName: pvc.Name, 923 }, 924 }, 925 }, 926 }, 927 }, 928 } 929 _, err = f.ClientSet.CoreV1().Pods(ns).Create(ctx, bindPvPod, metav1.CreateOptions{}) 930 framework.ExpectNoError(err) 931 framework.ExpectNoError(e2epod.WaitForPodNotPending(ctx, f.ClientSet, ns, podName)) 932 }) 933 }) 934 935 func patchPod(cs clientset.Interface, old, new *v1.Pod) (*v1.Pod, error) { 936 oldData, err := json.Marshal(old) 937 if err != nil { 938 return nil, err 939 } 940 941 newData, err := json.Marshal(new) 942 if err != nil { 943 return nil, err 944 } 945 patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, &v1.Pod{}) 946 if err != nil { 947 return nil, fmt.Errorf("failed to create merge patch for Pod %q: %w", old.Name, err) 948 } 949 return cs.CoreV1().Pods(new.Namespace).Patch(context.TODO(), new.Name, types.StrategicMergePatchType, patchBytes, metav1.PatchOptions{}) 950 } 951 952 // printAllPodsOnNode outputs status of all kubelet pods into log. 953 func printAllPodsOnNode(ctx context.Context, c clientset.Interface, nodeName string) { 954 podList, err := c.CoreV1().Pods(metav1.NamespaceAll).List(ctx, metav1.ListOptions{FieldSelector: "spec.nodeName=" + nodeName}) 955 if err != nil { 956 framework.Logf("Unable to retrieve pods for node %v: %v", nodeName, err) 957 return 958 } 959 for _, p := range podList.Items { 960 framework.Logf("%v from %v started at %v (%d container statuses recorded)", p.Name, p.Namespace, p.Status.StartTime, len(p.Status.ContainerStatuses)) 961 for _, c := range p.Status.ContainerStatuses { 962 framework.Logf("\tContainer %v ready: %v, restart count %v", 963 c.Name, c.Ready, c.RestartCount) 964 } 965 } 966 } 967 968 func initPausePod(f *framework.Framework, conf pausePodConfig) *v1.Pod { 969 var gracePeriod = int64(1) 970 pod := &v1.Pod{ 971 ObjectMeta: metav1.ObjectMeta{ 972 Name: conf.Name, 973 Namespace: conf.Namespace, 974 Labels: map[string]string{}, 975 Annotations: map[string]string{}, 976 OwnerReferences: conf.OwnerReferences, 977 Finalizers: conf.Finalizers, 978 }, 979 Spec: v1.PodSpec{ 980 SecurityContext: e2epod.GetRestrictedPodSecurityContext(), 981 NodeSelector: conf.NodeSelector, 982 Affinity: conf.Affinity, 983 TopologySpreadConstraints: conf.TopologySpreadConstraints, 984 RuntimeClassName: conf.RuntimeClassHandler, 985 Containers: []v1.Container{ 986 { 987 Name: conf.Name, 988 Image: imageutils.GetPauseImageName(), 989 Ports: conf.Ports, 990 SecurityContext: e2epod.GetRestrictedContainerSecurityContext(), 991 }, 992 }, 993 Tolerations: conf.Tolerations, 994 PriorityClassName: conf.PriorityClassName, 995 TerminationGracePeriodSeconds: &gracePeriod, 996 SchedulingGates: conf.SchedulingGates, 997 }, 998 } 999 for key, value := range conf.Labels { 1000 pod.ObjectMeta.Labels[key] = value 1001 } 1002 for key, value := range conf.Annotations { 1003 pod.ObjectMeta.Annotations[key] = value 1004 } 1005 // TODO: setting the Pod's nodeAffinity instead of setting .spec.nodeName works around the 1006 // Preemption e2e flake (#88441), but we should investigate deeper to get to the bottom of it. 1007 if len(conf.NodeName) != 0 { 1008 e2epod.SetNodeAffinity(&pod.Spec, conf.NodeName) 1009 } 1010 if conf.Resources != nil { 1011 pod.Spec.Containers[0].Resources = *conf.Resources 1012 } 1013 if conf.DeletionGracePeriodSeconds != nil { 1014 pod.ObjectMeta.DeletionGracePeriodSeconds = conf.DeletionGracePeriodSeconds 1015 } 1016 return pod 1017 } 1018 1019 func createPausePod(ctx context.Context, f *framework.Framework, conf pausePodConfig) *v1.Pod { 1020 namespace := conf.Namespace 1021 if len(namespace) == 0 { 1022 namespace = f.Namespace.Name 1023 } 1024 pod, err := f.ClientSet.CoreV1().Pods(namespace).Create(ctx, initPausePod(f, conf), metav1.CreateOptions{}) 1025 framework.ExpectNoError(err) 1026 return pod 1027 } 1028 1029 func runPausePod(ctx context.Context, f *framework.Framework, conf pausePodConfig) *v1.Pod { 1030 pod := createPausePod(ctx, f, conf) 1031 framework.ExpectNoError(e2epod.WaitTimeoutForPodRunningInNamespace(ctx, f.ClientSet, pod.Name, pod.Namespace, f.Timeouts.PodStartShort)) 1032 pod, err := f.ClientSet.CoreV1().Pods(pod.Namespace).Get(ctx, conf.Name, metav1.GetOptions{}) 1033 framework.ExpectNoError(err) 1034 return pod 1035 } 1036 1037 func runPodAndGetNodeName(ctx context.Context, f *framework.Framework, conf pausePodConfig) string { 1038 // launch a pod to find a node which can launch a pod. We intentionally do 1039 // not just take the node list and choose the first of them. Depending on the 1040 // cluster and the scheduler it might be that a "normal" pod cannot be 1041 // scheduled onto it. 1042 pod := runPausePod(ctx, f, conf) 1043 1044 ginkgo.By("Explicitly delete pod here to free the resource it takes.") 1045 err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Delete(ctx, pod.Name, *metav1.NewDeleteOptions(0)) 1046 framework.ExpectNoError(err) 1047 1048 return pod.Spec.NodeName 1049 } 1050 1051 func getRequestedCPU(pod v1.Pod) int64 { 1052 var result int64 1053 for _, container := range pod.Spec.Containers { 1054 result += container.Resources.Requests.Cpu().MilliValue() 1055 } 1056 return result 1057 } 1058 1059 func getRequestedStorageEphemeralStorage(pod v1.Pod) int64 { 1060 var result int64 1061 for _, container := range pod.Spec.Containers { 1062 result += container.Resources.Requests.StorageEphemeral().Value() 1063 } 1064 return result 1065 } 1066 1067 // removeTaintFromNodeAction returns a closure that removes the given taint 1068 // from the given node upon invocation. 1069 func removeTaintFromNodeAction(cs clientset.Interface, nodeName string, testTaint v1.Taint) Action { 1070 return func(ctx context.Context) error { 1071 e2enode.RemoveTaintOffNode(ctx, cs, nodeName, testTaint) 1072 return nil 1073 } 1074 } 1075 1076 // createPausePodAction returns a closure that creates a pause pod upon invocation. 1077 func createPausePodAction(f *framework.Framework, conf pausePodConfig) Action { 1078 return func(ctx context.Context) error { 1079 _, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Create(ctx, initPausePod(f, conf), metav1.CreateOptions{}) 1080 return err 1081 } 1082 } 1083 1084 // WaitForSchedulerAfterAction performs the provided action and then waits for 1085 // scheduler to act on the given pod. 1086 func WaitForSchedulerAfterAction(ctx context.Context, f *framework.Framework, action Action, ns, podName string, expectSuccess bool) { 1087 predicate := scheduleFailureEvent(podName) 1088 if expectSuccess { 1089 predicate = scheduleSuccessEvent(ns, podName, "" /* any node */) 1090 } 1091 observed, err := observeEventAfterAction(ctx, f.ClientSet, f.Namespace.Name, predicate, action) 1092 framework.ExpectNoError(err) 1093 if expectSuccess && !observed { 1094 framework.Failf("Did not observe success event after performing the supplied action for pod %v", podName) 1095 } 1096 if !expectSuccess && !observed { 1097 framework.Failf("Did not observe failed event after performing the supplied action for pod %v", podName) 1098 } 1099 } 1100 1101 // TODO: upgrade calls in PodAffinity tests when we're able to run them 1102 func verifyResult(ctx context.Context, c clientset.Interface, expectedScheduled int, expectedNotScheduled int, ns string) { 1103 allPods, err := c.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{}) 1104 framework.ExpectNoError(err) 1105 scheduledPods, notScheduledPods := GetPodsScheduled(workerNodes, allPods) 1106 1107 gomega.Expect(notScheduledPods).To(gomega.HaveLen(expectedNotScheduled), fmt.Sprintf("Not scheduled Pods: %#v", notScheduledPods)) 1108 gomega.Expect(scheduledPods).To(gomega.HaveLen(expectedScheduled), fmt.Sprintf("Scheduled Pods: %#v", scheduledPods)) 1109 } 1110 1111 // GetNodeThatCanRunPod trying to launch a pod without a label to get a node which can launch it 1112 func GetNodeThatCanRunPod(ctx context.Context, f *framework.Framework) string { 1113 ginkgo.By("Trying to launch a pod without a label to get a node which can launch it.") 1114 return runPodAndGetNodeName(ctx, f, pausePodConfig{Name: "without-label"}) 1115 } 1116 1117 // Get2NodesThatCanRunPod return a 2-node slice where can run pod. 1118 func Get2NodesThatCanRunPod(ctx context.Context, f *framework.Framework) []string { 1119 firstNode := GetNodeThatCanRunPod(ctx, f) 1120 ginkgo.By("Trying to launch a pod without a label to get a node which can launch it.") 1121 pod := pausePodConfig{ 1122 Name: "without-label", 1123 Affinity: &v1.Affinity{ 1124 NodeAffinity: &v1.NodeAffinity{ 1125 RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ 1126 NodeSelectorTerms: []v1.NodeSelectorTerm{ 1127 { 1128 MatchFields: []v1.NodeSelectorRequirement{ 1129 {Key: "metadata.name", Operator: v1.NodeSelectorOpNotIn, Values: []string{firstNode}}, 1130 }, 1131 }, 1132 }, 1133 }, 1134 }, 1135 }, 1136 } 1137 secondNode := runPodAndGetNodeName(ctx, f, pod) 1138 return []string{firstNode, secondNode} 1139 } 1140 1141 func getNodeThatCanRunPodWithoutToleration(ctx context.Context, f *framework.Framework) string { 1142 ginkgo.By("Trying to launch a pod without a toleration to get a node which can launch it.") 1143 return runPodAndGetNodeName(ctx, f, pausePodConfig{Name: "without-toleration"}) 1144 } 1145 1146 // CreateHostPortPods creates RC with host port 4321 1147 func CreateHostPortPods(ctx context.Context, f *framework.Framework, id string, replicas int, expectRunning bool) { 1148 ginkgo.By("Running RC which reserves host port") 1149 config := &testutils.RCConfig{ 1150 Client: f.ClientSet, 1151 Name: id, 1152 Namespace: f.Namespace.Name, 1153 Timeout: defaultTimeout, 1154 Image: imageutils.GetPauseImageName(), 1155 Replicas: replicas, 1156 HostPorts: map[string]int{"port1": 4321}, 1157 } 1158 err := e2erc.RunRC(ctx, *config) 1159 if expectRunning { 1160 framework.ExpectNoError(err) 1161 } 1162 } 1163 1164 // CreateNodeSelectorPods creates RC with host port 4321 and defines node selector 1165 func CreateNodeSelectorPods(ctx context.Context, f *framework.Framework, id string, replicas int, nodeSelector map[string]string, expectRunning bool) error { 1166 ginkgo.By("Running RC which reserves host port and defines node selector") 1167 1168 config := &testutils.RCConfig{ 1169 Client: f.ClientSet, 1170 Name: id, 1171 Namespace: f.Namespace.Name, 1172 Timeout: defaultTimeout, 1173 Image: imageutils.GetPauseImageName(), 1174 Replicas: replicas, 1175 HostPorts: map[string]int{"port1": 4321}, 1176 NodeSelector: nodeSelector, 1177 } 1178 err := e2erc.RunRC(ctx, *config) 1179 if expectRunning { 1180 return err 1181 } 1182 return nil 1183 } 1184 1185 // create pod which using hostport on the specified node according to the nodeSelector 1186 // it starts an http server on the exposed port 1187 func createHostPortPodOnNode(ctx context.Context, f *framework.Framework, podName, ns, hostIP string, port int32, protocol v1.Protocol, nodeSelector map[string]string, expectScheduled bool) { 1188 hostPortPod := &v1.Pod{ 1189 ObjectMeta: metav1.ObjectMeta{ 1190 Name: podName, 1191 }, 1192 Spec: v1.PodSpec{ 1193 Containers: []v1.Container{ 1194 { 1195 Name: "agnhost", 1196 Image: imageutils.GetE2EImage(imageutils.Agnhost), 1197 Args: []string{"netexec", "--http-port=8080", "--udp-port=8080"}, 1198 Ports: []v1.ContainerPort{ 1199 { 1200 HostPort: port, 1201 ContainerPort: 8080, 1202 Protocol: protocol, 1203 HostIP: hostIP, 1204 }, 1205 }, 1206 ReadinessProbe: &v1.Probe{ 1207 ProbeHandler: v1.ProbeHandler{ 1208 HTTPGet: &v1.HTTPGetAction{ 1209 Path: "/hostname", 1210 Port: intstr.IntOrString{ 1211 IntVal: int32(8080), 1212 }, 1213 Scheme: v1.URISchemeHTTP, 1214 }, 1215 }, 1216 }, 1217 }, 1218 }, 1219 NodeSelector: nodeSelector, 1220 }, 1221 } 1222 _, err := f.ClientSet.CoreV1().Pods(ns).Create(ctx, hostPortPod, metav1.CreateOptions{}) 1223 framework.ExpectNoError(err) 1224 1225 err = e2epod.WaitForPodNotPending(ctx, f.ClientSet, ns, podName) 1226 if expectScheduled { 1227 framework.ExpectNoError(err) 1228 } 1229 } 1230 1231 // GetPodsScheduled returns a number of currently scheduled and not scheduled Pods on worker nodes. 1232 func GetPodsScheduled(workerNodes sets.Set[string], pods *v1.PodList) (scheduledPods, notScheduledPods []v1.Pod) { 1233 for _, pod := range pods.Items { 1234 if pod.Spec.NodeName != "" && workerNodes.Has(pod.Spec.NodeName) { 1235 _, scheduledCondition := podutil.GetPodCondition(&pod.Status, v1.PodScheduled) 1236 if scheduledCondition == nil { 1237 framework.Failf("Did not find 'scheduled' condition for pod %+v", podName) 1238 } 1239 if scheduledCondition.Status != v1.ConditionTrue { 1240 framework.Failf("PodStatus isn't 'true' for pod %+v", podName) 1241 } 1242 scheduledPods = append(scheduledPods, pod) 1243 } else if pod.Spec.NodeName == "" { 1244 notScheduledPods = append(notScheduledPods, pod) 1245 } 1246 } 1247 return 1248 } 1249 1250 // getNodeHostIP returns the first internal IP on the node matching the main Cluster IP family 1251 func getNodeHostIP(ctx context.Context, f *framework.Framework, nodeName string) string { 1252 // Get the internal HostIP of the node 1253 family := v1.IPv4Protocol 1254 if framework.TestContext.ClusterIsIPv6() { 1255 family = v1.IPv6Protocol 1256 } 1257 node, err := f.ClientSet.CoreV1().Nodes().Get(ctx, nodeName, metav1.GetOptions{}) 1258 framework.ExpectNoError(err) 1259 ips := e2enode.GetAddressesByTypeAndFamily(node, v1.NodeInternalIP, family) 1260 gomega.Expect(ips).ToNot(gomega.BeEmpty()) 1261 return ips[0] 1262 }