1
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
56
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
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
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
84 conflictingGenericPVC = makeGenericEphemeralPVC("test-volume", false )
85 correctGenericPVC = makeGenericEphemeralPVC("test-volume", true )
86 pvBoundGeneric = makeTestPV("pv-bound", "node1", "1G", "1", correctGenericPVC, waitClass)
87
88
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
104 migrationPVBound = makeTestPVForCSIMigration(zone1Labels, boundMigrationPVC, true)
105 migrationPVBoundToUnbound = makeTestPVForCSIMigration(zone1Labels, unboundPVC, true)
106 nonmigrationPVBoundToUnbound = makeTestPVForCSIMigration(zone1Labels, unboundPVC, false)
107
108
109 waitClass = "waitClass"
110 immediateClass = "immediateClass"
111 waitClassWithProvisioner = "waitClassWithProvisioner"
112 topoMismatchClass = "topoMismatchClass"
113
114
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
122 csiNode1Migrated = makeCSINode("node1", "kubernetes.io/gce-pd")
123 csiNode1NotMigrated = makeCSINode("node1", "")
124
125
126 nodeLabelKey = "nodeKey"
127 nodeLabelValue = "node1"
128
129
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
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
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
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
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
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
435 t.Error("expected nil bindings, got empty")
436 } else if expectedBindings != nil && bindings == nil {
437
438 t.Error("expected empty bindings, got nil")
439 } else {
440 for i := 0; i < aLen; i++ {
441
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
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
457 t.Error("expected nil provisionings, got empty")
458 } else if expectedProvisionings != nil && provisionedClaims == nil {
459
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
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
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
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
513 if pv != nil && pv != apiPV {
514 t.Errorf("PV %q was modified in cache", b.pv.Name)
515 }
516 }
517
518
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
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
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
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
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
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
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
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
705 pv.ObjectMeta.Labels = labels
706
707
708
709
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
804 metav1.SetMetaDataAnnotation(&res.ObjectMeta, volume.AnnSelectedNode, nodeLabelValue)
805
806 return res
807 }
808
809
810
811
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
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
863 pvs []*v1.PersistentVolume
864 podPVCs []*v1.PersistentVolumeClaim
865
866 cachePVCs []*v1.PersistentVolumeClaim
867
868 pod *v1.Pod
869
870
871 expectedBindings []*BindingInfo
872
873
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
1012 testEnv := newTestBinder(t, ctx)
1013 testEnv.initVolumes(scenario.pvs, scenario.pvs)
1014 if csiDriver != nil {
1015 testEnv.addCSIDriver(csiDriver)
1016 }
1017
1018
1019 if scenario.cachePVCs == nil {
1020 scenario.cachePVCs = scenario.podPVCs
1021 }
1022 testEnv.initClaims(scenario.cachePVCs, scenario.cachePVCs)
1023
1024
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
1033 podVolumes, reasons, err := findPodVolumes(logger, testEnv.binder, scenario.pod, testNode)
1034
1035
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
1064 pvs []*v1.PersistentVolume
1065 podPVCs []*v1.PersistentVolumeClaim
1066
1067 cachePVCs []*v1.PersistentVolumeClaim
1068
1069 pod *v1.Pod
1070
1071
1072 expectedBindings []*BindingInfo
1073 expectedProvisions []*v1.PersistentVolumeClaim
1074
1075
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
1140 testEnv := newTestBinder(t, ctx)
1141 testEnv.initVolumes(scenario.pvs, scenario.pvs)
1142 if csiDriver != nil {
1143 testEnv.addCSIDriver(csiDriver)
1144 }
1145
1146
1147 if scenario.cachePVCs == nil {
1148 scenario.cachePVCs = scenario.podPVCs
1149 }
1150 testEnv.initClaims(scenario.cachePVCs, scenario.cachePVCs)
1151
1152
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
1161 podVolumes, reasons, err := findPodVolumes(logger, testEnv.binder, scenario.pod, testNode)
1162
1163
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
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
1196
1197 func TestFindPodVolumesWithCSIMigration(t *testing.T) {
1198 type scenarioType struct {
1199
1200 pvs []*v1.PersistentVolume
1201 podPVCs []*v1.PersistentVolumeClaim
1202
1203 cachePVCs []*v1.PersistentVolumeClaim
1204
1205 pod *v1.Pod
1206
1207
1208 initNodes []*v1.Node
1209 initCSINodes []*storagev1.CSINode
1210
1211
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
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
1264 if scenario.cachePVCs == nil {
1265 scenario.cachePVCs = scenario.podPVCs
1266 }
1267 testEnv.initClaims(scenario.cachePVCs, scenario.cachePVCs)
1268
1269
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
1278 _, reasons, err := findPodVolumes(logger, testEnv.binder, scenario.pod, node)
1279
1280
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
1298 podPVCs []*v1.PersistentVolumeClaim
1299 pvs []*v1.PersistentVolume
1300 bindings []*BindingInfo
1301 provisionedPVCs []*v1.PersistentVolumeClaim
1302
1303
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
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
1379 allBound, err := testEnv.binder.AssumePodVolumes(logger, pod, "node1", podVolumes)
1380
1381
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
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
1448 bindings []*BindingInfo
1449 cachedPVs []*v1.PersistentVolume
1450
1451 apiPVs []*v1.PersistentVolume
1452
1453 provisionedPVCs []*v1.PersistentVolumeClaim
1454 cachedPVCs []*v1.PersistentVolumeClaim
1455
1456 apiPVCs []*v1.PersistentVolumeClaim
1457
1458
1459 shouldFail bool
1460 expectedPVs []*v1.PersistentVolume
1461
1462 expectedAPIPVs []*v1.PersistentVolume
1463
1464 expectedPVCs []*v1.PersistentVolumeClaim
1465
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
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
1560 err := testEnv.internalBinder.bindAPIUpdate(ctx, pod, scenario.bindings, scenario.provisionedPVCs)
1561
1562
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
1589 initPVs []*v1.PersistentVolume
1590 initPVCs []*v1.PersistentVolumeClaim
1591
1592 bindings []*BindingInfo
1593 provisionedPVCs []*v1.PersistentVolumeClaim
1594
1595
1596 apiPVs []*v1.PersistentVolume
1597 apiPVCs []*v1.PersistentVolumeClaim
1598
1599
1600 deletePVs bool
1601 deletePVCs bool
1602
1603
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
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
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
1770 allBound, err := testEnv.internalBinder.checkBindings(logger, pod, scenario.bindings, scenario.provisionedPVCs)
1771
1772
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
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
1803 apiPVs []*v1.PersistentVolume
1804 apiPVCs []*v1.PersistentVolumeClaim
1805
1806
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
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
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
1890 allBound, err := testEnv.internalBinder.checkBindings(logger, pod, scenario.bindings, scenario.provisionedPVCs)
1891
1892
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
1914 bindingsNil bool
1915
1916 nodes []*v1.Node
1917
1918
1919 initPVs []*v1.PersistentVolume
1920 initPVCs []*v1.PersistentVolumeClaim
1921
1922
1923 binding *BindingInfo
1924 claimToProvision *v1.PersistentVolumeClaim
1925
1926
1927 apiPV *v1.PersistentVolume
1928 apiPVC *v1.PersistentVolumeClaim
1929
1930
1931 delayFunc func(t *testing.T, ctx context.Context, testEnv *testEnv, pod *v1.Pod, pvs []*v1.PersistentVolume, pvcs []*v1.PersistentVolumeClaim)
1932
1933
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,
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
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
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,
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
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
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
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
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
2101 logger.V(5).Info("Running delay function")
2102 scenario.delayFunc(t, ctx, testEnv, pod, scenario.initPVs, scenario.initPVCs)
2103 }(scenario)
2104 }
2105
2106
2107 podVolumes := &PodVolumes{
2108 StaticBindings: bindings,
2109 DynamicProvisions: claimsToProvision,
2110 }
2111 err := testEnv.binder.BindPodVolumes(ctx, pod, podVolumes)
2112
2113
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
2133 podPVCs := []*v1.PersistentVolumeClaim{unboundPVC}
2134 pvs := []*v1.PersistentVolume{pvNode2, pvNode1a, pvNode1c}
2135
2136
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
2158
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
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
2179 expectedBindings = podVolumes.StaticBindings
2180
2181
2182
2183
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
2197
2198 func TestCapacity(t *testing.T) {
2199 type scenarioType struct {
2200
2201 pvcs []*v1.PersistentVolumeClaim
2202 capacities []*storagev1.CSIStorageCapacity
2203
2204
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
2299 testEnv := newTestBinder(t, ctx)
2300 testEnv.addCSIDriver(makeCSIDriver(provisioner, optIn))
2301 testEnv.addCSIStorageCapacities(scenario.capacities)
2302
2303
2304 testEnv.initClaims(scenario.pvcs, scenario.pvcs)
2305
2306
2307 pod := makePod("test-pod").
2308 withNamespace("testns").
2309 withNodeName("node1").
2310 withPVCSVolume(scenario.pvcs).Pod
2311
2312
2313 podVolumes, reasons, err := findPodVolumes(logger, testEnv.binder, pod, testNode)
2314
2315
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
2350 pvcs []*v1.PersistentVolumeClaim
2351 pvs []*v1.PersistentVolume
2352 nodes []*v1.Node
2353
2354
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
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
2455 eligibleNodes := testEnv.binder.GetEligibleNodes(logger, scenario.pvcs)
2456
2457
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