k8s.io/kubernetes@v1.29.3/test/e2e/storage/local_volume_resize.go (about) 1 /* 2 Copyright 2021 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 "path/filepath" 23 "time" 24 25 v1 "k8s.io/api/core/v1" 26 storagev1 "k8s.io/api/storage/v1" 27 "k8s.io/apimachinery/pkg/api/resource" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 clientset "k8s.io/client-go/kubernetes" 30 31 "github.com/onsi/ginkgo/v2" 32 "github.com/onsi/gomega" 33 "k8s.io/apimachinery/pkg/util/rand" 34 "k8s.io/apimachinery/pkg/util/wait" 35 "k8s.io/kubernetes/test/e2e/framework" 36 e2enode "k8s.io/kubernetes/test/e2e/framework/node" 37 "k8s.io/kubernetes/test/e2e/storage/testsuites" 38 "k8s.io/kubernetes/test/e2e/storage/utils" 39 admissionapi "k8s.io/pod-security-admission/api" 40 ) 41 42 const ( 43 csiResizeWaitPeriod = 5 * time.Minute 44 ) 45 46 var _ = utils.SIGDescribe("PersistentVolumes-expansion", func() { 47 f := framework.NewDefaultFramework("persistent-local-volumes-expansion") 48 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged 49 ginkgo.Context("loopback local block volume", func() { 50 var ( 51 config *localTestConfig 52 scName string 53 ) 54 55 testVolType := BlockFsWithFormatLocalVolumeType 56 var testVol *localTestVolume 57 testMode := immediateMode 58 ginkgo.BeforeEach(func(ctx context.Context) { 59 nodes, err := e2enode.GetBoundedReadySchedulableNodes(ctx, f.ClientSet, maxNodes) 60 framework.ExpectNoError(err) 61 62 scName = fmt.Sprintf("%v-%v", testSCPrefix, f.Namespace.Name) 63 // Choose a random node 64 randomNode := &nodes.Items[rand.Intn(len(nodes.Items))] 65 66 hostExec := utils.NewHostExec(f) 67 ltrMgr := utils.NewLocalResourceManager("local-volume-test", hostExec, hostBase) 68 config = &localTestConfig{ 69 ns: f.Namespace.Name, 70 client: f.ClientSet, 71 timeouts: f.Timeouts, 72 nodes: nodes.Items, 73 randomNode: randomNode, 74 scName: scName, 75 discoveryDir: filepath.Join(hostBase, f.Namespace.Name), 76 hostExec: hostExec, 77 ltrMgr: ltrMgr, 78 } 79 80 setupExpandableLocalStorageClass(ctx, config, &testMode) 81 testVols := setupLocalVolumesPVCsPVs(ctx, config, testVolType, config.randomNode, 1, testMode) 82 testVol = testVols[0] 83 }) 84 ginkgo.AfterEach(func(ctx context.Context) { 85 cleanupLocalVolumes(ctx, config, []*localTestVolume{testVol}) 86 cleanupStorageClass(ctx, config) 87 }) 88 89 ginkgo.It("should support online expansion on node", func(ctx context.Context) { 90 var ( 91 pod1 *v1.Pod 92 pod1Err error 93 ) 94 ginkgo.By("Creating pod1") 95 pod1, pod1Err = createLocalPod(ctx, config, testVol, nil) 96 framework.ExpectNoError(pod1Err) 97 verifyLocalPod(ctx, config, testVol, pod1, config.randomNode.Name) 98 99 // We expand the PVC while l.pod is using it for online expansion. 100 ginkgo.By("Expanding current pvc") 101 currentPvcSize := testVol.pvc.Spec.Resources.Requests[v1.ResourceStorage] 102 newSize := currentPvcSize.DeepCopy() 103 newSize.Add(resource.MustParse("10Mi")) 104 framework.Logf("currentPvcSize %s, newSize %s", currentPvcSize.String(), newSize.String()) 105 newPVC, err := testsuites.ExpandPVCSize(ctx, testVol.pvc, newSize, f.ClientSet) 106 framework.ExpectNoError(err, "While updating pvc for more size") 107 testVol.pvc = newPVC 108 gomega.Expect(testVol.pvc).NotTo(gomega.BeNil()) 109 110 pvcSize := testVol.pvc.Spec.Resources.Requests[v1.ResourceStorage] 111 if pvcSize.Cmp(newSize) != 0 { 112 framework.Failf("error updating pvc size %q", testVol.pvc.Name) 113 } 114 115 // Now update the underlying volume manually 116 err = config.ltrMgr.ExpandBlockDevice(ctx, testVol.ltr, 10 /*number of 1M blocks to add*/) 117 framework.ExpectNoError(err, "while expanding loopback device") 118 119 // now update PV to matching size 120 pv, err := UpdatePVSize(ctx, testVol.pv, newSize, f.ClientSet) 121 framework.ExpectNoError(err, "while updating pv to more size") 122 gomega.Expect(pv).NotTo(gomega.BeNil()) 123 testVol.pv = pv 124 125 ginkgo.By("Waiting for file system resize to finish") 126 testVol.pvc, err = testsuites.WaitForFSResize(ctx, testVol.pvc, f.ClientSet) 127 framework.ExpectNoError(err, "while waiting for fs resize to finish") 128 129 pvcConditions := testVol.pvc.Status.Conditions 130 gomega.Expect(pvcConditions).To(gomega.BeEmpty(), "pvc should not have conditions") 131 }) 132 133 }) 134 135 }) 136 137 func UpdatePVSize(ctx context.Context, pv *v1.PersistentVolume, size resource.Quantity, c clientset.Interface) (*v1.PersistentVolume, error) { 138 pvName := pv.Name 139 pvToUpdate := pv.DeepCopy() 140 141 var lastError error 142 waitErr := wait.PollUntilContextTimeout(ctx, 5*time.Second, csiResizeWaitPeriod, true, func(ctx context.Context) (bool, error) { 143 var err error 144 pvToUpdate, err = c.CoreV1().PersistentVolumes().Get(ctx, pvName, metav1.GetOptions{}) 145 if err != nil { 146 return false, fmt.Errorf("error fetching pv %s: %w", pvName, err) 147 } 148 pvToUpdate.Spec.Capacity[v1.ResourceStorage] = size 149 pvToUpdate, err = c.CoreV1().PersistentVolumes().Update(ctx, pvToUpdate, metav1.UpdateOptions{}) 150 if err != nil { 151 framework.Logf("error updating PV %s: %v", pvName, err) 152 lastError = err 153 return false, nil 154 } 155 return true, nil 156 }) 157 if wait.Interrupted(waitErr) { 158 return nil, fmt.Errorf("timed out attempting to update PV size. last update error: %v", lastError) 159 } 160 if waitErr != nil { 161 return nil, fmt.Errorf("failed to expand PV size: %v", waitErr) 162 } 163 return pvToUpdate, nil 164 } 165 166 func setupExpandableLocalStorageClass(ctx context.Context, config *localTestConfig, mode *storagev1.VolumeBindingMode) { 167 enableExpansion := true 168 sc := &storagev1.StorageClass{ 169 ObjectMeta: metav1.ObjectMeta{ 170 Name: config.scName, 171 }, 172 Provisioner: "kubernetes.io/no-provisioner", 173 VolumeBindingMode: mode, 174 AllowVolumeExpansion: &enableExpansion, 175 } 176 177 _, err := config.client.StorageV1().StorageClasses().Create(ctx, sc, metav1.CreateOptions{}) 178 framework.ExpectNoError(err) 179 }