...

Source file src/k8s.io/kubernetes/pkg/scheduler/framework/plugins/volumebinding/binder_test.go

Documentation: k8s.io/kubernetes/pkg/scheduler/framework/plugins/volumebinding

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

View as plain text