k8s.io/kubernetes@v1.29.3/test/e2e/storage/csi_mock/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 csi_mock
    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  	"google.golang.org/grpc/codes"
    28  	"google.golang.org/grpc/status"
    29  	v1 "k8s.io/api/core/v1"
    30  	"k8s.io/apimachinery/pkg/api/resource"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/apimachinery/pkg/util/wait"
    33  	clientset "k8s.io/client-go/kubernetes"
    34  	"k8s.io/kubernetes/test/e2e/feature"
    35  	"k8s.io/kubernetes/test/e2e/framework"
    36  	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
    37  	"k8s.io/kubernetes/test/e2e/storage/drivers"
    38  	"k8s.io/kubernetes/test/e2e/storage/testsuites"
    39  	"k8s.io/kubernetes/test/e2e/storage/utils"
    40  	admissionapi "k8s.io/pod-security-admission/api"
    41  )
    42  
    43  type expansionStatus int
    44  
    45  const (
    46  	expansionSuccess = iota
    47  	expansionFailedOnController
    48  	expansionFailedOnNode
    49  	expansionFailedMissingStagingPath
    50  )
    51  
    52  const (
    53  	resizePollInterval = 2 * time.Second
    54  )
    55  
    56  var (
    57  	maxControllerSizeLimit = resource.MustParse("10Gi")
    58  
    59  	maxNodeExpansionLimit = resource.MustParse("8Gi")
    60  )
    61  
    62  type recoveryTest struct {
    63  	name                    string
    64  	pvcRequestSize          string
    65  	allocatedResource       string
    66  	simulatedCSIDriverError expansionStatus
    67  	expectedResizeStatus    v1.ClaimResourceStatus
    68  	recoverySize            resource.Quantity
    69  }
    70  
    71  var _ = utils.SIGDescribe("CSI Mock volume expansion", func() {
    72  	f := framework.NewDefaultFramework("csi-mock-volumes-expansion")
    73  	f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
    74  	m := newMockDriverSetup(f)
    75  
    76  	ginkgo.Context("CSI Volume expansion", func() {
    77  		tests := []struct {
    78  			name                    string
    79  			nodeExpansionRequired   bool
    80  			disableAttach           bool
    81  			disableResizingOnDriver bool
    82  			simulatedCSIDriverError expansionStatus
    83  			expectFailure           bool
    84  		}{
    85  			{
    86  				name:                    "should expand volume without restarting pod if nodeExpansion=off",
    87  				nodeExpansionRequired:   false,
    88  				simulatedCSIDriverError: expansionSuccess,
    89  			},
    90  			{
    91  				name:                    "should expand volume by restarting pod if attach=on, nodeExpansion=on",
    92  				nodeExpansionRequired:   true,
    93  				simulatedCSIDriverError: expansionSuccess,
    94  			},
    95  			{
    96  				name:                    "should not have staging_path missing in node expand volume pod if attach=on, nodeExpansion=on",
    97  				nodeExpansionRequired:   true,
    98  				simulatedCSIDriverError: expansionFailedMissingStagingPath,
    99  			},
   100  			{
   101  				name:                    "should expand volume by restarting pod if attach=off, nodeExpansion=on",
   102  				disableAttach:           true,
   103  				nodeExpansionRequired:   true,
   104  				simulatedCSIDriverError: expansionSuccess,
   105  			},
   106  			{
   107  				name:                    "should not expand volume if resizingOnDriver=off, resizingOnSC=on",
   108  				disableResizingOnDriver: true,
   109  				expectFailure:           true,
   110  				simulatedCSIDriverError: expansionSuccess,
   111  			},
   112  		}
   113  		for _, t := range tests {
   114  			test := t
   115  			ginkgo.It(t.name, func(ctx context.Context) {
   116  				var err error
   117  				tp := testParameters{
   118  					enableResizing:          true,
   119  					enableNodeExpansion:     test.nodeExpansionRequired,
   120  					disableResizingOnDriver: test.disableResizingOnDriver,
   121  				}
   122  				// disabling attach requires drive registration feature
   123  				if test.disableAttach {
   124  					tp.disableAttach = true
   125  					tp.registerDriver = true
   126  				}
   127  				tp.hooks = createExpansionHook(test.simulatedCSIDriverError)
   128  
   129  				m.init(ctx, tp)
   130  				ginkgo.DeferCleanup(m.cleanup)
   131  
   132  				sc, pvc, pod := m.createPod(ctx, pvcReference)
   133  				gomega.Expect(pod).NotTo(gomega.BeNil(), "while creating pod for resizing")
   134  
   135  				if !*sc.AllowVolumeExpansion {
   136  					framework.Fail("failed creating sc with allowed expansion")
   137  				}
   138  
   139  				err = e2epod.WaitForPodNameRunningInNamespace(ctx, m.cs, pod.Name, pod.Namespace)
   140  				framework.ExpectNoError(err, "Failed to start pod1: %v", err)
   141  
   142  				ginkgo.By("Expanding current pvc")
   143  				newSize := resource.MustParse("6Gi")
   144  				newPVC, err := testsuites.ExpandPVCSize(ctx, pvc, newSize, m.cs)
   145  				framework.ExpectNoError(err, "While updating pvc for more size")
   146  				pvc = newPVC
   147  				gomega.Expect(pvc).NotTo(gomega.BeNil())
   148  
   149  				pvcSize := pvc.Spec.Resources.Requests[v1.ResourceStorage]
   150  				if pvcSize.Cmp(newSize) != 0 {
   151  					framework.Failf("error updating pvc size %q", pvc.Name)
   152  				}
   153  				if test.expectFailure {
   154  					err = testsuites.WaitForResizingCondition(ctx, pvc, m.cs, csiResizingConditionWait)
   155  					framework.ExpectError(err, "unexpected resizing condition on PVC")
   156  					return
   157  				}
   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  }