k8s.io/kubernetes@v1.29.3/pkg/api/persistentvolumeclaim/util_test.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package persistentvolumeclaim 18 19 import ( 20 "fmt" 21 "reflect" 22 "testing" 23 24 "github.com/google/go-cmp/cmp" 25 "k8s.io/apimachinery/pkg/api/resource" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/util/sets" 28 utilfeature "k8s.io/apiserver/pkg/util/feature" 29 featuregatetesting "k8s.io/component-base/featuregate/testing" 30 "k8s.io/utils/ptr" 31 32 "k8s.io/kubernetes/pkg/apis/core" 33 "k8s.io/kubernetes/pkg/features" 34 ) 35 36 func TestDropDisabledSnapshotDataSource(t *testing.T) { 37 pvcWithoutDataSource := func() *core.PersistentVolumeClaim { 38 return &core.PersistentVolumeClaim{ 39 Spec: core.PersistentVolumeClaimSpec{ 40 DataSource: nil, 41 }, 42 } 43 } 44 apiGroup := "snapshot.storage.k8s.io" 45 pvcWithDataSource := func() *core.PersistentVolumeClaim { 46 return &core.PersistentVolumeClaim{ 47 Spec: core.PersistentVolumeClaimSpec{ 48 DataSource: &core.TypedLocalObjectReference{ 49 APIGroup: &apiGroup, 50 Kind: "VolumeSnapshot", 51 Name: "test_snapshot", 52 }, 53 }, 54 } 55 } 56 57 pvcInfo := []struct { 58 description string 59 pvc func() *core.PersistentVolumeClaim 60 }{ 61 { 62 description: "pvc without DataSource", 63 pvc: pvcWithoutDataSource, 64 }, 65 { 66 description: "pvc with DataSource", 67 pvc: pvcWithDataSource, 68 }, 69 { 70 description: "is nil", 71 pvc: func() *core.PersistentVolumeClaim { return nil }, 72 }, 73 } 74 75 for _, oldpvcInfo := range pvcInfo { 76 for _, newpvcInfo := range pvcInfo { 77 oldpvc := oldpvcInfo.pvc() 78 newpvc := newpvcInfo.pvc() 79 if newpvc == nil { 80 continue 81 } 82 83 t.Run(fmt.Sprintf("old pvc %v, new pvc %v", oldpvcInfo.description, newpvcInfo.description), func(t *testing.T) { 84 EnforceDataSourceBackwardsCompatibility(&newpvc.Spec, nil) 85 86 // old pvc should never be changed 87 if !reflect.DeepEqual(oldpvc, oldpvcInfo.pvc()) { 88 t.Errorf("old pvc changed: %v", cmp.Diff(oldpvc, oldpvcInfo.pvc())) 89 } 90 91 // new pvc should not be changed 92 if !reflect.DeepEqual(newpvc, newpvcInfo.pvc()) { 93 t.Errorf("new pvc changed: %v", cmp.Diff(newpvc, newpvcInfo.pvc())) 94 } 95 }) 96 } 97 } 98 } 99 100 // TestPVCDataSourceSpecFilter checks to ensure the DropDisabledFields function behaves correctly for PVCDataSource featuregate 101 func TestPVCDataSourceSpecFilter(t *testing.T) { 102 apiGroup := "" 103 validSpec := core.PersistentVolumeClaimSpec{ 104 DataSource: &core.TypedLocalObjectReference{ 105 APIGroup: &apiGroup, 106 Kind: "PersistentVolumeClaim", 107 Name: "test_clone", 108 }, 109 } 110 validSpecNilAPIGroup := core.PersistentVolumeClaimSpec{ 111 DataSource: &core.TypedLocalObjectReference{ 112 Kind: "PersistentVolumeClaim", 113 Name: "test_clone", 114 }, 115 } 116 117 invalidAPIGroup := "invalid.pvc.api.group" 118 invalidSpec := core.PersistentVolumeClaimSpec{ 119 DataSource: &core.TypedLocalObjectReference{ 120 APIGroup: &invalidAPIGroup, 121 Kind: "PersistentVolumeClaim", 122 Name: "test_clone_invalid", 123 }, 124 } 125 126 var tests = map[string]struct { 127 spec core.PersistentVolumeClaimSpec 128 want *core.TypedLocalObjectReference 129 }{ 130 "enabled with empty ds": { 131 spec: core.PersistentVolumeClaimSpec{}, 132 want: nil, 133 }, 134 "enabled with invalid spec": { 135 spec: invalidSpec, 136 want: nil, 137 }, 138 "enabled with valid spec": { 139 spec: validSpec, 140 want: validSpec.DataSource, 141 }, 142 "enabled with valid spec but nil APIGroup": { 143 spec: validSpecNilAPIGroup, 144 want: validSpecNilAPIGroup.DataSource, 145 }, 146 } 147 148 for testName, test := range tests { 149 t.Run(testName, func(t *testing.T) { 150 EnforceDataSourceBackwardsCompatibility(&test.spec, nil) 151 if test.spec.DataSource != test.want { 152 t.Errorf("expected drop datasource condition was not met, test: %s, spec: %v, expected: %v", testName, test.spec, test.want) 153 } 154 155 }) 156 } 157 } 158 159 var ( 160 coreGroup = "" 161 snapGroup = "snapshot.storage.k8s.io" 162 genericGroup = "generic.storage.k8s.io" 163 pvcKind = "PersistentVolumeClaim" 164 snapKind = "VolumeSnapshot" 165 genericKind = "Generic" 166 podKind = "Pod" 167 ) 168 169 func makeDataSource(apiGroup, kind, name string) *core.TypedLocalObjectReference { 170 return &core.TypedLocalObjectReference{ 171 APIGroup: &apiGroup, 172 Kind: kind, 173 Name: name, 174 } 175 } 176 177 func makeDataSourceRef(apiGroup, kind, name string, namespace *string) *core.TypedObjectReference { 178 return &core.TypedObjectReference{ 179 APIGroup: &apiGroup, 180 Kind: kind, 181 Name: name, 182 Namespace: namespace, 183 } 184 } 185 186 // TestDataSourceFilter checks to ensure the AnyVolumeDataSource feature gate and CrossNamespaceVolumeDataSource works 187 func TestDataSourceFilter(t *testing.T) { 188 ns := "ns1" 189 volumeDataSource := makeDataSource(coreGroup, pvcKind, "my-vol") 190 volumeDataSourceRef := makeDataSourceRef(coreGroup, pvcKind, "my-vol", nil) 191 xnsVolumeDataSourceRef := makeDataSourceRef(coreGroup, pvcKind, "my-vol", &ns) 192 193 var tests = map[string]struct { 194 spec core.PersistentVolumeClaimSpec 195 oldSpec core.PersistentVolumeClaimSpec 196 anyEnabled bool 197 xnsEnabled bool 198 want *core.TypedLocalObjectReference 199 wantRef *core.TypedObjectReference 200 }{ 201 "any disabled with empty ds": { 202 spec: core.PersistentVolumeClaimSpec{}, 203 }, 204 "any disabled with volume ds": { 205 spec: core.PersistentVolumeClaimSpec{DataSource: volumeDataSource}, 206 want: volumeDataSource, 207 }, 208 "any disabled with volume ds ref": { 209 spec: core.PersistentVolumeClaimSpec{DataSourceRef: volumeDataSourceRef}, 210 }, 211 "any disabled with both data sources": { 212 spec: core.PersistentVolumeClaimSpec{DataSource: volumeDataSource, DataSourceRef: volumeDataSourceRef}, 213 want: volumeDataSource, 214 }, 215 "any enabled with empty ds": { 216 spec: core.PersistentVolumeClaimSpec{}, 217 anyEnabled: true, 218 }, 219 "any enabled with volume ds": { 220 spec: core.PersistentVolumeClaimSpec{DataSource: volumeDataSource}, 221 anyEnabled: true, 222 want: volumeDataSource, 223 }, 224 "any enabled with volume ds ref": { 225 spec: core.PersistentVolumeClaimSpec{DataSourceRef: volumeDataSourceRef}, 226 anyEnabled: true, 227 wantRef: volumeDataSourceRef, 228 }, 229 "any enabled with both data sources": { 230 spec: core.PersistentVolumeClaimSpec{DataSource: volumeDataSource, DataSourceRef: volumeDataSourceRef}, 231 anyEnabled: true, 232 want: volumeDataSource, 233 wantRef: volumeDataSourceRef, 234 }, 235 "both any and xns enabled with xns volume ds": { 236 spec: core.PersistentVolumeClaimSpec{DataSourceRef: xnsVolumeDataSourceRef}, 237 anyEnabled: true, 238 xnsEnabled: true, 239 wantRef: xnsVolumeDataSourceRef, 240 }, 241 "both any and xns enabled with xns volume ds when xns volume exists in oldSpec": { 242 spec: core.PersistentVolumeClaimSpec{DataSourceRef: xnsVolumeDataSourceRef}, 243 oldSpec: core.PersistentVolumeClaimSpec{DataSourceRef: xnsVolumeDataSourceRef}, 244 anyEnabled: true, 245 xnsEnabled: true, 246 wantRef: xnsVolumeDataSourceRef, 247 }, 248 "only xns enabled with xns volume ds": { 249 spec: core.PersistentVolumeClaimSpec{DataSourceRef: xnsVolumeDataSourceRef}, 250 xnsEnabled: true, 251 }, 252 "only any enabled with xns volume ds": { 253 spec: core.PersistentVolumeClaimSpec{DataSourceRef: xnsVolumeDataSourceRef}, 254 anyEnabled: true, 255 }, 256 "only any enabled with xns volume ds when xns volume exists in oldSpec": { 257 spec: core.PersistentVolumeClaimSpec{DataSourceRef: xnsVolumeDataSourceRef}, 258 oldSpec: core.PersistentVolumeClaimSpec{DataSourceRef: xnsVolumeDataSourceRef}, 259 anyEnabled: true, 260 wantRef: xnsVolumeDataSourceRef, // existing field isn't dropped. 261 }, 262 "only any enabled with xns volume ds when volume exists in oldSpec": { 263 spec: core.PersistentVolumeClaimSpec{DataSourceRef: xnsVolumeDataSourceRef}, 264 oldSpec: core.PersistentVolumeClaimSpec{DataSourceRef: volumeDataSourceRef}, 265 anyEnabled: true, 266 wantRef: xnsVolumeDataSourceRef, // existing field isn't dropped.8 267 }, 268 } 269 270 for testName, test := range tests { 271 t.Run(testName, func(t *testing.T) { 272 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AnyVolumeDataSource, test.anyEnabled)() 273 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CrossNamespaceVolumeDataSource, test.xnsEnabled)() 274 DropDisabledFields(&test.spec, &test.oldSpec) 275 if test.spec.DataSource != test.want { 276 t.Errorf("expected condition was not met, test: %s, anyEnabled: %v, xnsEnabled: %v, spec: %+v, expected DataSource: %+v", 277 testName, test.anyEnabled, test.xnsEnabled, test.spec, test.want) 278 } 279 if test.spec.DataSourceRef != test.wantRef { 280 t.Errorf("expected condition was not met, test: %s, anyEnabled: %v, xnsEnabled: %v, spec: %+v, expected DataSourceRef: %+v", 281 testName, test.anyEnabled, test.xnsEnabled, test.spec, test.wantRef) 282 } 283 }) 284 } 285 } 286 287 // TestDataSourceRef checks to ensure the DataSourceRef field handles backwards 288 // compatibility with the DataSource field 289 func TestDataSourceRef(t *testing.T) { 290 ns := "ns1" 291 volumeDataSource := makeDataSource(coreGroup, pvcKind, "my-vol") 292 volumeDataSourceRef := makeDataSourceRef(coreGroup, pvcKind, "my-vol", nil) 293 xnsVolumeDataSourceRef := makeDataSourceRef(coreGroup, pvcKind, "my-vol", &ns) 294 snapshotDataSource := makeDataSource(snapGroup, snapKind, "my-snap") 295 snapshotDataSourceRef := makeDataSourceRef(snapGroup, snapKind, "my-snap", nil) 296 xnsSnapshotDataSourceRef := makeDataSourceRef(snapGroup, snapKind, "my-snap", &ns) 297 genericDataSource := makeDataSource(genericGroup, genericKind, "my-foo") 298 genericDataSourceRef := makeDataSourceRef(genericGroup, genericKind, "my-foo", nil) 299 xnsGenericDataSourceRef := makeDataSourceRef(genericGroup, genericKind, "my-foo", &ns) 300 coreDataSource := makeDataSource(coreGroup, podKind, "my-pod") 301 coreDataSourceRef := makeDataSourceRef(coreGroup, podKind, "my-pod", nil) 302 xnsCoreDataSourceRef := makeDataSourceRef(coreGroup, podKind, "my-pod", &ns) 303 304 var tests = map[string]struct { 305 spec core.PersistentVolumeClaimSpec 306 want *core.TypedLocalObjectReference 307 wantRef *core.TypedObjectReference 308 }{ 309 "empty ds": { 310 spec: core.PersistentVolumeClaimSpec{}, 311 }, 312 "volume ds": { 313 spec: core.PersistentVolumeClaimSpec{DataSource: volumeDataSource}, 314 want: volumeDataSource, 315 wantRef: volumeDataSourceRef, 316 }, 317 "snapshot ds": { 318 spec: core.PersistentVolumeClaimSpec{DataSource: snapshotDataSource}, 319 want: snapshotDataSource, 320 wantRef: snapshotDataSourceRef, 321 }, 322 "generic ds": { 323 spec: core.PersistentVolumeClaimSpec{DataSource: genericDataSource}, 324 want: genericDataSource, 325 wantRef: genericDataSourceRef, 326 }, 327 "core ds": { 328 spec: core.PersistentVolumeClaimSpec{DataSource: coreDataSource}, 329 want: coreDataSource, 330 wantRef: coreDataSourceRef, 331 }, 332 "volume ds ref": { 333 spec: core.PersistentVolumeClaimSpec{DataSourceRef: volumeDataSourceRef}, 334 want: volumeDataSource, 335 wantRef: volumeDataSourceRef, 336 }, 337 "snapshot ds ref": { 338 spec: core.PersistentVolumeClaimSpec{DataSourceRef: snapshotDataSourceRef}, 339 want: snapshotDataSource, 340 wantRef: snapshotDataSourceRef, 341 }, 342 "generic ds ref": { 343 spec: core.PersistentVolumeClaimSpec{DataSourceRef: genericDataSourceRef}, 344 want: genericDataSource, 345 wantRef: genericDataSourceRef, 346 }, 347 "core ds ref": { 348 spec: core.PersistentVolumeClaimSpec{DataSourceRef: coreDataSourceRef}, 349 want: coreDataSource, 350 wantRef: coreDataSourceRef, 351 }, 352 "xns volume ds ref": { 353 spec: core.PersistentVolumeClaimSpec{DataSourceRef: xnsVolumeDataSourceRef}, 354 wantRef: xnsVolumeDataSourceRef, 355 }, 356 "xns snapshot ds ref": { 357 spec: core.PersistentVolumeClaimSpec{DataSourceRef: xnsSnapshotDataSourceRef}, 358 wantRef: xnsSnapshotDataSourceRef, 359 }, 360 "xns generic ds ref": { 361 spec: core.PersistentVolumeClaimSpec{DataSourceRef: xnsGenericDataSourceRef}, 362 wantRef: xnsGenericDataSourceRef, 363 }, 364 "xns core ds ref": { 365 spec: core.PersistentVolumeClaimSpec{DataSourceRef: xnsCoreDataSourceRef}, 366 wantRef: xnsCoreDataSourceRef, 367 }, 368 } 369 370 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AnyVolumeDataSource, true)() 371 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CrossNamespaceVolumeDataSource, true)() 372 373 for testName, test := range tests { 374 t.Run(testName, func(t *testing.T) { 375 NormalizeDataSources(&test.spec) 376 if !reflect.DeepEqual(test.spec.DataSource, test.want) { 377 t.Errorf("expected condition was not met, test: %s, spec.datasource: %+v, want: %+v", 378 testName, test.spec.DataSource, test.want) 379 } 380 if !reflect.DeepEqual(test.spec.DataSourceRef, test.wantRef) { 381 t.Errorf("expected condition was not met, test: %s, spec.datasourceRef: %+v, wantRef: %+v", 382 testName, test.spec.DataSourceRef, test.wantRef) 383 } 384 }) 385 } 386 } 387 388 func TestDropDisabledVolumeAttributesClass(t *testing.T) { 389 vacName := ptr.To("foo") 390 391 var tests = map[string]struct { 392 spec core.PersistentVolumeClaimSpec 393 oldSpec core.PersistentVolumeClaimSpec 394 vacEnabled bool 395 wantVAC *string 396 }{ 397 "vac disabled with empty vac": { 398 spec: core.PersistentVolumeClaimSpec{}, 399 }, 400 "vac disabled with vac": { 401 spec: core.PersistentVolumeClaimSpec{VolumeAttributesClassName: vacName}, 402 }, 403 "vac enabled with empty vac": { 404 spec: core.PersistentVolumeClaimSpec{}, 405 vacEnabled: true, 406 }, 407 "vac enabled with vac": { 408 spec: core.PersistentVolumeClaimSpec{VolumeAttributesClassName: vacName}, 409 vacEnabled: true, 410 wantVAC: vacName, 411 }, 412 "vac disabled with vac when vac doesn't exists in oldSpec": { 413 spec: core.PersistentVolumeClaimSpec{VolumeAttributesClassName: vacName}, 414 oldSpec: core.PersistentVolumeClaimSpec{}, 415 }, 416 "vac disabled with vac when vac exists in oldSpec": { 417 spec: core.PersistentVolumeClaimSpec{VolumeAttributesClassName: vacName}, 418 oldSpec: core.PersistentVolumeClaimSpec{VolumeAttributesClassName: vacName}, 419 vacEnabled: false, 420 wantVAC: vacName, 421 }, 422 "vac enabled with vac when vac doesn't exists in oldSpec": { 423 spec: core.PersistentVolumeClaimSpec{VolumeAttributesClassName: vacName}, 424 oldSpec: core.PersistentVolumeClaimSpec{}, 425 vacEnabled: true, 426 wantVAC: vacName, 427 }, 428 "vac enable with vac when vac exists in oldSpec": { 429 spec: core.PersistentVolumeClaimSpec{VolumeAttributesClassName: vacName}, 430 oldSpec: core.PersistentVolumeClaimSpec{VolumeAttributesClassName: vacName}, 431 vacEnabled: true, 432 wantVAC: vacName, 433 }, 434 } 435 436 for testName, test := range tests { 437 t.Run(testName, func(t *testing.T) { 438 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, test.vacEnabled)() 439 DropDisabledFields(&test.spec, &test.oldSpec) 440 if test.spec.VolumeAttributesClassName != test.wantVAC { 441 t.Errorf("expected vac was not met, test: %s, vacEnabled: %v, spec: %+v, expected VAC: %+v", 442 testName, test.vacEnabled, test.spec, test.wantVAC) 443 } 444 }) 445 } 446 } 447 448 func TestDropDisabledFieldsFromStatus(t *testing.T) { 449 tests := []struct { 450 name string 451 enableRecoverVolumeExpansionFailure bool 452 enableVolumeAttributesClass bool 453 pvc *core.PersistentVolumeClaim 454 oldPVC *core.PersistentVolumeClaim 455 expected *core.PersistentVolumeClaim 456 }{ 457 { 458 name: "for:newPVC=hasAllocatedResource,oldPVC=doesnot,featuregate=false; should drop field", 459 enableRecoverVolumeExpansionFailure: false, 460 enableVolumeAttributesClass: false, 461 pvc: withAllocatedResource("5G"), 462 oldPVC: getPVC(), 463 expected: getPVC(), 464 }, 465 { 466 name: "for:newPVC=hasAllocatedResource,oldPVC=doesnot,featuregate=RecoverVolumeExpansionFailure=true; should keep field", 467 enableRecoverVolumeExpansionFailure: true, 468 enableVolumeAttributesClass: false, 469 pvc: withAllocatedResource("5G"), 470 oldPVC: getPVC(), 471 expected: withAllocatedResource("5G"), 472 }, 473 { 474 name: "for:newPVC=hasAllocatedResource,oldPVC=hasAllocatedResource,featuregate=RecoverVolumeExpansionFailure=true; should keep field", 475 enableRecoverVolumeExpansionFailure: true, 476 enableVolumeAttributesClass: false, 477 pvc: withAllocatedResource("5G"), 478 oldPVC: withAllocatedResource("5G"), 479 expected: withAllocatedResource("5G"), 480 }, 481 { 482 name: "for:newPVC=hasAllocatedResource,oldPVC=hasAllocatedResource,featuregate=false; should keep field", 483 enableRecoverVolumeExpansionFailure: false, 484 enableVolumeAttributesClass: false, 485 pvc: withAllocatedResource("10G"), 486 oldPVC: withAllocatedResource("5G"), 487 expected: withAllocatedResource("10G"), 488 }, 489 { 490 name: "for:newPVC=hasAllocatedResource,oldPVC=nil,featuregate=false; should drop field", 491 enableRecoverVolumeExpansionFailure: false, 492 enableVolumeAttributesClass: false, 493 pvc: withAllocatedResource("5G"), 494 oldPVC: nil, 495 expected: getPVC(), 496 }, 497 { 498 name: "for:newPVC=hasResizeStatus,oldPVC=nil, featuregate=false should drop field", 499 enableRecoverVolumeExpansionFailure: false, 500 enableVolumeAttributesClass: false, 501 pvc: withResizeStatus(core.PersistentVolumeClaimNodeResizeFailed), 502 oldPVC: nil, 503 expected: getPVC(), 504 }, 505 { 506 name: "for:newPVC=hasResizeStatus,oldPVC=doesnot,featuregate=RecoverVolumeExpansionFailure=true; should keep field", 507 enableRecoverVolumeExpansionFailure: true, 508 enableVolumeAttributesClass: false, 509 pvc: withResizeStatus(core.PersistentVolumeClaimNodeResizeFailed), 510 oldPVC: getPVC(), 511 expected: withResizeStatus(core.PersistentVolumeClaimNodeResizeFailed), 512 }, 513 { 514 name: "for:newPVC=hasResizeStatus,oldPVC=hasResizeStatus,featuregate=RecoverVolumeExpansionFailure=true; should keep field", 515 enableRecoverVolumeExpansionFailure: true, 516 enableVolumeAttributesClass: false, 517 pvc: withResizeStatus(core.PersistentVolumeClaimNodeResizeFailed), 518 oldPVC: withResizeStatus(core.PersistentVolumeClaimNodeResizeFailed), 519 expected: withResizeStatus(core.PersistentVolumeClaimNodeResizeFailed), 520 }, 521 { 522 name: "for:newPVC=hasResizeStatus,oldPVC=hasResizeStatus,featuregate=false; should keep field", 523 enableRecoverVolumeExpansionFailure: false, 524 enableVolumeAttributesClass: false, 525 pvc: withResizeStatus(core.PersistentVolumeClaimNodeResizeFailed), 526 oldPVC: withResizeStatus(core.PersistentVolumeClaimNodeResizeFailed), 527 expected: withResizeStatus(core.PersistentVolumeClaimNodeResizeFailed), 528 }, 529 { 530 name: "for:newPVC=hasVolumeAttributeClass,oldPVC=nil, featuregate=false should drop field", 531 enableRecoverVolumeExpansionFailure: false, 532 enableVolumeAttributesClass: false, 533 pvc: withVolumeAttributesClassName("foo"), 534 oldPVC: nil, 535 expected: getPVC(), 536 }, 537 { 538 name: "for:newPVC=hasVolumeAttributeClass,oldPVC=doesnot,featuregate=VolumeAttributesClass=true; should keep field", 539 enableRecoverVolumeExpansionFailure: false, 540 enableVolumeAttributesClass: true, 541 pvc: withVolumeAttributesClassName("foo"), 542 oldPVC: getPVC(), 543 expected: withVolumeAttributesClassName("foo"), 544 }, 545 { 546 name: "for:newPVC=hasVolumeAttributeClass,oldPVC=hasVolumeAttributeClass,featuregate=VolumeAttributesClass=true; should keep field", 547 enableRecoverVolumeExpansionFailure: false, 548 enableVolumeAttributesClass: true, 549 pvc: withVolumeAttributesClassName("foo"), 550 oldPVC: withVolumeAttributesClassName("foo"), 551 expected: withVolumeAttributesClassName("foo"), 552 }, 553 { 554 name: "for:newPVC=hasVolumeAttributeClass,oldPVC=hasVolumeAttributeClass,featuregate=false; should keep field", 555 enableRecoverVolumeExpansionFailure: false, 556 enableVolumeAttributesClass: false, 557 pvc: withVolumeAttributesClassName("foo"), 558 oldPVC: withVolumeAttributesClassName("foo"), 559 expected: withVolumeAttributesClassName("foo"), 560 }, 561 { 562 name: "for:newPVC=hasVolumeAttributesModifyStatus,oldPVC=nil, featuregate=false should drop field", 563 enableRecoverVolumeExpansionFailure: false, 564 enableVolumeAttributesClass: false, 565 pvc: withVolumeAttributesModifyStatus("bar", core.PersistentVolumeClaimModifyVolumePending), 566 oldPVC: nil, 567 expected: getPVC(), 568 }, 569 { 570 name: "for:newPVC=hasVolumeAttributesModifyStatus,oldPVC=doesnot,featuregate=VolumeAttributesClass=true; should keep field", 571 enableRecoverVolumeExpansionFailure: false, 572 enableVolumeAttributesClass: true, 573 pvc: withVolumeAttributesModifyStatus("bar", core.PersistentVolumeClaimModifyVolumePending), 574 oldPVC: getPVC(), 575 expected: withVolumeAttributesModifyStatus("bar", core.PersistentVolumeClaimModifyVolumePending), 576 }, 577 { 578 name: "for:newPVC=hasVolumeAttributesModifyStatus,oldPVC=hasVolumeAttributesModifyStatus,featuregate=VolumeAttributesClass=true; should keep field", 579 enableRecoverVolumeExpansionFailure: false, 580 enableVolumeAttributesClass: true, 581 pvc: withVolumeAttributesModifyStatus("bar", core.PersistentVolumeClaimModifyVolumePending), 582 oldPVC: withVolumeAttributesModifyStatus("bar", core.PersistentVolumeClaimModifyVolumePending), 583 expected: withVolumeAttributesModifyStatus("bar", core.PersistentVolumeClaimModifyVolumePending), 584 }, 585 { 586 name: "for:newPVC=hasVolumeAttributesModifyStatus,oldPVC=hasVolumeAttributesModifyStatus,featuregate=false; should keep field", 587 enableRecoverVolumeExpansionFailure: false, 588 enableVolumeAttributesClass: false, 589 pvc: withVolumeAttributesModifyStatus("bar", core.PersistentVolumeClaimModifyVolumePending), 590 oldPVC: withVolumeAttributesModifyStatus("bar", core.PersistentVolumeClaimModifyVolumePending), 591 expected: withVolumeAttributesModifyStatus("bar", core.PersistentVolumeClaimModifyVolumePending), 592 }, 593 } 594 595 for _, test := range tests { 596 t.Run(test.name, func(t *testing.T) { 597 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.RecoverVolumeExpansionFailure, test.enableRecoverVolumeExpansionFailure)() 598 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, test.enableVolumeAttributesClass)() 599 600 DropDisabledFieldsFromStatus(test.pvc, test.oldPVC) 601 602 if !reflect.DeepEqual(*test.expected, *test.pvc) { 603 t.Errorf("Unexpected change: %+v", cmp.Diff(test.expected, test.pvc)) 604 } 605 }) 606 } 607 } 608 609 func getPVC() *core.PersistentVolumeClaim { 610 return &core.PersistentVolumeClaim{} 611 } 612 613 func withAllocatedResource(q string) *core.PersistentVolumeClaim { 614 return &core.PersistentVolumeClaim{ 615 Status: core.PersistentVolumeClaimStatus{ 616 AllocatedResources: core.ResourceList{ 617 core.ResourceStorage: resource.MustParse(q), 618 }, 619 }, 620 } 621 } 622 623 func withResizeStatus(status core.ClaimResourceStatus) *core.PersistentVolumeClaim { 624 return &core.PersistentVolumeClaim{ 625 Status: core.PersistentVolumeClaimStatus{ 626 AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{ 627 core.ResourceStorage: status, 628 }, 629 }, 630 } 631 } 632 633 func withVolumeAttributesClassName(vacName string) *core.PersistentVolumeClaim { 634 return &core.PersistentVolumeClaim{ 635 Status: core.PersistentVolumeClaimStatus{ 636 CurrentVolumeAttributesClassName: &vacName, 637 }, 638 } 639 } 640 641 func withVolumeAttributesModifyStatus(target string, status core.PersistentVolumeClaimModifyVolumeStatus) *core.PersistentVolumeClaim { 642 return &core.PersistentVolumeClaim{ 643 Status: core.PersistentVolumeClaimStatus{ 644 ModifyVolumeStatus: &core.ModifyVolumeStatus{ 645 TargetVolumeAttributesClassName: target, 646 Status: status, 647 }, 648 }, 649 } 650 } 651 652 func TestWarnings(t *testing.T) { 653 testcases := []struct { 654 name string 655 template *core.PersistentVolumeClaim 656 expected []string 657 }{ 658 { 659 name: "null", 660 template: nil, 661 expected: nil, 662 }, 663 { 664 name: "200Mi requests no warning", 665 template: &core.PersistentVolumeClaim{ 666 Spec: core.PersistentVolumeClaimSpec{ 667 Resources: core.VolumeResourceRequirements{ 668 Requests: core.ResourceList{ 669 core.ResourceStorage: resource.MustParse("200Mi"), 670 }, 671 Limits: core.ResourceList{ 672 core.ResourceStorage: resource.MustParse("200Mi"), 673 }, 674 }, 675 }, 676 }, 677 expected: nil, 678 }, 679 { 680 name: "200m warning", 681 template: &core.PersistentVolumeClaim{ 682 Spec: core.PersistentVolumeClaimSpec{ 683 Resources: core.VolumeResourceRequirements{ 684 Requests: core.ResourceList{ 685 core.ResourceStorage: resource.MustParse("200m"), 686 }, 687 Limits: core.ResourceList{ 688 core.ResourceStorage: resource.MustParse("100m"), 689 }, 690 }, 691 }, 692 }, 693 expected: []string{ 694 `spec.resources.requests[storage]: fractional byte value "200m" is invalid, must be an integer`, 695 `spec.resources.limits[storage]: fractional byte value "100m" is invalid, must be an integer`, 696 }, 697 }, 698 { 699 name: "integer no warning", 700 template: &core.PersistentVolumeClaim{ 701 Spec: core.PersistentVolumeClaimSpec{ 702 Resources: core.VolumeResourceRequirements{ 703 Requests: core.ResourceList{ 704 core.ResourceStorage: resource.MustParse("200"), 705 }, 706 }, 707 }, 708 }, 709 expected: nil, 710 }, 711 { 712 name: "storageclass annotations warning", 713 template: &core.PersistentVolumeClaim{ 714 ObjectMeta: metav1.ObjectMeta{ 715 Name: "foo", 716 Annotations: map[string]string{ 717 core.BetaStorageClassAnnotation: "", 718 }, 719 }, 720 }, 721 expected: []string{ 722 `metadata.annotations[volume.beta.kubernetes.io/storage-class]: deprecated since v1.8; use "storageClassName" attribute instead`, 723 }, 724 }, 725 } 726 727 for _, tc := range testcases { 728 t.Run("pvcspec_"+tc.name, func(t *testing.T) { 729 actual := sets.NewString(GetWarningsForPersistentVolumeClaim(tc.template)...) 730 expected := sets.NewString(tc.expected...) 731 for _, missing := range expected.Difference(actual).List() { 732 t.Errorf("missing: %s", missing) 733 } 734 for _, extra := range actual.Difference(expected).List() { 735 t.Errorf("extra: %s", extra) 736 } 737 }) 738 739 } 740 }