mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-06 18:54:06 +00:00
DisallowInvalidLabelValueInNodeSelector
This commit is contained in:
parent
1e55df4985
commit
ae11c7deb1
@ -20,6 +20,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/node"
|
||||
@ -91,7 +92,7 @@ func GetWarningsForNodeSelector(nodeSelector *metav1.LabelSelector, fieldPath *f
|
||||
}
|
||||
|
||||
// GetWarningsForNodeSelectorTerm checks match expressions of node selector term
|
||||
func GetWarningsForNodeSelectorTerm(nodeSelectorTerm api.NodeSelectorTerm, fieldPath *field.Path) []string {
|
||||
func GetWarningsForNodeSelectorTerm(nodeSelectorTerm api.NodeSelectorTerm, checkLabelValue bool, fieldPath *field.Path) []string {
|
||||
var warnings []string
|
||||
// use of deprecated node labels in matchLabelExpressions
|
||||
for i, expression := range nodeSelectorTerm.MatchExpressions {
|
||||
@ -106,6 +107,19 @@ func GetWarningsForNodeSelectorTerm(nodeSelectorTerm api.NodeSelectorTerm, field
|
||||
),
|
||||
)
|
||||
}
|
||||
if checkLabelValue {
|
||||
for index, value := range expression.Values {
|
||||
for _, msg := range validation.IsValidLabelValue(value) {
|
||||
warnings = append(warnings,
|
||||
fmt.Sprintf(
|
||||
"%s: %s is invalid, %s",
|
||||
fieldPath.Child("matchExpressions").Index(i).Child("values").Index(index),
|
||||
value,
|
||||
msg,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return warnings
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ func warningsForPersistentVolumeSpecAndMeta(fieldPath *field.Path, pvSpec *api.P
|
||||
termFldPath := fieldPath.Child("spec", "nodeAffinity", "required", "nodeSelectorTerms")
|
||||
// use of deprecated node labels in node affinity
|
||||
for i, term := range pvSpec.NodeAffinity.Required.NodeSelectorTerms {
|
||||
warnings = append(warnings, nodeapi.GetWarningsForNodeSelectorTerm(term, termFldPath.Index(i))...)
|
||||
warnings = append(warnings, nodeapi.GetWarningsForNodeSelectorTerm(term, false, termFldPath.Index(i))...)
|
||||
}
|
||||
}
|
||||
// If we are on deprecated volume plugin
|
||||
|
@ -385,6 +385,7 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po
|
||||
AllowNonLocalProjectedTokenPath: false,
|
||||
AllowPodLifecycleSleepActionZeroValue: utilfeature.DefaultFeatureGate.Enabled(features.PodLifecycleSleepActionAllowZero),
|
||||
PodLevelResourcesEnabled: utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResources),
|
||||
AllowInvalidLabelValueInRequiredNodeAffinity: false,
|
||||
}
|
||||
|
||||
// If old spec uses relaxed validation or enabled the RelaxedEnvironmentVariableValidation feature gate,
|
||||
@ -399,6 +400,7 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po
|
||||
opts.AllowIndivisibleHugePagesValues = usesIndivisibleHugePagesValues(oldPodSpec)
|
||||
|
||||
opts.AllowInvalidLabelValueInSelector = hasInvalidLabelValueInAffinitySelector(oldPodSpec)
|
||||
opts.AllowInvalidLabelValueInRequiredNodeAffinity = hasInvalidLabelValueInRequiredNodeAffinity(oldPodSpec)
|
||||
// if old spec has invalid labelSelector in topologySpreadConstraint, we must allow it
|
||||
opts.AllowInvalidTopologySpreadConstraintLabelSelector = hasInvalidTopologySpreadConstraintLabelSelector(oldPodSpec)
|
||||
// if old spec has an invalid projected token volume path, we must allow it
|
||||
@ -1271,6 +1273,16 @@ func IsRestartableInitContainer(initContainer *api.Container) bool {
|
||||
return *initContainer.RestartPolicy == api.ContainerRestartPolicyAlways
|
||||
}
|
||||
|
||||
func hasInvalidLabelValueInRequiredNodeAffinity(spec *api.PodSpec) bool {
|
||||
if spec == nil ||
|
||||
spec.Affinity == nil ||
|
||||
spec.Affinity.NodeAffinity == nil ||
|
||||
spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution == nil {
|
||||
return false
|
||||
}
|
||||
return helper.HasInvalidLabelValueInNodeSelectorTerms(spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms)
|
||||
}
|
||||
|
||||
func MarkPodProposedForResize(oldPod, newPod *api.Pod) {
|
||||
if len(newPod.Spec.Containers) != len(oldPod.Spec.Containers) {
|
||||
// Update is invalid: ignore changes and let validation handle it
|
||||
|
@ -4204,3 +4204,77 @@ func TestValidateAllowSidecarResizePolicy(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateInvalidLabelValueInNodeSelectorOption(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
oldPodSpec *api.PodSpec
|
||||
wantOption bool
|
||||
}{
|
||||
{
|
||||
name: "Create",
|
||||
wantOption: false,
|
||||
},
|
||||
{
|
||||
name: "UpdateInvalidLabelSelector",
|
||||
oldPodSpec: &api.PodSpec{
|
||||
Affinity: &api.Affinity{
|
||||
NodeAffinity: &api.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{
|
||||
NodeSelectorTerms: []api.NodeSelectorTerm{{
|
||||
MatchExpressions: []api.NodeSelectorRequirement{{
|
||||
Key: "foo",
|
||||
Operator: api.NodeSelectorOpIn,
|
||||
Values: []string{"-1"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantOption: true,
|
||||
},
|
||||
{
|
||||
name: "UpdateValidLabelSelector",
|
||||
oldPodSpec: &api.PodSpec{
|
||||
Affinity: &api.Affinity{
|
||||
NodeAffinity: &api.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{
|
||||
NodeSelectorTerms: []api.NodeSelectorTerm{{
|
||||
MatchExpressions: []api.NodeSelectorRequirement{{
|
||||
Key: "foo",
|
||||
Operator: api.NodeSelectorOpIn,
|
||||
Values: []string{"bar"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantOption: false,
|
||||
},
|
||||
{
|
||||
name: "UpdateEmptyLabelSelector",
|
||||
oldPodSpec: &api.PodSpec{
|
||||
Affinity: &api.Affinity{
|
||||
NodeAffinity: &api.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{
|
||||
NodeSelectorTerms: []api.NodeSelectorTerm{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantOption: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Pod meta doesn't impact the outcome.
|
||||
gotOptions := GetValidationOptionsFromPodSpecAndMeta(&api.PodSpec{}, tc.oldPodSpec, nil, nil)
|
||||
if tc.wantOption != gotOptions.AllowInvalidLabelValueInRequiredNodeAffinity {
|
||||
t.Errorf("Got AllowInvalidLabelValueInRequiredNodeAffinity=%t, want %t", gotOptions.AllowInvalidLabelValueInRequiredNodeAffinity, tc.wantOption)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -96,12 +96,12 @@ func warningsForPodSpecAndMeta(fieldPath *field.Path, podSpec *api.PodSpec, meta
|
||||
if n.RequiredDuringSchedulingIgnoredDuringExecution != nil {
|
||||
termFldPath := fieldPath.Child("spec", "affinity", "nodeAffinity", "requiredDuringSchedulingIgnoredDuringExecution", "nodeSelectorTerms")
|
||||
for i, term := range n.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms {
|
||||
warnings = append(warnings, nodeapi.GetWarningsForNodeSelectorTerm(term, termFldPath.Index(i))...)
|
||||
warnings = append(warnings, nodeapi.GetWarningsForNodeSelectorTerm(term, false, termFldPath.Index(i))...)
|
||||
}
|
||||
}
|
||||
preferredFldPath := fieldPath.Child("spec", "affinity", "nodeAffinity", "preferredDuringSchedulingIgnoredDuringExecution")
|
||||
for i, term := range n.PreferredDuringSchedulingIgnoredDuringExecution {
|
||||
warnings = append(warnings, nodeapi.GetWarningsForNodeSelectorTerm(term.Preference, preferredFldPath.Index(i).Child("preference"))...)
|
||||
warnings = append(warnings, nodeapi.GetWarningsForNodeSelectorTerm(term.Preference, true, preferredFldPath.Index(i).Child("preference"))...)
|
||||
}
|
||||
}
|
||||
for i, t := range podSpec.TopologySpreadConstraints {
|
||||
|
@ -1711,6 +1711,62 @@ func TestWarnings(t *testing.T) {
|
||||
`spec.containers[0].ports[1]: duplicate port name "test" with spec.initContainers[0].ports[0], services and probes that select ports by name will use spec.initContainers[0].ports[0]`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "creating pod with invalid value in nodeaffinity",
|
||||
template: &api.PodTemplateSpec{Spec: api.PodSpec{
|
||||
Affinity: &api.Affinity{NodeAffinity: &api.NodeAffinity{
|
||||
PreferredDuringSchedulingIgnoredDuringExecution: []api.PreferredSchedulingTerm{{
|
||||
Weight: 10,
|
||||
Preference: api.NodeSelectorTerm{
|
||||
MatchExpressions: []api.NodeSelectorRequirement{{
|
||||
Key: "foo",
|
||||
Operator: api.NodeSelectorOpIn,
|
||||
Values: []string{"-1"},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
}},
|
||||
}},
|
||||
expected: []string{
|
||||
`spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].preference.matchExpressions[0].values[0]: -1 is invalid, a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyValue', or 'my_value', or '12345', regex used for validation is '(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?')`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "updating pod with invalid value in nodeaffinity",
|
||||
template: &api.PodTemplateSpec{Spec: api.PodSpec{
|
||||
Affinity: &api.Affinity{NodeAffinity: &api.NodeAffinity{
|
||||
PreferredDuringSchedulingIgnoredDuringExecution: []api.PreferredSchedulingTerm{{
|
||||
Weight: 10,
|
||||
Preference: api.NodeSelectorTerm{
|
||||
MatchExpressions: []api.NodeSelectorRequirement{{
|
||||
Key: "foo",
|
||||
Operator: api.NodeSelectorOpIn,
|
||||
Values: []string{"-1"},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
}},
|
||||
SchedulingGates: []api.PodSchedulingGate{{Name: "foo"}},
|
||||
}},
|
||||
oldTemplate: &api.PodTemplateSpec{Spec: api.PodSpec{
|
||||
Affinity: &api.Affinity{NodeAffinity: &api.NodeAffinity{
|
||||
PreferredDuringSchedulingIgnoredDuringExecution: []api.PreferredSchedulingTerm{{
|
||||
Weight: 10,
|
||||
Preference: api.NodeSelectorTerm{
|
||||
MatchExpressions: []api.NodeSelectorRequirement{{
|
||||
Key: "foo",
|
||||
Operator: api.NodeSelectorOpIn,
|
||||
Values: []string{"bar"},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
}},
|
||||
SchedulingGates: []api.PodSchedulingGate{{Name: "foo"}},
|
||||
}},
|
||||
expected: []string{
|
||||
`spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].preference.matchExpressions[0].values[0]: -1 is invalid, a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyValue', or 'my_value', or '12345', regex used for validation is '(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?')`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
|
@ -500,3 +500,18 @@ func validFirstDigit(str string) bool {
|
||||
}
|
||||
return str[0] == '-' || (str[0] == '0' && str == "0") || (str[0] >= '1' && str[0] <= '9')
|
||||
}
|
||||
|
||||
// HasInvalidLabelValueInNodeSelectorTerms checks if there's an invalid label value
|
||||
// in one NodeSelectorTerm's MatchExpression values
|
||||
func HasInvalidLabelValueInNodeSelectorTerms(terms []core.NodeSelectorTerm) bool {
|
||||
for _, term := range terms {
|
||||
for _, expression := range term.MatchExpressions {
|
||||
for _, value := range expression.Values {
|
||||
if len(validation.IsValidLabelValue(value)) > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -369,3 +369,48 @@ func TestIsServiceIPSet(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasInvalidLabelValueInNodeSelectorTerms(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
terms []core.NodeSelectorTerm
|
||||
expect bool
|
||||
}{
|
||||
{
|
||||
name: "valid values",
|
||||
terms: []core.NodeSelectorTerm{{
|
||||
MatchExpressions: []core.NodeSelectorRequirement{{
|
||||
Key: "foo",
|
||||
Operator: core.NodeSelectorOpIn,
|
||||
Values: []string{"far"},
|
||||
}},
|
||||
}},
|
||||
expect: false,
|
||||
},
|
||||
{
|
||||
name: "empty terms",
|
||||
terms: []core.NodeSelectorTerm{},
|
||||
expect: false,
|
||||
},
|
||||
{
|
||||
name: "invalid label value",
|
||||
terms: []core.NodeSelectorTerm{{
|
||||
MatchExpressions: []core.NodeSelectorRequirement{{
|
||||
Key: "foo",
|
||||
Operator: core.NodeSelectorOpIn,
|
||||
Values: []string{"-1"},
|
||||
}},
|
||||
}},
|
||||
expect: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := HasInvalidLabelValueInNodeSelectorTerms(tc.terms)
|
||||
if got != tc.expect {
|
||||
t.Errorf("exepct %v, got %v", tc.expect, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1777,6 +1777,8 @@ var allowedTemplateObjectMetaFields = map[string]bool{
|
||||
type PersistentVolumeSpecValidationOptions struct {
|
||||
// Allow users to modify the class of volume attributes
|
||||
EnableVolumeAttributesClass bool
|
||||
// Allow invalid label-value in RequiredNodeSelector
|
||||
AllowInvalidLabelValueInRequiredNodeAffinity bool
|
||||
}
|
||||
|
||||
// ValidatePersistentVolumeName checks that a name is appropriate for a
|
||||
@ -1798,11 +1800,17 @@ var supportedVolumeModes = sets.New(core.PersistentVolumeBlock, core.PersistentV
|
||||
|
||||
func ValidationOptionsForPersistentVolume(pv, oldPv *core.PersistentVolume) PersistentVolumeSpecValidationOptions {
|
||||
opts := PersistentVolumeSpecValidationOptions{
|
||||
EnableVolumeAttributesClass: utilfeature.DefaultMutableFeatureGate.Enabled(features.VolumeAttributesClass),
|
||||
EnableVolumeAttributesClass: utilfeature.DefaultMutableFeatureGate.Enabled(features.VolumeAttributesClass),
|
||||
AllowInvalidLabelValueInRequiredNodeAffinity: false,
|
||||
}
|
||||
if oldPv != nil && oldPv.Spec.VolumeAttributesClassName != nil {
|
||||
opts.EnableVolumeAttributesClass = true
|
||||
}
|
||||
if oldPv != nil && oldPv.Spec.NodeAffinity != nil &&
|
||||
oldPv.Spec.NodeAffinity.Required != nil {
|
||||
terms := oldPv.Spec.NodeAffinity.Required.NodeSelectorTerms
|
||||
opts.AllowInvalidLabelValueInRequiredNodeAffinity = helper.HasInvalidLabelValueInNodeSelectorTerms(terms)
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
@ -1874,7 +1882,7 @@ func ValidatePersistentVolumeSpec(pvSpec *core.PersistentVolumeSpec, pvName stri
|
||||
if validateInlinePersistentVolumeSpec {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("nodeAffinity"), "may not be specified in the context of inline volumes"))
|
||||
} else {
|
||||
nodeAffinitySpecified, errs = validateVolumeNodeAffinity(pvSpec.NodeAffinity, fldPath.Child("nodeAffinity"))
|
||||
nodeAffinitySpecified, errs = validateVolumeNodeAffinity(pvSpec.NodeAffinity, opts, fldPath.Child("nodeAffinity"))
|
||||
allErrs = append(allErrs, errs...)
|
||||
}
|
||||
}
|
||||
@ -3865,7 +3873,7 @@ func validateAffinity(affinity *core.Affinity, opts PodValidationOptions, fldPat
|
||||
|
||||
if affinity != nil {
|
||||
if affinity.NodeAffinity != nil {
|
||||
allErrs = append(allErrs, validateNodeAffinity(affinity.NodeAffinity, fldPath.Child("nodeAffinity"))...)
|
||||
allErrs = append(allErrs, validateNodeAffinity(affinity.NodeAffinity, opts, fldPath.Child("nodeAffinity"))...)
|
||||
}
|
||||
if affinity.PodAffinity != nil {
|
||||
allErrs = append(allErrs, validatePodAffinity(affinity.PodAffinity, opts.AllowInvalidLabelValueInSelector, fldPath.Child("podAffinity"))...)
|
||||
@ -4053,6 +4061,8 @@ type PodValidationOptions struct {
|
||||
PodLevelResourcesEnabled bool
|
||||
// Allow sidecar containers resize policy for backward compatibility
|
||||
AllowSidecarResizePolicy bool
|
||||
// Allow invalid label-value in RequiredNodeSelector
|
||||
AllowInvalidLabelValueInRequiredNodeAffinity bool
|
||||
}
|
||||
|
||||
// validatePodMetadataAndSpec tests if required fields in the pod.metadata and pod.spec are set,
|
||||
@ -4730,14 +4740,14 @@ func validatePodAntiAffinity(podAntiAffinity *core.PodAntiAffinity, allowInvalid
|
||||
}
|
||||
|
||||
// validateNodeAffinity tests that the specified nodeAffinity fields have valid data
|
||||
func validateNodeAffinity(na *core.NodeAffinity, fldPath *field.Path) field.ErrorList {
|
||||
func validateNodeAffinity(na *core.NodeAffinity, opts PodValidationOptions, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
// TODO: Uncomment the next three lines once RequiredDuringSchedulingRequiredDuringExecution is implemented.
|
||||
// if na.RequiredDuringSchedulingRequiredDuringExecution != nil {
|
||||
// allErrs = append(allErrs, ValidateNodeSelector(na.RequiredDuringSchedulingRequiredDuringExecution, fldPath.Child("requiredDuringSchedulingRequiredDuringExecution"))...)
|
||||
// }
|
||||
if na.RequiredDuringSchedulingIgnoredDuringExecution != nil {
|
||||
allErrs = append(allErrs, ValidateNodeSelector(na.RequiredDuringSchedulingIgnoredDuringExecution, true /* TODO: opts.AllowInvalidLabelValueInRequiredNodeAffinity */, fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...)
|
||||
allErrs = append(allErrs, ValidateNodeSelector(na.RequiredDuringSchedulingIgnoredDuringExecution, opts.AllowInvalidLabelValueInRequiredNodeAffinity, fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...)
|
||||
}
|
||||
if len(na.PreferredDuringSchedulingIgnoredDuringExecution) > 0 {
|
||||
allErrs = append(allErrs, ValidatePreferredSchedulingTerms(na.PreferredDuringSchedulingIgnoredDuringExecution, fldPath.Child("preferredDuringSchedulingIgnoredDuringExecution"))...)
|
||||
@ -7783,7 +7793,7 @@ func ValidateLoadBalancerStatus(status *core.LoadBalancerStatus, fldPath *field.
|
||||
// returns:
|
||||
// - true if volumeNodeAffinity is set
|
||||
// - errorList if there are validation errors
|
||||
func validateVolumeNodeAffinity(nodeAffinity *core.VolumeNodeAffinity, fldPath *field.Path) (bool, field.ErrorList) {
|
||||
func validateVolumeNodeAffinity(nodeAffinity *core.VolumeNodeAffinity, opts PersistentVolumeSpecValidationOptions, fldPath *field.Path) (bool, field.ErrorList) {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
if nodeAffinity == nil {
|
||||
@ -7791,7 +7801,7 @@ func validateVolumeNodeAffinity(nodeAffinity *core.VolumeNodeAffinity, fldPath *
|
||||
}
|
||||
|
||||
if nodeAffinity.Required != nil {
|
||||
allErrs = append(allErrs, ValidateNodeSelector(nodeAffinity.Required, true /* TODO: opts.AllowInvalidLabelValueInRequiredNodeAffinity */, fldPath.Child("required"))...)
|
||||
allErrs = append(allErrs, ValidateNodeSelector(nodeAffinity.Required, opts.AllowInvalidLabelValueInRequiredNodeAffinity, fldPath.Child("required"))...)
|
||||
} else {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("required"), "must specify required node constraints"))
|
||||
}
|
||||
|
@ -704,9 +704,28 @@ func TestValidatePersistentVolumeSpec(t *testing.T) {
|
||||
MountOptions: []string{"soft", "read-write"},
|
||||
},
|
||||
},
|
||||
"invalid-node-affinity": {
|
||||
isExpectedFailure: true,
|
||||
isInlineSpec: false,
|
||||
pvSpec: &core.PersistentVolumeSpec{
|
||||
NodeAffinity: &core.VolumeNodeAffinity{
|
||||
Required: &core.NodeSelector{
|
||||
NodeSelectorTerms: []core.NodeSelectorTerm{{
|
||||
MatchExpressions: []core.NodeSelectorRequirement{{
|
||||
Key: "foo",
|
||||
Operator: core.NodeSelectorOpIn,
|
||||
Values: []string{"-1"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, scenario := range scenarios {
|
||||
opts := PersistentVolumeSpecValidationOptions{}
|
||||
opts := ValidationOptionsForPersistentVolume(&core.PersistentVolume{
|
||||
Spec: *scenario.pvSpec.DeepCopy(),
|
||||
}, nil)
|
||||
errs := ValidatePersistentVolumeSpec(scenario.pvSpec, "", scenario.isInlineSpec, field.NewPath("field"), opts)
|
||||
if len(errs) == 0 && scenario.isExpectedFailure {
|
||||
t.Errorf("Unexpected success for scenario: %s", name)
|
||||
@ -998,6 +1017,24 @@ func TestValidationOptionsForPersistentVolume(t *testing.T) {
|
||||
enableVolumeAttributesClass: false,
|
||||
expectValidationOpts: PersistentVolumeSpecValidationOptions{EnableVolumeAttributesClass: true},
|
||||
},
|
||||
"old pv has invalid label-value in node affinity": {
|
||||
oldPv: &core.PersistentVolume{
|
||||
Spec: core.PersistentVolumeSpec{
|
||||
NodeAffinity: &core.VolumeNodeAffinity{
|
||||
Required: &core.NodeSelector{
|
||||
NodeSelectorTerms: []core.NodeSelectorTerm{{
|
||||
MatchExpressions: []core.NodeSelectorRequirement{{
|
||||
Key: "foo",
|
||||
Operator: core.NodeSelectorOpIn,
|
||||
Values: []string{"-1"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectValidationOpts: PersistentVolumeSpecValidationOptions{AllowInvalidLabelValueInRequiredNodeAffinity: true},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range tests {
|
||||
@ -1464,6 +1501,11 @@ func TestValidateVolumeNodeAffinityUpdate(t *testing.T) {
|
||||
oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
|
||||
newPV: testVolumeWithNodeAffinity(nil),
|
||||
},
|
||||
"old affinity already has invalid label-value": {
|
||||
isExpectedFailure: false,
|
||||
oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceType, "-1")),
|
||||
newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceTypeStable, "-1")),
|
||||
},
|
||||
}
|
||||
|
||||
for name, scenario := range scenarios {
|
||||
@ -12242,6 +12284,23 @@ func TestValidatePod(t *testing.T) {
|
||||
podtest.SetAnnotations(map[string]string{core.PodDeletionCost: "+10"}),
|
||||
),
|
||||
},
|
||||
"invalid required node affinity, value of NodeSelectorRequirement should be a valid label value": {
|
||||
expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].values[0]: Invalid value: \"-1\": a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyValue', or 'my_value', or '12345', regex used for validation is '(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?')",
|
||||
spec: *podtest.MakePod("123",
|
||||
podtest.SetAffinity(&core.Affinity{
|
||||
NodeAffinity: &core.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
|
||||
NodeSelectorTerms: []core.NodeSelectorTerm{{
|
||||
MatchExpressions: []core.NodeSelectorRequirement{{
|
||||
Key: "foo",
|
||||
Operator: core.NodeSelectorOpIn,
|
||||
Values: []string{"-1"},
|
||||
}},
|
||||
}},
|
||||
}},
|
||||
}),
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
for k, v := range errorCases {
|
||||
@ -12316,6 +12375,7 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
old core.Pod
|
||||
new core.Pod
|
||||
err string
|
||||
opts PodValidationOptions
|
||||
}{
|
||||
{new: *podtest.MakePod(""), old: *podtest.MakePod(""), err: "", test: "nothing"}, {
|
||||
new: *podtest.MakePod("foo"),
|
||||
@ -13669,6 +13729,73 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
err: "pod updates may not change fields other than",
|
||||
test: "the podAntiAffinity cannot be updated on gated pods",
|
||||
},
|
||||
{
|
||||
old: *podtest.MakePod("foo",
|
||||
podtest.SetAffinity(&core.Affinity{
|
||||
NodeAffinity: &core.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
|
||||
NodeSelectorTerms: []core.NodeSelectorTerm{{
|
||||
MatchExpressions: []core.NodeSelectorRequirement{{
|
||||
Key: "expr",
|
||||
Operator: core.NodeSelectorOpIn,
|
||||
Values: []string{"-1"},
|
||||
}},
|
||||
}},
|
||||
}},
|
||||
}),
|
||||
podtest.SetSchedulingGates(core.PodSchedulingGate{Name: "baz"}),
|
||||
),
|
||||
new: *podtest.MakePod("foo",
|
||||
podtest.SetAffinity(&core.Affinity{
|
||||
NodeAffinity: &core.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
|
||||
NodeSelectorTerms: []core.NodeSelectorTerm{{
|
||||
MatchExpressions: []core.NodeSelectorRequirement{{
|
||||
Key: "expr",
|
||||
Operator: core.NodeSelectorOpIn,
|
||||
Values: []string{"-1"},
|
||||
}},
|
||||
}},
|
||||
}},
|
||||
}),
|
||||
),
|
||||
test: "allow update pod if old pod already has invalid label-value in node affinity",
|
||||
opts: PodValidationOptions{AllowInvalidLabelValueInRequiredNodeAffinity: true},
|
||||
},
|
||||
{
|
||||
old: *podtest.MakePod("foo",
|
||||
podtest.SetAffinity(&core.Affinity{
|
||||
NodeAffinity: &core.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
|
||||
NodeSelectorTerms: []core.NodeSelectorTerm{{
|
||||
MatchExpressions: []core.NodeSelectorRequirement{{
|
||||
Key: "expr",
|
||||
Operator: core.NodeSelectorOpIn,
|
||||
Values: []string{"bar"},
|
||||
}},
|
||||
}},
|
||||
}},
|
||||
}),
|
||||
podtest.SetSchedulingGates(core.PodSchedulingGate{Name: "baz"}),
|
||||
),
|
||||
new: *podtest.MakePod("foo",
|
||||
podtest.SetAffinity(&core.Affinity{
|
||||
NodeAffinity: &core.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
|
||||
NodeSelectorTerms: []core.NodeSelectorTerm{{
|
||||
MatchExpressions: []core.NodeSelectorRequirement{{
|
||||
Key: "expr",
|
||||
Operator: core.NodeSelectorOpIn,
|
||||
Values: []string{"-1"},
|
||||
}},
|
||||
}},
|
||||
}},
|
||||
}),
|
||||
podtest.SetSchedulingGates(core.PodSchedulingGate{Name: "baz"}),
|
||||
),
|
||||
err: `a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '.'`,
|
||||
test: "not allow update node affinity to an invalid label-value",
|
||||
},
|
||||
{
|
||||
new: *podtest.MakePod("pod",
|
||||
podtest.SetContainers(podtest.MakeContainer("container",
|
||||
@ -13730,7 +13857,7 @@ func TestValidatePodUpdate(t *testing.T) {
|
||||
test.old.Spec.RestartPolicy = "Always"
|
||||
}
|
||||
|
||||
errs := ValidatePodUpdate(&test.new, &test.old, PodValidationOptions{})
|
||||
errs := ValidatePodUpdate(&test.new, &test.old, test.opts)
|
||||
if test.err == "" {
|
||||
if len(errs) != 0 {
|
||||
t.Errorf("unexpected invalid: %s (%+v)\nA: %+v\nB: %+v", test.test, errs, test.new, test.old)
|
||||
|
Loading…
Reference in New Issue
Block a user