k8s.io/kubernetes@v1.29.3/pkg/apis/storage/validation/validation_test.go (about) 1 /* 2 Copyright 2016 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 validation 18 19 import ( 20 "fmt" 21 "strings" 22 "testing" 23 24 "k8s.io/apimachinery/pkg/api/resource" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 utilfeature "k8s.io/apiserver/pkg/util/feature" 27 featuregatetesting "k8s.io/component-base/featuregate/testing" 28 api "k8s.io/kubernetes/pkg/apis/core" 29 "k8s.io/kubernetes/pkg/apis/storage" 30 "k8s.io/kubernetes/pkg/features" 31 utilpointer "k8s.io/utils/pointer" 32 ) 33 34 var ( 35 deleteReclaimPolicy = api.PersistentVolumeReclaimDelete 36 immediateMode1 = storage.VolumeBindingImmediate 37 immediateMode2 = storage.VolumeBindingImmediate 38 waitingMode = storage.VolumeBindingWaitForFirstConsumer 39 invalidMode = storage.VolumeBindingMode("foo") 40 inlineSpec = api.PersistentVolumeSpec{ 41 AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, 42 PersistentVolumeSource: api.PersistentVolumeSource{ 43 CSI: &api.CSIPersistentVolumeSource{ 44 Driver: "com.test.foo", 45 VolumeHandle: "foobar", 46 }, 47 }, 48 } 49 longerIDValidateOption = CSINodeValidationOptions{ 50 AllowLongNodeID: true, 51 } 52 shorterIDValidationOption = CSINodeValidationOptions{ 53 AllowLongNodeID: false, 54 } 55 ) 56 57 func TestValidateStorageClass(t *testing.T) { 58 deleteReclaimPolicy := api.PersistentVolumeReclaimPolicy("Delete") 59 retainReclaimPolicy := api.PersistentVolumeReclaimPolicy("Retain") 60 recycleReclaimPolicy := api.PersistentVolumeReclaimPolicy("Recycle") 61 successCases := []storage.StorageClass{{ 62 // empty parameters 63 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 64 Provisioner: "kubernetes.io/foo-provisioner", 65 Parameters: map[string]string{}, 66 ReclaimPolicy: &deleteReclaimPolicy, 67 VolumeBindingMode: &immediateMode1, 68 }, { 69 // nil parameters 70 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 71 Provisioner: "kubernetes.io/foo-provisioner", 72 ReclaimPolicy: &deleteReclaimPolicy, 73 VolumeBindingMode: &immediateMode1, 74 }, { 75 // some parameters 76 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 77 Provisioner: "kubernetes.io/foo-provisioner", 78 Parameters: map[string]string{ 79 "kubernetes.io/foo-parameter": "free/form/string", 80 "foo-parameter": "free-form-string", 81 "foo-parameter2": "{\"embedded\": \"json\", \"with\": {\"structures\":\"inside\"}}", 82 }, 83 ReclaimPolicy: &deleteReclaimPolicy, 84 VolumeBindingMode: &immediateMode1, 85 }, { 86 // retain reclaimPolicy 87 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 88 Provisioner: "kubernetes.io/foo-provisioner", 89 ReclaimPolicy: &retainReclaimPolicy, 90 VolumeBindingMode: &immediateMode1, 91 }} 92 93 // Success cases are expected to pass validation. 94 for k, v := range successCases { 95 if errs := ValidateStorageClass(&v); len(errs) != 0 { 96 t.Errorf("Expected success for %d, got %v", k, errs) 97 } 98 } 99 100 // generate a map longer than maxProvisionerParameterSize 101 longParameters := make(map[string]string) 102 totalSize := 0 103 for totalSize < maxProvisionerParameterSize { 104 k := fmt.Sprintf("param/%d", totalSize) 105 v := fmt.Sprintf("value-%d", totalSize) 106 longParameters[k] = v 107 totalSize = totalSize + len(k) + len(v) 108 } 109 110 errorCases := map[string]storage.StorageClass{ 111 "namespace is present": { 112 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, 113 Provisioner: "kubernetes.io/foo-provisioner", 114 ReclaimPolicy: &deleteReclaimPolicy, 115 }, 116 "invalid provisioner": { 117 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 118 Provisioner: "kubernetes.io/invalid/provisioner", 119 ReclaimPolicy: &deleteReclaimPolicy, 120 }, 121 "invalid empty parameter name": { 122 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 123 Provisioner: "kubernetes.io/foo", 124 Parameters: map[string]string{ 125 "": "value", 126 }, 127 ReclaimPolicy: &deleteReclaimPolicy, 128 }, 129 "provisioner: Required value": { 130 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 131 Provisioner: "", 132 ReclaimPolicy: &deleteReclaimPolicy, 133 }, 134 "too long parameters": { 135 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 136 Provisioner: "kubernetes.io/foo", 137 Parameters: longParameters, 138 ReclaimPolicy: &deleteReclaimPolicy, 139 }, 140 "invalid reclaimpolicy": { 141 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 142 Provisioner: "kubernetes.io/foo", 143 ReclaimPolicy: &recycleReclaimPolicy, 144 }, 145 } 146 147 // Error cases are not expected to pass validation. 148 for testName, storageClass := range errorCases { 149 if errs := ValidateStorageClass(&storageClass); len(errs) == 0 { 150 t.Errorf("Expected failure for test: %s", testName) 151 } 152 } 153 } 154 155 func TestVolumeAttachmentValidation(t *testing.T) { 156 volumeName := "pv-name" 157 empty := "" 158 migrationEnabledSuccessCases := []storage.VolumeAttachment{{ 159 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 160 Spec: storage.VolumeAttachmentSpec{ 161 Attacher: "myattacher", 162 Source: storage.VolumeAttachmentSource{ 163 PersistentVolumeName: &volumeName, 164 }, 165 NodeName: "mynode", 166 }, 167 }, { 168 ObjectMeta: metav1.ObjectMeta{Name: "foo-with-inlinespec"}, 169 Spec: storage.VolumeAttachmentSpec{ 170 Attacher: "myattacher", 171 Source: storage.VolumeAttachmentSource{ 172 InlineVolumeSpec: &inlineSpec, 173 }, 174 NodeName: "mynode", 175 }, 176 }, { 177 ObjectMeta: metav1.ObjectMeta{Name: "foo-with-status"}, 178 Spec: storage.VolumeAttachmentSpec{ 179 Attacher: "myattacher", 180 Source: storage.VolumeAttachmentSource{ 181 PersistentVolumeName: &volumeName, 182 }, 183 NodeName: "mynode", 184 }, 185 Status: storage.VolumeAttachmentStatus{ 186 Attached: true, 187 AttachmentMetadata: map[string]string{ 188 "foo": "bar", 189 }, 190 AttachError: &storage.VolumeError{ 191 Time: metav1.Time{}, 192 Message: "hello world", 193 }, 194 DetachError: &storage.VolumeError{ 195 Time: metav1.Time{}, 196 Message: "hello world", 197 }, 198 }, 199 }, { 200 ObjectMeta: metav1.ObjectMeta{Name: "foo-with-inlinespec-and-status"}, 201 Spec: storage.VolumeAttachmentSpec{ 202 Attacher: "myattacher", 203 Source: storage.VolumeAttachmentSource{ 204 InlineVolumeSpec: &inlineSpec, 205 }, 206 NodeName: "mynode", 207 }, 208 Status: storage.VolumeAttachmentStatus{ 209 Attached: true, 210 AttachmentMetadata: map[string]string{ 211 "foo": "bar", 212 }, 213 AttachError: &storage.VolumeError{ 214 Time: metav1.Time{}, 215 Message: "hello world", 216 }, 217 DetachError: &storage.VolumeError{ 218 Time: metav1.Time{}, 219 Message: "hello world", 220 }, 221 }, 222 }} 223 224 for _, volumeAttachment := range migrationEnabledSuccessCases { 225 if errs := ValidateVolumeAttachment(&volumeAttachment); len(errs) != 0 { 226 t.Errorf("expected success: %v %v", volumeAttachment, errs) 227 } 228 } 229 migrationEnabledErrorCases := []storage.VolumeAttachment{{ 230 // Empty attacher name 231 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 232 Spec: storage.VolumeAttachmentSpec{ 233 Attacher: "", 234 NodeName: "mynode", 235 Source: storage.VolumeAttachmentSource{ 236 PersistentVolumeName: &volumeName, 237 }, 238 }, 239 }, { 240 // Empty node name 241 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 242 Spec: storage.VolumeAttachmentSpec{ 243 Attacher: "myattacher", 244 NodeName: "", 245 Source: storage.VolumeAttachmentSource{ 246 PersistentVolumeName: &volumeName, 247 }, 248 }, 249 }, { 250 // No volume name 251 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 252 Spec: storage.VolumeAttachmentSpec{ 253 Attacher: "myattacher", 254 NodeName: "node", 255 Source: storage.VolumeAttachmentSource{ 256 PersistentVolumeName: nil, 257 }, 258 }, 259 }, { 260 // Empty volume name 261 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 262 Spec: storage.VolumeAttachmentSpec{ 263 Attacher: "myattacher", 264 NodeName: "node", 265 Source: storage.VolumeAttachmentSource{ 266 PersistentVolumeName: &empty, 267 }, 268 }, 269 }, { 270 // Too long error message 271 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 272 Spec: storage.VolumeAttachmentSpec{ 273 Attacher: "myattacher", 274 NodeName: "node", 275 Source: storage.VolumeAttachmentSource{ 276 PersistentVolumeName: &volumeName, 277 }, 278 }, 279 Status: storage.VolumeAttachmentStatus{ 280 Attached: true, 281 AttachmentMetadata: map[string]string{ 282 "foo": "bar", 283 }, 284 AttachError: &storage.VolumeError{ 285 Time: metav1.Time{}, 286 Message: "hello world", 287 }, 288 DetachError: &storage.VolumeError{ 289 Time: metav1.Time{}, 290 Message: strings.Repeat("a", maxVolumeErrorMessageSize+1), 291 }, 292 }, 293 }, { 294 // Too long metadata 295 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 296 Spec: storage.VolumeAttachmentSpec{ 297 Attacher: "myattacher", 298 NodeName: "node", 299 Source: storage.VolumeAttachmentSource{ 300 PersistentVolumeName: &volumeName, 301 }, 302 }, 303 Status: storage.VolumeAttachmentStatus{ 304 Attached: true, 305 AttachmentMetadata: map[string]string{ 306 "foo": strings.Repeat("a", maxAttachedVolumeMetadataSize), 307 }, 308 AttachError: &storage.VolumeError{ 309 Time: metav1.Time{}, 310 Message: "hello world", 311 }, 312 DetachError: &storage.VolumeError{ 313 Time: metav1.Time{}, 314 Message: "hello world", 315 }, 316 }, 317 }, { 318 // VolumeAttachmentSource with no PersistentVolumeName nor InlineSpec 319 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 320 Spec: storage.VolumeAttachmentSpec{ 321 Attacher: "myattacher", 322 NodeName: "node", 323 Source: storage.VolumeAttachmentSource{}, 324 }, 325 }, { 326 // VolumeAttachmentSource with PersistentVolumeName and InlineSpec 327 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 328 Spec: storage.VolumeAttachmentSpec{ 329 Attacher: "myattacher", 330 NodeName: "node", 331 Source: storage.VolumeAttachmentSource{ 332 PersistentVolumeName: &volumeName, 333 InlineVolumeSpec: &inlineSpec, 334 }, 335 }, 336 }, { 337 // VolumeAttachmentSource with InlineSpec without CSI PV Source 338 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 339 Spec: storage.VolumeAttachmentSpec{ 340 Attacher: "myattacher", 341 NodeName: "node", 342 Source: storage.VolumeAttachmentSource{ 343 PersistentVolumeName: &volumeName, 344 InlineVolumeSpec: &api.PersistentVolumeSpec{ 345 Capacity: api.ResourceList{ 346 api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), 347 }, 348 AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, 349 PersistentVolumeSource: api.PersistentVolumeSource{ 350 FlexVolume: &api.FlexPersistentVolumeSource{ 351 Driver: "kubernetes.io/blue", 352 FSType: "ext4", 353 }, 354 }, 355 StorageClassName: "test-storage-class", 356 }, 357 }, 358 }, 359 }} 360 361 for _, volumeAttachment := range migrationEnabledErrorCases { 362 if errs := ValidateVolumeAttachment(&volumeAttachment); len(errs) == 0 { 363 t.Errorf("expected failure for test: %v", volumeAttachment) 364 } 365 } 366 } 367 368 func TestVolumeAttachmentUpdateValidation(t *testing.T) { 369 volumeName := "foo" 370 newVolumeName := "bar" 371 372 old := storage.VolumeAttachment{ 373 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 374 Spec: storage.VolumeAttachmentSpec{ 375 Attacher: "myattacher", 376 Source: storage.VolumeAttachmentSource{}, 377 NodeName: "mynode", 378 }, 379 } 380 381 successCases := []storage.VolumeAttachment{{ 382 // no change 383 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 384 Spec: storage.VolumeAttachmentSpec{ 385 Attacher: "myattacher", 386 Source: storage.VolumeAttachmentSource{}, 387 NodeName: "mynode", 388 }, 389 }, { 390 // modify status 391 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 392 Spec: storage.VolumeAttachmentSpec{ 393 Attacher: "myattacher", 394 Source: storage.VolumeAttachmentSource{}, 395 NodeName: "mynode", 396 }, 397 Status: storage.VolumeAttachmentStatus{ 398 Attached: true, 399 AttachmentMetadata: map[string]string{ 400 "foo": "bar", 401 }, 402 AttachError: &storage.VolumeError{ 403 Time: metav1.Time{}, 404 Message: "hello world", 405 }, 406 DetachError: &storage.VolumeError{ 407 Time: metav1.Time{}, 408 Message: "hello world", 409 }, 410 }, 411 }} 412 413 for _, volumeAttachment := range successCases { 414 volumeAttachment.Spec.Source = storage.VolumeAttachmentSource{} 415 old.Spec.Source = storage.VolumeAttachmentSource{} 416 // test scenarios with PersistentVolumeName set 417 volumeAttachment.Spec.Source.PersistentVolumeName = &volumeName 418 old.Spec.Source.PersistentVolumeName = &volumeName 419 if errs := ValidateVolumeAttachmentUpdate(&volumeAttachment, &old); len(errs) != 0 { 420 t.Errorf("expected success: %+v", errs) 421 } 422 423 volumeAttachment.Spec.Source = storage.VolumeAttachmentSource{} 424 old.Spec.Source = storage.VolumeAttachmentSource{} 425 // test scenarios with InlineVolumeSpec set 426 volumeAttachment.Spec.Source.InlineVolumeSpec = &inlineSpec 427 old.Spec.Source.InlineVolumeSpec = &inlineSpec 428 if errs := ValidateVolumeAttachmentUpdate(&volumeAttachment, &old); len(errs) != 0 { 429 t.Errorf("expected success: %+v", errs) 430 } 431 } 432 433 // reset old's source with volumeName in case it was left with something else by earlier tests 434 old.Spec.Source = storage.VolumeAttachmentSource{} 435 old.Spec.Source.PersistentVolumeName = &volumeName 436 437 errorCases := []storage.VolumeAttachment{{ 438 // change attacher 439 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 440 Spec: storage.VolumeAttachmentSpec{ 441 Attacher: "another-attacher", 442 Source: storage.VolumeAttachmentSource{ 443 PersistentVolumeName: &volumeName, 444 }, 445 NodeName: "mynode", 446 }, 447 }, { 448 // change source volume name 449 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 450 Spec: storage.VolumeAttachmentSpec{ 451 Attacher: "myattacher", 452 Source: storage.VolumeAttachmentSource{ 453 PersistentVolumeName: &newVolumeName, 454 }, 455 NodeName: "mynode", 456 }, 457 }, { 458 // change node 459 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 460 Spec: storage.VolumeAttachmentSpec{ 461 Attacher: "myattacher", 462 Source: storage.VolumeAttachmentSource{ 463 PersistentVolumeName: &volumeName, 464 }, 465 NodeName: "anothernode", 466 }, 467 }, { 468 // change source 469 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 470 Spec: storage.VolumeAttachmentSpec{ 471 Attacher: "myattacher", 472 Source: storage.VolumeAttachmentSource{ 473 InlineVolumeSpec: &inlineSpec, 474 }, 475 NodeName: "mynode", 476 }, 477 }, { 478 // add invalid status 479 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 480 Spec: storage.VolumeAttachmentSpec{ 481 Attacher: "myattacher", 482 Source: storage.VolumeAttachmentSource{ 483 PersistentVolumeName: &volumeName, 484 }, 485 NodeName: "mynode", 486 }, 487 Status: storage.VolumeAttachmentStatus{ 488 Attached: true, 489 AttachmentMetadata: map[string]string{ 490 "foo": "bar", 491 }, 492 AttachError: &storage.VolumeError{ 493 Time: metav1.Time{}, 494 Message: strings.Repeat("a", maxAttachedVolumeMetadataSize), 495 }, 496 DetachError: &storage.VolumeError{ 497 Time: metav1.Time{}, 498 Message: "hello world", 499 }, 500 }, 501 }} 502 503 for _, volumeAttachment := range errorCases { 504 if errs := ValidateVolumeAttachmentUpdate(&volumeAttachment, &old); len(errs) == 0 { 505 t.Errorf("Expected failure for test: %+v", volumeAttachment) 506 } 507 } 508 } 509 510 func TestVolumeAttachmentValidationV1(t *testing.T) { 511 volumeName := "pv-name" 512 invalidVolumeName := "-invalid-@#$%^&*()-" 513 successCases := []storage.VolumeAttachment{{ 514 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 515 Spec: storage.VolumeAttachmentSpec{ 516 Attacher: "myattacher", 517 Source: storage.VolumeAttachmentSource{ 518 PersistentVolumeName: &volumeName, 519 }, 520 NodeName: "mynode", 521 }, 522 }} 523 524 for _, volumeAttachment := range successCases { 525 if errs := ValidateVolumeAttachmentV1(&volumeAttachment); len(errs) != 0 { 526 t.Errorf("expected success: %+v", errs) 527 } 528 } 529 530 errorCases := []storage.VolumeAttachment{{ 531 // Invalid attacher name 532 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 533 Spec: storage.VolumeAttachmentSpec{ 534 Attacher: "invalid-@#$%^&*()", 535 NodeName: "mynode", 536 Source: storage.VolumeAttachmentSource{ 537 PersistentVolumeName: &volumeName, 538 }, 539 }, 540 }, { 541 // Invalid PV name 542 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 543 Spec: storage.VolumeAttachmentSpec{ 544 Attacher: "myattacher", 545 NodeName: "mynode", 546 Source: storage.VolumeAttachmentSource{ 547 PersistentVolumeName: &invalidVolumeName, 548 }, 549 }, 550 }} 551 552 for _, volumeAttachment := range errorCases { 553 if errs := ValidateVolumeAttachmentV1(&volumeAttachment); len(errs) == 0 { 554 t.Errorf("Expected failure for test: %+v", volumeAttachment) 555 } 556 } 557 } 558 559 func makeClass(mode *storage.VolumeBindingMode, topologies []api.TopologySelectorTerm) *storage.StorageClass { 560 return &storage.StorageClass{ 561 ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "foo"}, 562 Provisioner: "kubernetes.io/foo-provisioner", 563 ReclaimPolicy: &deleteReclaimPolicy, 564 VolumeBindingMode: mode, 565 AllowedTopologies: topologies, 566 } 567 } 568 569 type bindingTest struct { 570 class *storage.StorageClass 571 shouldSucceed bool 572 } 573 574 func TestValidateVolumeBindingMode(t *testing.T) { 575 cases := map[string]bindingTest{ 576 "no mode": { 577 class: makeClass(nil, nil), 578 shouldSucceed: false, 579 }, 580 "immediate mode": { 581 class: makeClass(&immediateMode1, nil), 582 shouldSucceed: true, 583 }, 584 "waiting mode": { 585 class: makeClass(&waitingMode, nil), 586 shouldSucceed: true, 587 }, 588 "invalid mode": { 589 class: makeClass(&invalidMode, nil), 590 shouldSucceed: false, 591 }, 592 } 593 594 for testName, testCase := range cases { 595 errs := ValidateStorageClass(testCase.class) 596 if testCase.shouldSucceed && len(errs) != 0 { 597 t.Errorf("Expected success for test %q, got %v", testName, errs) 598 } 599 if !testCase.shouldSucceed && len(errs) == 0 { 600 t.Errorf("Expected failure for test %q, got success", testName) 601 } 602 } 603 } 604 605 type updateTest struct { 606 oldClass *storage.StorageClass 607 newClass *storage.StorageClass 608 shouldSucceed bool 609 } 610 611 func TestValidateUpdateVolumeBindingMode(t *testing.T) { 612 noBinding := makeClass(nil, nil) 613 immediateBinding1 := makeClass(&immediateMode1, nil) 614 immediateBinding2 := makeClass(&immediateMode2, nil) 615 waitBinding := makeClass(&waitingMode, nil) 616 617 cases := map[string]updateTest{ 618 "old and new no mode": { 619 oldClass: noBinding, 620 newClass: noBinding, 621 shouldSucceed: true, 622 }, 623 "old and new same mode ptr": { 624 oldClass: immediateBinding1, 625 newClass: immediateBinding1, 626 shouldSucceed: true, 627 }, 628 "old and new same mode value": { 629 oldClass: immediateBinding1, 630 newClass: immediateBinding2, 631 shouldSucceed: true, 632 }, 633 "old no mode, new mode": { 634 oldClass: noBinding, 635 newClass: waitBinding, 636 shouldSucceed: false, 637 }, 638 "old mode, new no mode": { 639 oldClass: waitBinding, 640 newClass: noBinding, 641 shouldSucceed: false, 642 }, 643 "old and new different modes": { 644 oldClass: waitBinding, 645 newClass: immediateBinding1, 646 shouldSucceed: false, 647 }, 648 } 649 650 for testName, testCase := range cases { 651 errs := ValidateStorageClassUpdate(testCase.newClass, testCase.oldClass) 652 if testCase.shouldSucceed && len(errs) != 0 { 653 t.Errorf("Expected success for %v, got %v", testName, errs) 654 } 655 if !testCase.shouldSucceed && len(errs) == 0 { 656 t.Errorf("Expected failure for %v, got success", testName) 657 } 658 } 659 } 660 661 func TestValidateAllowedTopologies(t *testing.T) { 662 663 validTopology := []api.TopologySelectorTerm{{ 664 MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ 665 Key: "failure-domain.beta.kubernetes.io/zone", 666 Values: []string{"zone1"}, 667 }, { 668 Key: "kubernetes.io/hostname", 669 Values: []string{"node1"}, 670 }}, 671 }, { 672 MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ 673 Key: "failure-domain.beta.kubernetes.io/zone", 674 Values: []string{"zone2"}, 675 }, { 676 Key: "kubernetes.io/hostname", 677 Values: []string{"node2"}, 678 }}, 679 }} 680 681 topologyInvalidKey := []api.TopologySelectorTerm{{ 682 MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ 683 Key: "/invalidkey", 684 Values: []string{"zone1"}, 685 }}, 686 }} 687 688 topologyLackOfValues := []api.TopologySelectorTerm{{ 689 MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ 690 Key: "kubernetes.io/hostname", 691 Values: []string{}, 692 }}, 693 }} 694 695 topologyDupValues := []api.TopologySelectorTerm{{ 696 MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ 697 Key: "kubernetes.io/hostname", 698 Values: []string{"node1", "node1"}, 699 }}, 700 }} 701 702 topologyMultiValues := []api.TopologySelectorTerm{{ 703 MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ 704 Key: "kubernetes.io/hostname", 705 Values: []string{"node1", "node2"}, 706 }}, 707 }} 708 709 topologyEmptyMatchLabelExpressions := []api.TopologySelectorTerm{{ 710 MatchLabelExpressions: nil, 711 }} 712 713 topologyDupKeys := []api.TopologySelectorTerm{{ 714 MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ 715 Key: "kubernetes.io/hostname", 716 Values: []string{"node1"}, 717 }, { 718 Key: "kubernetes.io/hostname", 719 Values: []string{"node2"}, 720 }}, 721 }} 722 723 topologyMultiTerm := []api.TopologySelectorTerm{{ 724 MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ 725 Key: "kubernetes.io/hostname", 726 Values: []string{"node1"}, 727 }}, 728 }, { 729 MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ 730 Key: "kubernetes.io/hostname", 731 Values: []string{"node2"}, 732 }}, 733 }} 734 735 topologyDupTermsIdentical := []api.TopologySelectorTerm{{ 736 MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ 737 Key: "failure-domain.beta.kubernetes.io/zone", 738 Values: []string{"zone1"}, 739 }, { 740 Key: "kubernetes.io/hostname", 741 Values: []string{"node1"}, 742 }}, 743 }, { 744 MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ 745 Key: "failure-domain.beta.kubernetes.io/zone", 746 Values: []string{"zone1"}, 747 }, { 748 Key: "kubernetes.io/hostname", 749 Values: []string{"node1"}, 750 }}, 751 }} 752 753 topologyExprsOneSameOneDiff := []api.TopologySelectorTerm{{ 754 MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ 755 Key: "failure-domain.beta.kubernetes.io/zone", 756 Values: []string{"zone1"}, 757 }, { 758 Key: "kubernetes.io/hostname", 759 Values: []string{"node1"}, 760 }}, 761 }, { 762 MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ 763 Key: "failure-domain.beta.kubernetes.io/zone", 764 Values: []string{"zone1"}, 765 }, { 766 Key: "kubernetes.io/hostname", 767 Values: []string{"node2"}, 768 }}, 769 }} 770 771 topologyValuesOneSameOneDiff := []api.TopologySelectorTerm{{ 772 MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ 773 Key: "kubernetes.io/hostname", 774 Values: []string{"node1", "node2"}, 775 }}, 776 }, { 777 MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ 778 Key: "kubernetes.io/hostname", 779 Values: []string{"node1", "node3"}, 780 }}, 781 }} 782 783 topologyDupTermsDiffExprOrder := []api.TopologySelectorTerm{{ 784 MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ 785 Key: "kubernetes.io/hostname", 786 Values: []string{"node1"}, 787 }, { 788 Key: "failure-domain.beta.kubernetes.io/zone", 789 Values: []string{"zone1"}, 790 }}, 791 }, { 792 MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ 793 Key: "failure-domain.beta.kubernetes.io/zone", 794 Values: []string{"zone1"}, 795 }, { 796 Key: "kubernetes.io/hostname", 797 Values: []string{"node1"}, 798 }}, 799 }} 800 801 topologyDupTermsDiffValueOrder := []api.TopologySelectorTerm{{ 802 MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ 803 Key: "failure-domain.beta.kubernetes.io/zone", 804 Values: []string{"zone1", "zone2"}, 805 }}, 806 }, { 807 MatchLabelExpressions: []api.TopologySelectorLabelRequirement{{ 808 Key: "failure-domain.beta.kubernetes.io/zone", 809 Values: []string{"zone2", "zone1"}, 810 }}, 811 }} 812 813 cases := map[string]bindingTest{ 814 "no topology": { 815 class: makeClass(&waitingMode, nil), 816 shouldSucceed: true, 817 }, 818 "valid topology": { 819 class: makeClass(&waitingMode, validTopology), 820 shouldSucceed: true, 821 }, 822 "topology invalid key": { 823 class: makeClass(&waitingMode, topologyInvalidKey), 824 shouldSucceed: false, 825 }, 826 "topology lack of values": { 827 class: makeClass(&waitingMode, topologyLackOfValues), 828 shouldSucceed: false, 829 }, 830 "duplicate TopologySelectorRequirement values": { 831 class: makeClass(&waitingMode, topologyDupValues), 832 shouldSucceed: false, 833 }, 834 "multiple TopologySelectorRequirement values": { 835 class: makeClass(&waitingMode, topologyMultiValues), 836 shouldSucceed: true, 837 }, 838 "empty MatchLabelExpressions": { 839 class: makeClass(&waitingMode, topologyEmptyMatchLabelExpressions), 840 shouldSucceed: false, 841 }, 842 "duplicate MatchLabelExpression keys": { 843 class: makeClass(&waitingMode, topologyDupKeys), 844 shouldSucceed: false, 845 }, 846 "duplicate MatchLabelExpression keys but across separate terms": { 847 class: makeClass(&waitingMode, topologyMultiTerm), 848 shouldSucceed: true, 849 }, 850 "duplicate AllowedTopologies terms - identical": { 851 class: makeClass(&waitingMode, topologyDupTermsIdentical), 852 shouldSucceed: false, 853 }, 854 "two AllowedTopologies terms, with a pair of the same MatchLabelExpressions and a pair of different ones": { 855 class: makeClass(&waitingMode, topologyExprsOneSameOneDiff), 856 shouldSucceed: true, 857 }, 858 "two AllowedTopologies terms, with a pair of the same Values and a pair of different ones": { 859 class: makeClass(&waitingMode, topologyValuesOneSameOneDiff), 860 shouldSucceed: true, 861 }, 862 "duplicate AllowedTopologies terms - different MatchLabelExpressions order": { 863 class: makeClass(&waitingMode, topologyDupTermsDiffExprOrder), 864 shouldSucceed: false, 865 }, 866 "duplicate AllowedTopologies terms - different TopologySelectorRequirement values order": { 867 class: makeClass(&waitingMode, topologyDupTermsDiffValueOrder), 868 shouldSucceed: false, 869 }, 870 } 871 872 for testName, testCase := range cases { 873 errs := ValidateStorageClass(testCase.class) 874 if testCase.shouldSucceed && len(errs) != 0 { 875 t.Errorf("Expected success for test %q, got %v", testName, errs) 876 } 877 if !testCase.shouldSucceed && len(errs) == 0 { 878 t.Errorf("Expected failure for test %q, got success", testName) 879 } 880 } 881 } 882 883 func TestCSINodeValidation(t *testing.T) { 884 driverName := "driver-name" 885 driverName2 := "1io.kubernetes-storage-2-csi-driver3" 886 longName := "my-a-b-c-d-c-f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-u-v-w-x-y-z-ABCDEFGHIJKLMNOPQRSTUVWXYZ-driver" // 88 chars 887 nodeID := "nodeA" 888 longID := longName + longName + "abcdefghijklmnopqrstuvwxyz" // 202 chars 889 successCases := []storage.CSINode{{ 890 // driver name: dot only 891 ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, 892 Spec: storage.CSINodeSpec{ 893 Drivers: []storage.CSINodeDriver{{ 894 Name: "io.kubernetes.storage.csi.driver", 895 NodeID: nodeID, 896 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 897 }}, 898 }, 899 }, { 900 // driver name: dash only 901 ObjectMeta: metav1.ObjectMeta{Name: "foo2"}, 902 Spec: storage.CSINodeSpec{ 903 Drivers: []storage.CSINodeDriver{{ 904 Name: "io-kubernetes-storage-csi-driver", 905 NodeID: nodeID, 906 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 907 }}, 908 }, 909 }, { 910 // driver name: numbers 911 ObjectMeta: metav1.ObjectMeta{Name: "foo3"}, 912 Spec: storage.CSINodeSpec{ 913 Drivers: []storage.CSINodeDriver{{ 914 Name: "1io-kubernetes-storage-2-csi-driver3", 915 NodeID: nodeID, 916 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 917 }}, 918 }, 919 }, { 920 // driver name: dot, dash 921 ObjectMeta: metav1.ObjectMeta{Name: "foo4"}, 922 Spec: storage.CSINodeSpec{ 923 Drivers: []storage.CSINodeDriver{{ 924 Name: "io.kubernetes.storage-csi-driver", 925 NodeID: nodeID, 926 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 927 }}, 928 }, 929 }, { 930 // driver name: dot, dash, and numbers 931 ObjectMeta: metav1.ObjectMeta{Name: "foo5"}, 932 Spec: storage.CSINodeSpec{ 933 Drivers: []storage.CSINodeDriver{{ 934 Name: driverName2, 935 NodeID: nodeID, 936 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 937 }}, 938 }, 939 }, { 940 // Driver name length 1 941 ObjectMeta: metav1.ObjectMeta{Name: "foo2"}, 942 Spec: storage.CSINodeSpec{ 943 Drivers: []storage.CSINodeDriver{{ 944 Name: "a", 945 NodeID: nodeID, 946 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 947 }}, 948 }, 949 }, { 950 // multiple drivers with different node IDs, topology keys 951 ObjectMeta: metav1.ObjectMeta{Name: "foo6"}, 952 Spec: storage.CSINodeSpec{ 953 Drivers: []storage.CSINodeDriver{{ 954 Name: "driver1", 955 NodeID: "node1", 956 TopologyKeys: []string{"key1", "key2"}, 957 }, { 958 Name: "driverB", 959 NodeID: "nodeA", 960 TopologyKeys: []string{"keyA", "keyB"}, 961 }}, 962 }, 963 }, { 964 // multiple drivers with same node IDs, topology keys 965 ObjectMeta: metav1.ObjectMeta{Name: "foo7"}, 966 Spec: storage.CSINodeSpec{ 967 Drivers: []storage.CSINodeDriver{{ 968 Name: "driver1", 969 NodeID: "node1", 970 TopologyKeys: []string{"key1"}, 971 }, { 972 Name: "driver2", 973 NodeID: "node1", 974 TopologyKeys: []string{"key1"}, 975 }}, 976 }, 977 }, { 978 // Volume limits being zero 979 ObjectMeta: metav1.ObjectMeta{Name: "foo11"}, 980 Spec: storage.CSINodeSpec{ 981 Drivers: []storage.CSINodeDriver{{ 982 Name: "io.kubernetes.storage.csi.driver", 983 NodeID: nodeID, 984 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 985 Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(0)}, 986 }}, 987 }, 988 }, { 989 // Volume limits with positive number 990 ObjectMeta: metav1.ObjectMeta{Name: "foo11"}, 991 Spec: storage.CSINodeSpec{ 992 Drivers: []storage.CSINodeDriver{{ 993 Name: "io.kubernetes.storage.csi.driver", 994 NodeID: nodeID, 995 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 996 Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(1)}, 997 }}, 998 }, 999 }, { 1000 // topology key names with -, _, and dot . 1001 ObjectMeta: metav1.ObjectMeta{Name: "foo8"}, 1002 Spec: storage.CSINodeSpec{ 1003 Drivers: []storage.CSINodeDriver{{ 1004 Name: "driver1", 1005 NodeID: "node1", 1006 TopologyKeys: []string{"zone_1", "zone.2"}, 1007 }, { 1008 Name: "driver2", 1009 NodeID: "node1", 1010 TopologyKeys: []string{"zone-3", "zone.4"}, 1011 }}, 1012 }, 1013 }, { 1014 // topology prefix with - and dot. 1015 ObjectMeta: metav1.ObjectMeta{Name: "foo9"}, 1016 Spec: storage.CSINodeSpec{ 1017 Drivers: []storage.CSINodeDriver{{ 1018 Name: "driver1", 1019 NodeID: "node1", 1020 TopologyKeys: []string{"company-com/zone1", "company.com/zone2"}, 1021 }}, 1022 }, 1023 }, { 1024 // No topology keys 1025 ObjectMeta: metav1.ObjectMeta{Name: "foo10"}, 1026 Spec: storage.CSINodeSpec{ 1027 Drivers: []storage.CSINodeDriver{{ 1028 Name: driverName, 1029 NodeID: nodeID, 1030 }}, 1031 }, 1032 }} 1033 1034 for _, csiNode := range successCases { 1035 if errs := ValidateCSINode(&csiNode, shorterIDValidationOption); len(errs) != 0 { 1036 t.Errorf("expected success: %v", errs) 1037 } 1038 } 1039 1040 nodeIDCase := storage.CSINode{ 1041 // node ID length > 128 but < 192 1042 ObjectMeta: metav1.ObjectMeta{Name: "foo7"}, 1043 Spec: storage.CSINodeSpec{ 1044 Drivers: []storage.CSINodeDriver{{ 1045 Name: driverName, 1046 NodeID: longID, 1047 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1048 }}, 1049 }, 1050 } 1051 1052 if errs := ValidateCSINode(&nodeIDCase, longerIDValidateOption); len(errs) != 0 { 1053 t.Errorf("expected success: %v", errs) 1054 } 1055 1056 errorCases := []storage.CSINode{{ 1057 // Empty driver name 1058 ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, 1059 Spec: storage.CSINodeSpec{ 1060 Drivers: []storage.CSINodeDriver{{ 1061 Name: "", 1062 NodeID: nodeID, 1063 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1064 }}, 1065 }, 1066 }, { 1067 // Invalid start char in driver name 1068 ObjectMeta: metav1.ObjectMeta{Name: "foo3"}, 1069 Spec: storage.CSINodeSpec{ 1070 Drivers: []storage.CSINodeDriver{{ 1071 Name: "_io.kubernetes.storage.csi.driver", 1072 NodeID: nodeID, 1073 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1074 }}, 1075 }, 1076 }, { 1077 // Invalid end char in driver name 1078 ObjectMeta: metav1.ObjectMeta{Name: "foo4"}, 1079 Spec: storage.CSINodeSpec{ 1080 Drivers: []storage.CSINodeDriver{{ 1081 Name: "io.kubernetes.storage.csi.driver/", 1082 NodeID: nodeID, 1083 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1084 }}, 1085 }, 1086 }, { 1087 // Invalid separators in driver name 1088 ObjectMeta: metav1.ObjectMeta{Name: "foo5"}, 1089 Spec: storage.CSINodeSpec{ 1090 Drivers: []storage.CSINodeDriver{{ 1091 Name: "io/kubernetes/storage/csi~driver", 1092 NodeID: nodeID, 1093 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1094 }}, 1095 }, 1096 }, { 1097 // driver name: underscore only 1098 ObjectMeta: metav1.ObjectMeta{Name: "foo6"}, 1099 Spec: storage.CSINodeSpec{ 1100 Drivers: []storage.CSINodeDriver{{ 1101 Name: "io_kubernetes_storage_csi_driver", 1102 NodeID: nodeID, 1103 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1104 }}, 1105 }, 1106 }, { 1107 // Driver name length > 63 1108 ObjectMeta: metav1.ObjectMeta{Name: "foo7"}, 1109 Spec: storage.CSINodeSpec{ 1110 Drivers: []storage.CSINodeDriver{{ 1111 Name: longName, 1112 NodeID: nodeID, 1113 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1114 }}, 1115 }, 1116 }, { 1117 // No driver name 1118 ObjectMeta: metav1.ObjectMeta{Name: "foo8"}, 1119 Spec: storage.CSINodeSpec{ 1120 Drivers: []storage.CSINodeDriver{{ 1121 NodeID: nodeID, 1122 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1123 }}, 1124 }, 1125 }, { 1126 // Empty individual topology key 1127 ObjectMeta: metav1.ObjectMeta{Name: "foo9"}, 1128 Spec: storage.CSINodeSpec{ 1129 Drivers: []storage.CSINodeDriver{{ 1130 Name: driverName, 1131 NodeID: nodeID, 1132 TopologyKeys: []string{"company.com/zone1", ""}, 1133 }}, 1134 }, 1135 }, { 1136 // duplicate drivers in driver specs 1137 ObjectMeta: metav1.ObjectMeta{Name: "foo10"}, 1138 Spec: storage.CSINodeSpec{ 1139 Drivers: []storage.CSINodeDriver{{ 1140 Name: "driver1", 1141 NodeID: "node1", 1142 TopologyKeys: []string{"key1", "key2"}, 1143 }, { 1144 Name: "driver1", 1145 NodeID: "nodeX", 1146 TopologyKeys: []string{"keyA", "keyB"}, 1147 }}, 1148 }, 1149 }, { 1150 // single driver with duplicate topology keys in driver specs 1151 ObjectMeta: metav1.ObjectMeta{Name: "foo11"}, 1152 Spec: storage.CSINodeSpec{ 1153 Drivers: []storage.CSINodeDriver{{ 1154 Name: "driver1", 1155 NodeID: "node1", 1156 TopologyKeys: []string{"key1", "key1"}, 1157 }}, 1158 }, 1159 }, { 1160 // multiple drivers with one set of duplicate topology keys in driver specs 1161 ObjectMeta: metav1.ObjectMeta{Name: "foo12"}, 1162 Spec: storage.CSINodeSpec{ 1163 Drivers: []storage.CSINodeDriver{{ 1164 Name: "driver1", 1165 NodeID: "node1", 1166 TopologyKeys: []string{"key1"}, 1167 }, { 1168 Name: "driver2", 1169 NodeID: "nodeX", 1170 TopologyKeys: []string{"keyA", "keyA"}, 1171 }}, 1172 }, 1173 }, { 1174 // Empty NodeID 1175 ObjectMeta: metav1.ObjectMeta{Name: "foo13"}, 1176 Spec: storage.CSINodeSpec{ 1177 Drivers: []storage.CSINodeDriver{{ 1178 Name: driverName, 1179 NodeID: "", 1180 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1181 }}, 1182 }, 1183 }, { 1184 // Volume limits with negative number 1185 ObjectMeta: metav1.ObjectMeta{Name: "foo11"}, 1186 Spec: storage.CSINodeSpec{ 1187 Drivers: []storage.CSINodeDriver{{ 1188 Name: "io.kubernetes.storage.csi.driver", 1189 NodeID: nodeID, 1190 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1191 Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(-1)}, 1192 }}, 1193 }, 1194 }, { 1195 // topology prefix should be lower case 1196 ObjectMeta: metav1.ObjectMeta{Name: "foo14"}, 1197 Spec: storage.CSINodeSpec{ 1198 Drivers: []storage.CSINodeDriver{{ 1199 Name: driverName, 1200 NodeID: "node1", 1201 TopologyKeys: []string{"Company.Com/zone1", "company.com/zone2"}, 1202 }}, 1203 }, 1204 }, 1205 nodeIDCase, 1206 } 1207 1208 for _, csiNode := range errorCases { 1209 if errs := ValidateCSINode(&csiNode, shorterIDValidationOption); len(errs) == 0 { 1210 t.Errorf("Expected failure for test: %v", csiNode) 1211 } 1212 } 1213 } 1214 1215 func TestCSINodeUpdateValidation(t *testing.T) { 1216 //driverName := "driver-name" 1217 //driverName2 := "1io.kubernetes-storage-2-csi-driver3" 1218 //longName := "my-a-b-c-d-c-f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-u-v-w-x-y-z-ABCDEFGHIJKLMNOPQRSTUVWXYZ-driver" 1219 nodeID := "nodeA" 1220 1221 old := storage.CSINode{ 1222 ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, 1223 Spec: storage.CSINodeSpec{ 1224 Drivers: []storage.CSINodeDriver{{ 1225 Name: "io.kubernetes.storage.csi.driver-1", 1226 NodeID: nodeID, 1227 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1228 }, { 1229 Name: "io.kubernetes.storage.csi.driver-2", 1230 NodeID: nodeID, 1231 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1232 Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(20)}, 1233 }}, 1234 }, 1235 } 1236 1237 successCases := []storage.CSINode{{ 1238 // no change 1239 ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, 1240 Spec: storage.CSINodeSpec{ 1241 Drivers: []storage.CSINodeDriver{{ 1242 Name: "io.kubernetes.storage.csi.driver-1", 1243 NodeID: nodeID, 1244 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1245 }, { 1246 Name: "io.kubernetes.storage.csi.driver-2", 1247 NodeID: nodeID, 1248 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1249 Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(20)}, 1250 }}, 1251 }, 1252 }, { 1253 // remove a driver 1254 ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, 1255 Spec: storage.CSINodeSpec{ 1256 Drivers: []storage.CSINodeDriver{{ 1257 Name: "io.kubernetes.storage.csi.driver-1", 1258 NodeID: nodeID, 1259 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1260 }}, 1261 }, 1262 }, { 1263 // add a driver 1264 ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, 1265 Spec: storage.CSINodeSpec{ 1266 Drivers: []storage.CSINodeDriver{{ 1267 Name: "io.kubernetes.storage.csi.driver-1", 1268 NodeID: nodeID, 1269 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1270 }, { 1271 Name: "io.kubernetes.storage.csi.driver-2", 1272 NodeID: nodeID, 1273 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1274 Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(20)}, 1275 }, { 1276 Name: "io.kubernetes.storage.csi.driver-3", 1277 NodeID: nodeID, 1278 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1279 Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(30)}, 1280 }}, 1281 }, 1282 }, { 1283 // remove a driver and add a driver 1284 ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, 1285 Spec: storage.CSINodeSpec{ 1286 Drivers: []storage.CSINodeDriver{{ 1287 Name: "io.kubernetes.storage.csi.driver-1", 1288 NodeID: nodeID, 1289 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1290 }, { 1291 Name: "io.kubernetes.storage.csi.new-driver", 1292 NodeID: nodeID, 1293 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1294 Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(30)}, 1295 }}, 1296 }, 1297 }} 1298 1299 for _, csiNode := range successCases { 1300 if errs := ValidateCSINodeUpdate(&csiNode, &old, shorterIDValidationOption); len(errs) != 0 { 1301 t.Errorf("expected success: %+v", errs) 1302 } 1303 } 1304 1305 errorCases := []storage.CSINode{{ 1306 // invalid change node id 1307 ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, 1308 Spec: storage.CSINodeSpec{ 1309 Drivers: []storage.CSINodeDriver{{ 1310 Name: "io.kubernetes.storage.csi.driver-1", 1311 NodeID: "nodeB", 1312 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1313 }, { 1314 Name: "io.kubernetes.storage.csi.driver-2", 1315 NodeID: nodeID, 1316 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1317 Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(20)}, 1318 }}, 1319 }, 1320 }, { 1321 // invalid change topology keys 1322 ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, 1323 Spec: storage.CSINodeSpec{ 1324 Drivers: []storage.CSINodeDriver{{ 1325 Name: "io.kubernetes.storage.csi.driver-1", 1326 NodeID: nodeID, 1327 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1328 }, { 1329 Name: "io.kubernetes.storage.csi.driver-2", 1330 NodeID: nodeID, 1331 TopologyKeys: []string{"company.com/zone2"}, 1332 Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(20)}, 1333 }}, 1334 }, 1335 }, { 1336 // invalid change trying to set a previously unset allocatable 1337 ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, 1338 Spec: storage.CSINodeSpec{ 1339 Drivers: []storage.CSINodeDriver{{ 1340 Name: "io.kubernetes.storage.csi.driver-1", 1341 NodeID: nodeID, 1342 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1343 Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(10)}, 1344 }, { 1345 Name: "io.kubernetes.storage.csi.driver-2", 1346 NodeID: nodeID, 1347 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1348 Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(20)}, 1349 }}, 1350 }, 1351 }, { 1352 // invalid change trying to update allocatable with a different volume limit 1353 ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, 1354 Spec: storage.CSINodeSpec{ 1355 Drivers: []storage.CSINodeDriver{{ 1356 Name: "io.kubernetes.storage.csi.driver-1", 1357 NodeID: nodeID, 1358 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1359 }, { 1360 Name: "io.kubernetes.storage.csi.driver-2", 1361 NodeID: nodeID, 1362 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1363 Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32(21)}, 1364 }}, 1365 }, 1366 }, { 1367 // invalid change trying to update allocatable with an empty volume limit 1368 ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, 1369 Spec: storage.CSINodeSpec{ 1370 Drivers: []storage.CSINodeDriver{{ 1371 Name: "io.kubernetes.storage.csi.driver-1", 1372 NodeID: nodeID, 1373 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1374 }, { 1375 Name: "io.kubernetes.storage.csi.driver-2", 1376 NodeID: nodeID, 1377 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1378 Allocatable: &storage.VolumeNodeResources{Count: nil}, 1379 }}, 1380 }, 1381 }, { 1382 // invalid change trying to remove allocatable 1383 ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, 1384 Spec: storage.CSINodeSpec{ 1385 Drivers: []storage.CSINodeDriver{{ 1386 Name: "io.kubernetes.storage.csi.driver-1", 1387 NodeID: nodeID, 1388 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1389 }, { 1390 Name: "io.kubernetes.storage.csi.driver-2", 1391 NodeID: nodeID, 1392 TopologyKeys: []string{"company.com/zone1", "company.com/zone2"}, 1393 }}, 1394 }, 1395 }} 1396 1397 for _, csiNode := range errorCases { 1398 if errs := ValidateCSINodeUpdate(&csiNode, &old, shorterIDValidationOption); len(errs) == 0 { 1399 t.Errorf("Expected failure for test: %+v", csiNode) 1400 } 1401 } 1402 } 1403 1404 func TestCSIDriverValidation(t *testing.T) { 1405 // assume this feature is on for this test, detailed enabled/disabled tests in TestCSIDriverValidationSELinuxMountEnabledDisabled 1406 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SELinuxMountReadWriteOncePod, true)() 1407 1408 driverName := "test-driver" 1409 longName := "my-a-b-c-d-c-f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-u-v-w-x-y-z-ABCDEFGHIJKLMNOPQRSTUVWXYZ-driver" 1410 invalidName := "-invalid-@#$%^&*()-" 1411 attachRequired := true 1412 attachNotRequired := false 1413 podInfoOnMount := true 1414 notPodInfoOnMount := false 1415 notRequiresRepublish := false 1416 storageCapacity := true 1417 notStorageCapacity := false 1418 seLinuxMount := true 1419 notSELinuxMount := false 1420 supportedFSGroupPolicy := storage.FileFSGroupPolicy 1421 invalidFSGroupPolicy := storage.FSGroupPolicy("invalid-mode") 1422 successCases := []storage.CSIDriver{{ 1423 ObjectMeta: metav1.ObjectMeta{Name: driverName}, 1424 Spec: storage.CSIDriverSpec{ 1425 AttachRequired: &attachRequired, 1426 PodInfoOnMount: &podInfoOnMount, 1427 RequiresRepublish: ¬RequiresRepublish, 1428 StorageCapacity: &storageCapacity, 1429 SELinuxMount: &seLinuxMount, 1430 }, 1431 }, { 1432 // driver name: dot only 1433 ObjectMeta: metav1.ObjectMeta{Name: "io.kubernetes.storage.csi.driver"}, 1434 Spec: storage.CSIDriverSpec{ 1435 AttachRequired: &attachRequired, 1436 PodInfoOnMount: &podInfoOnMount, 1437 RequiresRepublish: ¬RequiresRepublish, 1438 StorageCapacity: ¬StorageCapacity, 1439 SELinuxMount: &seLinuxMount, 1440 }, 1441 }, { 1442 // driver name: dash only 1443 ObjectMeta: metav1.ObjectMeta{Name: "io-kubernetes-storage-csi-driver"}, 1444 Spec: storage.CSIDriverSpec{ 1445 AttachRequired: &attachRequired, 1446 PodInfoOnMount: ¬PodInfoOnMount, 1447 RequiresRepublish: ¬RequiresRepublish, 1448 StorageCapacity: &storageCapacity, 1449 SELinuxMount: &seLinuxMount, 1450 }, 1451 }, { 1452 // driver name: numbers 1453 ObjectMeta: metav1.ObjectMeta{Name: "1csi2driver3"}, 1454 Spec: storage.CSIDriverSpec{ 1455 AttachRequired: &attachRequired, 1456 PodInfoOnMount: &podInfoOnMount, 1457 RequiresRepublish: ¬RequiresRepublish, 1458 StorageCapacity: &storageCapacity, 1459 SELinuxMount: &seLinuxMount, 1460 }, 1461 }, { 1462 // driver name: dot and dash 1463 ObjectMeta: metav1.ObjectMeta{Name: "io.kubernetes.storage.csi-driver"}, 1464 Spec: storage.CSIDriverSpec{ 1465 AttachRequired: &attachRequired, 1466 PodInfoOnMount: &podInfoOnMount, 1467 RequiresRepublish: ¬RequiresRepublish, 1468 StorageCapacity: &storageCapacity, 1469 SELinuxMount: &seLinuxMount, 1470 }, 1471 }, { 1472 ObjectMeta: metav1.ObjectMeta{Name: driverName}, 1473 Spec: storage.CSIDriverSpec{ 1474 AttachRequired: &attachRequired, 1475 PodInfoOnMount: ¬PodInfoOnMount, 1476 RequiresRepublish: ¬RequiresRepublish, 1477 StorageCapacity: &storageCapacity, 1478 SELinuxMount: &seLinuxMount, 1479 }, 1480 }, { 1481 ObjectMeta: metav1.ObjectMeta{Name: driverName}, 1482 Spec: storage.CSIDriverSpec{ 1483 AttachRequired: &attachRequired, 1484 PodInfoOnMount: &podInfoOnMount, 1485 RequiresRepublish: ¬RequiresRepublish, 1486 StorageCapacity: &storageCapacity, 1487 SELinuxMount: &seLinuxMount, 1488 }, 1489 }, { 1490 ObjectMeta: metav1.ObjectMeta{Name: driverName}, 1491 Spec: storage.CSIDriverSpec{ 1492 AttachRequired: &attachNotRequired, 1493 PodInfoOnMount: ¬PodInfoOnMount, 1494 RequiresRepublish: ¬RequiresRepublish, 1495 StorageCapacity: &storageCapacity, 1496 SELinuxMount: &seLinuxMount, 1497 }, 1498 }, { 1499 ObjectMeta: metav1.ObjectMeta{Name: driverName}, 1500 Spec: storage.CSIDriverSpec{ 1501 AttachRequired: &attachNotRequired, 1502 PodInfoOnMount: ¬PodInfoOnMount, 1503 RequiresRepublish: ¬RequiresRepublish, 1504 StorageCapacity: &storageCapacity, 1505 VolumeLifecycleModes: []storage.VolumeLifecycleMode{ 1506 storage.VolumeLifecyclePersistent, 1507 }, 1508 SELinuxMount: &seLinuxMount, 1509 }, 1510 }, { 1511 ObjectMeta: metav1.ObjectMeta{Name: driverName}, 1512 Spec: storage.CSIDriverSpec{ 1513 AttachRequired: &attachNotRequired, 1514 PodInfoOnMount: ¬PodInfoOnMount, 1515 RequiresRepublish: ¬RequiresRepublish, 1516 StorageCapacity: &storageCapacity, 1517 VolumeLifecycleModes: []storage.VolumeLifecycleMode{ 1518 storage.VolumeLifecycleEphemeral, 1519 }, 1520 SELinuxMount: &seLinuxMount, 1521 }, 1522 }, { 1523 ObjectMeta: metav1.ObjectMeta{Name: driverName}, 1524 Spec: storage.CSIDriverSpec{ 1525 AttachRequired: &attachNotRequired, 1526 PodInfoOnMount: ¬PodInfoOnMount, 1527 RequiresRepublish: ¬RequiresRepublish, 1528 StorageCapacity: &storageCapacity, 1529 VolumeLifecycleModes: []storage.VolumeLifecycleMode{ 1530 storage.VolumeLifecycleEphemeral, 1531 storage.VolumeLifecyclePersistent, 1532 }, 1533 SELinuxMount: &seLinuxMount, 1534 }, 1535 }, { 1536 ObjectMeta: metav1.ObjectMeta{Name: driverName}, 1537 Spec: storage.CSIDriverSpec{ 1538 AttachRequired: &attachNotRequired, 1539 PodInfoOnMount: ¬PodInfoOnMount, 1540 RequiresRepublish: ¬RequiresRepublish, 1541 StorageCapacity: &storageCapacity, 1542 VolumeLifecycleModes: []storage.VolumeLifecycleMode{ 1543 storage.VolumeLifecycleEphemeral, 1544 storage.VolumeLifecyclePersistent, 1545 storage.VolumeLifecycleEphemeral, 1546 }, 1547 SELinuxMount: &seLinuxMount, 1548 }, 1549 }, { 1550 ObjectMeta: metav1.ObjectMeta{Name: driverName}, 1551 Spec: storage.CSIDriverSpec{ 1552 AttachRequired: &attachNotRequired, 1553 PodInfoOnMount: ¬PodInfoOnMount, 1554 RequiresRepublish: ¬RequiresRepublish, 1555 StorageCapacity: &storageCapacity, 1556 FSGroupPolicy: &supportedFSGroupPolicy, 1557 SELinuxMount: &seLinuxMount, 1558 }, 1559 }, { 1560 // SELinuxMount: false 1561 ObjectMeta: metav1.ObjectMeta{Name: driverName}, 1562 Spec: storage.CSIDriverSpec{ 1563 AttachRequired: &attachNotRequired, 1564 PodInfoOnMount: ¬PodInfoOnMount, 1565 RequiresRepublish: ¬RequiresRepublish, 1566 StorageCapacity: &storageCapacity, 1567 SELinuxMount: ¬SELinuxMount, 1568 }, 1569 }} 1570 1571 for _, csiDriver := range successCases { 1572 if errs := ValidateCSIDriver(&csiDriver); len(errs) != 0 { 1573 t.Errorf("expected success: %v", errs) 1574 } 1575 } 1576 errorCases := []storage.CSIDriver{{ 1577 ObjectMeta: metav1.ObjectMeta{Name: invalidName}, 1578 Spec: storage.CSIDriverSpec{ 1579 AttachRequired: &attachRequired, 1580 PodInfoOnMount: &podInfoOnMount, 1581 StorageCapacity: &storageCapacity, 1582 SELinuxMount: &seLinuxMount, 1583 }, 1584 }, { 1585 ObjectMeta: metav1.ObjectMeta{Name: longName}, 1586 Spec: storage.CSIDriverSpec{ 1587 AttachRequired: &attachNotRequired, 1588 PodInfoOnMount: ¬PodInfoOnMount, 1589 StorageCapacity: &storageCapacity, 1590 SELinuxMount: &seLinuxMount, 1591 }, 1592 }, { 1593 // AttachRequired not set 1594 ObjectMeta: metav1.ObjectMeta{Name: driverName}, 1595 Spec: storage.CSIDriverSpec{ 1596 AttachRequired: nil, 1597 PodInfoOnMount: &podInfoOnMount, 1598 StorageCapacity: &storageCapacity, 1599 SELinuxMount: &seLinuxMount, 1600 }, 1601 }, { 1602 // PodInfoOnMount not set 1603 ObjectMeta: metav1.ObjectMeta{Name: driverName}, 1604 Spec: storage.CSIDriverSpec{ 1605 AttachRequired: &attachNotRequired, 1606 PodInfoOnMount: nil, 1607 StorageCapacity: &storageCapacity, 1608 SELinuxMount: &seLinuxMount, 1609 }, 1610 }, { 1611 // StorageCapacity not set 1612 ObjectMeta: metav1.ObjectMeta{Name: driverName}, 1613 Spec: storage.CSIDriverSpec{ 1614 AttachRequired: &attachNotRequired, 1615 PodInfoOnMount: &podInfoOnMount, 1616 StorageCapacity: nil, 1617 SELinuxMount: &seLinuxMount, 1618 }, 1619 }, { 1620 // invalid mode 1621 ObjectMeta: metav1.ObjectMeta{Name: driverName}, 1622 Spec: storage.CSIDriverSpec{ 1623 AttachRequired: &attachNotRequired, 1624 PodInfoOnMount: ¬PodInfoOnMount, 1625 StorageCapacity: &storageCapacity, 1626 VolumeLifecycleModes: []storage.VolumeLifecycleMode{ 1627 "no-such-mode", 1628 }, 1629 SELinuxMount: &seLinuxMount, 1630 }, 1631 }, { 1632 // invalid fsGroupPolicy 1633 ObjectMeta: metav1.ObjectMeta{Name: driverName}, 1634 Spec: storage.CSIDriverSpec{ 1635 AttachRequired: &attachNotRequired, 1636 PodInfoOnMount: ¬PodInfoOnMount, 1637 FSGroupPolicy: &invalidFSGroupPolicy, 1638 StorageCapacity: &storageCapacity, 1639 SELinuxMount: &seLinuxMount, 1640 }, 1641 }, { 1642 // no SELinuxMount 1643 ObjectMeta: metav1.ObjectMeta{Name: driverName}, 1644 Spec: storage.CSIDriverSpec{ 1645 AttachRequired: &attachNotRequired, 1646 PodInfoOnMount: ¬PodInfoOnMount, 1647 StorageCapacity: &storageCapacity, 1648 }, 1649 }} 1650 1651 for _, csiDriver := range errorCases { 1652 if errs := ValidateCSIDriver(&csiDriver); len(errs) == 0 { 1653 t.Errorf("Expected failure for test: %v", csiDriver) 1654 } 1655 } 1656 } 1657 1658 func TestCSIDriverValidationUpdate(t *testing.T) { 1659 // assume this feature is on for this test, detailed enabled/disabled tests in TestCSIDriverValidationSELinuxMountEnabledDisabled 1660 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SELinuxMountReadWriteOncePod, true)() 1661 1662 driverName := "test-driver" 1663 longName := "my-a-b-c-d-c-f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-u-v-w-x-y-z-ABCDEFGHIJKLMNOPQRSTUVWXYZ-driver" 1664 invalidName := "-invalid-@#$%^&*()-" 1665 attachRequired := true 1666 attachNotRequired := false 1667 podInfoOnMount := true 1668 storageCapacity := true 1669 notPodInfoOnMount := false 1670 gcp := "gcp" 1671 requiresRepublish := true 1672 notRequiresRepublish := false 1673 notStorageCapacity := false 1674 seLinuxMount := true 1675 notSELinuxMount := false 1676 resourceVersion := "1" 1677 old := storage.CSIDriver{ 1678 ObjectMeta: metav1.ObjectMeta{Name: driverName, ResourceVersion: resourceVersion}, 1679 Spec: storage.CSIDriverSpec{ 1680 AttachRequired: &attachNotRequired, 1681 PodInfoOnMount: ¬PodInfoOnMount, 1682 RequiresRepublish: ¬RequiresRepublish, 1683 VolumeLifecycleModes: []storage.VolumeLifecycleMode{ 1684 storage.VolumeLifecycleEphemeral, 1685 storage.VolumeLifecyclePersistent, 1686 }, 1687 StorageCapacity: &storageCapacity, 1688 SELinuxMount: &seLinuxMount, 1689 }, 1690 } 1691 1692 successCases := []struct { 1693 name string 1694 modify func(new *storage.CSIDriver) 1695 }{{ 1696 name: "no change", 1697 modify: func(new *storage.CSIDriver) {}, 1698 }, { 1699 name: "change TokenRequests", 1700 modify: func(new *storage.CSIDriver) { 1701 new.Spec.TokenRequests = []storage.TokenRequest{{Audience: gcp}} 1702 }, 1703 }, { 1704 name: "change RequiresRepublish", 1705 modify: func(new *storage.CSIDriver) { 1706 new.Spec.RequiresRepublish = &requiresRepublish 1707 }, 1708 }, { 1709 name: "StorageCapacity changed", 1710 modify: func(new *storage.CSIDriver) { 1711 new.Spec.StorageCapacity = ¬StorageCapacity 1712 }, 1713 }, { 1714 name: "SELinuxMount changed", 1715 modify: func(new *storage.CSIDriver) { 1716 new.Spec.SELinuxMount = ¬SELinuxMount 1717 }, 1718 }} 1719 for _, test := range successCases { 1720 t.Run(test.name, func(t *testing.T) { 1721 new := old.DeepCopy() 1722 test.modify(new) 1723 if errs := ValidateCSIDriverUpdate(new, &old); len(errs) != 0 { 1724 t.Errorf("Expected success for %+v: %v", new, errs) 1725 } 1726 }) 1727 } 1728 1729 // Each test case changes exactly one field. None of that is valid. 1730 errorCases := []struct { 1731 name string 1732 modify func(new *storage.CSIDriver) 1733 }{{ 1734 name: "invalid name", 1735 modify: func(new *storage.CSIDriver) { 1736 new.Name = invalidName 1737 }, 1738 }, { 1739 name: "long name", 1740 modify: func(new *storage.CSIDriver) { 1741 new.Name = longName 1742 }, 1743 }, { 1744 name: "AttachRequired not set", 1745 modify: func(new *storage.CSIDriver) { 1746 new.Spec.AttachRequired = nil 1747 }, 1748 }, { 1749 name: "AttachRequired changed", 1750 modify: func(new *storage.CSIDriver) { 1751 new.Spec.AttachRequired = &attachRequired 1752 }, 1753 }, { 1754 name: "PodInfoOnMount not set", 1755 modify: func(new *storage.CSIDriver) { 1756 new.Spec.PodInfoOnMount = nil 1757 }, 1758 }, { 1759 name: "PodInfoOnMount changed", 1760 modify: func(new *storage.CSIDriver) { 1761 new.Spec.PodInfoOnMount = &podInfoOnMount 1762 }, 1763 }, { 1764 name: "invalid volume lifecycle mode", 1765 modify: func(new *storage.CSIDriver) { 1766 new.Spec.VolumeLifecycleModes = []storage.VolumeLifecycleMode{ 1767 "no-such-mode", 1768 } 1769 }, 1770 }, { 1771 name: "volume lifecycle modes not set", 1772 modify: func(new *storage.CSIDriver) { 1773 new.Spec.VolumeLifecycleModes = nil 1774 }, 1775 }, { 1776 name: "VolumeLifecyclePersistent removed", 1777 modify: func(new *storage.CSIDriver) { 1778 new.Spec.VolumeLifecycleModes = []storage.VolumeLifecycleMode{ 1779 storage.VolumeLifecycleEphemeral, 1780 } 1781 }, 1782 }, { 1783 name: "VolumeLifecycleEphemeral removed", 1784 modify: func(new *storage.CSIDriver) { 1785 new.Spec.VolumeLifecycleModes = []storage.VolumeLifecycleMode{ 1786 storage.VolumeLifecyclePersistent, 1787 } 1788 }, 1789 }, { 1790 name: "FSGroupPolicy invalidated", 1791 modify: func(new *storage.CSIDriver) { 1792 invalidFSGroupPolicy := storage.FSGroupPolicy("invalid") 1793 new.Spec.FSGroupPolicy = &invalidFSGroupPolicy 1794 }, 1795 }, { 1796 name: "FSGroupPolicy changed", 1797 modify: func(new *storage.CSIDriver) { 1798 fileFSGroupPolicy := storage.FileFSGroupPolicy 1799 new.Spec.FSGroupPolicy = &fileFSGroupPolicy 1800 }, 1801 }, { 1802 name: "TokenRequests invalidated", 1803 modify: func(new *storage.CSIDriver) { 1804 new.Spec.TokenRequests = []storage.TokenRequest{{Audience: gcp}, {Audience: gcp}} 1805 }, 1806 }, { 1807 name: "invalid nil StorageCapacity", 1808 modify: func(new *storage.CSIDriver) { 1809 new.Spec.StorageCapacity = nil 1810 }, 1811 }, { 1812 name: "SELinuxMount not set", 1813 modify: func(new *storage.CSIDriver) { 1814 new.Spec.SELinuxMount = nil 1815 }, 1816 }} 1817 1818 for _, test := range errorCases { 1819 t.Run(test.name, func(t *testing.T) { 1820 new := old.DeepCopy() 1821 test.modify(new) 1822 if errs := ValidateCSIDriverUpdate(new, &old); len(errs) == 0 { 1823 t.Errorf("Expected failure for test: %+v", new) 1824 } 1825 }) 1826 } 1827 } 1828 1829 func TestCSIDriverStorageCapacityEnablement(t *testing.T) { 1830 run := func(t *testing.T, withField bool) { 1831 driverName := "test-driver" 1832 attachRequired := true 1833 podInfoOnMount := true 1834 requiresRepublish := true 1835 storageCapacity := true 1836 seLinuxMount := false 1837 csiDriver := storage.CSIDriver{ 1838 ObjectMeta: metav1.ObjectMeta{Name: driverName}, 1839 Spec: storage.CSIDriverSpec{ 1840 AttachRequired: &attachRequired, 1841 PodInfoOnMount: &podInfoOnMount, 1842 RequiresRepublish: &requiresRepublish, 1843 SELinuxMount: &seLinuxMount, 1844 }, 1845 } 1846 if withField { 1847 csiDriver.Spec.StorageCapacity = &storageCapacity 1848 } 1849 errs := ValidateCSIDriver(&csiDriver) 1850 success := withField 1851 if success && len(errs) != 0 { 1852 t.Errorf("expected success, got: %v", errs) 1853 } 1854 if !success && len(errs) == 0 { 1855 t.Errorf("expected error, got success") 1856 } 1857 } 1858 1859 yesNo := []bool{true, false} 1860 for _, withField := range yesNo { 1861 t.Run(fmt.Sprintf("with-field=%v", withField), func(t *testing.T) { 1862 run(t, withField) 1863 }) 1864 } 1865 } 1866 1867 func TestValidateCSIStorageCapacity(t *testing.T) { 1868 storageClassName := "test-sc" 1869 invalidName := "-invalid-@#$%^&*()-" 1870 1871 goodCapacity := storage.CSIStorageCapacity{ 1872 ObjectMeta: metav1.ObjectMeta{ 1873 Name: "csc-329803da-fdd2-42e4-af6f-7b07e7ccc305", 1874 Namespace: metav1.NamespaceDefault, 1875 }, 1876 StorageClassName: storageClassName, 1877 } 1878 goodTopology := metav1.LabelSelector{ 1879 MatchLabels: map[string]string{"foo": "bar"}, 1880 } 1881 1882 scenarios := map[string]struct { 1883 isExpectedFailure bool 1884 capacity *storage.CSIStorageCapacity 1885 }{ 1886 "good-capacity": { 1887 capacity: &goodCapacity, 1888 }, 1889 "missing-storage-class-name": { 1890 isExpectedFailure: true, 1891 capacity: func() *storage.CSIStorageCapacity { 1892 capacity := goodCapacity 1893 capacity.StorageClassName = "" 1894 return &capacity 1895 }(), 1896 }, 1897 "bad-storage-class-name": { 1898 isExpectedFailure: true, 1899 capacity: func() *storage.CSIStorageCapacity { 1900 capacity := goodCapacity 1901 capacity.StorageClassName = invalidName 1902 return &capacity 1903 }(), 1904 }, 1905 "good-capacity-value": { 1906 capacity: func() *storage.CSIStorageCapacity { 1907 capacity := goodCapacity 1908 capacity.Capacity = resource.NewQuantity(1, resource.BinarySI) 1909 return &capacity 1910 }(), 1911 }, 1912 "bad-capacity-value": { 1913 isExpectedFailure: true, 1914 capacity: func() *storage.CSIStorageCapacity { 1915 capacity := goodCapacity 1916 capacity.Capacity = resource.NewQuantity(-11, resource.BinarySI) 1917 return &capacity 1918 }(), 1919 }, 1920 "good-topology": { 1921 capacity: func() *storage.CSIStorageCapacity { 1922 capacity := goodCapacity 1923 capacity.NodeTopology = &goodTopology 1924 return &capacity 1925 }(), 1926 }, 1927 "empty-topology": { 1928 capacity: func() *storage.CSIStorageCapacity { 1929 capacity := goodCapacity 1930 capacity.NodeTopology = &metav1.LabelSelector{} 1931 return &capacity 1932 }(), 1933 }, 1934 "bad-topology-fields": { 1935 isExpectedFailure: true, 1936 capacity: func() *storage.CSIStorageCapacity { 1937 capacity := goodCapacity 1938 capacity.NodeTopology = &metav1.LabelSelector{ 1939 MatchExpressions: []metav1.LabelSelectorRequirement{{ 1940 Key: "foo", 1941 Operator: metav1.LabelSelectorOperator("no-such-operator"), 1942 Values: []string{ 1943 "bar", 1944 }, 1945 }}, 1946 } 1947 return &capacity 1948 }(), 1949 }, 1950 } 1951 1952 for name, scenario := range scenarios { 1953 t.Run(name, func(t *testing.T) { 1954 errs := ValidateCSIStorageCapacity(scenario.capacity, CSIStorageCapacityValidateOptions{false}) 1955 if len(errs) == 0 && scenario.isExpectedFailure { 1956 t.Errorf("Unexpected success") 1957 } 1958 if len(errs) > 0 && !scenario.isExpectedFailure { 1959 t.Errorf("Unexpected failure: %+v", errs) 1960 } 1961 }) 1962 } 1963 1964 } 1965 1966 func TestCSIServiceAccountToken(t *testing.T) { 1967 driverName := "test-driver" 1968 gcp := "gcp" 1969 aws := "aws" 1970 notRequiresRepublish := false 1971 tests := []struct { 1972 desc string 1973 csiDriver *storage.CSIDriver 1974 wantErr bool 1975 }{{ 1976 desc: "invalid - TokenRequests has tokens with the same audience", 1977 csiDriver: &storage.CSIDriver{ 1978 ObjectMeta: metav1.ObjectMeta{Name: driverName}, 1979 Spec: storage.CSIDriverSpec{ 1980 TokenRequests: []storage.TokenRequest{{Audience: gcp}, {Audience: gcp}}, 1981 RequiresRepublish: ¬RequiresRepublish, 1982 }, 1983 }, 1984 wantErr: true, 1985 }, { 1986 desc: "invalid - TokenRequests has tokens with ExpirationSeconds less than 10min", 1987 csiDriver: &storage.CSIDriver{ 1988 ObjectMeta: metav1.ObjectMeta{Name: driverName}, 1989 Spec: storage.CSIDriverSpec{ 1990 TokenRequests: []storage.TokenRequest{{Audience: gcp, ExpirationSeconds: utilpointer.Int64(10)}}, 1991 RequiresRepublish: ¬RequiresRepublish, 1992 }, 1993 }, 1994 wantErr: true, 1995 }, { 1996 desc: "invalid - TokenRequests has tokens with ExpirationSeconds longer than 1<<32 min", 1997 csiDriver: &storage.CSIDriver{ 1998 ObjectMeta: metav1.ObjectMeta{Name: driverName}, 1999 Spec: storage.CSIDriverSpec{ 2000 TokenRequests: []storage.TokenRequest{{Audience: gcp, ExpirationSeconds: utilpointer.Int64(1<<32 + 1)}}, 2001 RequiresRepublish: ¬RequiresRepublish, 2002 }, 2003 }, 2004 wantErr: true, 2005 }, { 2006 desc: "valid - TokenRequests has at most one token with empty string audience", 2007 csiDriver: &storage.CSIDriver{ 2008 ObjectMeta: metav1.ObjectMeta{Name: driverName}, 2009 Spec: storage.CSIDriverSpec{ 2010 TokenRequests: []storage.TokenRequest{{Audience: ""}}, 2011 RequiresRepublish: ¬RequiresRepublish, 2012 }, 2013 }, 2014 }, { 2015 desc: "valid - TokenRequests has tokens with different audience", 2016 csiDriver: &storage.CSIDriver{ 2017 ObjectMeta: metav1.ObjectMeta{Name: driverName}, 2018 Spec: storage.CSIDriverSpec{ 2019 TokenRequests: []storage.TokenRequest{{}, {Audience: gcp}, {Audience: aws}}, 2020 RequiresRepublish: ¬RequiresRepublish, 2021 }, 2022 }, 2023 }} 2024 2025 for _, test := range tests { 2026 test.csiDriver.Spec.AttachRequired = new(bool) 2027 test.csiDriver.Spec.PodInfoOnMount = new(bool) 2028 test.csiDriver.Spec.StorageCapacity = new(bool) 2029 test.csiDriver.Spec.SELinuxMount = new(bool) 2030 if errs := ValidateCSIDriver(test.csiDriver); test.wantErr != (len(errs) != 0) { 2031 t.Errorf("ValidateCSIDriver = %v, want err: %v", errs, test.wantErr) 2032 } 2033 } 2034 } 2035 2036 func TestCSIDriverValidationSELinuxMountEnabledDisabled(t *testing.T) { 2037 tests := []struct { 2038 name string 2039 featureEnabled bool 2040 seLinuxMountValue *bool 2041 expectError bool 2042 }{{ 2043 name: "feature enabled, nil value", 2044 featureEnabled: true, 2045 seLinuxMountValue: nil, 2046 expectError: true, 2047 }, { 2048 name: "feature enabled, non-nil value", 2049 featureEnabled: true, 2050 seLinuxMountValue: utilpointer.Bool(true), 2051 expectError: false, 2052 }, { 2053 name: "feature disabled, nil value", 2054 featureEnabled: false, 2055 seLinuxMountValue: nil, 2056 expectError: false, 2057 }, { 2058 name: "feature disabled, non-nil value", 2059 featureEnabled: false, 2060 seLinuxMountValue: utilpointer.Bool(true), 2061 expectError: false, 2062 }} 2063 for _, test := range tests { 2064 t.Run(test.name, func(t *testing.T) { 2065 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SELinuxMountReadWriteOncePod, test.featureEnabled)() 2066 csiDriver := &storage.CSIDriver{ 2067 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 2068 Spec: storage.CSIDriverSpec{ 2069 AttachRequired: utilpointer.Bool(true), 2070 PodInfoOnMount: utilpointer.Bool(true), 2071 RequiresRepublish: utilpointer.Bool(true), 2072 StorageCapacity: utilpointer.Bool(true), 2073 SELinuxMount: test.seLinuxMountValue, 2074 }, 2075 } 2076 err := ValidateCSIDriver(csiDriver) 2077 if test.expectError && err == nil { 2078 t.Error("Expected validation error, got nil") 2079 } 2080 if !test.expectError && err != nil { 2081 t.Errorf("Validation returned error: %s", err) 2082 } 2083 }) 2084 } 2085 2086 updateTests := []struct { 2087 name string 2088 featureEnabled bool 2089 oldValue *bool 2090 newValue *bool 2091 expectError bool 2092 }{{ 2093 name: "feature enabled, nil->nil", 2094 featureEnabled: true, 2095 oldValue: nil, 2096 newValue: nil, 2097 expectError: true, // populated by defaulting and required when feature is enabled 2098 }, { 2099 name: "feature enabled, nil->set", 2100 featureEnabled: true, 2101 oldValue: nil, 2102 newValue: utilpointer.Bool(true), 2103 expectError: false, 2104 }, { 2105 name: "feature enabled, set->set", 2106 featureEnabled: true, 2107 oldValue: utilpointer.Bool(true), 2108 newValue: utilpointer.Bool(true), 2109 expectError: false, 2110 }, { 2111 name: "feature enabled, set->nil", 2112 featureEnabled: true, 2113 oldValue: utilpointer.Bool(true), 2114 newValue: nil, 2115 expectError: true, // populated by defaulting and required when feature is enabled 2116 }, { 2117 name: "feature disabled, nil->nil", 2118 featureEnabled: false, 2119 oldValue: nil, 2120 newValue: nil, 2121 expectError: false, 2122 }, { 2123 name: "feature disabled, nil->set", 2124 featureEnabled: false, 2125 oldValue: nil, 2126 newValue: utilpointer.Bool(true), 2127 expectError: false, 2128 }, { 2129 name: "feature disabled, set->set", 2130 featureEnabled: false, 2131 oldValue: utilpointer.Bool(true), 2132 newValue: utilpointer.Bool(true), 2133 expectError: false, 2134 }, { 2135 name: "feature disabled, set->nil", 2136 featureEnabled: false, 2137 oldValue: utilpointer.Bool(true), 2138 newValue: nil, 2139 expectError: false, 2140 }} 2141 for _, test := range updateTests { 2142 t.Run(test.name, func(t *testing.T) { 2143 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SELinuxMountReadWriteOncePod, test.featureEnabled)() 2144 oldCSIDriver := &storage.CSIDriver{ 2145 ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "1"}, 2146 Spec: storage.CSIDriverSpec{ 2147 AttachRequired: utilpointer.Bool(true), 2148 PodInfoOnMount: utilpointer.Bool(true), 2149 RequiresRepublish: utilpointer.Bool(true), 2150 StorageCapacity: utilpointer.Bool(true), 2151 SELinuxMount: test.oldValue, 2152 }, 2153 } 2154 newCSIDriver := oldCSIDriver.DeepCopy() 2155 newCSIDriver.Spec.SELinuxMount = test.newValue 2156 err := ValidateCSIDriverUpdate(newCSIDriver, oldCSIDriver) 2157 if test.expectError && err == nil { 2158 t.Error("Expected validation error, got nil") 2159 } 2160 if !test.expectError && err != nil { 2161 t.Errorf("Validation returned error: %s", err) 2162 } 2163 }) 2164 } 2165 } 2166 2167 func TestValidateVolumeAttributesClass(t *testing.T) { 2168 successCases := []storage.VolumeAttributesClass{ 2169 { 2170 // driverName without a slash 2171 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 2172 DriverName: "foo", 2173 Parameters: map[string]string{ 2174 "foo-parameter": "free-form-string", 2175 }, 2176 }, 2177 { 2178 // some parameters 2179 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 2180 DriverName: "kubernetes.io/foo", 2181 Parameters: map[string]string{ 2182 "kubernetes.io/foo-parameter": "free/form/string", 2183 "foo-parameter": "free-form-string", 2184 "foo-parameter2": "{\"embedded\": \"json\", \"with\": {\"structures\":\"inside\"}}", 2185 "foo-parameter3": "", 2186 }, 2187 }} 2188 2189 // Success cases are expected to pass validation. 2190 for testName, v := range successCases { 2191 if errs := ValidateVolumeAttributesClass(&v); len(errs) != 0 { 2192 t.Errorf("Expected success for %d, got %v", testName, errs) 2193 } 2194 } 2195 2196 // generate a map longer than maxParameterSize 2197 longParameters := make(map[string]string) 2198 totalSize := 0 2199 for totalSize < maxProvisionerParameterSize { 2200 k := fmt.Sprintf("param/%d", totalSize) 2201 v := fmt.Sprintf("value-%d", totalSize) 2202 longParameters[k] = v 2203 totalSize = totalSize + len(k) + len(v) 2204 } 2205 2206 errorCases := map[string]storage.VolumeAttributesClass{ 2207 "namespace is present": { 2208 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, 2209 DriverName: "kubernetes.io/foo", 2210 Parameters: map[string]string{ 2211 "foo-parameter": "free-form-string", 2212 }, 2213 }, 2214 "invalid driverName": { 2215 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 2216 DriverName: "kubernetes.io/invalid/foo", 2217 Parameters: map[string]string{ 2218 "foo-parameter": "free-form-string", 2219 }, 2220 }, 2221 "invalid driverName with invalid chars": { 2222 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 2223 DriverName: "^/ ", 2224 Parameters: map[string]string{ 2225 "foo-parameter": "free-form-string", 2226 }, 2227 }, 2228 "empty parameters": { 2229 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 2230 DriverName: "kubernetes.io/foo", 2231 Parameters: map[string]string{}, 2232 }, 2233 "nil parameters": { 2234 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 2235 DriverName: "kubernetes.io/foo", 2236 }, 2237 "invalid empty parameter name": { 2238 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 2239 DriverName: "kubernetes.io/foo", 2240 Parameters: map[string]string{ 2241 "": "value", 2242 }, 2243 }, 2244 "driverName: Required value": { 2245 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 2246 DriverName: "", 2247 Parameters: map[string]string{ 2248 "foo-parameter": "free-form-string", 2249 }, 2250 }, 2251 "driverName: whitespace": { 2252 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 2253 DriverName: " ", 2254 Parameters: map[string]string{ 2255 "foo-parameter": "free-form-string", 2256 }, 2257 }, 2258 "too long parameters": { 2259 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 2260 DriverName: "kubernetes.io/foo", 2261 Parameters: longParameters, 2262 }, 2263 } 2264 2265 // Error cases are not expected to pass validation. 2266 for testName, v := range errorCases { 2267 if errs := ValidateVolumeAttributesClass(&v); len(errs) == 0 { 2268 t.Errorf("Expected failure for test: %s", testName) 2269 } 2270 } 2271 } 2272 2273 func TestValidateVolumeAttributesClassUpdate(t *testing.T) { 2274 cases := map[string]struct { 2275 oldClass *storage.VolumeAttributesClass 2276 newClass *storage.VolumeAttributesClass 2277 shouldSucceed bool 2278 }{ 2279 "invalid driverName update": { 2280 oldClass: &storage.VolumeAttributesClass{ 2281 DriverName: "kubernetes.io/foo", 2282 }, 2283 newClass: &storage.VolumeAttributesClass{ 2284 DriverName: "kubernetes.io/bar", 2285 }, 2286 shouldSucceed: false, 2287 }, 2288 "invalid parameter update which changes values": { 2289 oldClass: &storage.VolumeAttributesClass{ 2290 DriverName: "kubernetes.io/foo", 2291 Parameters: map[string]string{ 2292 "foo": "bar1", 2293 }, 2294 }, 2295 newClass: &storage.VolumeAttributesClass{ 2296 DriverName: "kubernetes.io/foo", 2297 Parameters: map[string]string{ 2298 "foo": "bar2", 2299 }, 2300 }, 2301 shouldSucceed: false, 2302 }, 2303 "invalid parameter update which add new item": { 2304 oldClass: &storage.VolumeAttributesClass{ 2305 DriverName: "kubernetes.io/foo", 2306 Parameters: map[string]string{}, 2307 }, 2308 newClass: &storage.VolumeAttributesClass{ 2309 DriverName: "kubernetes.io/foo", 2310 Parameters: map[string]string{ 2311 "foo": "bar", 2312 }, 2313 }, 2314 shouldSucceed: false, 2315 }, 2316 "invalid parameter update which remove a item": { 2317 oldClass: &storage.VolumeAttributesClass{ 2318 DriverName: "kubernetes.io/foo", 2319 Parameters: map[string]string{ 2320 "foo": "bar", 2321 }, 2322 }, 2323 newClass: &storage.VolumeAttributesClass{ 2324 DriverName: "kubernetes.io/foo", 2325 Parameters: map[string]string{}, 2326 }, 2327 shouldSucceed: false, 2328 }, 2329 } 2330 2331 for testName, testCase := range cases { 2332 errs := ValidateVolumeAttributesClassUpdate(testCase.newClass, testCase.oldClass) 2333 if testCase.shouldSucceed && len(errs) != 0 { 2334 t.Errorf("Expected success for %v, got %v", testName, errs) 2335 } 2336 if !testCase.shouldSucceed && len(errs) == 0 { 2337 t.Errorf("Expected failure for %v, got success", testName) 2338 } 2339 } 2340 }