k8s.io/kubernetes@v1.29.3/pkg/controller/volume/persistentvolume/index_test.go (about) 1 /* 2 Copyright 2014 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 persistentvolume 18 19 import ( 20 "sort" 21 "testing" 22 23 v1 "k8s.io/api/core/v1" 24 "k8s.io/apimachinery/pkg/api/resource" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/client-go/kubernetes/scheme" 27 ref "k8s.io/client-go/tools/reference" 28 "k8s.io/component-helpers/storage/volume" 29 "k8s.io/kubernetes/pkg/volume/util" 30 ) 31 32 func makePVC(size string, modfn func(*v1.PersistentVolumeClaim)) *v1.PersistentVolumeClaim { 33 fs := v1.PersistentVolumeFilesystem 34 pvc := v1.PersistentVolumeClaim{ 35 ObjectMeta: metav1.ObjectMeta{ 36 Name: "claim01", 37 Namespace: "myns", 38 }, 39 Spec: v1.PersistentVolumeClaimSpec{ 40 AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOnce}, 41 Resources: v1.VolumeResourceRequirements{ 42 Requests: v1.ResourceList{ 43 v1.ResourceName(v1.ResourceStorage): resource.MustParse(size), 44 }, 45 }, 46 VolumeMode: &fs, 47 }, 48 } 49 if modfn != nil { 50 modfn(&pvc) 51 } 52 return &pvc 53 } 54 55 func makeVolumeModePVC(size string, mode *v1.PersistentVolumeMode, modfn func(*v1.PersistentVolumeClaim)) *v1.PersistentVolumeClaim { 56 pvc := v1.PersistentVolumeClaim{ 57 ObjectMeta: metav1.ObjectMeta{ 58 Name: "claim01", 59 Namespace: "myns", 60 }, 61 Spec: v1.PersistentVolumeClaimSpec{ 62 VolumeMode: mode, 63 AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}, 64 Resources: v1.VolumeResourceRequirements{ 65 Requests: v1.ResourceList{ 66 v1.ResourceName(v1.ResourceStorage): resource.MustParse(size), 67 }, 68 }, 69 }, 70 } 71 if modfn != nil { 72 modfn(&pvc) 73 } 74 return &pvc 75 } 76 77 func TestMatchVolume(t *testing.T) { 78 volList := newPersistentVolumeOrderedIndex() 79 for _, pv := range createTestVolumes() { 80 volList.store.Add(pv) 81 } 82 83 scenarios := map[string]struct { 84 expectedMatch string 85 claim *v1.PersistentVolumeClaim 86 }{ 87 "successful-match-gce-10": { 88 expectedMatch: "gce-pd-10", 89 claim: makePVC("8G", nil), 90 }, 91 "successful-match-nfs-5": { 92 expectedMatch: "nfs-5", 93 claim: makePVC("5G", func(pvc *v1.PersistentVolumeClaim) { 94 pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOnce, v1.ReadWriteMany} 95 }), 96 }, 97 "successful-skip-1g-bound-volume": { 98 expectedMatch: "gce-pd-5", 99 claim: makePVC("1G", nil), 100 }, 101 "successful-no-match": { 102 expectedMatch: "", 103 claim: makePVC("999G", nil), 104 }, 105 "successful-no-match-due-to-label": { 106 expectedMatch: "", 107 claim: makePVC("999G", func(pvc *v1.PersistentVolumeClaim) { 108 pvc.Spec.Selector = &metav1.LabelSelector{ 109 MatchLabels: map[string]string{ 110 "should-not-exist": "true", 111 }, 112 } 113 }), 114 }, 115 "successful-no-match-due-to-size-constraint-with-label-selector": { 116 expectedMatch: "", 117 claim: makePVC("20000G", func(pvc *v1.PersistentVolumeClaim) { 118 pvc.Spec.Selector = &metav1.LabelSelector{ 119 MatchLabels: map[string]string{ 120 "should-exist": "true", 121 }, 122 } 123 pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOnce} 124 }), 125 }, 126 "successful-match-due-with-constraint-and-label-selector": { 127 expectedMatch: "gce-pd-2", 128 claim: makePVC("20000G", func(pvc *v1.PersistentVolumeClaim) { 129 pvc.Spec.Selector = &metav1.LabelSelector{ 130 MatchLabels: map[string]string{ 131 "should-exist": "true", 132 }, 133 } 134 pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce} 135 }), 136 }, 137 "successful-match-with-class": { 138 expectedMatch: "gce-pd-silver1", 139 claim: makePVC("1G", func(pvc *v1.PersistentVolumeClaim) { 140 pvc.Spec.Selector = &metav1.LabelSelector{ 141 MatchLabels: map[string]string{ 142 "should-exist": "true", 143 }, 144 } 145 pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce} 146 pvc.Spec.StorageClassName = &classSilver 147 }), 148 }, 149 "successful-match-with-class-and-labels": { 150 expectedMatch: "gce-pd-silver2", 151 claim: makePVC("1G", func(pvc *v1.PersistentVolumeClaim) { 152 pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce} 153 pvc.Spec.StorageClassName = &classSilver 154 }), 155 }, 156 "successful-match-very-large": { 157 expectedMatch: "local-pd-very-large", 158 // we keep the pvc size less than int64 so that in case the pv overflows 159 // the pvc does not overflow equally and give us false matching signals. 160 claim: makePVC("1E", func(pvc *v1.PersistentVolumeClaim) { 161 pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce} 162 pvc.Spec.StorageClassName = &classLarge 163 }), 164 }, 165 "successful-match-exact-extremely-large": { 166 expectedMatch: "local-pd-extremely-large", 167 claim: makePVC("800E", func(pvc *v1.PersistentVolumeClaim) { 168 pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce} 169 pvc.Spec.StorageClassName = &classLarge 170 }), 171 }, 172 "successful-no-match-way-too-large": { 173 expectedMatch: "", 174 claim: makePVC("950E", func(pvc *v1.PersistentVolumeClaim) { 175 pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce} 176 pvc.Spec.StorageClassName = &classLarge 177 }), 178 }, 179 } 180 181 for name, scenario := range scenarios { 182 volume, err := volList.findBestMatchForClaim(scenario.claim, false) 183 if err != nil { 184 t.Errorf("Unexpected error matching volume by claim: %v", err) 185 } 186 if len(scenario.expectedMatch) != 0 && volume == nil { 187 t.Errorf("Expected match but received nil volume for scenario: %s", name) 188 } 189 if len(scenario.expectedMatch) != 0 && volume != nil && string(volume.UID) != scenario.expectedMatch { 190 t.Errorf("Expected %s but got volume %s in scenario %s", scenario.expectedMatch, volume.UID, name) 191 } 192 if len(scenario.expectedMatch) == 0 && volume != nil { 193 t.Errorf("Unexpected match for scenario: %s, matched with %s instead", name, volume.UID) 194 } 195 } 196 } 197 198 func TestMatchingWithBoundVolumes(t *testing.T) { 199 fs := v1.PersistentVolumeFilesystem 200 volumeIndex := newPersistentVolumeOrderedIndex() 201 // two similar volumes, one is bound 202 pv1 := &v1.PersistentVolume{ 203 ObjectMeta: metav1.ObjectMeta{ 204 UID: "gce-pd-1", 205 Name: "gce001", 206 }, 207 Spec: v1.PersistentVolumeSpec{ 208 Capacity: v1.ResourceList{ 209 v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G"), 210 }, 211 PersistentVolumeSource: v1.PersistentVolumeSource{ 212 GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{}, 213 }, 214 AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce, v1.ReadOnlyMany}, 215 // this one we're pretending is already bound 216 ClaimRef: &v1.ObjectReference{UID: "abc123"}, 217 VolumeMode: &fs, 218 }, 219 Status: v1.PersistentVolumeStatus{ 220 Phase: v1.VolumeBound, 221 }, 222 } 223 224 pv2 := &v1.PersistentVolume{ 225 ObjectMeta: metav1.ObjectMeta{ 226 UID: "gce-pd-2", 227 Name: "gce002", 228 }, 229 Spec: v1.PersistentVolumeSpec{ 230 Capacity: v1.ResourceList{ 231 v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G"), 232 }, 233 PersistentVolumeSource: v1.PersistentVolumeSource{ 234 GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{}, 235 }, 236 AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce, v1.ReadOnlyMany}, 237 VolumeMode: &fs, 238 }, 239 Status: v1.PersistentVolumeStatus{ 240 Phase: v1.VolumeAvailable, 241 }, 242 } 243 244 volumeIndex.store.Add(pv1) 245 volumeIndex.store.Add(pv2) 246 247 claim := &v1.PersistentVolumeClaim{ 248 ObjectMeta: metav1.ObjectMeta{ 249 Name: "claim01", 250 Namespace: "myns", 251 }, 252 Spec: v1.PersistentVolumeClaimSpec{ 253 AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOnce}, 254 Resources: v1.VolumeResourceRequirements{ 255 Requests: v1.ResourceList{ 256 v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G"), 257 }, 258 }, 259 VolumeMode: &fs, 260 }, 261 } 262 263 volume, err := volumeIndex.findBestMatchForClaim(claim, false) 264 if err != nil { 265 t.Fatalf("Unexpected error matching volume by claim: %v", err) 266 } 267 if volume == nil { 268 t.Fatalf("Unexpected nil volume. Expected %s", pv2.Name) 269 } 270 if pv2.Name != volume.Name { 271 t.Errorf("Expected %s but got volume %s instead", pv2.Name, volume.Name) 272 } 273 } 274 275 func TestListByAccessModes(t *testing.T) { 276 volList := newPersistentVolumeOrderedIndex() 277 for _, pv := range createTestVolumes() { 278 volList.store.Add(pv) 279 } 280 281 volumes, err := volList.listByAccessModes([]v1.PersistentVolumeAccessMode{v1.ReadWriteOnce, v1.ReadOnlyMany}) 282 if err != nil { 283 t.Error("Unexpected error retrieving volumes by access modes:", err) 284 } 285 sort.Sort(byCapacity{volumes}) 286 287 for i, expected := range []string{"gce-pd-1", "gce-pd-5", "gce-pd-10"} { 288 if string(volumes[i].UID) != expected { 289 t.Errorf("Incorrect ordering of persistent volumes. Expected %s but got %s", expected, volumes[i].UID) 290 } 291 } 292 293 volumes, err = volList.listByAccessModes([]v1.PersistentVolumeAccessMode{v1.ReadWriteOnce, v1.ReadOnlyMany, v1.ReadWriteMany}) 294 if err != nil { 295 t.Error("Unexpected error retrieving volumes by access modes:", err) 296 } 297 sort.Sort(byCapacity{volumes}) 298 299 for i, expected := range []string{"nfs-1", "nfs-5", "nfs-10", "local-pd-very-large", "local-pd-extremely-large"} { 300 if string(volumes[i].UID) != expected { 301 t.Errorf("Incorrect ordering of persistent volumes. Expected %s but got %s", expected, volumes[i].UID) 302 } 303 } 304 } 305 306 func TestAllPossibleAccessModes(t *testing.T) { 307 index := newPersistentVolumeOrderedIndex() 308 for _, pv := range createTestVolumes() { 309 index.store.Add(pv) 310 } 311 312 // the mock PVs creates contain 2 types of accessmodes: RWO+ROX and RWO+ROW+RWX 313 possibleModes := index.allPossibleMatchingAccessModes([]v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}) 314 if len(possibleModes) != 3 { 315 t.Errorf("Expected 3 arrays of modes that match RWO, but got %v", len(possibleModes)) 316 } 317 for _, m := range possibleModes { 318 if !util.ContainsAccessMode(m, v1.ReadWriteOnce) { 319 t.Errorf("AccessModes does not contain %s", v1.ReadWriteOnce) 320 } 321 } 322 323 possibleModes = index.allPossibleMatchingAccessModes([]v1.PersistentVolumeAccessMode{v1.ReadWriteMany}) 324 if len(possibleModes) != 1 { 325 t.Errorf("Expected 1 array of modes that match RWX, but got %v", len(possibleModes)) 326 } 327 if !util.ContainsAccessMode(possibleModes[0], v1.ReadWriteMany) { 328 t.Errorf("AccessModes does not contain %s", v1.ReadWriteOnce) 329 } 330 331 } 332 333 func TestFindingVolumeWithDifferentAccessModes(t *testing.T) { 334 fs := v1.PersistentVolumeFilesystem 335 gce := &v1.PersistentVolume{ 336 ObjectMeta: metav1.ObjectMeta{UID: "001", Name: "gce"}, 337 Spec: v1.PersistentVolumeSpec{ 338 Capacity: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G")}, 339 PersistentVolumeSource: v1.PersistentVolumeSource{GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{}}, 340 AccessModes: []v1.PersistentVolumeAccessMode{ 341 v1.ReadWriteOnce, 342 v1.ReadOnlyMany, 343 }, 344 VolumeMode: &fs, 345 }, 346 Status: v1.PersistentVolumeStatus{ 347 Phase: v1.VolumeAvailable, 348 }, 349 } 350 351 ebs := &v1.PersistentVolume{ 352 ObjectMeta: metav1.ObjectMeta{UID: "002", Name: "ebs"}, 353 Spec: v1.PersistentVolumeSpec{ 354 Capacity: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G")}, 355 PersistentVolumeSource: v1.PersistentVolumeSource{AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{}}, 356 AccessModes: []v1.PersistentVolumeAccessMode{ 357 v1.ReadWriteOnce, 358 }, 359 VolumeMode: &fs, 360 }, 361 Status: v1.PersistentVolumeStatus{ 362 Phase: v1.VolumeAvailable, 363 }, 364 } 365 366 nfs := &v1.PersistentVolume{ 367 ObjectMeta: metav1.ObjectMeta{UID: "003", Name: "nfs"}, 368 Spec: v1.PersistentVolumeSpec{ 369 Capacity: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G")}, 370 PersistentVolumeSource: v1.PersistentVolumeSource{NFS: &v1.NFSVolumeSource{}}, 371 AccessModes: []v1.PersistentVolumeAccessMode{ 372 v1.ReadWriteOnce, 373 v1.ReadOnlyMany, 374 v1.ReadWriteMany, 375 }, 376 VolumeMode: &fs, 377 }, 378 Status: v1.PersistentVolumeStatus{ 379 Phase: v1.VolumeAvailable, 380 }, 381 } 382 383 claim := &v1.PersistentVolumeClaim{ 384 ObjectMeta: metav1.ObjectMeta{ 385 Name: "claim01", 386 Namespace: "myns", 387 }, 388 Spec: v1.PersistentVolumeClaimSpec{ 389 AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}, 390 Resources: v1.VolumeResourceRequirements{Requests: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G")}}, 391 VolumeMode: &fs, 392 }, 393 } 394 395 index := newPersistentVolumeOrderedIndex() 396 index.store.Add(gce) 397 index.store.Add(ebs) 398 index.store.Add(nfs) 399 400 volume, _ := index.findBestMatchForClaim(claim, false) 401 if volume.Name != ebs.Name { 402 t.Errorf("Expected %s but got volume %s instead", ebs.Name, volume.Name) 403 } 404 405 claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce, v1.ReadOnlyMany} 406 volume, _ = index.findBestMatchForClaim(claim, false) 407 if volume.Name != gce.Name { 408 t.Errorf("Expected %s but got volume %s instead", gce.Name, volume.Name) 409 } 410 411 // order of the requested modes should not matter 412 claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteMany, v1.ReadWriteOnce, v1.ReadOnlyMany} 413 volume, _ = index.findBestMatchForClaim(claim, false) 414 if volume.Name != nfs.Name { 415 t.Errorf("Expected %s but got volume %s instead", nfs.Name, volume.Name) 416 } 417 418 // fewer modes requested should still match 419 claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteMany} 420 volume, _ = index.findBestMatchForClaim(claim, false) 421 if volume.Name != nfs.Name { 422 t.Errorf("Expected %s but got volume %s instead", nfs.Name, volume.Name) 423 } 424 425 // pretend the exact match is bound. should get the next level up of modes. 426 ebs.Spec.ClaimRef = &v1.ObjectReference{} 427 claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce} 428 volume, _ = index.findBestMatchForClaim(claim, false) 429 if volume.Name != gce.Name { 430 t.Errorf("Expected %s but got volume %s instead", gce.Name, volume.Name) 431 } 432 433 // continue up the levels of modes. 434 gce.Spec.ClaimRef = &v1.ObjectReference{} 435 claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce} 436 volume, _ = index.findBestMatchForClaim(claim, false) 437 if volume.Name != nfs.Name { 438 t.Errorf("Expected %s but got volume %s instead", nfs.Name, volume.Name) 439 } 440 441 // partial mode request 442 gce.Spec.ClaimRef = nil 443 claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany} 444 volume, _ = index.findBestMatchForClaim(claim, false) 445 if volume.Name != gce.Name { 446 t.Errorf("Expected %s but got volume %s instead", gce.Name, volume.Name) 447 } 448 } 449 450 // createVolumeNodeAffinity returns a VolumeNodeAffinity for given key and value. 451 func createNodeAffinity(key string, value string) *v1.VolumeNodeAffinity { 452 return &v1.VolumeNodeAffinity{ 453 Required: &v1.NodeSelector{ 454 NodeSelectorTerms: []v1.NodeSelectorTerm{ 455 { 456 MatchExpressions: []v1.NodeSelectorRequirement{ 457 { 458 Key: key, 459 Operator: v1.NodeSelectorOpIn, 460 Values: []string{value}, 461 }, 462 }, 463 }, 464 }, 465 }, 466 } 467 } 468 469 func createTestVolumes() []*v1.PersistentVolume { 470 fs := v1.PersistentVolumeFilesystem 471 // these volumes are deliberately out-of-order to test indexing and sorting 472 return []*v1.PersistentVolume{ 473 { 474 ObjectMeta: metav1.ObjectMeta{ 475 UID: "gce-pd-10", 476 Name: "gce003", 477 }, 478 Spec: v1.PersistentVolumeSpec{ 479 Capacity: v1.ResourceList{ 480 v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G"), 481 }, 482 PersistentVolumeSource: v1.PersistentVolumeSource{ 483 GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{}, 484 }, 485 AccessModes: []v1.PersistentVolumeAccessMode{ 486 v1.ReadWriteOnce, 487 v1.ReadOnlyMany, 488 }, 489 VolumeMode: &fs, 490 }, 491 Status: v1.PersistentVolumeStatus{ 492 Phase: v1.VolumeAvailable, 493 }, 494 }, 495 { 496 ObjectMeta: metav1.ObjectMeta{ 497 UID: "gce-pd-20", 498 Name: "gce004", 499 }, 500 Spec: v1.PersistentVolumeSpec{ 501 Capacity: v1.ResourceList{ 502 v1.ResourceName(v1.ResourceStorage): resource.MustParse("20G"), 503 }, 504 PersistentVolumeSource: v1.PersistentVolumeSource{ 505 GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{}, 506 }, 507 AccessModes: []v1.PersistentVolumeAccessMode{ 508 v1.ReadWriteOnce, 509 v1.ReadOnlyMany, 510 }, 511 // this one we're pretending is already bound 512 ClaimRef: &v1.ObjectReference{UID: "def456"}, 513 VolumeMode: &fs, 514 }, 515 Status: v1.PersistentVolumeStatus{ 516 Phase: v1.VolumeBound, 517 }, 518 }, 519 { 520 ObjectMeta: metav1.ObjectMeta{ 521 UID: "nfs-5", 522 Name: "nfs002", 523 }, 524 Spec: v1.PersistentVolumeSpec{ 525 Capacity: v1.ResourceList{ 526 v1.ResourceName(v1.ResourceStorage): resource.MustParse("5G"), 527 }, 528 PersistentVolumeSource: v1.PersistentVolumeSource{ 529 Glusterfs: &v1.GlusterfsPersistentVolumeSource{}, 530 }, 531 AccessModes: []v1.PersistentVolumeAccessMode{ 532 v1.ReadWriteOnce, 533 v1.ReadOnlyMany, 534 v1.ReadWriteMany, 535 }, 536 VolumeMode: &fs, 537 }, 538 Status: v1.PersistentVolumeStatus{ 539 Phase: v1.VolumeAvailable, 540 }, 541 }, 542 { 543 ObjectMeta: metav1.ObjectMeta{ 544 UID: "gce-pd-1", 545 Name: "gce001", 546 }, 547 Spec: v1.PersistentVolumeSpec{ 548 Capacity: v1.ResourceList{ 549 v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G"), 550 }, 551 PersistentVolumeSource: v1.PersistentVolumeSource{ 552 GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{}, 553 }, 554 AccessModes: []v1.PersistentVolumeAccessMode{ 555 v1.ReadWriteOnce, 556 v1.ReadOnlyMany, 557 }, 558 // this one we're pretending is already bound 559 ClaimRef: &v1.ObjectReference{UID: "abc123"}, 560 VolumeMode: &fs, 561 }, 562 Status: v1.PersistentVolumeStatus{ 563 Phase: v1.VolumeBound, 564 }, 565 }, 566 { 567 ObjectMeta: metav1.ObjectMeta{ 568 UID: "nfs-10", 569 Name: "nfs003", 570 }, 571 Spec: v1.PersistentVolumeSpec{ 572 Capacity: v1.ResourceList{ 573 v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G"), 574 }, 575 PersistentVolumeSource: v1.PersistentVolumeSource{ 576 Glusterfs: &v1.GlusterfsPersistentVolumeSource{}, 577 }, 578 AccessModes: []v1.PersistentVolumeAccessMode{ 579 v1.ReadWriteOnce, 580 v1.ReadOnlyMany, 581 v1.ReadWriteMany, 582 }, 583 VolumeMode: &fs, 584 }, 585 Status: v1.PersistentVolumeStatus{ 586 Phase: v1.VolumeAvailable, 587 }, 588 }, 589 { 590 ObjectMeta: metav1.ObjectMeta{ 591 UID: "gce-pd-5", 592 Name: "gce002", 593 }, 594 Spec: v1.PersistentVolumeSpec{ 595 Capacity: v1.ResourceList{ 596 v1.ResourceName(v1.ResourceStorage): resource.MustParse("5G"), 597 }, 598 PersistentVolumeSource: v1.PersistentVolumeSource{ 599 GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{}, 600 }, 601 AccessModes: []v1.PersistentVolumeAccessMode{ 602 v1.ReadWriteOnce, 603 v1.ReadOnlyMany, 604 }, 605 VolumeMode: &fs, 606 }, 607 Status: v1.PersistentVolumeStatus{ 608 Phase: v1.VolumeAvailable, 609 }, 610 }, 611 { 612 ObjectMeta: metav1.ObjectMeta{ 613 UID: "nfs-1", 614 Name: "nfs001", 615 }, 616 Spec: v1.PersistentVolumeSpec{ 617 Capacity: v1.ResourceList{ 618 v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G"), 619 }, 620 PersistentVolumeSource: v1.PersistentVolumeSource{ 621 Glusterfs: &v1.GlusterfsPersistentVolumeSource{}, 622 }, 623 AccessModes: []v1.PersistentVolumeAccessMode{ 624 v1.ReadWriteOnce, 625 v1.ReadOnlyMany, 626 v1.ReadWriteMany, 627 }, 628 VolumeMode: &fs, 629 }, 630 Status: v1.PersistentVolumeStatus{ 631 Phase: v1.VolumeAvailable, 632 }, 633 }, 634 { 635 ObjectMeta: metav1.ObjectMeta{ 636 UID: "gce-pd-2", 637 Name: "gce0022", 638 Labels: map[string]string{ 639 "should-exist": "true", 640 }, 641 }, 642 Spec: v1.PersistentVolumeSpec{ 643 Capacity: v1.ResourceList{ 644 v1.ResourceName(v1.ResourceStorage): resource.MustParse("20000G"), 645 }, 646 PersistentVolumeSource: v1.PersistentVolumeSource{ 647 GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{}, 648 }, 649 AccessModes: []v1.PersistentVolumeAccessMode{ 650 v1.ReadWriteOnce, 651 }, 652 VolumeMode: &fs, 653 }, 654 Status: v1.PersistentVolumeStatus{ 655 Phase: v1.VolumeAvailable, 656 }, 657 }, 658 { 659 ObjectMeta: metav1.ObjectMeta{ 660 UID: "gce-pd-silver1", 661 Name: "gce0023", 662 Labels: map[string]string{ 663 "should-exist": "true", 664 }, 665 }, 666 Spec: v1.PersistentVolumeSpec{ 667 Capacity: v1.ResourceList{ 668 v1.ResourceName(v1.ResourceStorage): resource.MustParse("10000G"), 669 }, 670 PersistentVolumeSource: v1.PersistentVolumeSource{ 671 GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{}, 672 }, 673 AccessModes: []v1.PersistentVolumeAccessMode{ 674 v1.ReadWriteOnce, 675 }, 676 StorageClassName: classSilver, 677 VolumeMode: &fs, 678 }, 679 Status: v1.PersistentVolumeStatus{ 680 Phase: v1.VolumeAvailable, 681 }, 682 }, 683 { 684 ObjectMeta: metav1.ObjectMeta{ 685 UID: "gce-pd-silver2", 686 Name: "gce0024", 687 }, 688 Spec: v1.PersistentVolumeSpec{ 689 Capacity: v1.ResourceList{ 690 v1.ResourceName(v1.ResourceStorage): resource.MustParse("100G"), 691 }, 692 PersistentVolumeSource: v1.PersistentVolumeSource{ 693 GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{}, 694 }, 695 AccessModes: []v1.PersistentVolumeAccessMode{ 696 v1.ReadWriteOnce, 697 }, 698 StorageClassName: classSilver, 699 VolumeMode: &fs, 700 }, 701 Status: v1.PersistentVolumeStatus{ 702 Phase: v1.VolumeAvailable, 703 }, 704 }, 705 { 706 ObjectMeta: metav1.ObjectMeta{ 707 UID: "gce-pd-gold", 708 Name: "gce0025", 709 }, 710 Spec: v1.PersistentVolumeSpec{ 711 Capacity: v1.ResourceList{ 712 v1.ResourceName(v1.ResourceStorage): resource.MustParse("50G"), 713 }, 714 PersistentVolumeSource: v1.PersistentVolumeSource{ 715 GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{}, 716 }, 717 AccessModes: []v1.PersistentVolumeAccessMode{ 718 v1.ReadWriteOnce, 719 }, 720 StorageClassName: classGold, 721 VolumeMode: &fs, 722 }, 723 Status: v1.PersistentVolumeStatus{ 724 Phase: v1.VolumeAvailable, 725 }, 726 }, 727 { 728 ObjectMeta: metav1.ObjectMeta{ 729 UID: "local-pd-very-large", 730 Name: "local001", 731 }, 732 Spec: v1.PersistentVolumeSpec{ 733 Capacity: v1.ResourceList{ 734 v1.ResourceName(v1.ResourceStorage): resource.MustParse("200E"), 735 }, 736 PersistentVolumeSource: v1.PersistentVolumeSource{ 737 Local: &v1.LocalVolumeSource{}, 738 }, 739 AccessModes: []v1.PersistentVolumeAccessMode{ 740 v1.ReadWriteOnce, 741 v1.ReadOnlyMany, 742 v1.ReadWriteMany, 743 }, 744 StorageClassName: classLarge, 745 VolumeMode: &fs, 746 }, 747 Status: v1.PersistentVolumeStatus{ 748 Phase: v1.VolumeAvailable, 749 }, 750 }, 751 { 752 ObjectMeta: metav1.ObjectMeta{ 753 UID: "local-pd-extremely-large", 754 Name: "local002", 755 }, 756 Spec: v1.PersistentVolumeSpec{ 757 Capacity: v1.ResourceList{ 758 v1.ResourceName(v1.ResourceStorage): resource.MustParse("800E"), 759 }, 760 PersistentVolumeSource: v1.PersistentVolumeSource{ 761 Local: &v1.LocalVolumeSource{}, 762 }, 763 AccessModes: []v1.PersistentVolumeAccessMode{ 764 v1.ReadWriteOnce, 765 v1.ReadOnlyMany, 766 v1.ReadWriteMany, 767 }, 768 StorageClassName: classLarge, 769 VolumeMode: &fs, 770 }, 771 Status: v1.PersistentVolumeStatus{ 772 Phase: v1.VolumeAvailable, 773 }, 774 }, 775 { 776 ObjectMeta: metav1.ObjectMeta{ 777 UID: "affinity-pv", 778 Name: "affinity001", 779 }, 780 Spec: v1.PersistentVolumeSpec{ 781 Capacity: v1.ResourceList{ 782 v1.ResourceName(v1.ResourceStorage): resource.MustParse("100G"), 783 }, 784 PersistentVolumeSource: v1.PersistentVolumeSource{ 785 Local: &v1.LocalVolumeSource{}, 786 }, 787 AccessModes: []v1.PersistentVolumeAccessMode{ 788 v1.ReadWriteOnce, 789 v1.ReadOnlyMany, 790 }, 791 StorageClassName: classWait, 792 NodeAffinity: createNodeAffinity("key1", "value1"), 793 VolumeMode: &fs, 794 }, 795 Status: v1.PersistentVolumeStatus{ 796 Phase: v1.VolumeAvailable, 797 }, 798 }, 799 { 800 ObjectMeta: metav1.ObjectMeta{ 801 UID: "affinity-pv2", 802 Name: "affinity002", 803 }, 804 Spec: v1.PersistentVolumeSpec{ 805 Capacity: v1.ResourceList{ 806 v1.ResourceName(v1.ResourceStorage): resource.MustParse("150G"), 807 }, 808 PersistentVolumeSource: v1.PersistentVolumeSource{ 809 Local: &v1.LocalVolumeSource{}, 810 }, 811 AccessModes: []v1.PersistentVolumeAccessMode{ 812 v1.ReadWriteOnce, 813 v1.ReadOnlyMany, 814 }, 815 StorageClassName: classWait, 816 NodeAffinity: createNodeAffinity("key1", "value1"), 817 VolumeMode: &fs, 818 }, 819 Status: v1.PersistentVolumeStatus{ 820 Phase: v1.VolumeAvailable, 821 }, 822 }, 823 { 824 ObjectMeta: metav1.ObjectMeta{ 825 UID: "affinity-prebound", 826 Name: "affinity003", 827 }, 828 Spec: v1.PersistentVolumeSpec{ 829 Capacity: v1.ResourceList{ 830 v1.ResourceName(v1.ResourceStorage): resource.MustParse("100G"), 831 }, 832 PersistentVolumeSource: v1.PersistentVolumeSource{ 833 Local: &v1.LocalVolumeSource{}, 834 }, 835 AccessModes: []v1.PersistentVolumeAccessMode{ 836 v1.ReadWriteOnce, 837 v1.ReadOnlyMany, 838 }, 839 StorageClassName: classWait, 840 ClaimRef: &v1.ObjectReference{Name: "claim02", Namespace: "myns"}, 841 NodeAffinity: createNodeAffinity("key1", "value1"), 842 VolumeMode: &fs, 843 }, 844 Status: v1.PersistentVolumeStatus{ 845 Phase: v1.VolumeAvailable, 846 }, 847 }, 848 { 849 ObjectMeta: metav1.ObjectMeta{ 850 UID: "affinity-pv3", 851 Name: "affinity003", 852 }, 853 Spec: v1.PersistentVolumeSpec{ 854 Capacity: v1.ResourceList{ 855 v1.ResourceName(v1.ResourceStorage): resource.MustParse("200G"), 856 }, 857 PersistentVolumeSource: v1.PersistentVolumeSource{ 858 Local: &v1.LocalVolumeSource{}, 859 }, 860 AccessModes: []v1.PersistentVolumeAccessMode{ 861 v1.ReadWriteOnce, 862 v1.ReadOnlyMany, 863 }, 864 StorageClassName: classWait, 865 NodeAffinity: createNodeAffinity("key1", "value3"), 866 VolumeMode: &fs, 867 }, 868 Status: v1.PersistentVolumeStatus{ 869 Phase: v1.VolumeAvailable, 870 }, 871 }, 872 { 873 ObjectMeta: metav1.ObjectMeta{ 874 UID: "affinity-pv4-pending", 875 Name: "affinity004-pending", 876 }, 877 Spec: v1.PersistentVolumeSpec{ 878 Capacity: v1.ResourceList{ 879 v1.ResourceName(v1.ResourceStorage): resource.MustParse("200G"), 880 }, 881 PersistentVolumeSource: v1.PersistentVolumeSource{ 882 Local: &v1.LocalVolumeSource{}, 883 }, 884 AccessModes: []v1.PersistentVolumeAccessMode{ 885 v1.ReadWriteOnce, 886 v1.ReadOnlyMany, 887 }, 888 StorageClassName: classWait, 889 NodeAffinity: createNodeAffinity("key1", "value4"), 890 VolumeMode: &fs, 891 }, 892 Status: v1.PersistentVolumeStatus{ 893 Phase: v1.VolumePending, 894 }, 895 }, 896 { 897 ObjectMeta: metav1.ObjectMeta{ 898 UID: "affinity-pv4-failed", 899 Name: "affinity004-failed", 900 }, 901 Spec: v1.PersistentVolumeSpec{ 902 Capacity: v1.ResourceList{ 903 v1.ResourceName(v1.ResourceStorage): resource.MustParse("200G"), 904 }, 905 PersistentVolumeSource: v1.PersistentVolumeSource{ 906 Local: &v1.LocalVolumeSource{}, 907 }, 908 AccessModes: []v1.PersistentVolumeAccessMode{ 909 v1.ReadWriteOnce, 910 v1.ReadOnlyMany, 911 }, 912 StorageClassName: classWait, 913 NodeAffinity: createNodeAffinity("key1", "value4"), 914 VolumeMode: &fs, 915 }, 916 Status: v1.PersistentVolumeStatus{ 917 Phase: v1.VolumeFailed, 918 }, 919 }, 920 { 921 ObjectMeta: metav1.ObjectMeta{ 922 UID: "affinity-pv4-released", 923 Name: "affinity004-released", 924 }, 925 Spec: v1.PersistentVolumeSpec{ 926 Capacity: v1.ResourceList{ 927 v1.ResourceName(v1.ResourceStorage): resource.MustParse("200G"), 928 }, 929 PersistentVolumeSource: v1.PersistentVolumeSource{ 930 Local: &v1.LocalVolumeSource{}, 931 }, 932 AccessModes: []v1.PersistentVolumeAccessMode{ 933 v1.ReadWriteOnce, 934 v1.ReadOnlyMany, 935 }, 936 StorageClassName: classWait, 937 NodeAffinity: createNodeAffinity("key1", "value4"), 938 VolumeMode: &fs, 939 }, 940 Status: v1.PersistentVolumeStatus{ 941 Phase: v1.VolumeReleased, 942 }, 943 }, 944 { 945 ObjectMeta: metav1.ObjectMeta{ 946 UID: "affinity-pv4-empty", 947 Name: "affinity004-empty", 948 }, 949 Spec: v1.PersistentVolumeSpec{ 950 Capacity: v1.ResourceList{ 951 v1.ResourceName(v1.ResourceStorage): resource.MustParse("200G"), 952 }, 953 PersistentVolumeSource: v1.PersistentVolumeSource{ 954 Local: &v1.LocalVolumeSource{}, 955 }, 956 AccessModes: []v1.PersistentVolumeAccessMode{ 957 v1.ReadWriteOnce, 958 v1.ReadOnlyMany, 959 }, 960 StorageClassName: classWait, 961 NodeAffinity: createNodeAffinity("key1", "value4"), 962 VolumeMode: &fs, 963 }, 964 }, 965 } 966 } 967 968 func testVolume(name, size string) *v1.PersistentVolume { 969 fs := v1.PersistentVolumeFilesystem 970 return &v1.PersistentVolume{ 971 ObjectMeta: metav1.ObjectMeta{ 972 Name: name, 973 Annotations: map[string]string{}, 974 }, 975 Spec: v1.PersistentVolumeSpec{ 976 Capacity: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse(size)}, 977 PersistentVolumeSource: v1.PersistentVolumeSource{HostPath: &v1.HostPathVolumeSource{}}, 978 AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}, 979 VolumeMode: &fs, 980 }, 981 Status: v1.PersistentVolumeStatus{ 982 Phase: v1.VolumeAvailable, 983 }, 984 } 985 } 986 987 func createVolumeModeBlockTestVolume() *v1.PersistentVolume { 988 blockMode := v1.PersistentVolumeBlock 989 990 return &v1.PersistentVolume{ 991 ObjectMeta: metav1.ObjectMeta{ 992 UID: "local-1", 993 Name: "block", 994 }, 995 Spec: v1.PersistentVolumeSpec{ 996 Capacity: v1.ResourceList{ 997 v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G"), 998 }, 999 PersistentVolumeSource: v1.PersistentVolumeSource{ 1000 Local: &v1.LocalVolumeSource{}, 1001 }, 1002 AccessModes: []v1.PersistentVolumeAccessMode{ 1003 v1.ReadWriteOnce, 1004 }, 1005 VolumeMode: &blockMode, 1006 }, 1007 Status: v1.PersistentVolumeStatus{ 1008 Phase: v1.VolumeAvailable, 1009 }, 1010 } 1011 } 1012 1013 func createVolumeModeFilesystemTestVolume() *v1.PersistentVolume { 1014 filesystemMode := v1.PersistentVolumeFilesystem 1015 1016 return &v1.PersistentVolume{ 1017 ObjectMeta: metav1.ObjectMeta{ 1018 UID: "local-1", 1019 Name: "block", 1020 }, 1021 Spec: v1.PersistentVolumeSpec{ 1022 Capacity: v1.ResourceList{ 1023 v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G"), 1024 }, 1025 PersistentVolumeSource: v1.PersistentVolumeSource{ 1026 Local: &v1.LocalVolumeSource{}, 1027 }, 1028 AccessModes: []v1.PersistentVolumeAccessMode{ 1029 v1.ReadWriteOnce, 1030 }, 1031 VolumeMode: &filesystemMode, 1032 }, 1033 Status: v1.PersistentVolumeStatus{ 1034 Phase: v1.VolumeAvailable, 1035 }, 1036 } 1037 } 1038 1039 func createVolumeModeNilTestVolume() *v1.PersistentVolume { 1040 return &v1.PersistentVolume{ 1041 ObjectMeta: metav1.ObjectMeta{ 1042 UID: "local-1", 1043 Name: "nil-mode", 1044 }, 1045 Spec: v1.PersistentVolumeSpec{ 1046 Capacity: v1.ResourceList{ 1047 v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G"), 1048 }, 1049 PersistentVolumeSource: v1.PersistentVolumeSource{ 1050 Local: &v1.LocalVolumeSource{}, 1051 }, 1052 AccessModes: []v1.PersistentVolumeAccessMode{ 1053 v1.ReadWriteOnce, 1054 }, 1055 }, 1056 Status: v1.PersistentVolumeStatus{ 1057 Phase: v1.VolumeAvailable, 1058 }, 1059 } 1060 } 1061 1062 func createTestVolOrderedIndex(pv *v1.PersistentVolume) persistentVolumeOrderedIndex { 1063 volFile := newPersistentVolumeOrderedIndex() 1064 volFile.store.Add(pv) 1065 return volFile 1066 } 1067 1068 func TestVolumeModeCheck(t *testing.T) { 1069 1070 blockMode := v1.PersistentVolumeBlock 1071 filesystemMode := v1.PersistentVolumeFilesystem 1072 1073 // If feature gate is enabled, VolumeMode will always be defaulted 1074 // If feature gate is disabled, VolumeMode is dropped by API and ignored 1075 scenarios := map[string]struct { 1076 isExpectedMismatch bool 1077 vol *v1.PersistentVolume 1078 pvc *v1.PersistentVolumeClaim 1079 }{ 1080 "pvc block and pv filesystem": { 1081 isExpectedMismatch: true, 1082 vol: createVolumeModeFilesystemTestVolume(), 1083 pvc: makeVolumeModePVC("8G", &blockMode, nil), 1084 }, 1085 "pvc filesystem and pv block": { 1086 isExpectedMismatch: true, 1087 vol: createVolumeModeBlockTestVolume(), 1088 pvc: makeVolumeModePVC("8G", &filesystemMode, nil), 1089 }, 1090 "pvc block and pv block": { 1091 isExpectedMismatch: false, 1092 vol: createVolumeModeBlockTestVolume(), 1093 pvc: makeVolumeModePVC("8G", &blockMode, nil), 1094 }, 1095 "pvc filesystem and pv filesystem": { 1096 isExpectedMismatch: false, 1097 vol: createVolumeModeFilesystemTestVolume(), 1098 pvc: makeVolumeModePVC("8G", &filesystemMode, nil), 1099 }, 1100 "pvc filesystem and pv nil": { 1101 isExpectedMismatch: false, 1102 vol: createVolumeModeNilTestVolume(), 1103 pvc: makeVolumeModePVC("8G", &filesystemMode, nil), 1104 }, 1105 "pvc nil and pv filesystem": { 1106 isExpectedMismatch: false, 1107 vol: createVolumeModeFilesystemTestVolume(), 1108 pvc: makeVolumeModePVC("8G", nil, nil), 1109 }, 1110 "pvc nil and pv nil": { 1111 isExpectedMismatch: false, 1112 vol: createVolumeModeNilTestVolume(), 1113 pvc: makeVolumeModePVC("8G", nil, nil), 1114 }, 1115 "pvc nil and pv block": { 1116 isExpectedMismatch: true, 1117 vol: createVolumeModeBlockTestVolume(), 1118 pvc: makeVolumeModePVC("8G", nil, nil), 1119 }, 1120 "pvc block and pv nil": { 1121 isExpectedMismatch: true, 1122 vol: createVolumeModeNilTestVolume(), 1123 pvc: makeVolumeModePVC("8G", &blockMode, nil), 1124 }, 1125 } 1126 1127 for name, scenario := range scenarios { 1128 t.Run(name, func(t *testing.T) { 1129 expectedMismatch := volume.CheckVolumeModeMismatches(&scenario.pvc.Spec, &scenario.vol.Spec) 1130 // expected to match but either got an error or no returned pvmatch 1131 if expectedMismatch && !scenario.isExpectedMismatch { 1132 t.Errorf("Unexpected failure for scenario, expected not to mismatch on modes but did: %s", name) 1133 } 1134 if !expectedMismatch && scenario.isExpectedMismatch { 1135 t.Errorf("Unexpected failure for scenario, did not mismatch on mode when expected to mismatch: %s", name) 1136 } 1137 }) 1138 } 1139 } 1140 1141 func TestFilteringVolumeModes(t *testing.T) { 1142 blockMode := v1.PersistentVolumeBlock 1143 filesystemMode := v1.PersistentVolumeFilesystem 1144 1145 // If feature gate is enabled, VolumeMode will always be defaulted 1146 // If feature gate is disabled, VolumeMode is dropped by API and ignored 1147 scenarios := map[string]struct { 1148 isExpectedMatch bool 1149 vol persistentVolumeOrderedIndex 1150 pvc *v1.PersistentVolumeClaim 1151 }{ 1152 "pvc block and pv filesystem": { 1153 isExpectedMatch: false, 1154 vol: createTestVolOrderedIndex(createVolumeModeFilesystemTestVolume()), 1155 pvc: makeVolumeModePVC("8G", &blockMode, nil), 1156 }, 1157 "pvc filesystem and pv block": { 1158 isExpectedMatch: false, 1159 vol: createTestVolOrderedIndex(createVolumeModeBlockTestVolume()), 1160 pvc: makeVolumeModePVC("8G", &filesystemMode, nil), 1161 }, 1162 "pvc block and pv no mode with default filesystem": { 1163 isExpectedMatch: false, 1164 vol: createTestVolOrderedIndex(createVolumeModeFilesystemTestVolume()), 1165 pvc: makeVolumeModePVC("8G", &blockMode, nil), 1166 }, 1167 "pvc no mode defaulted to filesystem and pv block": { 1168 isExpectedMatch: false, 1169 vol: createTestVolOrderedIndex(createVolumeModeBlockTestVolume()), 1170 pvc: makeVolumeModePVC("8G", &filesystemMode, nil), 1171 }, 1172 "pvc block and pv block": { 1173 isExpectedMatch: true, 1174 vol: createTestVolOrderedIndex(createVolumeModeBlockTestVolume()), 1175 pvc: makeVolumeModePVC("8G", &blockMode, nil), 1176 }, 1177 "pvc filesystem and pv filesystem": { 1178 isExpectedMatch: true, 1179 vol: createTestVolOrderedIndex(createVolumeModeFilesystemTestVolume()), 1180 pvc: makeVolumeModePVC("8G", &filesystemMode, nil), 1181 }, 1182 "pvc mode is nil and defaulted and pv mode is nil and defaulted": { 1183 isExpectedMatch: true, 1184 vol: createTestVolOrderedIndex(createVolumeModeFilesystemTestVolume()), 1185 pvc: makeVolumeModePVC("8G", &filesystemMode, nil), 1186 }, 1187 } 1188 1189 for name, scenario := range scenarios { 1190 t.Run(name, func(t *testing.T) { 1191 pvmatch, err := scenario.vol.findBestMatchForClaim(scenario.pvc, false) 1192 // expected to match but either got an error or no returned pvmatch 1193 if pvmatch == nil && scenario.isExpectedMatch { 1194 t.Errorf("Unexpected failure for scenario, no matching volume: %s", name) 1195 } 1196 if err != nil && scenario.isExpectedMatch { 1197 t.Errorf("Unexpected failure for scenario: %s - %+v", name, err) 1198 } 1199 // expected to not match but either got an error or a returned pvmatch 1200 if pvmatch != nil && !scenario.isExpectedMatch { 1201 t.Errorf("Unexpected failure for scenario, expected no matching volume: %s", name) 1202 } 1203 if err != nil && !scenario.isExpectedMatch { 1204 t.Errorf("Unexpected failure for scenario: %s - %+v", name, err) 1205 } 1206 }) 1207 } 1208 } 1209 1210 func TestStorageObjectInUseProtectionFiltering(t *testing.T) { 1211 fs := v1.PersistentVolumeFilesystem 1212 pv := &v1.PersistentVolume{ 1213 ObjectMeta: metav1.ObjectMeta{ 1214 Name: "pv1", 1215 Annotations: map[string]string{}, 1216 }, 1217 Spec: v1.PersistentVolumeSpec{ 1218 Capacity: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G")}, 1219 PersistentVolumeSource: v1.PersistentVolumeSource{HostPath: &v1.HostPathVolumeSource{}}, 1220 AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}, 1221 VolumeMode: &fs, 1222 }, 1223 Status: v1.PersistentVolumeStatus{ 1224 Phase: v1.VolumeAvailable, 1225 }, 1226 } 1227 1228 pvToDelete := pv.DeepCopy() 1229 now := metav1.Now() 1230 pvToDelete.ObjectMeta.DeletionTimestamp = &now 1231 1232 pvc := &v1.PersistentVolumeClaim{ 1233 ObjectMeta: metav1.ObjectMeta{ 1234 Name: "pvc1", 1235 Namespace: "myns", 1236 }, 1237 Spec: v1.PersistentVolumeClaimSpec{ 1238 AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}, 1239 Resources: v1.VolumeResourceRequirements{Requests: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G")}}, 1240 VolumeMode: &fs, 1241 }, 1242 } 1243 1244 satisfyingTestCases := map[string]struct { 1245 isExpectedMatch bool 1246 vol *v1.PersistentVolume 1247 pvc *v1.PersistentVolumeClaim 1248 }{ 1249 "pv deletionTimeStamp not set": { 1250 isExpectedMatch: true, 1251 vol: pv, 1252 pvc: pvc, 1253 }, 1254 "pv deletionTimeStamp set": { 1255 isExpectedMatch: false, 1256 vol: pvToDelete, 1257 pvc: pvc, 1258 }, 1259 } 1260 1261 for name, testCase := range satisfyingTestCases { 1262 t.Run(name, func(t *testing.T) { 1263 err := checkVolumeSatisfyClaim(testCase.vol, testCase.pvc) 1264 // expected to match but got an error 1265 if err != nil && testCase.isExpectedMatch { 1266 t.Errorf("%s: expected to match but got an error: %v", name, err) 1267 } 1268 // not expected to match but did 1269 if err == nil && !testCase.isExpectedMatch { 1270 t.Errorf("%s: not expected to match but did", name) 1271 } 1272 }) 1273 } 1274 1275 filteringTestCases := map[string]struct { 1276 isExpectedMatch bool 1277 vol persistentVolumeOrderedIndex 1278 pvc *v1.PersistentVolumeClaim 1279 }{ 1280 "pv deletionTimeStamp not set": { 1281 isExpectedMatch: true, 1282 vol: createTestVolOrderedIndex(pv), 1283 pvc: pvc, 1284 }, 1285 "pv deletionTimeStamp set": { 1286 isExpectedMatch: false, 1287 vol: createTestVolOrderedIndex(pvToDelete), 1288 pvc: pvc, 1289 }, 1290 } 1291 for name, testCase := range filteringTestCases { 1292 t.Run(name, func(t *testing.T) { 1293 pvmatch, err := testCase.vol.findBestMatchForClaim(testCase.pvc, false) 1294 // expected to match but either got an error or no returned pvmatch 1295 if pvmatch == nil && testCase.isExpectedMatch { 1296 t.Errorf("Unexpected failure for testcase, no matching volume: %s", name) 1297 } 1298 if err != nil && testCase.isExpectedMatch { 1299 t.Errorf("Unexpected failure for testcase: %s - %+v", name, err) 1300 } 1301 // expected to not match but either got an error or a returned pvmatch 1302 if pvmatch != nil && !testCase.isExpectedMatch { 1303 t.Errorf("Unexpected failure for testcase, expected no matching volume: %s", name) 1304 } 1305 if err != nil && !testCase.isExpectedMatch { 1306 t.Errorf("Unexpected failure for testcase: %s - %+v", name, err) 1307 } 1308 }) 1309 } 1310 } 1311 1312 func TestFindingPreboundVolumes(t *testing.T) { 1313 fs := v1.PersistentVolumeFilesystem 1314 claim := &v1.PersistentVolumeClaim{ 1315 ObjectMeta: metav1.ObjectMeta{ 1316 Name: "claim01", 1317 Namespace: "myns", 1318 }, 1319 Spec: v1.PersistentVolumeClaimSpec{ 1320 AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}, 1321 Resources: v1.VolumeResourceRequirements{Requests: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("1Gi")}}, 1322 VolumeMode: &fs, 1323 }, 1324 } 1325 claimRef, err := ref.GetReference(scheme.Scheme, claim) 1326 if err != nil { 1327 t.Errorf("error getting claimRef: %v", err) 1328 } 1329 1330 pv1 := testVolume("pv1", "1Gi") 1331 pv5 := testVolume("pv5", "5Gi") 1332 pv8 := testVolume("pv8", "8Gi") 1333 pvBadSize := testVolume("pvBadSize", "1Mi") 1334 pvBadMode := testVolume("pvBadMode", "1Gi") 1335 pvBadMode.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany} 1336 1337 index := newPersistentVolumeOrderedIndex() 1338 index.store.Add(pv1) 1339 index.store.Add(pv5) 1340 index.store.Add(pv8) 1341 index.store.Add(pvBadSize) 1342 index.store.Add(pvBadMode) 1343 1344 // expected exact match on size 1345 volume, _ := index.findBestMatchForClaim(claim, false) 1346 if volume.Name != pv1.Name { 1347 t.Errorf("Expected %s but got volume %s instead", pv1.Name, volume.Name) 1348 } 1349 1350 // pretend the exact match is pre-bound. should get the next size up. 1351 pv1.Spec.ClaimRef = &v1.ObjectReference{Name: "foo", Namespace: "bar"} 1352 volume, _ = index.findBestMatchForClaim(claim, false) 1353 if volume.Name != pv5.Name { 1354 t.Errorf("Expected %s but got volume %s instead", pv5.Name, volume.Name) 1355 } 1356 1357 // pretend the exact match is available but the largest volume is pre-bound to the claim. 1358 pv1.Spec.ClaimRef = nil 1359 pv8.Spec.ClaimRef = claimRef 1360 volume, _ = index.findBestMatchForClaim(claim, false) 1361 if volume.Name != pv8.Name { 1362 t.Errorf("Expected %s but got volume %s instead", pv8.Name, volume.Name) 1363 } 1364 1365 // pretend the volume with too small a size is pre-bound to the claim. should get the exact match. 1366 pv8.Spec.ClaimRef = nil 1367 pvBadSize.Spec.ClaimRef = claimRef 1368 volume, _ = index.findBestMatchForClaim(claim, false) 1369 if volume.Name != pv1.Name { 1370 t.Errorf("Expected %s but got volume %s instead", pv1.Name, volume.Name) 1371 } 1372 1373 // pretend the volume without the right access mode is pre-bound to the claim. should get the exact match. 1374 pvBadSize.Spec.ClaimRef = nil 1375 pvBadMode.Spec.ClaimRef = claimRef 1376 volume, _ = index.findBestMatchForClaim(claim, false) 1377 if volume.Name != pv1.Name { 1378 t.Errorf("Expected %s but got volume %s instead", pv1.Name, volume.Name) 1379 } 1380 } 1381 1382 func TestBestMatchDelayed(t *testing.T) { 1383 volList := newPersistentVolumeOrderedIndex() 1384 for _, pv := range createTestVolumes() { 1385 volList.store.Add(pv) 1386 } 1387 1388 // binding through PV controller should be delayed 1389 claim := makePVC("8G", nil) 1390 volume, err := volList.findBestMatchForClaim(claim, true) 1391 if err != nil { 1392 t.Errorf("Unexpected error matching volume by claim: %v", err) 1393 } 1394 if volume != nil { 1395 t.Errorf("Unexpected match with %q", volume.UID) 1396 } 1397 } 1398 1399 func TestCheckAccessModes(t *testing.T) { 1400 pv := &v1.PersistentVolume{ 1401 Spec: v1.PersistentVolumeSpec{ 1402 AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce, v1.ReadWriteMany}, 1403 }, 1404 } 1405 1406 scenarios := map[string]struct { 1407 shouldSucceed bool 1408 claim *v1.PersistentVolumeClaim 1409 }{ 1410 "success-single-mode": { 1411 shouldSucceed: true, 1412 claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) { 1413 pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteMany} 1414 }), 1415 }, 1416 "success-many-modes": { 1417 shouldSucceed: true, 1418 claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) { 1419 pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteMany, v1.ReadWriteOnce} 1420 }), 1421 }, 1422 "fail-single-mode": { 1423 shouldSucceed: false, 1424 claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) { 1425 pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany} 1426 }), 1427 }, 1428 "fail-many-modes": { 1429 shouldSucceed: false, 1430 claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) { 1431 pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteMany, v1.ReadOnlyMany} 1432 }), 1433 }, 1434 } 1435 1436 for name, scenario := range scenarios { 1437 result := volume.CheckAccessModes(scenario.claim, pv) 1438 if result != scenario.shouldSucceed { 1439 t.Errorf("Test %q failed: Expected %v, got %v", name, scenario.shouldSucceed, result) 1440 } 1441 } 1442 } 1443 1444 // byCapacity is used to order volumes by ascending storage size 1445 type byCapacity struct { 1446 volumes []*v1.PersistentVolume 1447 } 1448 1449 func (c byCapacity) Less(i, j int) bool { 1450 return matchStorageCapacity(c.volumes[i], c.volumes[j]) 1451 } 1452 1453 func (c byCapacity) Swap(i, j int) { 1454 c.volumes[i], c.volumes[j] = c.volumes[j], c.volumes[i] 1455 } 1456 1457 func (c byCapacity) Len() int { 1458 return len(c.volumes) 1459 } 1460 1461 // matchStorageCapacity is a matchPredicate used to sort and find volumes 1462 func matchStorageCapacity(pvA, pvB *v1.PersistentVolume) bool { 1463 aQty := pvA.Spec.Capacity[v1.ResourceStorage] 1464 bQty := pvB.Spec.Capacity[v1.ResourceStorage] 1465 return aQty.Cmp(bQty) <= 0 1466 }