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  }