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