Merge pull request #72251 from sbezverk/VolumeMode

VolumeMode - Update DropDisabled[Alpha]Fields behaviour
pull/564/head
Kubernetes Prow Robot 2018-12-26 16:20:36 -08:00 committed by GitHub
commit d61286987d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 192 additions and 175 deletions

View File

@ -25,13 +25,8 @@ import (
// DropDisabledFields removes disabled fields from the pv spec. // DropDisabledFields removes disabled fields from the pv spec.
// This should be called from PrepareForCreate/PrepareForUpdate for all resources containing a pv spec. // This should be called from PrepareForCreate/PrepareForUpdate for all resources containing a pv spec.
func DropDisabledFields(pvSpec *api.PersistentVolumeSpec, oldPVSpec *api.PersistentVolumeSpec) { func DropDisabledFields(pvSpec *api.PersistentVolumeSpec, oldPVSpec *api.PersistentVolumeSpec) {
if !utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { if !utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) && !volumeModeInUse(oldPVSpec) {
// TODO(liggitt): change this to only drop pvSpec.VolumeMode if (oldPVSpec == nil || oldPVSpec.VolumeMode == nil)
// Requires more coordinated changes to validation
pvSpec.VolumeMode = nil pvSpec.VolumeMode = nil
if oldPVSpec != nil {
oldPVSpec.VolumeMode = nil
}
} }
if !utilfeature.DefaultFeatureGate.Enabled(features.CSIPersistentVolume) { if !utilfeature.DefaultFeatureGate.Enabled(features.CSIPersistentVolume) {
@ -41,3 +36,13 @@ func DropDisabledFields(pvSpec *api.PersistentVolumeSpec, oldPVSpec *api.Persist
} }
} }
} }
func volumeModeInUse(oldPVSpec *api.PersistentVolumeSpec) bool {
if oldPVSpec == nil {
return false
}
if oldPVSpec.VolumeMode != nil {
return true
}
return false
}

View File

@ -106,13 +106,12 @@ func TestDropDisabledFields(t *testing.T) {
oldSpec: specWithMode(nil), oldSpec: specWithMode(nil),
expectOldSpec: specWithMode(nil), expectOldSpec: specWithMode(nil),
}, },
// TODO: consider changing this case to preserve "disabled block does not clear new on update when old pv did use block": {
"disabled block clears old and new on update when old pv did use block": {
blockEnabled: false, blockEnabled: false,
newSpec: specWithMode(&modeBlock), newSpec: specWithMode(&modeBlock),
expectNewSpec: specWithMode(nil), expectNewSpec: specWithMode(&modeBlock),
oldSpec: specWithMode(&modeBlock), oldSpec: specWithMode(&modeBlock),
expectOldSpec: specWithMode(nil), expectOldSpec: specWithMode(&modeBlock),
}, },
"enabled block preserves new": { "enabled block preserves new": {

View File

@ -37,6 +37,7 @@ go_test(
deps = [ deps = [
"//pkg/apis/core:go_default_library", "//pkg/apis/core:go_default_library",
"//pkg/features:go_default_library", "//pkg/features:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library", "//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/feature/testing:go_default_library", "//staging/src/k8s.io/apiserver/pkg/util/feature/testing:go_default_library",
], ],

View File

@ -25,10 +25,17 @@ import (
// DropDisabledFields removes disabled fields from the pvc spec. // DropDisabledFields removes disabled fields from the pvc spec.
// This should be called from PrepareForCreate/PrepareForUpdate for all resources containing a pvc spec. // This should be called from PrepareForCreate/PrepareForUpdate for all resources containing a pvc spec.
func DropDisabledFields(pvcSpec, oldPVCSpec *core.PersistentVolumeClaimSpec) { func DropDisabledFields(pvcSpec, oldPVCSpec *core.PersistentVolumeClaimSpec) {
if !utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { if !utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) && !volumeModeInUse(oldPVCSpec) {
pvcSpec.VolumeMode = nil pvcSpec.VolumeMode = nil
if oldPVCSpec != nil {
oldPVCSpec.VolumeMode = nil
}
} }
} }
func volumeModeInUse(oldPVCSpec *core.PersistentVolumeClaimSpec) bool {
if oldPVCSpec == nil {
return false
}
if oldPVCSpec.VolumeMode != nil {
return true
}
return false
}

View File

@ -17,8 +17,11 @@ limitations under the License.
package persistentvolumeclaim package persistentvolumeclaim
import ( import (
"fmt"
"reflect"
"testing" "testing"
"k8s.io/apimachinery/pkg/util/diff"
utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeature "k8s.io/apiserver/pkg/util/feature"
utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing" utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing"
"k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/core"
@ -28,33 +31,89 @@ import (
func TestDropAlphaPVCVolumeMode(t *testing.T) { func TestDropAlphaPVCVolumeMode(t *testing.T) {
vmode := core.PersistentVolumeFilesystem vmode := core.PersistentVolumeFilesystem
// PersistentVolume with VolumeMode set pvcWithoutVolumeMode := func() *core.PersistentVolumeClaim {
pvc := core.PersistentVolumeClaim{ return &core.PersistentVolumeClaim{
Spec: core.PersistentVolumeClaimSpec{ Spec: core.PersistentVolumeClaimSpec{
AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, VolumeMode: nil,
VolumeMode: &vmode, },
}
}
pvcWithVolumeMode := func() *core.PersistentVolumeClaim {
return &core.PersistentVolumeClaim{
Spec: core.PersistentVolumeClaimSpec{
VolumeMode: &vmode,
},
}
}
pvcInfo := []struct {
description string
hasVolumeMode bool
pvc func() *core.PersistentVolumeClaim
}{
{
description: "pvc without VolumeMode",
hasVolumeMode: false,
pvc: pvcWithoutVolumeMode,
},
{
description: "pvc with Filesystem VolumeMode",
hasVolumeMode: true,
pvc: pvcWithVolumeMode,
},
{
description: "is nil",
hasVolumeMode: false,
pvc: func() *core.PersistentVolumeClaim { return nil },
}, },
} }
// Enable alpha feature BlockVolume for _, enabled := range []bool{true, false} {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.BlockVolume, true)() for _, oldpvcInfo := range pvcInfo {
// now test dropping the fields - should not be dropped for _, newpvcInfo := range pvcInfo {
DropDisabledFields(&pvc.Spec, nil) oldpvcHasVolumeMode, oldpvc := oldpvcInfo.hasVolumeMode, oldpvcInfo.pvc()
newpvcHasVolumeMode, newpvc := newpvcInfo.hasVolumeMode, newpvcInfo.pvc()
if newpvc == nil {
continue
}
// check to make sure VolumeDevices is still present t.Run(fmt.Sprintf("feature enabled=%v, old pvc %v, new pvc %v", enabled, oldpvcInfo.description, newpvcInfo.description), func(t *testing.T) {
// if featureset is set to true defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.BlockVolume, enabled)()
if pvc.Spec.VolumeMode == nil {
t.Error("VolumeMode in pvc.Spec should not have been dropped based on feature-gate")
}
// Disable alpha feature BlockVolume var oldpvcSpec *core.PersistentVolumeClaimSpec
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.BlockVolume, false)() if oldpvc != nil {
// now test dropping the fields oldpvcSpec = &oldpvc.Spec
DropDisabledFields(&pvc.Spec, nil) }
DropDisabledFields(&newpvc.Spec, oldpvcSpec)
// check to make sure VolumeDevices is nil // old pvc should never be changed
// if featureset is set to false if !reflect.DeepEqual(oldpvc, oldpvcInfo.pvc()) {
if pvc.Spec.VolumeMode != nil { t.Errorf("old pvc changed: %v", diff.ObjectReflectDiff(oldpvc, oldpvcInfo.pvc()))
t.Error("DropDisabledFields VolumeMode for pvc.Spec failed") }
switch {
case enabled || oldpvcHasVolumeMode:
// new pvc should not be changed if the feature is enabled, or if the old pvc had BlockVolume
if !reflect.DeepEqual(newpvc, newpvcInfo.pvc()) {
t.Errorf("new pvc changed: %v", diff.ObjectReflectDiff(newpvc, newpvcInfo.pvc()))
}
case newpvcHasVolumeMode:
// new pvc should be changed
if reflect.DeepEqual(newpvc, newpvcInfo.pvc()) {
t.Errorf("new pvc was not changed")
}
// new pvc should not have BlockVolume
if !reflect.DeepEqual(newpvc, pvcWithoutVolumeMode()) {
t.Errorf("new pvc had pvcBlockVolume: %v", diff.ObjectReflectDiff(newpvc, pvcWithoutVolumeMode()))
}
default:
// new pvc should not need to be changed
if !reflect.DeepEqual(newpvc, newpvcInfo.pvc()) {
t.Errorf("new pvc changed: %v", diff.ObjectReflectDiff(newpvc, newpvcInfo.pvc()))
}
}
})
}
}
} }
} }

View File

@ -15,7 +15,6 @@ go_library(
deps = [ deps = [
"//pkg/apis/apps:go_default_library", "//pkg/apis/apps:go_default_library",
"//pkg/apis/core:go_default_library", "//pkg/apis/core:go_default_library",
"//pkg/features:go_default_library",
"//pkg/util/parsers:go_default_library", "//pkg/util/parsers:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
@ -26,7 +25,6 @@ go_library(
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/intstr:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//vendor/k8s.io/utils/pointer:go_default_library", "//vendor/k8s.io/utils/pointer:go_default_library",
], ],
) )
@ -44,7 +42,6 @@ go_test(
"//pkg/apis/apps:go_default_library", "//pkg/apis/apps:go_default_library",
"//pkg/apis/core:go_default_library", "//pkg/apis/core:go_default_library",
"//pkg/apis/core/fuzzer:go_default_library", "//pkg/apis/core/fuzzer:go_default_library",
"//pkg/features:go_default_library",
"//staging/src/k8s.io/api/apps/v1:go_default_library", "//staging/src/k8s.io/api/apps/v1:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/apitesting/fuzzer:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/apitesting/fuzzer:go_default_library",
@ -55,8 +52,6 @@ go_test(
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/intstr:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/feature/testing:go_default_library",
"//vendor/k8s.io/utils/pointer:go_default_library", "//vendor/k8s.io/utils/pointer:go_default_library",
], ],
) )

View File

@ -22,8 +22,6 @@ import (
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/intstr"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/util/parsers" "k8s.io/kubernetes/pkg/util/parsers"
utilpointer "k8s.io/utils/pointer" utilpointer "k8s.io/utils/pointer"
) )
@ -247,7 +245,7 @@ func SetDefaults_PersistentVolume(obj *v1.PersistentVolume) {
if obj.Spec.PersistentVolumeReclaimPolicy == "" { if obj.Spec.PersistentVolumeReclaimPolicy == "" {
obj.Spec.PersistentVolumeReclaimPolicy = v1.PersistentVolumeReclaimRetain obj.Spec.PersistentVolumeReclaimPolicy = v1.PersistentVolumeReclaimRetain
} }
if obj.Spec.VolumeMode == nil && utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { if obj.Spec.VolumeMode == nil {
obj.Spec.VolumeMode = new(v1.PersistentVolumeMode) obj.Spec.VolumeMode = new(v1.PersistentVolumeMode)
*obj.Spec.VolumeMode = v1.PersistentVolumeFilesystem *obj.Spec.VolumeMode = v1.PersistentVolumeFilesystem
} }
@ -256,7 +254,7 @@ func SetDefaults_PersistentVolumeClaim(obj *v1.PersistentVolumeClaim) {
if obj.Status.Phase == "" { if obj.Status.Phase == "" {
obj.Status.Phase = v1.ClaimPending obj.Status.Phase = v1.ClaimPending
} }
if obj.Spec.VolumeMode == nil && utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { if obj.Spec.VolumeMode == nil {
obj.Spec.VolumeMode = new(v1.PersistentVolumeMode) obj.Spec.VolumeMode = new(v1.PersistentVolumeMode)
*obj.Spec.VolumeMode = v1.PersistentVolumeFilesystem *obj.Spec.VolumeMode = v1.PersistentVolumeFilesystem
} }

View File

@ -26,11 +26,8 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/intstr"
utilfeature "k8s.io/apiserver/pkg/util/feature"
utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing"
"k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/legacyscheme"
corev1 "k8s.io/kubernetes/pkg/apis/core/v1" corev1 "k8s.io/kubernetes/pkg/apis/core/v1"
"k8s.io/kubernetes/pkg/features"
utilpointer "k8s.io/utils/pointer" utilpointer "k8s.io/utils/pointer"
// enforce that all types are installed // enforce that all types are installed
@ -805,63 +802,91 @@ func TestSetDefaultSecret(t *testing.T) {
} }
func TestSetDefaultPersistentVolume(t *testing.T) { func TestSetDefaultPersistentVolume(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.BlockVolume, false)() fsMode := v1.PersistentVolumeFilesystem
pv := &v1.PersistentVolume{} blockMode := v1.PersistentVolumeBlock
obj2 := roundTrip(t, runtime.Object(pv))
pv2 := obj2.(*v1.PersistentVolume)
if pv2.Status.Phase != v1.VolumePending { tests := []struct {
t.Errorf("Expected volume phase %v, got %v", v1.VolumePending, pv2.Status.Phase) name string
} volumeMode *v1.PersistentVolumeMode
if pv2.Spec.PersistentVolumeReclaimPolicy != v1.PersistentVolumeReclaimRetain { expectedVolumeMode v1.PersistentVolumeMode
t.Errorf("Expected pv reclaim policy %v, got %v", v1.PersistentVolumeReclaimRetain, pv2.Spec.PersistentVolumeReclaimPolicy) }{
{
name: "volume mode nil",
volumeMode: nil,
expectedVolumeMode: v1.PersistentVolumeFilesystem,
},
{
name: "volume mode filesystem",
volumeMode: &fsMode,
expectedVolumeMode: v1.PersistentVolumeFilesystem,
},
{
name: "volume mode block",
volumeMode: &blockMode,
expectedVolumeMode: v1.PersistentVolumeBlock,
},
} }
// When feature gate is disabled, field should not be defaulted for _, test := range tests {
defaultMode := v1.PersistentVolumeFilesystem pv := &v1.PersistentVolume{
outputMode := pv2.Spec.VolumeMode Spec: v1.PersistentVolumeSpec{
if outputMode != nil { VolumeMode: test.volumeMode,
t.Errorf("Expected VolumeMode to not be defaulted, got: %+v", outputMode) },
} }
obj1 := roundTrip(t, runtime.Object(pv))
// When feature gate is enabled, field should be defaulted pv1 := obj1.(*v1.PersistentVolume)
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.BlockVolume, true)() if pv1.Status.Phase != v1.VolumePending {
obj3 := roundTrip(t, runtime.Object(pv)).(*v1.PersistentVolume) t.Errorf("Expected claim phase %v, got %v", v1.ClaimPending, pv1.Status.Phase)
outputMode3 := obj3.Spec.VolumeMode }
if pv1.Spec.PersistentVolumeReclaimPolicy != v1.PersistentVolumeReclaimRetain {
if outputMode3 == nil { t.Errorf("Expected pv reclaim policy %v, got %v", v1.PersistentVolumeReclaimRetain, pv1.Spec.PersistentVolumeReclaimPolicy)
t.Errorf("Expected VolumeMode to be defaulted to: %+v, got: nil", defaultMode) }
} else if *outputMode3 != defaultMode { if *pv1.Spec.VolumeMode != test.expectedVolumeMode {
t.Errorf("Expected VolumeMode to be defaulted to: %+v, got: %+v", defaultMode, outputMode3) t.Errorf("Test %s failed, Expected VolumeMode: %v, but got %v", test.name, test.volumeMode, *pv1.Spec.VolumeMode)
}
} }
} }
func TestSetDefaultPersistentVolumeClaim(t *testing.T) { func TestSetDefaultPersistentVolumeClaim(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.BlockVolume, false)() fsMode := v1.PersistentVolumeFilesystem
pvc := &v1.PersistentVolumeClaim{} blockMode := v1.PersistentVolumeBlock
obj2 := roundTrip(t, runtime.Object(pvc))
pvc2 := obj2.(*v1.PersistentVolumeClaim)
if pvc2.Status.Phase != v1.ClaimPending { tests := []struct {
t.Errorf("Expected claim phase %v, got %v", v1.ClaimPending, pvc2.Status.Phase) name string
volumeMode *v1.PersistentVolumeMode
expectedVolumeMode v1.PersistentVolumeMode
}{
{
name: "volume mode nil",
volumeMode: nil,
expectedVolumeMode: v1.PersistentVolumeFilesystem,
},
{
name: "volume mode filesystem",
volumeMode: &fsMode,
expectedVolumeMode: v1.PersistentVolumeFilesystem,
},
{
name: "volume mode block",
volumeMode: &blockMode,
expectedVolumeMode: v1.PersistentVolumeBlock,
},
} }
// When feature gate is disabled, field should not be defaulted for _, test := range tests {
defaultMode := v1.PersistentVolumeFilesystem pvc := &v1.PersistentVolumeClaim{
outputMode := pvc2.Spec.VolumeMode Spec: v1.PersistentVolumeClaimSpec{
if outputMode != nil { VolumeMode: test.volumeMode,
t.Errorf("Expected VolumeMode to not be defaulted, got: %+v", outputMode) },
} }
obj1 := roundTrip(t, runtime.Object(pvc))
// When feature gate is enabled, field should be defaulted pvc1 := obj1.(*v1.PersistentVolumeClaim)
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.BlockVolume, true)() if pvc1.Status.Phase != v1.ClaimPending {
obj3 := roundTrip(t, runtime.Object(pvc)).(*v1.PersistentVolumeClaim) t.Errorf("Expected claim phase %v, got %v", v1.ClaimPending, pvc1.Status.Phase)
outputMode3 := obj3.Spec.VolumeMode }
if *pvc1.Spec.VolumeMode != test.expectedVolumeMode {
if outputMode3 == nil { t.Errorf("Test %s failed, Expected VolumeMode: %v, but got %v", test.name, test.volumeMode, *pvc1.Spec.VolumeMode)
t.Errorf("Expected VolumeMode to be defaulted to: %+v, got: nil", defaultMode) }
} else if *outputMode3 != defaultMode {
t.Errorf("Expected VolumeMode to be defaulted to: %+v, got: %+v", defaultMode, outputMode3)
} }
} }

View File

@ -1763,9 +1763,7 @@ func ValidatePersistentVolume(pv *core.PersistentVolume) field.ErrorList {
allErrs = append(allErrs, field.Invalid(specPath.Child("storageClassName"), pv.Spec.StorageClassName, msg)) allErrs = append(allErrs, field.Invalid(specPath.Child("storageClassName"), pv.Spec.StorageClassName, msg))
} }
} }
if pv.Spec.VolumeMode != nil && !utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { if pv.Spec.VolumeMode != nil && !supportedVolumeModes.Has(string(*pv.Spec.VolumeMode)) {
allErrs = append(allErrs, field.Forbidden(specPath.Child("volumeMode"), "PersistentVolume volumeMode is disabled by feature-gate"))
} else if pv.Spec.VolumeMode != nil && !supportedVolumeModes.Has(string(*pv.Spec.VolumeMode)) {
allErrs = append(allErrs, field.NotSupported(specPath.Child("volumeMode"), *pv.Spec.VolumeMode, supportedVolumeModes.List())) allErrs = append(allErrs, field.NotSupported(specPath.Child("volumeMode"), *pv.Spec.VolumeMode, supportedVolumeModes.List()))
} }
return allErrs return allErrs
@ -1781,12 +1779,9 @@ func ValidatePersistentVolumeUpdate(newPv, oldPv *core.PersistentVolume) field.E
if !apiequality.Semantic.DeepEqual(newPv.Spec.PersistentVolumeSource, oldPv.Spec.PersistentVolumeSource) { if !apiequality.Semantic.DeepEqual(newPv.Spec.PersistentVolumeSource, oldPv.Spec.PersistentVolumeSource) {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "persistentvolumesource"), "is immutable after creation")) allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "persistentvolumesource"), "is immutable after creation"))
} }
newPv.Status = oldPv.Status newPv.Status = oldPv.Status
if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { allErrs = append(allErrs, ValidateImmutableField(newPv.Spec.VolumeMode, oldPv.Spec.VolumeMode, field.NewPath("volumeMode"))...)
allErrs = append(allErrs, ValidateImmutableField(newPv.Spec.VolumeMode, oldPv.Spec.VolumeMode, field.NewPath("volumeMode"))...)
}
if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) { if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) {
// Allow setting NodeAffinity if oldPv NodeAffinity was not set // Allow setting NodeAffinity if oldPv NodeAffinity was not set
@ -1843,9 +1838,7 @@ func ValidatePersistentVolumeClaimSpec(spec *core.PersistentVolumeClaimSpec, fld
allErrs = append(allErrs, field.Invalid(fldPath.Child("storageClassName"), *spec.StorageClassName, msg)) allErrs = append(allErrs, field.Invalid(fldPath.Child("storageClassName"), *spec.StorageClassName, msg))
} }
} }
if spec.VolumeMode != nil && !utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { if spec.VolumeMode != nil && !supportedVolumeModes.Has(string(*spec.VolumeMode)) {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("volumeMode"), "PersistentVolumeClaim volumeMode is disabled by feature-gate"))
} else if spec.VolumeMode != nil && !supportedVolumeModes.Has(string(*spec.VolumeMode)) {
allErrs = append(allErrs, field.NotSupported(fldPath.Child("volumeMode"), *spec.VolumeMode, supportedVolumeModes.List())) allErrs = append(allErrs, field.NotSupported(fldPath.Child("volumeMode"), *spec.VolumeMode, supportedVolumeModes.List()))
} }
@ -1920,9 +1913,8 @@ func ValidatePersistentVolumeClaimUpdate(newPvc, oldPvc *core.PersistentVolumeCl
} }
} }
if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { allErrs = append(allErrs, ValidateImmutableField(newPvc.Spec.VolumeMode, oldPvc.Spec.VolumeMode, field.NewPath("volumeMode"))...)
allErrs = append(allErrs, ValidateImmutableField(newPvc.Spec.VolumeMode, oldPvc.Spec.VolumeMode, field.NewPath("volumeMode"))...)
}
return allErrs return allErrs
} }

View File

@ -69,7 +69,6 @@ func TestValidatePersistentVolumes(t *testing.T) {
scenarios := map[string]struct { scenarios := map[string]struct {
isExpectedFailure bool isExpectedFailure bool
volume *core.PersistentVolume volume *core.PersistentVolume
disableBlock bool
}{ }{
"good-volume": { "good-volume": {
isExpectedFailure: false, isExpectedFailure: false,
@ -369,24 +368,6 @@ func TestValidatePersistentVolumes(t *testing.T) {
StorageClassName: "-invalid-", StorageClassName: "-invalid-",
}), }),
}, },
"feature disabled valid volume mode": {
disableBlock: true,
isExpectedFailure: true,
volume: testVolume("foo", "", core.PersistentVolumeSpec{
Capacity: core.ResourceList{
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
},
AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
PersistentVolumeSource: core.PersistentVolumeSource{
HostPath: &core.HostPathVolumeSource{
Path: "/foo",
Type: newHostPathType(string(core.HostPathDirectory)),
},
},
StorageClassName: "valid",
VolumeMode: &validMode,
}),
},
"bad-hostpath-volume-backsteps": { "bad-hostpath-volume-backsteps": {
isExpectedFailure: true, isExpectedFailure: true,
volume: testVolume("foo", "", core.PersistentVolumeSpec{ volume: testVolume("foo", "", core.PersistentVolumeSpec{
@ -433,7 +414,6 @@ func TestValidatePersistentVolumes(t *testing.T) {
for name, scenario := range scenarios { for name, scenario := range scenarios {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.BlockVolume, !scenario.disableBlock)()
errs := ValidatePersistentVolume(scenario.volume) errs := ValidatePersistentVolume(scenario.volume)
if len(errs) == 0 && scenario.isExpectedFailure { if len(errs) == 0 && scenario.isExpectedFailure {
t.Errorf("Unexpected success for scenario: %s", name) t.Errorf("Unexpected success for scenario: %s", name)
@ -823,7 +803,6 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
scenarios := map[string]struct { scenarios := map[string]struct {
isExpectedFailure bool isExpectedFailure bool
claim *core.PersistentVolumeClaim claim *core.PersistentVolumeClaim
disableBlock bool
}{ }{
"good-claim": { "good-claim": {
isExpectedFailure: false, isExpectedFailure: false,
@ -1018,31 +997,6 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
StorageClassName: &invalidClassName, StorageClassName: &invalidClassName,
}), }),
}, },
"feature disabled valid volume mode": {
disableBlock: true,
isExpectedFailure: true,
claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
Selector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "key2",
Operator: "Exists",
},
},
},
AccessModes: []core.PersistentVolumeAccessMode{
core.ReadWriteOnce,
core.ReadOnlyMany,
},
Resources: core.ResourceRequirements{
Requests: core.ResourceList{
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
},
},
StorageClassName: &validClassName,
VolumeMode: &validMode,
}),
},
"invalid-volume-mode": { "invalid-volume-mode": {
isExpectedFailure: true, isExpectedFailure: true,
claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
@ -1062,7 +1016,6 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
for name, scenario := range scenarios { for name, scenario := range scenarios {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.BlockVolume, !scenario.disableBlock)()
errs := ValidatePersistentVolumeClaim(scenario.claim) errs := ValidatePersistentVolumeClaim(scenario.claim)
if len(errs) == 0 && scenario.isExpectedFailure { if len(errs) == 0 && scenario.isExpectedFailure {
t.Errorf("Unexpected success for scenario: %s", name) t.Errorf("Unexpected success for scenario: %s", name)
@ -1082,80 +1035,62 @@ func TestAlphaPVVolumeModeUpdate(t *testing.T) {
isExpectedFailure bool isExpectedFailure bool
oldPV *core.PersistentVolume oldPV *core.PersistentVolume
newPV *core.PersistentVolume newPV *core.PersistentVolume
enableBlock bool
}{ }{
"valid-update-volume-mode-block-to-block": { "valid-update-volume-mode-block-to-block": {
isExpectedFailure: false, isExpectedFailure: false,
oldPV: createTestVolModePV(&block), oldPV: createTestVolModePV(&block),
newPV: createTestVolModePV(&block), newPV: createTestVolModePV(&block),
enableBlock: true,
}, },
"valid-update-volume-mode-file-to-file": { "valid-update-volume-mode-file-to-file": {
isExpectedFailure: false, isExpectedFailure: false,
oldPV: createTestVolModePV(&file), oldPV: createTestVolModePV(&file),
newPV: createTestVolModePV(&file), newPV: createTestVolModePV(&file),
enableBlock: true,
}, },
"invalid-update-volume-mode-to-block": { "invalid-update-volume-mode-to-block": {
isExpectedFailure: true, isExpectedFailure: true,
oldPV: createTestVolModePV(&file), oldPV: createTestVolModePV(&file),
newPV: createTestVolModePV(&block), newPV: createTestVolModePV(&block),
enableBlock: true,
}, },
"invalid-update-volume-mode-to-file": { "invalid-update-volume-mode-to-file": {
isExpectedFailure: true, isExpectedFailure: true,
oldPV: createTestVolModePV(&block), oldPV: createTestVolModePV(&block),
newPV: createTestVolModePV(&file), newPV: createTestVolModePV(&file),
enableBlock: true,
},
"invalid-update-blocksupport-disabled": {
isExpectedFailure: true,
oldPV: createTestVolModePV(&block),
newPV: createTestVolModePV(&block),
enableBlock: false,
}, },
"invalid-update-volume-mode-nil-to-file": { "invalid-update-volume-mode-nil-to-file": {
isExpectedFailure: true, isExpectedFailure: true,
oldPV: createTestVolModePV(nil), oldPV: createTestVolModePV(nil),
newPV: createTestVolModePV(&file), newPV: createTestVolModePV(&file),
enableBlock: true,
}, },
"invalid-update-volume-mode-nil-to-block": { "invalid-update-volume-mode-nil-to-block": {
isExpectedFailure: true, isExpectedFailure: true,
oldPV: createTestVolModePV(nil), oldPV: createTestVolModePV(nil),
newPV: createTestVolModePV(&block), newPV: createTestVolModePV(&block),
enableBlock: true,
}, },
"invalid-update-volume-mode-file-to-nil": { "invalid-update-volume-mode-file-to-nil": {
isExpectedFailure: true, isExpectedFailure: true,
oldPV: createTestVolModePV(&file), oldPV: createTestVolModePV(&file),
newPV: createTestVolModePV(nil), newPV: createTestVolModePV(nil),
enableBlock: true,
}, },
"invalid-update-volume-mode-block-to-nil": { "invalid-update-volume-mode-block-to-nil": {
isExpectedFailure: true, isExpectedFailure: true,
oldPV: createTestVolModePV(&block), oldPV: createTestVolModePV(&block),
newPV: createTestVolModePV(nil), newPV: createTestVolModePV(nil),
enableBlock: true,
}, },
"invalid-update-volume-mode-nil-to-nil": { "invalid-update-volume-mode-nil-to-nil": {
isExpectedFailure: false, isExpectedFailure: false,
oldPV: createTestVolModePV(nil), oldPV: createTestVolModePV(nil),
newPV: createTestVolModePV(nil), newPV: createTestVolModePV(nil),
enableBlock: true,
}, },
"invalid-update-volume-mode-empty-to-mode": { "invalid-update-volume-mode-empty-to-mode": {
isExpectedFailure: true, isExpectedFailure: true,
oldPV: createTestPV(), oldPV: createTestPV(),
newPV: createTestVolModePV(&block), newPV: createTestVolModePV(&block),
enableBlock: true,
}, },
} }
for name, scenario := range scenarios { for name, scenario := range scenarios {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
// ensure we have a resource version specified for updates // ensure we have a resource version specified for updates
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.BlockVolume, scenario.enableBlock)()
errs := ValidatePersistentVolumeUpdate(scenario.newPV, scenario.oldPV) errs := ValidatePersistentVolumeUpdate(scenario.newPV, scenario.oldPV)
if len(errs) == 0 && scenario.isExpectedFailure { if len(errs) == 0 && scenario.isExpectedFailure {
t.Errorf("Unexpected success for scenario: %s", name) t.Errorf("Unexpected success for scenario: %s", name)
@ -1503,8 +1438,9 @@ func TestValidatePersistentVolumeClaimUpdate(t *testing.T) {
enableResize: false, enableResize: false,
enableBlock: true, enableBlock: true,
}, },
"invalid-update-blocksupport-disabled": { // with the new validation changes this test should not fail
isExpectedFailure: true, "noop-update-volumemode-allowed": {
isExpectedFailure: false,
oldClaim: validClaimVolumeModeFile, oldClaim: validClaimVolumeModeFile,
newClaim: validClaimVolumeModeFile, newClaim: validClaimVolumeModeFile,
enableResize: false, enableResize: false,