k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/scheduler/framework/plugins/volumebinding/binder_test.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package volumebinding
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"os"
    23  	"reflect"
    24  	"sort"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/google/go-cmp/cmp"
    29  
    30  	v1 "k8s.io/api/core/v1"
    31  	storagev1 "k8s.io/api/storage/v1"
    32  	"k8s.io/apimachinery/pkg/api/resource"
    33  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    34  	"k8s.io/apimachinery/pkg/types"
    35  	"k8s.io/apimachinery/pkg/util/sets"
    36  	"k8s.io/apimachinery/pkg/util/wait"
    37  	"k8s.io/apimachinery/pkg/watch"
    38  	"k8s.io/client-go/informers"
    39  	coreinformers "k8s.io/client-go/informers/core/v1"
    40  	storageinformers "k8s.io/client-go/informers/storage/v1"
    41  	clientset "k8s.io/client-go/kubernetes"
    42  	"k8s.io/client-go/kubernetes/fake"
    43  	k8stesting "k8s.io/client-go/testing"
    44  	"k8s.io/component-helpers/storage/volume"
    45  	"k8s.io/klog/v2"
    46  	"k8s.io/klog/v2/ktesting"
    47  	_ "k8s.io/klog/v2/ktesting/init"
    48  	"k8s.io/kubernetes/pkg/controller"
    49  	pvtesting "k8s.io/kubernetes/pkg/controller/volume/persistentvolume/testing"
    50  	"k8s.io/kubernetes/pkg/scheduler/util/assumecache"
    51  )
    52  
    53  var (
    54  	provisioner = "test-provisioner"
    55  
    56  	// PVCs for manual binding
    57  	// TODO: clean up all of these
    58  	unboundPVC          = makeTestPVC("unbound-pvc", "1G", "", pvcUnbound, "", "1", &waitClass)
    59  	unboundPVC2         = makeTestPVC("unbound-pvc2", "5G", "", pvcUnbound, "", "1", &waitClass)
    60  	preboundPVC         = makeTestPVC("prebound-pvc", "1G", "", pvcPrebound, "pv-node1a", "1", &waitClass)
    61  	preboundPVCNode1a   = makeTestPVC("unbound-pvc", "1G", "", pvcPrebound, "pv-node1a", "1", &waitClass)
    62  	boundPVC            = makeTestPVC("bound-pvc", "1G", "", pvcBound, "pv-bound", "1", &waitClass)
    63  	boundPVCNode1a      = makeTestPVC("unbound-pvc", "1G", "", pvcBound, "pv-node1a", "1", &waitClass)
    64  	immediateUnboundPVC = makeTestPVC("immediate-unbound-pvc", "1G", "", pvcUnbound, "", "1", &immediateClass)
    65  	immediateBoundPVC   = makeTestPVC("immediate-bound-pvc", "1G", "", pvcBound, "pv-bound-immediate", "1", &immediateClass)
    66  	localPreboundPVC1a  = makeTestPVC("local-prebound-pvc-1a", "1G", "", pvcPrebound, "local-pv-node1a", "1", &waitClass)
    67  	localPreboundPVC1b  = makeTestPVC("local-prebound-pvc-1b", "1G", "", pvcPrebound, "local-pv-node1b", "1", &waitClass)
    68  	localPreboundPVC2a  = makeTestPVC("local-prebound-pvc-2a", "1G", "", pvcPrebound, "local-pv-node2a", "1", &waitClass)
    69  
    70  	// PVCs for dynamic provisioning
    71  	provisionedPVC              = makeTestPVC("provisioned-pvc", "1Gi", "", pvcUnbound, "", "1", &waitClassWithProvisioner)
    72  	provisionedPVC2             = makeTestPVC("provisioned-pvc2", "1Gi", "", pvcUnbound, "", "1", &waitClassWithProvisioner)
    73  	provisionedPVCHigherVersion = makeTestPVC("provisioned-pvc2", "1Gi", "", pvcUnbound, "", "2", &waitClassWithProvisioner)
    74  	provisionedPVCBound         = makeTestPVC("provisioned-pvc", "1Gi", "", pvcBound, "pv-bound", "1", &waitClassWithProvisioner)
    75  	noProvisionerPVC            = makeTestPVC("no-provisioner-pvc", "1Gi", "", pvcUnbound, "", "1", &waitClass)
    76  	topoMismatchPVC             = makeTestPVC("topo-mismatch-pvc", "1Gi", "", pvcUnbound, "", "1", &topoMismatchClass)
    77  
    78  	selectedNodePVC = makeTestPVC("provisioned-pvc", "1Gi", nodeLabelValue, pvcSelectedNode, "", "1", &waitClassWithProvisioner)
    79  
    80  	// PVCs for CSI migration
    81  	boundMigrationPVC     = makeTestPVC("pvc-migration-bound", "1G", "", pvcBound, "pv-migration-bound", "1", &waitClass)
    82  	provMigrationPVCBound = makeTestPVC("pvc-migration-provisioned", "1Gi", "", pvcBound, "pv-migration-bound", "1", &waitClassWithProvisioner)
    83  
    84  	// PVCs and PV for GenericEphemeralVolume
    85  	conflictingGenericPVC = makeGenericEphemeralPVC("test-volume", false /* not owned*/)
    86  	correctGenericPVC     = makeGenericEphemeralPVC("test-volume", true /* owned */)
    87  	pvBoundGeneric        = makeTestPV("pv-bound", "node1", "1G", "1", correctGenericPVC, waitClass)
    88  
    89  	// PVs for manual binding
    90  	pvNode1a                   = makeTestPV("pv-node1a", "node1", "5G", "1", nil, waitClass)
    91  	pvNode1b                   = makeTestPV("pv-node1b", "node1", "10G", "1", nil, waitClass)
    92  	pvNode1c                   = makeTestPV("pv-node1b", "node1", "5G", "1", nil, waitClass)
    93  	pvNode2                    = makeTestPV("pv-node2", "node2", "1G", "1", nil, waitClass)
    94  	pvBound                    = makeTestPV("pv-bound", "node1", "1G", "1", boundPVC, waitClass)
    95  	pvNode1aBound              = makeTestPV("pv-node1a", "node1", "5G", "1", unboundPVC, waitClass)
    96  	pvNode1bBound              = makeTestPV("pv-node1b", "node1", "10G", "1", unboundPVC2, waitClass)
    97  	pvNode1bBoundHigherVersion = makeTestPV("pv-node1b", "node1", "10G", "2", unboundPVC2, waitClass)
    98  	pvBoundImmediate           = makeTestPV("pv-bound-immediate", "node1", "1G", "1", immediateBoundPVC, immediateClass)
    99  	pvBoundImmediateNode2      = makeTestPV("pv-bound-immediate", "node2", "1G", "1", immediateBoundPVC, immediateClass)
   100  	localPVNode1a              = makeLocalPV("local-pv-node1a", "node1", "5G", "1", nil, waitClass)
   101  	localPVNode1b              = makeLocalPV("local-pv-node1b", "node1", "10G", "1", nil, waitClass)
   102  	localPVNode2a              = makeLocalPV("local-pv-node2a", "node2", "5G", "1", nil, waitClass)
   103  
   104  	// PVs for CSI migration
   105  	migrationPVBound             = makeTestPVForCSIMigration(zone1Labels, boundMigrationPVC, true)
   106  	migrationPVBoundToUnbound    = makeTestPVForCSIMigration(zone1Labels, unboundPVC, true)
   107  	nonmigrationPVBoundToUnbound = makeTestPVForCSIMigration(zone1Labels, unboundPVC, false)
   108  
   109  	// storage class names
   110  	waitClass                = "waitClass"
   111  	immediateClass           = "immediateClass"
   112  	waitClassWithProvisioner = "waitClassWithProvisioner"
   113  	topoMismatchClass        = "topoMismatchClass"
   114  
   115  	// nodes objects
   116  	node1         = makeNode("node1").withLabel(nodeLabelKey, "node1").Node
   117  	node2         = makeNode("node2").withLabel(nodeLabelKey, "node2").Node
   118  	node1NoLabels = makeNode("node1").Node
   119  	node1Zone1    = makeNode("node1").withLabel("topology.gke.io/zone", "us-east-1").Node
   120  	node1Zone2    = makeNode("node1").withLabel("topology.gke.io/zone", "us-east-2").Node
   121  
   122  	// csiNode objects
   123  	csiNode1Migrated    = makeCSINode("node1", "kubernetes.io/gce-pd")
   124  	csiNode1NotMigrated = makeCSINode("node1", "")
   125  
   126  	// node topology
   127  	nodeLabelKey   = "nodeKey"
   128  	nodeLabelValue = "node1"
   129  
   130  	// node topology for CSI migration
   131  	zone1Labels = map[string]string{v1.LabelFailureDomainBetaZone: "us-east-1", v1.LabelFailureDomainBetaRegion: "us-east-1a"}
   132  )
   133  
   134  type testEnv struct {
   135  	client                  clientset.Interface
   136  	reactor                 *pvtesting.VolumeReactor
   137  	binder                  SchedulerVolumeBinder
   138  	internalBinder          *volumeBinder
   139  	internalPodInformer     coreinformers.PodInformer
   140  	internalNodeInformer    coreinformers.NodeInformer
   141  	internalCSINodeInformer storageinformers.CSINodeInformer
   142  
   143  	// For CSIStorageCapacity feature testing:
   144  	internalCSIDriverInformer          storageinformers.CSIDriverInformer
   145  	internalCSIStorageCapacityInformer storageinformers.CSIStorageCapacityInformer
   146  }
   147  
   148  func newTestBinder(t *testing.T, ctx context.Context) *testEnv {
   149  	client := &fake.Clientset{}
   150  	logger := klog.FromContext(ctx)
   151  	reactor := pvtesting.NewVolumeReactor(ctx, client, nil, nil, nil)
   152  	// TODO refactor all tests to use real watch mechanism, see #72327
   153  	client.AddWatchReactor("*", func(action k8stesting.Action) (handled bool, ret watch.Interface, err error) {
   154  		gvr := action.GetResource()
   155  		ns := action.GetNamespace()
   156  		watch, err := reactor.Watch(gvr, ns)
   157  		if err != nil {
   158  			return false, nil, err
   159  		}
   160  		return true, watch, nil
   161  	})
   162  	informerFactory := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc())
   163  
   164  	podInformer := informerFactory.Core().V1().Pods()
   165  	nodeInformer := informerFactory.Core().V1().Nodes()
   166  	csiNodeInformer := informerFactory.Storage().V1().CSINodes()
   167  	pvcInformer := informerFactory.Core().V1().PersistentVolumeClaims()
   168  	classInformer := informerFactory.Storage().V1().StorageClasses()
   169  	csiDriverInformer := informerFactory.Storage().V1().CSIDrivers()
   170  	csiStorageCapacityInformer := informerFactory.Storage().V1().CSIStorageCapacities()
   171  	capacityCheck := CapacityCheck{
   172  		CSIDriverInformer:          csiDriverInformer,
   173  		CSIStorageCapacityInformer: csiStorageCapacityInformer,
   174  	}
   175  	binder := NewVolumeBinder(
   176  		logger,
   177  		client,
   178  		podInformer,
   179  		nodeInformer,
   180  		csiNodeInformer,
   181  		pvcInformer,
   182  		informerFactory.Core().V1().PersistentVolumes(),
   183  		classInformer,
   184  		capacityCheck,
   185  		10*time.Second)
   186  
   187  	// Wait for informers cache sync
   188  	informerFactory.Start(ctx.Done())
   189  	for v, synced := range informerFactory.WaitForCacheSync(ctx.Done()) {
   190  		if !synced {
   191  			logger.Error(nil, "Error syncing informer", "informer", v)
   192  			os.Exit(1)
   193  		}
   194  	}
   195  
   196  	// Add storageclasses
   197  	waitMode := storagev1.VolumeBindingWaitForFirstConsumer
   198  	immediateMode := storagev1.VolumeBindingImmediate
   199  	classes := []*storagev1.StorageClass{
   200  		{
   201  			ObjectMeta: metav1.ObjectMeta{
   202  				Name: waitClassWithProvisioner,
   203  			},
   204  			VolumeBindingMode: &waitMode,
   205  			Provisioner:       provisioner,
   206  			AllowedTopologies: []v1.TopologySelectorTerm{
   207  				{
   208  					MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{
   209  						{
   210  							Key:    nodeLabelKey,
   211  							Values: []string{nodeLabelValue, "reference-value"},
   212  						},
   213  					},
   214  				},
   215  			},
   216  		},
   217  		{
   218  			ObjectMeta: metav1.ObjectMeta{
   219  				Name: immediateClass,
   220  			},
   221  			VolumeBindingMode: &immediateMode,
   222  		},
   223  		{
   224  			ObjectMeta: metav1.ObjectMeta{
   225  				Name: waitClass,
   226  			},
   227  			VolumeBindingMode: &waitMode,
   228  			Provisioner:       "kubernetes.io/no-provisioner",
   229  		},
   230  		{
   231  			ObjectMeta: metav1.ObjectMeta{
   232  				Name: topoMismatchClass,
   233  			},
   234  			VolumeBindingMode: &waitMode,
   235  			Provisioner:       provisioner,
   236  			AllowedTopologies: []v1.TopologySelectorTerm{
   237  				{
   238  					MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{
   239  						{
   240  							Key:    nodeLabelKey,
   241  							Values: []string{"reference-value"},
   242  						},
   243  					},
   244  				},
   245  			},
   246  		},
   247  	}
   248  	for _, class := range classes {
   249  		if err := classInformer.Informer().GetIndexer().Add(class); err != nil {
   250  			t.Fatalf("Failed to add storage class to internal cache: %v", err)
   251  		}
   252  	}
   253  
   254  	// Get internal types
   255  	internalBinder, ok := binder.(*volumeBinder)
   256  	if !ok {
   257  		t.Fatalf("Failed to convert to internal binder")
   258  	}
   259  
   260  	return &testEnv{
   261  		client:                  client,
   262  		reactor:                 reactor,
   263  		binder:                  binder,
   264  		internalBinder:          internalBinder,
   265  		internalPodInformer:     podInformer,
   266  		internalNodeInformer:    nodeInformer,
   267  		internalCSINodeInformer: csiNodeInformer,
   268  
   269  		internalCSIDriverInformer:          csiDriverInformer,
   270  		internalCSIStorageCapacityInformer: csiStorageCapacityInformer,
   271  	}
   272  }
   273  
   274  func (env *testEnv) initNodes(cachedNodes []*v1.Node) {
   275  	nodeInformer := env.internalNodeInformer.Informer()
   276  	for _, node := range cachedNodes {
   277  		nodeInformer.GetIndexer().Add(node)
   278  	}
   279  }
   280  
   281  func (env *testEnv) initCSINodes(cachedCSINodes []*storagev1.CSINode) {
   282  	csiNodeInformer := env.internalCSINodeInformer.Informer()
   283  	for _, csiNode := range cachedCSINodes {
   284  		csiNodeInformer.GetIndexer().Add(csiNode)
   285  	}
   286  }
   287  
   288  func (env *testEnv) addCSIDriver(csiDriver *storagev1.CSIDriver) {
   289  	csiDriverInformer := env.internalCSIDriverInformer.Informer()
   290  	csiDriverInformer.GetIndexer().Add(csiDriver)
   291  }
   292  
   293  func (env *testEnv) addCSIStorageCapacities(capacities []*storagev1.CSIStorageCapacity) {
   294  	csiStorageCapacityInformer := env.internalCSIStorageCapacityInformer.Informer()
   295  	for _, capacity := range capacities {
   296  		csiStorageCapacityInformer.GetIndexer().Add(capacity)
   297  	}
   298  }
   299  
   300  func (env *testEnv) initClaims(cachedPVCs []*v1.PersistentVolumeClaim, apiPVCs []*v1.PersistentVolumeClaim) {
   301  	for _, pvc := range cachedPVCs {
   302  		assumecache.AddTestObject(env.internalBinder.pvcCache.AssumeCache, pvc)
   303  		if apiPVCs == nil {
   304  			env.reactor.AddClaim(pvc)
   305  		}
   306  	}
   307  	for _, pvc := range apiPVCs {
   308  		env.reactor.AddClaim(pvc)
   309  	}
   310  }
   311  
   312  func (env *testEnv) initVolumes(cachedPVs []*v1.PersistentVolume, apiPVs []*v1.PersistentVolume) {
   313  	for _, pv := range cachedPVs {
   314  		assumecache.AddTestObject(env.internalBinder.pvCache.AssumeCache, pv)
   315  		if apiPVs == nil {
   316  			env.reactor.AddVolume(pv)
   317  		}
   318  	}
   319  	for _, pv := range apiPVs {
   320  		env.reactor.AddVolume(pv)
   321  	}
   322  
   323  }
   324  
   325  func (env *testEnv) updateVolumes(ctx context.Context, pvs []*v1.PersistentVolume) error {
   326  	for i, pv := range pvs {
   327  		newPv, err := env.client.CoreV1().PersistentVolumes().Update(ctx, pv, metav1.UpdateOptions{})
   328  		if err != nil {
   329  			return err
   330  		}
   331  		pvs[i] = newPv
   332  	}
   333  	return wait.PollUntilContextTimeout(ctx, 100*time.Millisecond, 3*time.Second, false, func(ctx context.Context) (bool, error) {
   334  		for _, pv := range pvs {
   335  			obj, err := env.internalBinder.pvCache.GetAPIObj(pv.Name)
   336  			if obj == nil || err != nil {
   337  				return false, nil
   338  			}
   339  			pvInCache, ok := obj.(*v1.PersistentVolume)
   340  			if !ok {
   341  				return false, fmt.Errorf("PV %s invalid object", pvInCache.Name)
   342  			}
   343  			if versioner.CompareResourceVersion(pvInCache, pv) != 0 {
   344  				return false, nil
   345  			}
   346  		}
   347  		return true, nil
   348  	})
   349  }
   350  
   351  func (env *testEnv) updateClaims(ctx context.Context, pvcs []*v1.PersistentVolumeClaim) error {
   352  	for i, pvc := range pvcs {
   353  		newPvc, err := env.client.CoreV1().PersistentVolumeClaims(pvc.Namespace).Update(ctx, pvc, metav1.UpdateOptions{})
   354  		if err != nil {
   355  			return err
   356  		}
   357  		pvcs[i] = newPvc
   358  	}
   359  	return wait.PollUntilContextTimeout(ctx, 100*time.Millisecond, 3*time.Second, false, func(ctx context.Context) (bool, error) {
   360  		for _, pvc := range pvcs {
   361  			obj, err := env.internalBinder.pvcCache.GetAPIObj(getPVCName(pvc))
   362  			if obj == nil || err != nil {
   363  				return false, nil
   364  			}
   365  			pvcInCache, ok := obj.(*v1.PersistentVolumeClaim)
   366  			if !ok {
   367  				return false, fmt.Errorf("PVC %s invalid object", pvcInCache.Name)
   368  			}
   369  			if versioner.CompareResourceVersion(pvcInCache, pvc) != 0 {
   370  				return false, nil
   371  			}
   372  		}
   373  		return true, nil
   374  	})
   375  }
   376  
   377  func (env *testEnv) deleteVolumes(pvs []*v1.PersistentVolume) {
   378  	for _, pv := range pvs {
   379  		assumecache.DeleteTestObject(env.internalBinder.pvCache.AssumeCache, pv)
   380  	}
   381  }
   382  
   383  func (env *testEnv) deleteClaims(pvcs []*v1.PersistentVolumeClaim) {
   384  	for _, pvc := range pvcs {
   385  		assumecache.DeleteTestObject(env.internalBinder.pvcCache.AssumeCache, pvc)
   386  	}
   387  }
   388  
   389  func (env *testEnv) assumeVolumes(t *testing.T, node string, pod *v1.Pod, bindings []*BindingInfo, provisionings []*v1.PersistentVolumeClaim) {
   390  	pvCache := env.internalBinder.pvCache
   391  	for _, binding := range bindings {
   392  		if err := pvCache.Assume(binding.pv); err != nil {
   393  			t.Fatalf("error: %v", err)
   394  		}
   395  	}
   396  
   397  	pvcCache := env.internalBinder.pvcCache
   398  	for _, pvc := range provisionings {
   399  		if err := pvcCache.Assume(pvc); err != nil {
   400  			t.Fatalf("error: %v", err)
   401  		}
   402  	}
   403  }
   404  
   405  func (env *testEnv) validatePodCache(t *testing.T, node string, pod *v1.Pod, podVolumes *PodVolumes, expectedBindings []*BindingInfo, expectedProvisionings []*v1.PersistentVolumeClaim) {
   406  	var (
   407  		bindings          []*BindingInfo
   408  		provisionedClaims []*v1.PersistentVolumeClaim
   409  	)
   410  	if podVolumes != nil {
   411  		bindings = podVolumes.StaticBindings
   412  		provisionedClaims = podVolumes.DynamicProvisions
   413  	}
   414  	if aLen, eLen := len(bindings), len(expectedBindings); aLen != eLen {
   415  		t.Errorf("expected %v bindings, got %v", eLen, aLen)
   416  	} else if expectedBindings == nil && bindings != nil {
   417  		// nil and empty are different
   418  		t.Error("expected nil bindings, got empty")
   419  	} else if expectedBindings != nil && bindings == nil {
   420  		// nil and empty are different
   421  		t.Error("expected empty bindings, got nil")
   422  	} else {
   423  		for i := 0; i < aLen; i++ {
   424  			// Validate PV
   425  			if diff := cmp.Diff(expectedBindings[i].pv, bindings[i].pv); diff != "" {
   426  				t.Errorf("binding.pv doesn't match (-want, +got):\n%s", diff)
   427  			}
   428  
   429  			// Validate PVC
   430  			if diff := cmp.Diff(expectedBindings[i].pvc, bindings[i].pvc); diff != "" {
   431  				t.Errorf("binding.pvc doesn't match (-want, +got):\n%s", diff)
   432  			}
   433  		}
   434  	}
   435  
   436  	if aLen, eLen := len(provisionedClaims), len(expectedProvisionings); aLen != eLen {
   437  		t.Errorf("expected %v provisioned claims, got %v", eLen, aLen)
   438  	} else if expectedProvisionings == nil && provisionedClaims != nil {
   439  		// nil and empty are different
   440  		t.Error("expected nil provisionings, got empty")
   441  	} else if expectedProvisionings != nil && provisionedClaims == nil {
   442  		// nil and empty are different
   443  		t.Error("expected empty provisionings, got nil")
   444  	} else {
   445  		for i := 0; i < aLen; i++ {
   446  			if diff := cmp.Diff(expectedProvisionings[i], provisionedClaims[i]); diff != "" {
   447  				t.Errorf("provisioned claims doesn't match (-want, +got):\n%s", diff)
   448  			}
   449  		}
   450  	}
   451  }
   452  
   453  func (env *testEnv) validateAssume(t *testing.T, pod *v1.Pod, bindings []*BindingInfo, provisionings []*v1.PersistentVolumeClaim) {
   454  	// Check pv cache
   455  	pvCache := env.internalBinder.pvCache
   456  	for _, b := range bindings {
   457  		pv, err := pvCache.GetPV(b.pv.Name)
   458  		if err != nil {
   459  			t.Errorf("GetPV %q returned error: %v", b.pv.Name, err)
   460  			continue
   461  		}
   462  		if pv.Spec.ClaimRef == nil {
   463  			t.Errorf("PV %q ClaimRef is nil", b.pv.Name)
   464  			continue
   465  		}
   466  		if pv.Spec.ClaimRef.Name != b.pvc.Name {
   467  			t.Errorf("expected PV.ClaimRef.Name %q, got %q", b.pvc.Name, pv.Spec.ClaimRef.Name)
   468  		}
   469  		if pv.Spec.ClaimRef.Namespace != b.pvc.Namespace {
   470  			t.Errorf("expected PV.ClaimRef.Namespace %q, got %q", b.pvc.Namespace, pv.Spec.ClaimRef.Namespace)
   471  		}
   472  	}
   473  
   474  	// Check pvc cache
   475  	pvcCache := env.internalBinder.pvcCache
   476  	for _, p := range provisionings {
   477  		pvcKey := getPVCName(p)
   478  		pvc, err := pvcCache.GetPVC(pvcKey)
   479  		if err != nil {
   480  			t.Errorf("GetPVC %q returned error: %v", pvcKey, err)
   481  			continue
   482  		}
   483  		if pvc.Annotations[volume.AnnSelectedNode] != nodeLabelValue {
   484  			t.Errorf("expected volume.AnnSelectedNode of pvc %q to be %q, but got %q", pvcKey, nodeLabelValue, pvc.Annotations[volume.AnnSelectedNode])
   485  		}
   486  	}
   487  }
   488  
   489  func (env *testEnv) validateCacheRestored(t *testing.T, pod *v1.Pod, bindings []*BindingInfo, provisionings []*v1.PersistentVolumeClaim) {
   490  	// All PVs have been unmodified in cache
   491  	pvCache := env.internalBinder.pvCache
   492  	for _, b := range bindings {
   493  		pv, _ := pvCache.GetPV(b.pv.Name)
   494  		apiPV, _ := pvCache.GetAPIPV(b.pv.Name)
   495  		// PV could be nil if it's missing from cache
   496  		if pv != nil && pv != apiPV {
   497  			t.Errorf("PV %q was modified in cache", b.pv.Name)
   498  		}
   499  	}
   500  
   501  	// Check pvc cache
   502  	pvcCache := env.internalBinder.pvcCache
   503  	for _, p := range provisionings {
   504  		pvcKey := getPVCName(p)
   505  		pvc, err := pvcCache.GetPVC(pvcKey)
   506  		if err != nil {
   507  			t.Errorf("GetPVC %q returned error: %v", pvcKey, err)
   508  			continue
   509  		}
   510  		if pvc.Annotations[volume.AnnSelectedNode] != "" {
   511  			t.Errorf("expected volume.AnnSelectedNode of pvc %q empty, but got %q", pvcKey, pvc.Annotations[volume.AnnSelectedNode])
   512  		}
   513  	}
   514  }
   515  
   516  func (env *testEnv) validateBind(
   517  	t *testing.T,
   518  	pod *v1.Pod,
   519  	expectedPVs []*v1.PersistentVolume,
   520  	expectedAPIPVs []*v1.PersistentVolume) {
   521  
   522  	// Check pv cache
   523  	pvCache := env.internalBinder.pvCache
   524  	for _, pv := range expectedPVs {
   525  		cachedPV, err := pvCache.GetPV(pv.Name)
   526  		if err != nil {
   527  			t.Errorf("GetPV %q returned error: %v", pv.Name, err)
   528  		}
   529  		// Cache may be overridden by API object with higher version, compare but ignore resource version.
   530  		newCachedPV := cachedPV.DeepCopy()
   531  		newCachedPV.ResourceVersion = pv.ResourceVersion
   532  		if diff := cmp.Diff(pv, newCachedPV); diff != "" {
   533  			t.Errorf("cached PV check failed (-want, +got):\n%s", diff)
   534  		}
   535  	}
   536  
   537  	// Check reactor for API updates
   538  	if err := env.reactor.CheckVolumes(expectedAPIPVs); err != nil {
   539  		t.Errorf("API reactor validation failed: %v", err)
   540  	}
   541  }
   542  
   543  func (env *testEnv) validateProvision(
   544  	t *testing.T,
   545  	pod *v1.Pod,
   546  	expectedPVCs []*v1.PersistentVolumeClaim,
   547  	expectedAPIPVCs []*v1.PersistentVolumeClaim) {
   548  
   549  	// Check pvc cache
   550  	pvcCache := env.internalBinder.pvcCache
   551  	for _, pvc := range expectedPVCs {
   552  		cachedPVC, err := pvcCache.GetPVC(getPVCName(pvc))
   553  		if err != nil {
   554  			t.Errorf("GetPVC %q returned error: %v", getPVCName(pvc), err)
   555  		}
   556  		// Cache may be overridden by API object with higher version, compare but ignore resource version.
   557  		newCachedPVC := cachedPVC.DeepCopy()
   558  		newCachedPVC.ResourceVersion = pvc.ResourceVersion
   559  		if diff := cmp.Diff(pvc, newCachedPVC); diff != "" {
   560  			t.Errorf("cached PVC check failed (-want, +got):\n%s", diff)
   561  		}
   562  	}
   563  
   564  	// Check reactor for API updates
   565  	if err := env.reactor.CheckClaims(expectedAPIPVCs); err != nil {
   566  		t.Errorf("API reactor validation failed: %v", err)
   567  	}
   568  }
   569  
   570  const (
   571  	pvcUnbound = iota
   572  	pvcPrebound
   573  	pvcBound
   574  	pvcSelectedNode
   575  )
   576  
   577  func makeGenericEphemeralPVC(volumeName string, owned bool) *v1.PersistentVolumeClaim {
   578  	pod := makePod("test-pod").
   579  		withNamespace("testns").
   580  		withNodeName("node1").
   581  		withGenericEphemeralVolume("").Pod
   582  
   583  	pvc := makeTestPVC(pod.Name+"-"+volumeName, "1G", "", pvcBound, "pv-bound", "1", &immediateClass)
   584  	if owned {
   585  		controller := true
   586  		pvc.OwnerReferences = []metav1.OwnerReference{
   587  			{
   588  				Name:       pod.Name,
   589  				UID:        pod.UID,
   590  				Controller: &controller,
   591  			},
   592  		}
   593  	}
   594  	return pvc
   595  }
   596  
   597  func makeTestPVC(name, size, node string, pvcBoundState int, pvName, resourceVersion string, className *string) *v1.PersistentVolumeClaim {
   598  	fs := v1.PersistentVolumeFilesystem
   599  	pvc := &v1.PersistentVolumeClaim{
   600  		TypeMeta: metav1.TypeMeta{
   601  			Kind:       "PersistentVolumeClaim",
   602  			APIVersion: "v1",
   603  		},
   604  		ObjectMeta: metav1.ObjectMeta{
   605  			Name:            name,
   606  			Namespace:       "testns",
   607  			UID:             types.UID("pvc-uid"),
   608  			ResourceVersion: resourceVersion,
   609  		},
   610  		Spec: v1.PersistentVolumeClaimSpec{
   611  			Resources: v1.VolumeResourceRequirements{
   612  				Requests: v1.ResourceList{
   613  					v1.ResourceName(v1.ResourceStorage): resource.MustParse(size),
   614  				},
   615  			},
   616  			StorageClassName: className,
   617  			VolumeMode:       &fs,
   618  		},
   619  	}
   620  
   621  	switch pvcBoundState {
   622  	case pvcSelectedNode:
   623  		metav1.SetMetaDataAnnotation(&pvc.ObjectMeta, volume.AnnSelectedNode, node)
   624  		// don't fallthrough
   625  	case pvcBound:
   626  		metav1.SetMetaDataAnnotation(&pvc.ObjectMeta, volume.AnnBindCompleted, "yes")
   627  		fallthrough
   628  	case pvcPrebound:
   629  		pvc.Spec.VolumeName = pvName
   630  	}
   631  	return pvc
   632  }
   633  
   634  func makeTestPV(name, node, capacity, version string, boundToPVC *v1.PersistentVolumeClaim, className string) *v1.PersistentVolume {
   635  	fs := v1.PersistentVolumeFilesystem
   636  	pv := &v1.PersistentVolume{
   637  		ObjectMeta: metav1.ObjectMeta{
   638  			Name:            name,
   639  			ResourceVersion: version,
   640  		},
   641  		Spec: v1.PersistentVolumeSpec{
   642  			Capacity: v1.ResourceList{
   643  				v1.ResourceName(v1.ResourceStorage): resource.MustParse(capacity),
   644  			},
   645  			StorageClassName: className,
   646  			VolumeMode:       &fs,
   647  		},
   648  		Status: v1.PersistentVolumeStatus{
   649  			Phase: v1.VolumeAvailable,
   650  		},
   651  	}
   652  	if node != "" {
   653  		pv.Spec.NodeAffinity = &v1.VolumeNodeAffinity{
   654  			Required: &v1.NodeSelector{
   655  				NodeSelectorTerms: []v1.NodeSelectorTerm{
   656  					{
   657  						MatchExpressions: []v1.NodeSelectorRequirement{
   658  							{
   659  								Key:      nodeLabelKey,
   660  								Operator: v1.NodeSelectorOpIn,
   661  								Values:   []string{node},
   662  							},
   663  						},
   664  					},
   665  				},
   666  			},
   667  		}
   668  	}
   669  
   670  	if boundToPVC != nil {
   671  		pv.Spec.ClaimRef = &v1.ObjectReference{
   672  			Kind:            boundToPVC.Kind,
   673  			APIVersion:      boundToPVC.APIVersion,
   674  			ResourceVersion: boundToPVC.ResourceVersion,
   675  			Name:            boundToPVC.Name,
   676  			Namespace:       boundToPVC.Namespace,
   677  			UID:             boundToPVC.UID,
   678  		}
   679  		metav1.SetMetaDataAnnotation(&pv.ObjectMeta, volume.AnnBoundByController, "yes")
   680  	}
   681  
   682  	return pv
   683  }
   684  
   685  func makeTestPVForCSIMigration(labels map[string]string, pvc *v1.PersistentVolumeClaim, migrationEnabled bool) *v1.PersistentVolume {
   686  	pv := makeTestPV("pv-migration-bound", "node1", "1G", "1", pvc, waitClass)
   687  	pv.Spec.NodeAffinity = nil // Will be written by the CSI translation lib
   688  	pv.ObjectMeta.Labels = labels
   689  	// GCEPersistentDisk is used when migration is enabled, as its featuregate is locked to GA.
   690  	// RBD is used for the nonmigrated case, as its featuregate is still alpha. When RBD migration goes GA,
   691  	// a different nonmigrated plugin should be used instead. If there are no other plugins, then the
   692  	// nonmigrated test case is no longer relevant and can be removed.
   693  	if migrationEnabled {
   694  		pv.Spec.PersistentVolumeSource = v1.PersistentVolumeSource{
   695  			GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{
   696  				PDName:    "test-disk",
   697  				FSType:    "ext4",
   698  				Partition: 0,
   699  				ReadOnly:  false,
   700  			},
   701  		}
   702  	} else {
   703  		pv.Spec.PersistentVolumeSource = v1.PersistentVolumeSource{
   704  			RBD: &v1.RBDPersistentVolumeSource{
   705  				RBDImage: "test-disk",
   706  			},
   707  		}
   708  	}
   709  	return pv
   710  }
   711  
   712  func makeLocalPV(name, node, capacity, version string, boundToPVC *v1.PersistentVolumeClaim, className string) *v1.PersistentVolume {
   713  	pv := makeTestPV(name, node, capacity, version, boundToPVC, className)
   714  	pv.Spec.NodeAffinity.Required.NodeSelectorTerms[0].MatchExpressions[0].Key = v1.LabelHostname
   715  	return pv
   716  }
   717  
   718  func pvcSetSelectedNode(pvc *v1.PersistentVolumeClaim, node string) *v1.PersistentVolumeClaim {
   719  	newPVC := pvc.DeepCopy()
   720  	metav1.SetMetaDataAnnotation(&newPVC.ObjectMeta, volume.AnnSelectedNode, node)
   721  	return newPVC
   722  }
   723  
   724  func pvcSetEmptyAnnotations(pvc *v1.PersistentVolumeClaim) *v1.PersistentVolumeClaim {
   725  	newPVC := pvc.DeepCopy()
   726  	newPVC.Annotations = map[string]string{}
   727  	return newPVC
   728  }
   729  
   730  func pvRemoveClaimUID(pv *v1.PersistentVolume) *v1.PersistentVolume {
   731  	newPV := pv.DeepCopy()
   732  	newPV.Spec.ClaimRef.UID = ""
   733  	return newPV
   734  }
   735  
   736  func makeCSINode(name, migratedPlugin string) *storagev1.CSINode {
   737  	return &storagev1.CSINode{
   738  		ObjectMeta: metav1.ObjectMeta{
   739  			Name: name,
   740  			Annotations: map[string]string{
   741  				v1.MigratedPluginsAnnotationKey: migratedPlugin,
   742  			},
   743  		},
   744  	}
   745  }
   746  
   747  func makeCSIDriver(name string, storageCapacity bool) *storagev1.CSIDriver {
   748  	return &storagev1.CSIDriver{
   749  		ObjectMeta: metav1.ObjectMeta{
   750  			Name: name,
   751  		},
   752  		Spec: storagev1.CSIDriverSpec{
   753  			StorageCapacity: &storageCapacity,
   754  		},
   755  	}
   756  }
   757  
   758  func makeCapacity(name, storageClassName string, node *v1.Node, capacityStr, maximumVolumeSizeStr string) *storagev1.CSIStorageCapacity {
   759  	c := &storagev1.CSIStorageCapacity{
   760  		ObjectMeta: metav1.ObjectMeta{
   761  			Name: name,
   762  		},
   763  		StorageClassName: storageClassName,
   764  		NodeTopology:     &metav1.LabelSelector{},
   765  	}
   766  	if node != nil {
   767  		c.NodeTopology.MatchLabels = map[string]string{nodeLabelKey: node.Labels[nodeLabelKey]}
   768  	}
   769  	if capacityStr != "" {
   770  		capacityQuantity := resource.MustParse(capacityStr)
   771  		c.Capacity = &capacityQuantity
   772  	}
   773  	if maximumVolumeSizeStr != "" {
   774  		maximumVolumeSizeQuantity := resource.MustParse(maximumVolumeSizeStr)
   775  		c.MaximumVolumeSize = &maximumVolumeSizeQuantity
   776  	}
   777  	return c
   778  }
   779  
   780  func makeBinding(pvc *v1.PersistentVolumeClaim, pv *v1.PersistentVolume) *BindingInfo {
   781  	return &BindingInfo{pvc: pvc.DeepCopy(), pv: pv.DeepCopy()}
   782  }
   783  
   784  func addProvisionAnn(pvc *v1.PersistentVolumeClaim) *v1.PersistentVolumeClaim {
   785  	res := pvc.DeepCopy()
   786  	// Add provision related annotations
   787  	metav1.SetMetaDataAnnotation(&res.ObjectMeta, volume.AnnSelectedNode, nodeLabelValue)
   788  
   789  	return res
   790  }
   791  
   792  // reasonNames pretty-prints a list of reasons with variable names in
   793  // case of a test failure because that is easier to read than the full
   794  // strings.
   795  func reasonNames(reasons ConflictReasons) string {
   796  	var varNames []string
   797  	for _, reason := range reasons {
   798  		switch reason {
   799  		case ErrReasonBindConflict:
   800  			varNames = append(varNames, "ErrReasonBindConflict")
   801  		case ErrReasonNodeConflict:
   802  			varNames = append(varNames, "ErrReasonNodeConflict")
   803  		case ErrReasonNotEnoughSpace:
   804  			varNames = append(varNames, "ErrReasonNotEnoughSpace")
   805  		default:
   806  			varNames = append(varNames, string(reason))
   807  		}
   808  	}
   809  	return fmt.Sprintf("%v", varNames)
   810  }
   811  
   812  func checkReasons(t *testing.T, actual, expected ConflictReasons) {
   813  	equal := len(actual) == len(expected)
   814  	sort.Sort(actual)
   815  	sort.Sort(expected)
   816  	if equal {
   817  		for i, reason := range actual {
   818  			if reason != expected[i] {
   819  				equal = false
   820  				break
   821  			}
   822  		}
   823  	}
   824  	if !equal {
   825  		t.Errorf("expected failure reasons %s, got %s", reasonNames(expected), reasonNames(actual))
   826  	}
   827  }
   828  
   829  // findPodVolumes gets and finds volumes for given pod and node
   830  func findPodVolumes(logger klog.Logger, binder SchedulerVolumeBinder, pod *v1.Pod, node *v1.Node) (*PodVolumes, ConflictReasons, error) {
   831  	podVolumeClaims, err := binder.GetPodVolumeClaims(logger, pod)
   832  	if err != nil {
   833  		return nil, nil, err
   834  	}
   835  	if len(podVolumeClaims.unboundClaimsImmediate) > 0 {
   836  		return nil, nil, fmt.Errorf("pod has unbound immediate PersistentVolumeClaims")
   837  	}
   838  	return binder.FindPodVolumes(logger, pod, podVolumeClaims, node)
   839  }
   840  
   841  func TestFindPodVolumesWithoutProvisioning(t *testing.T) {
   842  	t.Parallel()
   843  
   844  	type scenarioType struct {
   845  		// Inputs
   846  		pvs     []*v1.PersistentVolume
   847  		podPVCs []*v1.PersistentVolumeClaim
   848  		// If nil, use pod PVCs
   849  		cachePVCs []*v1.PersistentVolumeClaim
   850  		// If nil, makePod with podPVCs
   851  		pod *v1.Pod
   852  
   853  		// Expected podBindingCache fields
   854  		expectedBindings []*BindingInfo
   855  
   856  		// Expected return values
   857  		reasons    ConflictReasons
   858  		shouldFail bool
   859  	}
   860  	scenarios := map[string]scenarioType{
   861  		"no-volumes": {
   862  			pod: makePod("test-pod").
   863  				withNamespace("testns").
   864  				withNodeName("node1").Pod,
   865  		},
   866  		"no-pvcs": {
   867  			pod: makePod("test-pod").
   868  				withNamespace("testns").
   869  				withNodeName("node1").
   870  				withEmptyDirVolume().Pod,
   871  		},
   872  		"pvc-not-found": {
   873  			cachePVCs:  []*v1.PersistentVolumeClaim{},
   874  			podPVCs:    []*v1.PersistentVolumeClaim{boundPVC},
   875  			shouldFail: true,
   876  		},
   877  		"bound-pvc": {
   878  			podPVCs: []*v1.PersistentVolumeClaim{boundPVC},
   879  			pvs:     []*v1.PersistentVolume{pvBound},
   880  		},
   881  		"bound-pvc,pv-not-exists": {
   882  			podPVCs:    []*v1.PersistentVolumeClaim{boundPVC},
   883  			shouldFail: false,
   884  			reasons:    ConflictReasons{ErrReasonPVNotExist},
   885  		},
   886  		"prebound-pvc": {
   887  			podPVCs:    []*v1.PersistentVolumeClaim{preboundPVC},
   888  			pvs:        []*v1.PersistentVolume{pvNode1aBound},
   889  			shouldFail: true,
   890  		},
   891  		"unbound-pvc,pv-same-node": {
   892  			podPVCs:          []*v1.PersistentVolumeClaim{unboundPVC},
   893  			pvs:              []*v1.PersistentVolume{pvNode2, pvNode1a, pvNode1b},
   894  			expectedBindings: []*BindingInfo{makeBinding(unboundPVC, pvNode1a)},
   895  		},
   896  		"unbound-pvc,pv-different-node": {
   897  			podPVCs: []*v1.PersistentVolumeClaim{unboundPVC},
   898  			pvs:     []*v1.PersistentVolume{pvNode2},
   899  			reasons: ConflictReasons{ErrReasonBindConflict},
   900  		},
   901  		"two-unbound-pvcs": {
   902  			podPVCs:          []*v1.PersistentVolumeClaim{unboundPVC, unboundPVC2},
   903  			pvs:              []*v1.PersistentVolume{pvNode1a, pvNode1b},
   904  			expectedBindings: []*BindingInfo{makeBinding(unboundPVC, pvNode1a), makeBinding(unboundPVC2, pvNode1b)},
   905  		},
   906  		"two-unbound-pvcs,order-by-size": {
   907  			podPVCs:          []*v1.PersistentVolumeClaim{unboundPVC2, unboundPVC},
   908  			pvs:              []*v1.PersistentVolume{pvNode1a, pvNode1b},
   909  			expectedBindings: []*BindingInfo{makeBinding(unboundPVC, pvNode1a), makeBinding(unboundPVC2, pvNode1b)},
   910  		},
   911  		"two-unbound-pvcs,partial-match": {
   912  			podPVCs:          []*v1.PersistentVolumeClaim{unboundPVC, unboundPVC2},
   913  			pvs:              []*v1.PersistentVolume{pvNode1a},
   914  			expectedBindings: []*BindingInfo{makeBinding(unboundPVC, pvNode1a)},
   915  			reasons:          ConflictReasons{ErrReasonBindConflict},
   916  		},
   917  		"one-bound,one-unbound": {
   918  			podPVCs:          []*v1.PersistentVolumeClaim{unboundPVC, boundPVC},
   919  			pvs:              []*v1.PersistentVolume{pvBound, pvNode1a},
   920  			expectedBindings: []*BindingInfo{makeBinding(unboundPVC, pvNode1a)},
   921  		},
   922  		"one-bound,one-unbound,no-match": {
   923  			podPVCs: []*v1.PersistentVolumeClaim{unboundPVC, boundPVC},
   924  			pvs:     []*v1.PersistentVolume{pvBound, pvNode2},
   925  			reasons: ConflictReasons{ErrReasonBindConflict},
   926  		},
   927  		"one-prebound,one-unbound": {
   928  			podPVCs:    []*v1.PersistentVolumeClaim{unboundPVC, preboundPVC},
   929  			pvs:        []*v1.PersistentVolume{pvNode1a, pvNode1b},
   930  			shouldFail: true,
   931  		},
   932  		"immediate-bound-pvc": {
   933  			podPVCs: []*v1.PersistentVolumeClaim{immediateBoundPVC},
   934  			pvs:     []*v1.PersistentVolume{pvBoundImmediate},
   935  		},
   936  		"immediate-bound-pvc-wrong-node": {
   937  			podPVCs: []*v1.PersistentVolumeClaim{immediateBoundPVC},
   938  			pvs:     []*v1.PersistentVolume{pvBoundImmediateNode2},
   939  			reasons: ConflictReasons{ErrReasonNodeConflict},
   940  		},
   941  		"immediate-unbound-pvc": {
   942  			podPVCs:    []*v1.PersistentVolumeClaim{immediateUnboundPVC},
   943  			shouldFail: true,
   944  		},
   945  		"immediate-unbound-pvc,delayed-mode-bound": {
   946  			podPVCs:    []*v1.PersistentVolumeClaim{immediateUnboundPVC, boundPVC},
   947  			pvs:        []*v1.PersistentVolume{pvBound},
   948  			shouldFail: true,
   949  		},
   950  		"immediate-unbound-pvc,delayed-mode-unbound": {
   951  			podPVCs:    []*v1.PersistentVolumeClaim{immediateUnboundPVC, unboundPVC},
   952  			shouldFail: true,
   953  		},
   954  		"generic-ephemeral,no-pvc": {
   955  			pod: makePod("test-pod").
   956  				withNamespace("testns").
   957  				withNodeName("node1").
   958  				withGenericEphemeralVolume("no-such-pvc").Pod,
   959  			shouldFail: true,
   960  		},
   961  		"generic-ephemeral,with-pvc": {
   962  			pod: makePod("test-pod").
   963  				withNamespace("testns").
   964  				withNodeName("node1").
   965  				withGenericEphemeralVolume("test-volume").Pod,
   966  			cachePVCs: []*v1.PersistentVolumeClaim{correctGenericPVC},
   967  			pvs:       []*v1.PersistentVolume{pvBoundGeneric},
   968  		},
   969  		"generic-ephemeral,wrong-pvc": {
   970  			pod: makePod("test-pod").
   971  				withNamespace("testns").
   972  				withNodeName("node1").
   973  				withGenericEphemeralVolume("test-volume").Pod,
   974  			cachePVCs:  []*v1.PersistentVolumeClaim{conflictingGenericPVC},
   975  			pvs:        []*v1.PersistentVolume{pvBoundGeneric},
   976  			shouldFail: true,
   977  		},
   978  	}
   979  
   980  	testNode := &v1.Node{
   981  		ObjectMeta: metav1.ObjectMeta{
   982  			Name: "node1",
   983  			Labels: map[string]string{
   984  				nodeLabelKey: "node1",
   985  			},
   986  		},
   987  	}
   988  
   989  	run := func(t *testing.T, scenario scenarioType, csiDriver *storagev1.CSIDriver) {
   990  		logger, ctx := ktesting.NewTestContext(t)
   991  		ctx, cancel := context.WithCancel(ctx)
   992  		defer cancel()
   993  
   994  		// Setup
   995  		testEnv := newTestBinder(t, ctx)
   996  		testEnv.initVolumes(scenario.pvs, scenario.pvs)
   997  		if csiDriver != nil {
   998  			testEnv.addCSIDriver(csiDriver)
   999  		}
  1000  
  1001  		// a. Init pvc cache
  1002  		if scenario.cachePVCs == nil {
  1003  			scenario.cachePVCs = scenario.podPVCs
  1004  		}
  1005  		testEnv.initClaims(scenario.cachePVCs, scenario.cachePVCs)
  1006  
  1007  		// b. Generate pod with given claims
  1008  		if scenario.pod == nil {
  1009  			scenario.pod = makePod("test-pod").
  1010  				withNamespace("testns").
  1011  				withNodeName("node1").
  1012  				withPVCSVolume(scenario.podPVCs).Pod
  1013  		}
  1014  
  1015  		// Execute
  1016  		podVolumes, reasons, err := findPodVolumes(logger, testEnv.binder, scenario.pod, testNode)
  1017  
  1018  		// Validate
  1019  		if !scenario.shouldFail && err != nil {
  1020  			t.Errorf("returned error: %v", err)
  1021  		}
  1022  		if scenario.shouldFail && err == nil {
  1023  			t.Error("returned success but expected error")
  1024  		}
  1025  		checkReasons(t, reasons, scenario.reasons)
  1026  		testEnv.validatePodCache(t, testNode.Name, scenario.pod, podVolumes, scenario.expectedBindings, nil)
  1027  	}
  1028  
  1029  	for description, csiDriver := range map[string]*storagev1.CSIDriver{
  1030  		"no CSIDriver":                        nil,
  1031  		"CSIDriver with capacity tracking":    makeCSIDriver(provisioner, true),
  1032  		"CSIDriver without capacity tracking": makeCSIDriver(provisioner, false),
  1033  	} {
  1034  		t.Run(description, func(t *testing.T) {
  1035  			for name, scenario := range scenarios {
  1036  				t.Run(name, func(t *testing.T) { run(t, scenario, csiDriver) })
  1037  			}
  1038  		})
  1039  	}
  1040  }
  1041  
  1042  func TestFindPodVolumesWithProvisioning(t *testing.T) {
  1043  	t.Parallel()
  1044  
  1045  	type scenarioType struct {
  1046  		// Inputs
  1047  		pvs     []*v1.PersistentVolume
  1048  		podPVCs []*v1.PersistentVolumeClaim
  1049  		// If nil, use pod PVCs
  1050  		cachePVCs []*v1.PersistentVolumeClaim
  1051  		// If nil, makePod with podPVCs
  1052  		pod *v1.Pod
  1053  
  1054  		// Expected podBindingCache fields
  1055  		expectedBindings   []*BindingInfo
  1056  		expectedProvisions []*v1.PersistentVolumeClaim
  1057  
  1058  		// Expected return values
  1059  		reasons       ConflictReasons
  1060  		shouldFail    bool
  1061  		needsCapacity bool
  1062  	}
  1063  	scenarios := map[string]scenarioType{
  1064  		"one-provisioned": {
  1065  			podPVCs:            []*v1.PersistentVolumeClaim{provisionedPVC},
  1066  			expectedProvisions: []*v1.PersistentVolumeClaim{provisionedPVC},
  1067  			needsCapacity:      true,
  1068  		},
  1069  		"two-unbound-pvcs,one-matched,one-provisioned": {
  1070  			podPVCs:            []*v1.PersistentVolumeClaim{unboundPVC, provisionedPVC},
  1071  			pvs:                []*v1.PersistentVolume{pvNode1a},
  1072  			expectedBindings:   []*BindingInfo{makeBinding(unboundPVC, pvNode1a)},
  1073  			expectedProvisions: []*v1.PersistentVolumeClaim{provisionedPVC},
  1074  			needsCapacity:      true,
  1075  		},
  1076  		"one-bound,one-provisioned": {
  1077  			podPVCs:            []*v1.PersistentVolumeClaim{boundPVC, provisionedPVC},
  1078  			pvs:                []*v1.PersistentVolume{pvBound},
  1079  			expectedProvisions: []*v1.PersistentVolumeClaim{provisionedPVC},
  1080  			needsCapacity:      true,
  1081  		},
  1082  		"one-binding,one-selected-node": {
  1083  			podPVCs:            []*v1.PersistentVolumeClaim{boundPVC, selectedNodePVC},
  1084  			pvs:                []*v1.PersistentVolume{pvBound},
  1085  			expectedProvisions: []*v1.PersistentVolumeClaim{selectedNodePVC},
  1086  			needsCapacity:      true,
  1087  		},
  1088  		"immediate-unbound-pvc": {
  1089  			podPVCs:    []*v1.PersistentVolumeClaim{immediateUnboundPVC},
  1090  			shouldFail: true,
  1091  		},
  1092  		"one-immediate-bound,one-provisioned": {
  1093  			podPVCs:            []*v1.PersistentVolumeClaim{immediateBoundPVC, provisionedPVC},
  1094  			pvs:                []*v1.PersistentVolume{pvBoundImmediate},
  1095  			expectedProvisions: []*v1.PersistentVolumeClaim{provisionedPVC},
  1096  			needsCapacity:      true,
  1097  		},
  1098  		"invalid-provisioner": {
  1099  			podPVCs: []*v1.PersistentVolumeClaim{noProvisionerPVC},
  1100  			reasons: ConflictReasons{ErrReasonBindConflict},
  1101  		},
  1102  		"volume-topology-unsatisfied": {
  1103  			podPVCs: []*v1.PersistentVolumeClaim{topoMismatchPVC},
  1104  			reasons: ConflictReasons{ErrReasonBindConflict},
  1105  		},
  1106  	}
  1107  
  1108  	testNode := &v1.Node{
  1109  		ObjectMeta: metav1.ObjectMeta{
  1110  			Name: "node1",
  1111  			Labels: map[string]string{
  1112  				nodeLabelKey: "node1",
  1113  			},
  1114  		},
  1115  	}
  1116  
  1117  	run := func(t *testing.T, scenario scenarioType, csiDriver *storagev1.CSIDriver) {
  1118  		logger, ctx := ktesting.NewTestContext(t)
  1119  		ctx, cancel := context.WithCancel(ctx)
  1120  		defer cancel()
  1121  
  1122  		// Setup
  1123  		testEnv := newTestBinder(t, ctx)
  1124  		testEnv.initVolumes(scenario.pvs, scenario.pvs)
  1125  		if csiDriver != nil {
  1126  			testEnv.addCSIDriver(csiDriver)
  1127  		}
  1128  
  1129  		// a. Init pvc cache
  1130  		if scenario.cachePVCs == nil {
  1131  			scenario.cachePVCs = scenario.podPVCs
  1132  		}
  1133  		testEnv.initClaims(scenario.cachePVCs, scenario.cachePVCs)
  1134  
  1135  		// b. Generate pod with given claims
  1136  		if scenario.pod == nil {
  1137  			scenario.pod = makePod("test-pod").
  1138  				withNamespace("testns").
  1139  				withNodeName("node1").
  1140  				withPVCSVolume(scenario.podPVCs).Pod
  1141  		}
  1142  
  1143  		// Execute
  1144  		podVolumes, reasons, err := findPodVolumes(logger, testEnv.binder, scenario.pod, testNode)
  1145  
  1146  		// Validate
  1147  		if !scenario.shouldFail && err != nil {
  1148  			t.Errorf("returned error: %v", err)
  1149  		}
  1150  		if scenario.shouldFail && err == nil {
  1151  			t.Error("returned success but expected error")
  1152  		}
  1153  		expectedReasons := scenario.reasons
  1154  		expectedProvisions := scenario.expectedProvisions
  1155  		if scenario.needsCapacity &&
  1156  			csiDriver != nil && csiDriver.Spec.StorageCapacity != nil && *csiDriver.Spec.StorageCapacity {
  1157  			// Without CSIStorageCapacity objects, provisioning is blocked.
  1158  			expectedReasons = append(expectedReasons, ErrReasonNotEnoughSpace)
  1159  			expectedProvisions = nil
  1160  		}
  1161  		checkReasons(t, reasons, expectedReasons)
  1162  		testEnv.validatePodCache(t, testNode.Name, scenario.pod, podVolumes, scenario.expectedBindings, expectedProvisions)
  1163  	}
  1164  
  1165  	for description, csiDriver := range map[string]*storagev1.CSIDriver{
  1166  		"no CSIDriver":                        nil,
  1167  		"CSIDriver with capacity tracking":    makeCSIDriver(provisioner, true),
  1168  		"CSIDriver without capacity tracking": makeCSIDriver(provisioner, false),
  1169  	} {
  1170  		t.Run(description, func(t *testing.T) {
  1171  			for name, scenario := range scenarios {
  1172  				t.Run(name, func(t *testing.T) { run(t, scenario, csiDriver) })
  1173  			}
  1174  		})
  1175  	}
  1176  }
  1177  
  1178  // TestFindPodVolumesWithCSIMigration aims to test the node affinity check procedure that's
  1179  // done in FindPodVolumes. In order to reach this code path, the given PVCs must be bound to a PV.
  1180  func TestFindPodVolumesWithCSIMigration(t *testing.T) {
  1181  	type scenarioType struct {
  1182  		// Inputs
  1183  		pvs     []*v1.PersistentVolume
  1184  		podPVCs []*v1.PersistentVolumeClaim
  1185  		// If nil, use pod PVCs
  1186  		cachePVCs []*v1.PersistentVolumeClaim
  1187  		// If nil, makePod with podPVCs
  1188  		pod *v1.Pod
  1189  
  1190  		// Setup
  1191  		initNodes    []*v1.Node
  1192  		initCSINodes []*storagev1.CSINode
  1193  
  1194  		// Expected return values
  1195  		reasons    ConflictReasons
  1196  		shouldFail bool
  1197  	}
  1198  	scenarios := map[string]scenarioType{
  1199  		"pvc-bound": {
  1200  			podPVCs:      []*v1.PersistentVolumeClaim{boundMigrationPVC},
  1201  			pvs:          []*v1.PersistentVolume{migrationPVBound},
  1202  			initNodes:    []*v1.Node{node1Zone1},
  1203  			initCSINodes: []*storagev1.CSINode{csiNode1Migrated},
  1204  		},
  1205  		"pvc-bound,csinode-not-migrated": {
  1206  			podPVCs:      []*v1.PersistentVolumeClaim{boundMigrationPVC},
  1207  			pvs:          []*v1.PersistentVolume{migrationPVBound},
  1208  			initNodes:    []*v1.Node{node1Zone1},
  1209  			initCSINodes: []*storagev1.CSINode{csiNode1NotMigrated},
  1210  		},
  1211  		"pvc-bound,missing-csinode": {
  1212  			podPVCs:   []*v1.PersistentVolumeClaim{boundMigrationPVC},
  1213  			pvs:       []*v1.PersistentVolume{migrationPVBound},
  1214  			initNodes: []*v1.Node{node1Zone1},
  1215  		},
  1216  		"pvc-bound,node-different-zone": {
  1217  			podPVCs:      []*v1.PersistentVolumeClaim{boundMigrationPVC},
  1218  			pvs:          []*v1.PersistentVolume{migrationPVBound},
  1219  			initNodes:    []*v1.Node{node1Zone2},
  1220  			initCSINodes: []*storagev1.CSINode{csiNode1Migrated},
  1221  			reasons:      ConflictReasons{ErrReasonNodeConflict},
  1222  		},
  1223  	}
  1224  
  1225  	run := func(t *testing.T, scenario scenarioType) {
  1226  		logger, ctx := ktesting.NewTestContext(t)
  1227  		ctx, cancel := context.WithCancel(ctx)
  1228  		defer cancel()
  1229  
  1230  		// Setup
  1231  		testEnv := newTestBinder(t, ctx)
  1232  		testEnv.initVolumes(scenario.pvs, scenario.pvs)
  1233  
  1234  		var node *v1.Node
  1235  		if len(scenario.initNodes) > 0 {
  1236  			testEnv.initNodes(scenario.initNodes)
  1237  			node = scenario.initNodes[0]
  1238  		} else {
  1239  			node = node1
  1240  		}
  1241  
  1242  		if len(scenario.initCSINodes) > 0 {
  1243  			testEnv.initCSINodes(scenario.initCSINodes)
  1244  		}
  1245  
  1246  		// a. Init pvc cache
  1247  		if scenario.cachePVCs == nil {
  1248  			scenario.cachePVCs = scenario.podPVCs
  1249  		}
  1250  		testEnv.initClaims(scenario.cachePVCs, scenario.cachePVCs)
  1251  
  1252  		// b. Generate pod with given claims
  1253  		if scenario.pod == nil {
  1254  			scenario.pod = makePod("test-pod").
  1255  				withNamespace("testns").
  1256  				withNodeName("node1").
  1257  				withPVCSVolume(scenario.podPVCs).Pod
  1258  		}
  1259  
  1260  		// Execute
  1261  		_, reasons, err := findPodVolumes(logger, testEnv.binder, scenario.pod, node)
  1262  
  1263  		// Validate
  1264  		if !scenario.shouldFail && err != nil {
  1265  			t.Errorf("returned error: %v", err)
  1266  		}
  1267  		if scenario.shouldFail && err == nil {
  1268  			t.Error("returned success but expected error")
  1269  		}
  1270  		checkReasons(t, reasons, scenario.reasons)
  1271  	}
  1272  
  1273  	for name, scenario := range scenarios {
  1274  		t.Run(name, func(t *testing.T) { run(t, scenario) })
  1275  	}
  1276  }
  1277  
  1278  func TestAssumePodVolumes(t *testing.T) {
  1279  	type scenarioType struct {
  1280  		// Inputs
  1281  		podPVCs         []*v1.PersistentVolumeClaim
  1282  		pvs             []*v1.PersistentVolume
  1283  		bindings        []*BindingInfo
  1284  		provisionedPVCs []*v1.PersistentVolumeClaim
  1285  
  1286  		// Expected return values
  1287  		shouldFail       bool
  1288  		expectedAllBound bool
  1289  
  1290  		expectedBindings      []*BindingInfo
  1291  		expectedProvisionings []*v1.PersistentVolumeClaim
  1292  	}
  1293  	scenarios := map[string]scenarioType{
  1294  		"all-bound": {
  1295  			podPVCs:          []*v1.PersistentVolumeClaim{boundPVC},
  1296  			pvs:              []*v1.PersistentVolume{pvBound},
  1297  			expectedAllBound: true,
  1298  		},
  1299  		"one-binding": {
  1300  			podPVCs:               []*v1.PersistentVolumeClaim{unboundPVC},
  1301  			bindings:              []*BindingInfo{makeBinding(unboundPVC, pvNode1a)},
  1302  			pvs:                   []*v1.PersistentVolume{pvNode1a},
  1303  			expectedBindings:      []*BindingInfo{makeBinding(unboundPVC, pvNode1aBound)},
  1304  			expectedProvisionings: []*v1.PersistentVolumeClaim{},
  1305  		},
  1306  		"two-bindings": {
  1307  			podPVCs:               []*v1.PersistentVolumeClaim{unboundPVC, unboundPVC2},
  1308  			bindings:              []*BindingInfo{makeBinding(unboundPVC, pvNode1a), makeBinding(unboundPVC2, pvNode1b)},
  1309  			pvs:                   []*v1.PersistentVolume{pvNode1a, pvNode1b},
  1310  			expectedBindings:      []*BindingInfo{makeBinding(unboundPVC, pvNode1aBound), makeBinding(unboundPVC2, pvNode1bBound)},
  1311  			expectedProvisionings: []*v1.PersistentVolumeClaim{},
  1312  		},
  1313  		"pv-already-bound": {
  1314  			podPVCs:               []*v1.PersistentVolumeClaim{unboundPVC},
  1315  			bindings:              []*BindingInfo{makeBinding(unboundPVC, pvNode1aBound)},
  1316  			pvs:                   []*v1.PersistentVolume{pvNode1aBound},
  1317  			expectedBindings:      []*BindingInfo{makeBinding(unboundPVC, pvNode1aBound)},
  1318  			expectedProvisionings: []*v1.PersistentVolumeClaim{},
  1319  		},
  1320  		"tmpupdate-failed": {
  1321  			podPVCs:    []*v1.PersistentVolumeClaim{unboundPVC},
  1322  			bindings:   []*BindingInfo{makeBinding(unboundPVC, pvNode1a), makeBinding(unboundPVC2, pvNode1b)},
  1323  			pvs:        []*v1.PersistentVolume{pvNode1a},
  1324  			shouldFail: true,
  1325  		},
  1326  		"one-binding, one-pvc-provisioned": {
  1327  			podPVCs:               []*v1.PersistentVolumeClaim{unboundPVC, provisionedPVC},
  1328  			bindings:              []*BindingInfo{makeBinding(unboundPVC, pvNode1a)},
  1329  			pvs:                   []*v1.PersistentVolume{pvNode1a},
  1330  			provisionedPVCs:       []*v1.PersistentVolumeClaim{provisionedPVC},
  1331  			expectedBindings:      []*BindingInfo{makeBinding(unboundPVC, pvNode1aBound)},
  1332  			expectedProvisionings: []*v1.PersistentVolumeClaim{selectedNodePVC},
  1333  		},
  1334  		"one-binding, one-provision-tmpupdate-failed": {
  1335  			podPVCs:         []*v1.PersistentVolumeClaim{unboundPVC, provisionedPVCHigherVersion},
  1336  			bindings:        []*BindingInfo{makeBinding(unboundPVC, pvNode1a)},
  1337  			pvs:             []*v1.PersistentVolume{pvNode1a},
  1338  			provisionedPVCs: []*v1.PersistentVolumeClaim{provisionedPVC2},
  1339  			shouldFail:      true,
  1340  		},
  1341  	}
  1342  
  1343  	run := func(t *testing.T, scenario scenarioType) {
  1344  		logger, ctx := ktesting.NewTestContext(t)
  1345  		ctx, cancel := context.WithCancel(ctx)
  1346  		defer cancel()
  1347  
  1348  		// Setup
  1349  		testEnv := newTestBinder(t, ctx)
  1350  		testEnv.initClaims(scenario.podPVCs, scenario.podPVCs)
  1351  		pod := makePod("test-pod").
  1352  			withNamespace("testns").
  1353  			withNodeName("node1").
  1354  			withPVCSVolume(scenario.podPVCs).Pod
  1355  		podVolumes := &PodVolumes{
  1356  			StaticBindings:    scenario.bindings,
  1357  			DynamicProvisions: scenario.provisionedPVCs,
  1358  		}
  1359  		testEnv.initVolumes(scenario.pvs, scenario.pvs)
  1360  
  1361  		// Execute
  1362  		allBound, err := testEnv.binder.AssumePodVolumes(logger, pod, "node1", podVolumes)
  1363  
  1364  		// Validate
  1365  		if !scenario.shouldFail && err != nil {
  1366  			t.Errorf("returned error: %v", err)
  1367  		}
  1368  		if scenario.shouldFail && err == nil {
  1369  			t.Error("returned success but expected error")
  1370  		}
  1371  		if scenario.expectedAllBound != allBound {
  1372  			t.Errorf("returned unexpected allBound: %v", allBound)
  1373  		}
  1374  		if scenario.expectedBindings == nil {
  1375  			scenario.expectedBindings = scenario.bindings
  1376  		}
  1377  		if scenario.expectedProvisionings == nil {
  1378  			scenario.expectedProvisionings = scenario.provisionedPVCs
  1379  		}
  1380  		if scenario.shouldFail {
  1381  			testEnv.validateCacheRestored(t, pod, scenario.bindings, scenario.provisionedPVCs)
  1382  		} else {
  1383  			testEnv.validateAssume(t, pod, scenario.expectedBindings, scenario.expectedProvisionings)
  1384  		}
  1385  		testEnv.validatePodCache(t, pod.Spec.NodeName, pod, podVolumes, scenario.expectedBindings, scenario.expectedProvisionings)
  1386  	}
  1387  
  1388  	for name, scenario := range scenarios {
  1389  		t.Run(name, func(t *testing.T) { run(t, scenario) })
  1390  	}
  1391  }
  1392  
  1393  func TestRevertAssumedPodVolumes(t *testing.T) {
  1394  	logger, ctx := ktesting.NewTestContext(t)
  1395  	ctx, cancel := context.WithCancel(ctx)
  1396  	defer cancel()
  1397  
  1398  	podPVCs := []*v1.PersistentVolumeClaim{unboundPVC, provisionedPVC}
  1399  	bindings := []*BindingInfo{makeBinding(unboundPVC, pvNode1a)}
  1400  	pvs := []*v1.PersistentVolume{pvNode1a}
  1401  	provisionedPVCs := []*v1.PersistentVolumeClaim{provisionedPVC}
  1402  	expectedBindings := []*BindingInfo{makeBinding(unboundPVC, pvNode1aBound)}
  1403  	expectedProvisionings := []*v1.PersistentVolumeClaim{selectedNodePVC}
  1404  
  1405  	// Setup
  1406  	testEnv := newTestBinder(t, ctx)
  1407  	testEnv.initClaims(podPVCs, podPVCs)
  1408  	pod := makePod("test-pod").
  1409  		withNamespace("testns").
  1410  		withNodeName("node1").
  1411  		withPVCSVolume(podPVCs).Pod
  1412  	podVolumes := &PodVolumes{
  1413  		StaticBindings:    bindings,
  1414  		DynamicProvisions: provisionedPVCs,
  1415  	}
  1416  	testEnv.initVolumes(pvs, pvs)
  1417  
  1418  	allbound, err := testEnv.binder.AssumePodVolumes(logger, pod, "node1", podVolumes)
  1419  	if allbound || err != nil {
  1420  		t.Errorf("No volumes are assumed")
  1421  	}
  1422  	testEnv.validateAssume(t, pod, expectedBindings, expectedProvisionings)
  1423  
  1424  	testEnv.binder.RevertAssumedPodVolumes(podVolumes)
  1425  	testEnv.validateCacheRestored(t, pod, bindings, provisionedPVCs)
  1426  }
  1427  
  1428  func TestBindAPIUpdate(t *testing.T) {
  1429  	type scenarioType struct {
  1430  		// Inputs
  1431  		bindings  []*BindingInfo
  1432  		cachedPVs []*v1.PersistentVolume
  1433  		// if nil, use cachedPVs
  1434  		apiPVs []*v1.PersistentVolume
  1435  
  1436  		provisionedPVCs []*v1.PersistentVolumeClaim
  1437  		cachedPVCs      []*v1.PersistentVolumeClaim
  1438  		// if nil, use cachedPVCs
  1439  		apiPVCs []*v1.PersistentVolumeClaim
  1440  
  1441  		// Expected return values
  1442  		shouldFail  bool
  1443  		expectedPVs []*v1.PersistentVolume
  1444  		// if nil, use expectedPVs
  1445  		expectedAPIPVs []*v1.PersistentVolume
  1446  
  1447  		expectedPVCs []*v1.PersistentVolumeClaim
  1448  		// if nil, use expectedPVCs
  1449  		expectedAPIPVCs []*v1.PersistentVolumeClaim
  1450  	}
  1451  	scenarios := map[string]scenarioType{
  1452  		"nothing-to-bind-nil": {
  1453  			shouldFail: true,
  1454  		},
  1455  		"nothing-to-bind-bindings-nil": {
  1456  			provisionedPVCs: []*v1.PersistentVolumeClaim{},
  1457  			shouldFail:      true,
  1458  		},
  1459  		"nothing-to-bind-provisionings-nil": {
  1460  			bindings:   []*BindingInfo{},
  1461  			shouldFail: true,
  1462  		},
  1463  		"nothing-to-bind-empty": {
  1464  			bindings:        []*BindingInfo{},
  1465  			provisionedPVCs: []*v1.PersistentVolumeClaim{},
  1466  		},
  1467  		"one-binding": {
  1468  			bindings:        []*BindingInfo{makeBinding(unboundPVC, pvNode1aBound)},
  1469  			cachedPVs:       []*v1.PersistentVolume{pvNode1a},
  1470  			expectedPVs:     []*v1.PersistentVolume{pvNode1aBound},
  1471  			provisionedPVCs: []*v1.PersistentVolumeClaim{},
  1472  		},
  1473  		"two-bindings": {
  1474  			bindings:        []*BindingInfo{makeBinding(unboundPVC, pvNode1aBound), makeBinding(unboundPVC2, pvNode1bBound)},
  1475  			cachedPVs:       []*v1.PersistentVolume{pvNode1a, pvNode1b},
  1476  			expectedPVs:     []*v1.PersistentVolume{pvNode1aBound, pvNode1bBound},
  1477  			provisionedPVCs: []*v1.PersistentVolumeClaim{},
  1478  		},
  1479  		"api-already-updated": {
  1480  			bindings:        []*BindingInfo{makeBinding(unboundPVC, pvNode1aBound)},
  1481  			cachedPVs:       []*v1.PersistentVolume{pvNode1aBound},
  1482  			expectedPVs:     []*v1.PersistentVolume{pvNode1aBound},
  1483  			provisionedPVCs: []*v1.PersistentVolumeClaim{},
  1484  		},
  1485  		"api-update-failed": {
  1486  			bindings:        []*BindingInfo{makeBinding(unboundPVC, pvNode1aBound), makeBinding(unboundPVC2, pvNode1bBound)},
  1487  			cachedPVs:       []*v1.PersistentVolume{pvNode1a, pvNode1b},
  1488  			apiPVs:          []*v1.PersistentVolume{pvNode1a, pvNode1bBoundHigherVersion},
  1489  			expectedPVs:     []*v1.PersistentVolume{pvNode1aBound, pvNode1b},
  1490  			expectedAPIPVs:  []*v1.PersistentVolume{pvNode1aBound, pvNode1bBoundHigherVersion},
  1491  			provisionedPVCs: []*v1.PersistentVolumeClaim{},
  1492  			shouldFail:      true,
  1493  		},
  1494  		"one-provisioned-pvc": {
  1495  			bindings:        []*BindingInfo{},
  1496  			provisionedPVCs: []*v1.PersistentVolumeClaim{addProvisionAnn(provisionedPVC)},
  1497  			cachedPVCs:      []*v1.PersistentVolumeClaim{provisionedPVC},
  1498  			expectedPVCs:    []*v1.PersistentVolumeClaim{addProvisionAnn(provisionedPVC)},
  1499  		},
  1500  		"provision-api-update-failed": {
  1501  			bindings:        []*BindingInfo{},
  1502  			provisionedPVCs: []*v1.PersistentVolumeClaim{addProvisionAnn(provisionedPVC), addProvisionAnn(provisionedPVC2)},
  1503  			cachedPVCs:      []*v1.PersistentVolumeClaim{provisionedPVC, provisionedPVC2},
  1504  			apiPVCs:         []*v1.PersistentVolumeClaim{provisionedPVC, provisionedPVCHigherVersion},
  1505  			expectedPVCs:    []*v1.PersistentVolumeClaim{addProvisionAnn(provisionedPVC), provisionedPVC2},
  1506  			expectedAPIPVCs: []*v1.PersistentVolumeClaim{addProvisionAnn(provisionedPVC), provisionedPVCHigherVersion},
  1507  			shouldFail:      true,
  1508  		},
  1509  		"binding-succeed, provision-api-update-failed": {
  1510  			bindings:        []*BindingInfo{makeBinding(unboundPVC, pvNode1aBound)},
  1511  			cachedPVs:       []*v1.PersistentVolume{pvNode1a},
  1512  			expectedPVs:     []*v1.PersistentVolume{pvNode1aBound},
  1513  			provisionedPVCs: []*v1.PersistentVolumeClaim{addProvisionAnn(provisionedPVC), addProvisionAnn(provisionedPVC2)},
  1514  			cachedPVCs:      []*v1.PersistentVolumeClaim{provisionedPVC, provisionedPVC2},
  1515  			apiPVCs:         []*v1.PersistentVolumeClaim{provisionedPVC, provisionedPVCHigherVersion},
  1516  			expectedPVCs:    []*v1.PersistentVolumeClaim{addProvisionAnn(provisionedPVC), provisionedPVC2},
  1517  			expectedAPIPVCs: []*v1.PersistentVolumeClaim{addProvisionAnn(provisionedPVC), provisionedPVCHigherVersion},
  1518  			shouldFail:      true,
  1519  		},
  1520  	}
  1521  
  1522  	run := func(t *testing.T, scenario scenarioType) {
  1523  		_, ctx := ktesting.NewTestContext(t)
  1524  		ctx, cancel := context.WithCancel(ctx)
  1525  		defer cancel()
  1526  
  1527  		// Setup
  1528  		testEnv := newTestBinder(t, ctx)
  1529  		pod := makePod("test-pod").
  1530  			withNamespace("testns").
  1531  			withNodeName("node1").Pod
  1532  		if scenario.apiPVs == nil {
  1533  			scenario.apiPVs = scenario.cachedPVs
  1534  		}
  1535  		if scenario.apiPVCs == nil {
  1536  			scenario.apiPVCs = scenario.cachedPVCs
  1537  		}
  1538  		testEnv.initVolumes(scenario.cachedPVs, scenario.apiPVs)
  1539  		testEnv.initClaims(scenario.cachedPVCs, scenario.apiPVCs)
  1540  		testEnv.assumeVolumes(t, "node1", pod, scenario.bindings, scenario.provisionedPVCs)
  1541  
  1542  		// Execute
  1543  		err := testEnv.internalBinder.bindAPIUpdate(ctx, pod, scenario.bindings, scenario.provisionedPVCs)
  1544  
  1545  		// Validate
  1546  		if !scenario.shouldFail && err != nil {
  1547  			t.Errorf("returned error: %v", err)
  1548  		}
  1549  		if scenario.shouldFail && err == nil {
  1550  			t.Error("returned success but expected error")
  1551  		}
  1552  		if scenario.expectedAPIPVs == nil {
  1553  			scenario.expectedAPIPVs = scenario.expectedPVs
  1554  		}
  1555  		if scenario.expectedAPIPVCs == nil {
  1556  			scenario.expectedAPIPVCs = scenario.expectedPVCs
  1557  		}
  1558  		testEnv.validateBind(t, pod, scenario.expectedPVs, scenario.expectedAPIPVs)
  1559  		testEnv.validateProvision(t, pod, scenario.expectedPVCs, scenario.expectedAPIPVCs)
  1560  	}
  1561  
  1562  	for name, scenario := range scenarios {
  1563  		t.Run(name, func(t *testing.T) { run(t, scenario) })
  1564  	}
  1565  }
  1566  
  1567  func TestCheckBindings(t *testing.T) {
  1568  	t.Parallel()
  1569  
  1570  	type scenarioType struct {
  1571  		// Inputs
  1572  		initPVs  []*v1.PersistentVolume
  1573  		initPVCs []*v1.PersistentVolumeClaim
  1574  
  1575  		bindings        []*BindingInfo
  1576  		provisionedPVCs []*v1.PersistentVolumeClaim
  1577  
  1578  		// api updates before checking
  1579  		apiPVs  []*v1.PersistentVolume
  1580  		apiPVCs []*v1.PersistentVolumeClaim
  1581  
  1582  		// delete objects before checking
  1583  		deletePVs  bool
  1584  		deletePVCs bool
  1585  
  1586  		// Expected return values
  1587  		shouldFail    bool
  1588  		expectedBound bool
  1589  	}
  1590  	scenarios := map[string]scenarioType{
  1591  		"nothing-to-bind-nil": {
  1592  			shouldFail: true,
  1593  		},
  1594  		"nothing-to-bind-bindings-nil": {
  1595  			provisionedPVCs: []*v1.PersistentVolumeClaim{},
  1596  			shouldFail:      true,
  1597  		},
  1598  		"nothing-to-bind-provisionings-nil": {
  1599  			bindings:   []*BindingInfo{},
  1600  			shouldFail: true,
  1601  		},
  1602  		"nothing-to-bind": {
  1603  			bindings:        []*BindingInfo{},
  1604  			provisionedPVCs: []*v1.PersistentVolumeClaim{},
  1605  			expectedBound:   true,
  1606  		},
  1607  		"binding-bound": {
  1608  			bindings:        []*BindingInfo{makeBinding(unboundPVC, pvNode1aBound)},
  1609  			provisionedPVCs: []*v1.PersistentVolumeClaim{},
  1610  			initPVs:         []*v1.PersistentVolume{pvNode1aBound},
  1611  			initPVCs:        []*v1.PersistentVolumeClaim{boundPVCNode1a},
  1612  			expectedBound:   true,
  1613  		},
  1614  		"binding-prebound": {
  1615  			bindings:        []*BindingInfo{makeBinding(unboundPVC, pvNode1aBound)},
  1616  			provisionedPVCs: []*v1.PersistentVolumeClaim{},
  1617  			initPVs:         []*v1.PersistentVolume{pvNode1aBound},
  1618  			initPVCs:        []*v1.PersistentVolumeClaim{preboundPVCNode1a},
  1619  		},
  1620  		"binding-unbound": {
  1621  			bindings:        []*BindingInfo{makeBinding(unboundPVC, pvNode1aBound)},
  1622  			provisionedPVCs: []*v1.PersistentVolumeClaim{},
  1623  			initPVs:         []*v1.PersistentVolume{pvNode1aBound},
  1624  			initPVCs:        []*v1.PersistentVolumeClaim{unboundPVC},
  1625  		},
  1626  		"binding-pvc-not-exists": {
  1627  			bindings:        []*BindingInfo{makeBinding(unboundPVC, pvNode1aBound)},
  1628  			provisionedPVCs: []*v1.PersistentVolumeClaim{},
  1629  			initPVs:         []*v1.PersistentVolume{pvNode1aBound},
  1630  			shouldFail:      true,
  1631  		},
  1632  		"binding-pv-not-exists": {
  1633  			bindings:        []*BindingInfo{makeBinding(unboundPVC, pvNode1aBound)},
  1634  			provisionedPVCs: []*v1.PersistentVolumeClaim{},
  1635  			initPVs:         []*v1.PersistentVolume{pvNode1aBound},
  1636  			initPVCs:        []*v1.PersistentVolumeClaim{boundPVCNode1a},
  1637  			deletePVs:       true,
  1638  			shouldFail:      true,
  1639  		},
  1640  		"binding-claimref-nil": {
  1641  			bindings:        []*BindingInfo{makeBinding(unboundPVC, pvNode1aBound)},
  1642  			provisionedPVCs: []*v1.PersistentVolumeClaim{},
  1643  			initPVs:         []*v1.PersistentVolume{pvNode1a},
  1644  			initPVCs:        []*v1.PersistentVolumeClaim{boundPVCNode1a},
  1645  			apiPVs:          []*v1.PersistentVolume{pvNode1a},
  1646  			apiPVCs:         []*v1.PersistentVolumeClaim{boundPVCNode1a},
  1647  			shouldFail:      true,
  1648  		},
  1649  		"binding-claimref-uid-empty": {
  1650  			bindings:        []*BindingInfo{makeBinding(unboundPVC, pvNode1aBound)},
  1651  			provisionedPVCs: []*v1.PersistentVolumeClaim{},
  1652  			initPVs:         []*v1.PersistentVolume{pvNode1aBound},
  1653  			initPVCs:        []*v1.PersistentVolumeClaim{boundPVCNode1a},
  1654  			apiPVs:          []*v1.PersistentVolume{pvRemoveClaimUID(pvNode1aBound)},
  1655  			apiPVCs:         []*v1.PersistentVolumeClaim{boundPVCNode1a},
  1656  			shouldFail:      true,
  1657  		},
  1658  		"binding-one-bound,one-unbound": {
  1659  			bindings:        []*BindingInfo{makeBinding(unboundPVC, pvNode1aBound), makeBinding(unboundPVC2, pvNode1bBound)},
  1660  			provisionedPVCs: []*v1.PersistentVolumeClaim{},
  1661  			initPVs:         []*v1.PersistentVolume{pvNode1aBound, pvNode1bBound},
  1662  			initPVCs:        []*v1.PersistentVolumeClaim{boundPVCNode1a, unboundPVC2},
  1663  		},
  1664  		"provisioning-pvc-bound": {
  1665  			bindings:        []*BindingInfo{},
  1666  			provisionedPVCs: []*v1.PersistentVolumeClaim{addProvisionAnn(provisionedPVC)},
  1667  			initPVs:         []*v1.PersistentVolume{pvBound},
  1668  			initPVCs:        []*v1.PersistentVolumeClaim{provisionedPVCBound},
  1669  			apiPVCs:         []*v1.PersistentVolumeClaim{addProvisionAnn(provisionedPVCBound)},
  1670  			expectedBound:   true,
  1671  		},
  1672  		"provisioning-pvc-unbound": {
  1673  			bindings:        []*BindingInfo{},
  1674  			provisionedPVCs: []*v1.PersistentVolumeClaim{addProvisionAnn(provisionedPVC)},
  1675  			initPVCs:        []*v1.PersistentVolumeClaim{addProvisionAnn(provisionedPVC)},
  1676  		},
  1677  		"provisioning-pvc-not-exists": {
  1678  			bindings:        []*BindingInfo{},
  1679  			provisionedPVCs: []*v1.PersistentVolumeClaim{addProvisionAnn(provisionedPVC)},
  1680  			initPVCs:        []*v1.PersistentVolumeClaim{provisionedPVC},
  1681  			deletePVCs:      true,
  1682  			shouldFail:      true,
  1683  		},
  1684  		"provisioning-pvc-annotations-nil": {
  1685  			bindings:        []*BindingInfo{},
  1686  			provisionedPVCs: []*v1.PersistentVolumeClaim{addProvisionAnn(provisionedPVC)},
  1687  			initPVCs:        []*v1.PersistentVolumeClaim{provisionedPVC},
  1688  			apiPVCs:         []*v1.PersistentVolumeClaim{provisionedPVC},
  1689  			shouldFail:      true,
  1690  		},
  1691  		"provisioning-pvc-selected-node-dropped": {
  1692  			bindings:        []*BindingInfo{},
  1693  			provisionedPVCs: []*v1.PersistentVolumeClaim{addProvisionAnn(provisionedPVC)},
  1694  			initPVCs:        []*v1.PersistentVolumeClaim{provisionedPVC},
  1695  			apiPVCs:         []*v1.PersistentVolumeClaim{pvcSetEmptyAnnotations(provisionedPVC)},
  1696  			shouldFail:      true,
  1697  		},
  1698  		"provisioning-pvc-selected-node-wrong-node": {
  1699  			initPVCs:        []*v1.PersistentVolumeClaim{provisionedPVC},
  1700  			bindings:        []*BindingInfo{},
  1701  			provisionedPVCs: []*v1.PersistentVolumeClaim{addProvisionAnn(provisionedPVC)},
  1702  			apiPVCs:         []*v1.PersistentVolumeClaim{pvcSetSelectedNode(provisionedPVC, "wrong-node")},
  1703  			shouldFail:      true,
  1704  		},
  1705  		"binding-bound-provisioning-unbound": {
  1706  			bindings:        []*BindingInfo{makeBinding(unboundPVC, pvNode1aBound)},
  1707  			provisionedPVCs: []*v1.PersistentVolumeClaim{addProvisionAnn(provisionedPVC)},
  1708  			initPVs:         []*v1.PersistentVolume{pvNode1aBound},
  1709  			initPVCs:        []*v1.PersistentVolumeClaim{boundPVCNode1a, addProvisionAnn(provisionedPVC)},
  1710  		},
  1711  		"tolerate-provisioning-pvc-bound-pv-not-found": {
  1712  			initPVs:         []*v1.PersistentVolume{pvNode1a},
  1713  			initPVCs:        []*v1.PersistentVolumeClaim{provisionedPVC},
  1714  			bindings:        []*BindingInfo{},
  1715  			provisionedPVCs: []*v1.PersistentVolumeClaim{addProvisionAnn(provisionedPVC)},
  1716  			apiPVCs:         []*v1.PersistentVolumeClaim{addProvisionAnn(provisionedPVCBound)},
  1717  			deletePVs:       true,
  1718  		},
  1719  	}
  1720  
  1721  	run := func(t *testing.T, scenario scenarioType) {
  1722  		logger, ctx := ktesting.NewTestContext(t)
  1723  		ctx, cancel := context.WithCancel(ctx)
  1724  		defer cancel()
  1725  		// Setup
  1726  		pod := makePod("test-pod").
  1727  			withNamespace("testns").
  1728  			withNodeName("node1").Pod
  1729  		testEnv := newTestBinder(t, ctx)
  1730  		testEnv.internalPodInformer.Informer().GetIndexer().Add(pod)
  1731  		testEnv.initNodes([]*v1.Node{node1})
  1732  		testEnv.initVolumes(scenario.initPVs, nil)
  1733  		testEnv.initClaims(scenario.initPVCs, nil)
  1734  		testEnv.assumeVolumes(t, "node1", pod, scenario.bindings, scenario.provisionedPVCs)
  1735  
  1736  		// Before execute
  1737  		if scenario.deletePVs {
  1738  			testEnv.deleteVolumes(scenario.initPVs)
  1739  		} else {
  1740  			if err := testEnv.updateVolumes(ctx, scenario.apiPVs); err != nil {
  1741  				t.Errorf("Failed to update PVs: %v", err)
  1742  			}
  1743  		}
  1744  		if scenario.deletePVCs {
  1745  			testEnv.deleteClaims(scenario.initPVCs)
  1746  		} else {
  1747  			if err := testEnv.updateClaims(ctx, scenario.apiPVCs); err != nil {
  1748  				t.Errorf("Failed to update PVCs: %v", err)
  1749  			}
  1750  		}
  1751  
  1752  		// Execute
  1753  		allBound, err := testEnv.internalBinder.checkBindings(logger, pod, scenario.bindings, scenario.provisionedPVCs)
  1754  
  1755  		// Validate
  1756  		if !scenario.shouldFail && err != nil {
  1757  			t.Errorf("returned error: %v", err)
  1758  		}
  1759  		if scenario.shouldFail && err == nil {
  1760  			t.Error("returned success but expected error")
  1761  		}
  1762  		if scenario.expectedBound != allBound {
  1763  			t.Errorf("returned bound %v", allBound)
  1764  		}
  1765  	}
  1766  
  1767  	for name, scenario := range scenarios {
  1768  		t.Run(name, func(t *testing.T) { run(t, scenario) })
  1769  	}
  1770  }
  1771  
  1772  func TestCheckBindingsWithCSIMigration(t *testing.T) {
  1773  	t.Parallel()
  1774  
  1775  	type scenarioType struct {
  1776  		// Inputs
  1777  		initPVs      []*v1.PersistentVolume
  1778  		initPVCs     []*v1.PersistentVolumeClaim
  1779  		initNodes    []*v1.Node
  1780  		initCSINodes []*storagev1.CSINode
  1781  
  1782  		bindings        []*BindingInfo
  1783  		provisionedPVCs []*v1.PersistentVolumeClaim
  1784  
  1785  		// API updates before checking
  1786  		apiPVs  []*v1.PersistentVolume
  1787  		apiPVCs []*v1.PersistentVolumeClaim
  1788  
  1789  		// Expected return values
  1790  		shouldFail    bool
  1791  		expectedBound bool
  1792  	}
  1793  	scenarios := map[string]scenarioType{
  1794  		"provisioning-pvc-bound": {
  1795  			bindings:        []*BindingInfo{},
  1796  			provisionedPVCs: []*v1.PersistentVolumeClaim{addProvisionAnn(provMigrationPVCBound)},
  1797  			initPVs:         []*v1.PersistentVolume{migrationPVBound},
  1798  			initPVCs:        []*v1.PersistentVolumeClaim{provMigrationPVCBound},
  1799  			initNodes:       []*v1.Node{node1Zone1},
  1800  			initCSINodes:    []*storagev1.CSINode{csiNode1Migrated},
  1801  			apiPVCs:         []*v1.PersistentVolumeClaim{addProvisionAnn(provMigrationPVCBound)},
  1802  			expectedBound:   true,
  1803  		},
  1804  		"binding-node-pv-same-zone": {
  1805  			bindings:        []*BindingInfo{makeBinding(unboundPVC, migrationPVBoundToUnbound)},
  1806  			provisionedPVCs: []*v1.PersistentVolumeClaim{},
  1807  			initPVs:         []*v1.PersistentVolume{migrationPVBoundToUnbound},
  1808  			initPVCs:        []*v1.PersistentVolumeClaim{unboundPVC},
  1809  			initNodes:       []*v1.Node{node1Zone1},
  1810  			initCSINodes:    []*storagev1.CSINode{csiNode1Migrated},
  1811  		},
  1812  		"binding-without-csinode": {
  1813  			bindings:        []*BindingInfo{makeBinding(unboundPVC, migrationPVBoundToUnbound)},
  1814  			provisionedPVCs: []*v1.PersistentVolumeClaim{},
  1815  			initPVs:         []*v1.PersistentVolume{migrationPVBoundToUnbound},
  1816  			initPVCs:        []*v1.PersistentVolumeClaim{unboundPVC},
  1817  			initNodes:       []*v1.Node{node1Zone1},
  1818  			initCSINodes:    []*storagev1.CSINode{},
  1819  		},
  1820  		"binding-non-migrated-plugin": {
  1821  			bindings:        []*BindingInfo{makeBinding(unboundPVC, migrationPVBoundToUnbound)},
  1822  			provisionedPVCs: []*v1.PersistentVolumeClaim{},
  1823  			initPVs:         []*v1.PersistentVolume{migrationPVBoundToUnbound},
  1824  			initPVCs:        []*v1.PersistentVolumeClaim{unboundPVC},
  1825  			initNodes:       []*v1.Node{node1Zone1},
  1826  			initCSINodes:    []*storagev1.CSINode{csiNode1NotMigrated},
  1827  		},
  1828  		"binding-node-pv-in-different-zones": {
  1829  			bindings:        []*BindingInfo{makeBinding(unboundPVC, migrationPVBoundToUnbound)},
  1830  			provisionedPVCs: []*v1.PersistentVolumeClaim{},
  1831  			initPVs:         []*v1.PersistentVolume{migrationPVBoundToUnbound},
  1832  			initPVCs:        []*v1.PersistentVolumeClaim{unboundPVC},
  1833  			initNodes:       []*v1.Node{node1Zone2},
  1834  			initCSINodes:    []*storagev1.CSINode{csiNode1Migrated},
  1835  			shouldFail:      true,
  1836  		},
  1837  		"binding-node-pv-different-zones-migration-off": {
  1838  			bindings:        []*BindingInfo{makeBinding(unboundPVC, nonmigrationPVBoundToUnbound)},
  1839  			provisionedPVCs: []*v1.PersistentVolumeClaim{},
  1840  			initPVs:         []*v1.PersistentVolume{nonmigrationPVBoundToUnbound},
  1841  			initPVCs:        []*v1.PersistentVolumeClaim{unboundPVC},
  1842  			initNodes:       []*v1.Node{node1Zone2},
  1843  			initCSINodes:    []*storagev1.CSINode{csiNode1Migrated},
  1844  		},
  1845  	}
  1846  
  1847  	run := func(t *testing.T, scenario scenarioType) {
  1848  		logger, ctx := ktesting.NewTestContext(t)
  1849  		ctx, cancel := context.WithCancel(ctx)
  1850  		defer cancel()
  1851  
  1852  		// Setup
  1853  		pod := makePod("test-pod").
  1854  			withNamespace("testns").
  1855  			withNodeName("node1").Pod
  1856  		testEnv := newTestBinder(t, ctx)
  1857  		testEnv.internalPodInformer.Informer().GetIndexer().Add(pod)
  1858  		testEnv.initNodes(scenario.initNodes)
  1859  		testEnv.initCSINodes(scenario.initCSINodes)
  1860  		testEnv.initVolumes(scenario.initPVs, nil)
  1861  		testEnv.initClaims(scenario.initPVCs, nil)
  1862  		testEnv.assumeVolumes(t, "node1", pod, scenario.bindings, scenario.provisionedPVCs)
  1863  
  1864  		// Before execute
  1865  		if err := testEnv.updateVolumes(ctx, scenario.apiPVs); err != nil {
  1866  			t.Errorf("Failed to update PVs: %v", err)
  1867  		}
  1868  		if err := testEnv.updateClaims(ctx, scenario.apiPVCs); err != nil {
  1869  			t.Errorf("Failed to update PVCs: %v", err)
  1870  		}
  1871  
  1872  		// Execute
  1873  		allBound, err := testEnv.internalBinder.checkBindings(logger, pod, scenario.bindings, scenario.provisionedPVCs)
  1874  
  1875  		// Validate
  1876  		if !scenario.shouldFail && err != nil {
  1877  			t.Errorf("returned error: %v", err)
  1878  		}
  1879  		if scenario.shouldFail && err == nil {
  1880  			t.Error("returned success but expected error")
  1881  		}
  1882  		if scenario.expectedBound != allBound {
  1883  			t.Errorf("returned bound %v", allBound)
  1884  		}
  1885  	}
  1886  
  1887  	for name, scenario := range scenarios {
  1888  		t.Run(name, func(t *testing.T) { run(t, scenario) })
  1889  	}
  1890  }
  1891  
  1892  func TestBindPodVolumes(t *testing.T) {
  1893  	t.Parallel()
  1894  
  1895  	type scenarioType struct {
  1896  		// Inputs
  1897  		bindingsNil bool // Pass in nil bindings slice
  1898  
  1899  		nodes []*v1.Node
  1900  
  1901  		// before assume
  1902  		initPVs  []*v1.PersistentVolume
  1903  		initPVCs []*v1.PersistentVolumeClaim
  1904  
  1905  		// assume PV & PVC with these binding results
  1906  		binding          *BindingInfo
  1907  		claimToProvision *v1.PersistentVolumeClaim
  1908  
  1909  		// API updates after assume before bind
  1910  		apiPV  *v1.PersistentVolume
  1911  		apiPVC *v1.PersistentVolumeClaim
  1912  
  1913  		// This function runs with a delay of 5 seconds
  1914  		delayFunc func(t *testing.T, ctx context.Context, testEnv *testEnv, pod *v1.Pod, pvs []*v1.PersistentVolume, pvcs []*v1.PersistentVolumeClaim)
  1915  
  1916  		// Expected return values
  1917  		shouldFail bool
  1918  	}
  1919  	scenarios := map[string]scenarioType{
  1920  		"nothing-to-bind-nil": {
  1921  			bindingsNil: true,
  1922  			shouldFail:  true,
  1923  		},
  1924  		"nothing-to-bind-empty": {},
  1925  		"already-bound": {
  1926  			binding:  makeBinding(unboundPVC, pvNode1aBound),
  1927  			initPVs:  []*v1.PersistentVolume{pvNode1aBound},
  1928  			initPVCs: []*v1.PersistentVolumeClaim{boundPVCNode1a},
  1929  		},
  1930  		"binding-static-pv-succeeds-after-time": {
  1931  			initPVs:    []*v1.PersistentVolume{pvNode1a},
  1932  			initPVCs:   []*v1.PersistentVolumeClaim{unboundPVC},
  1933  			binding:    makeBinding(unboundPVC, pvNode1aBound),
  1934  			shouldFail: false, // Will succeed after PVC is fully bound to this PV by pv controller.
  1935  			delayFunc: func(t *testing.T, ctx context.Context, testEnv *testEnv, pod *v1.Pod, pvs []*v1.PersistentVolume, pvcs []*v1.PersistentVolumeClaim) {
  1936  				pvc := pvcs[0]
  1937  				pv := pvs[0]
  1938  				// Update PVC to be fully bound to PV
  1939  				newPVC := pvc.DeepCopy()
  1940  				newPVC.Spec.VolumeName = pv.Name
  1941  				metav1.SetMetaDataAnnotation(&newPVC.ObjectMeta, volume.AnnBindCompleted, "yes")
  1942  				if _, err := testEnv.client.CoreV1().PersistentVolumeClaims(newPVC.Namespace).Update(ctx, newPVC, metav1.UpdateOptions{}); err != nil {
  1943  					t.Errorf("failed to update PVC %q: %v", newPVC.Name, err)
  1944  				}
  1945  			},
  1946  		},
  1947  		"binding-dynamic-pv-succeeds-after-time": {
  1948  			claimToProvision: pvcSetSelectedNode(provisionedPVC, "node1"),
  1949  			initPVCs:         []*v1.PersistentVolumeClaim{provisionedPVC},
  1950  			delayFunc: func(t *testing.T, ctx context.Context, testEnv *testEnv, pod *v1.Pod, pvs []*v1.PersistentVolume, pvcs []*v1.PersistentVolumeClaim) {
  1951  				pvc := pvcs[0]
  1952  				// Update PVC to be fully bound to PV
  1953  				newPVC, err := testEnv.client.CoreV1().PersistentVolumeClaims(pvc.Namespace).Get(ctx, pvc.Name, metav1.GetOptions{})
  1954  				if err != nil {
  1955  					t.Errorf("failed to get PVC %q: %v", pvc.Name, err)
  1956  					return
  1957  				}
  1958  				dynamicPV := makeTestPV("dynamic-pv", "node1", "1G", "1", newPVC, waitClass)
  1959  				dynamicPV, err = testEnv.client.CoreV1().PersistentVolumes().Create(ctx, dynamicPV, metav1.CreateOptions{})
  1960  				if err != nil {
  1961  					t.Errorf("failed to create PV %q: %v", dynamicPV.Name, err)
  1962  					return
  1963  				}
  1964  				newPVC.Spec.VolumeName = dynamicPV.Name
  1965  				metav1.SetMetaDataAnnotation(&newPVC.ObjectMeta, volume.AnnBindCompleted, "yes")
  1966  				if _, err := testEnv.client.CoreV1().PersistentVolumeClaims(newPVC.Namespace).Update(ctx, newPVC, metav1.UpdateOptions{}); err != nil {
  1967  					t.Errorf("failed to update PVC %q: %v", newPVC.Name, err)
  1968  				}
  1969  			},
  1970  		},
  1971  		"bound-by-pv-controller-before-bind": {
  1972  			initPVs:    []*v1.PersistentVolume{pvNode1a},
  1973  			initPVCs:   []*v1.PersistentVolumeClaim{unboundPVC},
  1974  			binding:    makeBinding(unboundPVC, pvNode1aBound),
  1975  			apiPV:      pvNode1aBound,
  1976  			apiPVC:     boundPVCNode1a,
  1977  			shouldFail: true, // bindAPIUpdate will fail because API conflict
  1978  		},
  1979  		"pod-deleted-after-time": {
  1980  			binding:  makeBinding(unboundPVC, pvNode1aBound),
  1981  			initPVs:  []*v1.PersistentVolume{pvNode1a},
  1982  			initPVCs: []*v1.PersistentVolumeClaim{unboundPVC},
  1983  			delayFunc: func(t *testing.T, ctx context.Context, testEnv *testEnv, pod *v1.Pod, pvs []*v1.PersistentVolume, pvcs []*v1.PersistentVolumeClaim) {
  1984  				testEnv.client.CoreV1().Pods(pod.Namespace).Delete(ctx, pod.Name, metav1.DeleteOptions{})
  1985  			},
  1986  			shouldFail: true,
  1987  		},
  1988  		"binding-times-out": {
  1989  			binding:    makeBinding(unboundPVC, pvNode1aBound),
  1990  			initPVs:    []*v1.PersistentVolume{pvNode1a},
  1991  			initPVCs:   []*v1.PersistentVolumeClaim{unboundPVC},
  1992  			shouldFail: true,
  1993  		},
  1994  		"binding-fails": {
  1995  			binding:    makeBinding(unboundPVC2, pvNode1bBound),
  1996  			initPVs:    []*v1.PersistentVolume{pvNode1b},
  1997  			initPVCs:   []*v1.PersistentVolumeClaim{unboundPVC2},
  1998  			shouldFail: true,
  1999  		},
  2000  		"check-fails": {
  2001  			binding:  makeBinding(unboundPVC, pvNode1aBound),
  2002  			initPVs:  []*v1.PersistentVolume{pvNode1a},
  2003  			initPVCs: []*v1.PersistentVolumeClaim{unboundPVC},
  2004  			delayFunc: func(t *testing.T, ctx context.Context, testEnv *testEnv, pod *v1.Pod, pvs []*v1.PersistentVolume, pvcs []*v1.PersistentVolumeClaim) {
  2005  				pvc := pvcs[0]
  2006  				// Delete PVC will fail check
  2007  				if err := testEnv.client.CoreV1().PersistentVolumeClaims(pvc.Namespace).Delete(ctx, pvc.Name, metav1.DeleteOptions{}); err != nil {
  2008  					t.Errorf("failed to delete PVC %q: %v", pvc.Name, err)
  2009  				}
  2010  			},
  2011  			shouldFail: true,
  2012  		},
  2013  		"node-affinity-fails": {
  2014  			binding:    makeBinding(unboundPVC, pvNode1aBound),
  2015  			initPVs:    []*v1.PersistentVolume{pvNode1aBound},
  2016  			initPVCs:   []*v1.PersistentVolumeClaim{boundPVCNode1a},
  2017  			nodes:      []*v1.Node{node1NoLabels},
  2018  			shouldFail: true,
  2019  		},
  2020  		"node-affinity-fails-dynamic-provisioning": {
  2021  			initPVs:          []*v1.PersistentVolume{pvNode1a, pvNode2},
  2022  			initPVCs:         []*v1.PersistentVolumeClaim{selectedNodePVC},
  2023  			claimToProvision: selectedNodePVC,
  2024  			nodes:            []*v1.Node{node1, node2},
  2025  			delayFunc: func(t *testing.T, ctx context.Context, testEnv *testEnv, pod *v1.Pod, pvs []*v1.PersistentVolume, pvcs []*v1.PersistentVolumeClaim) {
  2026  				// Update PVC to be fully bound to a PV with a different node
  2027  				newPVC := pvcs[0].DeepCopy()
  2028  				newPVC.Spec.VolumeName = pvNode2.Name
  2029  				metav1.SetMetaDataAnnotation(&newPVC.ObjectMeta, volume.AnnBindCompleted, "yes")
  2030  				if _, err := testEnv.client.CoreV1().PersistentVolumeClaims(newPVC.Namespace).Update(ctx, newPVC, metav1.UpdateOptions{}); err != nil {
  2031  					t.Errorf("failed to update PVC %q: %v", newPVC.Name, err)
  2032  				}
  2033  			},
  2034  			shouldFail: true,
  2035  		},
  2036  	}
  2037  
  2038  	run := func(t *testing.T, scenario scenarioType) {
  2039  		logger, ctx := ktesting.NewTestContext(t)
  2040  		ctx, cancel := context.WithCancel(ctx)
  2041  		defer cancel()
  2042  		// Setup
  2043  		pod := makePod("test-pod").
  2044  			withNamespace("testns").
  2045  			withNodeName("node1").Pod
  2046  		testEnv := newTestBinder(t, ctx)
  2047  		testEnv.internalPodInformer.Informer().GetIndexer().Add(pod)
  2048  		if scenario.nodes == nil {
  2049  			scenario.nodes = []*v1.Node{node1}
  2050  		}
  2051  		bindings := []*BindingInfo{}
  2052  		claimsToProvision := []*v1.PersistentVolumeClaim{}
  2053  		if !scenario.bindingsNil {
  2054  			if scenario.binding != nil {
  2055  				bindings = []*BindingInfo{scenario.binding}
  2056  			}
  2057  			if scenario.claimToProvision != nil {
  2058  				claimsToProvision = []*v1.PersistentVolumeClaim{scenario.claimToProvision}
  2059  			}
  2060  			testEnv.initNodes(scenario.nodes)
  2061  			testEnv.initVolumes(scenario.initPVs, scenario.initPVs)
  2062  			testEnv.initClaims(scenario.initPVCs, scenario.initPVCs)
  2063  			testEnv.assumeVolumes(t, "node1", pod, bindings, claimsToProvision)
  2064  		}
  2065  
  2066  		// Before Execute
  2067  		if scenario.apiPV != nil {
  2068  			_, err := testEnv.client.CoreV1().PersistentVolumes().Update(ctx, scenario.apiPV, metav1.UpdateOptions{})
  2069  			if err != nil {
  2070  				t.Fatalf("failed to update PV %q", scenario.apiPV.Name)
  2071  			}
  2072  		}
  2073  		if scenario.apiPVC != nil {
  2074  			_, err := testEnv.client.CoreV1().PersistentVolumeClaims(scenario.apiPVC.Namespace).Update(ctx, scenario.apiPVC, metav1.UpdateOptions{})
  2075  			if err != nil {
  2076  				t.Fatalf("failed to update PVC %q", getPVCName(scenario.apiPVC))
  2077  			}
  2078  		}
  2079  
  2080  		if scenario.delayFunc != nil {
  2081  			go func(scenario scenarioType) {
  2082  				time.Sleep(5 * time.Second)
  2083  				// Sleep a while to run after bindAPIUpdate in BindPodVolumes
  2084  				logger.V(5).Info("Running delay function")
  2085  				scenario.delayFunc(t, ctx, testEnv, pod, scenario.initPVs, scenario.initPVCs)
  2086  			}(scenario)
  2087  		}
  2088  
  2089  		// Execute
  2090  		podVolumes := &PodVolumes{
  2091  			StaticBindings:    bindings,
  2092  			DynamicProvisions: claimsToProvision,
  2093  		}
  2094  		err := testEnv.binder.BindPodVolumes(ctx, pod, podVolumes)
  2095  
  2096  		// Validate
  2097  		if !scenario.shouldFail && err != nil {
  2098  			t.Errorf("returned error: %v", err)
  2099  		}
  2100  		if scenario.shouldFail && err == nil {
  2101  			t.Error("returned success but expected error")
  2102  		}
  2103  	}
  2104  
  2105  	for name, scenario := range scenarios {
  2106  		scenario := scenario
  2107  		t.Run(name, func(t *testing.T) {
  2108  			t.Parallel()
  2109  			run(t, scenario)
  2110  		})
  2111  	}
  2112  }
  2113  
  2114  func TestFindAssumeVolumes(t *testing.T) {
  2115  	// Test case
  2116  	podPVCs := []*v1.PersistentVolumeClaim{unboundPVC}
  2117  	pvs := []*v1.PersistentVolume{pvNode2, pvNode1a, pvNode1c}
  2118  
  2119  	// Setup
  2120  	logger, ctx := ktesting.NewTestContext(t)
  2121  	ctx, cancel := context.WithCancel(ctx)
  2122  	defer cancel()
  2123  	testEnv := newTestBinder(t, ctx)
  2124  	testEnv.initVolumes(pvs, pvs)
  2125  	testEnv.initClaims(podPVCs, podPVCs)
  2126  	pod := makePod("test-pod").
  2127  		withNamespace("testns").
  2128  		withNodeName("node1").
  2129  		withPVCSVolume(podPVCs).Pod
  2130  
  2131  	testNode := &v1.Node{
  2132  		ObjectMeta: metav1.ObjectMeta{
  2133  			Name: "node1",
  2134  			Labels: map[string]string{
  2135  				nodeLabelKey: "node1",
  2136  			},
  2137  		},
  2138  	}
  2139  
  2140  	// Execute
  2141  	// 1. Find matching PVs
  2142  	podVolumes, reasons, err := findPodVolumes(logger, testEnv.binder, pod, testNode)
  2143  	if err != nil {
  2144  		t.Errorf("Test failed: FindPodVolumes returned error: %v", err)
  2145  	}
  2146  	if len(reasons) > 0 {
  2147  		t.Errorf("Test failed: couldn't find PVs for all PVCs: %v", reasons)
  2148  	}
  2149  	expectedBindings := podVolumes.StaticBindings
  2150  
  2151  	// 2. Assume matches
  2152  	allBound, err := testEnv.binder.AssumePodVolumes(logger, pod, testNode.Name, podVolumes)
  2153  	if err != nil {
  2154  		t.Errorf("Test failed: AssumePodVolumes returned error: %v", err)
  2155  	}
  2156  	if allBound {
  2157  		t.Errorf("Test failed: detected unbound volumes as bound")
  2158  	}
  2159  	testEnv.validateAssume(t, pod, expectedBindings, nil)
  2160  
  2161  	// After assume, claimref should be set on pv
  2162  	expectedBindings = podVolumes.StaticBindings
  2163  
  2164  	// 3. Find matching PVs again
  2165  	// This should always return the original chosen pv
  2166  	// Run this many times in case sorting returns different orders for the two PVs.
  2167  	for i := 0; i < 50; i++ {
  2168  		podVolumes, reasons, err := findPodVolumes(logger, testEnv.binder, pod, testNode)
  2169  		if err != nil {
  2170  			t.Errorf("Test failed: FindPodVolumes returned error: %v", err)
  2171  		}
  2172  		if len(reasons) > 0 {
  2173  			t.Errorf("Test failed: couldn't find PVs for all PVCs: %v", reasons)
  2174  		}
  2175  		testEnv.validatePodCache(t, testNode.Name, pod, podVolumes, expectedBindings, nil)
  2176  	}
  2177  }
  2178  
  2179  // TestCapacity covers different scenarios involving CSIStorageCapacity objects.
  2180  // Scenarios without those are covered by TestFindPodVolumesWithProvisioning.
  2181  func TestCapacity(t *testing.T) {
  2182  	type scenarioType struct {
  2183  		// Inputs
  2184  		pvcs       []*v1.PersistentVolumeClaim
  2185  		capacities []*storagev1.CSIStorageCapacity
  2186  
  2187  		// Expected return values
  2188  		reasons    ConflictReasons
  2189  		shouldFail bool
  2190  	}
  2191  	scenarios := map[string]scenarioType{
  2192  		"network-attached": {
  2193  			pvcs: []*v1.PersistentVolumeClaim{provisionedPVC},
  2194  			capacities: []*storagev1.CSIStorageCapacity{
  2195  				makeCapacity("net", waitClassWithProvisioner, nil, "1Gi", ""),
  2196  			},
  2197  		},
  2198  		"local-storage": {
  2199  			pvcs: []*v1.PersistentVolumeClaim{provisionedPVC},
  2200  			capacities: []*storagev1.CSIStorageCapacity{
  2201  				makeCapacity("net", waitClassWithProvisioner, node1, "1Gi", ""),
  2202  			},
  2203  		},
  2204  		"multiple": {
  2205  			pvcs: []*v1.PersistentVolumeClaim{provisionedPVC},
  2206  			capacities: []*storagev1.CSIStorageCapacity{
  2207  				makeCapacity("net", waitClassWithProvisioner, nil, "1Gi", ""),
  2208  				makeCapacity("net", waitClassWithProvisioner, node2, "1Gi", ""),
  2209  				makeCapacity("net", waitClassWithProvisioner, node1, "1Gi", ""),
  2210  			},
  2211  		},
  2212  		"no-storage": {
  2213  			pvcs:    []*v1.PersistentVolumeClaim{provisionedPVC},
  2214  			reasons: ConflictReasons{ErrReasonNotEnoughSpace},
  2215  		},
  2216  		"wrong-node": {
  2217  			pvcs: []*v1.PersistentVolumeClaim{provisionedPVC},
  2218  			capacities: []*storagev1.CSIStorageCapacity{
  2219  				makeCapacity("net", waitClassWithProvisioner, node2, "1Gi", ""),
  2220  			},
  2221  			reasons: ConflictReasons{ErrReasonNotEnoughSpace},
  2222  		},
  2223  		"wrong-storage-class": {
  2224  			pvcs: []*v1.PersistentVolumeClaim{provisionedPVC},
  2225  			capacities: []*storagev1.CSIStorageCapacity{
  2226  				makeCapacity("net", waitClass, node1, "1Gi", ""),
  2227  			},
  2228  			reasons: ConflictReasons{ErrReasonNotEnoughSpace},
  2229  		},
  2230  		"insufficient-storage": {
  2231  			pvcs: []*v1.PersistentVolumeClaim{provisionedPVC},
  2232  			capacities: []*storagev1.CSIStorageCapacity{
  2233  				makeCapacity("net", waitClassWithProvisioner, node1, "1Mi", ""),
  2234  			},
  2235  			reasons: ConflictReasons{ErrReasonNotEnoughSpace},
  2236  		},
  2237  		"insufficient-volume-size": {
  2238  			pvcs: []*v1.PersistentVolumeClaim{provisionedPVC},
  2239  			capacities: []*storagev1.CSIStorageCapacity{
  2240  				makeCapacity("net", waitClassWithProvisioner, node1, "1Gi", "1Mi"),
  2241  			},
  2242  			reasons: ConflictReasons{ErrReasonNotEnoughSpace},
  2243  		},
  2244  		"zero-storage": {
  2245  			pvcs: []*v1.PersistentVolumeClaim{provisionedPVC},
  2246  			capacities: []*storagev1.CSIStorageCapacity{
  2247  				makeCapacity("net", waitClassWithProvisioner, node1, "0Mi", ""),
  2248  			},
  2249  			reasons: ConflictReasons{ErrReasonNotEnoughSpace},
  2250  		},
  2251  		"zero-volume-size": {
  2252  			pvcs: []*v1.PersistentVolumeClaim{provisionedPVC},
  2253  			capacities: []*storagev1.CSIStorageCapacity{
  2254  				makeCapacity("net", waitClassWithProvisioner, node1, "", "0Mi"),
  2255  			},
  2256  			reasons: ConflictReasons{ErrReasonNotEnoughSpace},
  2257  		},
  2258  		"nil-storage": {
  2259  			pvcs: []*v1.PersistentVolumeClaim{provisionedPVC},
  2260  			capacities: []*storagev1.CSIStorageCapacity{
  2261  				makeCapacity("net", waitClassWithProvisioner, node1, "", ""),
  2262  			},
  2263  			reasons: ConflictReasons{ErrReasonNotEnoughSpace},
  2264  		},
  2265  	}
  2266  
  2267  	testNode := &v1.Node{
  2268  		ObjectMeta: metav1.ObjectMeta{
  2269  			Name: "node1",
  2270  			Labels: map[string]string{
  2271  				nodeLabelKey: "node1",
  2272  			},
  2273  		},
  2274  	}
  2275  
  2276  	run := func(t *testing.T, scenario scenarioType, optIn bool) {
  2277  		logger, ctx := ktesting.NewTestContext(t)
  2278  		ctx, cancel := context.WithCancel(ctx)
  2279  		defer cancel()
  2280  
  2281  		// Setup: the driver has the feature enabled, but the scheduler might not.
  2282  		testEnv := newTestBinder(t, ctx)
  2283  		testEnv.addCSIDriver(makeCSIDriver(provisioner, optIn))
  2284  		testEnv.addCSIStorageCapacities(scenario.capacities)
  2285  
  2286  		// a. Init pvc cache
  2287  		testEnv.initClaims(scenario.pvcs, scenario.pvcs)
  2288  
  2289  		// b. Generate pod with given claims
  2290  		pod := makePod("test-pod").
  2291  			withNamespace("testns").
  2292  			withNodeName("node1").
  2293  			withPVCSVolume(scenario.pvcs).Pod
  2294  
  2295  		// Execute
  2296  		podVolumes, reasons, err := findPodVolumes(logger, testEnv.binder, pod, testNode)
  2297  
  2298  		// Validate
  2299  		shouldFail := scenario.shouldFail
  2300  		expectedReasons := scenario.reasons
  2301  		if !optIn {
  2302  			shouldFail = false
  2303  			expectedReasons = nil
  2304  		}
  2305  		if !shouldFail && err != nil {
  2306  			t.Errorf("returned error: %v", err)
  2307  		}
  2308  		if shouldFail && err == nil {
  2309  			t.Error("returned success but expected error")
  2310  		}
  2311  		checkReasons(t, reasons, expectedReasons)
  2312  		provisions := scenario.pvcs
  2313  		if len(reasons) > 0 {
  2314  			provisions = nil
  2315  		}
  2316  		testEnv.validatePodCache(t, pod.Spec.NodeName, pod, podVolumes, nil, provisions)
  2317  	}
  2318  
  2319  	yesNo := []bool{true, false}
  2320  	for _, optIn := range yesNo {
  2321  		name := fmt.Sprintf("CSIDriver.StorageCapacity=%v", optIn)
  2322  		t.Run(name, func(t *testing.T) {
  2323  			for name, scenario := range scenarios {
  2324  				t.Run(name, func(t *testing.T) { run(t, scenario, optIn) })
  2325  			}
  2326  		})
  2327  	}
  2328  }
  2329  
  2330  func TestGetEligibleNodes(t *testing.T) {
  2331  	type scenarioType struct {
  2332  		// Inputs
  2333  		pvcs  []*v1.PersistentVolumeClaim
  2334  		pvs   []*v1.PersistentVolume
  2335  		nodes []*v1.Node
  2336  
  2337  		// Expected return values
  2338  		eligibleNodes sets.Set[string]
  2339  	}
  2340  
  2341  	scenarios := map[string]scenarioType{
  2342  		"no-bound-claims": {},
  2343  		"no-nodes-found": {
  2344  			pvcs: []*v1.PersistentVolumeClaim{
  2345  				preboundPVC,
  2346  				preboundPVCNode1a,
  2347  			},
  2348  		},
  2349  		"pv-not-found": {
  2350  			pvcs: []*v1.PersistentVolumeClaim{
  2351  				preboundPVC,
  2352  				preboundPVCNode1a,
  2353  			},
  2354  			nodes: []*v1.Node{
  2355  				node1,
  2356  			},
  2357  		},
  2358  		"node-affinity-mismatch": {
  2359  			pvcs: []*v1.PersistentVolumeClaim{
  2360  				preboundPVC,
  2361  				preboundPVCNode1a,
  2362  			},
  2363  			pvs: []*v1.PersistentVolume{
  2364  				pvNode1a,
  2365  			},
  2366  			nodes: []*v1.Node{
  2367  				node1,
  2368  				node2,
  2369  			},
  2370  		},
  2371  		"local-pv-with-node-affinity": {
  2372  			pvcs: []*v1.PersistentVolumeClaim{
  2373  				localPreboundPVC1a,
  2374  				localPreboundPVC1b,
  2375  			},
  2376  			pvs: []*v1.PersistentVolume{
  2377  				localPVNode1a,
  2378  				localPVNode1b,
  2379  			},
  2380  			nodes: []*v1.Node{
  2381  				node1,
  2382  				node2,
  2383  			},
  2384  			eligibleNodes: sets.New("node1"),
  2385  		},
  2386  		"multi-local-pv-with-different-nodes": {
  2387  			pvcs: []*v1.PersistentVolumeClaim{
  2388  				localPreboundPVC1a,
  2389  				localPreboundPVC1b,
  2390  				localPreboundPVC2a,
  2391  			},
  2392  			pvs: []*v1.PersistentVolume{
  2393  				localPVNode1a,
  2394  				localPVNode1b,
  2395  				localPVNode2a,
  2396  			},
  2397  			nodes: []*v1.Node{
  2398  				node1,
  2399  				node2,
  2400  			},
  2401  			eligibleNodes: sets.New[string](),
  2402  		},
  2403  		"local-and-non-local-pv": {
  2404  			pvcs: []*v1.PersistentVolumeClaim{
  2405  				localPreboundPVC1a,
  2406  				localPreboundPVC1b,
  2407  				preboundPVC,
  2408  				immediateBoundPVC,
  2409  			},
  2410  			pvs: []*v1.PersistentVolume{
  2411  				localPVNode1a,
  2412  				localPVNode1b,
  2413  				pvNode1a,
  2414  				pvBoundImmediate,
  2415  				pvBoundImmediateNode2,
  2416  			},
  2417  			nodes: []*v1.Node{
  2418  				node1,
  2419  				node2,
  2420  			},
  2421  			eligibleNodes: sets.New("node1"),
  2422  		},
  2423  	}
  2424  
  2425  	run := func(t *testing.T, scenario scenarioType) {
  2426  		logger, ctx := ktesting.NewTestContext(t)
  2427  		ctx, cancel := context.WithCancel(ctx)
  2428  		defer cancel()
  2429  
  2430  		// Setup
  2431  		testEnv := newTestBinder(t, ctx)
  2432  		testEnv.initVolumes(scenario.pvs, scenario.pvs)
  2433  
  2434  		testEnv.initNodes(scenario.nodes)
  2435  		testEnv.initClaims(scenario.pvcs, scenario.pvcs)
  2436  
  2437  		// Execute
  2438  		eligibleNodes := testEnv.binder.GetEligibleNodes(logger, scenario.pvcs)
  2439  
  2440  		// Validate
  2441  		if reflect.DeepEqual(scenario.eligibleNodes, eligibleNodes) {
  2442  			fmt.Println("foo")
  2443  		}
  2444  
  2445  		if compDiff := cmp.Diff(scenario.eligibleNodes, eligibleNodes, cmp.Comparer(func(a, b sets.Set[string]) bool {
  2446  			return reflect.DeepEqual(a, b)
  2447  		})); compDiff != "" {
  2448  			t.Errorf("Unexpected eligible nodes (-want +got):\n%s", compDiff)
  2449  		}
  2450  	}
  2451  
  2452  	for name, scenario := range scenarios {
  2453  		t.Run(name, func(t *testing.T) { run(t, scenario) })
  2454  	}
  2455  }