Merge pull request #108929 from gnufied/move-expansion-feature-gate-ga

Move all volume expansion feature gates to GA
This commit is contained in:
Kubernetes Prow Robot
2022-03-25 18:08:16 -07:00
committed by GitHub
29 changed files with 130 additions and 616 deletions

View File

@@ -2019,8 +2019,6 @@ func ValidatePersistentVolumeStatusUpdate(newPv, oldPv *core.PersistentVolume) f
type PersistentVolumeClaimSpecValidationOptions struct {
// Allow spec to contain the "ReadWiteOncePod" access mode
AllowReadWriteOncePod bool
// Allow pvc expansion after PVC is created and bound to a PV
EnableExpansion bool
// Allow users to recover from previously failing expansion operation
EnableRecoverFromExpansionFailure bool
}
@@ -2028,7 +2026,6 @@ type PersistentVolumeClaimSpecValidationOptions struct {
func ValidationOptionsForPersistentVolumeClaim(pvc, oldPvc *core.PersistentVolumeClaim) PersistentVolumeClaimSpecValidationOptions {
opts := PersistentVolumeClaimSpecValidationOptions{
AllowReadWriteOncePod: utilfeature.DefaultFeatureGate.Enabled(features.ReadWriteOncePod),
EnableExpansion: utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes),
EnableRecoverFromExpansionFailure: utilfeature.DefaultFeatureGate.Enabled(features.RecoverVolumeExpansionFailure),
}
if oldPvc == nil {
@@ -2175,40 +2172,30 @@ func ValidatePersistentVolumeClaimUpdate(newPvc, oldPvc *core.PersistentVolumeCl
allErrs = append(allErrs, ValidateImmutableAnnotation(newPvc.ObjectMeta.Annotations[v1.BetaStorageClassAnnotation], oldPvc.ObjectMeta.Annotations[v1.BetaStorageClassAnnotation], v1.BetaStorageClassAnnotation, field.NewPath("metadata"))...)
}
if opts.EnableExpansion {
// lets make sure storage values are same.
if newPvc.Status.Phase == core.ClaimBound && newPvcClone.Spec.Resources.Requests != nil {
newPvcClone.Spec.Resources.Requests["storage"] = oldPvc.Spec.Resources.Requests["storage"] // +k8s:verify-mutation:reason=clone
}
// lets make sure storage values are same.
if newPvc.Status.Phase == core.ClaimBound && newPvcClone.Spec.Resources.Requests != nil {
newPvcClone.Spec.Resources.Requests["storage"] = oldPvc.Spec.Resources.Requests["storage"] // +k8s:verify-mutation:reason=clone
}
oldSize := oldPvc.Spec.Resources.Requests["storage"]
newSize := newPvc.Spec.Resources.Requests["storage"]
statusSize := oldPvc.Status.Capacity["storage"]
oldSize := oldPvc.Spec.Resources.Requests["storage"]
newSize := newPvc.Spec.Resources.Requests["storage"]
statusSize := oldPvc.Status.Capacity["storage"]
if !apiequality.Semantic.DeepEqual(newPvcClone.Spec, oldPvcClone.Spec) {
specDiff := cmp.Diff(oldPvcClone.Spec, newPvcClone.Spec)
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), fmt.Sprintf("spec is immutable after creation except resources.requests for bound claims\n%v", specDiff)))
}
if newSize.Cmp(oldSize) < 0 {
if !opts.EnableRecoverFromExpansionFailure {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "resources", "requests", "storage"), "field can not be less than previous value"))
} else {
// This validation permits reducing pvc requested size up to capacity recorded in pvc.status
// so that users can recover from volume expansion failure, but Kubernetes does not actually
// support volume shrinking
if newSize.Cmp(statusSize) <= 0 {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "resources", "requests", "storage"), "field can not be less than status.capacity"))
}
if !apiequality.Semantic.DeepEqual(newPvcClone.Spec, oldPvcClone.Spec) {
specDiff := cmp.Diff(oldPvcClone.Spec, newPvcClone.Spec)
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), fmt.Sprintf("spec is immutable after creation except resources.requests for bound claims\n%v", specDiff)))
}
if newSize.Cmp(oldSize) < 0 {
if !opts.EnableRecoverFromExpansionFailure {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "resources", "requests", "storage"), "field can not be less than previous value"))
} else {
// This validation permits reducing pvc requested size up to capacity recorded in pvc.status
// so that users can recover from volume expansion failure, but Kubernetes does not actually
// support volume shrinking
if newSize.Cmp(statusSize) <= 0 {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "resources", "requests", "storage"), "field can not be less than status.capacity"))
}
}
} else {
// changes to Spec are not allowed, but updates to label/and some annotations are OK.
// no-op updates pass validation.
if !apiequality.Semantic.DeepEqual(newPvcClone.Spec, oldPvcClone.Spec) {
specDiff := cmp.Diff(oldPvcClone.Spec, newPvcClone.Spec)
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), fmt.Sprintf("field is immutable after creation\n%v", specDiff)))
}
}
allErrs = append(allErrs, ValidateImmutableField(newPvc.Spec.VolumeMode, oldPvc.Spec.VolumeMode, field.NewPath("volumeMode"))...)

View File

@@ -2108,215 +2108,173 @@ func TestValidatePersistentVolumeClaimUpdate(t *testing.T) {
isExpectedFailure bool
oldClaim *core.PersistentVolumeClaim
newClaim *core.PersistentVolumeClaim
enableResize bool
enableRecoverFromExpansion bool
}{
"valid-update-volumeName-only": {
isExpectedFailure: false,
oldClaim: validClaim,
newClaim: validUpdateClaim,
enableResize: false,
},
"valid-no-op-update": {
isExpectedFailure: false,
oldClaim: validUpdateClaim,
newClaim: validUpdateClaim,
enableResize: false,
},
"invalid-update-change-resources-on-bound-claim": {
isExpectedFailure: true,
oldClaim: validUpdateClaim,
newClaim: invalidUpdateClaimResources,
enableResize: false,
},
"invalid-update-change-access-modes-on-bound-claim": {
isExpectedFailure: true,
oldClaim: validUpdateClaim,
newClaim: invalidUpdateClaimAccessModes,
enableResize: false,
},
"valid-update-volume-mode-block-to-block": {
isExpectedFailure: false,
oldClaim: validClaimVolumeModeBlock,
newClaim: validClaimVolumeModeBlock,
enableResize: false,
},
"valid-update-volume-mode-file-to-file": {
isExpectedFailure: false,
oldClaim: validClaimVolumeModeFile,
newClaim: validClaimVolumeModeFile,
enableResize: false,
},
"invalid-update-volume-mode-to-block": {
isExpectedFailure: true,
oldClaim: validClaimVolumeModeFile,
newClaim: validClaimVolumeModeBlock,
enableResize: false,
},
"invalid-update-volume-mode-to-file": {
isExpectedFailure: true,
oldClaim: validClaimVolumeModeBlock,
newClaim: validClaimVolumeModeFile,
enableResize: false,
},
"invalid-update-volume-mode-nil-to-file": {
isExpectedFailure: true,
oldClaim: invalidClaimVolumeModeNil,
newClaim: validClaimVolumeModeFile,
enableResize: false,
},
"invalid-update-volume-mode-nil-to-block": {
isExpectedFailure: true,
oldClaim: invalidClaimVolumeModeNil,
newClaim: validClaimVolumeModeBlock,
enableResize: false,
},
"invalid-update-volume-mode-block-to-nil": {
isExpectedFailure: true,
oldClaim: validClaimVolumeModeBlock,
newClaim: invalidClaimVolumeModeNil,
enableResize: false,
},
"invalid-update-volume-mode-file-to-nil": {
isExpectedFailure: true,
oldClaim: validClaimVolumeModeFile,
newClaim: invalidClaimVolumeModeNil,
enableResize: false,
},
"invalid-update-volume-mode-empty-to-mode": {
isExpectedFailure: true,
oldClaim: validClaim,
newClaim: validClaimVolumeModeBlock,
enableResize: false,
},
"invalid-update-volume-mode-mode-to-empty": {
isExpectedFailure: true,
oldClaim: validClaimVolumeModeBlock,
newClaim: validClaim,
enableResize: false,
},
"invalid-update-change-storage-class-annotation-after-creation": {
isExpectedFailure: true,
oldClaim: validClaimStorageClass,
newClaim: invalidUpdateClaimStorageClass,
enableResize: false,
},
"valid-update-mutable-annotation": {
isExpectedFailure: false,
oldClaim: validClaimAnnotation,
newClaim: validUpdateClaimMutableAnnotation,
enableResize: false,
},
"valid-update-add-annotation": {
isExpectedFailure: false,
oldClaim: validClaim,
newClaim: validAddClaimAnnotation,
enableResize: false,
},
"valid-size-update-resize-disabled": {
isExpectedFailure: true,
oldClaim: validClaim,
newClaim: validSizeUpdate,
enableResize: false,
oldClaim: validClaim,
newClaim: validSizeUpdate,
},
"valid-size-update-resize-enabled": {
isExpectedFailure: false,
oldClaim: validClaim,
newClaim: validSizeUpdate,
enableResize: true,
},
"invalid-size-update-resize-enabled": {
isExpectedFailure: true,
oldClaim: validClaim,
newClaim: invalidSizeUpdate,
enableResize: true,
},
"unbound-size-update-resize-enabled": {
isExpectedFailure: true,
oldClaim: validClaim,
newClaim: unboundSizeUpdate,
enableResize: true,
},
"valid-upgrade-storage-class-annotation-to-spec": {
isExpectedFailure: false,
oldClaim: validClaimStorageClass,
newClaim: validClaimStorageClassInSpec,
enableResize: false,
},
"invalid-upgrade-storage-class-annotation-to-spec": {
isExpectedFailure: true,
oldClaim: validClaimStorageClass,
newClaim: invalidClaimStorageClassInSpec,
enableResize: false,
},
"valid-upgrade-storage-class-annotation-to-annotation-and-spec": {
isExpectedFailure: false,
oldClaim: validClaimStorageClass,
newClaim: validClaimStorageClassInAnnotationAndSpec,
enableResize: false,
},
"invalid-upgrade-storage-class-annotation-to-annotation-and-spec": {
isExpectedFailure: true,
oldClaim: validClaimStorageClass,
newClaim: invalidClaimStorageClassInAnnotationAndSpec,
enableResize: false,
},
"invalid-upgrade-storage-class-in-spec": {
isExpectedFailure: true,
oldClaim: validClaimStorageClassInSpec,
newClaim: invalidClaimStorageClassInSpec,
enableResize: false,
},
"invalid-downgrade-storage-class-spec-to-annotation": {
isExpectedFailure: true,
oldClaim: validClaimStorageClassInSpec,
newClaim: validClaimStorageClass,
enableResize: false,
},
"valid-update-rwop-used-and-rwop-feature-disabled": {
isExpectedFailure: false,
oldClaim: validClaimRWOPAccessMode,
newClaim: validClaimRWOPAccessModeAddAnnotation,
enableResize: false,
},
"valid-expand-shrink-resize-enabled": {
oldClaim: validClaimShrinkInitial,
newClaim: validClaimShrink,
enableResize: true,
enableRecoverFromExpansion: true,
},
"invalid-expand-shrink-resize-enabled": {
oldClaim: validClaimShrinkInitial,
newClaim: invalidClaimShrink,
enableResize: true,
enableRecoverFromExpansion: true,
isExpectedFailure: true,
},
"invalid-expand-shrink-to-status-resize-enabled": {
oldClaim: validClaimShrinkInitial,
newClaim: invalidShrinkToStatus,
enableResize: true,
enableRecoverFromExpansion: true,
isExpectedFailure: true,
},
"invalid-expand-shrink-recover-disabled": {
oldClaim: validClaimShrinkInitial,
newClaim: validClaimShrink,
enableResize: true,
enableRecoverFromExpansion: false,
isExpectedFailure: true,
},
"invalid-expand-shrink-resize-disabled": {
oldClaim: validClaimShrinkInitial,
newClaim: validClaimShrink,
enableResize: false,
enableRecoverFromExpansion: true,
isExpectedFailure: true,
},
"unbound-size-shrink-resize-enabled": {
oldClaim: validClaimShrinkInitial,
newClaim: unboundShrink,
enableResize: true,
enableRecoverFromExpansion: true,
isExpectedFailure: true,
},
@@ -2324,7 +2282,6 @@ func TestValidatePersistentVolumeClaimUpdate(t *testing.T) {
for name, scenario := range scenarios {
t.Run(name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ExpandPersistentVolumes, scenario.enableResize)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.RecoverVolumeExpansionFailure, scenario.enableRecoverFromExpansion)()
scenario.oldClaim.ResourceVersion = "1"
scenario.newClaim.ResourceVersion = "1"
@@ -2351,7 +2308,6 @@ func TestValidationOptionsForPersistentVolumeClaim(t *testing.T) {
enableReadWriteOncePod: true,
expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
AllowReadWriteOncePod: true,
EnableExpansion: true,
EnableRecoverFromExpansionFailure: false,
},
},
@@ -2360,7 +2316,6 @@ func TestValidationOptionsForPersistentVolumeClaim(t *testing.T) {
enableReadWriteOncePod: true,
expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
AllowReadWriteOncePod: true,
EnableExpansion: true,
EnableRecoverFromExpansionFailure: false,
},
},
@@ -2369,7 +2324,6 @@ func TestValidationOptionsForPersistentVolumeClaim(t *testing.T) {
enableReadWriteOncePod: false,
expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
AllowReadWriteOncePod: false,
EnableExpansion: true,
EnableRecoverFromExpansionFailure: false,
},
},
@@ -2378,7 +2332,6 @@ func TestValidationOptionsForPersistentVolumeClaim(t *testing.T) {
enableReadWriteOncePod: true,
expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
AllowReadWriteOncePod: true,
EnableExpansion: true,
EnableRecoverFromExpansionFailure: false,
},
},
@@ -2387,7 +2340,6 @@ func TestValidationOptionsForPersistentVolumeClaim(t *testing.T) {
enableReadWriteOncePod: false,
expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
AllowReadWriteOncePod: true,
EnableExpansion: true,
EnableRecoverFromExpansionFailure: false,
},
},

View File

@@ -1,40 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/apis/storage"
"k8s.io/kubernetes/pkg/features"
)
// DropDisabledFields removes disabled fields from the StorageClass object.
func DropDisabledFields(class, oldClass *storage.StorageClass) {
if !utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) && !allowVolumeExpansionInUse(oldClass) {
class.AllowVolumeExpansion = nil
}
}
func allowVolumeExpansionInUse(oldClass *storage.StorageClass) bool {
if oldClass == nil {
return false
}
if oldClass.AllowVolumeExpansion != nil {
return true
}
return false
}

View File

@@ -1,108 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"fmt"
"reflect"
"testing"
"k8s.io/apimachinery/pkg/util/diff"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/apis/storage"
"k8s.io/kubernetes/pkg/features"
)
func TestDropAllowVolumeExpansion(t *testing.T) {
allowVolumeExpansion := false
scWithoutAllowVolumeExpansion := func() *storage.StorageClass {
return &storage.StorageClass{}
}
scWithAllowVolumeExpansion := func() *storage.StorageClass {
return &storage.StorageClass{
AllowVolumeExpansion: &allowVolumeExpansion,
}
}
scInfo := []struct {
description string
hasAllowVolumeExpansion bool
sc func() *storage.StorageClass
}{
{
description: "StorageClass Without AllowVolumeExpansion",
hasAllowVolumeExpansion: false,
sc: scWithoutAllowVolumeExpansion,
},
{
description: "StorageClass With AllowVolumeExpansion",
hasAllowVolumeExpansion: true,
sc: scWithAllowVolumeExpansion,
},
{
description: "is nil",
hasAllowVolumeExpansion: false,
sc: func() *storage.StorageClass { return nil },
},
}
for _, enabled := range []bool{true, false} {
for _, oldStorageClassInfo := range scInfo {
for _, newStorageClassInfo := range scInfo {
oldStorageClassHasAllowVolumeExpansion, oldStorageClass := oldStorageClassInfo.hasAllowVolumeExpansion, oldStorageClassInfo.sc()
newStorageClassHasAllowVolumeExpansion, newStorageClass := newStorageClassInfo.hasAllowVolumeExpansion, newStorageClassInfo.sc()
if newStorageClass == nil {
continue
}
t.Run(fmt.Sprintf("feature enabled=%v, old StorageClass %v, new StorageClass %v", enabled, oldStorageClassInfo.description, newStorageClassInfo.description), func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ExpandPersistentVolumes, enabled)()
DropDisabledFields(newStorageClass, oldStorageClass)
// old StorageClass should never be changed
if !reflect.DeepEqual(oldStorageClass, oldStorageClassInfo.sc()) {
t.Errorf("old StorageClass changed: %v", diff.ObjectReflectDiff(oldStorageClass, oldStorageClassInfo.sc()))
}
switch {
case enabled || oldStorageClassHasAllowVolumeExpansion:
// new StorageClass should not be changed if the feature is enabled, or if the old StorageClass had AllowVolumeExpansion
if !reflect.DeepEqual(newStorageClass, newStorageClassInfo.sc()) {
t.Errorf("new StorageClass changed: %v", diff.ObjectReflectDiff(newStorageClass, newStorageClassInfo.sc()))
}
case newStorageClassHasAllowVolumeExpansion:
// new StorageClass should be changed
if reflect.DeepEqual(newStorageClass, newStorageClassInfo.sc()) {
t.Errorf("new StorageClass was not changed")
}
// new StorageClass should not have AllowVolumeExpansion
if !reflect.DeepEqual(newStorageClass, scWithoutAllowVolumeExpansion()) {
t.Errorf("new StorageClass had StorageClassAllowVolumeExpansion: %v", diff.ObjectReflectDiff(newStorageClass, scWithoutAllowVolumeExpansion()))
}
default:
// new StorageClass should not need to be changed
if !reflect.DeepEqual(newStorageClass, newStorageClassInfo.sc()) {
t.Errorf("new StorageClass changed: %v", diff.ObjectReflectDiff(newStorageClass, newStorageClassInfo.sc()))
}
}
})
}
}
}
}