k8s.io/kubernetes@v1.29.3/test/e2e/storage/mounted_volume_resize.go (about)

     1  /*
     2  Copyright 2018 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 storage
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"time"
    23  
    24  	"github.com/onsi/ginkgo/v2"
    25  	"github.com/onsi/gomega"
    26  	appsv1 "k8s.io/api/apps/v1"
    27  	v1 "k8s.io/api/core/v1"
    28  	storagev1 "k8s.io/api/storage/v1"
    29  	"k8s.io/apimachinery/pkg/api/resource"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	admissionapi "k8s.io/pod-security-admission/api"
    32  
    33  	utilerrors "k8s.io/apimachinery/pkg/util/errors"
    34  	"k8s.io/apimachinery/pkg/util/wait"
    35  	clientset "k8s.io/client-go/kubernetes"
    36  	"k8s.io/kubernetes/pkg/client/conditions"
    37  	"k8s.io/kubernetes/test/e2e/feature"
    38  	"k8s.io/kubernetes/test/e2e/framework"
    39  	e2edeployment "k8s.io/kubernetes/test/e2e/framework/deployment"
    40  	e2enode "k8s.io/kubernetes/test/e2e/framework/node"
    41  	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
    42  	e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
    43  	e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
    44  	"k8s.io/kubernetes/test/e2e/storage/testsuites"
    45  	"k8s.io/kubernetes/test/e2e/storage/utils"
    46  )
    47  
    48  var _ = utils.SIGDescribe("Mounted volume expand", feature.StorageProvider, func() {
    49  	var (
    50  		c                 clientset.Interface
    51  		ns                string
    52  		pvc               *v1.PersistentVolumeClaim
    53  		sc                *storagev1.StorageClass
    54  		nodeName          string
    55  		nodeKeyValueLabel map[string]string
    56  		nodeLabelValue    string
    57  		nodeKey           string
    58  	)
    59  
    60  	f := framework.NewDefaultFramework("mounted-volume-expand")
    61  	f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
    62  	ginkgo.BeforeEach(func(ctx context.Context) {
    63  		e2eskipper.SkipUnlessProviderIs("aws", "gce")
    64  		c = f.ClientSet
    65  		ns = f.Namespace.Name
    66  		framework.ExpectNoError(e2enode.WaitForAllNodesSchedulable(ctx, c, f.Timeouts.NodeSchedulable))
    67  
    68  		node, err := e2enode.GetRandomReadySchedulableNode(ctx, f.ClientSet)
    69  		framework.ExpectNoError(err)
    70  		nodeName = node.Name
    71  
    72  		nodeKey = "mounted_volume_expand_" + ns
    73  		nodeLabelValue = ns
    74  		nodeKeyValueLabel = map[string]string{nodeKey: nodeLabelValue}
    75  		e2enode.AddOrUpdateLabelOnNode(c, nodeName, nodeKey, nodeLabelValue)
    76  		ginkgo.DeferCleanup(e2enode.RemoveLabelOffNode, c, nodeName, nodeKey)
    77  
    78  		test := testsuites.StorageClassTest{
    79  			Name:                 "default",
    80  			Timeouts:             f.Timeouts,
    81  			ClaimSize:            "2Gi",
    82  			AllowVolumeExpansion: true,
    83  			DelayBinding:         true,
    84  			Parameters:           make(map[string]string),
    85  		}
    86  
    87  		sc = testsuites.SetupStorageClass(ctx, c, newStorageClass(test, ns, "resizing"))
    88  		if !*sc.AllowVolumeExpansion {
    89  			framework.Failf("Class %s does not allow volume expansion", sc.Name)
    90  		}
    91  
    92  		pvc = e2epv.MakePersistentVolumeClaim(e2epv.PersistentVolumeClaimConfig{
    93  			ClaimSize:        test.ClaimSize,
    94  			StorageClassName: &(sc.Name),
    95  			VolumeMode:       &test.VolumeMode,
    96  		}, ns)
    97  		pvc, err = c.CoreV1().PersistentVolumeClaims(pvc.Namespace).Create(ctx, pvc, metav1.CreateOptions{})
    98  		framework.ExpectNoError(err, "Error creating pvc")
    99  		ginkgo.DeferCleanup(func(ctx context.Context) {
   100  			framework.Logf("AfterEach: Cleaning up resources for mounted volume resize")
   101  
   102  			if errs := e2epv.PVPVCCleanup(ctx, c, ns, nil, pvc); len(errs) > 0 {
   103  				framework.Failf("AfterEach: Failed to delete PVC and/or PV. Errors: %v", utilerrors.NewAggregate(errs))
   104  			}
   105  		})
   106  	})
   107  
   108  	ginkgo.It("Should verify mounted devices can be resized", func(ctx context.Context) {
   109  		pvcClaims := []*v1.PersistentVolumeClaim{pvc}
   110  
   111  		// The reason we use a node selector is because we do not want pod to move to different node when pod is deleted.
   112  		// Keeping pod on same node reproduces the scenario that volume might already be mounted when resize is attempted.
   113  		// We should consider adding a unit test that exercises this better.
   114  		ginkgo.By("Creating a deployment with selected PVC")
   115  		deployment, err := e2edeployment.CreateDeployment(ctx, c, int32(1), map[string]string{"test": "app"}, nodeKeyValueLabel, ns, pvcClaims, admissionapi.LevelRestricted, "")
   116  		framework.ExpectNoError(err, "Failed creating deployment %v", err)
   117  		ginkgo.DeferCleanup(c.AppsV1().Deployments(ns).Delete, deployment.Name, metav1.DeleteOptions{})
   118  
   119  		// PVC should be bound at this point
   120  		ginkgo.By("Checking for bound PVC")
   121  		pvs, err := e2epv.WaitForPVClaimBoundPhase(ctx, c, pvcClaims, framework.ClaimProvisionTimeout)
   122  		framework.ExpectNoError(err, "Failed waiting for PVC to be bound %v", err)
   123  		gomega.Expect(pvs).To(gomega.HaveLen(1))
   124  
   125  		ginkgo.By("Wait for a pod from deployment to be running")
   126  		podList, err := e2edeployment.GetPodsForDeployment(ctx, c, deployment)
   127  		framework.ExpectNoError(err, "While getting pods from deployment")
   128  		gomega.Expect(podList.Items).NotTo(gomega.BeEmpty())
   129  		pod := podList.Items[0]
   130  		err = e2epod.WaitTimeoutForPodRunningInNamespace(ctx, c, pod.Name, pod.Namespace, f.Timeouts.PodStart)
   131  		framework.ExpectNoError(err, "While waiting for pods to be ready")
   132  
   133  		ginkgo.By("Expanding current pvc")
   134  		newSize := resource.MustParse("6Gi")
   135  		newPVC, err := testsuites.ExpandPVCSize(ctx, pvc, newSize, c)
   136  		framework.ExpectNoError(err, "While updating pvc for more size")
   137  		pvc = newPVC
   138  		gomega.Expect(pvc).NotTo(gomega.BeNil())
   139  
   140  		pvcSize := pvc.Spec.Resources.Requests[v1.ResourceStorage]
   141  		if pvcSize.Cmp(newSize) != 0 {
   142  			framework.Failf("error updating pvc size %q", pvc.Name)
   143  		}
   144  
   145  		ginkgo.By("Waiting for cloudprovider resize to finish")
   146  		err = testsuites.WaitForControllerVolumeResize(ctx, pvc, c, totalResizeWaitPeriod)
   147  		framework.ExpectNoError(err, "While waiting for pvc resize to finish")
   148  
   149  		ginkgo.By("Getting a pod from deployment")
   150  		podList, err = e2edeployment.GetPodsForDeployment(ctx, c, deployment)
   151  		framework.ExpectNoError(err, "While getting pods from deployment")
   152  		gomega.Expect(podList.Items).NotTo(gomega.BeEmpty())
   153  		pod = podList.Items[0]
   154  
   155  		ginkgo.By("Deleting the pod from deployment")
   156  		err = e2epod.DeletePodWithWait(ctx, c, &pod)
   157  		framework.ExpectNoError(err, "while deleting pod for resizing")
   158  
   159  		ginkgo.By("Waiting for deployment to create new pod")
   160  		pod, err = waitForDeploymentToRecreatePod(ctx, c, deployment)
   161  		framework.ExpectNoError(err, "While waiting for pod to be recreated")
   162  
   163  		ginkgo.By("Waiting for file system resize to finish")
   164  		pvc, err = testsuites.WaitForFSResize(ctx, pvc, c)
   165  		framework.ExpectNoError(err, "while waiting for fs resize to finish")
   166  
   167  		pvcConditions := pvc.Status.Conditions
   168  		gomega.Expect(pvcConditions).To(gomega.BeEmpty(), "pvc should not have conditions")
   169  	})
   170  })
   171  
   172  func waitForDeploymentToRecreatePod(ctx context.Context, client clientset.Interface, deployment *appsv1.Deployment) (v1.Pod, error) {
   173  	var runningPod v1.Pod
   174  	waitErr := wait.PollImmediate(10*time.Second, 5*time.Minute, func() (bool, error) {
   175  		podList, err := e2edeployment.GetPodsForDeployment(ctx, client, deployment)
   176  		if err != nil {
   177  			return false, fmt.Errorf("failed to get pods for deployment: %w", err)
   178  		}
   179  		for _, pod := range podList.Items {
   180  			switch pod.Status.Phase {
   181  			case v1.PodRunning:
   182  				runningPod = pod
   183  				return true, nil
   184  			case v1.PodFailed, v1.PodSucceeded:
   185  				return false, conditions.ErrPodCompleted
   186  			}
   187  		}
   188  		return false, nil
   189  	})
   190  	if waitErr != nil {
   191  		return runningPod, fmt.Errorf("error waiting for recreated pod: %v", waitErr)
   192  	}
   193  	return runningPod, nil
   194  }