Merge pull request #121104 from carlory/kep-3751-api-changes

[KEP-3571] introduce the VolumeAttributesClass API
This commit is contained in:
Kubernetes Prow Robot
2023-10-31 20:23:50 +01:00
committed by GitHub
144 changed files with 9439 additions and 1288 deletions

View File

@@ -1665,6 +1665,8 @@ var allowedTemplateObjectMetaFields = map[string]bool{
// PersistentVolumeSpecValidationOptions contains the different settings for PeristentVolume validation
type PersistentVolumeSpecValidationOptions struct {
// Allow users to modify the class of volume attributes
EnableVolumeAttributesClass bool
}
// ValidatePersistentVolumeName checks that a name is appropriate for a
@@ -1685,7 +1687,13 @@ var supportedReclaimPolicy = sets.New(
var supportedVolumeModes = sets.New(core.PersistentVolumeBlock, core.PersistentVolumeFilesystem)
func ValidationOptionsForPersistentVolume(pv, oldPv *core.PersistentVolume) PersistentVolumeSpecValidationOptions {
return PersistentVolumeSpecValidationOptions{}
opts := PersistentVolumeSpecValidationOptions{
EnableVolumeAttributesClass: utilfeature.DefaultMutableFeatureGate.Enabled(features.VolumeAttributesClass),
}
if oldPv != nil && oldPv.Spec.VolumeAttributesClassName != nil {
opts.EnableVolumeAttributesClass = true
}
return opts
}
func ValidatePersistentVolumeSpec(pvSpec *core.PersistentVolumeSpec, pvName string, validateInlinePersistentVolumeSpec bool, fldPath *field.Path, opts PersistentVolumeSpecValidationOptions) field.ErrorList {
@@ -1970,6 +1978,18 @@ func ValidatePersistentVolumeSpec(pvSpec *core.PersistentVolumeSpec, pvName stri
}
}
}
if pvSpec.VolumeAttributesClassName != nil && opts.EnableVolumeAttributesClass {
if len(*pvSpec.VolumeAttributesClassName) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("volumeAttributesClassName"), "an empty string is disallowed"))
} else {
for _, msg := range ValidateClassName(*pvSpec.VolumeAttributesClassName, false) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("volumeAttributesClassName"), *pvSpec.VolumeAttributesClassName, msg))
}
}
if pvSpec.CSI == nil {
allErrs = append(allErrs, field.Required(fldPath.Child("csi"), "has to be specified when using volumeAttributesClassName"))
}
}
return allErrs
}
@@ -2004,6 +2024,17 @@ func ValidatePersistentVolumeUpdate(newPv, oldPv *core.PersistentVolume, opts Pe
allErrs = append(allErrs, validatePvNodeAffinity(newPv.Spec.NodeAffinity, oldPv.Spec.NodeAffinity, field.NewPath("nodeAffinity"))...)
}
if !apiequality.Semantic.DeepEqual(oldPv.Spec.VolumeAttributesClassName, newPv.Spec.VolumeAttributesClassName) {
if !utilfeature.DefaultFeatureGate.Enabled(features.VolumeAttributesClass) {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "volumeAttributesClassName"), "update is forbidden when the VolumeAttributesClass feature gate is disabled"))
}
if opts.EnableVolumeAttributesClass {
if oldPv.Spec.VolumeAttributesClassName != nil && newPv.Spec.VolumeAttributesClassName == nil {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "volumeAttributesClassName"), "update from non-nil value to nil is forbidden"))
}
}
}
return allErrs
}
@@ -2023,12 +2054,15 @@ type PersistentVolumeClaimSpecValidationOptions struct {
AllowInvalidLabelValueInSelector bool
// Allow to validate the API group of the data source and data source reference
AllowInvalidAPIGroupInDataSourceOrRef bool
// Allow users to modify the class of volume attributes
EnableVolumeAttributesClass bool
}
func ValidationOptionsForPersistentVolumeClaim(pvc, oldPvc *core.PersistentVolumeClaim) PersistentVolumeClaimSpecValidationOptions {
opts := PersistentVolumeClaimSpecValidationOptions{
EnableRecoverFromExpansionFailure: utilfeature.DefaultFeatureGate.Enabled(features.RecoverVolumeExpansionFailure),
AllowInvalidLabelValueInSelector: false,
EnableVolumeAttributesClass: utilfeature.DefaultFeatureGate.Enabled(features.VolumeAttributesClass),
}
if oldPvc == nil {
// If there's no old PVC, use the options based solely on feature enablement
@@ -2038,6 +2072,11 @@ func ValidationOptionsForPersistentVolumeClaim(pvc, oldPvc *core.PersistentVolum
// If the old object had an invalid API group in the data source or data source reference, continue to allow it in the new object
opts.AllowInvalidAPIGroupInDataSourceOrRef = allowInvalidAPIGroupInDataSourceOrRef(&oldPvc.Spec)
if oldPvc.Spec.VolumeAttributesClassName != nil {
// If the old object had a volume attributes class, continue to validate it in the new object.
opts.EnableVolumeAttributesClass = true
}
labelSelectorValidationOpts := unversionedvalidation.LabelSelectorValidationOptions{
AllowInvalidLabelValueInSelector: opts.AllowInvalidLabelValueInSelector,
}
@@ -2056,6 +2095,7 @@ func ValidationOptionsForPersistentVolumeClaim(pvc, oldPvc *core.PersistentVolum
func ValidationOptionsForPersistentVolumeClaimTemplate(claimTemplate, oldClaimTemplate *core.PersistentVolumeClaimTemplate) PersistentVolumeClaimSpecValidationOptions {
opts := PersistentVolumeClaimSpecValidationOptions{
AllowInvalidLabelValueInSelector: false,
EnableVolumeAttributesClass: utilfeature.DefaultFeatureGate.Enabled(features.VolumeAttributesClass),
}
if oldClaimTemplate == nil {
// If there's no old PVC template, use the options based solely on feature enablement
@@ -2211,6 +2251,11 @@ func ValidatePersistentVolumeClaimSpec(spec *core.PersistentVolumeClaimSpec, fld
"must match dataSourceRef"))
}
}
if spec.VolumeAttributesClassName != nil && len(*spec.VolumeAttributesClassName) > 0 && opts.EnableVolumeAttributesClass {
for _, msg := range ValidateClassName(*spec.VolumeAttributesClassName, false) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("volumeAttributesClassName"), *spec.VolumeAttributesClassName, msg))
}
}
return allErrs
}
@@ -2254,6 +2299,8 @@ func ValidatePersistentVolumeClaimUpdate(newPvc, oldPvc *core.PersistentVolumeCl
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 volume attributes class name is same.
newPvcClone.Spec.VolumeAttributesClassName = oldPvcClone.Spec.VolumeAttributesClassName // +k8s:verify-mutation:reason=clone
oldSize := oldPvc.Spec.Resources.Requests["storage"]
newSize := newPvc.Spec.Resources.Requests["storage"]
@@ -2261,7 +2308,7 @@ func ValidatePersistentVolumeClaimUpdate(newPvc, oldPvc *core.PersistentVolumeCl
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)))
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), fmt.Sprintf("spec is immutable after creation except resources.requests and volumeAttributesClassName for bound claims\n%v", specDiff)))
}
if newSize.Cmp(oldSize) < 0 {
if !opts.EnableRecoverFromExpansionFailure {
@@ -2278,6 +2325,21 @@ func ValidatePersistentVolumeClaimUpdate(newPvc, oldPvc *core.PersistentVolumeCl
allErrs = append(allErrs, ValidateImmutableField(newPvc.Spec.VolumeMode, oldPvc.Spec.VolumeMode, field.NewPath("volumeMode"))...)
if !apiequality.Semantic.DeepEqual(oldPvc.Spec.VolumeAttributesClassName, newPvc.Spec.VolumeAttributesClassName) {
if !utilfeature.DefaultFeatureGate.Enabled(features.VolumeAttributesClass) {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "volumeAttributesClassName"), "update is forbidden when the VolumeAttributesClass feature gate is disabled"))
}
if opts.EnableVolumeAttributesClass {
if oldPvc.Spec.VolumeAttributesClassName != nil {
if newPvc.Spec.VolumeAttributesClassName == nil {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "volumeAttributesClassName"), "update from non-nil value to nil is forbidden"))
} else if len(*newPvc.Spec.VolumeAttributesClassName) == 0 {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "volumeAttributesClassName"), "update from non-nil value to an empty string is forbidden"))
}
}
}
}
return allErrs
}

View File

@@ -46,6 +46,7 @@ import (
"k8s.io/kubernetes/pkg/capabilities"
"k8s.io/kubernetes/pkg/features"
utilpointer "k8s.io/utils/pointer"
"k8s.io/utils/ptr"
)
const (
@@ -109,8 +110,9 @@ func TestValidatePersistentVolumes(t *testing.T) {
validMode := core.PersistentVolumeFilesystem
invalidMode := core.PersistentVolumeMode("fakeVolumeMode")
scenarios := map[string]struct {
isExpectedFailure bool
volume *core.PersistentVolume
isExpectedFailure bool
enableVolumeAttributesClass bool
volume *core.PersistentVolume
}{
"good-volume": {
isExpectedFailure: false,
@@ -478,10 +480,84 @@ func TestValidatePersistentVolumes(t *testing.T) {
},
}),
},
"invalid-volume-attributes-class-name": {
isExpectedFailure: true,
enableVolumeAttributesClass: true,
volume: testVolume("invalid-volume-attributes-class-name", "", 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: "invalid",
VolumeAttributesClassName: ptr.To("-invalid-"),
}),
},
"invalid-empty-volume-attributes-class-name": {
isExpectedFailure: true,
enableVolumeAttributesClass: true,
volume: testVolume("invalid-empty-volume-attributes-class-name", "", 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: "invalid",
VolumeAttributesClassName: ptr.To(""),
}),
},
"volume-with-good-volume-attributes-class-and-matched-volume-resource-when-feature-gate-is-on": {
isExpectedFailure: false,
enableVolumeAttributesClass: true,
volume: testVolume("foo", "", core.PersistentVolumeSpec{
Capacity: core.ResourceList{
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
},
AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
PersistentVolumeSource: core.PersistentVolumeSource{
CSI: &core.CSIPersistentVolumeSource{
Driver: "test-driver",
VolumeHandle: "test-123",
},
},
StorageClassName: "valid",
VolumeAttributesClassName: ptr.To("valid"),
}),
},
"volume-with-good-volume-attributes-class-and-mismatched-volume-resource-when-feature-gate-is-on": {
isExpectedFailure: true,
enableVolumeAttributesClass: 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",
VolumeAttributesClassName: ptr.To("valid"),
}),
},
}
for name, scenario := range scenarios {
t.Run(name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, scenario.enableVolumeAttributesClass)()
opts := ValidationOptionsForPersistentVolume(scenario.volume, nil)
errs := ValidatePersistentVolume(scenario.volume, opts)
if len(errs) == 0 && scenario.isExpectedFailure {
@@ -882,17 +958,48 @@ func TestValidatePersistentVolumeSourceUpdate(t *testing.T) {
func TestValidationOptionsForPersistentVolume(t *testing.T) {
tests := map[string]struct {
oldPv *core.PersistentVolume
expectValidationOpts PersistentVolumeSpecValidationOptions
oldPv *core.PersistentVolume
enableVolumeAttributesClass bool
expectValidationOpts PersistentVolumeSpecValidationOptions
}{
"nil old pv": {
oldPv: nil,
expectValidationOpts: PersistentVolumeSpecValidationOptions{},
},
"nil old pv and feature-gate VolumeAttrributesClass is on": {
oldPv: nil,
enableVolumeAttributesClass: true,
expectValidationOpts: PersistentVolumeSpecValidationOptions{EnableVolumeAttributesClass: true},
},
"nil old pv and feature-gate VolumeAttrributesClass is off": {
oldPv: nil,
enableVolumeAttributesClass: false,
expectValidationOpts: PersistentVolumeSpecValidationOptions{EnableVolumeAttributesClass: false},
},
"old pv has volumeAttributesClass and feature-gate VolumeAttrributesClass is on": {
oldPv: &core.PersistentVolume{
Spec: core.PersistentVolumeSpec{
VolumeAttributesClassName: ptr.To("foo"),
},
},
enableVolumeAttributesClass: true,
expectValidationOpts: PersistentVolumeSpecValidationOptions{EnableVolumeAttributesClass: true},
},
"old pv has volumeAttributesClass and feature-gate VolumeAttrributesClass is off": {
oldPv: &core.PersistentVolume{
Spec: core.PersistentVolumeSpec{
VolumeAttributesClassName: ptr.To("foo"),
},
},
enableVolumeAttributesClass: false,
expectValidationOpts: PersistentVolumeSpecValidationOptions{EnableVolumeAttributesClass: true},
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, tc.enableVolumeAttributesClass)()
opts := ValidationOptionsForPersistentVolume(nil, tc.oldPv)
if opts != tc.expectValidationOpts {
t.Errorf("Expected opts: %+v, received: %+v", opts, tc.expectValidationOpts)
@@ -919,6 +1026,14 @@ func getCSIVolumeWithSecret(pv *core.PersistentVolume, secret *core.SecretRefere
return pvCopy
}
func pvcWithVolumeAttributesClassName(vacName *string) *core.PersistentVolumeClaim {
return &core.PersistentVolumeClaim{
Spec: core.PersistentVolumeClaimSpec{
VolumeAttributesClassName: vacName,
},
}
}
func pvcWithDataSource(dataSource *core.TypedLocalObjectReference) *core.PersistentVolumeClaim {
return &core.PersistentVolumeClaim{
Spec: core.PersistentVolumeClaimSpec{
@@ -934,6 +1049,14 @@ func pvcWithDataSourceRef(ref *core.TypedObjectReference) *core.PersistentVolume
}
}
func pvcTemplateWithVolumeAttributesClassName(vacName *string) *core.PersistentVolumeClaimTemplate {
return &core.PersistentVolumeClaimTemplate{
Spec: core.PersistentVolumeClaimSpec{
VolumeAttributesClassName: vacName,
},
}
}
func testLocalVolume(path string, affinity *core.VolumeNodeAffinity) core.PersistentVolumeSpec {
return core.PersistentVolumeSpec{
Capacity: core.ResourceList{
@@ -1001,6 +1124,24 @@ func TestValidateLocalVolumes(t *testing.T) {
}
}
func testVolumeWithVolumeAttributesClass(vacName *string) *core.PersistentVolume {
return testVolume("test-volume-with-volume-attributes-class", "",
core.PersistentVolumeSpec{
Capacity: core.ResourceList{
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
},
AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
PersistentVolumeSource: core.PersistentVolumeSource{
CSI: &core.CSIPersistentVolumeSource{
Driver: "test-driver",
VolumeHandle: "test-123",
},
},
StorageClassName: "test-storage-class",
VolumeAttributesClassName: vacName,
})
}
func testVolumeWithNodeAffinity(affinity *core.VolumeNodeAffinity) *core.PersistentVolume {
return testVolume("test-affinity-volume", "",
core.PersistentVolumeSpec{
@@ -1341,6 +1482,115 @@ func TestValidateVolumeNodeAffinityUpdate(t *testing.T) {
}
}
func TestValidatePeristentVolumeAttributesClassUpdate(t *testing.T) {
scenarios := map[string]struct {
isExpectedFailure bool
enableVolumeAttributesClass bool
oldPV *core.PersistentVolume
newPV *core.PersistentVolume
}{
"nil-nothing-changed": {
isExpectedFailure: false,
enableVolumeAttributesClass: true,
oldPV: testVolumeWithVolumeAttributesClass(nil),
newPV: testVolumeWithVolumeAttributesClass(nil),
},
"vac-nothing-changed": {
isExpectedFailure: false,
enableVolumeAttributesClass: true,
oldPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")),
newPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")),
},
"vac-changed": {
isExpectedFailure: false,
enableVolumeAttributesClass: true,
oldPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")),
newPV: testVolumeWithVolumeAttributesClass(ptr.To("bar")),
},
"nil-to-string": {
isExpectedFailure: false,
enableVolumeAttributesClass: true,
oldPV: testVolumeWithVolumeAttributesClass(nil),
newPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")),
},
"nil-to-empty-string": {
isExpectedFailure: true,
enableVolumeAttributesClass: true,
oldPV: testVolumeWithVolumeAttributesClass(nil),
newPV: testVolumeWithVolumeAttributesClass(ptr.To("")),
},
"string-to-nil": {
isExpectedFailure: true,
enableVolumeAttributesClass: true,
oldPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")),
newPV: testVolumeWithVolumeAttributesClass(nil),
},
"string-to-empty-string": {
isExpectedFailure: true,
enableVolumeAttributesClass: true,
oldPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")),
newPV: testVolumeWithVolumeAttributesClass(ptr.To("")),
},
"vac-nothing-changed-when-feature-gate-is-off": {
isExpectedFailure: false,
enableVolumeAttributesClass: false,
oldPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")),
newPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")),
},
"vac-changed-when-feature-gate-is-off": {
isExpectedFailure: true,
enableVolumeAttributesClass: false,
oldPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")),
newPV: testVolumeWithVolumeAttributesClass(ptr.To("bar")),
},
"nil-to-string-when-feature-gate-is-off": {
isExpectedFailure: true,
enableVolumeAttributesClass: false,
oldPV: testVolumeWithVolumeAttributesClass(nil),
newPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")),
},
"nil-to-empty-string-when-feature-gate-is-off": {
isExpectedFailure: true,
enableVolumeAttributesClass: false,
oldPV: testVolumeWithVolumeAttributesClass(nil),
newPV: testVolumeWithVolumeAttributesClass(ptr.To("")),
},
"string-to-nil-when-feature-gate-is-off": {
isExpectedFailure: true,
enableVolumeAttributesClass: false,
oldPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")),
newPV: testVolumeWithVolumeAttributesClass(nil),
},
"string-to-empty-string-when-feature-gate-is-off": {
isExpectedFailure: true,
enableVolumeAttributesClass: false,
oldPV: testVolumeWithVolumeAttributesClass(ptr.To("foo")),
newPV: testVolumeWithVolumeAttributesClass(ptr.To("")),
},
}
for name, scenario := range scenarios {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, scenario.enableVolumeAttributesClass)()
originalNewPV := scenario.newPV.DeepCopy()
originalOldPV := scenario.oldPV.DeepCopy()
opts := ValidationOptionsForPersistentVolume(scenario.newPV, scenario.oldPV)
errs := ValidatePersistentVolumeUpdate(scenario.newPV, scenario.oldPV, opts)
if len(errs) == 0 && scenario.isExpectedFailure {
t.Errorf("Unexpected success for scenario: %s", name)
}
if len(errs) > 0 && !scenario.isExpectedFailure {
t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
}
if diff := cmp.Diff(originalNewPV, scenario.newPV); len(diff) > 0 {
t.Errorf("newPV was modified: %s", diff)
}
if diff := cmp.Diff(originalOldPV, scenario.oldPV); len(diff) > 0 {
t.Errorf("oldPV was modified: %s", diff)
}
}
}
func testVolumeClaim(name string, namespace string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim {
return &core.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
@@ -1516,8 +1766,9 @@ func testValidatePVC(t *testing.T, ephemeral bool) {
ten := int64(10)
scenarios := map[string]struct {
isExpectedFailure bool
claim *core.PersistentVolumeClaim
isExpectedFailure bool
enableVolumeAttributesClass bool
claim *core.PersistentVolumeClaim
}{
"good-claim": {
isExpectedFailure: false,
@@ -1894,10 +2145,34 @@ func testValidatePVC(t *testing.T, ephemeral bool) {
},
}),
},
"invalid-volume-attributes-class-name": {
isExpectedFailure: true,
enableVolumeAttributesClass: true,
claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
Selector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{{
Key: "key2",
Operator: "Exists",
}},
},
AccessModes: []core.PersistentVolumeAccessMode{
core.ReadWriteOnce,
core.ReadOnlyMany,
},
Resources: core.VolumeResourceRequirements{
Requests: core.ResourceList{
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
},
},
VolumeAttributesClassName: &invalidClassName,
}),
},
}
for name, scenario := range scenarios {
t.Run(name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, scenario.enableVolumeAttributesClass)()
var errs field.ErrorList
if ephemeral {
volumes := []core.Volume{{
@@ -2422,11 +2697,68 @@ func TestValidatePersistentVolumeClaimUpdate(t *testing.T) {
},
})
validClaimNilVolumeAttributesClass := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
AccessModes: []core.PersistentVolumeAccessMode{
core.ReadWriteOnce,
core.ReadOnlyMany,
},
Resources: core.VolumeResourceRequirements{
Requests: core.ResourceList{
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
},
},
}, core.PersistentVolumeClaimStatus{
Phase: core.ClaimBound,
})
validClaimEmptyVolumeAttributesClass := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
VolumeAttributesClassName: utilpointer.String(""),
AccessModes: []core.PersistentVolumeAccessMode{
core.ReadWriteOnce,
core.ReadOnlyMany,
},
Resources: core.VolumeResourceRequirements{
Requests: core.ResourceList{
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
},
},
}, core.PersistentVolumeClaimStatus{
Phase: core.ClaimBound,
})
validClaimVolumeAttributesClass1 := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
VolumeAttributesClassName: utilpointer.String("vac1"),
AccessModes: []core.PersistentVolumeAccessMode{
core.ReadWriteOnce,
core.ReadOnlyMany,
},
Resources: core.VolumeResourceRequirements{
Requests: core.ResourceList{
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
},
},
}, core.PersistentVolumeClaimStatus{
Phase: core.ClaimBound,
})
validClaimVolumeAttributesClass2 := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
VolumeAttributesClassName: utilpointer.String("vac2"),
AccessModes: []core.PersistentVolumeAccessMode{
core.ReadWriteOnce,
core.ReadOnlyMany,
},
Resources: core.VolumeResourceRequirements{
Requests: core.ResourceList{
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
},
},
}, core.PersistentVolumeClaimStatus{
Phase: core.ClaimBound,
})
scenarios := map[string]struct {
isExpectedFailure bool
oldClaim *core.PersistentVolumeClaim
newClaim *core.PersistentVolumeClaim
enableRecoverFromExpansion bool
isExpectedFailure bool
oldClaim *core.PersistentVolumeClaim
newClaim *core.PersistentVolumeClaim
enableRecoverFromExpansion bool
enableVolumeAttributesClass bool
}{
"valid-update-volumeName-only": {
isExpectedFailure: false,
@@ -2636,11 +2968,61 @@ func TestValidatePersistentVolumeClaimUpdate(t *testing.T) {
newClaim: invalidClaimDataSourceRefAPIGroup,
isExpectedFailure: false,
},
"valid-update-volume-attributes-class-from-nil": {
oldClaim: validClaimNilVolumeAttributesClass,
newClaim: validClaimVolumeAttributesClass1,
enableVolumeAttributesClass: true,
isExpectedFailure: false,
},
"valid-update-volume-attributes-class-from-empty": {
oldClaim: validClaimEmptyVolumeAttributesClass,
newClaim: validClaimVolumeAttributesClass1,
enableVolumeAttributesClass: true,
isExpectedFailure: false,
},
"valid-update-volume-attributes-class": {
oldClaim: validClaimVolumeAttributesClass1,
newClaim: validClaimVolumeAttributesClass2,
enableVolumeAttributesClass: true,
isExpectedFailure: false,
},
"invalid-update-volume-attributes-class": {
oldClaim: validClaimVolumeAttributesClass1,
newClaim: validClaimNilVolumeAttributesClass,
enableVolumeAttributesClass: true,
isExpectedFailure: true,
},
"invalid-update-volume-attributes-class-to-nil": {
oldClaim: validClaimVolumeAttributesClass1,
newClaim: validClaimNilVolumeAttributesClass,
enableVolumeAttributesClass: true,
isExpectedFailure: true,
},
"invalid-update-volume-attributes-class-to-empty": {
oldClaim: validClaimVolumeAttributesClass1,
newClaim: validClaimEmptyVolumeAttributesClass,
enableVolumeAttributesClass: true,
isExpectedFailure: true,
},
"invalid-update-volume-attributes-class-to-nil-without-featuregate-enabled": {
oldClaim: validClaimVolumeAttributesClass1,
newClaim: validClaimNilVolumeAttributesClass,
enableVolumeAttributesClass: false,
isExpectedFailure: true,
},
"invalid-update-volume-attributes-class-without-featuregate-enabled": {
oldClaim: validClaimVolumeAttributesClass1,
newClaim: validClaimVolumeAttributesClass2,
enableVolumeAttributesClass: false,
isExpectedFailure: true,
},
}
for name, scenario := range scenarios {
t.Run(name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.RecoverVolumeExpansionFailure, scenario.enableRecoverFromExpansion)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, scenario.enableVolumeAttributesClass)()
scenario.oldClaim.ResourceVersion = "1"
scenario.newClaim.ResourceVersion = "1"
opts := ValidationOptionsForPersistentVolumeClaim(scenario.newClaim, scenario.oldClaim)
@@ -2659,13 +3041,15 @@ func TestValidationOptionsForPersistentVolumeClaim(t *testing.T) {
invaildAPIGroup := "^invalid"
tests := map[string]struct {
oldPvc *core.PersistentVolumeClaim
expectValidationOpts PersistentVolumeClaimSpecValidationOptions
oldPvc *core.PersistentVolumeClaim
enableVolumeAttributesClass bool
expectValidationOpts PersistentVolumeClaimSpecValidationOptions
}{
"nil pv": {
oldPvc: nil,
expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
EnableRecoverFromExpansionFailure: false,
EnableVolumeAttributesClass: false,
},
},
"invaild apiGroup in dataSource allowed because the old pvc is used": {
@@ -2680,10 +3064,28 @@ func TestValidationOptionsForPersistentVolumeClaim(t *testing.T) {
AllowInvalidAPIGroupInDataSourceOrRef: true,
},
},
"volume attributes class allowed because feature enable": {
oldPvc: pvcWithVolumeAttributesClassName(utilpointer.String("foo")),
enableVolumeAttributesClass: true,
expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
EnableRecoverFromExpansionFailure: false,
EnableVolumeAttributesClass: true,
},
},
"volume attributes class validated because used and feature disabled": {
oldPvc: pvcWithVolumeAttributesClassName(utilpointer.String("foo")),
enableVolumeAttributesClass: false,
expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
EnableRecoverFromExpansionFailure: false,
EnableVolumeAttributesClass: true,
},
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, tc.enableVolumeAttributesClass)()
opts := ValidationOptionsForPersistentVolumeClaim(nil, tc.oldPvc)
if opts != tc.expectValidationOpts {
t.Errorf("Expected opts: %+v, received: %+v", tc.expectValidationOpts, opts)
@@ -2694,17 +3096,27 @@ func TestValidationOptionsForPersistentVolumeClaim(t *testing.T) {
func TestValidationOptionsForPersistentVolumeClaimTemplate(t *testing.T) {
tests := map[string]struct {
oldPvcTemplate *core.PersistentVolumeClaimTemplate
expectValidationOpts PersistentVolumeClaimSpecValidationOptions
oldPvcTemplate *core.PersistentVolumeClaimTemplate
enableVolumeAttributesClass bool
expectValidationOpts PersistentVolumeClaimSpecValidationOptions
}{
"nil pv": {
oldPvcTemplate: nil,
expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{},
},
"volume attributes class allowed because feature enable": {
oldPvcTemplate: pvcTemplateWithVolumeAttributesClassName(utilpointer.String("foo")),
enableVolumeAttributesClass: true,
expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
EnableVolumeAttributesClass: true,
},
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, tc.enableVolumeAttributesClass)()
opts := ValidationOptionsForPersistentVolumeClaimTemplate(nil, tc.oldPvcTemplate)
if opts != tc.expectValidationOpts {
t.Errorf("Expected opts: %+v, received: %+v", opts, tc.expectValidationOpts)
@@ -22178,6 +22590,71 @@ func TestCrossNamespaceSource(t *testing.T) {
}
}
func pvcSpecWithVolumeAttributesClassName(vacName *string) *core.PersistentVolumeClaimSpec {
scName := "csi-plugin"
spec := core.PersistentVolumeClaimSpec{
AccessModes: []core.PersistentVolumeAccessMode{
core.ReadOnlyMany,
},
Resources: core.VolumeResourceRequirements{
Requests: core.ResourceList{
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
},
},
StorageClassName: &scName,
VolumeAttributesClassName: vacName,
}
return &spec
}
func TestVolumeAttributesClass(t *testing.T) {
testCases := []struct {
testName string
expectedFail bool
enableVolumeAttributesClass bool
claimSpec *core.PersistentVolumeClaimSpec
}{
{
testName: "Feature gate enabled and valid no volumeAttributesClassName specified",
expectedFail: false,
enableVolumeAttributesClass: true,
claimSpec: pvcSpecWithVolumeAttributesClassName(nil),
},
{
testName: "Feature gate enabled and an empty volumeAttributesClassName specified",
expectedFail: false,
enableVolumeAttributesClass: true,
claimSpec: pvcSpecWithVolumeAttributesClassName(utilpointer.String("")),
},
{
testName: "Feature gate enabled and valid volumeAttributesClassName specified",
expectedFail: false,
enableVolumeAttributesClass: true,
claimSpec: pvcSpecWithVolumeAttributesClassName(utilpointer.String("foo")),
},
{
testName: "Feature gate enabled and invalid volumeAttributesClassName specified",
expectedFail: true,
enableVolumeAttributesClass: true,
claimSpec: pvcSpecWithVolumeAttributesClassName(utilpointer.String("-invalid-")),
},
}
for _, tc := range testCases {
opts := PersistentVolumeClaimSpecValidationOptions{
EnableVolumeAttributesClass: tc.enableVolumeAttributesClass,
}
if tc.expectedFail {
if errs := ValidatePersistentVolumeClaimSpec(tc.claimSpec, field.NewPath("spec"), opts); len(errs) == 0 {
t.Errorf("%s: expected failure: %v", tc.testName, errs)
}
} else {
if errs := ValidatePersistentVolumeClaimSpec(tc.claimSpec, field.NewPath("spec"), opts); len(errs) != 0 {
t.Errorf("%s: expected success: %v", tc.testName, errs)
}
}
}
}
func TestValidateTopologySpreadConstraints(t *testing.T) {
fieldPath := field.NewPath("field")
subFldPath0 := fieldPath.Index(0)