Merge pull request #121104 from carlory/kep-3751-api-changes
[KEP-3571] introduce the VolumeAttributesClass API
This commit is contained in:
@@ -335,6 +335,16 @@ type PersistentVolumeSpec struct {
|
||||
// This field influences the scheduling of pods that use this volume.
|
||||
// +optional
|
||||
NodeAffinity *VolumeNodeAffinity
|
||||
// Name of VolumeAttributesClass to which this persistent volume belongs. Empty value
|
||||
// is not allowed. When this field is not set, it indicates that this volume does not belong to any
|
||||
// VolumeAttributesClass. This field is mutable and can be changed by the CSI driver
|
||||
// after a volume has been updated successfully to a new class.
|
||||
// For an unbound PersistentVolume, the volumeAttributesClassName will be matched with unbound
|
||||
// PersistentVolumeClaims during the binding process.
|
||||
// This is an alpha field and requires enabling VolumeAttributesClass feature.
|
||||
// +featureGate=VolumeAttributesClass
|
||||
// +optional
|
||||
VolumeAttributesClassName *string
|
||||
}
|
||||
|
||||
// VolumeNodeAffinity defines constraints that limit what nodes this volume can be accessed from.
|
||||
@@ -488,6 +498,21 @@ type PersistentVolumeClaimSpec struct {
|
||||
// (Alpha) Using the namespace field of dataSourceRef requires the CrossNamespaceVolumeDataSource feature gate to be enabled.
|
||||
// +optional
|
||||
DataSourceRef *TypedObjectReference
|
||||
// volumeAttributesClassName may be used to set the VolumeAttributesClass used by this claim.
|
||||
// If specified, the CSI driver will create or update the volume with the attributes defined
|
||||
// in the corresponding VolumeAttributesClass. This has a different purpose than storageClassName,
|
||||
// it can be changed after the claim is created. An empty string value means that no VolumeAttributesClass
|
||||
// will be applied to the claim but it's not allowed to reset this field to empty string once it is set.
|
||||
// If unspecified and the PersistentVolumeClaim is unbound, the default VolumeAttributesClass
|
||||
// will be set by the persistentvolume controller if it exists.
|
||||
// If the resource referred to by volumeAttributesClass does not exist, this PersistentVolumeClaim will be
|
||||
// set to a Pending state, as reflected by the modifyVolumeStatus field, until such as a resource
|
||||
// exists.
|
||||
// More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#volumeattributesclass
|
||||
// (Alpha) Using this field requires the VolumeAttributesClass feature gate to be enabled.
|
||||
// +featureGate=VolumeAttributesClass
|
||||
// +optional
|
||||
VolumeAttributesClassName *string
|
||||
}
|
||||
|
||||
type TypedObjectReference struct {
|
||||
@@ -518,6 +543,11 @@ const (
|
||||
PersistentVolumeClaimResizing PersistentVolumeClaimConditionType = "Resizing"
|
||||
// PersistentVolumeClaimFileSystemResizePending - controller resize is finished and a file system resize is pending on node
|
||||
PersistentVolumeClaimFileSystemResizePending PersistentVolumeClaimConditionType = "FileSystemResizePending"
|
||||
|
||||
// Applying the target VolumeAttributesClass encountered an error
|
||||
PersistentVolumeClaimVolumeModifyVolumeError PersistentVolumeClaimConditionType = "ModifyVolumeError"
|
||||
// Volume is being modified
|
||||
PersistentVolumeClaimVolumeModifyingVolume PersistentVolumeClaimConditionType = "ModifyingVolume"
|
||||
)
|
||||
|
||||
// +enum
|
||||
@@ -544,6 +574,38 @@ const (
|
||||
PersistentVolumeClaimNodeResizeFailed ClaimResourceStatus = "NodeResizeFailed"
|
||||
)
|
||||
|
||||
// +enum
|
||||
// New statuses can be added in the future. Consumers should check for unknown statuses and fail appropriately
|
||||
type PersistentVolumeClaimModifyVolumeStatus string
|
||||
|
||||
const (
|
||||
// Pending indicates that the PersistentVolumeClaim cannot be modified due to unmet requirements, such as
|
||||
// the specified VolumeAttributesClass not existing
|
||||
PersistentVolumeClaimModifyVolumePending PersistentVolumeClaimModifyVolumeStatus = "Pending"
|
||||
// InProgress indicates that the volume is being modified
|
||||
PersistentVolumeClaimModifyVolumeInProgress PersistentVolumeClaimModifyVolumeStatus = "InProgress"
|
||||
// Infeasible indicates that the request has been rejected as invalid by the CSI driver. To
|
||||
// resolve the error, a valid VolumeAttributesClass needs to be specified
|
||||
PersistentVolumeClaimModifyVolumeInfeasible PersistentVolumeClaimModifyVolumeStatus = "Infeasible"
|
||||
)
|
||||
|
||||
// ModifyVolumeStatus represents the status object of ControllerModifyVolume operation
|
||||
type ModifyVolumeStatus struct {
|
||||
// targetVolumeAttributesClassName is the name of the VolumeAttributesClass the PVC currently being reconciled
|
||||
TargetVolumeAttributesClassName string
|
||||
// status is the status of the ControllerModifyVolume operation. It can be in any of following states:
|
||||
// - Pending
|
||||
// Pending indicates that the PersistentVolumeClaim cannot be modified due to unmet requirements, such as
|
||||
// the specified VolumeAttributesClass not existing.
|
||||
// - InProgress
|
||||
// InProgress indicates that the volume is being modified.
|
||||
// - Infeasible
|
||||
// Infeasible indicates that the request has been rejected as invalid by the CSI driver. To
|
||||
// resolve the error, a valid VolumeAttributesClass needs to be specified.
|
||||
// Note: New statuses can be added in the future. Consumers should check for unknown statuses and fail appropriately.
|
||||
Status PersistentVolumeClaimModifyVolumeStatus
|
||||
}
|
||||
|
||||
// PersistentVolumeClaimCondition represents the current condition of PV claim
|
||||
type PersistentVolumeClaimCondition struct {
|
||||
Type PersistentVolumeClaimConditionType
|
||||
@@ -635,6 +697,18 @@ type PersistentVolumeClaimStatus struct {
|
||||
// +mapType=granular
|
||||
// +optional
|
||||
AllocatedResourceStatuses map[ResourceName]ClaimResourceStatus
|
||||
// currentVolumeAttributesClassName is the current name of the VolumeAttributesClass the PVC is using.
|
||||
// When unset, there is no VolumeAttributeClass applied to this PersistentVolumeClaim
|
||||
// This is an alpha field and requires enabling VolumeAttributesClass feature.
|
||||
// +featureGate=VolumeAttributesClass
|
||||
// +optional
|
||||
CurrentVolumeAttributesClassName *string
|
||||
// ModifyVolumeStatus represents the status object of ControllerModifyVolume operation.
|
||||
// When this is unset, there is no ModifyVolume operation being attempted.
|
||||
// This is an alpha field and requires enabling VolumeAttributesClass feature.
|
||||
// +featureGate=VolumeAttributesClass
|
||||
// +optional
|
||||
ModifyVolumeStatus *ModifyVolumeStatus
|
||||
}
|
||||
|
||||
// PersistentVolumeAccessMode defines various access modes for PV.
|
||||
|
40
pkg/apis/core/v1/zz_generated.conversion.go
generated
40
pkg/apis/core/v1/zz_generated.conversion.go
generated
@@ -882,6 +882,16 @@ func RegisterConversions(s *runtime.Scheme) error {
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*v1.ModifyVolumeStatus)(nil), (*core.ModifyVolumeStatus)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_ModifyVolumeStatus_To_core_ModifyVolumeStatus(a.(*v1.ModifyVolumeStatus), b.(*core.ModifyVolumeStatus), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*core.ModifyVolumeStatus)(nil), (*v1.ModifyVolumeStatus)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_core_ModifyVolumeStatus_To_v1_ModifyVolumeStatus(a.(*core.ModifyVolumeStatus), b.(*v1.ModifyVolumeStatus), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*v1.NFSVolumeSource)(nil), (*core.NFSVolumeSource)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_NFSVolumeSource_To_core_NFSVolumeSource(a.(*v1.NFSVolumeSource), b.(*core.NFSVolumeSource), scope)
|
||||
}); err != nil {
|
||||
@@ -4575,6 +4585,28 @@ func Convert_core_LocalVolumeSource_To_v1_LocalVolumeSource(in *core.LocalVolume
|
||||
return autoConvert_core_LocalVolumeSource_To_v1_LocalVolumeSource(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_ModifyVolumeStatus_To_core_ModifyVolumeStatus(in *v1.ModifyVolumeStatus, out *core.ModifyVolumeStatus, s conversion.Scope) error {
|
||||
out.TargetVolumeAttributesClassName = in.TargetVolumeAttributesClassName
|
||||
out.Status = core.PersistentVolumeClaimModifyVolumeStatus(in.Status)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_ModifyVolumeStatus_To_core_ModifyVolumeStatus is an autogenerated conversion function.
|
||||
func Convert_v1_ModifyVolumeStatus_To_core_ModifyVolumeStatus(in *v1.ModifyVolumeStatus, out *core.ModifyVolumeStatus, s conversion.Scope) error {
|
||||
return autoConvert_v1_ModifyVolumeStatus_To_core_ModifyVolumeStatus(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_core_ModifyVolumeStatus_To_v1_ModifyVolumeStatus(in *core.ModifyVolumeStatus, out *v1.ModifyVolumeStatus, s conversion.Scope) error {
|
||||
out.TargetVolumeAttributesClassName = in.TargetVolumeAttributesClassName
|
||||
out.Status = v1.PersistentVolumeClaimModifyVolumeStatus(in.Status)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_core_ModifyVolumeStatus_To_v1_ModifyVolumeStatus is an autogenerated conversion function.
|
||||
func Convert_core_ModifyVolumeStatus_To_v1_ModifyVolumeStatus(in *core.ModifyVolumeStatus, out *v1.ModifyVolumeStatus, s conversion.Scope) error {
|
||||
return autoConvert_core_ModifyVolumeStatus_To_v1_ModifyVolumeStatus(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_NFSVolumeSource_To_core_NFSVolumeSource(in *v1.NFSVolumeSource, out *core.NFSVolumeSource, s conversion.Scope) error {
|
||||
out.Server = in.Server
|
||||
out.Path = in.Path
|
||||
@@ -5353,6 +5385,7 @@ func autoConvert_v1_PersistentVolumeClaimSpec_To_core_PersistentVolumeClaimSpec(
|
||||
out.VolumeMode = (*core.PersistentVolumeMode)(unsafe.Pointer(in.VolumeMode))
|
||||
out.DataSource = (*core.TypedLocalObjectReference)(unsafe.Pointer(in.DataSource))
|
||||
out.DataSourceRef = (*core.TypedObjectReference)(unsafe.Pointer(in.DataSourceRef))
|
||||
out.VolumeAttributesClassName = (*string)(unsafe.Pointer(in.VolumeAttributesClassName))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5372,6 +5405,7 @@ func autoConvert_core_PersistentVolumeClaimSpec_To_v1_PersistentVolumeClaimSpec(
|
||||
out.VolumeMode = (*v1.PersistentVolumeMode)(unsafe.Pointer(in.VolumeMode))
|
||||
out.DataSource = (*v1.TypedLocalObjectReference)(unsafe.Pointer(in.DataSource))
|
||||
out.DataSourceRef = (*v1.TypedObjectReference)(unsafe.Pointer(in.DataSourceRef))
|
||||
out.VolumeAttributesClassName = (*string)(unsafe.Pointer(in.VolumeAttributesClassName))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5387,6 +5421,8 @@ func autoConvert_v1_PersistentVolumeClaimStatus_To_core_PersistentVolumeClaimSta
|
||||
out.Conditions = *(*[]core.PersistentVolumeClaimCondition)(unsafe.Pointer(&in.Conditions))
|
||||
out.AllocatedResources = *(*core.ResourceList)(unsafe.Pointer(&in.AllocatedResources))
|
||||
out.AllocatedResourceStatuses = *(*map[core.ResourceName]core.ClaimResourceStatus)(unsafe.Pointer(&in.AllocatedResourceStatuses))
|
||||
out.CurrentVolumeAttributesClassName = (*string)(unsafe.Pointer(in.CurrentVolumeAttributesClassName))
|
||||
out.ModifyVolumeStatus = (*core.ModifyVolumeStatus)(unsafe.Pointer(in.ModifyVolumeStatus))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5402,6 +5438,8 @@ func autoConvert_core_PersistentVolumeClaimStatus_To_v1_PersistentVolumeClaimSta
|
||||
out.Conditions = *(*[]v1.PersistentVolumeClaimCondition)(unsafe.Pointer(&in.Conditions))
|
||||
out.AllocatedResources = *(*v1.ResourceList)(unsafe.Pointer(&in.AllocatedResources))
|
||||
out.AllocatedResourceStatuses = *(*map[v1.ResourceName]v1.ClaimResourceStatus)(unsafe.Pointer(&in.AllocatedResourceStatuses))
|
||||
out.CurrentVolumeAttributesClassName = (*string)(unsafe.Pointer(in.CurrentVolumeAttributesClassName))
|
||||
out.ModifyVolumeStatus = (*v1.ModifyVolumeStatus)(unsafe.Pointer(in.ModifyVolumeStatus))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5574,6 +5612,7 @@ func autoConvert_v1_PersistentVolumeSpec_To_core_PersistentVolumeSpec(in *v1.Per
|
||||
out.MountOptions = *(*[]string)(unsafe.Pointer(&in.MountOptions))
|
||||
out.VolumeMode = (*core.PersistentVolumeMode)(unsafe.Pointer(in.VolumeMode))
|
||||
out.NodeAffinity = (*core.VolumeNodeAffinity)(unsafe.Pointer(in.NodeAffinity))
|
||||
out.VolumeAttributesClassName = (*string)(unsafe.Pointer(in.VolumeAttributesClassName))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5589,6 +5628,7 @@ func autoConvert_core_PersistentVolumeSpec_To_v1_PersistentVolumeSpec(in *core.P
|
||||
out.MountOptions = *(*[]string)(unsafe.Pointer(&in.MountOptions))
|
||||
out.VolumeMode = (*v1.PersistentVolumeMode)(unsafe.Pointer(in.VolumeMode))
|
||||
out.NodeAffinity = (*v1.VolumeNodeAffinity)(unsafe.Pointer(in.NodeAffinity))
|
||||
out.VolumeAttributesClassName = (*string)(unsafe.Pointer(in.VolumeAttributesClassName))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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)
|
||||
|
36
pkg/apis/core/zz_generated.deepcopy.go
generated
36
pkg/apis/core/zz_generated.deepcopy.go
generated
@@ -2320,6 +2320,22 @@ func (in *LocalVolumeSource) DeepCopy() *LocalVolumeSource {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ModifyVolumeStatus) DeepCopyInto(out *ModifyVolumeStatus) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ModifyVolumeStatus.
|
||||
func (in *ModifyVolumeStatus) DeepCopy() *ModifyVolumeStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ModifyVolumeStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NFSVolumeSource) DeepCopyInto(out *NFSVolumeSource) {
|
||||
*out = *in
|
||||
@@ -3068,6 +3084,11 @@ func (in *PersistentVolumeClaimSpec) DeepCopyInto(out *PersistentVolumeClaimSpec
|
||||
*out = new(TypedObjectReference)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.VolumeAttributesClassName != nil {
|
||||
in, out := &in.VolumeAttributesClassName, &out.VolumeAttributesClassName
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3117,6 +3138,16 @@ func (in *PersistentVolumeClaimStatus) DeepCopyInto(out *PersistentVolumeClaimSt
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
if in.CurrentVolumeAttributesClassName != nil {
|
||||
in, out := &in.CurrentVolumeAttributesClassName, &out.CurrentVolumeAttributesClassName
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.ModifyVolumeStatus != nil {
|
||||
in, out := &in.ModifyVolumeStatus, &out.ModifyVolumeStatus
|
||||
*out = new(ModifyVolumeStatus)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3359,6 +3390,11 @@ func (in *PersistentVolumeSpec) DeepCopyInto(out *PersistentVolumeSpec) {
|
||||
*out = new(VolumeNodeAffinity)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.VolumeAttributesClassName != nil {
|
||||
in, out := &in.VolumeAttributesClassName, &out.VolumeAttributesClassName
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user