k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/e2e/storage/csimock/csi_volume_expansion.go (about) 1 /* 2 Copyright 2022 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 csimock 18 19 import ( 20 "context" 21 "fmt" 22 "time" 23 24 csipbv1 "github.com/container-storage-interface/spec/lib/go/csi" 25 "github.com/onsi/ginkgo/v2" 26 "github.com/onsi/gomega" 27 28 "google.golang.org/grpc/codes" 29 "google.golang.org/grpc/status" 30 v1 "k8s.io/api/core/v1" 31 "k8s.io/apimachinery/pkg/api/resource" 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 "k8s.io/apimachinery/pkg/util/wait" 34 clientset "k8s.io/client-go/kubernetes" 35 "k8s.io/kubernetes/test/e2e/feature" 36 "k8s.io/kubernetes/test/e2e/framework" 37 e2epod "k8s.io/kubernetes/test/e2e/framework/pod" 38 "k8s.io/kubernetes/test/e2e/storage/drivers" 39 "k8s.io/kubernetes/test/e2e/storage/testsuites" 40 "k8s.io/kubernetes/test/e2e/storage/utils" 41 admissionapi "k8s.io/pod-security-admission/api" 42 ) 43 44 type expansionStatus int 45 46 const ( 47 expansionSuccess = iota 48 expansionFailedOnController 49 expansionFailedOnNode 50 expansionFailedMissingStagingPath 51 ) 52 53 const ( 54 resizePollInterval = 2 * time.Second 55 ) 56 57 var ( 58 maxControllerSizeLimit = resource.MustParse("10Gi") 59 60 maxNodeExpansionLimit = resource.MustParse("8Gi") 61 ) 62 63 type recoveryTest struct { 64 name string 65 pvcRequestSize string 66 allocatedResource string 67 simulatedCSIDriverError expansionStatus 68 expectedResizeStatus v1.ClaimResourceStatus 69 recoverySize resource.Quantity 70 } 71 72 var _ = utils.SIGDescribe("CSI Mock volume expansion", func() { 73 f := framework.NewDefaultFramework("csi-mock-volumes-expansion") 74 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged 75 m := newMockDriverSetup(f) 76 77 ginkgo.Context("CSI Volume expansion", func() { 78 tests := []struct { 79 name string 80 nodeExpansionRequired bool 81 disableAttach bool 82 disableResizingOnDriver bool 83 simulatedCSIDriverError expansionStatus 84 expectFailure bool 85 }{ 86 { 87 name: "should expand volume without restarting pod if nodeExpansion=off", 88 nodeExpansionRequired: false, 89 simulatedCSIDriverError: expansionSuccess, 90 }, 91 { 92 name: "should expand volume by restarting pod if attach=on, nodeExpansion=on", 93 nodeExpansionRequired: true, 94 simulatedCSIDriverError: expansionSuccess, 95 }, 96 { 97 name: "should not have staging_path missing in node expand volume pod if attach=on, nodeExpansion=on", 98 nodeExpansionRequired: true, 99 simulatedCSIDriverError: expansionFailedMissingStagingPath, 100 }, 101 { 102 name: "should expand volume by restarting pod if attach=off, nodeExpansion=on", 103 disableAttach: true, 104 nodeExpansionRequired: true, 105 simulatedCSIDriverError: expansionSuccess, 106 }, 107 { 108 name: "should not expand volume if resizingOnDriver=off, resizingOnSC=on", 109 disableResizingOnDriver: true, 110 expectFailure: true, 111 simulatedCSIDriverError: expansionSuccess, 112 }, 113 } 114 for _, t := range tests { 115 test := t 116 ginkgo.It(t.name, func(ctx context.Context) { 117 var err error 118 tp := testParameters{ 119 enableResizing: true, 120 enableNodeExpansion: test.nodeExpansionRequired, 121 disableResizingOnDriver: test.disableResizingOnDriver, 122 } 123 // disabling attach requires drive registration feature 124 if test.disableAttach { 125 tp.disableAttach = true 126 tp.registerDriver = true 127 } 128 tp.hooks = createExpansionHook(test.simulatedCSIDriverError) 129 130 m.init(ctx, tp) 131 ginkgo.DeferCleanup(m.cleanup) 132 133 sc, pvc, pod := m.createPod(ctx, pvcReference) 134 gomega.Expect(pod).NotTo(gomega.BeNil(), "while creating pod for resizing") 135 136 if !*sc.AllowVolumeExpansion { 137 framework.Fail("failed creating sc with allowed expansion") 138 } 139 140 err = e2epod.WaitForPodNameRunningInNamespace(ctx, m.cs, pod.Name, pod.Namespace) 141 framework.ExpectNoError(err, "Failed to start pod1: %v", err) 142 143 ginkgo.By("Expanding current pvc") 144 newSize := resource.MustParse("6Gi") 145 newPVC, err := testsuites.ExpandPVCSize(ctx, pvc, newSize, m.cs) 146 framework.ExpectNoError(err, "While updating pvc for more size") 147 pvc = newPVC 148 gomega.Expect(pvc).NotTo(gomega.BeNil()) 149 150 pvcSize := pvc.Spec.Resources.Requests[v1.ResourceStorage] 151 if pvcSize.Cmp(newSize) != 0 { 152 framework.Failf("error updating pvc size %q", pvc.Name) 153 } 154 if test.expectFailure { 155 gomega.Consistently(ctx, framework.GetObject(m.cs.CoreV1().PersistentVolumeClaims(pvc.Namespace).Get, pvc.Name, metav1.GetOptions{})).WithTimeout(csiResizingConditionWait). 156 ShouldNot(gomega.HaveField("Status.Conditions", gomega.ContainElement(gomega.HaveField("Type", gomega.Equal("PersistentVolumeClaimResizing")))), "unexpected resizing condition on PVC") 157 return 158 } 159 ginkgo.By("Waiting for persistent volume resize to finish") 160 err = testsuites.WaitForControllerVolumeResize(ctx, pvc, m.cs, csiResizeWaitPeriod) 161 framework.ExpectNoError(err, "While waiting for CSI PV resize to finish") 162 163 checkPVCSize := func() { 164 ginkgo.By("Waiting for PVC resize to finish") 165 pvc, err = testsuites.WaitForFSResize(ctx, pvc, m.cs) 166 framework.ExpectNoError(err, "while waiting for PVC resize to finish") 167 168 pvcConditions := pvc.Status.Conditions 169 gomega.Expect(pvcConditions).To(gomega.BeEmpty(), "pvc should not have conditions") 170 } 171 172 // if node expansion is not required PVC should be resized as well 173 if !test.nodeExpansionRequired { 174 checkPVCSize() 175 } else { 176 ginkgo.By("Checking for conditions on pvc") 177 npvc, err := testsuites.WaitForPendingFSResizeCondition(ctx, pvc, m.cs) 178 framework.ExpectNoError(err, "While waiting for pvc to have fs resizing condition") 179 pvc = npvc 180 181 inProgressConditions := pvc.Status.Conditions 182 if len(inProgressConditions) > 0 { 183 gomega.Expect(inProgressConditions[0].Type).To(gomega.Equal(v1.PersistentVolumeClaimFileSystemResizePending), "pvc must have fs resizing condition") 184 } 185 186 ginkgo.By("Deleting the previously created pod") 187 if test.simulatedCSIDriverError == expansionFailedMissingStagingPath { 188 e2epod.DeletePodOrFail(ctx, m.cs, pod.Namespace, pod.Name) 189 } else { 190 err = e2epod.DeletePodWithWait(ctx, m.cs, pod) 191 framework.ExpectNoError(err, "while deleting pod for resizing") 192 } 193 194 ginkgo.By("Creating a new pod with same volume") 195 pod2, err := m.createPodWithPVC(pvc) 196 gomega.Expect(pod2).NotTo(gomega.BeNil(), "while creating pod for csi resizing") 197 framework.ExpectNoError(err, "while recreating pod for resizing") 198 199 checkPVCSize() 200 } 201 }) 202 } 203 }) 204 ginkgo.Context("CSI online volume expansion with secret", func() { 205 var stringSecret = map[string]string{ 206 "username": "admin", 207 "password": "t0p-Secret", 208 } 209 trackedCalls := []string{ 210 "NodeExpandVolume", 211 } 212 tests := []struct { 213 name string 214 disableAttach bool 215 expectedCalls []csiCall 216 217 // Called for each NodeExpandVolume calls, with counter incremented atomically before 218 // the invocation (i.e first value will be 1). 219 nodeExpandHook func(counter int64) error 220 }{ 221 { 222 name: "should expand volume without restarting pod if attach=on, nodeExpansion=on, csiNodeExpandSecret=on", 223 expectedCalls: []csiCall{ 224 {expectedMethod: "NodeExpandVolume", expectedError: codes.OK, expectedSecret: stringSecret}, 225 }, 226 }, 227 } 228 for _, t := range tests { 229 test := t 230 ginkgo.It(test.name, func(ctx context.Context) { 231 var ( 232 err error 233 hooks *drivers.Hooks 234 secretName = "test-secret" 235 secret = &v1.Secret{ 236 ObjectMeta: metav1.ObjectMeta{ 237 Namespace: f.Namespace.Name, 238 Name: secretName, 239 }, 240 StringData: stringSecret, 241 } 242 ) 243 if test.nodeExpandHook != nil { 244 hooks = createPreHook("NodeExpandVolume", test.nodeExpandHook) 245 } 246 params := testParameters{enableResizing: true, enableNodeExpansion: true, enableCSINodeExpandSecret: true, hooks: hooks} 247 if test.disableAttach { 248 params.disableAttach = true 249 params.registerDriver = true 250 } 251 252 m.init(ctx, params) 253 ginkgo.DeferCleanup(m.cleanup) 254 255 if secret, err := m.cs.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), secret, metav1.CreateOptions{}); err != nil { 256 framework.Failf("unable to create test secret %s: %v", secret.Name, err) 257 } 258 259 sc, pvc, pod := m.createPod(ctx, pvcReference) 260 gomega.Expect(pod).NotTo(gomega.BeNil(), "while creating pod for resizing") 261 262 if !*sc.AllowVolumeExpansion { 263 framework.Fail("failed creating sc with allowed expansion") 264 } 265 if sc.Parameters == nil { 266 framework.Fail("failed creating sc with secret") 267 } 268 if _, ok := sc.Parameters[csiNodeExpandSecretKey]; !ok { 269 framework.Failf("creating sc without %s", csiNodeExpandSecretKey) 270 } 271 if _, ok := sc.Parameters[csiNodeExpandSecretNamespaceKey]; !ok { 272 framework.Failf("creating sc without %s", csiNodeExpandSecretNamespaceKey) 273 } 274 err = e2epod.WaitForPodNameRunningInNamespace(ctx, m.cs, pod.Name, pod.Namespace) 275 framework.ExpectNoError(err, "Failed to start pod1: %v", err) 276 277 pvc, err = m.cs.CoreV1().PersistentVolumeClaims(pvc.Namespace).Get(ctx, pvc.Name, metav1.GetOptions{}) 278 if err != nil { 279 framework.Failf("failed to get pvc %s, %v", pvc.Name, err) 280 } 281 gomega.Expect(pvc.Spec.VolumeName).ShouldNot(gomega.BeEquivalentTo(""), "while provisioning a volume for resizing") 282 pv, err := m.cs.CoreV1().PersistentVolumes().Get(ctx, pvc.Spec.VolumeName, metav1.GetOptions{}) 283 if err != nil { 284 framework.Failf("failed to get pv %s, %v", pvc.Spec.VolumeName, err) 285 } 286 if pv.Spec.CSI == nil || pv.Spec.CSI.NodeExpandSecretRef == nil { 287 framework.Fail("creating pv without 'NodeExpandSecretRef'") 288 } 289 if pv.Spec.CSI.NodeExpandSecretRef.Namespace != f.Namespace.Name || pv.Spec.CSI.NodeExpandSecretRef.Name != secretName { 290 framework.Failf("failed to set node expand secret ref, namespace: %s name: %s", pv.Spec.CSI.NodeExpandSecretRef.Namespace, pv.Spec.CSI.NodeExpandSecretRef.Name) 291 } 292 293 ginkgo.By("Expanding current pvc") 294 newSize := resource.MustParse("6Gi") 295 newPVC, err := testsuites.ExpandPVCSize(ctx, pvc, newSize, m.cs) 296 framework.ExpectNoError(err, "While updating pvc for more size") 297 pvc = newPVC 298 gomega.Expect(pvc).NotTo(gomega.BeNil()) 299 300 pvcSize := pvc.Spec.Resources.Requests[v1.ResourceStorage] 301 if pvcSize.Cmp(newSize) != 0 { 302 framework.Failf("error updating pvc size %q", pvc.Name) 303 } 304 305 ginkgo.By("Waiting for persistent volume resize to finish") 306 err = testsuites.WaitForControllerVolumeResize(ctx, pvc, m.cs, csiResizeWaitPeriod) 307 framework.ExpectNoError(err, "While waiting for PV resize to finish") 308 309 ginkgo.By("Waiting for PVC resize to finish") 310 pvc, err = testsuites.WaitForFSResize(ctx, pvc, m.cs) 311 framework.ExpectNoError(err, "while waiting for PVC to finish") 312 313 ginkgo.By("Waiting for all remaining expected CSI calls") 314 err = wait.Poll(time.Second, csiResizeWaitPeriod, func() (done bool, err error) { 315 var index int 316 _, index, err = compareCSICalls(ctx, trackedCalls, test.expectedCalls, m.driver.GetCalls) 317 if err != nil { 318 return true, err 319 } 320 if index == 0 { 321 // No CSI call received yet 322 return false, nil 323 } 324 if len(test.expectedCalls) == index { 325 // all calls received 326 return true, nil 327 } 328 return false, nil 329 }) 330 framework.ExpectNoError(err, "while waiting for all CSI calls") 331 332 pvcConditions := pvc.Status.Conditions 333 gomega.Expect(pvcConditions).To(gomega.BeEmpty(), "pvc should not have conditions") 334 }) 335 } 336 }) 337 ginkgo.Context("CSI online volume expansion", func() { 338 tests := []struct { 339 name string 340 disableAttach bool 341 }{ 342 { 343 name: "should expand volume without restarting pod if attach=on, nodeExpansion=on", 344 }, 345 { 346 name: "should expand volume without restarting pod if attach=off, nodeExpansion=on", 347 disableAttach: true, 348 }, 349 } 350 for _, t := range tests { 351 test := t 352 ginkgo.It(test.name, func(ctx context.Context) { 353 var err error 354 params := testParameters{enableResizing: true, enableNodeExpansion: true} 355 if test.disableAttach { 356 params.disableAttach = true 357 params.registerDriver = true 358 } 359 360 m.init(ctx, params) 361 ginkgo.DeferCleanup(m.cleanup) 362 363 sc, pvc, pod := m.createPod(ctx, pvcReference) 364 gomega.Expect(pod).NotTo(gomega.BeNil(), "while creating pod for resizing") 365 366 if !*sc.AllowVolumeExpansion { 367 framework.Fail("failed creating sc with allowed expansion") 368 } 369 370 err = e2epod.WaitForPodNameRunningInNamespace(ctx, m.cs, pod.Name, pod.Namespace) 371 framework.ExpectNoError(err, "Failed to start pod1: %v", err) 372 373 ginkgo.By("Expanding current pvc") 374 newSize := resource.MustParse("6Gi") 375 newPVC, err := testsuites.ExpandPVCSize(ctx, pvc, newSize, m.cs) 376 framework.ExpectNoError(err, "While updating pvc for more size") 377 pvc = newPVC 378 gomega.Expect(pvc).NotTo(gomega.BeNil()) 379 380 pvcSize := pvc.Spec.Resources.Requests[v1.ResourceStorage] 381 if pvcSize.Cmp(newSize) != 0 { 382 framework.Failf("error updating pvc size %q", pvc.Name) 383 } 384 385 ginkgo.By("Waiting for persistent volume resize to finish") 386 err = testsuites.WaitForControllerVolumeResize(ctx, pvc, m.cs, csiResizeWaitPeriod) 387 framework.ExpectNoError(err, "While waiting for PV resize to finish") 388 389 ginkgo.By("Waiting for PVC resize to finish") 390 pvc, err = testsuites.WaitForFSResize(ctx, pvc, m.cs) 391 framework.ExpectNoError(err, "while waiting for PVC to finish") 392 393 pvcConditions := pvc.Status.Conditions 394 gomega.Expect(pvcConditions).To(gomega.BeEmpty(), "pvc should not have conditions") 395 396 }) 397 } 398 }) 399 400 f.Context("Expansion with recovery", feature.RecoverVolumeExpansionFailure, func() { 401 tests := []recoveryTest{ 402 { 403 name: "should record target size in allocated resources", 404 pvcRequestSize: "4Gi", 405 allocatedResource: "4Gi", 406 simulatedCSIDriverError: expansionSuccess, 407 expectedResizeStatus: "", 408 }, 409 { 410 name: "should allow recovery if controller expansion fails with final error", 411 pvcRequestSize: "11Gi", // expansion to 11Gi will cause expansion to fail on controller 412 allocatedResource: "11Gi", 413 simulatedCSIDriverError: expansionFailedOnController, 414 expectedResizeStatus: v1.PersistentVolumeClaimControllerResizeFailed, 415 recoverySize: resource.MustParse("4Gi"), 416 }, 417 { 418 name: "recovery should not be possible in partially expanded volumes", 419 pvcRequestSize: "9Gi", // expansion to 9Gi will cause expansion to fail on node 420 allocatedResource: "9Gi", 421 simulatedCSIDriverError: expansionFailedOnNode, 422 expectedResizeStatus: v1.PersistentVolumeClaimNodeResizeFailed, 423 recoverySize: resource.MustParse("5Gi"), 424 }, 425 } 426 427 for _, t := range tests { 428 test := t 429 ginkgo.It(test.name, func(ctx context.Context) { 430 var err error 431 params := testParameters{enableResizing: true, enableNodeExpansion: true, enableRecoverExpansionFailure: true} 432 433 if test.simulatedCSIDriverError != expansionSuccess { 434 params.hooks = createExpansionHook(test.simulatedCSIDriverError) 435 } 436 437 m.init(ctx, params) 438 ginkgo.DeferCleanup(m.cleanup) 439 440 sc, pvc, pod := m.createPod(ctx, pvcReference) 441 gomega.Expect(pod).NotTo(gomega.BeNil(), "while creating pod for resizing") 442 443 if !*sc.AllowVolumeExpansion { 444 framework.Fail("failed creating sc with allowed expansion") 445 } 446 447 err = e2epod.WaitForPodNameRunningInNamespace(ctx, m.cs, pod.Name, pod.Namespace) 448 framework.ExpectNoError(err, "Failed to start pod1: %v", err) 449 450 ginkgo.By("Expanding current pvc") 451 newSize := resource.MustParse(test.pvcRequestSize) 452 newPVC, err := testsuites.ExpandPVCSize(ctx, pvc, newSize, m.cs) 453 framework.ExpectNoError(err, "While updating pvc for more size") 454 pvc = newPVC 455 gomega.Expect(pvc).NotTo(gomega.BeNil()) 456 457 pvcSize := pvc.Spec.Resources.Requests[v1.ResourceStorage] 458 if pvcSize.Cmp(newSize) != 0 { 459 framework.Failf("error updating pvc size %q", pvc.Name) 460 } 461 462 if test.simulatedCSIDriverError == expansionSuccess { 463 validateExpansionSuccess(ctx, pvc, m, test, test.allocatedResource) 464 } else { 465 validateRecoveryBehaviour(ctx, pvc, m, test) 466 } 467 }) 468 } 469 470 }) 471 }) 472 473 func validateRecoveryBehaviour(ctx context.Context, pvc *v1.PersistentVolumeClaim, m *mockDriverSetup, test recoveryTest) { 474 var err error 475 ginkgo.By("Waiting for resizer to set allocated resource") 476 err = waitForAllocatedResource(pvc, m, test.allocatedResource) 477 framework.ExpectNoError(err, "While waiting for allocated resource to be updated") 478 479 ginkgo.By("Waiting for resizer to set resize status") 480 err = waitForResizeStatus(pvc, m.cs, test.expectedResizeStatus) 481 framework.ExpectNoError(err, "While waiting for resize status to be set") 482 483 ginkgo.By("Recover pvc size") 484 newPVC, err := testsuites.ExpandPVCSize(ctx, pvc, test.recoverySize, m.cs) 485 framework.ExpectNoError(err, "While updating pvc for more size") 486 pvc = newPVC 487 gomega.Expect(pvc).NotTo(gomega.BeNil()) 488 489 pvcSize := pvc.Spec.Resources.Requests[v1.ResourceStorage] 490 if pvcSize.Cmp(test.recoverySize) != 0 { 491 framework.Failf("error updating pvc size %q", pvc.Name) 492 } 493 494 // if expansion failed on controller with final error, then recovery should be possible 495 if test.simulatedCSIDriverError == expansionFailedOnController { 496 validateExpansionSuccess(ctx, pvc, m, test, test.recoverySize.String()) 497 return 498 } 499 500 // if expansion succeeded on controller but failed on the node 501 if test.simulatedCSIDriverError == expansionFailedOnNode { 502 ginkgo.By("Wait for expansion to fail on node again") 503 err = waitForResizeStatus(pvc, m.cs, v1.PersistentVolumeClaimNodeResizeFailed) 504 framework.ExpectNoError(err, "While waiting for resize status to be set to expansion-failed-on-node") 505 506 ginkgo.By("verify allocated resources after recovery") 507 pvc, err = m.cs.CoreV1().PersistentVolumeClaims(pvc.Namespace).Get(context.TODO(), pvc.Name, metav1.GetOptions{}) 508 framework.ExpectNoError(err, "while fetching pvc") 509 actualAllocatedResource := pvc.Status.AllocatedResources.Storage() 510 511 if actualAllocatedResource.Equal(test.recoverySize) { 512 framework.Failf("unexpected allocated resource size %s after node expansion failure", actualAllocatedResource.String()) 513 } 514 515 if !actualAllocatedResource.Equal(resource.MustParse(test.allocatedResource)) { 516 framework.Failf("expected allocated resources to be %s got %s", test.allocatedResource, actualAllocatedResource.String()) 517 } 518 } 519 } 520 521 func validateExpansionSuccess(ctx context.Context, pvc *v1.PersistentVolumeClaim, m *mockDriverSetup, test recoveryTest, expectedAllocatedSize string) { 522 var err error 523 ginkgo.By("Waiting for persistent volume resize to finish") 524 err = testsuites.WaitForControllerVolumeResize(ctx, pvc, m.cs, csiResizeWaitPeriod) 525 framework.ExpectNoError(err, "While waiting for PV resize to finish") 526 527 ginkgo.By("Waiting for PVC resize to finish") 528 pvc, err = testsuites.WaitForFSResize(ctx, pvc, m.cs) 529 framework.ExpectNoError(err, "while waiting for PVC to finish") 530 531 pvcConditions := pvc.Status.Conditions 532 gomega.Expect(pvcConditions).To(gomega.BeEmpty(), "pvc should not have conditions") 533 allocatedResource := pvc.Status.AllocatedResources.Storage() 534 gomega.Expect(allocatedResource).NotTo(gomega.BeNil()) 535 expectedAllocatedResource := resource.MustParse(expectedAllocatedSize) 536 if allocatedResource.Cmp(expectedAllocatedResource) != 0 { 537 framework.Failf("expected allocated Resources to be %s got %s", expectedAllocatedResource.String(), allocatedResource.String()) 538 } 539 540 resizeStatus := pvc.Status.AllocatedResourceStatuses[v1.ResourceStorage] 541 gomega.Expect(resizeStatus).To(gomega.BeZero(), "resize status should be empty") 542 } 543 544 func waitForResizeStatus(pvc *v1.PersistentVolumeClaim, c clientset.Interface, expectedState v1.ClaimResourceStatus) error { 545 var actualResizeStatus *v1.ClaimResourceStatus 546 547 waitErr := wait.PollImmediate(resizePollInterval, csiResizeWaitPeriod, func() (bool, error) { 548 var err error 549 updatedPVC, err := c.CoreV1().PersistentVolumeClaims(pvc.Namespace).Get(context.TODO(), pvc.Name, metav1.GetOptions{}) 550 551 if err != nil { 552 return false, fmt.Errorf("error fetching pvc %q for checking for resize status: %w", pvc.Name, err) 553 } 554 555 actualResizeStatus := updatedPVC.Status.AllocatedResourceStatuses[v1.ResourceStorage] 556 return (actualResizeStatus == expectedState), nil 557 }) 558 if waitErr != nil { 559 return fmt.Errorf("error while waiting for resize status to sync to %v, actualStatus %s: %v", expectedState, *actualResizeStatus, waitErr) 560 } 561 return nil 562 } 563 564 func waitForAllocatedResource(pvc *v1.PersistentVolumeClaim, m *mockDriverSetup, expectedSize string) error { 565 expectedQuantity := resource.MustParse(expectedSize) 566 waitErr := wait.PollImmediate(resizePollInterval, csiResizeWaitPeriod, func() (bool, error) { 567 var err error 568 updatedPVC, err := m.cs.CoreV1().PersistentVolumeClaims(pvc.Namespace).Get(context.TODO(), pvc.Name, metav1.GetOptions{}) 569 570 if err != nil { 571 return false, fmt.Errorf("error fetching pvc %q for checking for resize status: %w", pvc.Name, err) 572 } 573 actualAllocatedSize := updatedPVC.Status.AllocatedResources.Storage() 574 if actualAllocatedSize != nil && actualAllocatedSize.Equal(expectedQuantity) { 575 return true, nil 576 } 577 return false, nil 578 579 }) 580 if waitErr != nil { 581 return fmt.Errorf("error while waiting for allocatedSize to sync to %s: %v", expectedSize, waitErr) 582 } 583 return nil 584 } 585 586 func createExpansionHook(expectedExpansionStatus expansionStatus) *drivers.Hooks { 587 return &drivers.Hooks{ 588 Pre: func(ctx context.Context, method string, request interface{}) (reply interface{}, err error) { 589 switch expectedExpansionStatus { 590 case expansionFailedMissingStagingPath: 591 expansionRequest, ok := request.(*csipbv1.NodeExpandVolumeRequest) 592 if ok { 593 stagingPath := expansionRequest.StagingTargetPath 594 if stagingPath == "" { 595 return nil, status.Error(codes.InvalidArgument, "invalid node expansion request, missing staging path") 596 } 597 598 } 599 case expansionFailedOnController: 600 expansionRequest, ok := request.(*csipbv1.ControllerExpandVolumeRequest) 601 if ok { 602 requestedSize := resource.NewQuantity(expansionRequest.CapacityRange.RequiredBytes, resource.BinarySI) 603 if requestedSize.Cmp(maxControllerSizeLimit) > 0 { 604 return nil, status.Error(codes.InvalidArgument, "invalid expansion request") 605 } 606 } 607 case expansionFailedOnNode: 608 expansionRequest, ok := request.(*csipbv1.NodeExpandVolumeRequest) 609 if ok { 610 requestedSize := resource.NewQuantity(expansionRequest.CapacityRange.RequiredBytes, resource.BinarySI) 611 if requestedSize.Cmp(maxNodeExpansionLimit) > 0 { 612 return nil, status.Error(codes.InvalidArgument, "invalid node expansion request") 613 } 614 615 } 616 } 617 618 return nil, nil 619 }, 620 } 621 }