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