diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json index 7fcf5594401..0381a450215 100644 --- a/api/openapi-spec/swagger.json +++ b/api/openapi-spec/swagger.json @@ -9488,7 +9488,7 @@ }, "nodeAffinity": { "$ref": "#/definitions/io.k8s.api.core.v1.VolumeNodeAffinity", - "description": "nodeAffinity defines constraints that limit what nodes this volume can be accessed from. This field influences the scheduling of pods that use this volume." + "description": "nodeAffinity defines constraints that limit what nodes this volume can be accessed from. This field influences the scheduling of pods that use this volume. This field is mutable if MutablePVNodeAffinity feature gate is enabled." }, "persistentVolumeReclaimPolicy": { "description": "persistentVolumeReclaimPolicy defines what happens to a persistent volume when released from its claim. Valid options are Retain (default for manually created PersistentVolumes), Delete (default for dynamically provisioned PersistentVolumes), and Recycle (deprecated). Recycle must be supported by the volume plugin underlying this PersistentVolume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#reclaiming", diff --git a/api/openapi-spec/v3/api__v1_openapi.json b/api/openapi-spec/v3/api__v1_openapi.json index 1b242705467..6b2aacaa352 100644 --- a/api/openapi-spec/v3/api__v1_openapi.json +++ b/api/openapi-spec/v3/api__v1_openapi.json @@ -5018,7 +5018,7 @@ "$ref": "#/components/schemas/io.k8s.api.core.v1.VolumeNodeAffinity" } ], - "description": "nodeAffinity defines constraints that limit what nodes this volume can be accessed from. This field influences the scheduling of pods that use this volume." + "description": "nodeAffinity defines constraints that limit what nodes this volume can be accessed from. This field influences the scheduling of pods that use this volume. This field is mutable if MutablePVNodeAffinity feature gate is enabled." }, "persistentVolumeReclaimPolicy": { "description": "persistentVolumeReclaimPolicy defines what happens to a persistent volume when released from its claim. Valid options are Retain (default for manually created PersistentVolumes), Delete (default for dynamically provisioned PersistentVolumes), and Recycle (deprecated). Recycle must be supported by the volume plugin underlying this PersistentVolume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#reclaiming", diff --git a/api/openapi-spec/v3/apis__storage.k8s.io__v1_openapi.json b/api/openapi-spec/v3/apis__storage.k8s.io__v1_openapi.json index 1f343a7ee12..6212f1d5363 100644 --- a/api/openapi-spec/v3/apis__storage.k8s.io__v1_openapi.json +++ b/api/openapi-spec/v3/apis__storage.k8s.io__v1_openapi.json @@ -800,7 +800,7 @@ "$ref": "#/components/schemas/io.k8s.api.core.v1.VolumeNodeAffinity" } ], - "description": "nodeAffinity defines constraints that limit what nodes this volume can be accessed from. This field influences the scheduling of pods that use this volume." + "description": "nodeAffinity defines constraints that limit what nodes this volume can be accessed from. This field influences the scheduling of pods that use this volume. This field is mutable if MutablePVNodeAffinity feature gate is enabled." }, "persistentVolumeReclaimPolicy": { "description": "persistentVolumeReclaimPolicy defines what happens to a persistent volume when released from its claim. Valid options are Retain (default for manually created PersistentVolumes), Delete (default for dynamically provisioned PersistentVolumes), and Recycle (deprecated). Recycle must be supported by the volume plugin underlying this PersistentVolume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#reclaiming", diff --git a/pkg/apis/core/types.go b/pkg/apis/core/types.go index f3530bd3fce..b145c73711a 100644 --- a/pkg/apis/core/types.go +++ b/pkg/apis/core/types.go @@ -398,6 +398,7 @@ type PersistentVolumeSpec struct { VolumeMode *PersistentVolumeMode // NodeAffinity defines constraints that limit what nodes this volume can be accessed from. // This field influences the scheduling of pods that use this volume. + // This field is mutable if MutablePVNodeAffinity feature gate is enabled. // +optional NodeAffinity *VolumeNodeAffinity // Name of VolumeAttributesClass to which this persistent volume belongs. Empty value diff --git a/pkg/apis/core/validation/validation.go b/pkg/apis/core/validation/validation.go index ea92de242cb..08eb1b2dbed 100644 --- a/pkg/apis/core/validation/validation.go +++ b/pkg/apis/core/validation/validation.go @@ -2272,7 +2272,8 @@ func ValidatePersistentVolumeUpdate(newPv, oldPv *core.PersistentVolume, opts Pe allErrs = append(allErrs, ValidateImmutableField(newPv.Spec.VolumeMode, oldPv.Spec.VolumeMode, field.NewPath("volumeMode"))...) // Allow setting NodeAffinity if oldPv NodeAffinity was not set - if oldPv.Spec.NodeAffinity != nil { + if !utilfeature.DefaultFeatureGate.Enabled(features.MutablePVNodeAffinity) && + oldPv.Spec.NodeAffinity != nil { allErrs = append(allErrs, validatePvNodeAffinity(newPv.Spec.NodeAffinity, oldPv.Spec.NodeAffinity, field.NewPath("nodeAffinity"))...) } diff --git a/pkg/apis/core/validation/validation_test.go b/pkg/apis/core/validation/validation_test.go index 63b20774a22..0ffad0c3b1c 100644 --- a/pkg/apis/core/validation/validation_test.go +++ b/pkg/apis/core/validation/validation_test.go @@ -1241,9 +1241,10 @@ func multipleVolumeNodeAffinity(terms [][]topologyPair) *core.VolumeNodeAffinity func TestValidateVolumeNodeAffinityUpdate(t *testing.T) { scenarios := map[string]struct { - isExpectedFailure bool - oldPV *core.PersistentVolume - newPV *core.PersistentVolume + mutablePVNodeAffinity bool + isExpectedFailure bool + oldPV *core.PersistentVolume + newPV *core.PersistentVolume }{ "nil-nothing-changed": { isExpectedFailure: false, @@ -1508,9 +1509,16 @@ func TestValidateVolumeNodeAffinityUpdate(t *testing.T) { oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceType, "-1")), newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceTypeStable, "-1")), }, + "MutablePVNodeAffinity": { + mutablePVNodeAffinity: true, + isExpectedFailure: false, + oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")), + newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "baz")), + }, } for name, scenario := range scenarios { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MutablePVNodeAffinity, scenario.mutablePVNodeAffinity) originalNewPV := scenario.newPV.DeepCopy() originalOldPV := scenario.oldPV.DeepCopy() opts := ValidationOptionsForPersistentVolume(scenario.newPV, scenario.oldPV) diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index fc285d90885..6756d93fe88 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -596,6 +596,13 @@ const ( // update the number of volumes that can be allocated on a node MutableCSINodeAllocatableCount featuregate.Feature = "MutableCSINodeAllocatableCount" + // owner: huww98 + // kep: https://kep.k8s.io/5381 + // + // Makes PersistentVolume.Spec.NodeAffinity mutable, allowing CSI drivers to + // update the topology info when the data is migrated + MutablePVNodeAffinity featuregate.Feature = "MutablePVNodeAffinity" + // owner: @kannon92 // kep: https://kep.k8s.io/5440 // @@ -1480,6 +1487,10 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.35"), Default: true, PreRelease: featuregate.Beta}, }, + MutablePVNodeAffinity: { + {Version: version.MustParse("1.35"), Default: false, PreRelease: featuregate.Alpha}, + }, + MutablePodResourcesForSuspendedJobs: { {Version: version.MustParse("1.35"), Default: false, PreRelease: featuregate.Alpha}, }, @@ -2244,6 +2255,8 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature MutableCSINodeAllocatableCount: {}, + MutablePVNodeAffinity: {}, + MutablePodResourcesForSuspendedJobs: {}, MutableSchedulingDirectivesForSuspendedJobs: {}, diff --git a/pkg/generated/openapi/zz_generated.openapi.go b/pkg/generated/openapi/zz_generated.openapi.go index dd24b6f815c..2482e7aa717 100644 --- a/pkg/generated/openapi/zz_generated.openapi.go +++ b/pkg/generated/openapi/zz_generated.openapi.go @@ -28414,7 +28414,7 @@ func schema_k8sio_api_core_v1_PersistentVolumeSpec(ref common.ReferenceCallback) }, "nodeAffinity": { SchemaProps: spec.SchemaProps{ - Description: "nodeAffinity defines constraints that limit what nodes this volume can be accessed from. This field influences the scheduling of pods that use this volume.", + Description: "nodeAffinity defines constraints that limit what nodes this volume can be accessed from. This field influences the scheduling of pods that use this volume. This field is mutable if MutablePVNodeAffinity feature gate is enabled.", Ref: ref(corev1.VolumeNodeAffinity{}.OpenAPIModelName()), }, }, diff --git a/staging/src/k8s.io/api/core/v1/generated.proto b/staging/src/k8s.io/api/core/v1/generated.proto index 6e53dc96e05..5814af470bb 100644 --- a/staging/src/k8s.io/api/core/v1/generated.proto +++ b/staging/src/k8s.io/api/core/v1/generated.proto @@ -3604,6 +3604,7 @@ message PersistentVolumeSpec { // nodeAffinity defines constraints that limit what nodes this volume can be accessed from. // This field influences the scheduling of pods that use this volume. + // This field is mutable if MutablePVNodeAffinity feature gate is enabled. // +optional optional VolumeNodeAffinity nodeAffinity = 9; diff --git a/staging/src/k8s.io/api/core/v1/types.go b/staging/src/k8s.io/api/core/v1/types.go index f265002a487..38114decde1 100644 --- a/staging/src/k8s.io/api/core/v1/types.go +++ b/staging/src/k8s.io/api/core/v1/types.go @@ -427,6 +427,7 @@ type PersistentVolumeSpec struct { VolumeMode *PersistentVolumeMode `json:"volumeMode,omitempty" protobuf:"bytes,8,opt,name=volumeMode,casttype=PersistentVolumeMode"` // nodeAffinity defines constraints that limit what nodes this volume can be accessed from. // This field influences the scheduling of pods that use this volume. + // This field is mutable if MutablePVNodeAffinity feature gate is enabled. // +optional NodeAffinity *VolumeNodeAffinity `json:"nodeAffinity,omitempty" protobuf:"bytes,9,opt,name=nodeAffinity"` // Name of VolumeAttributesClass to which this persistent volume belongs. Empty value diff --git a/staging/src/k8s.io/api/core/v1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/core/v1/types_swagger_doc_generated.go index 837394d3c32..d21e4cfec8f 100644 --- a/staging/src/k8s.io/api/core/v1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/core/v1/types_swagger_doc_generated.go @@ -1585,7 +1585,7 @@ var map_PersistentVolumeSpec = map[string]string{ "storageClassName": "storageClassName is the name of StorageClass to which this persistent volume belongs. Empty value means that this volume does not belong to any StorageClass.", "mountOptions": "mountOptions is the list of mount options, e.g. [\"ro\", \"soft\"]. Not validated - mount will simply fail if one is invalid. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#mount-options", "volumeMode": "volumeMode defines if a volume is intended to be used with a formatted filesystem or to remain in raw block state. Value of Filesystem is implied when not included in spec.", - "nodeAffinity": "nodeAffinity defines constraints that limit what nodes this volume can be accessed from. This field influences the scheduling of pods that use this volume.", + "nodeAffinity": "nodeAffinity defines constraints that limit what nodes this volume can be accessed from. This field influences the scheduling of pods that use this volume. This field is mutable if MutablePVNodeAffinity feature gate is enabled.", "volumeAttributesClassName": "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.", } diff --git a/staging/src/k8s.io/client-go/applyconfigurations/core/v1/persistentvolumespec.go b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/persistentvolumespec.go index 7dc11740586..8c166102228 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/core/v1/persistentvolumespec.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/core/v1/persistentvolumespec.go @@ -58,6 +58,7 @@ type PersistentVolumeSpecApplyConfiguration struct { VolumeMode *corev1.PersistentVolumeMode `json:"volumeMode,omitempty"` // nodeAffinity defines constraints that limit what nodes this volume can be accessed from. // This field influences the scheduling of pods that use this volume. + // This field is mutable if MutablePVNodeAffinity feature gate is enabled. NodeAffinity *VolumeNodeAffinityApplyConfiguration `json:"nodeAffinity,omitempty"` // 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 diff --git a/test/compatibility_lifecycle/reference/versioned_feature_list.yaml b/test/compatibility_lifecycle/reference/versioned_feature_list.yaml index d0f3da48214..e19926634cb 100644 --- a/test/compatibility_lifecycle/reference/versioned_feature_list.yaml +++ b/test/compatibility_lifecycle/reference/versioned_feature_list.yaml @@ -1071,6 +1071,12 @@ lockToDefault: false preRelease: Alpha version: "1.35" +- name: MutablePVNodeAffinity + versionedSpecs: + - default: false + lockToDefault: false + preRelease: Alpha + version: "1.35" - name: MutableSchedulingDirectivesForSuspendedJobs versionedSpecs: - default: false