Unverified Commit 6ba46963 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #59391 from msau42/topology-beta

Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. Move volume scheduling and local storage to beta **What this PR does / why we need it**: * Move the feature gates and APIs for volume scheduling and local storage to beta * Update tests to use the beta fields @kubernetes/sig-storage-pr-reviews **Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*: Fixes #59390 **Special notes for your reviewer**: **Release note**: ```release-note ACTION REQUIRED: VolumeScheduling and LocalPersistentVolume features are beta and enabled by default. The PersistentVolume NodeAffinity alpha annotation is deprecated and will be removed in a future release. ```
parents 6235a609 a6a22562
...@@ -78602,6 +78602,10 @@ ...@@ -78602,6 +78602,10 @@
"description": "NFS represents an NFS mount on the host. Provisioned by an admin. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs", "description": "NFS represents an NFS mount on the host. Provisioned by an admin. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs",
"$ref": "#/definitions/io.k8s.api.core.v1.NFSVolumeSource" "$ref": "#/definitions/io.k8s.api.core.v1.NFSVolumeSource"
}, },
"nodeAffinity": {
"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.",
"$ref": "#/definitions/io.k8s.api.core.v1.VolumeNodeAffinity"
},
"persistentVolumeReclaimPolicy": { "persistentVolumeReclaimPolicy": {
"description": "What happens to a persistent volume when released from its claim. Valid options are Retain (default) and Recycle. Recycling must be supported by the volume plugin underlying this persistent volume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#reclaiming", "description": "What happens to a persistent volume when released from its claim. Valid options are Retain (default) and Recycle. Recycling must be supported by the volume plugin underlying this persistent volume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#reclaiming",
"type": "string" "type": "string"
...@@ -80580,6 +80584,15 @@ ...@@ -80580,6 +80584,15 @@
} }
} }
}, },
"io.k8s.api.core.v1.VolumeNodeAffinity": {
"description": "VolumeNodeAffinity defines constraints that limit what nodes this volume can be accessed from.",
"properties": {
"required": {
"description": "Required specifies hard node constraints that must be met.",
"$ref": "#/definitions/io.k8s.api.core.v1.NodeSelector"
}
}
},
"io.k8s.api.core.v1.VolumeProjection": { "io.k8s.api.core.v1.VolumeProjection": {
"description": "Projection that may be projected along with other supported volume types", "description": "Projection that may be projected along with other supported volume types",
"properties": { "properties": {
...@@ -20682,6 +20682,10 @@ ...@@ -20682,6 +20682,10 @@
"volumeMode": { "volumeMode": {
"$ref": "v1.PersistentVolumeMode", "$ref": "v1.PersistentVolumeMode",
"description": "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. This is an alpha feature and may change in the future." "description": "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. This is an alpha feature and may change in the future."
},
"nodeAffinity": {
"$ref": "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."
} }
} }
}, },
...@@ -21331,6 +21335,73 @@ ...@@ -21331,6 +21335,73 @@
} }
} }
}, },
"v1.VolumeNodeAffinity": {
"id": "v1.VolumeNodeAffinity",
"description": "VolumeNodeAffinity defines constraints that limit what nodes this volume can be accessed from.",
"properties": {
"required": {
"$ref": "v1.NodeSelector",
"description": "Required specifies hard node constraints that must be met."
}
}
},
"v1.NodeSelector": {
"id": "v1.NodeSelector",
"description": "A node selector represents the union of the results of one or more label queries over a set of nodes; that is, it represents the OR of the selectors represented by the node selector terms.",
"required": [
"nodeSelectorTerms"
],
"properties": {
"nodeSelectorTerms": {
"type": "array",
"items": {
"$ref": "v1.NodeSelectorTerm"
},
"description": "Required. A list of node selector terms. The terms are ORed."
}
}
},
"v1.NodeSelectorTerm": {
"id": "v1.NodeSelectorTerm",
"description": "A null or empty node selector term matches no objects.",
"required": [
"matchExpressions"
],
"properties": {
"matchExpressions": {
"type": "array",
"items": {
"$ref": "v1.NodeSelectorRequirement"
},
"description": "Required. A list of node selector requirements. The requirements are ANDed."
}
}
},
"v1.NodeSelectorRequirement": {
"id": "v1.NodeSelectorRequirement",
"description": "A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values.",
"required": [
"key",
"operator"
],
"properties": {
"key": {
"type": "string",
"description": "The label key that the selector applies to."
},
"operator": {
"type": "string",
"description": "Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt."
},
"values": {
"type": "array",
"items": {
"type": "string"
},
"description": "An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch."
}
}
},
"v1.PersistentVolumeStatus": { "v1.PersistentVolumeStatus": {
"id": "v1.PersistentVolumeStatus", "id": "v1.PersistentVolumeStatus",
"description": "PersistentVolumeStatus is the current status of a persistent volume.", "description": "PersistentVolumeStatus is the current status of a persistent volume.",
...@@ -22869,63 +22940,6 @@ ...@@ -22869,63 +22940,6 @@
} }
} }
}, },
"v1.NodeSelector": {
"id": "v1.NodeSelector",
"description": "A node selector represents the union of the results of one or more label queries over a set of nodes; that is, it represents the OR of the selectors represented by the node selector terms.",
"required": [
"nodeSelectorTerms"
],
"properties": {
"nodeSelectorTerms": {
"type": "array",
"items": {
"$ref": "v1.NodeSelectorTerm"
},
"description": "Required. A list of node selector terms. The terms are ORed."
}
}
},
"v1.NodeSelectorTerm": {
"id": "v1.NodeSelectorTerm",
"description": "A null or empty node selector term matches no objects.",
"required": [
"matchExpressions"
],
"properties": {
"matchExpressions": {
"type": "array",
"items": {
"$ref": "v1.NodeSelectorRequirement"
},
"description": "Required. A list of node selector requirements. The requirements are ANDed."
}
}
},
"v1.NodeSelectorRequirement": {
"id": "v1.NodeSelectorRequirement",
"description": "A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values.",
"required": [
"key",
"operator"
],
"properties": {
"key": {
"type": "string",
"description": "The label key that the selector applies to."
},
"operator": {
"type": "string",
"description": "Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt."
},
"values": {
"type": "array",
"items": {
"type": "string"
},
"description": "An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch."
}
}
},
"v1.PreferredSchedulingTerm": { "v1.PreferredSchedulingTerm": {
"id": "v1.PreferredSchedulingTerm", "id": "v1.PreferredSchedulingTerm",
"description": "An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op).", "description": "An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op).",
......
...@@ -467,6 +467,16 @@ type PersistentVolumeSpec struct { ...@@ -467,6 +467,16 @@ type PersistentVolumeSpec struct {
// This is an alpha feature and may change in the future. // This is an alpha feature and may change in the future.
// +optional // +optional
VolumeMode *PersistentVolumeMode 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.
// +optional
NodeAffinity *VolumeNodeAffinity
}
// VolumeNodeAffinity defines constraints that limit what nodes this volume can be accessed from.
type VolumeNodeAffinity struct {
// Required specifies hard node constraints that must be met.
Required *NodeSelector
} }
// PersistentVolumeReclaimPolicy describes a policy for end-of-life maintenance of persistent volumes // PersistentVolumeReclaimPolicy describes a policy for end-of-life maintenance of persistent volumes
......
...@@ -406,6 +406,8 @@ func RegisterConversions(scheme *runtime.Scheme) error { ...@@ -406,6 +406,8 @@ func RegisterConversions(scheme *runtime.Scheme) error {
Convert_core_VolumeDevice_To_v1_VolumeDevice, Convert_core_VolumeDevice_To_v1_VolumeDevice,
Convert_v1_VolumeMount_To_core_VolumeMount, Convert_v1_VolumeMount_To_core_VolumeMount,
Convert_core_VolumeMount_To_v1_VolumeMount, Convert_core_VolumeMount_To_v1_VolumeMount,
Convert_v1_VolumeNodeAffinity_To_core_VolumeNodeAffinity,
Convert_core_VolumeNodeAffinity_To_v1_VolumeNodeAffinity,
Convert_v1_VolumeProjection_To_core_VolumeProjection, Convert_v1_VolumeProjection_To_core_VolumeProjection,
Convert_core_VolumeProjection_To_v1_VolumeProjection, Convert_core_VolumeProjection_To_v1_VolumeProjection,
Convert_v1_VolumeSource_To_core_VolumeSource, Convert_v1_VolumeSource_To_core_VolumeSource,
...@@ -3346,6 +3348,7 @@ func autoConvert_v1_PersistentVolumeSpec_To_core_PersistentVolumeSpec(in *v1.Per ...@@ -3346,6 +3348,7 @@ func autoConvert_v1_PersistentVolumeSpec_To_core_PersistentVolumeSpec(in *v1.Per
out.StorageClassName = in.StorageClassName out.StorageClassName = in.StorageClassName
out.MountOptions = *(*[]string)(unsafe.Pointer(&in.MountOptions)) out.MountOptions = *(*[]string)(unsafe.Pointer(&in.MountOptions))
out.VolumeMode = (*core.PersistentVolumeMode)(unsafe.Pointer(in.VolumeMode)) out.VolumeMode = (*core.PersistentVolumeMode)(unsafe.Pointer(in.VolumeMode))
out.NodeAffinity = (*core.VolumeNodeAffinity)(unsafe.Pointer(in.NodeAffinity))
return nil return nil
} }
...@@ -3365,6 +3368,7 @@ func autoConvert_core_PersistentVolumeSpec_To_v1_PersistentVolumeSpec(in *core.P ...@@ -3365,6 +3368,7 @@ func autoConvert_core_PersistentVolumeSpec_To_v1_PersistentVolumeSpec(in *core.P
out.StorageClassName = in.StorageClassName out.StorageClassName = in.StorageClassName
out.MountOptions = *(*[]string)(unsafe.Pointer(&in.MountOptions)) out.MountOptions = *(*[]string)(unsafe.Pointer(&in.MountOptions))
out.VolumeMode = (*v1.PersistentVolumeMode)(unsafe.Pointer(in.VolumeMode)) out.VolumeMode = (*v1.PersistentVolumeMode)(unsafe.Pointer(in.VolumeMode))
out.NodeAffinity = (*v1.VolumeNodeAffinity)(unsafe.Pointer(in.NodeAffinity))
return nil return nil
} }
...@@ -5508,6 +5512,26 @@ func Convert_core_VolumeMount_To_v1_VolumeMount(in *core.VolumeMount, out *v1.Vo ...@@ -5508,6 +5512,26 @@ func Convert_core_VolumeMount_To_v1_VolumeMount(in *core.VolumeMount, out *v1.Vo
return autoConvert_core_VolumeMount_To_v1_VolumeMount(in, out, s) return autoConvert_core_VolumeMount_To_v1_VolumeMount(in, out, s)
} }
func autoConvert_v1_VolumeNodeAffinity_To_core_VolumeNodeAffinity(in *v1.VolumeNodeAffinity, out *core.VolumeNodeAffinity, s conversion.Scope) error {
out.Required = (*core.NodeSelector)(unsafe.Pointer(in.Required))
return nil
}
// Convert_v1_VolumeNodeAffinity_To_core_VolumeNodeAffinity is an autogenerated conversion function.
func Convert_v1_VolumeNodeAffinity_To_core_VolumeNodeAffinity(in *v1.VolumeNodeAffinity, out *core.VolumeNodeAffinity, s conversion.Scope) error {
return autoConvert_v1_VolumeNodeAffinity_To_core_VolumeNodeAffinity(in, out, s)
}
func autoConvert_core_VolumeNodeAffinity_To_v1_VolumeNodeAffinity(in *core.VolumeNodeAffinity, out *v1.VolumeNodeAffinity, s conversion.Scope) error {
out.Required = (*v1.NodeSelector)(unsafe.Pointer(in.Required))
return nil
}
// Convert_core_VolumeNodeAffinity_To_v1_VolumeNodeAffinity is an autogenerated conversion function.
func Convert_core_VolumeNodeAffinity_To_v1_VolumeNodeAffinity(in *core.VolumeNodeAffinity, out *v1.VolumeNodeAffinity, s conversion.Scope) error {
return autoConvert_core_VolumeNodeAffinity_To_v1_VolumeNodeAffinity(in, out, s)
}
func autoConvert_v1_VolumeProjection_To_core_VolumeProjection(in *v1.VolumeProjection, out *core.VolumeProjection, s conversion.Scope) error { func autoConvert_v1_VolumeProjection_To_core_VolumeProjection(in *v1.VolumeProjection, out *core.VolumeProjection, s conversion.Scope) error {
out.Secret = (*core.SecretProjection)(unsafe.Pointer(in.Secret)) out.Secret = (*core.SecretProjection)(unsafe.Pointer(in.Secret))
out.DownwardAPI = (*core.DownwardAPIProjection)(unsafe.Pointer(in.DownwardAPI)) out.DownwardAPI = (*core.DownwardAPIProjection)(unsafe.Pointer(in.DownwardAPI))
......
...@@ -1383,6 +1383,9 @@ func validateLocalVolumeSource(ls *core.LocalVolumeSource, fldPath *field.Path) ...@@ -1383,6 +1383,9 @@ func validateLocalVolumeSource(ls *core.LocalVolumeSource, fldPath *field.Path)
return allErrs return allErrs
} }
if !path.IsAbs(ls.Path) {
allErrs = append(allErrs, field.Invalid(fldPath, ls.Path, "must be an absolute path"))
}
allErrs = append(allErrs, validatePathNoBacksteps(ls.Path, fldPath.Child("path"))...) allErrs = append(allErrs, validatePathNoBacksteps(ls.Path, fldPath.Child("path"))...)
return allErrs return allErrs
} }
...@@ -1497,6 +1500,15 @@ func ValidatePersistentVolume(pv *core.PersistentVolume) field.ErrorList { ...@@ -1497,6 +1500,15 @@ func ValidatePersistentVolume(pv *core.PersistentVolume) field.ErrorList {
nodeAffinitySpecified, errs := validateStorageNodeAffinityAnnotation(pv.ObjectMeta.Annotations, metaPath.Child("annotations")) nodeAffinitySpecified, errs := validateStorageNodeAffinityAnnotation(pv.ObjectMeta.Annotations, metaPath.Child("annotations"))
allErrs = append(allErrs, errs...) allErrs = append(allErrs, errs...)
volumeNodeAffinitySpecified, errs := validateVolumeNodeAffinity(pv.Spec.NodeAffinity, specPath.Child("nodeAffinity"))
allErrs = append(allErrs, errs...)
if nodeAffinitySpecified && volumeNodeAffinitySpecified {
allErrs = append(allErrs, field.Forbidden(specPath.Child("nodeAffinity"), "may not specify both alpha nodeAffinity annotation and nodeAffinity field"))
}
nodeAffinitySpecified = nodeAffinitySpecified || volumeNodeAffinitySpecified
numVolumes := 0 numVolumes := 0
if pv.Spec.HostPath != nil { if pv.Spec.HostPath != nil {
if numVolumes > 0 { if numVolumes > 0 {
...@@ -1725,6 +1737,13 @@ func ValidatePersistentVolumeUpdate(newPv, oldPv *core.PersistentVolume) field.E ...@@ -1725,6 +1737,13 @@ func ValidatePersistentVolumeUpdate(newPv, oldPv *core.PersistentVolume) field.E
allErrs = append(allErrs, ValidateImmutableField(newPv.Spec.VolumeMode, oldPv.Spec.VolumeMode, field.NewPath("volumeMode"))...) allErrs = append(allErrs, ValidateImmutableField(newPv.Spec.VolumeMode, oldPv.Spec.VolumeMode, field.NewPath("volumeMode"))...)
} }
if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) {
// Allow setting NodeAffinity if oldPv NodeAffinity was not set
if oldPv.Spec.NodeAffinity != nil {
allErrs = append(allErrs, ValidateImmutableField(newPv.Spec.NodeAffinity, oldPv.Spec.NodeAffinity, field.NewPath("nodeAffinity"))...)
}
}
return allErrs return allErrs
} }
...@@ -4936,7 +4955,7 @@ func validateStorageNodeAffinityAnnotation(annotations map[string]string, fldPat ...@@ -4936,7 +4955,7 @@ func validateStorageNodeAffinityAnnotation(annotations map[string]string, fldPat
return false, allErrs return false, allErrs
} }
if !utilfeature.DefaultFeatureGate.Enabled(features.PersistentLocalVolumes) { if !utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) {
allErrs = append(allErrs, field.Forbidden(fldPath, "Storage node affinity is disabled by feature-gate")) allErrs = append(allErrs, field.Forbidden(fldPath, "Storage node affinity is disabled by feature-gate"))
} }
...@@ -4952,6 +4971,30 @@ func validateStorageNodeAffinityAnnotation(annotations map[string]string, fldPat ...@@ -4952,6 +4971,30 @@ func validateStorageNodeAffinityAnnotation(annotations map[string]string, fldPat
return policySpecified, allErrs return policySpecified, allErrs
} }
// validateVolumeNodeAffinity tests that the PersistentVolume.NodeAffinity has valid data
// returns:
// - true if volumeNodeAffinity is set
// - errorList if there are validation errors
func validateVolumeNodeAffinity(nodeAffinity *core.VolumeNodeAffinity, fldPath *field.Path) (bool, field.ErrorList) {
allErrs := field.ErrorList{}
if nodeAffinity == nil {
return false, allErrs
}
if !utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) {
allErrs = append(allErrs, field.Forbidden(fldPath, "Volume node affinity is disabled by feature-gate"))
}
if nodeAffinity.Required != nil {
allErrs = append(allErrs, ValidateNodeSelector(nodeAffinity.Required, fldPath.Child("required"))...)
} else {
allErrs = append(allErrs, field.Required(fldPath.Child("required"), "must specify required node constraints"))
}
return true, allErrs
}
// ValidateCIDR validates whether a CIDR matches the conventions expected by net.ParseCIDR // ValidateCIDR validates whether a CIDR matches the conventions expected by net.ParseCIDR
func ValidateCIDR(cidr string) (*net.IPNet, error) { func ValidateCIDR(cidr string) (*net.IPNet, error) {
_, net, err := net.ParseCIDR(cidr) _, net, err := net.ParseCIDR(cidr)
......
...@@ -3369,6 +3369,15 @@ func (in *PersistentVolumeSpec) DeepCopyInto(out *PersistentVolumeSpec) { ...@@ -3369,6 +3369,15 @@ func (in *PersistentVolumeSpec) DeepCopyInto(out *PersistentVolumeSpec) {
**out = **in **out = **in
} }
} }
if in.NodeAffinity != nil {
in, out := &in.NodeAffinity, &out.NodeAffinity
if *in == nil {
*out = nil
} else {
*out = new(VolumeNodeAffinity)
(*in).DeepCopyInto(*out)
}
}
return return
} }
...@@ -5571,6 +5580,31 @@ func (in *VolumeMount) DeepCopy() *VolumeMount { ...@@ -5571,6 +5580,31 @@ func (in *VolumeMount) DeepCopy() *VolumeMount {
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VolumeNodeAffinity) DeepCopyInto(out *VolumeNodeAffinity) {
*out = *in
if in.Required != nil {
in, out := &in.Required, &out.Required
if *in == nil {
*out = nil
} else {
*out = new(NodeSelector)
(*in).DeepCopyInto(*out)
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeNodeAffinity.
func (in *VolumeNodeAffinity) DeepCopy() *VolumeNodeAffinity {
if in == nil {
return nil
}
out := new(VolumeNodeAffinity)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VolumeProjection) DeepCopyInto(out *VolumeProjection) { func (in *VolumeProjection) DeepCopyInto(out *VolumeProjection) {
*out = *in *out = *in
if in.Secret != nil { if in.Secret != nil {
......
...@@ -22,6 +22,7 @@ import ( ...@@ -22,6 +22,7 @@ import (
runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/storage" "k8s.io/kubernetes/pkg/apis/storage"
storageapi "k8s.io/kubernetes/pkg/apis/storage"
) )
// Funcs returns the fuzzer functions for the storage api group. // Funcs returns the fuzzer functions for the storage api group.
...@@ -31,6 +32,8 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} { ...@@ -31,6 +32,8 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
c.FuzzNoCustom(obj) // fuzz self without calling this function again c.FuzzNoCustom(obj) // fuzz self without calling this function again
reclamationPolicies := []api.PersistentVolumeReclaimPolicy{api.PersistentVolumeReclaimDelete, api.PersistentVolumeReclaimRetain} reclamationPolicies := []api.PersistentVolumeReclaimPolicy{api.PersistentVolumeReclaimDelete, api.PersistentVolumeReclaimRetain}
obj.ReclaimPolicy = &reclamationPolicies[c.Rand.Intn(len(reclamationPolicies))] obj.ReclaimPolicy = &reclamationPolicies[c.Rand.Intn(len(reclamationPolicies))]
bindingModes := []storageapi.VolumeBindingMode{storageapi.VolumeBindingImmediate, storageapi.VolumeBindingWaitForFirstConsumer}
obj.VolumeBindingMode = &bindingModes[c.Rand.Intn(len(bindingModes))]
}, },
} }
} }
...@@ -27,6 +27,9 @@ func TestDropAlphaFields(t *testing.T) { ...@@ -27,6 +27,9 @@ func TestDropAlphaFields(t *testing.T) {
bindingMode := storage.VolumeBindingWaitForFirstConsumer bindingMode := storage.VolumeBindingWaitForFirstConsumer
// Test that field gets dropped when feature gate is not set // Test that field gets dropped when feature gate is not set
if err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false"); err != nil {
t.Fatalf("Failed to set feature gate for VolumeScheduling: %v", err)
}
class := &storage.StorageClass{ class := &storage.StorageClass{
VolumeBindingMode: &bindingMode, VolumeBindingMode: &bindingMode,
} }
......
...@@ -52,6 +52,10 @@ func TestSetDefaultVolumeBindingMode(t *testing.T) { ...@@ -52,6 +52,10 @@ func TestSetDefaultVolumeBindingMode(t *testing.T) {
class := &storagev1.StorageClass{} class := &storagev1.StorageClass{}
// When feature gate is disabled, field should not be defaulted // When feature gate is disabled, field should not be defaulted
err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false")
if err != nil {
t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err)
}
output := roundTrip(t, runtime.Object(class)).(*storagev1.StorageClass) output := roundTrip(t, runtime.Object(class)).(*storagev1.StorageClass)
if output.VolumeBindingMode != nil { if output.VolumeBindingMode != nil {
t.Errorf("Expected VolumeBindingMode to not be defaulted, got: %+v", output.VolumeBindingMode) t.Errorf("Expected VolumeBindingMode to not be defaulted, got: %+v", output.VolumeBindingMode)
...@@ -59,12 +63,11 @@ func TestSetDefaultVolumeBindingMode(t *testing.T) { ...@@ -59,12 +63,11 @@ func TestSetDefaultVolumeBindingMode(t *testing.T) {
class = &storagev1.StorageClass{} class = &storagev1.StorageClass{}
err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true") // When feature gate is enabled, field should be defaulted
err = utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true")
if err != nil { if err != nil {
t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err) t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err)
} }
// When feature gate is enabled, field should be defaulted
defaultMode := storagev1.VolumeBindingImmediate defaultMode := storagev1.VolumeBindingImmediate
output = roundTrip(t, runtime.Object(class)).(*storagev1.StorageClass) output = roundTrip(t, runtime.Object(class)).(*storagev1.StorageClass)
outMode := output.VolumeBindingMode outMode := output.VolumeBindingMode
......
...@@ -52,6 +52,10 @@ func TestSetDefaultVolumeBindingMode(t *testing.T) { ...@@ -52,6 +52,10 @@ func TestSetDefaultVolumeBindingMode(t *testing.T) {
class := &storagev1beta1.StorageClass{} class := &storagev1beta1.StorageClass{}
// When feature gate is disabled, field should not be defaulted // When feature gate is disabled, field should not be defaulted
err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false")
if err != nil {
t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err)
}
output := roundTrip(t, runtime.Object(class)).(*storagev1beta1.StorageClass) output := roundTrip(t, runtime.Object(class)).(*storagev1beta1.StorageClass)
if output.VolumeBindingMode != nil { if output.VolumeBindingMode != nil {
t.Errorf("Expected VolumeBindingMode to not be defaulted, got: %+v", output.VolumeBindingMode) t.Errorf("Expected VolumeBindingMode to not be defaulted, got: %+v", output.VolumeBindingMode)
...@@ -59,12 +63,11 @@ func TestSetDefaultVolumeBindingMode(t *testing.T) { ...@@ -59,12 +63,11 @@ func TestSetDefaultVolumeBindingMode(t *testing.T) {
class = &storagev1beta1.StorageClass{} class = &storagev1beta1.StorageClass{}
err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true") // When feature gate is enabled, field should be defaulted
err = utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true")
if err != nil { if err != nil {
t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err) t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err)
} }
// When feature gate is enabled, field should be defaulted
defaultMode := storagev1beta1.VolumeBindingImmediate defaultMode := storagev1beta1.VolumeBindingImmediate
output = roundTrip(t, runtime.Object(class)).(*storagev1beta1.StorageClass) output = roundTrip(t, runtime.Object(class)).(*storagev1beta1.StorageClass)
outMode := output.VolumeBindingMode outMode := output.VolumeBindingMode
......
...@@ -42,16 +42,18 @@ func TestValidateStorageClass(t *testing.T) { ...@@ -42,16 +42,18 @@ func TestValidateStorageClass(t *testing.T) {
successCases := []storage.StorageClass{ successCases := []storage.StorageClass{
{ {
// empty parameters // empty parameters
ObjectMeta: metav1.ObjectMeta{Name: "foo"}, ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Provisioner: "kubernetes.io/foo-provisioner", Provisioner: "kubernetes.io/foo-provisioner",
Parameters: map[string]string{}, Parameters: map[string]string{},
ReclaimPolicy: &deleteReclaimPolicy, ReclaimPolicy: &deleteReclaimPolicy,
VolumeBindingMode: &immediateMode1,
}, },
{ {
// nil parameters // nil parameters
ObjectMeta: metav1.ObjectMeta{Name: "foo"}, ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Provisioner: "kubernetes.io/foo-provisioner", Provisioner: "kubernetes.io/foo-provisioner",
ReclaimPolicy: &deleteReclaimPolicy, ReclaimPolicy: &deleteReclaimPolicy,
VolumeBindingMode: &immediateMode1,
}, },
{ {
// some parameters // some parameters
...@@ -62,13 +64,15 @@ func TestValidateStorageClass(t *testing.T) { ...@@ -62,13 +64,15 @@ func TestValidateStorageClass(t *testing.T) {
"foo-parameter": "free-form-string", "foo-parameter": "free-form-string",
"foo-parameter2": "{\"embedded\": \"json\", \"with\": {\"structures\":\"inside\"}}", "foo-parameter2": "{\"embedded\": \"json\", \"with\": {\"structures\":\"inside\"}}",
}, },
ReclaimPolicy: &deleteReclaimPolicy, ReclaimPolicy: &deleteReclaimPolicy,
VolumeBindingMode: &immediateMode1,
}, },
{ {
// retain reclaimPolicy // retain reclaimPolicy
ObjectMeta: metav1.ObjectMeta{Name: "foo"}, ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Provisioner: "kubernetes.io/foo-provisioner", Provisioner: "kubernetes.io/foo-provisioner",
ReclaimPolicy: &retainReclaimPolicy, ReclaimPolicy: &retainReclaimPolicy,
VolumeBindingMode: &immediateMode1,
}, },
} }
...@@ -144,6 +148,7 @@ func TestAlphaExpandPersistentVolumesFeatureValidation(t *testing.T) { ...@@ -144,6 +148,7 @@ func TestAlphaExpandPersistentVolumesFeatureValidation(t *testing.T) {
Parameters: map[string]string{}, Parameters: map[string]string{},
ReclaimPolicy: &deleteReclaimPolicy, ReclaimPolicy: &deleteReclaimPolicy,
AllowVolumeExpansion: &falseVar, AllowVolumeExpansion: &falseVar,
VolumeBindingMode: &immediateMode1,
} }
// Enable alpha feature ExpandPersistentVolumes // Enable alpha feature ExpandPersistentVolumes
...@@ -462,6 +467,10 @@ func TestValidateVolumeBindingModeAlphaDisabled(t *testing.T) { ...@@ -462,6 +467,10 @@ func TestValidateVolumeBindingModeAlphaDisabled(t *testing.T) {
"invalid mode": makeClassWithBinding(&invalidMode), "invalid mode": makeClassWithBinding(&invalidMode),
} }
err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false")
if err != nil {
t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err)
}
for testName, storageClass := range errorCases { for testName, storageClass := range errorCases {
if errs := ValidateStorageClass(storageClass); len(errs) == 0 { if errs := ValidateStorageClass(storageClass); len(errs) == 0 {
t.Errorf("Expected failure for test: %v", testName) t.Errorf("Expected failure for test: %v", testName)
......
...@@ -78,7 +78,6 @@ go_test( ...@@ -78,7 +78,6 @@ go_test(
deps = [ deps = [
"//pkg/api/testapi:go_default_library", "//pkg/api/testapi:go_default_library",
"//pkg/apis/core:go_default_library", "//pkg/apis/core:go_default_library",
"//pkg/apis/core/v1/helper:go_default_library",
"//pkg/controller:go_default_library", "//pkg/controller:go_default_library",
"//pkg/volume:go_default_library", "//pkg/volume:go_default_library",
"//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/golang/glog:go_default_library",
......
...@@ -20,8 +20,6 @@ import ( ...@@ -20,8 +20,6 @@ import (
"sort" "sort"
"testing" "testing"
"github.com/golang/glog"
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
...@@ -29,7 +27,6 @@ import ( ...@@ -29,7 +27,6 @@ import (
"k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/kubernetes/scheme"
ref "k8s.io/client-go/tools/reference" ref "k8s.io/client-go/tools/reference"
"k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/apis/core/v1/helper"
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
) )
...@@ -680,9 +677,8 @@ func createTestVolumes() []*v1.PersistentVolume { ...@@ -680,9 +677,8 @@ func createTestVolumes() []*v1.PersistentVolume {
}, },
{ {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
UID: "affinity-pv", UID: "affinity-pv",
Name: "affinity001", Name: "affinity001",
Annotations: getAnnotationWithNodeAffinity("key1", "value1"),
}, },
Spec: v1.PersistentVolumeSpec{ Spec: v1.PersistentVolumeSpec{
Capacity: v1.ResourceList{ Capacity: v1.ResourceList{
...@@ -696,13 +692,13 @@ func createTestVolumes() []*v1.PersistentVolume { ...@@ -696,13 +692,13 @@ func createTestVolumes() []*v1.PersistentVolume {
v1.ReadOnlyMany, v1.ReadOnlyMany,
}, },
StorageClassName: classWait, StorageClassName: classWait,
NodeAffinity: getVolumeNodeAffinity("key1", "value1"),
}, },
}, },
{ {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
UID: "affinity-pv2", UID: "affinity-pv2",
Name: "affinity002", Name: "affinity002",
Annotations: getAnnotationWithNodeAffinity("key1", "value1"),
}, },
Spec: v1.PersistentVolumeSpec{ Spec: v1.PersistentVolumeSpec{
Capacity: v1.ResourceList{ Capacity: v1.ResourceList{
...@@ -716,13 +712,13 @@ func createTestVolumes() []*v1.PersistentVolume { ...@@ -716,13 +712,13 @@ func createTestVolumes() []*v1.PersistentVolume {
v1.ReadOnlyMany, v1.ReadOnlyMany,
}, },
StorageClassName: classWait, StorageClassName: classWait,
NodeAffinity: getVolumeNodeAffinity("key1", "value1"),
}, },
}, },
{ {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
UID: "affinity-prebound", UID: "affinity-prebound",
Name: "affinity003", Name: "affinity003",
Annotations: getAnnotationWithNodeAffinity("key1", "value1"),
}, },
Spec: v1.PersistentVolumeSpec{ Spec: v1.PersistentVolumeSpec{
Capacity: v1.ResourceList{ Capacity: v1.ResourceList{
...@@ -737,13 +733,13 @@ func createTestVolumes() []*v1.PersistentVolume { ...@@ -737,13 +733,13 @@ func createTestVolumes() []*v1.PersistentVolume {
}, },
StorageClassName: classWait, StorageClassName: classWait,
ClaimRef: &v1.ObjectReference{Name: "claim02", Namespace: "myns"}, ClaimRef: &v1.ObjectReference{Name: "claim02", Namespace: "myns"},
NodeAffinity: getVolumeNodeAffinity("key1", "value1"),
}, },
}, },
{ {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
UID: "affinity-pv3", UID: "affinity-pv3",
Name: "affinity003", Name: "affinity003",
Annotations: getAnnotationWithNodeAffinity("key1", "value3"),
}, },
Spec: v1.PersistentVolumeSpec{ Spec: v1.PersistentVolumeSpec{
Capacity: v1.ResourceList{ Capacity: v1.ResourceList{
...@@ -757,6 +753,7 @@ func createTestVolumes() []*v1.PersistentVolume { ...@@ -757,6 +753,7 @@ func createTestVolumes() []*v1.PersistentVolume {
v1.ReadOnlyMany, v1.ReadOnlyMany,
}, },
StorageClassName: classWait, StorageClassName: classWait,
NodeAffinity: getVolumeNodeAffinity("key1", "value3"),
}, },
}, },
} }
...@@ -776,9 +773,9 @@ func testVolume(name, size string) *v1.PersistentVolume { ...@@ -776,9 +773,9 @@ func testVolume(name, size string) *v1.PersistentVolume {
} }
} }
func getAnnotationWithNodeAffinity(key string, value string) map[string]string { func getVolumeNodeAffinity(key string, value string) *v1.VolumeNodeAffinity {
affinity := &v1.NodeAffinity{ return &v1.VolumeNodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ Required: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{ NodeSelectorTerms: []v1.NodeSelectorTerm{
{ {
MatchExpressions: []v1.NodeSelectorRequirement{ MatchExpressions: []v1.NodeSelectorRequirement{
...@@ -792,14 +789,6 @@ func getAnnotationWithNodeAffinity(key string, value string) map[string]string { ...@@ -792,14 +789,6 @@ func getAnnotationWithNodeAffinity(key string, value string) map[string]string {
}, },
}, },
} }
annotations := map[string]string{}
err := helper.StorageNodeAffinityToAlphaAnnotation(annotations, affinity)
if err != nil {
glog.Fatalf("Failed to get node affinity annotation: %v", err)
}
return annotations
} }
func createVolumeModeBlockTestVolume() *v1.PersistentVolume { func createVolumeModeBlockTestVolume() *v1.PersistentVolume {
......
...@@ -331,7 +331,7 @@ func makeTestPV(name, node, capacity, version string, boundToPVC *v1.PersistentV ...@@ -331,7 +331,7 @@ func makeTestPV(name, node, capacity, version string, boundToPVC *v1.PersistentV
}, },
} }
if node != "" { if node != "" {
pv.Annotations = getAnnotationWithNodeAffinity("key1", node) pv.Spec.NodeAffinity = getVolumeNodeAffinity("key1", node)
} }
if boundToPVC != nil { if boundToPVC != nil {
......
...@@ -263,7 +263,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS ...@@ -263,7 +263,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS
TaintBasedEvictions: {Default: false, PreRelease: utilfeature.Alpha}, TaintBasedEvictions: {Default: false, PreRelease: utilfeature.Alpha},
RotateKubeletServerCertificate: {Default: false, PreRelease: utilfeature.Alpha}, RotateKubeletServerCertificate: {Default: false, PreRelease: utilfeature.Alpha},
RotateKubeletClientCertificate: {Default: true, PreRelease: utilfeature.Beta}, RotateKubeletClientCertificate: {Default: true, PreRelease: utilfeature.Beta},
PersistentLocalVolumes: {Default: false, PreRelease: utilfeature.Alpha}, PersistentLocalVolumes: {Default: true, PreRelease: utilfeature.Beta},
LocalStorageCapacityIsolation: {Default: false, PreRelease: utilfeature.Alpha}, LocalStorageCapacityIsolation: {Default: false, PreRelease: utilfeature.Alpha},
HugePages: {Default: true, PreRelease: utilfeature.Beta}, HugePages: {Default: true, PreRelease: utilfeature.Beta},
DebugContainers: {Default: false, PreRelease: utilfeature.Alpha}, DebugContainers: {Default: false, PreRelease: utilfeature.Alpha},
...@@ -276,7 +276,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS ...@@ -276,7 +276,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS
CPUManager: {Default: true, PreRelease: utilfeature.Beta}, CPUManager: {Default: true, PreRelease: utilfeature.Beta},
ServiceNodeExclusion: {Default: false, PreRelease: utilfeature.Alpha}, ServiceNodeExclusion: {Default: false, PreRelease: utilfeature.Alpha},
MountContainers: {Default: false, PreRelease: utilfeature.Alpha}, MountContainers: {Default: false, PreRelease: utilfeature.Alpha},
VolumeScheduling: {Default: false, PreRelease: utilfeature.Alpha}, VolumeScheduling: {Default: true, PreRelease: utilfeature.Beta},
CSIPersistentVolume: {Default: true, PreRelease: utilfeature.Beta}, CSIPersistentVolume: {Default: true, PreRelease: utilfeature.Beta},
CustomPodDNS: {Default: false, PreRelease: utilfeature.Alpha}, CustomPodDNS: {Default: false, PreRelease: utilfeature.Alpha},
BlockVolume: {Default: false, PreRelease: utilfeature.Alpha}, BlockVolume: {Default: false, PreRelease: utilfeature.Alpha},
......
...@@ -45,6 +45,7 @@ func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) { ...@@ -45,6 +45,7 @@ func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) {
func validNewStorageClass(name string) *storageapi.StorageClass { func validNewStorageClass(name string) *storageapi.StorageClass {
deleteReclaimPolicy := api.PersistentVolumeReclaimDelete deleteReclaimPolicy := api.PersistentVolumeReclaimDelete
bindingMode := storageapi.VolumeBindingImmediate
return &storageapi.StorageClass{ return &storageapi.StorageClass{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: name, Name: name,
...@@ -53,7 +54,8 @@ func validNewStorageClass(name string) *storageapi.StorageClass { ...@@ -53,7 +54,8 @@ func validNewStorageClass(name string) *storageapi.StorageClass {
Parameters: map[string]string{ Parameters: map[string]string{
"foo": "bar", "foo": "bar",
}, },
ReclaimPolicy: &deleteReclaimPolicy, ReclaimPolicy: &deleteReclaimPolicy,
VolumeBindingMode: &bindingMode,
} }
} }
......
...@@ -207,7 +207,8 @@ func TestScheduler(t *testing.T) { ...@@ -207,7 +207,8 @@ func TestScheduler(t *testing.T) {
NextPod: func() *v1.Pod { NextPod: func() *v1.Pod {
return item.sendPod return item.sendPod
}, },
Recorder: eventBroadcaster.NewRecorder(legacyscheme.Scheme, v1.EventSource{Component: "scheduler"}), Recorder: eventBroadcaster.NewRecorder(legacyscheme.Scheme, v1.EventSource{Component: "scheduler"}),
VolumeBinder: volumebinder.NewFakeVolumeBinder(&persistentvolume.FakeVolumeBinderConfig{AllBound: true}),
}, },
} }
...@@ -555,6 +556,7 @@ func setupTestScheduler(queuedPodStore *clientcache.FIFO, scache schedulercache. ...@@ -555,6 +556,7 @@ func setupTestScheduler(queuedPodStore *clientcache.FIFO, scache schedulercache.
Recorder: &record.FakeRecorder{}, Recorder: &record.FakeRecorder{},
PodConditionUpdater: fakePodConditionUpdater{}, PodConditionUpdater: fakePodConditionUpdater{},
PodPreemptor: fakePodPreemptor{}, PodPreemptor: fakePodPreemptor{},
VolumeBinder: volumebinder.NewFakeVolumeBinder(&persistentvolume.FakeVolumeBinderConfig{AllBound: true}),
}, },
} }
...@@ -604,6 +606,7 @@ func setupTestSchedulerLongBindingWithRetry(queuedPodStore *clientcache.FIFO, sc ...@@ -604,6 +606,7 @@ func setupTestSchedulerLongBindingWithRetry(queuedPodStore *clientcache.FIFO, sc
PodConditionUpdater: fakePodConditionUpdater{}, PodConditionUpdater: fakePodConditionUpdater{},
PodPreemptor: fakePodPreemptor{}, PodPreemptor: fakePodPreemptor{},
StopEverything: stop, StopEverything: stop,
VolumeBinder: volumebinder.NewFakeVolumeBinder(&persistentvolume.FakeVolumeBinderConfig{AllBound: true}),
}, },
} }
......
...@@ -237,6 +237,13 @@ func GetClassForVolume(kubeClient clientset.Interface, pv *v1.PersistentVolume) ...@@ -237,6 +237,13 @@ func GetClassForVolume(kubeClient clientset.Interface, pv *v1.PersistentVolume)
// CheckNodeAffinity looks at the PV node affinity, and checks if the node has the same corresponding labels // CheckNodeAffinity looks at the PV node affinity, and checks if the node has the same corresponding labels
// This ensures that we don't mount a volume that doesn't belong to this node // This ensures that we don't mount a volume that doesn't belong to this node
func CheckNodeAffinity(pv *v1.PersistentVolume, nodeLabels map[string]string) error { func CheckNodeAffinity(pv *v1.PersistentVolume, nodeLabels map[string]string) error {
if err := checkAlphaNodeAffinity(pv, nodeLabels); err != nil {
return err
}
return checkVolumeNodeAffinity(pv, nodeLabels)
}
func checkAlphaNodeAffinity(pv *v1.PersistentVolume, nodeLabels map[string]string) error {
affinity, err := v1helper.GetStorageNodeAffinityFromAnnotation(pv.Annotations) affinity, err := v1helper.GetStorageNodeAffinityFromAnnotation(pv.Annotations)
if err != nil { if err != nil {
return fmt.Errorf("Error getting storage node affinity: %v", err) return fmt.Errorf("Error getting storage node affinity: %v", err)
...@@ -261,6 +268,27 @@ func CheckNodeAffinity(pv *v1.PersistentVolume, nodeLabels map[string]string) er ...@@ -261,6 +268,27 @@ func CheckNodeAffinity(pv *v1.PersistentVolume, nodeLabels map[string]string) er
return nil return nil
} }
func checkVolumeNodeAffinity(pv *v1.PersistentVolume, nodeLabels map[string]string) error {
if pv.Spec.NodeAffinity == nil {
return nil
}
if pv.Spec.NodeAffinity.Required != nil {
terms := pv.Spec.NodeAffinity.Required.NodeSelectorTerms
glog.V(10).Infof("Match for Required node selector terms %+v", terms)
for _, term := range terms {
selector, err := v1helper.NodeSelectorRequirementsAsSelector(term.MatchExpressions)
if err != nil {
return fmt.Errorf("Failed to parse MatchExpressions: %v", err)
}
if !selector.Matches(labels.Set(nodeLabels)) {
return fmt.Errorf("NodeSelectorTerm %+v does not match node labels", term.MatchExpressions)
}
}
}
return nil
}
// LoadPodFromFile will read, decode, and return a Pod from a file. // LoadPodFromFile will read, decode, and return a Pod from a file.
func LoadPodFromFile(filePath string) (*v1.Pod, error) { func LoadPodFromFile(filePath string) (*v1.Pod, error) {
if filePath == "" { if filePath == "" {
......
...@@ -37,7 +37,7 @@ var nodeLabels map[string]string = map[string]string{ ...@@ -37,7 +37,7 @@ var nodeLabels map[string]string = map[string]string{
"test-key2": "test-value2", "test-key2": "test-value2",
} }
func TestCheckNodeAffinity(t *testing.T) { func TestCheckAlphaNodeAffinity(t *testing.T) {
type affinityTest struct { type affinityTest struct {
name string name string
expectSuccess bool expectSuccess bool
...@@ -48,12 +48,12 @@ func TestCheckNodeAffinity(t *testing.T) { ...@@ -48,12 +48,12 @@ func TestCheckNodeAffinity(t *testing.T) {
{ {
name: "valid-no-constraints", name: "valid-no-constraints",
expectSuccess: true, expectSuccess: true,
pv: testVolumeWithNodeAffinity(t, &v1.NodeAffinity{}), pv: testVolumeWithAlphaNodeAffinity(t, &v1.NodeAffinity{}),
}, },
{ {
name: "valid-constraints", name: "valid-constraints",
expectSuccess: true, expectSuccess: true,
pv: testVolumeWithNodeAffinity(t, &v1.NodeAffinity{ pv: testVolumeWithAlphaNodeAffinity(t, &v1.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{ NodeSelectorTerms: []v1.NodeSelectorTerm{
{ {
...@@ -77,7 +77,7 @@ func TestCheckNodeAffinity(t *testing.T) { ...@@ -77,7 +77,7 @@ func TestCheckNodeAffinity(t *testing.T) {
{ {
name: "invalid-key", name: "invalid-key",
expectSuccess: false, expectSuccess: false,
pv: testVolumeWithNodeAffinity(t, &v1.NodeAffinity{ pv: testVolumeWithAlphaNodeAffinity(t, &v1.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{ NodeSelectorTerms: []v1.NodeSelectorTerm{
{ {
...@@ -101,7 +101,7 @@ func TestCheckNodeAffinity(t *testing.T) { ...@@ -101,7 +101,7 @@ func TestCheckNodeAffinity(t *testing.T) {
{ {
name: "invalid-values", name: "invalid-values",
expectSuccess: false, expectSuccess: false,
pv: testVolumeWithNodeAffinity(t, &v1.NodeAffinity{ pv: testVolumeWithAlphaNodeAffinity(t, &v1.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{ NodeSelectorTerms: []v1.NodeSelectorTerm{
{ {
...@@ -136,7 +136,111 @@ func TestCheckNodeAffinity(t *testing.T) { ...@@ -136,7 +136,111 @@ func TestCheckNodeAffinity(t *testing.T) {
} }
} }
func testVolumeWithNodeAffinity(t *testing.T, affinity *v1.NodeAffinity) *v1.PersistentVolume { func TestCheckVolumeNodeAffinity(t *testing.T) {
type affinityTest struct {
name string
expectSuccess bool
pv *v1.PersistentVolume
}
cases := []affinityTest{
{
name: "valid-nil",
expectSuccess: true,
pv: testVolumeWithNodeAffinity(t, nil),
},
{
name: "valid-no-constraints",
expectSuccess: true,
pv: testVolumeWithNodeAffinity(t, &v1.VolumeNodeAffinity{}),
},
{
name: "valid-constraints",
expectSuccess: true,
pv: testVolumeWithNodeAffinity(t, &v1.VolumeNodeAffinity{
Required: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{
{
MatchExpressions: []v1.NodeSelectorRequirement{
{
Key: "test-key1",
Operator: v1.NodeSelectorOpIn,
Values: []string{"test-value1", "test-value3"},
},
{
Key: "test-key2",
Operator: v1.NodeSelectorOpIn,
Values: []string{"test-value0", "test-value2"},
},
},
},
},
},
}),
},
{
name: "invalid-key",
expectSuccess: false,
pv: testVolumeWithNodeAffinity(t, &v1.VolumeNodeAffinity{
Required: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{
{
MatchExpressions: []v1.NodeSelectorRequirement{
{
Key: "test-key1",
Operator: v1.NodeSelectorOpIn,
Values: []string{"test-value1", "test-value3"},
},
{
Key: "test-key3",
Operator: v1.NodeSelectorOpIn,
Values: []string{"test-value0", "test-value2"},
},
},
},
},
},
}),
},
{
name: "invalid-values",
expectSuccess: false,
pv: testVolumeWithNodeAffinity(t, &v1.VolumeNodeAffinity{
Required: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{
{
MatchExpressions: []v1.NodeSelectorRequirement{
{
Key: "test-key1",
Operator: v1.NodeSelectorOpIn,
Values: []string{"test-value3", "test-value4"},
},
{
Key: "test-key2",
Operator: v1.NodeSelectorOpIn,
Values: []string{"test-value0", "test-value2"},
},
},
},
},
},
}),
},
}
for _, c := range cases {
err := CheckNodeAffinity(c.pv, nodeLabels)
if err != nil && c.expectSuccess {
t.Errorf("CheckTopology %v returned error: %v", c.name, err)
}
if err == nil && !c.expectSuccess {
t.Errorf("CheckTopology %v returned success, expected error", c.name)
}
}
}
func testVolumeWithAlphaNodeAffinity(t *testing.T, affinity *v1.NodeAffinity) *v1.PersistentVolume {
objMeta := metav1.ObjectMeta{Name: "test-constraints"} objMeta := metav1.ObjectMeta{Name: "test-constraints"}
objMeta.Annotations = map[string]string{} objMeta.Annotations = map[string]string{}
err := helper.StorageNodeAffinityToAlphaAnnotation(objMeta.Annotations, affinity) err := helper.StorageNodeAffinityToAlphaAnnotation(objMeta.Annotations, affinity)
...@@ -149,6 +253,16 @@ func testVolumeWithNodeAffinity(t *testing.T, affinity *v1.NodeAffinity) *v1.Per ...@@ -149,6 +253,16 @@ func testVolumeWithNodeAffinity(t *testing.T, affinity *v1.NodeAffinity) *v1.Per
} }
} }
func testVolumeWithNodeAffinity(t *testing.T, affinity *v1.VolumeNodeAffinity) *v1.PersistentVolume {
objMeta := metav1.ObjectMeta{Name: "test-constraints"}
return &v1.PersistentVolume{
ObjectMeta: objMeta,
Spec: v1.PersistentVolumeSpec{
NodeAffinity: affinity,
},
}
}
func TestLoadPodFromFile(t *testing.T) { func TestLoadPodFromFile(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
......
...@@ -27,8 +27,9 @@ import ( ...@@ -27,8 +27,9 @@ import (
) )
var ( var (
ReadWrite = []string{"get", "list", "watch", "create", "update", "patch", "delete", "deletecollection"} ReadWrite = []string{"get", "list", "watch", "create", "update", "patch", "delete", "deletecollection"}
Read = []string{"get", "list", "watch"} Read = []string{"get", "list", "watch"}
ReadUpdate = []string{"get", "list", "watch", "update", "patch"}
Label = map[string]string{"kubernetes.io/bootstrapping": "rbac-defaults"} Label = map[string]string{"kubernetes.io/bootstrapping": "rbac-defaults"}
Annotation = map[string]string{rbac.AutoUpdateAnnotationKey: "true"} Annotation = map[string]string{rbac.AutoUpdateAnnotationKey: "true"}
...@@ -483,15 +484,13 @@ func ClusterRoles() []rbac.ClusterRole { ...@@ -483,15 +484,13 @@ func ClusterRoles() []rbac.ClusterRole {
} }
if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) { if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) {
// Find the scheduler role roles = append(roles, rbac.ClusterRole{
for i, role := range roles { ObjectMeta: metav1.ObjectMeta{Name: "system:volume-scheduler"},
if role.Name == "system:kube-scheduler" { Rules: []rbac.PolicyRule{
pvRule := rbac.NewRule("update").Groups(legacyGroup).Resources("persistentvolumes").RuleOrDie() rbac.NewRule(ReadUpdate...).Groups(legacyGroup).Resources("persistentvolumes").RuleOrDie(),
scRule := rbac.NewRule(Read...).Groups(storageGroup).Resources("storageclasses").RuleOrDie() rbac.NewRule(Read...).Groups(storageGroup).Resources("storageclasses").RuleOrDie(),
roles[i].Rules = append(role.Rules, pvRule, scRule) },
break })
}
}
} }
addClusterRoleLabel(roles) addClusterRoleLabel(roles)
...@@ -520,6 +519,10 @@ func ClusterRoleBindings() []rbac.ClusterRoleBinding { ...@@ -520,6 +519,10 @@ func ClusterRoleBindings() []rbac.ClusterRoleBinding {
}, },
} }
if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) {
rolebindings = append(rolebindings, rbac.NewClusterBinding("system:volume-scheduler").Users(user.KubeScheduler).BindingOrDie())
}
addClusterRoleBindingLabel(rolebindings) addClusterRoleBindingLabel(rolebindings)
return rolebindings return rolebindings
......
...@@ -156,5 +156,22 @@ items: ...@@ -156,5 +156,22 @@ items:
- apiGroup: rbac.authorization.k8s.io - apiGroup: rbac.authorization.k8s.io
kind: User kind: User
name: system:kube-proxy name: system:kube-proxy
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:volume-scheduler
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:volume-scheduler
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: system:kube-scheduler
kind: List kind: List
metadata: {} metadata: {}
...@@ -1171,6 +1171,34 @@ items: ...@@ -1171,6 +1171,34 @@ items:
- create - create
- patch - patch
- update - update
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: null
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:volume-scheduler
rules:
- apiGroups:
- ""
resources:
- persistentvolumes
verbs:
- get
- list
- patch
- update
- watch
- apiGroups:
- storage.k8s.io
resources:
- storageclasses
verbs:
- get
- list
- watch
- aggregationRule: - aggregationRule:
clusterRoleSelectors: clusterRoleSelectors:
- matchLabels: - matchLabels:
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -2578,6 +2578,11 @@ message PersistentVolumeSpec { ...@@ -2578,6 +2578,11 @@ message PersistentVolumeSpec {
// This is an alpha feature and may change in the future. // This is an alpha feature and may change in the future.
// +optional // +optional
optional string volumeMode = 8; optional string volumeMode = 8;
// NodeAffinity defines constraints that limit what nodes this volume can be accessed from.
// This field influences the scheduling of pods that use this volume.
// +optional
optional VolumeNodeAffinity nodeAffinity = 9;
} }
// PersistentVolumeStatus is the current status of a persistent volume. // PersistentVolumeStatus is the current status of a persistent volume.
...@@ -4457,6 +4462,12 @@ message VolumeMount { ...@@ -4457,6 +4462,12 @@ message VolumeMount {
optional string mountPropagation = 5; optional string mountPropagation = 5;
} }
// VolumeNodeAffinity defines constraints that limit what nodes this volume can be accessed from.
message VolumeNodeAffinity {
// Required specifies hard node constraints that must be met.
optional NodeSelector required = 1;
}
// Projection that may be projected along with other supported volume types // Projection that may be projected along with other supported volume types
message VolumeProjection { message VolumeProjection {
// information about the secret data to project // information about the secret data to project
......
...@@ -530,6 +530,16 @@ type PersistentVolumeSpec struct { ...@@ -530,6 +530,16 @@ type PersistentVolumeSpec struct {
// This is an alpha feature and may change in the future. // This is an alpha feature and may change in the future.
// +optional // +optional
VolumeMode *PersistentVolumeMode `json:"volumeMode,omitempty" protobuf:"bytes,8,opt,name=volumeMode,casttype=PersistentVolumeMode"` 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.
// +optional
NodeAffinity *VolumeNodeAffinity `json:"nodeAffinity,omitempty" protobuf:"bytes,9,opt,name=nodeAffinity"`
}
// VolumeNodeAffinity defines constraints that limit what nodes this volume can be accessed from.
type VolumeNodeAffinity struct {
// Required specifies hard node constraints that must be met.
Required *NodeSelector `json:"required,omitempty" protobuf:"bytes,1,opt,name=required"`
} }
// PersistentVolumeReclaimPolicy describes a policy for end-of-life maintenance of persistent volumes. // PersistentVolumeReclaimPolicy describes a policy for end-of-life maintenance of persistent volumes.
......
...@@ -1292,6 +1292,7 @@ var map_PersistentVolumeSpec = map[string]string{ ...@@ -1292,6 +1292,7 @@ var map_PersistentVolumeSpec = map[string]string{
"storageClassName": "Name of StorageClass to which this persistent volume belongs. Empty value means that this volume does not belong to any StorageClass.", "storageClassName": "Name of StorageClass to which this persistent volume belongs. Empty value means that this volume does not belong to any StorageClass.",
"mountOptions": "A 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", "mountOptions": "A 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. This is an alpha feature and may change in the future.", "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. This is an alpha feature and may change in the future.",
"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.",
} }
func (PersistentVolumeSpec) SwaggerDoc() map[string]string { func (PersistentVolumeSpec) SwaggerDoc() map[string]string {
...@@ -2176,6 +2177,15 @@ func (VolumeMount) SwaggerDoc() map[string]string { ...@@ -2176,6 +2177,15 @@ func (VolumeMount) SwaggerDoc() map[string]string {
return map_VolumeMount return map_VolumeMount
} }
var map_VolumeNodeAffinity = map[string]string{
"": "VolumeNodeAffinity defines constraints that limit what nodes this volume can be accessed from.",
"required": "Required specifies hard node constraints that must be met.",
}
func (VolumeNodeAffinity) SwaggerDoc() map[string]string {
return map_VolumeNodeAffinity
}
var map_VolumeProjection = map[string]string{ var map_VolumeProjection = map[string]string{
"": "Projection that may be projected along with other supported volume types", "": "Projection that may be projected along with other supported volume types",
"secret": "information about the secret data to project", "secret": "information about the secret data to project",
......
...@@ -3355,6 +3355,15 @@ func (in *PersistentVolumeSpec) DeepCopyInto(out *PersistentVolumeSpec) { ...@@ -3355,6 +3355,15 @@ func (in *PersistentVolumeSpec) DeepCopyInto(out *PersistentVolumeSpec) {
**out = **in **out = **in
} }
} }
if in.NodeAffinity != nil {
in, out := &in.NodeAffinity, &out.NodeAffinity
if *in == nil {
*out = nil
} else {
*out = new(VolumeNodeAffinity)
(*in).DeepCopyInto(*out)
}
}
return return
} }
...@@ -5573,6 +5582,31 @@ func (in *VolumeMount) DeepCopy() *VolumeMount { ...@@ -5573,6 +5582,31 @@ func (in *VolumeMount) DeepCopy() *VolumeMount {
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VolumeNodeAffinity) DeepCopyInto(out *VolumeNodeAffinity) {
*out = *in
if in.Required != nil {
in, out := &in.Required, &out.Required
if *in == nil {
*out = nil
} else {
*out = new(NodeSelector)
(*in).DeepCopyInto(*out)
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeNodeAffinity.
func (in *VolumeNodeAffinity) DeepCopy() *VolumeNodeAffinity {
if in == nil {
return nil
}
out := new(VolumeNodeAffinity)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VolumeProjection) DeepCopyInto(out *VolumeProjection) { func (in *VolumeProjection) DeepCopyInto(out *VolumeProjection) {
*out = *in *out = *in
if in.Secret != nil { if in.Secret != nil {
......
...@@ -50,7 +50,6 @@ go_library( ...@@ -50,7 +50,6 @@ go_library(
"//pkg/apis/apps:go_default_library", "//pkg/apis/apps:go_default_library",
"//pkg/apis/batch:go_default_library", "//pkg/apis/batch:go_default_library",
"//pkg/apis/core:go_default_library", "//pkg/apis/core:go_default_library",
"//pkg/apis/core/v1/helper:go_default_library",
"//pkg/apis/extensions:go_default_library", "//pkg/apis/extensions:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/conditions:go_default_library", "//pkg/client/conditions:go_default_library",
......
...@@ -36,7 +36,6 @@ import ( ...@@ -36,7 +36,6 @@ import (
"k8s.io/apimachinery/pkg/util/uuid" "k8s.io/apimachinery/pkg/util/uuid"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
"k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/apis/core/v1/helper"
awscloud "k8s.io/kubernetes/pkg/cloudprovider/providers/aws" awscloud "k8s.io/kubernetes/pkg/cloudprovider/providers/aws"
gcecloud "k8s.io/kubernetes/pkg/cloudprovider/providers/gce" gcecloud "k8s.io/kubernetes/pkg/cloudprovider/providers/gce"
"k8s.io/kubernetes/pkg/volume/util/volumehelper" "k8s.io/kubernetes/pkg/volume/util/volumehelper"
...@@ -81,7 +80,7 @@ type PersistentVolumeConfig struct { ...@@ -81,7 +80,7 @@ type PersistentVolumeConfig struct {
NamePrefix string NamePrefix string
Labels labels.Set Labels labels.Set
StorageClassName string StorageClassName string
NodeAffinity *v1.NodeAffinity NodeAffinity *v1.VolumeNodeAffinity
} }
// PersistentVolumeClaimConfig is consumed by MakePersistentVolumeClaim() to generate a PVC object. // PersistentVolumeClaimConfig is consumed by MakePersistentVolumeClaim() to generate a PVC object.
...@@ -603,13 +602,9 @@ func MakePersistentVolume(pvConfig PersistentVolumeConfig) *v1.PersistentVolume ...@@ -603,13 +602,9 @@ func MakePersistentVolume(pvConfig PersistentVolumeConfig) *v1.PersistentVolume
}, },
ClaimRef: claimRef, ClaimRef: claimRef,
StorageClassName: pvConfig.StorageClassName, StorageClassName: pvConfig.StorageClassName,
NodeAffinity: pvConfig.NodeAffinity,
}, },
} }
err := helper.StorageNodeAffinityToAlphaAnnotation(pv.Annotations, pvConfig.NodeAffinity)
if err != nil {
Logf("Setting storage node affinity failed: %v", err)
return nil
}
return pv return pv
} }
......
...@@ -141,7 +141,7 @@ var ( ...@@ -141,7 +141,7 @@ var (
Level: "s0:c0,c1"} Level: "s0:c0,c1"}
) )
var _ = utils.SIGDescribe("PersistentVolumes-local [Feature:LocalPersistentVolumes]", func() { var _ = utils.SIGDescribe("PersistentVolumes-local ", func() {
f := framework.NewDefaultFramework("persistent-local-volumes-test") f := framework.NewDefaultFramework("persistent-local-volumes-test")
var ( var (
...@@ -680,8 +680,8 @@ func makeLocalPVConfig(config *localTestConfig, volume *localTestVolume) framewo ...@@ -680,8 +680,8 @@ func makeLocalPVConfig(config *localTestConfig, volume *localTestVolume) framewo
}, },
NamePrefix: "local-pv", NamePrefix: "local-pv",
StorageClassName: config.scName, StorageClassName: config.scName,
NodeAffinity: &v1.NodeAffinity{ NodeAffinity: &v1.VolumeNodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ Required: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{ NodeSelectorTerms: []v1.NodeSelectorTerm{
{ {
MatchExpressions: []v1.NodeSelectorRequirement{ MatchExpressions: []v1.NodeSelectorRequirement{
......
...@@ -27,7 +27,6 @@ go_test( ...@@ -27,7 +27,6 @@ go_test(
"//pkg/api/legacyscheme:go_default_library", "//pkg/api/legacyscheme:go_default_library",
"//pkg/api/testapi:go_default_library", "//pkg/api/testapi:go_default_library",
"//pkg/apis/componentconfig:go_default_library", "//pkg/apis/componentconfig:go_default_library",
"//pkg/apis/core/v1/helper:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/informers/informers_generated/internalversion:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library",
"//pkg/controller/nodelifecycle:go_default_library", "//pkg/controller/nodelifecycle:go_default_library",
......
...@@ -39,7 +39,6 @@ import ( ...@@ -39,7 +39,6 @@ import (
"k8s.io/client-go/tools/record" "k8s.io/client-go/tools/record"
"k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/apis/core/v1/helper"
"k8s.io/kubernetes/pkg/controller/volume/persistentvolume" "k8s.io/kubernetes/pkg/controller/volume/persistentvolume"
"k8s.io/kubernetes/pkg/scheduler" "k8s.io/kubernetes/pkg/scheduler"
"k8s.io/kubernetes/pkg/scheduler/factory" "k8s.io/kubernetes/pkg/scheduler/factory"
...@@ -253,31 +252,26 @@ func makeHostBoundPV(t *testing.T, name, scName, pvcName, ns string, node string ...@@ -253,31 +252,26 @@ func makeHostBoundPV(t *testing.T, name, scName, pvcName, ns string, node string
Path: "/tmp/" + node + "/test-path", Path: "/tmp/" + node + "/test-path",
}, },
}, },
}, NodeAffinity: &v1.VolumeNodeAffinity{
} Required: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{
if pvcName != "" {
pv.Spec.ClaimRef = &v1.ObjectReference{Name: pvcName, Namespace: ns}
}
testNodeAffinity := &v1.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{
{
MatchExpressions: []v1.NodeSelectorRequirement{
{ {
Key: affinityLabelKey, MatchExpressions: []v1.NodeSelectorRequirement{
Operator: v1.NodeSelectorOpIn, {
Values: []string{node}, Key: affinityLabelKey,
Operator: v1.NodeSelectorOpIn,
Values: []string{node},
},
},
}, },
}, },
}, },
}, },
}, },
} }
err := helper.StorageNodeAffinityToAlphaAnnotation(pv.Annotations, testNodeAffinity)
if err != nil { if pvcName != "" {
t.Fatalf("Setting storage node affinity failed: %v", err) pv.Spec.ClaimRef = &v1.ObjectReference{Name: pvcName, Namespace: ns}
} }
return pv return pv
......
...@@ -29,7 +29,6 @@ import ( ...@@ -29,7 +29,6 @@ import (
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
"k8s.io/kubernetes/pkg/apis/core/v1/helper"
) )
type testConfig struct { type testConfig struct {
...@@ -252,32 +251,28 @@ func makePV(t *testing.T, name, scName, pvcName, ns string) *v1.PersistentVolume ...@@ -252,32 +251,28 @@ func makePV(t *testing.T, name, scName, pvcName, ns string) *v1.PersistentVolume
Path: "/test-path", Path: "/test-path",
}, },
}, },
}, NodeAffinity: &v1.VolumeNodeAffinity{
} Required: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{
if pvcName != "" {
pv.Spec.ClaimRef = &v1.ObjectReference{Name: pvcName, Namespace: ns}
}
testNodeAffinity := &v1.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{
{
MatchExpressions: []v1.NodeSelectorRequirement{
{ {
Key: labelKey, MatchExpressions: []v1.NodeSelectorRequirement{
Operator: v1.NodeSelectorOpIn, {
Values: []string{labelValue}, Key: labelKey,
Operator: v1.NodeSelectorOpIn,
Values: []string{labelValue},
},
},
}, },
}, },
}, },
}, },
}, },
} }
err := helper.StorageNodeAffinityToAlphaAnnotation(pv.Annotations, testNodeAffinity)
if err != nil { if pvcName != "" {
t.Fatalf("Setting storage node affinity failed: %v", err) pv.Spec.ClaimRef = &v1.ObjectReference{Name: pvcName, Namespace: ns}
} }
return pv return pv
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment