diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index 7ee1a6d813..4df371f7d6 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -106,7 +106,7 @@ const ( ExpandPersistentVolumes featuregate.Feature = "ExpandPersistentVolumes" // owner: @mlmhl - // alpha: v1.11 + // beta: v1.15 // Ability to expand persistent volumes' file system without unmounting volumes. ExpandInUsePersistentVolumes featuregate.Feature = "ExpandInUsePersistentVolumes" @@ -507,7 +507,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS TaintNodesByCondition: {Default: true, PreRelease: featuregate.Beta}, QOSReserved: {Default: false, PreRelease: featuregate.Alpha}, ExpandPersistentVolumes: {Default: true, PreRelease: featuregate.Beta}, - ExpandInUsePersistentVolumes: {Default: false, PreRelease: featuregate.Alpha}, + ExpandInUsePersistentVolumes: {Default: true, PreRelease: featuregate.Beta}, ExpandCSIVolumes: {Default: false, PreRelease: featuregate.Alpha}, AttachVolumeLimit: {Default: true, PreRelease: featuregate.Beta}, CPUManager: {Default: true, PreRelease: featuregate.Beta}, diff --git a/pkg/kubelet/volumemanager/reconciler/reconciler.go b/pkg/kubelet/volumemanager/reconciler/reconciler.go index dfacf036f4..3c77d20e84 100644 --- a/pkg/kubelet/volumemanager/reconciler/reconciler.go +++ b/pkg/kubelet/volumemanager/reconciler/reconciler.go @@ -252,8 +252,8 @@ func (rc *reconciler) reconcile() { } } else if cache.IsFSResizeRequiredError(err) && utilfeature.DefaultFeatureGate.Enabled(features.ExpandInUsePersistentVolumes) { - klog.V(4).Infof(volumeToMount.GenerateMsgDetailed("Starting operationExecutor.ExpandVolumeFSWithoutUnmounting", "")) - err := rc.operationExecutor.ExpandVolumeFSWithoutUnmounting( + klog.V(4).Infof(volumeToMount.GenerateMsgDetailed("Starting operationExecutor.ExpandInUseVolume", "")) + err := rc.operationExecutor.ExpandInUseVolume( volumeToMount.VolumeToMount, rc.actualStateOfWorld) if err != nil && @@ -261,10 +261,10 @@ func (rc *reconciler) reconcile() { !exponentialbackoff.IsExponentialBackoff(err) { // Ignore nestedpendingoperations.IsAlreadyExists and exponentialbackoff.IsExponentialBackoff errors, they are expected. // Log all other errors. - klog.Errorf(volumeToMount.GenerateErrorDetailed("operationExecutor.ExpandVolumeFSWithoutUnmounting failed", err).Error()) + klog.Errorf(volumeToMount.GenerateErrorDetailed("operationExecutor.ExpandInUseVolume failed", err).Error()) } if err == nil { - klog.V(4).Infof(volumeToMount.GenerateMsgDetailed("operationExecutor.ExpandVolumeFSWithoutUnmounting started", "")) + klog.V(4).Infof(volumeToMount.GenerateMsgDetailed("operationExecutor.ExpandInUseVolume started", "")) } } } diff --git a/pkg/volume/util/operationexecutor/fakegenerator.go b/pkg/volume/util/operationexecutor/fakegenerator.go index 0dc2c9e414..e41b73d155 100644 --- a/pkg/volume/util/operationexecutor/fakegenerator.go +++ b/pkg/volume/util/operationexecutor/fakegenerator.go @@ -99,8 +99,8 @@ func (f *fakeOGCounter) GenerateExpandVolumeFunc(*v1.PersistentVolumeClaim, *v1. return f.recordFuncCall("GenerateExpandVolumeFunc"), nil } -func (f *fakeOGCounter) GenerateExpandVolumeFSWithoutUnmountingFunc(volumeToMount VolumeToMount, actualStateOfWorld ActualStateOfWorldMounterUpdater) (volumetypes.GeneratedOperations, error) { - return f.recordFuncCall("GenerateExpandVolumeFSWithoutUnmountingFunc"), nil +func (f *fakeOGCounter) GenerateExpandInUseVolumeFunc(volumeToMount VolumeToMount, actualStateOfWorld ActualStateOfWorldMounterUpdater) (volumetypes.GeneratedOperations, error) { + return f.recordFuncCall("GenerateExpandInUseVolumeFunc"), nil } func (f *fakeOGCounter) recordFuncCall(name string) volumetypes.GeneratedOperations { diff --git a/pkg/volume/util/operationexecutor/operation_executor.go b/pkg/volume/util/operationexecutor/operation_executor.go index 9206d5cb9e..fce380d6b3 100644 --- a/pkg/volume/util/operationexecutor/operation_executor.go +++ b/pkg/volume/util/operationexecutor/operation_executor.go @@ -140,8 +140,8 @@ type OperationExecutor interface { // IsOperationPending returns true if an operation for the given volumeName and podName is pending, // otherwise it returns false IsOperationPending(volumeName v1.UniqueVolumeName, podName volumetypes.UniquePodName) bool - // ExpandVolumeFSWithoutUnmounting will resize volume's file system to expected size without unmounting the volume. - ExpandVolumeFSWithoutUnmounting(volumeToMount VolumeToMount, actualStateOfWorld ActualStateOfWorldMounterUpdater) error + // ExpandInUseVolume will resize volume's file system to expected size without unmounting the volume. + ExpandInUseVolume(volumeToMount VolumeToMount, actualStateOfWorld ActualStateOfWorldMounterUpdater) error // ReconstructVolumeOperation construct a new volumeSpec and returns it created by plugin ReconstructVolumeOperation(volumeMode v1.PersistentVolumeMode, plugin volume.VolumePlugin, mapperPlugin volume.BlockVolumePlugin, uid types.UID, podName volumetypes.UniquePodName, volumeSpecName string, volumePath string, pluginName string) (*volume.Spec, error) // CheckVolumeExistenceOperation checks volume existence @@ -820,8 +820,8 @@ func (oe *operationExecutor) UnmountDevice( deviceToDetach.VolumeName, podName, generatedOperations) } -func (oe *operationExecutor) ExpandVolumeFSWithoutUnmounting(volumeToMount VolumeToMount, actualStateOfWorld ActualStateOfWorldMounterUpdater) error { - generatedOperations, err := oe.operationGenerator.GenerateExpandVolumeFSWithoutUnmountingFunc(volumeToMount, actualStateOfWorld) +func (oe *operationExecutor) ExpandInUseVolume(volumeToMount VolumeToMount, actualStateOfWorld ActualStateOfWorldMounterUpdater) error { + generatedOperations, err := oe.operationGenerator.GenerateExpandInUseVolumeFunc(volumeToMount, actualStateOfWorld) if err != nil { return err } diff --git a/pkg/volume/util/operationexecutor/operation_executor_test.go b/pkg/volume/util/operationexecutor/operation_executor_test.go index c5eb06e9fd..a1036987c7 100644 --- a/pkg/volume/util/operationexecutor/operation_executor_test.go +++ b/pkg/volume/util/operationexecutor/operation_executor_test.go @@ -462,7 +462,7 @@ func (fopg *fakeOperationGenerator) GenerateExpandVolumeFunc(pvc *v1.PersistentV }, nil } -func (fopg *fakeOperationGenerator) GenerateExpandVolumeFSWithoutUnmountingFunc(volumeToMount VolumeToMount, actualStateOfWorld ActualStateOfWorldMounterUpdater) (volumetypes.GeneratedOperations, error) { +func (fopg *fakeOperationGenerator) GenerateExpandInUseVolumeFunc(volumeToMount VolumeToMount, actualStateOfWorld ActualStateOfWorldMounterUpdater) (volumetypes.GeneratedOperations, error) { opFunc := func() (error, error) { startOperationAndBlock(fopg.ch, fopg.quit) return nil, nil diff --git a/pkg/volume/util/operationexecutor/operation_generator.go b/pkg/volume/util/operationexecutor/operation_generator.go index 8510bec483..0e64bbe4cf 100644 --- a/pkg/volume/util/operationexecutor/operation_generator.go +++ b/pkg/volume/util/operationexecutor/operation_generator.go @@ -131,7 +131,7 @@ type OperationGenerator interface { GenerateExpandVolumeFunc(*v1.PersistentVolumeClaim, *v1.PersistentVolume) (volumetypes.GeneratedOperations, error) // Generates the volume file system resize function, which can resize volume's file system to expected size without unmounting the volume. - GenerateExpandVolumeFSWithoutUnmountingFunc(volumeToMount VolumeToMount, actualStateOfWorld ActualStateOfWorldMounterUpdater) (volumetypes.GeneratedOperations, error) + GenerateExpandInUseVolumeFunc(volumeToMount VolumeToMount, actualStateOfWorld ActualStateOfWorldMounterUpdater) (volumetypes.GeneratedOperations, error) } func (og *operationGenerator) GenerateVolumesAreAttachedFunc( @@ -1578,7 +1578,7 @@ func (og *operationGenerator) GenerateExpandVolumeFunc( }, nil } -func (og *operationGenerator) GenerateExpandVolumeFSWithoutUnmountingFunc( +func (og *operationGenerator) GenerateExpandInUseVolumeFunc( volumeToMount VolumeToMount, actualStateOfWorld ActualStateOfWorldMounterUpdater) (volumetypes.GeneratedOperations, error) { diff --git a/test/e2e/storage/volume_expand.go b/test/e2e/storage/volume_expand.go index ba5eeca2e4..c3828f9bae 100644 --- a/test/e2e/storage/volume_expand.go +++ b/test/e2e/storage/volume_expand.go @@ -104,7 +104,7 @@ var _ = utils.SIGDescribe("Volume expand", func() { framework.ExpectError(err, "While updating non-expandable PVC") }) - ginkgo.It("Verify if editing PVC allows resize", func() { + ginkgo.It("Verify if offline PVC expansion works", func() { pvc, storageClassVar, err = setupFunc(true /* allowExpansion */, false /*BlockVolume*/) framework.ExpectNoError(err, "Error creating non-expandable PVC") @@ -122,6 +122,11 @@ var _ = utils.SIGDescribe("Volume expand", func() { framework.ExpectNoError(err, "while cleaning up pod already deleted in resize test") }() + ginkgo.By("Deleting the previously created pod") + err = framework.DeletePodWithWait(f, c, pod) + framework.ExpectNoError(err, "while deleting pod for resizing") + + // We expand the PVC while no pod is using it to ensure offline expansion ginkgo.By("Expanding current pvc") newSize := resource.MustParse("6Gi") pvc, err = expandPVCSize(pvc, newSize, c) @@ -145,10 +150,6 @@ var _ = utils.SIGDescribe("Volume expand", func() { gomega.Expect(len(inProgressConditions)).To(gomega.Equal(1), "pvc must have file system resize pending condition") gomega.Expect(inProgressConditions[0].Type).To(gomega.Equal(v1.PersistentVolumeClaimFileSystemResizePending), "pvc must have fs resizing condition") - ginkgo.By("Deleting the previously created pod") - err = framework.DeletePodWithWait(f, c, pod) - framework.ExpectNoError(err, "while deleting pod for resizing") - ginkgo.By("Creating a new pod with same volume") pod2, err := framework.CreatePod(c, ns, nil, pvcClaims, false, "") framework.ExpectNoError(err, "while recreating pod for resizing") @@ -165,6 +166,48 @@ var _ = utils.SIGDescribe("Volume expand", func() { gomega.Expect(len(pvcConditions)).To(gomega.Equal(0), "pvc should not have conditions") }) + ginkgo.It("should resize volume when PVC is edited while pod is using it", func() { + pvc, storageClassVar, err = setupFunc(true /* allowExpansion */, false /*BlockVolume*/) + framework.ExpectNoError(err, "Error creating non-expandable PVC") + + ginkgo.By("Waiting for pvc to be in bound phase") + pvcClaims := []*v1.PersistentVolumeClaim{pvc} + pvs, err := framework.WaitForPVClaimBoundPhase(c, pvcClaims, framework.ClaimProvisionTimeout) + framework.ExpectNoError(err, "Failed waiting for PVC to be bound %v", err) + gomega.Expect(len(pvs)).To(gomega.Equal(1)) + + ginkgo.By("Creating a pod with dynamically provisioned volume") + pod, err := framework.CreatePod(c, ns, nil, pvcClaims, false, "") + framework.ExpectNoError(err, "While creating pods for resizing") + defer func() { + err = framework.DeletePodWithWait(f, c, pod) + framework.ExpectNoError(err, "while cleaning up pod already deleted in resize test") + }() + + // We expand the PVC while no pod is using it to ensure online expansion + ginkgo.By("Expanding current pvc") + newSize := resource.MustParse("6Gi") + pvc, err = expandPVCSize(pvc, newSize, c) + framework.ExpectNoError(err, "While updating pvc for more size") + gomega.Expect(pvc).NotTo(gomega.BeNil()) + + pvcSize := pvc.Spec.Resources.Requests[v1.ResourceStorage] + if pvcSize.Cmp(newSize) != 0 { + framework.Failf("error updating pvc size %q", pvc.Name) + } + + ginkgo.By("Waiting for cloudprovider resize to finish") + err = waitForControllerVolumeResize(pvc, c, totalResizeWaitPeriod) + framework.ExpectNoError(err, "While waiting for pvc resize to finish") + + ginkgo.By("Waiting for file system resize to finish") + pvc, err = waitForFSResize(pvc, c) + framework.ExpectNoError(err, "while waiting for fs resize to finish") + + pvcConditions := pvc.Status.Conditions + gomega.Expect(len(pvcConditions)).To(gomega.Equal(0), "pvc should not have conditions") + }) + ginkgo.It("should allow expansion of block volumes", func() { pvc, storageClassVar, err = setupFunc(true /*allowExpansion*/, true /*blockVolume*/)