Merge pull request #134339 from huww98/mutable-pv-affinity

KEP-5381: mutable pv nodeAffinity
This commit is contained in:
Kubernetes Prow Robot 2025-11-06 01:33:11 -08:00 committed by GitHub
commit 326ce8b16d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 41 additions and 9 deletions

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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

View file

@ -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"))...)
}

View file

@ -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)

View file

@ -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: {},

View file

@ -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()),
},
},

View file

@ -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;

View file

@ -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

View file

@ -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.",
}

View file

@ -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

View file

@ -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