k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/e2e/dra/dra.go (about) 1 /* 2 Copyright 2022 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 dra 18 19 import ( 20 "context" 21 "encoding/json" 22 "errors" 23 "fmt" 24 "strings" 25 "sync" 26 "time" 27 28 "github.com/onsi/ginkgo/v2" 29 "github.com/onsi/gomega" 30 "github.com/onsi/gomega/gcustom" 31 "github.com/onsi/gomega/gstruct" 32 33 v1 "k8s.io/api/core/v1" 34 resourcev1alpha2 "k8s.io/api/resource/v1alpha2" 35 apierrors "k8s.io/apimachinery/pkg/api/errors" 36 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 37 "k8s.io/apimachinery/pkg/labels" 38 "k8s.io/apimachinery/pkg/runtime" 39 "k8s.io/client-go/kubernetes" 40 "k8s.io/dynamic-resource-allocation/controller" 41 "k8s.io/klog/v2" 42 "k8s.io/kubernetes/test/e2e/dra/test-driver/app" 43 "k8s.io/kubernetes/test/e2e/feature" 44 "k8s.io/kubernetes/test/e2e/framework" 45 e2enode "k8s.io/kubernetes/test/e2e/framework/node" 46 e2epod "k8s.io/kubernetes/test/e2e/framework/pod" 47 admissionapi "k8s.io/pod-security-admission/api" 48 "k8s.io/utils/ptr" 49 ) 50 51 const ( 52 // podStartTimeout is how long to wait for the pod to be started. 53 podStartTimeout = 5 * time.Minute 54 ) 55 56 // networkResources can be passed to NewDriver directly. 57 func networkResources() app.Resources { 58 return app.Resources{ 59 Shareable: true, 60 } 61 } 62 63 // perNode returns a function which can be passed to NewDriver. The nodes 64 // parameter has be instantiated, but not initialized yet, so the returned 65 // function has to capture it and use it when being called. 66 func perNode(maxAllocations int, nodes *Nodes) func() app.Resources { 67 return func() app.Resources { 68 return app.Resources{ 69 NodeLocal: true, 70 MaxAllocations: maxAllocations, 71 Nodes: nodes.NodeNames, 72 } 73 } 74 } 75 76 var _ = framework.SIGDescribe("node")("DRA", feature.DynamicResourceAllocation, func() { 77 f := framework.NewDefaultFramework("dra") 78 79 // The driver containers have to run with sufficient privileges to 80 // modify /var/lib/kubelet/plugins. 81 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged 82 83 ginkgo.Context("kubelet", func() { 84 nodes := NewNodes(f, 1, 1) 85 86 ginkgo.Context("with ConfigMap parameters", func() { 87 driver := NewDriver(f, nodes, networkResources) 88 b := newBuilder(f, driver) 89 90 ginkgo.It("registers plugin", func() { 91 ginkgo.By("the driver is running") 92 }) 93 94 ginkgo.It("must retry NodePrepareResources", func(ctx context.Context) { 95 // We have exactly one host. 96 m := MethodInstance{driver.Nodenames()[0], NodePrepareResourcesMethod} 97 98 driver.Fail(m, true) 99 100 ginkgo.By("waiting for container startup to fail") 101 parameters := b.parameters() 102 pod, template := b.podInline(resourcev1alpha2.AllocationModeWaitForFirstConsumer) 103 104 b.create(ctx, parameters, pod, template) 105 106 ginkgo.By("wait for NodePrepareResources call") 107 gomega.Eventually(ctx, func(ctx context.Context) error { 108 if driver.CallCount(m) == 0 { 109 return errors.New("NodePrepareResources not called yet") 110 } 111 return nil 112 }).WithTimeout(podStartTimeout).Should(gomega.Succeed()) 113 114 ginkgo.By("allowing container startup to succeed") 115 callCount := driver.CallCount(m) 116 driver.Fail(m, false) 117 err := e2epod.WaitForPodNameRunningInNamespace(ctx, f.ClientSet, pod.Name, pod.Namespace) 118 framework.ExpectNoError(err, "start pod with inline resource claim") 119 if driver.CallCount(m) == callCount { 120 framework.Fail("NodePrepareResources should have been called again") 121 } 122 }) 123 124 ginkgo.It("must not run a pod if a claim is not reserved for it", func(ctx context.Context) { 125 // Pretend that the resource is allocated and reserved for some other entity. 126 // Until the resourceclaim controller learns to remove reservations for 127 // arbitrary types we can simply fake somthing here. 128 claim := b.externalClaim(resourcev1alpha2.AllocationModeWaitForFirstConsumer) 129 b.create(ctx, claim) 130 131 claim, err := f.ClientSet.ResourceV1alpha2().ResourceClaims(f.Namespace.Name).Get(ctx, claim.Name, metav1.GetOptions{}) 132 framework.ExpectNoError(err, "get claim") 133 134 claim.Finalizers = append(claim.Finalizers, "e2e.test/delete-protection") 135 claim, err = f.ClientSet.ResourceV1alpha2().ResourceClaims(f.Namespace.Name).Update(ctx, claim, metav1.UpdateOptions{}) 136 framework.ExpectNoError(err, "add claim finalizer") 137 138 ginkgo.DeferCleanup(func(ctx context.Context) { 139 claim.Status.Allocation = nil 140 claim.Status.ReservedFor = nil 141 claim, err = f.ClientSet.ResourceV1alpha2().ResourceClaims(f.Namespace.Name).UpdateStatus(ctx, claim, metav1.UpdateOptions{}) 142 framework.ExpectNoError(err, "update claim") 143 144 claim.Finalizers = nil 145 _, err = f.ClientSet.ResourceV1alpha2().ResourceClaims(f.Namespace.Name).Update(ctx, claim, metav1.UpdateOptions{}) 146 framework.ExpectNoError(err, "remove claim finalizer") 147 }) 148 149 claim.Status.Allocation = &resourcev1alpha2.AllocationResult{} 150 claim.Status.DriverName = driver.Name 151 claim.Status.ReservedFor = append(claim.Status.ReservedFor, resourcev1alpha2.ResourceClaimConsumerReference{ 152 APIGroup: "example.com", 153 Resource: "some", 154 Name: "thing", 155 UID: "12345", 156 }) 157 claim, err = f.ClientSet.ResourceV1alpha2().ResourceClaims(f.Namespace.Name).UpdateStatus(ctx, claim, metav1.UpdateOptions{}) 158 framework.ExpectNoError(err, "update claim") 159 160 pod := b.podExternal() 161 162 // This bypasses scheduling and therefore the pod gets 163 // to run on the node although it never gets added to 164 // the `ReservedFor` field of the claim. 165 pod.Spec.NodeName = nodes.NodeNames[0] 166 b.create(ctx, pod) 167 168 gomega.Consistently(ctx, func(ctx context.Context) error { 169 testPod, err := b.f.ClientSet.CoreV1().Pods(pod.Namespace).Get(ctx, pod.Name, metav1.GetOptions{}) 170 if err != nil { 171 return fmt.Errorf("expected the test pod %s to exist: %w", pod.Name, err) 172 } 173 if testPod.Status.Phase != v1.PodPending { 174 return fmt.Errorf("pod %s: unexpected status %s, expected status: %s", pod.Name, testPod.Status.Phase, v1.PodPending) 175 } 176 return nil 177 }, 20*time.Second, 200*time.Millisecond).Should(gomega.BeNil()) 178 }) 179 180 ginkgo.It("must unprepare resources for force-deleted pod", func(ctx context.Context) { 181 parameters := b.parameters() 182 claim := b.externalClaim(resourcev1alpha2.AllocationModeImmediate) 183 pod := b.podExternal() 184 zero := int64(0) 185 pod.Spec.TerminationGracePeriodSeconds = &zero 186 187 b.create(ctx, parameters, claim, pod) 188 189 b.testPod(ctx, f.ClientSet, pod) 190 191 ginkgo.By(fmt.Sprintf("force delete test pod %s", pod.Name)) 192 err := b.f.ClientSet.CoreV1().Pods(b.f.Namespace.Name).Delete(ctx, pod.Name, metav1.DeleteOptions{GracePeriodSeconds: &zero}) 193 if !apierrors.IsNotFound(err) { 194 framework.ExpectNoError(err, "force delete test pod") 195 } 196 197 for host, plugin := range b.driver.Nodes { 198 ginkgo.By(fmt.Sprintf("waiting for resources on %s to be unprepared", host)) 199 gomega.Eventually(plugin.GetPreparedResources).WithTimeout(time.Minute).Should(gomega.BeEmpty(), "prepared claims on host %s", host) 200 } 201 }) 202 203 ginkgo.It("must skip NodePrepareResource if not used by any container", func(ctx context.Context) { 204 parameters := b.parameters() 205 pod, template := b.podInline(resourcev1alpha2.AllocationModeWaitForFirstConsumer) 206 for i := range pod.Spec.Containers { 207 pod.Spec.Containers[i].Resources.Claims = nil 208 } 209 b.create(ctx, parameters, pod, template) 210 framework.ExpectNoError(e2epod.WaitForPodRunningInNamespace(ctx, f.ClientSet, pod), "start pod") 211 for host, plugin := range b.driver.Nodes { 212 gomega.Expect(plugin.GetPreparedResources()).Should(gomega.BeEmpty(), "not claims should be prepared on host %s while pod is running", host) 213 } 214 }) 215 216 }) 217 }) 218 219 // claimTests tries out several different combinations of pods with 220 // claims, both inline and external. 221 claimTests := func(b *builder, driver *Driver, allocationMode resourcev1alpha2.AllocationMode) { 222 ginkgo.It("supports simple pod referencing inline resource claim", func(ctx context.Context) { 223 objects, expectedEnv := b.flexibleParameters() 224 pod, template := b.podInline(allocationMode) 225 objects = append(objects, pod, template) 226 b.create(ctx, objects...) 227 228 b.testPod(ctx, f.ClientSet, pod, expectedEnv...) 229 }) 230 231 ginkgo.It("supports inline claim referenced by multiple containers", func(ctx context.Context) { 232 objects, expectedEnv := b.flexibleParameters() 233 pod, template := b.podInlineMultiple(allocationMode) 234 objects = append(objects, pod, template) 235 b.create(ctx, objects...) 236 237 b.testPod(ctx, f.ClientSet, pod, expectedEnv...) 238 }) 239 240 ginkgo.It("supports simple pod referencing external resource claim", func(ctx context.Context) { 241 objects, expectedEnv := b.flexibleParameters() 242 pod := b.podExternal() 243 claim := b.externalClaim(allocationMode) 244 objects = append(objects, claim, pod) 245 b.create(ctx, objects...) 246 247 b.testPod(ctx, f.ClientSet, pod, expectedEnv...) 248 }) 249 250 ginkgo.It("supports external claim referenced by multiple pods", func(ctx context.Context) { 251 objects, expectedEnv := b.flexibleParameters() 252 pod1 := b.podExternal() 253 pod2 := b.podExternal() 254 pod3 := b.podExternal() 255 claim := b.externalClaim(allocationMode) 256 objects = append(objects, claim, pod1, pod2, pod3) 257 b.create(ctx, objects...) 258 259 for _, pod := range []*v1.Pod{pod1, pod2, pod3} { 260 b.testPod(ctx, f.ClientSet, pod, expectedEnv...) 261 } 262 }) 263 264 ginkgo.It("supports external claim referenced by multiple containers of multiple pods", func(ctx context.Context) { 265 objects, expectedEnv := b.flexibleParameters() 266 pod1 := b.podExternalMultiple() 267 pod2 := b.podExternalMultiple() 268 pod3 := b.podExternalMultiple() 269 claim := b.externalClaim(allocationMode) 270 objects = append(objects, claim, pod1, pod2, pod3) 271 b.create(ctx, objects...) 272 273 for _, pod := range []*v1.Pod{pod1, pod2, pod3} { 274 b.testPod(ctx, f.ClientSet, pod, expectedEnv...) 275 } 276 }) 277 278 ginkgo.It("supports init containers", func(ctx context.Context) { 279 objects, expectedEnv := b.flexibleParameters() 280 pod, template := b.podInline(allocationMode) 281 pod.Spec.InitContainers = []v1.Container{pod.Spec.Containers[0]} 282 pod.Spec.InitContainers[0].Name += "-init" 283 // This must succeed for the pod to start. 284 pod.Spec.InitContainers[0].Command = []string{"sh", "-c", "env | grep user_a=b"} 285 objects = append(objects, pod, template) 286 b.create(ctx, objects...) 287 288 b.testPod(ctx, f.ClientSet, pod, expectedEnv...) 289 }) 290 291 ginkgo.It("removes reservation from claim when pod is done", func(ctx context.Context) { 292 objects, _ := b.flexibleParameters() 293 pod := b.podExternal() 294 claim := b.externalClaim(allocationMode) 295 pod.Spec.Containers[0].Command = []string{"true"} 296 objects = append(objects, claim, pod) 297 b.create(ctx, objects...) 298 299 ginkgo.By("waiting for pod to finish") 300 framework.ExpectNoError(e2epod.WaitForPodNoLongerRunningInNamespace(ctx, f.ClientSet, pod.Name, pod.Namespace), "wait for pod to finish") 301 ginkgo.By("waiting for claim to be unreserved") 302 gomega.Eventually(ctx, func(ctx context.Context) (*resourcev1alpha2.ResourceClaim, error) { 303 return f.ClientSet.ResourceV1alpha2().ResourceClaims(pod.Namespace).Get(ctx, claim.Name, metav1.GetOptions{}) 304 }).WithTimeout(f.Timeouts.PodDelete).Should(gomega.HaveField("Status.ReservedFor", gomega.BeEmpty()), "reservation should have been removed") 305 }) 306 307 ginkgo.It("deletes generated claims when pod is done", func(ctx context.Context) { 308 objects, _ := b.flexibleParameters() 309 pod, template := b.podInline(allocationMode) 310 pod.Spec.Containers[0].Command = []string{"true"} 311 objects = append(objects, template, pod) 312 b.create(ctx, objects...) 313 314 ginkgo.By("waiting for pod to finish") 315 framework.ExpectNoError(e2epod.WaitForPodNoLongerRunningInNamespace(ctx, f.ClientSet, pod.Name, pod.Namespace), "wait for pod to finish") 316 ginkgo.By("waiting for claim to be deleted") 317 gomega.Eventually(ctx, func(ctx context.Context) ([]resourcev1alpha2.ResourceClaim, error) { 318 claims, err := f.ClientSet.ResourceV1alpha2().ResourceClaims(pod.Namespace).List(ctx, metav1.ListOptions{}) 319 if err != nil { 320 return nil, err 321 } 322 return claims.Items, nil 323 }).WithTimeout(f.Timeouts.PodDelete).Should(gomega.BeEmpty(), "claim should have been deleted") 324 }) 325 326 ginkgo.It("does not delete generated claims when pod is restarting", func(ctx context.Context) { 327 objects, _ := b.flexibleParameters() 328 pod, template := b.podInline(allocationMode) 329 pod.Spec.Containers[0].Command = []string{"sh", "-c", "sleep 1; exit 1"} 330 pod.Spec.RestartPolicy = v1.RestartPolicyAlways 331 objects = append(objects, template, pod) 332 b.create(ctx, objects...) 333 334 ginkgo.By("waiting for pod to restart twice") 335 gomega.Eventually(ctx, func(ctx context.Context) (*v1.Pod, error) { 336 return f.ClientSet.CoreV1().Pods(pod.Namespace).Get(ctx, pod.Name, metav1.GetOptions{}) 337 }).WithTimeout(f.Timeouts.PodStartSlow).Should(gomega.HaveField("Status.ContainerStatuses", gomega.ContainElements(gomega.HaveField("RestartCount", gomega.BeNumerically(">=", 2))))) 338 if driver.Controller != nil { 339 gomega.Expect(driver.Controller.GetNumAllocations()).To(gomega.Equal(int64(1)), "number of allocations") 340 } 341 }) 342 343 ginkgo.It("must deallocate after use when using delayed allocation", func(ctx context.Context) { 344 objects, expectedEnv := b.flexibleParameters() 345 pod := b.podExternal() 346 claim := b.externalClaim(resourcev1alpha2.AllocationModeWaitForFirstConsumer) 347 objects = append(objects, claim, pod) 348 b.create(ctx, objects...) 349 350 gomega.Eventually(ctx, func(ctx context.Context) (*resourcev1alpha2.ResourceClaim, error) { 351 return b.f.ClientSet.ResourceV1alpha2().ResourceClaims(b.f.Namespace.Name).Get(ctx, claim.Name, metav1.GetOptions{}) 352 }).WithTimeout(f.Timeouts.PodDelete).ShouldNot(gomega.HaveField("Status.Allocation", (*resourcev1alpha2.AllocationResult)(nil))) 353 354 b.testPod(ctx, f.ClientSet, pod, expectedEnv...) 355 356 ginkgo.By(fmt.Sprintf("deleting pod %s", klog.KObj(pod))) 357 framework.ExpectNoError(b.f.ClientSet.CoreV1().Pods(b.f.Namespace.Name).Delete(ctx, pod.Name, metav1.DeleteOptions{})) 358 359 ginkgo.By("waiting for claim to get deallocated") 360 gomega.Eventually(ctx, func(ctx context.Context) (*resourcev1alpha2.ResourceClaim, error) { 361 return b.f.ClientSet.ResourceV1alpha2().ResourceClaims(b.f.Namespace.Name).Get(ctx, claim.Name, metav1.GetOptions{}) 362 }).WithTimeout(f.Timeouts.PodDelete).Should(gomega.HaveField("Status.Allocation", (*resourcev1alpha2.AllocationResult)(nil))) 363 }) 364 } 365 366 singleNodeTests := func(parameterMode parameterMode) { 367 nodes := NewNodes(f, 1, 1) 368 maxAllocations := 1 369 numPods := 10 370 generateResources := func() app.Resources { 371 resources := perNode(maxAllocations, nodes)() 372 resources.Shareable = true 373 return resources 374 } 375 driver := NewDriver(f, nodes, generateResources) // All tests get their own driver instance. 376 driver.parameterMode = parameterMode 377 b := newBuilder(f, driver) 378 // We need the parameters name *before* creating it. 379 b.parametersCounter = 1 380 b.classParametersName = b.parametersName() 381 382 ginkgo.It("supports claim and class parameters", func(ctx context.Context) { 383 objects, expectedEnv := b.flexibleParameters() 384 385 pod, template := b.podInline(resourcev1alpha2.AllocationModeWaitForFirstConsumer) 386 objects = append(objects, pod, template) 387 388 b.create(ctx, objects...) 389 390 b.testPod(ctx, f.ClientSet, pod, expectedEnv...) 391 }) 392 393 ginkgo.It("supports reusing resources", func(ctx context.Context) { 394 objects, expectedEnv := b.flexibleParameters() 395 pods := make([]*v1.Pod, numPods) 396 for i := 0; i < numPods; i++ { 397 pod, template := b.podInline(resourcev1alpha2.AllocationModeWaitForFirstConsumer) 398 pods[i] = pod 399 objects = append(objects, pod, template) 400 } 401 402 b.create(ctx, objects...) 403 404 // We don't know the order. All that matters is that all of them get scheduled eventually. 405 var wg sync.WaitGroup 406 wg.Add(numPods) 407 for i := 0; i < numPods; i++ { 408 pod := pods[i] 409 go func() { 410 defer ginkgo.GinkgoRecover() 411 defer wg.Done() 412 b.testPod(ctx, f.ClientSet, pod, expectedEnv...) 413 err := f.ClientSet.CoreV1().Pods(pod.Namespace).Delete(ctx, pod.Name, metav1.DeleteOptions{}) 414 framework.ExpectNoError(err, "delete pod") 415 framework.ExpectNoError(e2epod.WaitForPodNotFoundInNamespace(ctx, f.ClientSet, pod.Name, pod.Namespace, f.Timeouts.PodStartSlow)) 416 }() 417 } 418 wg.Wait() 419 }) 420 421 ginkgo.It("supports sharing a claim concurrently", func(ctx context.Context) { 422 objects, expectedEnv := b.flexibleParameters() 423 objects = append(objects, b.externalClaim(resourcev1alpha2.AllocationModeWaitForFirstConsumer)) 424 425 pods := make([]*v1.Pod, numPods) 426 for i := 0; i < numPods; i++ { 427 pod := b.podExternal() 428 pods[i] = pod 429 objects = append(objects, pod) 430 } 431 432 b.create(ctx, objects...) 433 434 // We don't know the order. All that matters is that all of them get scheduled eventually. 435 f.Timeouts.PodStartSlow *= time.Duration(numPods) 436 var wg sync.WaitGroup 437 wg.Add(numPods) 438 for i := 0; i < numPods; i++ { 439 pod := pods[i] 440 go func() { 441 defer ginkgo.GinkgoRecover() 442 defer wg.Done() 443 b.testPod(ctx, f.ClientSet, pod, expectedEnv...) 444 }() 445 } 446 wg.Wait() 447 }) 448 449 f.It("supports sharing a claim sequentially", f.WithSlow(), func(ctx context.Context) { 450 objects, expectedEnv := b.flexibleParameters() 451 numPods := numPods / 2 452 453 // Change from "shareable" to "not shareable", if possible. 454 switch parameterMode { 455 case parameterModeConfigMap: 456 ginkgo.Skip("cannot change the driver's controller behavior on-the-fly") 457 case parameterModeTranslated, parameterModeStructured: 458 objects[len(objects)-1].(*resourcev1alpha2.ResourceClaimParameters).Shareable = false 459 } 460 461 objects = append(objects, b.externalClaim(resourcev1alpha2.AllocationModeWaitForFirstConsumer)) 462 463 pods := make([]*v1.Pod, numPods) 464 for i := 0; i < numPods; i++ { 465 pod := b.podExternal() 466 pods[i] = pod 467 objects = append(objects, pod) 468 } 469 470 b.create(ctx, objects...) 471 472 // We don't know the order. All that matters is that all of them get scheduled eventually. 473 f.Timeouts.PodStartSlow *= time.Duration(numPods) 474 var wg sync.WaitGroup 475 wg.Add(numPods) 476 for i := 0; i < numPods; i++ { 477 pod := pods[i] 478 go func() { 479 defer ginkgo.GinkgoRecover() 480 defer wg.Done() 481 b.testPod(ctx, f.ClientSet, pod, expectedEnv...) 482 // We need to delete each running pod, otherwise the others cannot use the claim. 483 err := f.ClientSet.CoreV1().Pods(pod.Namespace).Delete(ctx, pod.Name, metav1.DeleteOptions{}) 484 framework.ExpectNoError(err, "delete pod") 485 framework.ExpectNoError(e2epod.WaitForPodNotFoundInNamespace(ctx, f.ClientSet, pod.Name, pod.Namespace, f.Timeouts.PodStartSlow)) 486 }() 487 } 488 wg.Wait() 489 }) 490 491 ginkgo.It("retries pod scheduling after creating resource class", func(ctx context.Context) { 492 objects, expectedEnv := b.flexibleParameters() 493 pod, template := b.podInline(resourcev1alpha2.AllocationModeWaitForFirstConsumer) 494 class, err := f.ClientSet.ResourceV1alpha2().ResourceClasses().Get(ctx, template.Spec.Spec.ResourceClassName, metav1.GetOptions{}) 495 framework.ExpectNoError(err) 496 template.Spec.Spec.ResourceClassName += "-b" 497 objects = append(objects, template, pod) 498 b.create(ctx, objects...) 499 500 framework.ExpectNoError(e2epod.WaitForPodNameUnschedulableInNamespace(ctx, f.ClientSet, pod.Name, pod.Namespace)) 501 502 class.UID = "" 503 class.ResourceVersion = "" 504 class.Name = template.Spec.Spec.ResourceClassName 505 b.create(ctx, class) 506 507 b.testPod(ctx, f.ClientSet, pod, expectedEnv...) 508 }) 509 510 ginkgo.It("retries pod scheduling after updating resource class", func(ctx context.Context) { 511 objects, expectedEnv := b.flexibleParameters() 512 pod, template := b.podInline(resourcev1alpha2.AllocationModeWaitForFirstConsumer) 513 514 // First modify the class so that it matches no nodes. 515 class, err := f.ClientSet.ResourceV1alpha2().ResourceClasses().Get(ctx, template.Spec.Spec.ResourceClassName, metav1.GetOptions{}) 516 framework.ExpectNoError(err) 517 class.SuitableNodes = &v1.NodeSelector{ 518 NodeSelectorTerms: []v1.NodeSelectorTerm{ 519 { 520 MatchExpressions: []v1.NodeSelectorRequirement{ 521 { 522 Key: "no-such-label", 523 Operator: v1.NodeSelectorOpIn, 524 Values: []string{"no-such-value"}, 525 }, 526 }, 527 }, 528 }, 529 } 530 class, err = f.ClientSet.ResourceV1alpha2().ResourceClasses().Update(ctx, class, metav1.UpdateOptions{}) 531 framework.ExpectNoError(err) 532 533 // Now create the pod. 534 objects = append(objects, template, pod) 535 b.create(ctx, objects...) 536 537 framework.ExpectNoError(e2epod.WaitForPodNameUnschedulableInNamespace(ctx, f.ClientSet, pod.Name, pod.Namespace)) 538 539 // Unblock the pod. 540 class.SuitableNodes = nil 541 _, err = f.ClientSet.ResourceV1alpha2().ResourceClasses().Update(ctx, class, metav1.UpdateOptions{}) 542 framework.ExpectNoError(err) 543 544 b.testPod(ctx, f.ClientSet, pod, expectedEnv...) 545 }) 546 547 ginkgo.It("runs a pod without a generated resource claim", func(ctx context.Context) { 548 pod, _ /* template */ := b.podInline(resourcev1alpha2.AllocationModeWaitForFirstConsumer) 549 created := b.create(ctx, pod) 550 pod = created[0].(*v1.Pod) 551 552 // Normally, this pod would be stuck because the 553 // ResourceClaim cannot be created without the 554 // template. We allow it to run by communicating 555 // through the status that the ResourceClaim is not 556 // needed. 557 pod.Status.ResourceClaimStatuses = []v1.PodResourceClaimStatus{ 558 {Name: pod.Spec.ResourceClaims[0].Name, ResourceClaimName: nil}, 559 } 560 _, err := f.ClientSet.CoreV1().Pods(pod.Namespace).UpdateStatus(ctx, pod, metav1.UpdateOptions{}) 561 framework.ExpectNoError(err) 562 framework.ExpectNoError(e2epod.WaitForPodRunningInNamespace(ctx, f.ClientSet, pod)) 563 }) 564 565 ginkgo.Context("with delayed allocation", func() { 566 claimTests(b, driver, resourcev1alpha2.AllocationModeWaitForFirstConsumer) 567 }) 568 569 ginkgo.Context("with immediate allocation", func() { 570 claimTests(b, driver, resourcev1alpha2.AllocationModeImmediate) 571 }) 572 } 573 574 // These tests depend on having more than one node and a DRA driver controller. 575 multiNodeDRAControllerTests := func(nodes *Nodes) { 576 driver := NewDriver(f, nodes, networkResources) 577 b := newBuilder(f, driver) 578 579 ginkgo.It("schedules onto different nodes", func(ctx context.Context) { 580 parameters := b.parameters() 581 label := "app.kubernetes.io/instance" 582 instance := f.UniqueName + "-test-app" 583 antiAffinity := &v1.Affinity{ 584 PodAntiAffinity: &v1.PodAntiAffinity{ 585 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 586 { 587 TopologyKey: "kubernetes.io/hostname", 588 LabelSelector: &metav1.LabelSelector{ 589 MatchLabels: map[string]string{ 590 label: instance, 591 }, 592 }, 593 }, 594 }, 595 }, 596 } 597 createPod := func() *v1.Pod { 598 pod := b.podExternal() 599 pod.Labels[label] = instance 600 pod.Spec.Affinity = antiAffinity 601 return pod 602 } 603 pod1 := createPod() 604 pod2 := createPod() 605 claim := b.externalClaim(resourcev1alpha2.AllocationModeWaitForFirstConsumer) 606 b.create(ctx, parameters, claim, pod1, pod2) 607 608 for _, pod := range []*v1.Pod{pod1, pod2} { 609 err := e2epod.WaitForPodRunningInNamespace(ctx, f.ClientSet, pod) 610 framework.ExpectNoError(err, "start pod") 611 } 612 }) 613 614 // This test covers aspects of non graceful node shutdown by DRA controller 615 // More details about this can be found in the KEP: 616 // https://github.com/kubernetes/enhancements/tree/master/keps/sig-storage/2268-non-graceful-shutdown 617 // NOTE: this test depends on kind. It will only work with kind cluster as it shuts down one of the 618 // nodes by running `docker stop <node name>`, which is very kind-specific. 619 f.It(f.WithSerial(), f.WithDisruptive(), f.WithSlow(), "must deallocate on non graceful node shutdown", func(ctx context.Context) { 620 ginkgo.By("create test pod") 621 parameters := b.parameters() 622 label := "app.kubernetes.io/instance" 623 instance := f.UniqueName + "-test-app" 624 pod := b.podExternal() 625 pod.Labels[label] = instance 626 claim := b.externalClaim(resourcev1alpha2.AllocationModeWaitForFirstConsumer) 627 b.create(ctx, parameters, claim, pod) 628 629 ginkgo.By("wait for test pod " + pod.Name + " to run") 630 labelSelector := labels.SelectorFromSet(labels.Set(pod.Labels)) 631 pods, err := e2epod.WaitForPodsWithLabelRunningReady(ctx, f.ClientSet, pod.Namespace, labelSelector, 1, framework.PodStartTimeout) 632 framework.ExpectNoError(err, "start pod") 633 runningPod := &pods.Items[0] 634 635 nodeName := runningPod.Spec.NodeName 636 // Prevent builder tearDown to fail waiting for unprepared resources 637 delete(b.driver.Nodes, nodeName) 638 ginkgo.By("stop node " + nodeName + " non gracefully") 639 _, stderr, err := framework.RunCmd("docker", "stop", nodeName) 640 gomega.Expect(stderr).To(gomega.BeEmpty()) 641 framework.ExpectNoError(err) 642 ginkgo.DeferCleanup(framework.RunCmd, "docker", "start", nodeName) 643 if ok := e2enode.WaitForNodeToBeNotReady(ctx, f.ClientSet, nodeName, f.Timeouts.NodeNotReady); !ok { 644 framework.Failf("Node %s failed to enter NotReady state", nodeName) 645 } 646 647 ginkgo.By("apply out-of-service taint on node " + nodeName) 648 taint := v1.Taint{ 649 Key: v1.TaintNodeOutOfService, 650 Effect: v1.TaintEffectNoExecute, 651 } 652 e2enode.AddOrUpdateTaintOnNode(ctx, f.ClientSet, nodeName, taint) 653 e2enode.ExpectNodeHasTaint(ctx, f.ClientSet, nodeName, &taint) 654 ginkgo.DeferCleanup(e2enode.RemoveTaintOffNode, f.ClientSet, nodeName, taint) 655 656 ginkgo.By("waiting for claim to get deallocated") 657 gomega.Eventually(ctx, framework.GetObject(b.f.ClientSet.ResourceV1alpha2().ResourceClaims(b.f.Namespace.Name).Get, claim.Name, metav1.GetOptions{})).WithTimeout(f.Timeouts.PodDelete).Should(gomega.HaveField("Status.Allocation", gomega.BeNil())) 658 }) 659 } 660 661 // The following tests only make sense when there is more than one node. 662 // They get skipped when there's only one node. 663 multiNodeTests := func(parameterMode parameterMode) { 664 nodes := NewNodes(f, 2, 8) 665 666 if parameterMode == parameterModeConfigMap { 667 ginkgo.Context("with network-attached resources", func() { 668 multiNodeDRAControllerTests(nodes) 669 }) 670 671 ginkgo.Context("reallocation", func() { 672 var allocateWrapper2 app.AllocateWrapperType 673 driver := NewDriver(f, nodes, perNode(1, nodes)) 674 driver2 := NewDriver(f, nodes, func() app.Resources { 675 return app.Resources{ 676 NodeLocal: true, 677 MaxAllocations: 1, 678 Nodes: nodes.NodeNames, 679 680 AllocateWrapper: func( 681 ctx context.Context, 682 claimAllocations []*controller.ClaimAllocation, 683 selectedNode string, 684 handler func( 685 ctx context.Context, 686 claimAllocations []*controller.ClaimAllocation, 687 selectedNode string), 688 ) { 689 allocateWrapper2(ctx, claimAllocations, selectedNode, handler) 690 }, 691 } 692 }) 693 driver2.NameSuffix = "-other" 694 695 b := newBuilder(f, driver) 696 b2 := newBuilder(f, driver2) 697 698 ginkgo.It("works", func(ctx context.Context) { 699 // A pod with multiple claims can run on a node, but 700 // only if allocation of all succeeds. This 701 // test simulates the scenario where one claim 702 // gets allocated from one driver, but the claims 703 // from second driver fail allocation because of a 704 // race with some other pod. 705 // 706 // To ensure the right timing, allocation of the 707 // claims from second driver are delayed while 708 // creating another pod that gets the remaining 709 // resource on the node from second driver. 710 ctx, cancel := context.WithCancel(ctx) 711 defer cancel() 712 713 parameters1 := b.parameters() 714 parameters2 := b2.parameters() 715 // Order is relevant here: each pod must be matched with its own claim. 716 pod1claim1 := b.externalClaim(resourcev1alpha2.AllocationModeWaitForFirstConsumer) 717 pod1 := b.podExternal() 718 pod2claim1 := b2.externalClaim(resourcev1alpha2.AllocationModeWaitForFirstConsumer) 719 pod2 := b2.podExternal() 720 721 // Add another claim to pod1. 722 pod1claim2 := b2.externalClaim(resourcev1alpha2.AllocationModeWaitForFirstConsumer) 723 pod1.Spec.ResourceClaims = append(pod1.Spec.ResourceClaims, 724 v1.PodResourceClaim{ 725 Name: "claim-other", 726 Source: v1.ClaimSource{ 727 ResourceClaimName: &pod1claim2.Name, 728 }, 729 }, 730 ) 731 732 // Allocating the second claim in pod1 has to wait until pod2 has 733 // consumed the available resources on the node. 734 blockClaim, cancelBlockClaim := context.WithCancel(ctx) 735 defer cancelBlockClaim() 736 allocateWrapper2 = func(ctx context.Context, 737 claimAllocations []*controller.ClaimAllocation, 738 selectedNode string, 739 handler func(ctx context.Context, 740 claimAllocations []*controller.ClaimAllocation, 741 selectedNode string), 742 ) { 743 if claimAllocations[0].Claim.Name == pod1claim2.Name { 744 <-blockClaim.Done() 745 } 746 handler(ctx, claimAllocations, selectedNode) 747 } 748 749 b.create(ctx, parameters1, parameters2, pod1claim1, pod1claim2, pod1) 750 751 ginkgo.By("waiting for one claim from driver1 to be allocated") 752 var nodeSelector *v1.NodeSelector 753 gomega.Eventually(ctx, func(ctx context.Context) (int, error) { 754 claims, err := f.ClientSet.ResourceV1alpha2().ResourceClaims(f.Namespace.Name).List(ctx, metav1.ListOptions{}) 755 if err != nil { 756 return 0, err 757 } 758 allocated := 0 759 for _, claim := range claims.Items { 760 if claim.Status.Allocation != nil { 761 allocated++ 762 nodeSelector = claim.Status.Allocation.AvailableOnNodes 763 } 764 } 765 return allocated, nil 766 }).WithTimeout(time.Minute).Should(gomega.Equal(1), "one claim allocated") 767 768 // Now create a second pod which we force to 769 // run on the same node that is currently being 770 // considered for the first one. We know what 771 // the node selector looks like and can 772 // directly access the key and value from it. 773 ginkgo.By(fmt.Sprintf("create second pod on the same node %s", nodeSelector)) 774 775 req := nodeSelector.NodeSelectorTerms[0].MatchExpressions[0] 776 node := req.Values[0] 777 pod2.Spec.NodeSelector = map[string]string{req.Key: node} 778 779 b2.create(ctx, pod2claim1, pod2) 780 framework.ExpectNoError(e2epod.WaitForPodRunningInNamespace(ctx, f.ClientSet, pod2), "start pod 2") 781 782 // Allow allocation of second claim in pod1 to proceed. It should fail now 783 // and the other node must be used instead, after deallocating 784 // the first claim. 785 ginkgo.By("move first pod to other node") 786 cancelBlockClaim() 787 788 framework.ExpectNoError(e2epod.WaitForPodRunningInNamespace(ctx, f.ClientSet, pod1), "start pod 1") 789 pod1, err := f.ClientSet.CoreV1().Pods(pod1.Namespace).Get(ctx, pod1.Name, metav1.GetOptions{}) 790 framework.ExpectNoError(err, "get first pod") 791 if pod1.Spec.NodeName == "" { 792 framework.Fail("first pod should be running on node, was not scheduled") 793 } 794 gomega.Expect(pod1.Spec.NodeName).ToNot(gomega.Equal(node), "first pod should run on different node than second one") 795 gomega.Expect(driver.Controller.GetNumDeallocations()).To(gomega.Equal(int64(1)), "number of deallocations") 796 }) 797 }) 798 } 799 800 ginkgo.Context("with node-local resources", func() { 801 driver := NewDriver(f, nodes, perNode(1, nodes)) 802 driver.parameterMode = parameterMode 803 b := newBuilder(f, driver) 804 805 tests := func(allocationMode resourcev1alpha2.AllocationMode) { 806 ginkgo.It("uses all resources", func(ctx context.Context) { 807 objs, _ := b.flexibleParameters() 808 var pods []*v1.Pod 809 for i := 0; i < len(nodes.NodeNames); i++ { 810 pod, template := b.podInline(allocationMode) 811 pods = append(pods, pod) 812 objs = append(objs, pod, template) 813 } 814 b.create(ctx, objs...) 815 816 for _, pod := range pods { 817 err := e2epod.WaitForPodRunningInNamespace(ctx, f.ClientSet, pod) 818 framework.ExpectNoError(err, "start pod") 819 } 820 821 // The pods all should run on different 822 // nodes because the maximum number of 823 // claims per node was limited to 1 for 824 // this test. 825 // 826 // We cannot know for sure why the pods 827 // ran on two different nodes (could 828 // also be a coincidence) but if they 829 // don't cover all nodes, then we have 830 // a problem. 831 used := make(map[string]*v1.Pod) 832 for _, pod := range pods { 833 pod, err := f.ClientSet.CoreV1().Pods(pod.Namespace).Get(ctx, pod.Name, metav1.GetOptions{}) 834 framework.ExpectNoError(err, "get pod") 835 nodeName := pod.Spec.NodeName 836 if other, ok := used[nodeName]; ok { 837 framework.Failf("Pod %s got started on the same node %s as pod %s although claim allocation should have been limited to one claim per node.", pod.Name, nodeName, other.Name) 838 } 839 used[nodeName] = pod 840 } 841 }) 842 } 843 844 ginkgo.Context("with delayed allocation", func() { 845 tests(resourcev1alpha2.AllocationModeWaitForFirstConsumer) 846 }) 847 848 ginkgo.Context("with immediate allocation", func() { 849 tests(resourcev1alpha2.AllocationModeImmediate) 850 }) 851 }) 852 } 853 854 tests := func(parameterMode parameterMode) { 855 ginkgo.Context("on single node", func() { 856 singleNodeTests(parameterMode) 857 }) 858 ginkgo.Context("on multiple nodes", func() { 859 multiNodeTests(parameterMode) 860 }) 861 } 862 863 ginkgo.Context("with ConfigMap parameters", func() { tests(parameterModeConfigMap) }) 864 ginkgo.Context("with translated parameters", func() { tests(parameterModeTranslated) }) 865 ginkgo.Context("with structured parameters", func() { tests(parameterModeStructured) }) 866 867 // TODO (https://github.com/kubernetes/kubernetes/issues/123699): move most of the test below into `testDriver` so that they get 868 // executed with different parameters. 869 870 ginkgo.Context("cluster", func() { 871 nodes := NewNodes(f, 1, 1) 872 driver := NewDriver(f, nodes, networkResources) 873 b := newBuilder(f, driver) 874 875 ginkgo.It("truncates the name of a generated resource claim", func(ctx context.Context) { 876 parameters := b.parameters() 877 pod, template := b.podInline(resourcev1alpha2.AllocationModeWaitForFirstConsumer) 878 pod.Name = strings.Repeat("p", 63) 879 pod.Spec.ResourceClaims[0].Name = strings.Repeat("c", 63) 880 pod.Spec.Containers[0].Resources.Claims[0].Name = pod.Spec.ResourceClaims[0].Name 881 b.create(ctx, parameters, template, pod) 882 883 b.testPod(ctx, f.ClientSet, pod) 884 }) 885 }) 886 887 // The following tests are all about behavior in combination with a 888 // control-plane DRA driver controller. 889 ginkgo.Context("cluster with DRA driver controller", func() { 890 nodes := NewNodes(f, 1, 4) 891 892 ginkgo.Context("with structured parameters", func() { 893 driver := NewDriver(f, nodes, perNode(1, nodes)) 894 driver.parameterMode = parameterModeStructured 895 896 f.It("must manage ResourceSlices", f.WithSlow(), func(ctx context.Context) { 897 nodeName := nodes.NodeNames[0] 898 driverName := driver.Name 899 900 // Check for gRPC call on one node. If that already fails, then 901 // we have a fundamental problem. 902 m := MethodInstance{nodeName, NodeListAndWatchResourcesMethod} 903 ginkgo.By("wait for NodeListAndWatchResources call") 904 gomega.Eventually(ctx, func() int64 { 905 return driver.CallCount(m) 906 }).WithTimeout(podStartTimeout).Should(gomega.BeNumerically(">", int64(0)), "NodeListAndWatchResources call count") 907 908 // Now check for exactly the right set of objects for all nodes. 909 ginkgo.By("check if ResourceSlice object(s) exist on the API server") 910 resourceClient := f.ClientSet.ResourceV1alpha2().ResourceSlices() 911 var expectedObjects []any 912 for _, nodeName := range nodes.NodeNames { 913 node, err := f.ClientSet.CoreV1().Nodes().Get(ctx, nodeName, metav1.GetOptions{}) 914 framework.ExpectNoError(err, "get node") 915 expectedObjects = append(expectedObjects, 916 gstruct.MatchAllFields(gstruct.Fields{ 917 "TypeMeta": gstruct.Ignore(), 918 "ObjectMeta": gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{ 919 "OwnerReferences": gomega.ContainElements( 920 gstruct.MatchAllFields(gstruct.Fields{ 921 "APIVersion": gomega.Equal("v1"), 922 "Kind": gomega.Equal("Node"), 923 "Name": gomega.Equal(nodeName), 924 "UID": gomega.Equal(node.UID), 925 "Controller": gomega.Equal(ptr.To(true)), 926 "BlockOwnerDeletion": gomega.BeNil(), 927 }), 928 ), 929 }), 930 "NodeName": gomega.Equal(nodeName), 931 "DriverName": gomega.Equal(driver.Name), 932 "ResourceModel": gomega.Equal(resourcev1alpha2.ResourceModel{NamedResources: &resourcev1alpha2.NamedResourcesResources{ 933 Instances: []resourcev1alpha2.NamedResourcesInstance{{Name: "instance-00"}}, 934 }}), 935 }), 936 ) 937 } 938 matchSlices := gomega.ContainElements(expectedObjects...) 939 getSlices := func(ctx context.Context) ([]resourcev1alpha2.ResourceSlice, error) { 940 slices, err := resourceClient.List(ctx, metav1.ListOptions{FieldSelector: fmt.Sprintf("driverName=%s", driverName)}) 941 if err != nil { 942 return nil, err 943 } 944 return slices.Items, nil 945 } 946 gomega.Eventually(ctx, getSlices).WithTimeout(20 * time.Second).Should(matchSlices) 947 gomega.Consistently(ctx, getSlices).WithTimeout(20 * time.Second).Should(matchSlices) 948 949 // Removal of node resource slice is tested by the general driver removal code. 950 }) 951 952 // TODO (https://github.com/kubernetes/kubernetes/issues/123699): more test scenarios: 953 // - driver returns "unimplemented" as method response 954 // - driver returns "Unimplemented" as part of stream 955 // - driver returns EOF 956 // - driver changes resources 957 // 958 // None of those matter if the publishing gets moved into the driver itself, 959 // which is the goal for 1.31 to support version skew for kubelet. 960 }) 961 962 ginkgo.Context("with local unshared resources", func() { 963 driver := NewDriver(f, nodes, func() app.Resources { 964 return app.Resources{ 965 NodeLocal: true, 966 MaxAllocations: 10, 967 Nodes: nodes.NodeNames, 968 } 969 }) 970 b := newBuilder(f, driver) 971 972 // This test covers some special code paths in the scheduler: 973 // - Patching the ReservedFor during PreBind because in contrast 974 // to claims specifically allocated for a pod, here the claim 975 // gets allocated without reserving it. 976 // - Error handling when PreBind fails: multiple attempts to bind pods 977 // are started concurrently, only one attempt succeeds. 978 // - Removing a ReservedFor entry because the first inline claim gets 979 // reserved during allocation. 980 ginkgo.It("reuses an allocated immediate claim", func(ctx context.Context) { 981 objects := []klog.KMetadata{ 982 b.parameters(), 983 b.externalClaim(resourcev1alpha2.AllocationModeImmediate), 984 } 985 podExternal := b.podExternal() 986 987 // Create many pods to increase the chance that the scheduler will 988 // try to bind two pods at the same time. 989 numPods := 5 990 for i := 0; i < numPods; i++ { 991 podInline, claimTemplate := b.podInline(resourcev1alpha2.AllocationModeWaitForFirstConsumer) 992 podInline.Spec.Containers[0].Resources.Claims = append(podInline.Spec.Containers[0].Resources.Claims, podExternal.Spec.Containers[0].Resources.Claims[0]) 993 podInline.Spec.ResourceClaims = append(podInline.Spec.ResourceClaims, podExternal.Spec.ResourceClaims[0]) 994 objects = append(objects, claimTemplate, podInline) 995 } 996 b.create(ctx, objects...) 997 998 var runningPod *v1.Pod 999 haveRunningPod := gcustom.MakeMatcher(func(pods []v1.Pod) (bool, error) { 1000 numRunning := 0 1001 runningPod = nil 1002 for _, pod := range pods { 1003 if pod.Status.Phase == v1.PodRunning { 1004 pod := pod // Don't keep pointer to loop variable... 1005 runningPod = &pod 1006 numRunning++ 1007 } 1008 } 1009 return numRunning == 1, nil 1010 }).WithTemplate("Expected one running Pod.\nGot instead:\n{{.FormattedActual}}") 1011 1012 for i := 0; i < numPods; i++ { 1013 ginkgo.By("waiting for exactly one pod to start") 1014 runningPod = nil 1015 gomega.Eventually(ctx, b.listTestPods).WithTimeout(f.Timeouts.PodStartSlow).Should(haveRunningPod) 1016 1017 ginkgo.By("checking that no other pod gets scheduled") 1018 havePendingPods := gcustom.MakeMatcher(func(pods []v1.Pod) (bool, error) { 1019 numPending := 0 1020 for _, pod := range pods { 1021 if pod.Status.Phase == v1.PodPending { 1022 numPending++ 1023 } 1024 } 1025 return numPending == numPods-1-i, nil 1026 }).WithTemplate("Expected only one running Pod.\nGot instead:\n{{.FormattedActual}}") 1027 gomega.Consistently(ctx, b.listTestPods).WithTimeout(time.Second).Should(havePendingPods) 1028 1029 ginkgo.By(fmt.Sprintf("deleting pod %s", klog.KObj(runningPod))) 1030 framework.ExpectNoError(b.f.ClientSet.CoreV1().Pods(b.f.Namespace.Name).Delete(ctx, runningPod.Name, metav1.DeleteOptions{})) 1031 1032 ginkgo.By(fmt.Sprintf("waiting for pod %s to disappear", klog.KObj(runningPod))) 1033 framework.ExpectNoError(e2epod.WaitForPodNotFoundInNamespace(ctx, b.f.ClientSet, runningPod.Name, runningPod.Namespace, f.Timeouts.PodDelete)) 1034 } 1035 }) 1036 }) 1037 1038 ginkgo.Context("with shared network resources", func() { 1039 driver := NewDriver(f, nodes, networkResources) 1040 b := newBuilder(f, driver) 1041 1042 // This test complements "reuses an allocated immediate claim" above: 1043 // because the claim can be shared, each PreBind attempt succeeds. 1044 ginkgo.It("shares an allocated immediate claim", func(ctx context.Context) { 1045 objects := []klog.KMetadata{ 1046 b.parameters(), 1047 b.externalClaim(resourcev1alpha2.AllocationModeImmediate), 1048 } 1049 // Create many pods to increase the chance that the scheduler will 1050 // try to bind two pods at the same time. 1051 numPods := 5 1052 pods := make([]*v1.Pod, numPods) 1053 for i := 0; i < numPods; i++ { 1054 pods[i] = b.podExternal() 1055 objects = append(objects, pods[i]) 1056 } 1057 b.create(ctx, objects...) 1058 1059 ginkgo.By("waiting all pods to start") 1060 framework.ExpectNoError(e2epod.WaitForPodsRunning(ctx, b.f.ClientSet, f.Namespace.Name, numPods+len(nodes.NodeNames) /* driver(s) */, f.Timeouts.PodStartSlow)) 1061 }) 1062 }) 1063 1064 // kube-controller-manager can trigger delayed allocation for pods where the 1065 // node name was already selected when creating the pod. For immediate 1066 // allocation, the creator has to ensure that the node matches the claims. 1067 // This does not work for resource claim templates and only isn't 1068 // a problem here because the resource is network-attached and available 1069 // on all nodes. 1070 preScheduledTests := func(b *builder, driver *Driver, allocationMode resourcev1alpha2.AllocationMode) { 1071 ginkgo.It("supports scheduled pod referencing inline resource claim", func(ctx context.Context) { 1072 parameters := b.parameters() 1073 pod, template := b.podInline(allocationMode) 1074 pod.Spec.NodeName = nodes.NodeNames[0] 1075 b.create(ctx, parameters, pod, template) 1076 1077 b.testPod(ctx, f.ClientSet, pod) 1078 }) 1079 1080 ginkgo.It("supports scheduled pod referencing external resource claim", func(ctx context.Context) { 1081 parameters := b.parameters() 1082 claim := b.externalClaim(allocationMode) 1083 pod := b.podExternal() 1084 pod.Spec.NodeName = nodes.NodeNames[0] 1085 b.create(ctx, parameters, claim, pod) 1086 1087 b.testPod(ctx, f.ClientSet, pod) 1088 }) 1089 } 1090 1091 ginkgo.Context("with delayed allocation and setting ReservedFor", func() { 1092 driver := NewDriver(f, nodes, networkResources) 1093 b := newBuilder(f, driver) 1094 preScheduledTests(b, driver, resourcev1alpha2.AllocationModeWaitForFirstConsumer) 1095 claimTests(b, driver, resourcev1alpha2.AllocationModeWaitForFirstConsumer) 1096 }) 1097 1098 ginkgo.Context("with delayed allocation and not setting ReservedFor", func() { 1099 driver := NewDriver(f, nodes, func() app.Resources { 1100 resources := networkResources() 1101 resources.DontSetReservedFor = true 1102 return resources 1103 }) 1104 b := newBuilder(f, driver) 1105 preScheduledTests(b, driver, resourcev1alpha2.AllocationModeWaitForFirstConsumer) 1106 claimTests(b, driver, resourcev1alpha2.AllocationModeWaitForFirstConsumer) 1107 }) 1108 1109 ginkgo.Context("with immediate allocation", func() { 1110 driver := NewDriver(f, nodes, networkResources) 1111 b := newBuilder(f, driver) 1112 preScheduledTests(b, driver, resourcev1alpha2.AllocationModeImmediate) 1113 claimTests(b, driver, resourcev1alpha2.AllocationModeImmediate) 1114 }) 1115 }) 1116 1117 multipleDrivers := func(nodeV1alpha3 bool) { 1118 nodes := NewNodes(f, 1, 4) 1119 driver1 := NewDriver(f, nodes, perNode(2, nodes)) 1120 driver1.NodeV1alpha3 = nodeV1alpha3 1121 b1 := newBuilder(f, driver1) 1122 1123 driver2 := NewDriver(f, nodes, perNode(2, nodes)) 1124 driver2.NameSuffix = "-other" 1125 driver2.NodeV1alpha3 = nodeV1alpha3 1126 b2 := newBuilder(f, driver2) 1127 1128 ginkgo.It("work", func(ctx context.Context) { 1129 parameters1 := b1.parameters() 1130 parameters2 := b2.parameters() 1131 claim1 := b1.externalClaim(resourcev1alpha2.AllocationModeWaitForFirstConsumer) 1132 claim1b := b1.externalClaim(resourcev1alpha2.AllocationModeWaitForFirstConsumer) 1133 claim2 := b2.externalClaim(resourcev1alpha2.AllocationModeWaitForFirstConsumer) 1134 claim2b := b2.externalClaim(resourcev1alpha2.AllocationModeWaitForFirstConsumer) 1135 pod := b1.podExternal() 1136 for i, claim := range []*resourcev1alpha2.ResourceClaim{claim1b, claim2, claim2b} { 1137 claim := claim 1138 pod.Spec.ResourceClaims = append(pod.Spec.ResourceClaims, 1139 v1.PodResourceClaim{ 1140 Name: fmt.Sprintf("claim%d", i+1), 1141 Source: v1.ClaimSource{ 1142 ResourceClaimName: &claim.Name, 1143 }, 1144 }, 1145 ) 1146 } 1147 b1.create(ctx, parameters1, parameters2, claim1, claim1b, claim2, claim2b, pod) 1148 b1.testPod(ctx, f.ClientSet, pod) 1149 }) 1150 } 1151 multipleDriversContext := func(prefix string, nodeV1alpha3 bool) { 1152 ginkgo.Context(prefix, func() { 1153 multipleDrivers(nodeV1alpha3) 1154 }) 1155 } 1156 1157 ginkgo.Context("multiple drivers", func() { 1158 multipleDriversContext("using only drapbv1alpha3", true) 1159 }) 1160 }) 1161 1162 // builder contains a running counter to make objects unique within thir 1163 // namespace. 1164 type builder struct { 1165 f *framework.Framework 1166 driver *Driver 1167 1168 podCounter int 1169 parametersCounter int 1170 claimCounter int 1171 1172 classParametersName string 1173 } 1174 1175 // className returns the default resource class name. 1176 func (b *builder) className() string { 1177 return b.f.UniqueName + b.driver.NameSuffix + "-class" 1178 } 1179 1180 // class returns the resource class that the builder's other objects 1181 // reference. 1182 func (b *builder) class() *resourcev1alpha2.ResourceClass { 1183 class := &resourcev1alpha2.ResourceClass{ 1184 ObjectMeta: metav1.ObjectMeta{ 1185 Name: b.className(), 1186 }, 1187 DriverName: b.driver.Name, 1188 SuitableNodes: b.nodeSelector(), 1189 StructuredParameters: ptr.To(b.driver.parameterMode != parameterModeConfigMap), 1190 } 1191 if b.classParametersName != "" { 1192 class.ParametersRef = &resourcev1alpha2.ResourceClassParametersReference{ 1193 APIGroup: b.driver.parameterAPIGroup, 1194 Kind: b.driver.classParameterAPIKind, 1195 Name: b.classParametersName, 1196 Namespace: b.f.Namespace.Name, 1197 } 1198 } 1199 return class 1200 } 1201 1202 // nodeSelector returns a node selector that matches all nodes on which the 1203 // kubelet plugin was deployed. 1204 func (b *builder) nodeSelector() *v1.NodeSelector { 1205 return &v1.NodeSelector{ 1206 NodeSelectorTerms: []v1.NodeSelectorTerm{ 1207 { 1208 MatchExpressions: []v1.NodeSelectorRequirement{ 1209 { 1210 Key: "kubernetes.io/hostname", 1211 Operator: v1.NodeSelectorOpIn, 1212 Values: b.driver.Nodenames(), 1213 }, 1214 }, 1215 }, 1216 }, 1217 } 1218 } 1219 1220 // externalClaim returns external resource claim 1221 // that test pods can reference 1222 func (b *builder) externalClaim(allocationMode resourcev1alpha2.AllocationMode) *resourcev1alpha2.ResourceClaim { 1223 b.claimCounter++ 1224 name := "external-claim" + b.driver.NameSuffix // This is what podExternal expects. 1225 if b.claimCounter > 1 { 1226 name += fmt.Sprintf("-%d", b.claimCounter) 1227 } 1228 return &resourcev1alpha2.ResourceClaim{ 1229 ObjectMeta: metav1.ObjectMeta{ 1230 Name: name, 1231 }, 1232 Spec: resourcev1alpha2.ResourceClaimSpec{ 1233 ResourceClassName: b.className(), 1234 ParametersRef: &resourcev1alpha2.ResourceClaimParametersReference{ 1235 APIGroup: b.driver.parameterAPIGroup, 1236 Kind: b.driver.claimParameterAPIKind, 1237 Name: b.parametersName(), 1238 }, 1239 AllocationMode: allocationMode, 1240 }, 1241 } 1242 } 1243 1244 // flexibleParameters returns parameter objects for claims and 1245 // class with their type depending on the current parameter mode. 1246 // It also returns the expected environment in a pod using 1247 // the corresponding resource. 1248 func (b *builder) flexibleParameters() ([]klog.KMetadata, []string) { 1249 var objects []klog.KMetadata 1250 switch b.driver.parameterMode { 1251 case parameterModeConfigMap: 1252 objects = append(objects, 1253 b.parameters("x", "y"), 1254 b.parameters("a", "b", "request_foo", "bar"), 1255 ) 1256 case parameterModeTranslated: 1257 objects = append(objects, 1258 b.parameters("x", "y"), 1259 b.classParameters(b.parametersName(), "x", "y"), 1260 b.parameters("a", "b", "request_foo", "bar"), 1261 b.claimParameters(b.parametersName(), []string{"a", "b"}, []string{"request_foo", "bar"}), 1262 ) 1263 // The parameters object is not the last one but the second-last. 1264 b.parametersCounter-- 1265 case parameterModeStructured: 1266 objects = append(objects, 1267 b.classParameters("", "x", "y"), 1268 b.claimParameters("", []string{"a", "b"}, []string{"request_foo", "bar"}), 1269 ) 1270 } 1271 env := []string{"user_a", "b", "user_request_foo", "bar"} 1272 if b.classParametersName != "" { 1273 env = append(env, "admin_x", "y") 1274 } 1275 return objects, env 1276 } 1277 1278 // parametersName returns the current ConfigMap name for resource 1279 // claim or class parameters. 1280 func (b *builder) parametersName() string { 1281 return fmt.Sprintf("parameters%s-%d", b.driver.NameSuffix, b.parametersCounter) 1282 } 1283 1284 // parametersEnv returns the default env variables. 1285 func (b *builder) parametersEnv() map[string]string { 1286 return map[string]string{ 1287 "a": "b", 1288 "request_foo": "bar", 1289 } 1290 } 1291 1292 // parameters returns a config map with the default env variables. 1293 func (b *builder) parameters(kv ...string) *v1.ConfigMap { 1294 data := b.parameterData(kv...) 1295 b.parametersCounter++ 1296 return &v1.ConfigMap{ 1297 ObjectMeta: metav1.ObjectMeta{ 1298 Namespace: b.f.Namespace.Name, 1299 Name: b.parametersName(), 1300 }, 1301 Data: data, 1302 } 1303 } 1304 1305 func (b *builder) classParameters(generatedFrom string, kv ...string) *resourcev1alpha2.ResourceClassParameters { 1306 raw := b.rawParameterData(kv...) 1307 b.parametersCounter++ 1308 parameters := &resourcev1alpha2.ResourceClassParameters{ 1309 ObjectMeta: metav1.ObjectMeta{ 1310 Namespace: b.f.Namespace.Name, 1311 Name: b.parametersName(), 1312 }, 1313 1314 VendorParameters: []resourcev1alpha2.VendorParameters{ 1315 {DriverName: b.driver.Name, Parameters: runtime.RawExtension{Raw: raw}}, 1316 }, 1317 } 1318 1319 if generatedFrom != "" { 1320 parameters.GeneratedFrom = &resourcev1alpha2.ResourceClassParametersReference{ 1321 Kind: "ConfigMap", 1322 Namespace: b.f.Namespace.Name, 1323 Name: generatedFrom, 1324 } 1325 } 1326 1327 return parameters 1328 } 1329 1330 func (b *builder) claimParameters(generatedFrom string, claimKV, requestKV []string) *resourcev1alpha2.ResourceClaimParameters { 1331 b.parametersCounter++ 1332 parameters := &resourcev1alpha2.ResourceClaimParameters{ 1333 ObjectMeta: metav1.ObjectMeta{ 1334 Namespace: b.f.Namespace.Name, 1335 Name: b.parametersName(), 1336 }, 1337 1338 Shareable: true, 1339 1340 // Without any request, nothing gets allocated and vendor 1341 // parameters are also not passed down because they get 1342 // attached to the allocation result. 1343 DriverRequests: []resourcev1alpha2.DriverRequests{ 1344 { 1345 DriverName: b.driver.Name, 1346 VendorParameters: runtime.RawExtension{Raw: b.rawParameterData(claimKV...)}, 1347 Requests: []resourcev1alpha2.ResourceRequest{ 1348 { 1349 VendorParameters: runtime.RawExtension{Raw: b.rawParameterData(requestKV...)}, 1350 ResourceRequestModel: resourcev1alpha2.ResourceRequestModel{ 1351 NamedResources: &resourcev1alpha2.NamedResourcesRequest{ 1352 Selector: "true", 1353 }, 1354 }, 1355 }, 1356 }, 1357 }, 1358 }, 1359 } 1360 1361 if generatedFrom != "" { 1362 parameters.GeneratedFrom = &resourcev1alpha2.ResourceClaimParametersReference{ 1363 Kind: "ConfigMap", 1364 Name: generatedFrom, 1365 } 1366 } 1367 1368 return parameters 1369 } 1370 1371 func (b *builder) parameterData(kv ...string) map[string]string { 1372 data := map[string]string{} 1373 for i := 0; i < len(kv); i += 2 { 1374 data[kv[i]] = kv[i+1] 1375 } 1376 if len(data) == 0 { 1377 data = b.parametersEnv() 1378 } 1379 return data 1380 } 1381 1382 func (b *builder) rawParameterData(kv ...string) []byte { 1383 data := b.parameterData(kv...) 1384 raw, err := json.Marshal(data) 1385 framework.ExpectNoError(err, "JSON encoding of parameter data") 1386 return raw 1387 } 1388 1389 // makePod returns a simple pod with no resource claims. 1390 // The pod prints its env and waits. 1391 func (b *builder) pod() *v1.Pod { 1392 pod := e2epod.MakePod(b.f.Namespace.Name, nil, nil, b.f.NamespacePodSecurityLevel, "env && sleep 100000") 1393 pod.Labels = make(map[string]string) 1394 pod.Spec.RestartPolicy = v1.RestartPolicyNever 1395 // Let kubelet kill the pods quickly. Setting 1396 // TerminationGracePeriodSeconds to zero would bypass kubelet 1397 // completely because then the apiserver enables a force-delete even 1398 // when DeleteOptions for the pod don't ask for it (see 1399 // https://github.com/kubernetes/kubernetes/blob/0f582f7c3f504e807550310d00f130cb5c18c0c3/pkg/registry/core/pod/strategy.go#L151-L171). 1400 // 1401 // We don't do that because it breaks tracking of claim usage: the 1402 // kube-controller-manager assumes that kubelet is done with the pod 1403 // once it got removed or has a grace period of 0. Setting the grace 1404 // period to zero directly in DeletionOptions or indirectly through 1405 // TerminationGracePeriodSeconds causes the controller to remove 1406 // the pod from ReservedFor before it actually has stopped on 1407 // the node. 1408 one := int64(1) 1409 pod.Spec.TerminationGracePeriodSeconds = &one 1410 pod.ObjectMeta.GenerateName = "" 1411 b.podCounter++ 1412 pod.ObjectMeta.Name = fmt.Sprintf("tester%s-%d", b.driver.NameSuffix, b.podCounter) 1413 return pod 1414 } 1415 1416 // makePodInline adds an inline resource claim with default class name and parameters. 1417 func (b *builder) podInline(allocationMode resourcev1alpha2.AllocationMode) (*v1.Pod, *resourcev1alpha2.ResourceClaimTemplate) { 1418 pod := b.pod() 1419 pod.Spec.Containers[0].Name = "with-resource" 1420 podClaimName := "my-inline-claim" 1421 pod.Spec.Containers[0].Resources.Claims = []v1.ResourceClaim{{Name: podClaimName}} 1422 pod.Spec.ResourceClaims = []v1.PodResourceClaim{ 1423 { 1424 Name: podClaimName, 1425 Source: v1.ClaimSource{ 1426 ResourceClaimTemplateName: ptr.To(pod.Name), 1427 }, 1428 }, 1429 } 1430 template := &resourcev1alpha2.ResourceClaimTemplate{ 1431 ObjectMeta: metav1.ObjectMeta{ 1432 Name: pod.Name, 1433 Namespace: pod.Namespace, 1434 }, 1435 Spec: resourcev1alpha2.ResourceClaimTemplateSpec{ 1436 Spec: resourcev1alpha2.ResourceClaimSpec{ 1437 ResourceClassName: b.className(), 1438 ParametersRef: &resourcev1alpha2.ResourceClaimParametersReference{ 1439 APIGroup: b.driver.parameterAPIGroup, 1440 Kind: b.driver.claimParameterAPIKind, 1441 Name: b.parametersName(), 1442 }, 1443 AllocationMode: allocationMode, 1444 }, 1445 }, 1446 } 1447 return pod, template 1448 } 1449 1450 // podInlineMultiple returns a pod with inline resource claim referenced by 3 containers 1451 func (b *builder) podInlineMultiple(allocationMode resourcev1alpha2.AllocationMode) (*v1.Pod, *resourcev1alpha2.ResourceClaimTemplate) { 1452 pod, template := b.podInline(allocationMode) 1453 pod.Spec.Containers = append(pod.Spec.Containers, *pod.Spec.Containers[0].DeepCopy(), *pod.Spec.Containers[0].DeepCopy()) 1454 pod.Spec.Containers[1].Name = pod.Spec.Containers[1].Name + "-1" 1455 pod.Spec.Containers[2].Name = pod.Spec.Containers[1].Name + "-2" 1456 return pod, template 1457 } 1458 1459 // podExternal adds a pod that references external resource claim with default class name and parameters. 1460 func (b *builder) podExternal() *v1.Pod { 1461 pod := b.pod() 1462 pod.Spec.Containers[0].Name = "with-resource" 1463 podClaimName := "resource-claim" 1464 externalClaimName := "external-claim" + b.driver.NameSuffix 1465 pod.Spec.ResourceClaims = []v1.PodResourceClaim{ 1466 { 1467 Name: podClaimName, 1468 Source: v1.ClaimSource{ 1469 ResourceClaimName: &externalClaimName, 1470 }, 1471 }, 1472 } 1473 pod.Spec.Containers[0].Resources.Claims = []v1.ResourceClaim{{Name: podClaimName}} 1474 return pod 1475 } 1476 1477 // podShared returns a pod with 3 containers that reference external resource claim with default class name and parameters. 1478 func (b *builder) podExternalMultiple() *v1.Pod { 1479 pod := b.podExternal() 1480 pod.Spec.Containers = append(pod.Spec.Containers, *pod.Spec.Containers[0].DeepCopy(), *pod.Spec.Containers[0].DeepCopy()) 1481 pod.Spec.Containers[1].Name = pod.Spec.Containers[1].Name + "-1" 1482 pod.Spec.Containers[2].Name = pod.Spec.Containers[1].Name + "-2" 1483 return pod 1484 } 1485 1486 // create takes a bunch of objects and calls their Create function. 1487 func (b *builder) create(ctx context.Context, objs ...klog.KMetadata) []klog.KMetadata { 1488 var createdObjs []klog.KMetadata 1489 for _, obj := range objs { 1490 ginkgo.By(fmt.Sprintf("creating %T %s", obj, obj.GetName())) 1491 var err error 1492 var createdObj klog.KMetadata 1493 switch obj := obj.(type) { 1494 case *resourcev1alpha2.ResourceClass: 1495 createdObj, err = b.f.ClientSet.ResourceV1alpha2().ResourceClasses().Create(ctx, obj, metav1.CreateOptions{}) 1496 ginkgo.DeferCleanup(func(ctx context.Context) { 1497 err := b.f.ClientSet.ResourceV1alpha2().ResourceClasses().Delete(ctx, createdObj.GetName(), metav1.DeleteOptions{}) 1498 framework.ExpectNoError(err, "delete resource class") 1499 }) 1500 case *v1.Pod: 1501 createdObj, err = b.f.ClientSet.CoreV1().Pods(b.f.Namespace.Name).Create(ctx, obj, metav1.CreateOptions{}) 1502 case *v1.ConfigMap: 1503 createdObj, err = b.f.ClientSet.CoreV1().ConfigMaps(b.f.Namespace.Name).Create(ctx, obj, metav1.CreateOptions{}) 1504 case *resourcev1alpha2.ResourceClaim: 1505 createdObj, err = b.f.ClientSet.ResourceV1alpha2().ResourceClaims(b.f.Namespace.Name).Create(ctx, obj, metav1.CreateOptions{}) 1506 case *resourcev1alpha2.ResourceClaimTemplate: 1507 createdObj, err = b.f.ClientSet.ResourceV1alpha2().ResourceClaimTemplates(b.f.Namespace.Name).Create(ctx, obj, metav1.CreateOptions{}) 1508 case *resourcev1alpha2.ResourceClassParameters: 1509 createdObj, err = b.f.ClientSet.ResourceV1alpha2().ResourceClassParameters(b.f.Namespace.Name).Create(ctx, obj, metav1.CreateOptions{}) 1510 case *resourcev1alpha2.ResourceClaimParameters: 1511 createdObj, err = b.f.ClientSet.ResourceV1alpha2().ResourceClaimParameters(b.f.Namespace.Name).Create(ctx, obj, metav1.CreateOptions{}) 1512 case *resourcev1alpha2.ResourceSlice: 1513 createdObj, err = b.f.ClientSet.ResourceV1alpha2().ResourceSlices().Create(ctx, obj, metav1.CreateOptions{}) 1514 ginkgo.DeferCleanup(func(ctx context.Context) { 1515 err := b.f.ClientSet.ResourceV1alpha2().ResourceSlices().Delete(ctx, createdObj.GetName(), metav1.DeleteOptions{}) 1516 framework.ExpectNoError(err, "delete node resource slice") 1517 }) 1518 default: 1519 framework.Fail(fmt.Sprintf("internal error, unsupported type %T", obj), 1) 1520 } 1521 framework.ExpectNoErrorWithOffset(1, err, "create %T", obj) 1522 createdObjs = append(createdObjs, createdObj) 1523 } 1524 return createdObjs 1525 } 1526 1527 // testPod runs pod and checks if container logs contain expected environment variables 1528 func (b *builder) testPod(ctx context.Context, clientSet kubernetes.Interface, pod *v1.Pod, env ...string) { 1529 err := e2epod.WaitForPodRunningInNamespace(ctx, clientSet, pod) 1530 framework.ExpectNoError(err, "start pod") 1531 1532 for _, container := range pod.Spec.Containers { 1533 log, err := e2epod.GetPodLogs(ctx, clientSet, pod.Namespace, pod.Name, container.Name) 1534 framework.ExpectNoError(err, "get logs") 1535 if len(env) == 0 { 1536 for key, value := range b.parametersEnv() { 1537 envStr := fmt.Sprintf("\nuser_%s=%s\n", key, value) 1538 gomega.Expect(log).To(gomega.ContainSubstring(envStr), "container env variables") 1539 } 1540 } else { 1541 for i := 0; i < len(env); i += 2 { 1542 envStr := fmt.Sprintf("\n%s=%s\n", env[i], env[i+1]) 1543 gomega.Expect(log).To(gomega.ContainSubstring(envStr), "container env variables") 1544 } 1545 } 1546 } 1547 } 1548 1549 func newBuilder(f *framework.Framework, driver *Driver) *builder { 1550 b := &builder{f: f, driver: driver} 1551 1552 ginkgo.BeforeEach(b.setUp) 1553 1554 return b 1555 } 1556 1557 func (b *builder) setUp() { 1558 b.podCounter = 0 1559 b.parametersCounter = 0 1560 b.claimCounter = 0 1561 b.create(context.Background(), b.class()) 1562 ginkgo.DeferCleanup(b.tearDown) 1563 } 1564 1565 func (b *builder) tearDown(ctx context.Context) { 1566 // Before we allow the namespace and all objects in it do be deleted by 1567 // the framework, we must ensure that test pods and the claims that 1568 // they use are deleted. Otherwise the driver might get deleted first, 1569 // in which case deleting the claims won't work anymore. 1570 ginkgo.By("delete pods and claims") 1571 pods, err := b.listTestPods(ctx) 1572 framework.ExpectNoError(err, "list pods") 1573 for _, pod := range pods { 1574 if pod.DeletionTimestamp != nil { 1575 continue 1576 } 1577 ginkgo.By(fmt.Sprintf("deleting %T %s", &pod, klog.KObj(&pod))) 1578 err := b.f.ClientSet.CoreV1().Pods(b.f.Namespace.Name).Delete(ctx, pod.Name, metav1.DeleteOptions{}) 1579 if !apierrors.IsNotFound(err) { 1580 framework.ExpectNoError(err, "delete pod") 1581 } 1582 } 1583 gomega.Eventually(func() ([]v1.Pod, error) { 1584 return b.listTestPods(ctx) 1585 }).WithTimeout(time.Minute).Should(gomega.BeEmpty(), "remaining pods despite deletion") 1586 1587 claims, err := b.f.ClientSet.ResourceV1alpha2().ResourceClaims(b.f.Namespace.Name).List(ctx, metav1.ListOptions{}) 1588 framework.ExpectNoError(err, "get resource claims") 1589 for _, claim := range claims.Items { 1590 if claim.DeletionTimestamp != nil { 1591 continue 1592 } 1593 ginkgo.By(fmt.Sprintf("deleting %T %s", &claim, klog.KObj(&claim))) 1594 err := b.f.ClientSet.ResourceV1alpha2().ResourceClaims(b.f.Namespace.Name).Delete(ctx, claim.Name, metav1.DeleteOptions{}) 1595 if !apierrors.IsNotFound(err) { 1596 framework.ExpectNoError(err, "delete claim") 1597 } 1598 } 1599 1600 for host, plugin := range b.driver.Nodes { 1601 ginkgo.By(fmt.Sprintf("waiting for resources on %s to be unprepared", host)) 1602 gomega.Eventually(plugin.GetPreparedResources).WithTimeout(time.Minute).Should(gomega.BeEmpty(), "prepared claims on host %s", host) 1603 } 1604 1605 ginkgo.By("waiting for claims to be deallocated and deleted") 1606 gomega.Eventually(func() ([]resourcev1alpha2.ResourceClaim, error) { 1607 claims, err := b.f.ClientSet.ResourceV1alpha2().ResourceClaims(b.f.Namespace.Name).List(ctx, metav1.ListOptions{}) 1608 if err != nil { 1609 return nil, err 1610 } 1611 return claims.Items, nil 1612 }).WithTimeout(time.Minute).Should(gomega.BeEmpty(), "claims in the namespaces") 1613 } 1614 1615 func (b *builder) listTestPods(ctx context.Context) ([]v1.Pod, error) { 1616 pods, err := b.f.ClientSet.CoreV1().Pods(b.f.Namespace.Name).List(ctx, metav1.ListOptions{}) 1617 if err != nil { 1618 return nil, err 1619 } 1620 1621 var testPods []v1.Pod 1622 for _, pod := range pods.Items { 1623 if pod.Labels["app.kubernetes.io/part-of"] == "dra-test-driver" { 1624 continue 1625 } 1626 testPods = append(testPods, pod) 1627 } 1628 return testPods, nil 1629 }