Merge pull request #111441 from denkensk/respect-topology

Respect PodTopologySpread after rolling upgrades
This commit is contained in:
Kubernetes Prow Robot 2022-07-30 01:40:42 -07:00 committed by GitHub
commit 3902a53419
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
93 changed files with 1975 additions and 1102 deletions

View File

@ -9652,6 +9652,14 @@
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector",
"description": "LabelSelector is used to find matching pods. Pods that match this label selector are counted to determine the number of pods in their corresponding topology domain." "description": "LabelSelector is used to find matching pods. Pods that match this label selector are counted to determine the number of pods in their corresponding topology domain."
}, },
"matchLabelKeys": {
"description": "MatchLabelKeys is a set of pod label keys to select the pods over which spreading will be calculated. The keys are used to lookup values from the incoming pod labels, those key-value labels are ANDed with labelSelector to select the group of existing pods over which spreading will be calculated for the incoming pod. Keys that don't exist in the incoming pod labels will be ignored. A null or empty list means only match against labelSelector.",
"items": {
"type": "string"
},
"type": "array",
"x-kubernetes-list-type": "atomic"
},
"maxSkew": { "maxSkew": {
"description": "MaxSkew describes the degree to which pods may be unevenly distributed. When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference between the number of matching pods in the target topology and the global minimum. The global minimum is the minimum number of matching pods in an eligible domain or zero if the number of eligible domains is less than MinDomains. For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same labelSelector spread as 2/2/1: In this case, the global minimum is 1. | zone1 | zone2 | zone3 | | P P | P P | P | - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) violate MaxSkew(1). - if MaxSkew is 2, incoming pod can be scheduled onto any zone. When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence to topologies that satisfy it. It's a required field. Default value is 1 and 0 is not allowed.", "description": "MaxSkew describes the degree to which pods may be unevenly distributed. When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference between the number of matching pods in the target topology and the global minimum. The global minimum is the minimum number of matching pods in an eligible domain or zero if the number of eligible domains is less than MinDomains. For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same labelSelector spread as 2/2/1: In this case, the global minimum is 1. | zone1 | zone2 | zone3 | | P P | P P | P | - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) violate MaxSkew(1). - if MaxSkew is 2, incoming pod can be scheduled onto any zone. When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence to topologies that satisfy it. It's a required field. Default value is 1 and 0 is not allowed.",
"format": "int32", "format": "int32",

View File

@ -7251,6 +7251,15 @@
], ],
"description": "LabelSelector is used to find matching pods. Pods that match this label selector are counted to determine the number of pods in their corresponding topology domain." "description": "LabelSelector is used to find matching pods. Pods that match this label selector are counted to determine the number of pods in their corresponding topology domain."
}, },
"matchLabelKeys": {
"description": "MatchLabelKeys is a set of pod label keys to select the pods over which spreading will be calculated. The keys are used to lookup values from the incoming pod labels, those key-value labels are ANDed with labelSelector to select the group of existing pods over which spreading will be calculated for the incoming pod. Keys that don't exist in the incoming pod labels will be ignored. A null or empty list means only match against labelSelector.",
"items": {
"default": "",
"type": "string"
},
"type": "array",
"x-kubernetes-list-type": "atomic"
},
"maxSkew": { "maxSkew": {
"default": 0, "default": 0,
"description": "MaxSkew describes the degree to which pods may be unevenly distributed. When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference between the number of matching pods in the target topology and the global minimum. The global minimum is the minimum number of matching pods in an eligible domain or zero if the number of eligible domains is less than MinDomains. For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same labelSelector spread as 2/2/1: In this case, the global minimum is 1. | zone1 | zone2 | zone3 | | P P | P P | P | - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) violate MaxSkew(1). - if MaxSkew is 2, incoming pod can be scheduled onto any zone. When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence to topologies that satisfy it. It's a required field. Default value is 1 and 0 is not allowed.", "description": "MaxSkew describes the degree to which pods may be unevenly distributed. When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference between the number of matching pods in the target topology and the global minimum. The global minimum is the minimum number of matching pods in an eligible domain or zero if the number of eligible domains is less than MinDomains. For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same labelSelector spread as 2/2/1: In this case, the global minimum is 1. | zone1 | zone2 | zone3 | | P P | P P | P | - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) violate MaxSkew(1). - if MaxSkew is 2, incoming pod can be scheduled onto any zone. When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence to topologies that satisfy it. It's a required field. Default value is 1 and 0 is not allowed.",

View File

@ -4348,6 +4348,15 @@
], ],
"description": "LabelSelector is used to find matching pods. Pods that match this label selector are counted to determine the number of pods in their corresponding topology domain." "description": "LabelSelector is used to find matching pods. Pods that match this label selector are counted to determine the number of pods in their corresponding topology domain."
}, },
"matchLabelKeys": {
"description": "MatchLabelKeys is a set of pod label keys to select the pods over which spreading will be calculated. The keys are used to lookup values from the incoming pod labels, those key-value labels are ANDed with labelSelector to select the group of existing pods over which spreading will be calculated for the incoming pod. Keys that don't exist in the incoming pod labels will be ignored. A null or empty list means only match against labelSelector.",
"items": {
"default": "",
"type": "string"
},
"type": "array",
"x-kubernetes-list-type": "atomic"
},
"maxSkew": { "maxSkew": {
"default": 0, "default": 0,
"description": "MaxSkew describes the degree to which pods may be unevenly distributed. When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference between the number of matching pods in the target topology and the global minimum. The global minimum is the minimum number of matching pods in an eligible domain or zero if the number of eligible domains is less than MinDomains. For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same labelSelector spread as 2/2/1: In this case, the global minimum is 1. | zone1 | zone2 | zone3 | | P P | P P | P | - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) violate MaxSkew(1). - if MaxSkew is 2, incoming pod can be scheduled onto any zone. When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence to topologies that satisfy it. It's a required field. Default value is 1 and 0 is not allowed.", "description": "MaxSkew describes the degree to which pods may be unevenly distributed. When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference between the number of matching pods in the target topology and the global minimum. The global minimum is the minimum number of matching pods in an eligible domain or zero if the number of eligible domains is less than MinDomains. For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same labelSelector spread as 2/2/1: In this case, the global minimum is 1. | zone1 | zone2 | zone3 | | P P | P P | P | - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) violate MaxSkew(1). - if MaxSkew is 2, incoming pod can be scheduled onto any zone. When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence to topologies that satisfy it. It's a required field. Default value is 1 and 0 is not allowed.",

View File

@ -3427,6 +3427,15 @@
], ],
"description": "LabelSelector is used to find matching pods. Pods that match this label selector are counted to determine the number of pods in their corresponding topology domain." "description": "LabelSelector is used to find matching pods. Pods that match this label selector are counted to determine the number of pods in their corresponding topology domain."
}, },
"matchLabelKeys": {
"description": "MatchLabelKeys is a set of pod label keys to select the pods over which spreading will be calculated. The keys are used to lookup values from the incoming pod labels, those key-value labels are ANDed with labelSelector to select the group of existing pods over which spreading will be calculated for the incoming pod. Keys that don't exist in the incoming pod labels will be ignored. A null or empty list means only match against labelSelector.",
"items": {
"default": "",
"type": "string"
},
"type": "array",
"x-kubernetes-list-type": "atomic"
},
"maxSkew": { "maxSkew": {
"default": 0, "default": 0,
"description": "MaxSkew describes the degree to which pods may be unevenly distributed. When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference between the number of matching pods in the target topology and the global minimum. The global minimum is the minimum number of matching pods in an eligible domain or zero if the number of eligible domains is less than MinDomains. For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same labelSelector spread as 2/2/1: In this case, the global minimum is 1. | zone1 | zone2 | zone3 | | P P | P P | P | - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) violate MaxSkew(1). - if MaxSkew is 2, incoming pod can be scheduled onto any zone. When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence to topologies that satisfy it. It's a required field. Default value is 1 and 0 is not allowed.", "description": "MaxSkew describes the degree to which pods may be unevenly distributed. When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference between the number of matching pods in the target topology and the global minimum. The global minimum is the minimum number of matching pods in an eligible domain or zero if the number of eligible domains is less than MinDomains. For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same labelSelector spread as 2/2/1: In this case, the global minimum is 1. | zone1 | zone2 | zone3 | | P P | P P | P | - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) violate MaxSkew(1). - if MaxSkew is 2, incoming pod can be scheduled onto any zone. When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence to topologies that satisfy it. It's a required field. Default value is 1 and 0 is not allowed.",

View File

@ -545,6 +545,7 @@ func dropDisabledFields(
dropDisabledTopologySpreadConstraintsFields(podSpec, oldPodSpec) dropDisabledTopologySpreadConstraintsFields(podSpec, oldPodSpec)
dropDisabledNodeInclusionPolicyFields(podSpec, oldPodSpec) dropDisabledNodeInclusionPolicyFields(podSpec, oldPodSpec)
dropDisabledMatchLabelKeysField(podSpec, oldPodSpec)
} }
// dropDisabledTopologySpreadConstraintsFields removes disabled fields from PodSpec related // dropDisabledTopologySpreadConstraintsFields removes disabled fields from PodSpec related
@ -618,6 +619,31 @@ func dropDisabledNodeInclusionPolicyFields(podSpec, oldPodSpec *api.PodSpec) {
} }
} }
// dropDisabledMatchLabelKeysField removes disabled fields from PodSpec related
// to MatchLabelKeys only if it is not already used by the old spec.
func dropDisabledMatchLabelKeysField(podSpec, oldPodSpec *api.PodSpec) {
if !utilfeature.DefaultFeatureGate.Enabled(features.MatchLabelKeysInPodTopologySpread) && !matchLabelKeysInUse(oldPodSpec) {
for i := range podSpec.TopologySpreadConstraints {
podSpec.TopologySpreadConstraints[i].MatchLabelKeys = nil
}
}
}
// matchLabelKeysInUse returns true if the pod spec is non-nil
// and has MatchLabelKeys field set in TopologySpreadConstraints.
func matchLabelKeysInUse(podSpec *api.PodSpec) bool {
if podSpec == nil {
return false
}
for _, c := range podSpec.TopologySpreadConstraints {
if len(c.MatchLabelKeys) > 0 {
return true
}
}
return false
}
// nodeAffinityPolicyInUse returns true if the pod spec is non-nil and has NodeAffinityPolicy field set // nodeAffinityPolicyInUse returns true if the pod spec is non-nil and has NodeAffinityPolicy field set
// in TopologySpreadConstraints // in TopologySpreadConstraints
func nodeAffinityPolicyInUse(podSpec *api.PodSpec) bool { func nodeAffinityPolicyInUse(podSpec *api.PodSpec) bool {

View File

@ -1814,3 +1814,138 @@ func TestDropNodeInclusionPolicyFields(t *testing.T) {
}) })
} }
} }
func TestDropDisabledMatchLabelKeysField(t *testing.T) {
tests := []struct {
name string
enabled bool
podSpec *api.PodSpec
oldPodSpec *api.PodSpec
wantPodSpec *api.PodSpec
}{
{
name: "feature disabled, both pods don't use MatchLabelKeys fields",
enabled: false,
oldPodSpec: &api.PodSpec{
TopologySpreadConstraints: []api.TopologySpreadConstraint{},
},
podSpec: &api.PodSpec{
TopologySpreadConstraints: []api.TopologySpreadConstraint{},
},
wantPodSpec: &api.PodSpec{
TopologySpreadConstraints: []api.TopologySpreadConstraint{},
},
},
{
name: "feature disabled, only old pod uses MatchLabelKeys field",
enabled: false,
oldPodSpec: &api.PodSpec{
TopologySpreadConstraints: []api.TopologySpreadConstraint{
{MatchLabelKeys: []string{"foo"}},
},
},
podSpec: &api.PodSpec{
TopologySpreadConstraints: []api.TopologySpreadConstraint{},
},
wantPodSpec: &api.PodSpec{
TopologySpreadConstraints: []api.TopologySpreadConstraint{},
},
},
{
name: "feature disabled, only current pod uses MatchLabelKeys field",
enabled: false,
oldPodSpec: &api.PodSpec{
TopologySpreadConstraints: []api.TopologySpreadConstraint{},
},
podSpec: &api.PodSpec{
TopologySpreadConstraints: []api.TopologySpreadConstraint{
{MatchLabelKeys: []string{"foo"}},
},
},
wantPodSpec: &api.PodSpec{
TopologySpreadConstraints: []api.TopologySpreadConstraint{{}},
},
},
{
name: "feature disabled, both pods use MatchLabelKeys fields",
enabled: false,
oldPodSpec: &api.PodSpec{
TopologySpreadConstraints: []api.TopologySpreadConstraint{
{MatchLabelKeys: []string{"foo"}},
},
},
podSpec: &api.PodSpec{
TopologySpreadConstraints: []api.TopologySpreadConstraint{
{MatchLabelKeys: []string{"foo"}},
},
},
wantPodSpec: &api.PodSpec{
TopologySpreadConstraints: []api.TopologySpreadConstraint{
{MatchLabelKeys: []string{"foo"}},
},
},
},
{
name: "feature enabled, only old pod uses MatchLabelKeys field",
enabled: true,
oldPodSpec: &api.PodSpec{
TopologySpreadConstraints: []api.TopologySpreadConstraint{
{MatchLabelKeys: []string{"foo"}},
},
},
podSpec: &api.PodSpec{
TopologySpreadConstraints: []api.TopologySpreadConstraint{},
},
wantPodSpec: &api.PodSpec{
TopologySpreadConstraints: []api.TopologySpreadConstraint{},
},
},
{
name: "feature enabled, only current pod uses MatchLabelKeys field",
enabled: true,
oldPodSpec: &api.PodSpec{
TopologySpreadConstraints: []api.TopologySpreadConstraint{},
},
podSpec: &api.PodSpec{
TopologySpreadConstraints: []api.TopologySpreadConstraint{
{MatchLabelKeys: []string{"foo"}},
},
},
wantPodSpec: &api.PodSpec{
TopologySpreadConstraints: []api.TopologySpreadConstraint{
{MatchLabelKeys: []string{"foo"}},
},
},
},
{
name: "feature enabled, both pods use MatchLabelKeys fields",
enabled: false,
oldPodSpec: &api.PodSpec{
TopologySpreadConstraints: []api.TopologySpreadConstraint{
{MatchLabelKeys: []string{"foo"}},
},
},
podSpec: &api.PodSpec{
TopologySpreadConstraints: []api.TopologySpreadConstraint{
{MatchLabelKeys: []string{"foo"}},
},
},
wantPodSpec: &api.PodSpec{
TopologySpreadConstraints: []api.TopologySpreadConstraint{
{MatchLabelKeys: []string{"foo"}},
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MatchLabelKeysInPodTopologySpread, test.enabled)()
dropDisabledFields(test.podSpec, nil, test.oldPodSpec, nil)
if diff := cmp.Diff(test.wantPodSpec, test.podSpec); diff != "" {
t.Errorf("unexpected pod spec (-want, +got):\n%s", diff)
}
})
}
}

View File

@ -5733,6 +5733,15 @@ type TopologySpreadConstraint struct {
// This is a alpha-level feature enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. // This is a alpha-level feature enabled by the NodeInclusionPolicyInPodTopologySpread feature flag.
// +optional // +optional
NodeTaintsPolicy *NodeInclusionPolicy NodeTaintsPolicy *NodeInclusionPolicy
// MatchLabelKeys is a set of pod label keys to select the pods over which
// spreading will be calculated. The keys are used to lookup values from the
// incoming pod labels, those key-value labels are ANDed with labelSelector
// to select the group of existing pods over which spreading will be calculated
// for the incoming pod. Keys that don't exist in the incoming pod labels will
// be ignored. A null or empty list means only match against labelSelector.
// +listType=atomic
// +optional
MatchLabelKeys []string
} }
// These are the built-in errors for PortStatus. // These are the built-in errors for PortStatus.

View File

@ -7999,6 +7999,7 @@ func autoConvert_v1_TopologySpreadConstraint_To_core_TopologySpreadConstraint(in
out.MinDomains = (*int32)(unsafe.Pointer(in.MinDomains)) out.MinDomains = (*int32)(unsafe.Pointer(in.MinDomains))
out.NodeAffinityPolicy = (*core.NodeInclusionPolicy)(unsafe.Pointer(in.NodeAffinityPolicy)) out.NodeAffinityPolicy = (*core.NodeInclusionPolicy)(unsafe.Pointer(in.NodeAffinityPolicy))
out.NodeTaintsPolicy = (*core.NodeInclusionPolicy)(unsafe.Pointer(in.NodeTaintsPolicy)) out.NodeTaintsPolicy = (*core.NodeInclusionPolicy)(unsafe.Pointer(in.NodeTaintsPolicy))
out.MatchLabelKeys = *(*[]string)(unsafe.Pointer(&in.MatchLabelKeys))
return nil return nil
} }
@ -8015,6 +8016,7 @@ func autoConvert_core_TopologySpreadConstraint_To_v1_TopologySpreadConstraint(in
out.MinDomains = (*int32)(unsafe.Pointer(in.MinDomains)) out.MinDomains = (*int32)(unsafe.Pointer(in.MinDomains))
out.NodeAffinityPolicy = (*v1.NodeInclusionPolicy)(unsafe.Pointer(in.NodeAffinityPolicy)) out.NodeAffinityPolicy = (*v1.NodeInclusionPolicy)(unsafe.Pointer(in.NodeAffinityPolicy))
out.NodeTaintsPolicy = (*v1.NodeInclusionPolicy)(unsafe.Pointer(in.NodeTaintsPolicy)) out.NodeTaintsPolicy = (*v1.NodeInclusionPolicy)(unsafe.Pointer(in.NodeTaintsPolicy))
out.MatchLabelKeys = *(*[]string)(unsafe.Pointer(&in.MatchLabelKeys))
return nil return nil
} }

View File

@ -6520,6 +6520,7 @@ func validateTopologySpreadConstraints(constraints []core.TopologySpreadConstrai
if err := validateNodeInclusionPolicy(subFldPath.Child("nodeTaintsPolicy"), constraint.NodeTaintsPolicy); err != nil { if err := validateNodeInclusionPolicy(subFldPath.Child("nodeTaintsPolicy"), constraint.NodeTaintsPolicy); err != nil {
allErrs = append(allErrs, err) allErrs = append(allErrs, err)
} }
allErrs = append(allErrs, validateMatchLabelKeys(subFldPath.Child("matchLabelKeys"), constraint.MatchLabelKeys, constraint.LabelSelector)...)
} }
return allErrs return allErrs
@ -6593,6 +6594,33 @@ func validateNodeInclusionPolicy(fldPath *field.Path, policy *core.NodeInclusion
return nil return nil
} }
// validateMatchLabelKeys tests that the elements are a valid label name and are not already included in labelSelector.
func validateMatchLabelKeys(fldPath *field.Path, matchLabelKeys []string, labelSelector *metav1.LabelSelector) field.ErrorList {
if len(matchLabelKeys) == 0 {
return nil
}
labelSelectorKeys := sets.String{}
if labelSelector != nil {
for key := range labelSelector.MatchLabels {
labelSelectorKeys.Insert(key)
}
for _, matchExpression := range labelSelector.MatchExpressions {
labelSelectorKeys.Insert(matchExpression.Key)
}
}
allErrs := field.ErrorList{}
for i, key := range matchLabelKeys {
allErrs = append(allErrs, unversionedvalidation.ValidateLabelName(key, fldPath.Index(i))...)
if labelSelectorKeys.Has(key) {
allErrs = append(allErrs, field.Invalid(fldPath.Index(i), key, "exists in both matchLabelKeys and labelSelector"))
}
}
return allErrs
}
// ValidateServiceClusterIPsRelatedFields validates .spec.ClusterIPs,, // ValidateServiceClusterIPsRelatedFields validates .spec.ClusterIPs,,
// .spec.IPFamilies, .spec.ipFamilyPolicy. This is exported because it is used // .spec.IPFamilies, .spec.ipFamilyPolicy. This is exported because it is used
// during IP init and allocation. // during IP init and allocation.

View File

@ -19539,6 +19539,7 @@ func TestValidateTopologySpreadConstraints(t *testing.T) {
fieldPathTopologyKey := subFldPath0.Child("topologyKey") fieldPathTopologyKey := subFldPath0.Child("topologyKey")
fieldPathWhenUnsatisfiable := subFldPath0.Child("whenUnsatisfiable") fieldPathWhenUnsatisfiable := subFldPath0.Child("whenUnsatisfiable")
fieldPathTopologyKeyAndWhenUnsatisfiable := subFldPath0.Child("{topologyKey, whenUnsatisfiable}") fieldPathTopologyKeyAndWhenUnsatisfiable := subFldPath0.Child("{topologyKey, whenUnsatisfiable}")
fieldPathMatchLabelKeys := subFldPath0.Child("matchLabelKeys")
nodeAffinityField := subFldPath0.Child("nodeAffinityPolicy") nodeAffinityField := subFldPath0.Child("nodeAffinityPolicy")
nodeTaintsField := subFldPath0.Child("nodeTaintsPolicy") nodeTaintsField := subFldPath0.Child("nodeTaintsPolicy")
unknown := core.NodeInclusionPolicy("Unknown") unknown := core.NodeInclusionPolicy("Unknown")
@ -19717,6 +19718,39 @@ func TestValidateTopologySpreadConstraints(t *testing.T) {
field.NotSupported(nodeTaintsField, &unknown, supportedPodTopologySpreadNodePolicies.List()), field.NotSupported(nodeTaintsField, &unknown, supportedPodTopologySpreadNodePolicies.List()),
}, },
}, },
{
name: "key in MatchLabelKeys isn't correctly defined",
constraints: []core.TopologySpreadConstraint{
{
MaxSkew: 1,
TopologyKey: "k8s.io/zone",
WhenUnsatisfiable: core.DoNotSchedule,
MatchLabelKeys: []string{"/simple"},
},
},
wantFieldErrors: []*field.Error{field.Invalid(fieldPathMatchLabelKeys.Index(0), "/simple", "prefix part must be non-empty")},
},
{
name: "key exists in both matchLabelKeys and labelSelector",
constraints: []core.TopologySpreadConstraint{
{
MaxSkew: 1,
TopologyKey: "k8s.io/zone",
WhenUnsatisfiable: core.DoNotSchedule,
MatchLabelKeys: []string{"foo"},
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "foo",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"value1", "value2"},
},
},
},
},
},
wantFieldErrors: []*field.Error{field.Invalid(fieldPathMatchLabelKeys.Index(0), "foo", "exists in both matchLabelKeys and labelSelector")},
},
} }
for _, tc := range testCases { for _, tc := range testCases {

View File

@ -5649,6 +5649,11 @@ func (in *TopologySpreadConstraint) DeepCopyInto(out *TopologySpreadConstraint)
*out = new(NodeInclusionPolicy) *out = new(NodeInclusionPolicy)
**out = **in **out = **in
} }
if in.MatchLabelKeys != nil {
in, out := &in.MatchLabelKeys, &out.MatchLabelKeys
*out = make([]string, len(*in))
copy(*out, *in)
}
return return
} }

View File

@ -518,6 +518,13 @@ const (
// Enables scaling down replicas via logarithmic comparison of creation/ready timestamps // Enables scaling down replicas via logarithmic comparison of creation/ready timestamps
LogarithmicScaleDown featuregate.Feature = "LogarithmicScaleDown" LogarithmicScaleDown featuregate.Feature = "LogarithmicScaleDown"
// owner: @denkensk
// kep: http://kep.k8s.io/3243
// alpha: v1.25
//
// Enable MatchLabelKeys in PodTopologySpread.
MatchLabelKeysInPodTopologySpread featuregate.Feature = "MatchLabelKeysInPodTopologySpread"
// owner: @krmayankk // owner: @krmayankk
// alpha: v1.24 // alpha: v1.24
// //
@ -951,6 +958,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
LogarithmicScaleDown: {Default: true, PreRelease: featuregate.Beta}, LogarithmicScaleDown: {Default: true, PreRelease: featuregate.Beta},
MatchLabelKeysInPodTopologySpread: {Default: false, PreRelease: featuregate.Alpha},
MaxUnavailableStatefulSet: {Default: false, PreRelease: featuregate.Alpha}, MaxUnavailableStatefulSet: {Default: false, PreRelease: featuregate.Alpha},
MemoryManager: {Default: true, PreRelease: featuregate.Beta}, MemoryManager: {Default: true, PreRelease: featuregate.Beta},

View File

@ -25478,6 +25478,26 @@ func schema_k8sio_api_core_v1_TopologySpreadConstraint(ref common.ReferenceCallb
Format: "", Format: "",
}, },
}, },
"matchLabelKeys": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-type": "atomic",
},
},
SchemaProps: spec.SchemaProps{
Description: "MatchLabelKeys is a set of pod label keys to select the pods over which spreading will be calculated. The keys are used to lookup values from the incoming pod labels, those key-value labels are ANDed with labelSelector to select the group of existing pods over which spreading will be calculated for the incoming pod. Keys that don't exist in the incoming pod labels will be ignored. A null or empty list means only match against labelSelector.",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
}, },
Required: []string{"maxSkew", "topologyKey", "whenUnsatisfiable"}, Required: []string{"maxSkew", "topologyKey", "whenUnsatisfiable"},
}, },

View File

@ -644,8 +644,8 @@ func TestDryRunPreemption(t *testing.T) {
nodeNames: []string{"node-a/zone1", "node-b/zone1", "node-x/zone2"}, nodeNames: []string{"node-a/zone1", "node-b/zone1", "node-x/zone2"},
testPods: []*v1.Pod{ testPods: []*v1.Pod{
st.MakePod().Name("p").UID("p").Label("foo", "").Priority(highPriority). st.MakePod().Name("p").UID("p").Label("foo", "").Priority(highPriority).
SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
SpreadConstraint(1, "hostname", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil). SpreadConstraint(1, "hostname", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
Obj(), Obj(),
}, },
initPods: []*v1.Pod{ initPods: []*v1.Pod{
@ -1486,8 +1486,8 @@ func TestPreempt(t *testing.T) {
{ {
name: "preemption for topology spread constraints", name: "preemption for topology spread constraints",
pod: st.MakePod().Name("p").UID("p").Namespace(v1.NamespaceDefault).Label("foo", "").Priority(highPriority). pod: st.MakePod().Name("p").UID("p").Namespace(v1.NamespaceDefault).Label("foo", "").Priority(highPriority).
SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
SpreadConstraint(1, "hostname", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil). SpreadConstraint(1, "hostname", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
Obj(), Obj(),
pods: []*v1.Pod{ pods: []*v1.Pod{
st.MakePod().Name("p-a1").UID("p-a1").Namespace(v1.NamespaceDefault).Node("node-a").Label("foo", "").Priority(highPriority).Obj(), st.MakePod().Name("p-a1").UID("p-a1").Namespace(v1.NamespaceDefault).Node("node-a").Label("foo", "").Priority(highPriority).Obj(),

View File

@ -24,4 +24,5 @@ type Features struct {
EnableVolumeCapacityPriority bool EnableVolumeCapacityPriority bool
EnableMinDomainsInPodTopologySpread bool EnableMinDomainsInPodTopologySpread bool
EnableNodeInclusionPolicyInPodTopologySpread bool EnableNodeInclusionPolicyInPodTopologySpread bool
EnableMatchLabelKeysInPodTopologySpread bool
} }

View File

@ -63,7 +63,7 @@ func (tsc *topologySpreadConstraint) matchNodeInclusionPolicies(pod *v1.Pod, nod
// .DefaultConstraints and the selectors from the services, replication // .DefaultConstraints and the selectors from the services, replication
// controllers, replica sets and stateful sets that match the pod. // controllers, replica sets and stateful sets that match the pod.
func (pl *PodTopologySpread) buildDefaultConstraints(p *v1.Pod, action v1.UnsatisfiableConstraintAction) ([]topologySpreadConstraint, error) { func (pl *PodTopologySpread) buildDefaultConstraints(p *v1.Pod, action v1.UnsatisfiableConstraintAction) ([]topologySpreadConstraint, error) {
constraints, err := filterTopologySpreadConstraints(pl.defaultConstraints, action, pl.enableMinDomainsInPodTopologySpread, pl.enableNodeInclusionPolicyInPodTopologySpread) constraints, err := pl.filterTopologySpreadConstraints(pl.defaultConstraints, p.Labels, action)
if err != nil || len(constraints) == 0 { if err != nil || len(constraints) == 0 {
return nil, err return nil, err
} }
@ -87,7 +87,7 @@ func nodeLabelsMatchSpreadConstraints(nodeLabels map[string]string, constraints
return true return true
} }
func filterTopologySpreadConstraints(constraints []v1.TopologySpreadConstraint, action v1.UnsatisfiableConstraintAction, enableMinDomainsInPodTopologySpread, enableNodeInclusionPolicyInPodTopologySpread bool) ([]topologySpreadConstraint, error) { func (pl *PodTopologySpread) filterTopologySpreadConstraints(constraints []v1.TopologySpreadConstraint, podLabels map[string]string, action v1.UnsatisfiableConstraintAction) ([]topologySpreadConstraint, error) {
var result []topologySpreadConstraint var result []topologySpreadConstraint
for _, c := range constraints { for _, c := range constraints {
if c.WhenUnsatisfiable == action { if c.WhenUnsatisfiable == action {
@ -95,6 +95,19 @@ func filterTopologySpreadConstraints(constraints []v1.TopologySpreadConstraint,
if err != nil { if err != nil {
return nil, err return nil, err
} }
if pl.enableMatchLabelKeysInPodTopologySpread && len(c.MatchLabelKeys) > 0 {
matchLabels := make(labels.Set)
for _, labelKey := range c.MatchLabelKeys {
if value, ok := podLabels[labelKey]; ok {
matchLabels[labelKey] = value
}
}
if len(matchLabels) > 0 {
selector = mergeLabelSetWithSelector(matchLabels, selector)
}
}
tsc := topologySpreadConstraint{ tsc := topologySpreadConstraint{
MaxSkew: c.MaxSkew, MaxSkew: c.MaxSkew,
TopologyKey: c.TopologyKey, TopologyKey: c.TopologyKey,
@ -103,10 +116,10 @@ func filterTopologySpreadConstraints(constraints []v1.TopologySpreadConstraint,
NodeAffinityPolicy: v1.NodeInclusionPolicyHonor, // If NodeAffinityPolicy is nil, we treat NodeAffinityPolicy as "Honor". NodeAffinityPolicy: v1.NodeInclusionPolicyHonor, // If NodeAffinityPolicy is nil, we treat NodeAffinityPolicy as "Honor".
NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, // If NodeTaintsPolicy is nil, we treat NodeTaintsPolicy as "Ignore". NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, // If NodeTaintsPolicy is nil, we treat NodeTaintsPolicy as "Ignore".
} }
if enableMinDomainsInPodTopologySpread && c.MinDomains != nil { if pl.enableMinDomainsInPodTopologySpread && c.MinDomains != nil {
tsc.MinDomains = *c.MinDomains tsc.MinDomains = *c.MinDomains
} }
if enableNodeInclusionPolicyInPodTopologySpread { if pl.enableNodeInclusionPolicyInPodTopologySpread {
if c.NodeAffinityPolicy != nil { if c.NodeAffinityPolicy != nil {
tsc.NodeAffinityPolicy = *c.NodeAffinityPolicy tsc.NodeAffinityPolicy = *c.NodeAffinityPolicy
} }
@ -120,6 +133,17 @@ func filterTopologySpreadConstraints(constraints []v1.TopologySpreadConstraint,
return result, nil return result, nil
} }
func mergeLabelSetWithSelector(matchLabels labels.Set, s labels.Selector) labels.Selector {
mergedSelector := labels.SelectorFromSet(matchLabels)
if requirements, ok := s.Requirements(); ok {
for _, r := range requirements {
mergedSelector = mergedSelector.Add(r)
}
}
return mergedSelector
}
func countPodsMatchSelector(podInfos []*framework.PodInfo, selector labels.Selector, ns string) int { func countPodsMatchSelector(podInfos []*framework.PodInfo, selector labels.Selector, ns string) int {
count := 0 count := 0
for _, p := range podInfos { for _, p := range podInfos {

View File

@ -244,11 +244,10 @@ func (pl *PodTopologySpread) calPreFilterState(ctx context.Context, pod *v1.Pod)
if len(pod.Spec.TopologySpreadConstraints) > 0 { if len(pod.Spec.TopologySpreadConstraints) > 0 {
// We have feature gating in APIServer to strip the spec // We have feature gating in APIServer to strip the spec
// so don't need to re-check feature gate, just check length of Constraints. // so don't need to re-check feature gate, just check length of Constraints.
constraints, err = filterTopologySpreadConstraints( constraints, err = pl.filterTopologySpreadConstraints(
pod.Spec.TopologySpreadConstraints, pod.Spec.TopologySpreadConstraints,
pod.Labels,
v1.DoNotSchedule, v1.DoNotSchedule,
pl.enableMinDomainsInPodTopologySpread,
pl.enableNodeInclusionPolicyInPodTopologySpread,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("obtaining pod's hard topology spread constraints: %w", err) return nil, fmt.Errorf("obtaining pod's hard topology spread constraints: %w", err)

View File

@ -55,7 +55,8 @@ var (
honorPolicy = v1.NodeInclusionPolicyHonor honorPolicy = v1.NodeInclusionPolicyHonor
fooSelector = st.MakeLabelSelector().Exists("foo").Obj() fooSelector = st.MakeLabelSelector().Exists("foo").Obj()
barSelector = st.MakeLabelSelector().Exists("bar").Obj() barSelector = st.MakeLabelSelector().Exists("bar").Obj()
taints = []v1.Taint{{Key: v1.TaintNodeUnschedulable, Value: "", Effect: v1.TaintEffectPreferNoSchedule}}
taints = []v1.Taint{{Key: v1.TaintNodeUnschedulable, Value: "", Effect: v1.TaintEffectPreferNoSchedule}}
) )
func (p *criticalPaths) sort() { func (p *criticalPaths) sort() {
@ -77,11 +78,12 @@ func TestPreFilterState(t *testing.T) {
want *preFilterState want *preFilterState
enableMinDomains bool enableMinDomains bool
enableNodeInclustionPolicy bool enableNodeInclustionPolicy bool
enableMatchLabelKeys bool
}{ }{
{ {
name: "clean cluster with one spreadConstraint", name: "clean cluster with one spreadConstraint",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(5, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Label("foo", "bar").Obj(), nil, nil, nil). SpreadConstraint(5, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Label("foo", "bar").Obj(), nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -112,7 +114,7 @@ func TestPreFilterState(t *testing.T) {
{ {
name: "normal case with one spreadConstraint", name: "normal case with one spreadConstraint",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -150,7 +152,7 @@ func TestPreFilterState(t *testing.T) {
{ {
name: "normal case with one spreadConstraint, on a 3-zone cluster", name: "normal case with one spreadConstraint, on a 3-zone cluster",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -191,7 +193,7 @@ func TestPreFilterState(t *testing.T) {
{ {
name: "namespace mismatch doesn't count", name: "namespace mismatch doesn't count",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -229,8 +231,8 @@ func TestPreFilterState(t *testing.T) {
{ {
name: "normal case with two spreadConstraints", name: "normal case with two spreadConstraints",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -283,10 +285,10 @@ func TestPreFilterState(t *testing.T) {
{ {
name: "soft spreadConstraints should be bypassed", name: "soft spreadConstraints should be bypassed",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.ScheduleAnyway, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, "node", v1.ScheduleAnyway, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -337,8 +339,8 @@ func TestPreFilterState(t *testing.T) {
{ {
name: "different labelSelectors - simple version", name: "different labelSelectors - simple version",
pod: st.MakePod().Name("p").Label("foo", "").Label("bar", ""). pod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -384,8 +386,8 @@ func TestPreFilterState(t *testing.T) {
{ {
name: "different labelSelectors - complex pods", name: "different labelSelectors - complex pods",
pod: st.MakePod().Name("p").Label("foo", "").Label("bar", ""). pod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -437,8 +439,8 @@ func TestPreFilterState(t *testing.T) {
name: "two spreadConstraints, and with podAffinity", name: "two spreadConstraints, and with podAffinity",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
NodeAffinityNotIn("node", []string{"node-x"}). // exclude node-x NodeAffinityNotIn("node", []string{"node-x"}). // exclude node-x
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -538,8 +540,8 @@ func TestPreFilterState(t *testing.T) {
{ {
name: "default constraints and a service, but pod has constraints", name: "default constraints and a service, but pod has constraints",
pod: st.MakePod().Name("p").Label("foo", "bar").Label("baz", "tar"). pod: st.MakePod().Name("p").Label("foo", "bar").Label("baz", "tar").
SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Label("baz", "tar").Obj(), nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Label("baz", "tar").Obj(), nil, nil, nil, nil).
SpreadConstraint(2, "planet", v1.ScheduleAnyway, st.MakeLabelSelector().Label("fot", "rok").Obj(), nil, nil, nil). SpreadConstraint(2, "planet", v1.ScheduleAnyway, st.MakeLabelSelector().Label("fot", "rok").Obj(), nil, nil, nil, nil).
Obj(), Obj(),
defaultConstraints: []v1.TopologySpreadConstraint{ defaultConstraints: []v1.TopologySpreadConstraint{
{MaxSkew: 2, TopologyKey: "node", WhenUnsatisfiable: v1.DoNotSchedule}, {MaxSkew: 2, TopologyKey: "node", WhenUnsatisfiable: v1.DoNotSchedule},
@ -578,8 +580,8 @@ func TestPreFilterState(t *testing.T) {
{ {
name: "TpKeyToDomainsNum is calculated when MinDomains is enabled", name: "TpKeyToDomainsNum is calculated when MinDomains is enabled",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -638,7 +640,7 @@ func TestPreFilterState(t *testing.T) {
name: "feature gate disabled with NodeAffinityPolicy", name: "feature gate disabled with NodeAffinityPolicy",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
NodeSelector(map[string]string{"foo": ""}). NodeSelector(map[string]string{"foo": ""}).
SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(), st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(),
@ -676,7 +678,7 @@ func TestPreFilterState(t *testing.T) {
name: "NodeAffinityPolicy honored with labelSelectors", name: "NodeAffinityPolicy honored with labelSelectors",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
NodeSelector(map[string]string{"foo": ""}). NodeSelector(map[string]string{"foo": ""}).
SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(), st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(),
@ -714,7 +716,7 @@ func TestPreFilterState(t *testing.T) {
name: "NodeAffinityPolicy ignored with labelSelectors", name: "NodeAffinityPolicy ignored with labelSelectors",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
NodeSelector(map[string]string{"foo": ""}). NodeSelector(map[string]string{"foo": ""}).
SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, &ignorePolicy, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, &ignorePolicy, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(), st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(),
@ -753,7 +755,7 @@ func TestPreFilterState(t *testing.T) {
name: "NodeAffinityPolicy honored with nodeAffinity", name: "NodeAffinityPolicy honored with nodeAffinity",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
NodeAffinityIn("foo", []string{""}). NodeAffinityIn("foo", []string{""}).
SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(), st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(),
@ -791,7 +793,7 @@ func TestPreFilterState(t *testing.T) {
name: "NodeAffinityPolicy ignored with nodeAffinity", name: "NodeAffinityPolicy ignored with nodeAffinity",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
NodeAffinityIn("foo", []string{""}). NodeAffinityIn("foo", []string{""}).
SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, &ignorePolicy, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, &ignorePolicy, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(), st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(),
@ -829,7 +831,7 @@ func TestPreFilterState(t *testing.T) {
{ {
name: "feature gate disabled with NodeTaintsPolicy", name: "feature gate disabled with NodeTaintsPolicy",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("node", "node-a").Obj(),
@ -867,7 +869,7 @@ func TestPreFilterState(t *testing.T) {
{ {
name: "NodeTaintsPolicy ignored", name: "NodeTaintsPolicy ignored",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("node", "node-a").Obj(),
@ -905,7 +907,7 @@ func TestPreFilterState(t *testing.T) {
{ {
name: "NodeTaintsPolicy honored", name: "NodeTaintsPolicy honored",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, &honorPolicy). SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, &honorPolicy, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("node", "node-a").Obj(),
@ -943,7 +945,7 @@ func TestPreFilterState(t *testing.T) {
name: "NodeTaintsPolicy honored with tolerated taints", name: "NodeTaintsPolicy honored with tolerated taints",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
Toleration(v1.TaintNodeUnschedulable). Toleration(v1.TaintNodeUnschedulable).
SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, &honorPolicy). SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, &honorPolicy, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("node", "node-a").Obj(),
@ -982,8 +984,8 @@ func TestPreFilterState(t *testing.T) {
name: "two node inclusion Constraints, zone: honor/ignore, node: ignore/ignore", name: "two node inclusion Constraints, zone: honor/ignore, node: ignore/ignore",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
NodeSelector(map[string]string{"foo": ""}). NodeSelector(map[string]string{"foo": ""}).
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, &ignorePolicy, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, &ignorePolicy, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Label("foo", "").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Label("foo", "").Obj(),
@ -1031,8 +1033,8 @@ func TestPreFilterState(t *testing.T) {
{ {
name: "two node inclusion Constraints, zone: honor/honor, node: honor/ignore", name: "two node inclusion Constraints, zone: honor/honor, node: honor/ignore",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, &honorPolicy). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, &honorPolicy, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Label("foo", "").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Label("foo", "").Obj(),
@ -1081,8 +1083,8 @@ func TestPreFilterState(t *testing.T) {
name: "two node inclusion Constraints, zone: honor/ignore, node: honor/ignore", name: "two node inclusion Constraints, zone: honor/ignore, node: honor/ignore",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
NodeSelector(map[string]string{"foo": ""}). NodeSelector(map[string]string{"foo": ""}).
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -1132,8 +1134,8 @@ func TestPreFilterState(t *testing.T) {
name: "two node inclusion Constraints, zone: ignore/ignore, node: honor/honor", name: "two node inclusion Constraints, zone: ignore/ignore, node: honor/honor",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
NodeSelector(map[string]string{"foo": ""}). NodeSelector(map[string]string{"foo": ""}).
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, &ignorePolicy, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, &ignorePolicy, nil, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, &honorPolicy). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, &honorPolicy, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -1180,6 +1182,162 @@ func TestPreFilterState(t *testing.T) {
}, },
enableNodeInclustionPolicy: true, enableNodeInclustionPolicy: true,
}, },
{
name: "matchLabelKeys ignored when feature gate disabled",
pod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, []string{"bar"}).
Obj(),
nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
st.MakeNode().Name("node-y").Label("zone", "zone2").Label("node", "node-y").Obj(),
},
existingPods: []*v1.Pod{
st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(),
st.MakePod().Name("p-y2").Node("node-y").Label("foo", "").Obj(),
},
want: &preFilterState{
Constraints: []topologySpreadConstraint{
{
MaxSkew: 1,
TopologyKey: "zone",
Selector: mustConvertLabelSelectorAsSelector(t, fooSelector),
MinDomains: 1,
NodeAffinityPolicy: v1.NodeInclusionPolicyHonor,
NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore,
},
},
TpKeyToCriticalPaths: map[string]*criticalPaths{
"zone": {{"zone2", 2}, {"zone1", 3}},
},
TpPairToMatchNum: map[topologyPair]int{
{key: "zone", value: "zone1"}: 3,
{key: "zone", value: "zone2"}: 2,
},
},
enableMatchLabelKeys: false,
},
{
name: "matchLabelKeys ANDed with LabelSelector when LabelSelector isn't empty",
pod: st.MakePod().Name("p").Label("foo", "").Label("bar", "a").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, []string{"bar"}).
Obj(),
nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
st.MakeNode().Name("node-y").Label("zone", "zone2").Label("node", "node-y").Obj(),
},
existingPods: []*v1.Pod{
st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Label("bar", "a").Obj(),
st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Label("bar", "a").Obj(),
st.MakePod().Name("p-y2").Node("node-y").Label("bar", "").Obj(),
},
want: &preFilterState{
Constraints: []topologySpreadConstraint{
{
MaxSkew: 1,
TopologyKey: "zone",
Selector: mustConvertLabelSelectorAsSelector(t, st.MakeLabelSelector().Exists("foo").Label("bar", "a").Obj()),
MinDomains: 1,
NodeAffinityPolicy: v1.NodeInclusionPolicyHonor,
NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore,
},
},
TpKeyToCriticalPaths: map[string]*criticalPaths{
"zone": {{"zone2", 1}, {"zone1", 1}},
},
TpPairToMatchNum: map[topologyPair]int{
{key: "zone", value: "zone1"}: 1,
{key: "zone", value: "zone2"}: 1,
},
},
enableMatchLabelKeys: true,
},
{
name: "matchLabelKeys ANDed with LabelSelector when LabelSelector is empty",
pod: st.MakePod().Name("p").Label("foo", "a").
SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Obj(), nil, nil, nil, []string{"foo"}).
Obj(),
nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
st.MakeNode().Name("node-y").Label("zone", "zone2").Label("node", "node-y").Obj(),
},
existingPods: []*v1.Pod{
st.MakePod().Name("p-a1").Node("node-a").Label("foo", "a").Obj(),
st.MakePod().Name("p-a2").Node("node-a").Label("foo", "a").Obj(),
st.MakePod().Name("p-b1").Node("node-b").Label("foo", "a").Obj(),
st.MakePod().Name("p-y1").Node("node-y").Label("foo", "a").Obj(),
st.MakePod().Name("p-y2").Node("node-y").Label("foo", "a").Obj(),
},
want: &preFilterState{
Constraints: []topologySpreadConstraint{
{
MaxSkew: 1,
TopologyKey: "zone",
Selector: mustConvertLabelSelectorAsSelector(t, st.MakeLabelSelector().Label("foo", "a").Obj()),
MinDomains: 1,
NodeAffinityPolicy: v1.NodeInclusionPolicyHonor,
NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore,
},
},
TpKeyToCriticalPaths: map[string]*criticalPaths{
"zone": {{"zone2", 2}, {"zone1", 3}},
},
TpPairToMatchNum: map[topologyPair]int{
{key: "zone", value: "zone1"}: 3,
{key: "zone", value: "zone2"}: 2,
},
},
enableMatchLabelKeys: true,
},
{
name: "key in matchLabelKeys is ignored when it isn't exist in pod.labels",
pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, []string{"bar"}).
Obj(),
nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
st.MakeNode().Name("node-y").Label("zone", "zone2").Label("node", "node-y").Obj(),
},
existingPods: []*v1.Pod{
st.MakePod().Name("p-a1").Node("node-a").Label("foo", "a").Obj(),
st.MakePod().Name("p-a2").Node("node-a").Label("foo", "a").Obj(),
st.MakePod().Name("p-b1").Node("node-b").Label("foo", "a").Obj(),
st.MakePod().Name("p-y1").Node("node-y").Label("foo", "a").Obj(),
st.MakePod().Name("p-y2").Node("node-y").Label("foo", "a").Obj(),
},
want: &preFilterState{
Constraints: []topologySpreadConstraint{
{
MaxSkew: 1,
TopologyKey: "zone",
Selector: mustConvertLabelSelectorAsSelector(t, fooSelector),
MinDomains: 1,
NodeAffinityPolicy: v1.NodeInclusionPolicyHonor,
NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore,
},
},
TpKeyToCriticalPaths: map[string]*criticalPaths{
"zone": {{"zone2", 2}, {"zone1", 3}},
},
TpPairToMatchNum: map[topologyPair]int{
{key: "zone", value: "zone1"}: 3,
{key: "zone", value: "zone2"}: 2,
},
},
enableMatchLabelKeys: true,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@ -1193,6 +1351,7 @@ func TestPreFilterState(t *testing.T) {
p := plugintesting.SetupPluginWithInformers(ctx, t, topologySpreadFunc, args, cache.NewSnapshot(tt.existingPods, tt.nodes), tt.objs) p := plugintesting.SetupPluginWithInformers(ctx, t, topologySpreadFunc, args, cache.NewSnapshot(tt.existingPods, tt.nodes), tt.objs)
p.(*PodTopologySpread).enableMinDomainsInPodTopologySpread = tt.enableMinDomains p.(*PodTopologySpread).enableMinDomainsInPodTopologySpread = tt.enableMinDomains
p.(*PodTopologySpread).enableNodeInclusionPolicyInPodTopologySpread = tt.enableNodeInclustionPolicy p.(*PodTopologySpread).enableNodeInclusionPolicyInPodTopologySpread = tt.enableNodeInclustionPolicy
p.(*PodTopologySpread).enableMatchLabelKeysInPodTopologySpread = tt.enableMatchLabelKeys
cs := framework.NewCycleState() cs := framework.NewCycleState()
if _, s := p.(*PodTopologySpread).PreFilter(ctx, cs, tt.pod); !s.IsSuccess() { if _, s := p.(*PodTopologySpread).PreFilter(ctx, cs, tt.pod); !s.IsSuccess() {
@ -1233,7 +1392,7 @@ func TestPreFilterStateAddPod(t *testing.T) {
{ {
name: "node a and b both impact current min match", name: "node a and b both impact current min match",
preemptor: st.MakePod().Name("p").Label("foo", ""). preemptor: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
addedPod: st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(), addedPod: st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
existingPods: nil, // it's an empty cluster existingPods: nil, // it's an empty cluster
@ -1256,7 +1415,7 @@ func TestPreFilterStateAddPod(t *testing.T) {
{ {
name: "only node a impacts current min match", name: "only node a impacts current min match",
preemptor: st.MakePod().Name("p").Label("foo", ""). preemptor: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
addedPod: st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(), addedPod: st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
@ -1281,7 +1440,7 @@ func TestPreFilterStateAddPod(t *testing.T) {
{ {
name: "add a pod in a different namespace doesn't change topologyKeyToMinPodsMap", name: "add a pod in a different namespace doesn't change topologyKeyToMinPodsMap",
preemptor: st.MakePod().Name("p").Label("foo", ""). preemptor: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
addedPod: st.MakePod().Name("p-a1").Namespace("ns1").Node("node-a").Label("foo", "").Obj(), addedPod: st.MakePod().Name("p-a1").Namespace("ns1").Node("node-a").Label("foo", "").Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
@ -1306,7 +1465,7 @@ func TestPreFilterStateAddPod(t *testing.T) {
{ {
name: "add pod on non-critical node won't trigger re-calculation", name: "add pod on non-critical node won't trigger re-calculation",
preemptor: st.MakePod().Name("p").Label("foo", ""). preemptor: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
addedPod: st.MakePod().Name("p-b2").Node("node-b").Label("foo", "").Obj(), addedPod: st.MakePod().Name("p-b2").Node("node-b").Label("foo", "").Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
@ -1331,8 +1490,8 @@ func TestPreFilterStateAddPod(t *testing.T) {
{ {
name: "node a and x both impact topologyKeyToMinPodsMap on zone and node", name: "node a and x both impact topologyKeyToMinPodsMap on zone and node",
preemptor: st.MakePod().Name("p").Label("foo", ""). preemptor: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
addedPod: st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(), addedPod: st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
existingPods: nil, // it's an empty cluster existingPods: nil, // it's an empty cluster
@ -1358,8 +1517,8 @@ func TestPreFilterStateAddPod(t *testing.T) {
{ {
name: "only node a impacts topologyKeyToMinPodsMap on zone and node", name: "only node a impacts topologyKeyToMinPodsMap on zone and node",
preemptor: st.MakePod().Name("p").Label("foo", ""). preemptor: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
addedPod: st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(), addedPod: st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
@ -1387,8 +1546,8 @@ func TestPreFilterStateAddPod(t *testing.T) {
{ {
name: "node a impacts topologyKeyToMinPodsMap on node, node x impacts topologyKeyToMinPodsMap on zone", name: "node a impacts topologyKeyToMinPodsMap on node, node x impacts topologyKeyToMinPodsMap on zone",
preemptor: st.MakePod().Name("p").Label("foo", ""). preemptor: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
addedPod: st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(), addedPod: st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
@ -1420,8 +1579,8 @@ func TestPreFilterStateAddPod(t *testing.T) {
{ {
name: "Constraints hold different labelSelectors, node a impacts topologyKeyToMinPodsMap on zone", name: "Constraints hold different labelSelectors, node a impacts topologyKeyToMinPodsMap on zone",
preemptor: st.MakePod().Name("p").Label("foo", "").Label("bar", ""). preemptor: st.MakePod().Name("p").Label("foo", "").Label("bar", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, nil, nil).
Obj(), Obj(),
addedPod: st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(), addedPod: st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
@ -1463,8 +1622,8 @@ func TestPreFilterStateAddPod(t *testing.T) {
{ {
name: "Constraints hold different labelSelectors, node a impacts topologyKeyToMinPodsMap on both zone and node", name: "Constraints hold different labelSelectors, node a impacts topologyKeyToMinPodsMap on both zone and node",
preemptor: st.MakePod().Name("p").Label("foo", "").Label("bar", ""). preemptor: st.MakePod().Name("p").Label("foo", "").Label("bar", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, nil, nil).
Obj(), Obj(),
addedPod: st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Label("bar", "").Obj(), addedPod: st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Label("bar", "").Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
@ -1506,7 +1665,7 @@ func TestPreFilterStateAddPod(t *testing.T) {
{ {
name: "add a pod that doesn't match node affinity when NodeInclustionPolicy disabled", name: "add a pod that doesn't match node affinity when NodeInclustionPolicy disabled",
preemptor: st.MakePod().Name("p").Label("foo", "").NodeAffinityNotIn("foo", []string{"bar"}). preemptor: st.MakePod().Name("p").Label("foo", "").NodeAffinityNotIn("foo", []string{"bar"}).
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodeIdx: 0, nodeIdx: 0,
addedPod: st.MakePod().Name("p-a1").Node("node-b").Label("foo", "").Label("zone", "zone2").Obj(), addedPod: st.MakePod().Name("p-a1").Node("node-b").Label("foo", "").Label("zone", "zone2").Obj(),
@ -1532,7 +1691,7 @@ func TestPreFilterStateAddPod(t *testing.T) {
{ {
name: "add a pod that doesn't match node affinity when NodeInclustionPolicy enabled", name: "add a pod that doesn't match node affinity when NodeInclustionPolicy enabled",
preemptor: st.MakePod().Name("p").Label("foo", "").NodeAffinityNotIn("foo", []string{"bar"}). preemptor: st.MakePod().Name("p").Label("foo", "").NodeAffinityNotIn("foo", []string{"bar"}).
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodeIdx: 0, nodeIdx: 0,
addedPod: st.MakePod().Name("p-a1").Node("node-b").Label("foo", "").Label("zone", "zone2").Obj(), addedPod: st.MakePod().Name("p-a1").Node("node-b").Label("foo", "").Label("zone", "zone2").Obj(),
@ -1612,7 +1771,7 @@ func TestPreFilterStateRemovePod(t *testing.T) {
// So preemption is triggered. // So preemption is triggered.
name: "one spreadConstraint on zone, topologyKeyToMinPodsMap unchanged", name: "one spreadConstraint on zone, topologyKeyToMinPodsMap unchanged",
preemptor: st.MakePod().Name("p").Label("foo", ""). preemptor: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -1640,7 +1799,7 @@ func TestPreFilterStateRemovePod(t *testing.T) {
{ {
name: "one spreadConstraint on node, topologyKeyToMinPodsMap changed", name: "one spreadConstraint on node, topologyKeyToMinPodsMap changed",
preemptor: st.MakePod().Name("p").Label("foo", ""). preemptor: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -1670,7 +1829,7 @@ func TestPreFilterStateRemovePod(t *testing.T) {
{ {
name: "delete an irrelevant pod won't help", name: "delete an irrelevant pod won't help",
preemptor: st.MakePod().Name("p").Label("foo", ""). preemptor: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -1701,7 +1860,7 @@ func TestPreFilterStateRemovePod(t *testing.T) {
{ {
name: "delete a non-existing pod won't help", name: "delete a non-existing pod won't help",
preemptor: st.MakePod().Name("p").Label("foo", ""). preemptor: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -1732,8 +1891,8 @@ func TestPreFilterStateRemovePod(t *testing.T) {
{ {
name: "two spreadConstraints", name: "two spreadConstraints",
preemptor: st.MakePod().Name("p").Label("foo", ""). preemptor: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -1811,7 +1970,7 @@ func BenchmarkFilter(b *testing.B) {
{ {
name: "1000nodes/single-constraint-zone", name: "1000nodes/single-constraint-zone",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, v1.LabelTopologyZone, v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, v1.LabelTopologyZone, v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
existingPodsNum: 10000, existingPodsNum: 10000,
allNodesNum: 1000, allNodesNum: 1000,
@ -1820,7 +1979,7 @@ func BenchmarkFilter(b *testing.B) {
{ {
name: "1000nodes/single-constraint-node", name: "1000nodes/single-constraint-node",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, v1.LabelHostname, v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, v1.LabelHostname, v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
existingPodsNum: 10000, existingPodsNum: 10000,
allNodesNum: 1000, allNodesNum: 1000,
@ -1829,8 +1988,8 @@ func BenchmarkFilter(b *testing.B) {
{ {
name: "1000nodes/two-Constraints-zone-node", name: "1000nodes/two-Constraints-zone-node",
pod: st.MakePod().Name("p").Label("foo", "").Label("bar", ""). pod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").
SpreadConstraint(1, v1.LabelTopologyZone, v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, v1.LabelTopologyZone, v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, v1.LabelHostname, v1.DoNotSchedule, barSelector, nil, nil, nil). SpreadConstraint(1, v1.LabelHostname, v1.DoNotSchedule, barSelector, nil, nil, nil, nil).
Obj(), Obj(),
existingPodsNum: 10000, existingPodsNum: 10000,
allNodesNum: 1000, allNodesNum: 1000,
@ -1887,7 +2046,7 @@ func TestSingleConstraint(t *testing.T) {
{ {
name: "no existing pods", name: "no existing pods",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -1905,7 +2064,7 @@ func TestSingleConstraint(t *testing.T) {
{ {
name: "no existing pods, incoming pod doesn't match itself", name: "no existing pods, incoming pod doesn't match itself",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, barSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, barSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -1923,7 +2082,7 @@ func TestSingleConstraint(t *testing.T) {
{ {
name: "existing pods in a different namespace do not count", name: "existing pods in a different namespace do not count",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -1947,7 +2106,7 @@ func TestSingleConstraint(t *testing.T) {
{ {
name: "pods spread across zones as 3/3, all nodes fit", name: "pods spread across zones as 3/3, all nodes fit",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -1975,7 +2134,7 @@ func TestSingleConstraint(t *testing.T) {
// can cause unexpected behavior // can cause unexpected behavior
name: "pods spread across zones as 1/2 due to absence of label 'zone' on node-b", name: "pods spread across zones as 1/2 due to absence of label 'zone' on node-b",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -1999,7 +2158,7 @@ func TestSingleConstraint(t *testing.T) {
{ {
name: "pod cannot be scheduled as all nodes don't have label 'rack'", name: "pod cannot be scheduled as all nodes don't have label 'rack'",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "rack", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "rack", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -2013,7 +2172,7 @@ func TestSingleConstraint(t *testing.T) {
{ {
name: "pods spread across nodes as 2/1/0/3, only node-x fits", name: "pods spread across nodes as 2/1/0/3, only node-x fits",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -2039,7 +2198,7 @@ func TestSingleConstraint(t *testing.T) {
{ {
name: "pods spread across nodes as 2/1/0/3, maxSkew is 2, node-b and node-x fit", name: "pods spread across nodes as 2/1/0/3, maxSkew is 2, node-b and node-x fit",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(2, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(2, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -2069,7 +2228,7 @@ func TestSingleConstraint(t *testing.T) {
// as the incoming pod doesn't have label "foo" // as the incoming pod doesn't have label "foo"
name: "pods spread across nodes as 2/1/0/3, but pod doesn't match itself", name: "pods spread across nodes as 2/1/0/3, but pod doesn't match itself",
pod: st.MakePod().Name("p").Label("bar", ""). pod: st.MakePod().Name("p").Label("bar", "").
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -2101,7 +2260,7 @@ func TestSingleConstraint(t *testing.T) {
name: "incoming pod has nodeAffinity, pods spread as 2/~1~/~0~/3, hence node-a fits", name: "incoming pod has nodeAffinity, pods spread as 2/~1~/~0~/3, hence node-a fits",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
NodeAffinityIn("node", []string{"node-a", "node-y"}). NodeAffinityIn("node", []string{"node-a", "node-y"}).
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -2127,7 +2286,7 @@ func TestSingleConstraint(t *testing.T) {
{ {
name: "terminating Pods should be excluded", name: "terminating Pods should be excluded",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("node", "node-a").Obj(),
@ -2147,7 +2306,7 @@ func TestSingleConstraint(t *testing.T) {
name: "incoming pod has nodeAffinity, pods spread as 0/~2~/0/1, hence node-a fits", name: "incoming pod has nodeAffinity, pods spread as 0/~2~/0/1, hence node-a fits",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
NodeAffinityNotIn("node", []string{"node-b"}). NodeAffinityNotIn("node", []string{"node-b"}).
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -2177,6 +2336,7 @@ func TestSingleConstraint(t *testing.T) {
pointer.Int32(4), // larger than the number of domains(3) pointer.Int32(4), // larger than the number of domains(3)
nil, nil,
nil, nil,
nil,
).Obj(), ).Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("node", "node-a").Obj(),
@ -2207,6 +2367,7 @@ func TestSingleConstraint(t *testing.T) {
pointer.Int32(2), // smaller than the number of domains(3) pointer.Int32(2), // smaller than the number of domains(3)
nil, nil,
nil, nil,
nil,
).Obj(), ).Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("node", "node-a").Obj(),
@ -2237,6 +2398,7 @@ func TestSingleConstraint(t *testing.T) {
pointer.Int32(3), // larger than the number of domains(2) pointer.Int32(3), // larger than the number of domains(2)
nil, nil,
nil, nil,
nil,
).Obj(), ).Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -2267,6 +2429,7 @@ func TestSingleConstraint(t *testing.T) {
pointer.Int32(1), // smaller than the number of domains(2) pointer.Int32(1), // smaller than the number of domains(2)
nil, nil,
nil, nil,
nil,
).Obj(), ).Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -2292,7 +2455,7 @@ func TestSingleConstraint(t *testing.T) {
name: "NodeAffinityPolicy honored with labelSelectors", name: "NodeAffinityPolicy honored with labelSelectors",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
NodeSelector(map[string]string{"foo": ""}). NodeSelector(map[string]string{"foo": ""}).
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(), st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(),
@ -2318,7 +2481,7 @@ func TestSingleConstraint(t *testing.T) {
name: "NodeAffinityPolicy ignored with labelSelectors", name: "NodeAffinityPolicy ignored with labelSelectors",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
NodeSelector(map[string]string{"foo": ""}). NodeSelector(map[string]string{"foo": ""}).
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, &ignorePolicy, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, &ignorePolicy, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(), st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(),
@ -2344,7 +2507,7 @@ func TestSingleConstraint(t *testing.T) {
name: "NodeAffinityPolicy honored with nodeAffinity", name: "NodeAffinityPolicy honored with nodeAffinity",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
NodeAffinityIn("foo", []string{""}). NodeAffinityIn("foo", []string{""}).
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(), st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(),
@ -2370,7 +2533,7 @@ func TestSingleConstraint(t *testing.T) {
name: "NodeAffinityPolicy ignored with labelSelectors", name: "NodeAffinityPolicy ignored with labelSelectors",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
NodeAffinityIn("foo", []string{""}). NodeAffinityIn("foo", []string{""}).
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, &ignorePolicy, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, &ignorePolicy, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(), st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(),
@ -2395,7 +2558,7 @@ func TestSingleConstraint(t *testing.T) {
// pods spread across node as 1/1/0/~0~ // pods spread across node as 1/1/0/~0~
name: "NodeTaintsPolicy honored", name: "NodeTaintsPolicy honored",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, &honorPolicy). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, &honorPolicy, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(), st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(),
@ -2420,7 +2583,7 @@ func TestSingleConstraint(t *testing.T) {
// pods spread across node as 1/1/0/~1~ // pods spread across node as 1/1/0/~1~
name: "NodeTaintsPolicy ignored", name: "NodeTaintsPolicy ignored",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(), st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(),
@ -2480,8 +2643,8 @@ func TestMultipleConstraints(t *testing.T) {
// intersection of (1) and (2) returns node-x // intersection of (1) and (2) returns node-x
name: "two Constraints on zone and node, spreads = [3/3, 2/1/0/3]", name: "two Constraints on zone and node, spreads = [3/3, 2/1/0/3]",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -2510,8 +2673,8 @@ func TestMultipleConstraints(t *testing.T) {
// intersection of (1) and (2) returns no node // intersection of (1) and (2) returns no node
name: "two Constraints on zone and node, spreads = [3/4, 2/1/0/4]", name: "two Constraints on zone and node, spreads = [3/4, 2/1/0/4]",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -2541,8 +2704,8 @@ func TestMultipleConstraints(t *testing.T) {
// intersection of (1) and (2) returns node-x // intersection of (1) and (2) returns node-x
name: "Constraints hold different labelSelectors, spreads = [1/0, 1/0/0/1]", name: "Constraints hold different labelSelectors, spreads = [1/0, 1/0/0/1]",
pod: st.MakePod().Name("p").Label("foo", "").Label("bar", ""). pod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -2567,8 +2730,8 @@ func TestMultipleConstraints(t *testing.T) {
// intersection of (1) and (2) returns no node // intersection of (1) and (2) returns no node
name: "Constraints hold different labelSelectors, spreads = [1/0, 0/0/1/1]", name: "Constraints hold different labelSelectors, spreads = [1/0, 0/0/1/1]",
pod: st.MakePod().Name("p").Label("foo", "").Label("bar", ""). pod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -2594,8 +2757,8 @@ func TestMultipleConstraints(t *testing.T) {
// intersection of (1) and (2) returns node-b // intersection of (1) and (2) returns node-b
name: "Constraints hold different labelSelectors, spreads = [2/3, 1/0/0/1]", name: "Constraints hold different labelSelectors, spreads = [2/3, 1/0/0/1]",
pod: st.MakePod().Name("p").Label("foo", "").Label("bar", ""). pod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -2623,8 +2786,8 @@ func TestMultipleConstraints(t *testing.T) {
// intersection of (1) and (2) returns node-a and node-b // intersection of (1) and (2) returns node-a and node-b
name: "Constraints hold different labelSelectors but pod doesn't match itself on 'zone' constraint", name: "Constraints hold different labelSelectors but pod doesn't match itself on 'zone' constraint",
pod: st.MakePod().Name("p").Label("bar", ""). pod: st.MakePod().Name("p").Label("bar", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -2650,8 +2813,8 @@ func TestMultipleConstraints(t *testing.T) {
// intersection of (1) and (2) returns node-b // intersection of (1) and (2) returns node-b
name: "two Constraints on zone and node, absence of label 'node' on node-x, spreads = [1/1, 1/0/0/1]", name: "two Constraints on zone and node, absence of label 'node' on node-x, spreads = [1/1, 1/0/0/1]",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
@ -2677,8 +2840,8 @@ func TestMultipleConstraints(t *testing.T) {
name: "two node inclusion Constraints, zone: honor/ignore, node: ignore/ignore", name: "two node inclusion Constraints, zone: honor/ignore, node: ignore/ignore",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
NodeSelector(map[string]string{"foo": ""}). NodeSelector(map[string]string{"foo": ""}).
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, &ignorePolicy, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, &ignorePolicy, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Label("foo", "").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Label("foo", "").Obj(),
@ -2705,8 +2868,8 @@ func TestMultipleConstraints(t *testing.T) {
// intersection of (1) and (2) returns node-x // intersection of (1) and (2) returns node-x
name: "two node inclusion Constraints, zone: honor/honor, node: honor/ignore", name: "two node inclusion Constraints, zone: honor/honor, node: honor/ignore",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, &honorPolicy). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, &honorPolicy, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Label("foo", "").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Label("foo", "").Obj(),
@ -2734,8 +2897,8 @@ func TestMultipleConstraints(t *testing.T) {
name: "two node inclusion Constraints, zone: honor/ignore, node: honor/ignore", name: "two node inclusion Constraints, zone: honor/ignore, node: honor/ignore",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
NodeSelector(map[string]string{"foo": ""}). NodeSelector(map[string]string{"foo": ""}).
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Label("foo", "").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Label("foo", "").Obj(),
@ -2763,8 +2926,8 @@ func TestMultipleConstraints(t *testing.T) {
name: "two node inclusion Constraints, zone: honor/honor, node: ignore/ignore", name: "two node inclusion Constraints, zone: honor/honor, node: ignore/ignore",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
NodeSelector(map[string]string{"foo": ""}). NodeSelector(map[string]string{"foo": ""}).
SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, &honorPolicy). SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, &honorPolicy, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, &ignorePolicy, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, &ignorePolicy, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Label("foo", "").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Label("foo", "").Obj(),

View File

@ -64,6 +64,7 @@ type PodTopologySpread struct {
statefulSets appslisters.StatefulSetLister statefulSets appslisters.StatefulSetLister
enableMinDomainsInPodTopologySpread bool enableMinDomainsInPodTopologySpread bool
enableNodeInclusionPolicyInPodTopologySpread bool enableNodeInclusionPolicyInPodTopologySpread bool
enableMatchLabelKeysInPodTopologySpread bool
} }
var _ framework.PreFilterPlugin = &PodTopologySpread{} var _ framework.PreFilterPlugin = &PodTopologySpread{}
@ -98,6 +99,7 @@ func New(plArgs runtime.Object, h framework.Handle, fts feature.Features) (frame
defaultConstraints: args.DefaultConstraints, defaultConstraints: args.DefaultConstraints,
enableMinDomainsInPodTopologySpread: fts.EnableMinDomainsInPodTopologySpread, enableMinDomainsInPodTopologySpread: fts.EnableMinDomainsInPodTopologySpread,
enableNodeInclusionPolicyInPodTopologySpread: fts.EnableNodeInclusionPolicyInPodTopologySpread, enableNodeInclusionPolicyInPodTopologySpread: fts.EnableNodeInclusionPolicyInPodTopologySpread,
enableMatchLabelKeysInPodTopologySpread: fts.EnableMatchLabelKeysInPodTopologySpread,
} }
if args.DefaultingType == config.SystemDefaulting { if args.DefaultingType == config.SystemDefaulting {
pl.defaultConstraints = systemDefaultConstraints pl.defaultConstraints = systemDefaultConstraints

View File

@ -59,11 +59,10 @@ func (s *preScoreState) Clone() framework.StateData {
func (pl *PodTopologySpread) initPreScoreState(s *preScoreState, pod *v1.Pod, filteredNodes []*v1.Node, requireAllTopologies bool) error { func (pl *PodTopologySpread) initPreScoreState(s *preScoreState, pod *v1.Pod, filteredNodes []*v1.Node, requireAllTopologies bool) error {
var err error var err error
if len(pod.Spec.TopologySpreadConstraints) > 0 { if len(pod.Spec.TopologySpreadConstraints) > 0 {
s.Constraints, err = filterTopologySpreadConstraints( s.Constraints, err = pl.filterTopologySpreadConstraints(
pod.Spec.TopologySpreadConstraints, pod.Spec.TopologySpreadConstraints,
pod.Labels,
v1.ScheduleAnyway, v1.ScheduleAnyway,
pl.enableMinDomainsInPodTopologySpread,
pl.enableNodeInclusionPolicyInPodTopologySpread,
) )
if err != nil { if err != nil {
return fmt.Errorf("obtaining pod's soft topology spread constraints: %w", err) return fmt.Errorf("obtaining pod's soft topology spread constraints: %w", err)

View File

@ -55,8 +55,8 @@ func TestPreScoreStateEmptyNodes(t *testing.T) {
{ {
name: "normal case", name: "normal case",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.ScheduleAnyway, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil). SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label(v1.LabelHostname, "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label(v1.LabelHostname, "node-a").Obj(),
@ -96,8 +96,8 @@ func TestPreScoreStateEmptyNodes(t *testing.T) {
{ {
name: "node-x doesn't have label zone", name: "node-x doesn't have label zone",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.ScheduleAnyway, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, barSelector, nil, nil, nil). SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, barSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label(v1.LabelHostname, "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label(v1.LabelHostname, "node-a").Obj(),
@ -253,8 +253,8 @@ func TestPreScoreStateEmptyNodes(t *testing.T) {
name: "default constraints and a replicaset, but pod has constraints", name: "default constraints and a replicaset, but pod has constraints",
pod: st.MakePod().Name("p").Namespace("default").Label("foo", "bar").Label("baz", "sup"). pod: st.MakePod().Name("p").Namespace("default").Label("foo", "bar").Label("baz", "sup").
OwnerReference("rs1", appsv1.SchemeGroupVersion.WithKind("ReplicaSet")). OwnerReference("rs1", appsv1.SchemeGroupVersion.WithKind("ReplicaSet")).
SpreadConstraint(1, "zone", v1.DoNotSchedule, barSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, barSelector, nil, nil, nil, nil).
SpreadConstraint(2, "planet", v1.ScheduleAnyway, st.MakeLabelSelector().Label("baz", "sup").Obj(), nil, nil, nil). SpreadConstraint(2, "planet", v1.ScheduleAnyway, st.MakeLabelSelector().Label("baz", "sup").Obj(), nil, nil, nil, nil).
Obj(), Obj(),
config: config.PodTopologySpreadArgs{ config: config.PodTopologySpreadArgs{
DefaultConstraints: []v1.TopologySpreadConstraint{ DefaultConstraints: []v1.TopologySpreadConstraint{
@ -294,7 +294,7 @@ func TestPreScoreStateEmptyNodes(t *testing.T) {
name: "NodeAffinityPolicy honored with labelSelectors", name: "NodeAffinityPolicy honored with labelSelectors",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
NodeSelector(map[string]string{"foo": ""}). NodeSelector(map[string]string{"foo": ""}).
SpreadConstraint(1, "zone", v1.ScheduleAnyway, barSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.ScheduleAnyway, barSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("foo", "").Label(v1.LabelHostname, "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("foo", "").Label(v1.LabelHostname, "node-a").Obj(),
@ -328,7 +328,7 @@ func TestPreScoreStateEmptyNodes(t *testing.T) {
name: "NodeAffinityPolicy ignored with labelSelectors", name: "NodeAffinityPolicy ignored with labelSelectors",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
NodeSelector(map[string]string{"foo": ""}). NodeSelector(map[string]string{"foo": ""}).
SpreadConstraint(1, "zone", v1.ScheduleAnyway, barSelector, nil, &ignorePolicy, nil). SpreadConstraint(1, "zone", v1.ScheduleAnyway, barSelector, nil, &ignorePolicy, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("foo", "").Label(v1.LabelHostname, "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("foo", "").Label(v1.LabelHostname, "node-a").Obj(),
@ -362,7 +362,7 @@ func TestPreScoreStateEmptyNodes(t *testing.T) {
name: "NodeAffinityPolicy honored with nodeAffinity", name: "NodeAffinityPolicy honored with nodeAffinity",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
NodeAffinityIn("foo", []string{""}). NodeAffinityIn("foo", []string{""}).
SpreadConstraint(1, "zone", v1.ScheduleAnyway, barSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.ScheduleAnyway, barSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("foo", "").Label(v1.LabelHostname, "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("foo", "").Label(v1.LabelHostname, "node-a").Obj(),
@ -396,7 +396,7 @@ func TestPreScoreStateEmptyNodes(t *testing.T) {
name: "NodeAffinityPolicy ignored with nodeAffinity", name: "NodeAffinityPolicy ignored with nodeAffinity",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
NodeAffinityIn("foo", []string{""}). NodeAffinityIn("foo", []string{""}).
SpreadConstraint(1, "zone", v1.ScheduleAnyway, barSelector, nil, &ignorePolicy, nil). SpreadConstraint(1, "zone", v1.ScheduleAnyway, barSelector, nil, &ignorePolicy, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("foo", "").Label(v1.LabelHostname, "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("foo", "").Label(v1.LabelHostname, "node-a").Obj(),
@ -429,7 +429,7 @@ func TestPreScoreStateEmptyNodes(t *testing.T) {
{ {
name: "NodeTaintsPolicy honored", name: "NodeTaintsPolicy honored",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.ScheduleAnyway, barSelector, nil, nil, &honorPolicy). SpreadConstraint(1, "zone", v1.ScheduleAnyway, barSelector, nil, nil, &honorPolicy, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("foo", "").Label(v1.LabelHostname, "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("foo", "").Label(v1.LabelHostname, "node-a").Obj(),
@ -462,7 +462,7 @@ func TestPreScoreStateEmptyNodes(t *testing.T) {
{ {
name: "NodeTaintsPolicy ignored", name: "NodeTaintsPolicy ignored",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.ScheduleAnyway, barSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.ScheduleAnyway, barSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("foo", "").Label(v1.LabelHostname, "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("foo", "").Label(v1.LabelHostname, "node-a").Obj(),
@ -537,6 +537,7 @@ func TestPodTopologySpreadScore(t *testing.T) {
objs []runtime.Object objs []runtime.Object
want framework.NodeScoreList want framework.NodeScoreList
enableNodeInclustionPolicy bool enableNodeInclustionPolicy bool
enableMatchLabelKeys bool
}{ }{
// Explanation on the Legend: // Explanation on the Legend:
// a) X/Y means there are X matching pods on node1 and Y on node2, both nodes are candidates // a) X/Y means there are X matching pods on node1 and Y on node2, both nodes are candidates
@ -547,7 +548,7 @@ func TestPodTopologySpreadScore(t *testing.T) {
{ {
name: "one constraint on node, no existing pods", name: "one constraint on node, no existing pods",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil). SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label(v1.LabelHostname, "node-a").Obj(), st.MakeNode().Name("node-a").Label(v1.LabelHostname, "node-a").Obj(),
@ -562,7 +563,7 @@ func TestPodTopologySpreadScore(t *testing.T) {
// if there is only one candidate node, it should be scored to 100 // if there is only one candidate node, it should be scored to 100
name: "one constraint on node, only one node is candidate", name: "one constraint on node, only one node is candidate",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil). SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(), st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
@ -582,7 +583,7 @@ func TestPodTopologySpreadScore(t *testing.T) {
{ {
name: "one constraint on node, all nodes have the same number of matching pods", name: "one constraint on node, all nodes have the same number of matching pods",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil). SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(), st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
@ -601,7 +602,7 @@ func TestPodTopologySpreadScore(t *testing.T) {
// matching pods spread as 2/1/0/3. // matching pods spread as 2/1/0/3.
name: "one constraint on node, all 4 nodes are candidates", name: "one constraint on node, all 4 nodes are candidates",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil). SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(), st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
@ -628,7 +629,7 @@ func TestPodTopologySpreadScore(t *testing.T) {
{ {
name: "one constraint on node, all 4 nodes are candidates, maxSkew=2", name: "one constraint on node, all 4 nodes are candidates, maxSkew=2",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(2, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil). SpreadConstraint(2, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
// matching pods spread as 2/1/0/3. // matching pods spread as 2/1/0/3.
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
@ -656,7 +657,7 @@ func TestPodTopologySpreadScore(t *testing.T) {
{ {
name: "one constraint on node, all 4 nodes are candidates, maxSkew=3", name: "one constraint on node, all 4 nodes are candidates, maxSkew=3",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(3, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil). SpreadConstraint(3, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
// matching pods spread as 4/3/2/1. // matching pods spread as 4/3/2/1.
@ -723,7 +724,7 @@ func TestPodTopologySpreadScore(t *testing.T) {
// matching pods spread as 4/2/1/~3~ (node4 is not a candidate) // matching pods spread as 4/2/1/~3~ (node4 is not a candidate)
name: "one constraint on node, 3 out of 4 nodes are candidates", name: "one constraint on node, 3 out of 4 nodes are candidates",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil). SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(), st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
@ -755,7 +756,7 @@ func TestPodTopologySpreadScore(t *testing.T) {
// matching pods spread as 4/?2?/1/~3~, total = 4+?+1 = 5 (as node2 is problematic) // matching pods spread as 4/?2?/1/~3~, total = 4+?+1 = 5 (as node2 is problematic)
name: "one constraint on node, 3 out of 4 nodes are candidates, one node doesn't match topology key", name: "one constraint on node, 3 out of 4 nodes are candidates, one node doesn't match topology key",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil). SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(), st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
@ -787,7 +788,7 @@ func TestPodTopologySpreadScore(t *testing.T) {
// matching pods spread as 4/2/1/~3~ // matching pods spread as 4/2/1/~3~
name: "one constraint on zone, 3 out of 4 nodes are candidates", name: "one constraint on zone, 3 out of 4 nodes are candidates",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.ScheduleAnyway, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(), st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
@ -819,8 +820,8 @@ func TestPodTopologySpreadScore(t *testing.T) {
// matching pods spread as 2/~1~/2/~4~. // matching pods spread as 2/~1~/2/~4~.
name: "two Constraints on zone and node, 2 out of 4 nodes are candidates", name: "two Constraints on zone and node, 2 out of 4 nodes are candidates",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", v1.ScheduleAnyway, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil). SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(), st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
@ -859,8 +860,8 @@ func TestPodTopologySpreadScore(t *testing.T) {
// For the second constraint (node): the matching pods spread as 0/1/0/1 // For the second constraint (node): the matching pods spread as 0/1/0/1
name: "two Constraints on zone and node, with different labelSelectors", name: "two Constraints on zone and node, with different labelSelectors",
pod: st.MakePod().Name("p").Label("foo", "").Label("bar", ""). pod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").
SpreadConstraint(1, "zone", v1.ScheduleAnyway, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, barSelector, nil, nil, nil). SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, barSelector, nil, nil, nil, nil).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(), st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
@ -887,8 +888,8 @@ func TestPodTopologySpreadScore(t *testing.T) {
// For the second constraint (node): the matching pods spread as 0/1/0/1 // For the second constraint (node): the matching pods spread as 0/1/0/1
name: "two Constraints on zone and node, with different labelSelectors, some nodes have 0 pods", name: "two Constraints on zone and node, with different labelSelectors, some nodes have 0 pods",
pod: st.MakePod().Name("p").Label("foo", "").Label("bar", ""). pod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").
SpreadConstraint(1, "zone", v1.ScheduleAnyway, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, barSelector, nil, nil, nil). SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, barSelector, nil, nil, nil, nil).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p-b1").Node("node-b").Label("bar", "").Obj(), st.MakePod().Name("p-b1").Node("node-b").Label("bar", "").Obj(),
@ -914,8 +915,8 @@ func TestPodTopologySpreadScore(t *testing.T) {
// For the second constraint (node): the matching pods spread as 0/1/0/~1~ // For the second constraint (node): the matching pods spread as 0/1/0/~1~
name: "two Constraints on zone and node, with different labelSelectors, 3 out of 4 nodes are candidates", name: "two Constraints on zone and node, with different labelSelectors, 3 out of 4 nodes are candidates",
pod: st.MakePod().Name("p").Label("foo", "").Label("bar", ""). pod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").
SpreadConstraint(1, "zone", v1.ScheduleAnyway, fooSelector, nil, nil, nil). SpreadConstraint(1, "zone", v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, barSelector, nil, nil, nil). SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, barSelector, nil, nil, nil, nil).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(), st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
@ -940,7 +941,7 @@ func TestPodTopologySpreadScore(t *testing.T) {
{ {
name: "existing pods in a different namespace do not count", name: "existing pods in a different namespace do not count",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil). SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p-a1").Namespace("ns1").Node("node-a").Label("foo", "").Obj(), st.MakePod().Name("p-a1").Namespace("ns1").Node("node-a").Label("foo", "").Obj(),
@ -960,7 +961,7 @@ func TestPodTopologySpreadScore(t *testing.T) {
{ {
name: "terminating Pods should be excluded", name: "terminating Pods should be excluded",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil). SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label(v1.LabelHostname, "node-a").Obj(), st.MakeNode().Name("node-a").Label(v1.LabelHostname, "node-a").Obj(),
@ -987,6 +988,7 @@ func TestPodTopologySpreadScore(t *testing.T) {
pointer.Int32(10), // larger than the number of domains(3) pointer.Int32(10), // larger than the number of domains(3)
nil, nil,
nil, nil,
nil,
).Obj(), ).Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("node", "node-a").Obj(),
@ -1010,7 +1012,7 @@ func TestPodTopologySpreadScore(t *testing.T) {
name: "NodeAffinityPolicy honoed with labelSelectors", name: "NodeAffinityPolicy honoed with labelSelectors",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
NodeSelector(map[string]string{"foo": ""}). NodeSelector(map[string]string{"foo": ""}).
SpreadConstraint(1, "node", v1.ScheduleAnyway, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(), st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(),
@ -1034,7 +1036,7 @@ func TestPodTopologySpreadScore(t *testing.T) {
name: "NodeAffinityPolicy ignored with labelSelectors", name: "NodeAffinityPolicy ignored with labelSelectors",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
NodeSelector(map[string]string{"foo": ""}). NodeSelector(map[string]string{"foo": ""}).
SpreadConstraint(1, "node", v1.ScheduleAnyway, fooSelector, nil, &ignorePolicy, nil). SpreadConstraint(1, "node", v1.ScheduleAnyway, fooSelector, nil, &ignorePolicy, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(), st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(),
@ -1058,7 +1060,7 @@ func TestPodTopologySpreadScore(t *testing.T) {
name: "NodeAffinityPolicy honoed with nodeAffinity", name: "NodeAffinityPolicy honoed with nodeAffinity",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
NodeAffinityIn("foo", []string{""}). NodeAffinityIn("foo", []string{""}).
SpreadConstraint(1, "node", v1.ScheduleAnyway, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(), st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(),
@ -1082,7 +1084,7 @@ func TestPodTopologySpreadScore(t *testing.T) {
name: "NodeAffinityPolicy ignored with nodeAffinity", name: "NodeAffinityPolicy ignored with nodeAffinity",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
NodeAffinityIn("foo", []string{""}). NodeAffinityIn("foo", []string{""}).
SpreadConstraint(1, "node", v1.ScheduleAnyway, fooSelector, nil, &ignorePolicy, nil). SpreadConstraint(1, "node", v1.ScheduleAnyway, fooSelector, nil, &ignorePolicy, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(), st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(),
@ -1105,7 +1107,7 @@ func TestPodTopologySpreadScore(t *testing.T) {
{ {
name: "NodeTaintsPolicy honored", name: "NodeTaintsPolicy honored",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "node", v1.ScheduleAnyway, fooSelector, nil, nil, &honorPolicy). SpreadConstraint(1, "node", v1.ScheduleAnyway, fooSelector, nil, nil, &honorPolicy, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("node", "node-a").Obj(),
@ -1128,7 +1130,7 @@ func TestPodTopologySpreadScore(t *testing.T) {
{ {
name: "NodeTaintsPolicy ignored", name: "NodeTaintsPolicy ignored",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "node", v1.ScheduleAnyway, fooSelector, nil, nil, nil). SpreadConstraint(1, "node", v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
nodes: []*v1.Node{ nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("node", "node-a").Obj(),
@ -1148,6 +1150,85 @@ func TestPodTopologySpreadScore(t *testing.T) {
}, },
enableNodeInclustionPolicy: true, enableNodeInclustionPolicy: true,
}, },
{
name: "matchLabelKeys ignored when feature gate disabled",
pod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").Label("baz", "").
SpreadConstraint(1, "zone", v1.ScheduleAnyway, fooSelector, nil, nil, nil, []string{"baz"}).
SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, barSelector, nil, nil, nil, []string{"baz"}).
Obj(),
existingPods: []*v1.Pod{
st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Label("bar", "").Obj(),
st.MakePod().Name("p-y1").Node("node-c").Label("foo", "").Obj(),
st.MakePod().Name("p-y2").Node("node-c").Label("bar", "").Obj(),
},
nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label(v1.LabelHostname, "node-a").Obj(),
st.MakeNode().Name("node-b").Label("zone", "zone1").Label(v1.LabelHostname, "node-b").Obj(),
st.MakeNode().Name("node-c").Label("zone", "zone2").Label(v1.LabelHostname, "node-c").Obj(),
st.MakeNode().Name("node-d").Label("zone", "zone2").Label(v1.LabelHostname, "node-d").Obj(),
},
want: []framework.NodeScore{
{Name: "node-a", Score: 60},
{Name: "node-b", Score: 20},
{Name: "node-c", Score: 60},
{Name: "node-d", Score: 100},
},
enableMatchLabelKeys: false,
},
{
name: "matchLabelKeys ANDed with LabelSelector when LabelSelector is empty",
pod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").
SpreadConstraint(1, "zone", v1.ScheduleAnyway, st.MakeLabelSelector().Obj(), nil, nil, nil, []string{"foo"}).
SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, st.MakeLabelSelector().Obj(), nil, nil, nil, []string{"bar"}).
Obj(),
existingPods: []*v1.Pod{
st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Label("bar", "").Obj(),
st.MakePod().Name("p-y1").Node("node-c").Label("foo", "").Obj(),
st.MakePod().Name("p-y2").Node("node-c").Label("bar", "").Obj(),
},
nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label(v1.LabelHostname, "node-a").Obj(),
st.MakeNode().Name("node-b").Label("zone", "zone1").Label(v1.LabelHostname, "node-b").Obj(),
st.MakeNode().Name("node-c").Label("zone", "zone2").Label(v1.LabelHostname, "node-c").Obj(),
st.MakeNode().Name("node-d").Label("zone", "zone2").Label(v1.LabelHostname, "node-d").Obj(),
},
want: []framework.NodeScore{
{Name: "node-a", Score: 60},
{Name: "node-b", Score: 20},
{Name: "node-c", Score: 60},
{Name: "node-d", Score: 100},
},
enableMatchLabelKeys: true,
},
{
name: "matchLabelKeys ANDed with LabelSelector when LabelSelector isn't empty",
pod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").Label("baz", "").
SpreadConstraint(1, "zone", v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, barSelector, nil, nil, nil, []string{"baz"}).
Obj(),
existingPods: []*v1.Pod{
st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Label("bar", "").Label("baz", "").Obj(),
st.MakePod().Name("p-c1").Node("node-c").Label("foo", "").Obj(),
st.MakePod().Name("p-c2").Node("node-c").Label("bar", "").Obj(),
st.MakePod().Name("p-d3").Node("node-c").Label("bar", "").Label("baz", "").Obj(),
},
nodes: []*v1.Node{
st.MakeNode().Name("node-a").Label("zone", "zone1").Label(v1.LabelHostname, "node-a").Obj(),
st.MakeNode().Name("node-b").Label("zone", "zone1").Label(v1.LabelHostname, "node-b").Obj(),
st.MakeNode().Name("node-c").Label("zone", "zone2").Label(v1.LabelHostname, "node-c").Obj(),
st.MakeNode().Name("node-d").Label("zone", "zone2").Label(v1.LabelHostname, "node-d").Obj(),
},
want: []framework.NodeScore{
{Name: "node-a", Score: 60},
{Name: "node-b", Score: 20},
{Name: "node-c", Score: 60},
{Name: "node-d", Score: 100},
},
enableMatchLabelKeys: true,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@ -1159,6 +1240,7 @@ func TestPodTopologySpreadScore(t *testing.T) {
pl := plugintesting.SetupPluginWithInformers(ctx, t, podTopologySpreadFunc, &config.PodTopologySpreadArgs{DefaultingType: config.SystemDefaulting}, cache.NewSnapshot(tt.existingPods, allNodes), tt.objs) pl := plugintesting.SetupPluginWithInformers(ctx, t, podTopologySpreadFunc, &config.PodTopologySpreadArgs{DefaultingType: config.SystemDefaulting}, cache.NewSnapshot(tt.existingPods, allNodes), tt.objs)
p := pl.(*PodTopologySpread) p := pl.(*PodTopologySpread)
p.enableNodeInclusionPolicyInPodTopologySpread = tt.enableNodeInclustionPolicy p.enableNodeInclusionPolicyInPodTopologySpread = tt.enableNodeInclustionPolicy
p.enableMatchLabelKeysInPodTopologySpread = tt.enableMatchLabelKeys
status := p.PreScore(context.Background(), state, tt.pod, tt.nodes) status := p.PreScore(context.Background(), state, tt.pod, tt.nodes)
if !status.IsSuccess() { if !status.IsSuccess() {
@ -1199,7 +1281,7 @@ func BenchmarkTestPodTopologySpreadScore(b *testing.B) {
{ {
name: "1000nodes/single-constraint-zone", name: "1000nodes/single-constraint-zone",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, v1.LabelTopologyZone, v1.ScheduleAnyway, fooSelector, nil, nil, nil). SpreadConstraint(1, v1.LabelTopologyZone, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
existingPodsNum: 10000, existingPodsNum: 10000,
allNodesNum: 1000, allNodesNum: 1000,
@ -1208,7 +1290,7 @@ func BenchmarkTestPodTopologySpreadScore(b *testing.B) {
{ {
name: "1000nodes/single-constraint-node", name: "1000nodes/single-constraint-node",
pod: st.MakePod().Name("p").Label("foo", ""). pod: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil). SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
Obj(), Obj(),
existingPodsNum: 10000, existingPodsNum: 10000,
allNodesNum: 1000, allNodesNum: 1000,
@ -1217,8 +1299,8 @@ func BenchmarkTestPodTopologySpreadScore(b *testing.B) {
{ {
name: "1000nodes/two-Constraints-zone-node", name: "1000nodes/two-Constraints-zone-node",
pod: st.MakePod().Name("p").Label("foo", "").Label("bar", ""). pod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").
SpreadConstraint(1, v1.LabelTopologyZone, v1.ScheduleAnyway, fooSelector, nil, nil, nil). SpreadConstraint(1, v1.LabelTopologyZone, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, barSelector, nil, nil, nil). SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, barSelector, nil, nil, nil, nil).
Obj(), Obj(),
existingPodsNum: 10000, existingPodsNum: 10000,
allNodesNum: 1000, allNodesNum: 1000,

View File

@ -49,6 +49,7 @@ func NewInTreeRegistry() runtime.Registry {
EnableVolumeCapacityPriority: feature.DefaultFeatureGate.Enabled(features.VolumeCapacityPriority), EnableVolumeCapacityPriority: feature.DefaultFeatureGate.Enabled(features.VolumeCapacityPriority),
EnableMinDomainsInPodTopologySpread: feature.DefaultFeatureGate.Enabled(features.MinDomainsInPodTopologySpread), EnableMinDomainsInPodTopologySpread: feature.DefaultFeatureGate.Enabled(features.MinDomainsInPodTopologySpread),
EnableNodeInclusionPolicyInPodTopologySpread: feature.DefaultFeatureGate.Enabled(features.NodeInclusionPolicyInPodTopologySpread), EnableNodeInclusionPolicyInPodTopologySpread: feature.DefaultFeatureGate.Enabled(features.NodeInclusionPolicyInPodTopologySpread),
EnableMatchLabelKeysInPodTopologySpread: feature.DefaultFeatureGate.Enabled(features.MatchLabelKeysInPodTopologySpread),
} }
return runtime.Registry{ return runtime.Registry{

View File

@ -1759,7 +1759,7 @@ func TestSchedulerSchedulePod(t *testing.T) {
Operator: metav1.LabelSelectorOpExists, Operator: metav1.LabelSelectorOpExists,
}, },
}, },
}, nil, nil, nil).Obj(), }, nil, nil, nil, nil).Obj(),
pods: []*v1.Pod{ pods: []*v1.Pod{
st.MakePod().Name("pod1").UID("pod1").Label("foo", "").Node("node1").Phase(v1.PodRunning).Obj(), st.MakePod().Name("pod1").UID("pod1").Label("foo", "").Node("node1").Phase(v1.PodRunning).Obj(),
}, },
@ -1786,7 +1786,7 @@ func TestSchedulerSchedulePod(t *testing.T) {
Operator: metav1.LabelSelectorOpExists, Operator: metav1.LabelSelectorOpExists,
}, },
}, },
}, nil, nil, nil).Obj(), }, nil, nil, nil, nil).Obj(),
pods: []*v1.Pod{ pods: []*v1.Pod{
st.MakePod().Name("pod1a").UID("pod1a").Label("foo", "").Node("node1").Phase(v1.PodRunning).Obj(), st.MakePod().Name("pod1a").UID("pod1a").Label("foo", "").Node("node1").Phase(v1.PodRunning).Obj(),
st.MakePod().Name("pod1b").UID("pod1b").Label("foo", "").Node("node1").Phase(v1.PodRunning).Obj(), st.MakePod().Name("pod1b").UID("pod1b").Label("foo", "").Node("node1").Phase(v1.PodRunning).Obj(),

View File

@ -547,7 +547,7 @@ func (p *PodWrapper) PodAntiAffinityNotIn(labelKey, topologyKey string, vals []s
// SpreadConstraint constructs a TopologySpreadConstraint object and injects // SpreadConstraint constructs a TopologySpreadConstraint object and injects
// into the inner pod. // into the inner pod.
func (p *PodWrapper) SpreadConstraint(maxSkew int, tpKey string, mode v1.UnsatisfiableConstraintAction, selector *metav1.LabelSelector, minDomains *int32, nodeAffinityPolicy, nodeTaintsPolicy *v1.NodeInclusionPolicy) *PodWrapper { func (p *PodWrapper) SpreadConstraint(maxSkew int, tpKey string, mode v1.UnsatisfiableConstraintAction, selector *metav1.LabelSelector, minDomains *int32, nodeAffinityPolicy, nodeTaintsPolicy *v1.NodeInclusionPolicy, matchLabelKeys []string) *PodWrapper {
c := v1.TopologySpreadConstraint{ c := v1.TopologySpreadConstraint{
MaxSkew: int32(maxSkew), MaxSkew: int32(maxSkew),
TopologyKey: tpKey, TopologyKey: tpKey,
@ -556,6 +556,7 @@ func (p *PodWrapper) SpreadConstraint(maxSkew int, tpKey string, mode v1.Unsatis
MinDomains: minDomains, MinDomains: minDomains,
NodeAffinityPolicy: nodeAffinityPolicy, NodeAffinityPolicy: nodeAffinityPolicy,
NodeTaintsPolicy: nodeTaintsPolicy, NodeTaintsPolicy: nodeTaintsPolicy,
MatchLabelKeys: matchLabelKeys,
} }
p.Spec.TopologySpreadConstraints = append(p.Spec.TopologySpreadConstraints, c) p.Spec.TopologySpreadConstraints = append(p.Spec.TopologySpreadConstraints, c)
return p return p

File diff suppressed because it is too large Load Diff

View File

@ -5499,6 +5499,16 @@ message TopologySpreadConstraint {
// This is a alpha-level feature enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. // This is a alpha-level feature enabled by the NodeInclusionPolicyInPodTopologySpread feature flag.
// +optional // +optional
optional string nodeTaintsPolicy = 7; optional string nodeTaintsPolicy = 7;
// MatchLabelKeys is a set of pod label keys to select the pods over which
// spreading will be calculated. The keys are used to lookup values from the
// incoming pod labels, those key-value labels are ANDed with labelSelector
// to select the group of existing pods over which spreading will be calculated
// for the incoming pod. Keys that don't exist in the incoming pod labels will
// be ignored. A null or empty list means only match against labelSelector.
// +listType=atomic
// +optional
repeated string matchLabelKeys = 8;
} }
// TypedLocalObjectReference contains enough information to let you locate the // TypedLocalObjectReference contains enough information to let you locate the

View File

@ -3454,6 +3454,15 @@ type TopologySpreadConstraint struct {
// This is a alpha-level feature enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. // This is a alpha-level feature enabled by the NodeInclusionPolicyInPodTopologySpread feature flag.
// +optional // +optional
NodeTaintsPolicy *NodeInclusionPolicy `json:"nodeTaintsPolicy,omitempty" protobuf:"bytes,7,opt,name=nodeTaintsPolicy"` NodeTaintsPolicy *NodeInclusionPolicy `json:"nodeTaintsPolicy,omitempty" protobuf:"bytes,7,opt,name=nodeTaintsPolicy"`
// MatchLabelKeys is a set of pod label keys to select the pods over which
// spreading will be calculated. The keys are used to lookup values from the
// incoming pod labels, those key-value labels are ANDed with labelSelector
// to select the group of existing pods over which spreading will be calculated
// for the incoming pod. Keys that don't exist in the incoming pod labels will
// be ignored. A null or empty list means only match against labelSelector.
// +listType=atomic
// +optional
MatchLabelKeys []string `json:"matchLabelKeys,omitempty" protobuf:"bytes,8,opt,name=matchLabelKeys"`
} }
const ( const (

View File

@ -2408,6 +2408,7 @@ var map_TopologySpreadConstraint = map[string]string{
"minDomains": "MinDomains indicates a minimum number of eligible domains. When the number of eligible domains with matching topology keys is less than minDomains, Pod Topology Spread treats \"global minimum\" as 0, and then the calculation of Skew is performed. And when the number of eligible domains with matching topology keys equals or greater than minDomains, this value has no effect on scheduling. As a result, when the number of eligible domains is less than minDomains, scheduler won't schedule more than maxSkew Pods to those domains. If value is nil, the constraint behaves as if MinDomains is equal to 1. Valid values are integers greater than 0. When value is not nil, WhenUnsatisfiable must be DoNotSchedule.\n\nFor example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same labelSelector spread as 2/2/2: ", "minDomains": "MinDomains indicates a minimum number of eligible domains. When the number of eligible domains with matching topology keys is less than minDomains, Pod Topology Spread treats \"global minimum\" as 0, and then the calculation of Skew is performed. And when the number of eligible domains with matching topology keys equals or greater than minDomains, this value has no effect on scheduling. As a result, when the number of eligible domains is less than minDomains, scheduler won't schedule more than maxSkew Pods to those domains. If value is nil, the constraint behaves as if MinDomains is equal to 1. Valid values are integers greater than 0. When value is not nil, WhenUnsatisfiable must be DoNotSchedule.\n\nFor example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same labelSelector spread as 2/2/2: ",
"nodeAffinityPolicy": "NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector when calculating pod topology spread skew. Options are: - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations.\n\nIf this value is nil, the behavior is equivalent to the Honor policy. This is a alpha-level feature enabled by the NodeInclusionPolicyInPodTopologySpread feature flag.", "nodeAffinityPolicy": "NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector when calculating pod topology spread skew. Options are: - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations.\n\nIf this value is nil, the behavior is equivalent to the Honor policy. This is a alpha-level feature enabled by the NodeInclusionPolicyInPodTopologySpread feature flag.",
"nodeTaintsPolicy": "NodeTaintsPolicy indicates how we will treat node taints when calculating pod topology spread skew. Options are: - Honor: nodes without taints, along with tainted nodes for which the incoming pod has a toleration, are included. - Ignore: node taints are ignored. All nodes are included.\n\nIf this value is nil, the behavior is equivalent to the Ignore policy. This is a alpha-level feature enabled by the NodeInclusionPolicyInPodTopologySpread feature flag.", "nodeTaintsPolicy": "NodeTaintsPolicy indicates how we will treat node taints when calculating pod topology spread skew. Options are: - Honor: nodes without taints, along with tainted nodes for which the incoming pod has a toleration, are included. - Ignore: node taints are ignored. All nodes are included.\n\nIf this value is nil, the behavior is equivalent to the Ignore policy. This is a alpha-level feature enabled by the NodeInclusionPolicyInPodTopologySpread feature flag.",
"matchLabelKeys": "MatchLabelKeys is a set of pod label keys to select the pods over which spreading will be calculated. The keys are used to lookup values from the incoming pod labels, those key-value labels are ANDed with labelSelector to select the group of existing pods over which spreading will be calculated for the incoming pod. Keys that don't exist in the incoming pod labels will be ignored. A null or empty list means only match against labelSelector.",
} }
func (TopologySpreadConstraint) SwaggerDoc() map[string]string { func (TopologySpreadConstraint) SwaggerDoc() map[string]string {

View File

@ -5664,6 +5664,11 @@ func (in *TopologySpreadConstraint) DeepCopyInto(out *TopologySpreadConstraint)
*out = new(NodeInclusionPolicy) *out = new(NodeInclusionPolicy)
**out = **in **out = **in
} }
if in.MatchLabelKeys != nil {
in, out := &in.MatchLabelKeys, &out.MatchLabelKeys
*out = make([]string, len(*in))
copy(*out, *in)
}
return return
} }

View File

@ -1616,7 +1616,10 @@
}, },
"minDomains": 5, "minDomains": 5,
"nodeAffinityPolicy": "nodeAffinityPolicyValue", "nodeAffinityPolicy": "nodeAffinityPolicyValue",
"nodeTaintsPolicy": "nodeTaintsPolicyValue" "nodeTaintsPolicy": "nodeTaintsPolicyValue",
"matchLabelKeys": [
"matchLabelKeysValue"
]
} }
], ],
"setHostnameAsFQDN": true, "setHostnameAsFQDN": true,

View File

@ -846,6 +846,8 @@ spec:
- valuesValue - valuesValue
matchLabels: matchLabels:
matchLabelsKey: matchLabelsValue matchLabelsKey: matchLabelsValue
matchLabelKeys:
- matchLabelKeysValue
maxSkew: 1 maxSkew: 1
minDomains: 5 minDomains: 5
nodeAffinityPolicy: nodeAffinityPolicyValue nodeAffinityPolicy: nodeAffinityPolicyValue

View File

@ -1617,7 +1617,10 @@
}, },
"minDomains": 5, "minDomains": 5,
"nodeAffinityPolicy": "nodeAffinityPolicyValue", "nodeAffinityPolicy": "nodeAffinityPolicyValue",
"nodeTaintsPolicy": "nodeTaintsPolicyValue" "nodeTaintsPolicy": "nodeTaintsPolicyValue",
"matchLabelKeys": [
"matchLabelKeysValue"
]
} }
], ],
"setHostnameAsFQDN": true, "setHostnameAsFQDN": true,

View File

@ -854,6 +854,8 @@ spec:
- valuesValue - valuesValue
matchLabels: matchLabels:
matchLabelsKey: matchLabelsValue matchLabelsKey: matchLabelsValue
matchLabelKeys:
- matchLabelKeysValue
maxSkew: 1 maxSkew: 1
minDomains: 5 minDomains: 5
nodeAffinityPolicy: nodeAffinityPolicyValue nodeAffinityPolicy: nodeAffinityPolicyValue

View File

@ -1618,7 +1618,10 @@
}, },
"minDomains": 5, "minDomains": 5,
"nodeAffinityPolicy": "nodeAffinityPolicyValue", "nodeAffinityPolicy": "nodeAffinityPolicyValue",
"nodeTaintsPolicy": "nodeTaintsPolicyValue" "nodeTaintsPolicy": "nodeTaintsPolicyValue",
"matchLabelKeys": [
"matchLabelKeysValue"
]
} }
], ],
"setHostnameAsFQDN": true, "setHostnameAsFQDN": true,

View File

@ -846,6 +846,8 @@ spec:
- valuesValue - valuesValue
matchLabels: matchLabels:
matchLabelsKey: matchLabelsValue matchLabelsKey: matchLabelsValue
matchLabelKeys:
- matchLabelKeysValue
maxSkew: 1 maxSkew: 1
minDomains: 5 minDomains: 5
nodeAffinityPolicy: nodeAffinityPolicyValue nodeAffinityPolicy: nodeAffinityPolicyValue

View File

@ -1617,7 +1617,10 @@
}, },
"minDomains": 5, "minDomains": 5,
"nodeAffinityPolicy": "nodeAffinityPolicyValue", "nodeAffinityPolicy": "nodeAffinityPolicyValue",
"nodeTaintsPolicy": "nodeTaintsPolicyValue" "nodeTaintsPolicy": "nodeTaintsPolicyValue",
"matchLabelKeys": [
"matchLabelKeysValue"
]
} }
], ],
"setHostnameAsFQDN": true, "setHostnameAsFQDN": true,

View File

@ -852,6 +852,8 @@ spec:
- valuesValue - valuesValue
matchLabels: matchLabels:
matchLabelsKey: matchLabelsValue matchLabelsKey: matchLabelsValue
matchLabelKeys:
- matchLabelKeysValue
maxSkew: 1 maxSkew: 1
minDomains: 5 minDomains: 5
nodeAffinityPolicy: nodeAffinityPolicyValue nodeAffinityPolicy: nodeAffinityPolicyValue

View File

@ -1617,7 +1617,10 @@
}, },
"minDomains": 5, "minDomains": 5,
"nodeAffinityPolicy": "nodeAffinityPolicyValue", "nodeAffinityPolicy": "nodeAffinityPolicyValue",
"nodeTaintsPolicy": "nodeTaintsPolicyValue" "nodeTaintsPolicy": "nodeTaintsPolicyValue",
"matchLabelKeys": [
"matchLabelKeysValue"
]
} }
], ],
"setHostnameAsFQDN": true, "setHostnameAsFQDN": true,

View File

@ -856,6 +856,8 @@ spec:
- valuesValue - valuesValue
matchLabels: matchLabels:
matchLabelsKey: matchLabelsValue matchLabelsKey: matchLabelsValue
matchLabelKeys:
- matchLabelKeysValue
maxSkew: 1 maxSkew: 1
minDomains: 5 minDomains: 5
nodeAffinityPolicy: nodeAffinityPolicyValue nodeAffinityPolicy: nodeAffinityPolicyValue

View File

@ -1617,7 +1617,10 @@
}, },
"minDomains": 5, "minDomains": 5,
"nodeAffinityPolicy": "nodeAffinityPolicyValue", "nodeAffinityPolicy": "nodeAffinityPolicyValue",
"nodeTaintsPolicy": "nodeTaintsPolicyValue" "nodeTaintsPolicy": "nodeTaintsPolicyValue",
"matchLabelKeys": [
"matchLabelKeysValue"
]
} }
], ],
"setHostnameAsFQDN": true, "setHostnameAsFQDN": true,

View File

@ -852,6 +852,8 @@ spec:
- valuesValue - valuesValue
matchLabels: matchLabels:
matchLabelsKey: matchLabelsValue matchLabelsKey: matchLabelsValue
matchLabelKeys:
- matchLabelKeysValue
maxSkew: 1 maxSkew: 1
minDomains: 5 minDomains: 5
nodeAffinityPolicy: nodeAffinityPolicyValue nodeAffinityPolicy: nodeAffinityPolicyValue

View File

@ -1616,7 +1616,10 @@
}, },
"minDomains": 5, "minDomains": 5,
"nodeAffinityPolicy": "nodeAffinityPolicyValue", "nodeAffinityPolicy": "nodeAffinityPolicyValue",
"nodeTaintsPolicy": "nodeTaintsPolicyValue" "nodeTaintsPolicy": "nodeTaintsPolicyValue",
"matchLabelKeys": [
"matchLabelKeysValue"
]
} }
], ],
"setHostnameAsFQDN": true, "setHostnameAsFQDN": true,

View File

@ -846,6 +846,8 @@ spec:
- valuesValue - valuesValue
matchLabels: matchLabels:
matchLabelsKey: matchLabelsValue matchLabelsKey: matchLabelsValue
matchLabelKeys:
- matchLabelKeysValue
maxSkew: 1 maxSkew: 1
minDomains: 5 minDomains: 5
nodeAffinityPolicy: nodeAffinityPolicyValue nodeAffinityPolicy: nodeAffinityPolicyValue

View File

@ -1617,7 +1617,10 @@
}, },
"minDomains": 5, "minDomains": 5,
"nodeAffinityPolicy": "nodeAffinityPolicyValue", "nodeAffinityPolicy": "nodeAffinityPolicyValue",
"nodeTaintsPolicy": "nodeTaintsPolicyValue" "nodeTaintsPolicy": "nodeTaintsPolicyValue",
"matchLabelKeys": [
"matchLabelKeysValue"
]
} }
], ],
"setHostnameAsFQDN": true, "setHostnameAsFQDN": true,

View File

@ -854,6 +854,8 @@ spec:
- valuesValue - valuesValue
matchLabels: matchLabels:
matchLabelsKey: matchLabelsValue matchLabelsKey: matchLabelsValue
matchLabelKeys:
- matchLabelKeysValue
maxSkew: 1 maxSkew: 1
minDomains: 5 minDomains: 5
nodeAffinityPolicy: nodeAffinityPolicyValue nodeAffinityPolicy: nodeAffinityPolicyValue

View File

@ -1618,7 +1618,10 @@
}, },
"minDomains": 5, "minDomains": 5,
"nodeAffinityPolicy": "nodeAffinityPolicyValue", "nodeAffinityPolicy": "nodeAffinityPolicyValue",
"nodeTaintsPolicy": "nodeTaintsPolicyValue" "nodeTaintsPolicy": "nodeTaintsPolicyValue",
"matchLabelKeys": [
"matchLabelKeysValue"
]
} }
], ],
"setHostnameAsFQDN": true, "setHostnameAsFQDN": true,

View File

@ -846,6 +846,8 @@ spec:
- valuesValue - valuesValue
matchLabels: matchLabels:
matchLabelsKey: matchLabelsValue matchLabelsKey: matchLabelsValue
matchLabelKeys:
- matchLabelKeysValue
maxSkew: 1 maxSkew: 1
minDomains: 5 minDomains: 5
nodeAffinityPolicy: nodeAffinityPolicyValue nodeAffinityPolicy: nodeAffinityPolicyValue

View File

@ -1617,7 +1617,10 @@
}, },
"minDomains": 5, "minDomains": 5,
"nodeAffinityPolicy": "nodeAffinityPolicyValue", "nodeAffinityPolicy": "nodeAffinityPolicyValue",
"nodeTaintsPolicy": "nodeTaintsPolicyValue" "nodeTaintsPolicy": "nodeTaintsPolicyValue",
"matchLabelKeys": [
"matchLabelKeysValue"
]
} }
], ],
"setHostnameAsFQDN": true, "setHostnameAsFQDN": true,

View File

@ -852,6 +852,8 @@ spec:
- valuesValue - valuesValue
matchLabels: matchLabels:
matchLabelsKey: matchLabelsValue matchLabelsKey: matchLabelsValue
matchLabelKeys:
- matchLabelKeysValue
maxSkew: 1 maxSkew: 1
minDomains: 5 minDomains: 5
nodeAffinityPolicy: nodeAffinityPolicyValue nodeAffinityPolicy: nodeAffinityPolicyValue

View File

@ -1670,7 +1670,10 @@
}, },
"minDomains": 5, "minDomains": 5,
"nodeAffinityPolicy": "nodeAffinityPolicyValue", "nodeAffinityPolicy": "nodeAffinityPolicyValue",
"nodeTaintsPolicy": "nodeTaintsPolicyValue" "nodeTaintsPolicy": "nodeTaintsPolicyValue",
"matchLabelKeys": [
"matchLabelKeysValue"
]
} }
], ],
"setHostnameAsFQDN": true, "setHostnameAsFQDN": true,

View File

@ -887,6 +887,8 @@ spec:
- valuesValue - valuesValue
matchLabels: matchLabels:
matchLabelsKey: matchLabelsValue matchLabelsKey: matchLabelsValue
matchLabelKeys:
- matchLabelKeysValue
maxSkew: 1 maxSkew: 1
minDomains: 5 minDomains: 5
nodeAffinityPolicy: nodeAffinityPolicyValue nodeAffinityPolicy: nodeAffinityPolicyValue

View File

@ -1621,7 +1621,10 @@
}, },
"minDomains": 5, "minDomains": 5,
"nodeAffinityPolicy": "nodeAffinityPolicyValue", "nodeAffinityPolicy": "nodeAffinityPolicyValue",
"nodeTaintsPolicy": "nodeTaintsPolicyValue" "nodeTaintsPolicy": "nodeTaintsPolicyValue",
"matchLabelKeys": [
"matchLabelKeysValue"
]
} }
], ],
"setHostnameAsFQDN": true, "setHostnameAsFQDN": true,

View File

@ -851,6 +851,8 @@ spec:
- valuesValue - valuesValue
matchLabels: matchLabels:
matchLabelsKey: matchLabelsValue matchLabelsKey: matchLabelsValue
matchLabelKeys:
- matchLabelKeysValue
maxSkew: 1 maxSkew: 1
minDomains: 5 minDomains: 5
nodeAffinityPolicy: nodeAffinityPolicyValue nodeAffinityPolicy: nodeAffinityPolicyValue

View File

@ -1670,7 +1670,10 @@
}, },
"minDomains": 5, "minDomains": 5,
"nodeAffinityPolicy": "nodeAffinityPolicyValue", "nodeAffinityPolicy": "nodeAffinityPolicyValue",
"nodeTaintsPolicy": "nodeTaintsPolicyValue" "nodeTaintsPolicy": "nodeTaintsPolicyValue",
"matchLabelKeys": [
"matchLabelKeysValue"
]
} }
], ],
"setHostnameAsFQDN": true, "setHostnameAsFQDN": true,

View File

@ -887,6 +887,8 @@ spec:
- valuesValue - valuesValue
matchLabels: matchLabels:
matchLabelsKey: matchLabelsValue matchLabelsKey: matchLabelsValue
matchLabelKeys:
- matchLabelKeysValue
maxSkew: 1 maxSkew: 1
minDomains: 5 minDomains: 5
nodeAffinityPolicy: nodeAffinityPolicyValue nodeAffinityPolicy: nodeAffinityPolicyValue

View File

@ -1664,7 +1664,10 @@
}, },
"minDomains": 5, "minDomains": 5,
"nodeAffinityPolicy": "nodeAffinityPolicyValue", "nodeAffinityPolicy": "nodeAffinityPolicyValue",
"nodeTaintsPolicy": "nodeTaintsPolicyValue" "nodeTaintsPolicy": "nodeTaintsPolicyValue",
"matchLabelKeys": [
"matchLabelKeysValue"
]
} }
], ],
"setHostnameAsFQDN": true, "setHostnameAsFQDN": true,

View File

@ -884,6 +884,8 @@ template:
- valuesValue - valuesValue
matchLabels: matchLabels:
matchLabelsKey: matchLabelsValue matchLabelsKey: matchLabelsValue
matchLabelKeys:
- matchLabelKeysValue
maxSkew: 1 maxSkew: 1
minDomains: 5 minDomains: 5
nodeAffinityPolicy: nodeAffinityPolicyValue nodeAffinityPolicy: nodeAffinityPolicyValue

View File

@ -1558,7 +1558,10 @@
}, },
"minDomains": 5, "minDomains": 5,
"nodeAffinityPolicy": "nodeAffinityPolicyValue", "nodeAffinityPolicy": "nodeAffinityPolicyValue",
"nodeTaintsPolicy": "nodeTaintsPolicyValue" "nodeTaintsPolicy": "nodeTaintsPolicyValue",
"matchLabelKeys": [
"matchLabelKeysValue"
]
} }
], ],
"setHostnameAsFQDN": true, "setHostnameAsFQDN": true,

View File

@ -802,6 +802,8 @@ spec:
- valuesValue - valuesValue
matchLabels: matchLabels:
matchLabelsKey: matchLabelsValue matchLabelsKey: matchLabelsValue
matchLabelKeys:
- matchLabelKeysValue
maxSkew: 1 maxSkew: 1
minDomains: 5 minDomains: 5
nodeAffinityPolicy: nodeAffinityPolicyValue nodeAffinityPolicy: nodeAffinityPolicyValue

View File

@ -1601,7 +1601,10 @@
}, },
"minDomains": 5, "minDomains": 5,
"nodeAffinityPolicy": "nodeAffinityPolicyValue", "nodeAffinityPolicy": "nodeAffinityPolicyValue",
"nodeTaintsPolicy": "nodeTaintsPolicyValue" "nodeTaintsPolicy": "nodeTaintsPolicyValue",
"matchLabelKeys": [
"matchLabelKeysValue"
]
} }
], ],
"setHostnameAsFQDN": true, "setHostnameAsFQDN": true,

View File

@ -835,6 +835,8 @@ template:
- valuesValue - valuesValue
matchLabels: matchLabels:
matchLabelsKey: matchLabelsValue matchLabelsKey: matchLabelsValue
matchLabelKeys:
- matchLabelKeysValue
maxSkew: 1 maxSkew: 1
minDomains: 5 minDomains: 5
nodeAffinityPolicy: nodeAffinityPolicyValue nodeAffinityPolicy: nodeAffinityPolicyValue

View File

@ -1607,7 +1607,10 @@
}, },
"minDomains": 5, "minDomains": 5,
"nodeAffinityPolicy": "nodeAffinityPolicyValue", "nodeAffinityPolicy": "nodeAffinityPolicyValue",
"nodeTaintsPolicy": "nodeTaintsPolicyValue" "nodeTaintsPolicy": "nodeTaintsPolicyValue",
"matchLabelKeys": [
"matchLabelKeysValue"
]
} }
], ],
"setHostnameAsFQDN": true, "setHostnameAsFQDN": true,

View File

@ -840,6 +840,8 @@ spec:
- valuesValue - valuesValue
matchLabels: matchLabels:
matchLabelsKey: matchLabelsValue matchLabelsKey: matchLabelsValue
matchLabelKeys:
- matchLabelKeysValue
maxSkew: 1 maxSkew: 1
minDomains: 5 minDomains: 5
nodeAffinityPolicy: nodeAffinityPolicyValue nodeAffinityPolicy: nodeAffinityPolicyValue

View File

@ -1616,7 +1616,10 @@
}, },
"minDomains": 5, "minDomains": 5,
"nodeAffinityPolicy": "nodeAffinityPolicyValue", "nodeAffinityPolicy": "nodeAffinityPolicyValue",
"nodeTaintsPolicy": "nodeTaintsPolicyValue" "nodeTaintsPolicy": "nodeTaintsPolicyValue",
"matchLabelKeys": [
"matchLabelKeysValue"
]
} }
], ],
"setHostnameAsFQDN": true, "setHostnameAsFQDN": true,

View File

@ -846,6 +846,8 @@ spec:
- valuesValue - valuesValue
matchLabels: matchLabels:
matchLabelsKey: matchLabelsValue matchLabelsKey: matchLabelsValue
matchLabelKeys:
- matchLabelKeysValue
maxSkew: 1 maxSkew: 1
minDomains: 5 minDomains: 5
nodeAffinityPolicy: nodeAffinityPolicyValue nodeAffinityPolicy: nodeAffinityPolicyValue

View File

@ -1617,7 +1617,10 @@
}, },
"minDomains": 5, "minDomains": 5,
"nodeAffinityPolicy": "nodeAffinityPolicyValue", "nodeAffinityPolicy": "nodeAffinityPolicyValue",
"nodeTaintsPolicy": "nodeTaintsPolicyValue" "nodeTaintsPolicy": "nodeTaintsPolicyValue",
"matchLabelKeys": [
"matchLabelKeysValue"
]
} }
], ],
"setHostnameAsFQDN": true, "setHostnameAsFQDN": true,

View File

@ -856,6 +856,8 @@ spec:
- valuesValue - valuesValue
matchLabels: matchLabels:
matchLabelsKey: matchLabelsValue matchLabelsKey: matchLabelsValue
matchLabelKeys:
- matchLabelKeysValue
maxSkew: 1 maxSkew: 1
minDomains: 5 minDomains: 5
nodeAffinityPolicy: nodeAffinityPolicyValue nodeAffinityPolicy: nodeAffinityPolicyValue

View File

@ -1618,7 +1618,10 @@
}, },
"minDomains": 5, "minDomains": 5,
"nodeAffinityPolicy": "nodeAffinityPolicyValue", "nodeAffinityPolicy": "nodeAffinityPolicyValue",
"nodeTaintsPolicy": "nodeTaintsPolicyValue" "nodeTaintsPolicy": "nodeTaintsPolicyValue",
"matchLabelKeys": [
"matchLabelKeysValue"
]
} }
], ],
"setHostnameAsFQDN": true, "setHostnameAsFQDN": true,

View File

@ -846,6 +846,8 @@ spec:
- valuesValue - valuesValue
matchLabels: matchLabels:
matchLabelsKey: matchLabelsValue matchLabelsKey: matchLabelsValue
matchLabelKeys:
- matchLabelKeysValue
maxSkew: 1 maxSkew: 1
minDomains: 5 minDomains: 5
nodeAffinityPolicy: nodeAffinityPolicyValue nodeAffinityPolicy: nodeAffinityPolicyValue

View File

@ -33,6 +33,7 @@ type TopologySpreadConstraintApplyConfiguration struct {
MinDomains *int32 `json:"minDomains,omitempty"` MinDomains *int32 `json:"minDomains,omitempty"`
NodeAffinityPolicy *v1.NodeInclusionPolicy `json:"nodeAffinityPolicy,omitempty"` NodeAffinityPolicy *v1.NodeInclusionPolicy `json:"nodeAffinityPolicy,omitempty"`
NodeTaintsPolicy *v1.NodeInclusionPolicy `json:"nodeTaintsPolicy,omitempty"` NodeTaintsPolicy *v1.NodeInclusionPolicy `json:"nodeTaintsPolicy,omitempty"`
MatchLabelKeys []string `json:"matchLabelKeys,omitempty"`
} }
// TopologySpreadConstraintApplyConfiguration constructs an declarative configuration of the TopologySpreadConstraint type for use with // TopologySpreadConstraintApplyConfiguration constructs an declarative configuration of the TopologySpreadConstraint type for use with
@ -96,3 +97,13 @@ func (b *TopologySpreadConstraintApplyConfiguration) WithNodeTaintsPolicy(value
b.NodeTaintsPolicy = &value b.NodeTaintsPolicy = &value
return b return b
} }
// WithMatchLabelKeys adds the given value to the MatchLabelKeys field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, values provided by each call will be appended to the MatchLabelKeys field.
func (b *TopologySpreadConstraintApplyConfiguration) WithMatchLabelKeys(values ...string) *TopologySpreadConstraintApplyConfiguration {
for i := range values {
b.MatchLabelKeys = append(b.MatchLabelKeys, values[i])
}
return b
}

View File

@ -6858,6 +6858,12 @@ var schemaYAML = typed.YAMLObject(`types:
- name: labelSelector - name: labelSelector
type: type:
namedType: io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector namedType: io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector
- name: matchLabelKeys
type:
list:
elementType:
scalar: string
elementRelationship: atomic
- name: maxSkew - name: maxSkew
type: type:
scalar: numeric scalar: numeric

View File

@ -1076,12 +1076,13 @@ func TestPodTopologySpreadFilter(t *testing.T) {
candidateNodes []string // nodes expected to schedule onto candidateNodes []string // nodes expected to schedule onto
enableMinDomains bool enableMinDomains bool
enableNodeInclustionPolicy bool enableNodeInclustionPolicy bool
enableMatchLabelKeys bool
}{ }{
// note: naming starts at index 0 // note: naming starts at index 0
{ {
name: "place pod on a 1/1/0/1 cluster with MaxSkew=1, node-2 is the only fit", name: "place pod on a 1/1/0/1 cluster with MaxSkew=1, node-2 is the only fit",
incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).
SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil). SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p0").Node("node-0").Label("foo", "").Container(pause).Obj(), st.MakePod().Name("p0").Node("node-0").Label("foo", "").Container(pause).Obj(),
@ -1095,7 +1096,7 @@ func TestPodTopologySpreadFilter(t *testing.T) {
{ {
name: "place pod on a 2/0/0/1 cluster with MaxSkew=2, node-{1,2,3} are good fits", name: "place pod on a 2/0/0/1 cluster with MaxSkew=2, node-{1,2,3} are good fits",
incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).
SpreadConstraint(2, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil). SpreadConstraint(2, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p0a").Node("node-0").Label("foo", "").Container(pause).Obj(), st.MakePod().Name("p0a").Node("node-0").Label("foo", "").Container(pause).Obj(),
@ -1110,7 +1111,7 @@ func TestPodTopologySpreadFilter(t *testing.T) {
name: "pod is required to be placed on zone0, so only node-1 fits", name: "pod is required to be placed on zone0, so only node-1 fits",
incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).
NodeAffinityIn("zone", []string{"zone-0"}). NodeAffinityIn("zone", []string{"zone-0"}).
SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil). SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p0").Node("node-0").Label("foo", "").Container(pause).Obj(), st.MakePod().Name("p0").Node("node-0").Label("foo", "").Container(pause).Obj(),
@ -1123,8 +1124,8 @@ func TestPodTopologySpreadFilter(t *testing.T) {
{ {
name: "two constraints: pod can only be placed to zone-1/node-2", name: "two constraints: pod can only be placed to zone-1/node-2",
incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).
SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil). SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil). SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p0").Node("node-0").Label("foo", "").Container(pause).Obj(), st.MakePod().Name("p0").Node("node-0").Label("foo", "").Container(pause).Obj(),
@ -1140,8 +1141,8 @@ func TestPodTopologySpreadFilter(t *testing.T) {
name: "pod cannot be placed onto any node", name: "pod cannot be placed onto any node",
incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).
NodeAffinityNotIn("node", []string{"node-0"}). // mock a 3-node cluster NodeAffinityNotIn("node", []string{"node-0"}). // mock a 3-node cluster
SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil). SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil). SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(), st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(),
@ -1157,8 +1158,8 @@ func TestPodTopologySpreadFilter(t *testing.T) {
name: "high priority pod can preempt others", name: "high priority pod can preempt others",
incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).Priority(100). incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).Priority(100).
NodeAffinityNotIn("node", []string{"node-0"}). // mock a 3-node cluster NodeAffinityNotIn("node", []string{"node-0"}). // mock a 3-node cluster
SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil). SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil). SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().ZeroTerminationGracePeriod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(), st.MakePod().ZeroTerminationGracePeriod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(),
@ -1183,6 +1184,7 @@ func TestPodTopologySpreadFilter(t *testing.T) {
pointer.Int32(4), // larger than the number of domains (= 3) pointer.Int32(4), // larger than the number of domains (= 3)
nil, nil,
nil, nil,
nil,
). ).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
@ -1209,6 +1211,7 @@ func TestPodTopologySpreadFilter(t *testing.T) {
pointer.Int32(2), // smaller than the number of domains (= 3) pointer.Int32(2), // smaller than the number of domains (= 3)
nil, nil,
nil, nil,
nil,
). ).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
@ -1234,6 +1237,7 @@ func TestPodTopologySpreadFilter(t *testing.T) {
pointer.Int32(3), // larger than the number of domains(2) pointer.Int32(3), // larger than the number of domains(2)
nil, nil,
nil, nil,
nil,
).Obj(), ).Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p1a").Node("node-0").Label("foo", "").Container(pause).Obj(), st.MakePod().Name("p1a").Node("node-0").Label("foo", "").Container(pause).Obj(),
@ -1256,6 +1260,7 @@ func TestPodTopologySpreadFilter(t *testing.T) {
pointer.Int32(1), // smaller than the number of domains(2) pointer.Int32(1), // smaller than the number of domains(2)
nil, nil,
nil, nil,
nil,
).Obj(), ).Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(), st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(),
@ -1271,7 +1276,7 @@ func TestPodTopologySpreadFilter(t *testing.T) {
name: "NodeAffinityPolicy honored with labelSelectors, pods spread across zone as 2/1", name: "NodeAffinityPolicy honored with labelSelectors, pods spread across zone as 2/1",
incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).
NodeSelector(map[string]string{"foo": ""}). NodeSelector(map[string]string{"foo": ""}).
SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(), st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(),
@ -1293,7 +1298,7 @@ func TestPodTopologySpreadFilter(t *testing.T) {
name: "NodeAffinityPolicy ignored with nodeAffinity, pods spread across zone as 1/~2~", name: "NodeAffinityPolicy ignored with nodeAffinity, pods spread across zone as 1/~2~",
incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).
NodeAffinityIn("foo", []string{""}). NodeAffinityIn("foo", []string{""}).
SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, &ignorePolicy, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, &ignorePolicy, nil, nil).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(), st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(),
@ -1313,7 +1318,7 @@ func TestPodTopologySpreadFilter(t *testing.T) {
{ {
name: "NodeTaintsPolicy honored, pods spread across zone as 2/1", name: "NodeTaintsPolicy honored, pods spread across zone as 2/1",
incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).
SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, &honorPolicy). SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, &honorPolicy, nil).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(), st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(),
@ -1334,7 +1339,7 @@ func TestPodTopologySpreadFilter(t *testing.T) {
{ {
name: "NodeTaintsPolicy ignored, pods spread across zone as 2/2", name: "NodeTaintsPolicy ignored, pods spread across zone as 2/2",
incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).
SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(), st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(),
@ -1359,8 +1364,8 @@ func TestPodTopologySpreadFilter(t *testing.T) {
name: "two node inclusion Constraints, zone: honor/ignore, node: honor/ignore", name: "two node inclusion Constraints, zone: honor/ignore, node: honor/ignore",
incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).
NodeSelector(map[string]string{"foo": ""}). NodeSelector(map[string]string{"foo": ""}).
SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(), st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(),
@ -1385,8 +1390,8 @@ func TestPodTopologySpreadFilter(t *testing.T) {
name: "feature gate disabled, two node inclusion Constraints, zone: honor/ignore, node: honor/ignore", name: "feature gate disabled, two node inclusion Constraints, zone: honor/ignore, node: honor/ignore",
incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).
NodeSelector(map[string]string{"foo": ""}). NodeSelector(map[string]string{"foo": ""}).
SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil). SpreadConstraint(1, "node", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(), st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(),
@ -1411,8 +1416,8 @@ func TestPodTopologySpreadFilter(t *testing.T) {
name: "two node inclusion Constraints, zone: ignore/ignore, node: honor/honor", name: "two node inclusion Constraints, zone: ignore/ignore, node: honor/honor",
incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).
NodeSelector(map[string]string{"foo": ""}). NodeSelector(map[string]string{"foo": ""}).
SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, &ignorePolicy, nil). SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, &ignorePolicy, nil, nil).
SpreadConstraint(1, "node", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, &honorPolicy). SpreadConstraint(1, "node", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, &honorPolicy, nil).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(), st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(),
@ -1430,11 +1435,57 @@ func TestPodTopologySpreadFilter(t *testing.T) {
candidateNodes: []string{"node-1", "node-4"}, candidateNodes: []string{"node-1", "node-4"},
enableNodeInclustionPolicy: true, enableNodeInclustionPolicy: true,
}, },
{
name: "matchLabelKeys ignored when feature gate disabled, pods spread across zone as 2/1",
incomingPod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").Container(pause).
SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, []string{"bar"}).
Obj(),
existingPods: []*v1.Pod{
st.MakePod().Name("p1a").Node("node-0").Label("foo", "").Container(pause).Obj(),
st.MakePod().Name("p2a").Node("node-1").Label("foo", "").Container(pause).Obj(),
st.MakePod().Name("p3a").Node("node-2").Label("foo", "").Label("bar", "").Container(pause).Obj(),
},
fits: true,
nodes: defaultNodes,
candidateNodes: []string{"node-2", "node-3"},
enableMatchLabelKeys: false,
},
{
name: "matchLabelKeys ANDed with LabelSelector when LabelSelector isn't empty, pods spread across zone as 0/1",
incomingPod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").Container(pause).
SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, []string{"bar"}).
Obj(),
existingPods: []*v1.Pod{
st.MakePod().Name("p1a").Node("node-0").Label("foo", "").Container(pause).Obj(),
st.MakePod().Name("p2a").Node("node-1").Label("foo", "").Container(pause).Obj(),
st.MakePod().Name("p3a").Node("node-2").Label("foo", "").Label("bar", "").Container(pause).Obj(),
},
fits: true,
nodes: defaultNodes,
candidateNodes: []string{"node-0", "node-1"},
enableMatchLabelKeys: true,
},
{
name: "matchLabelKeys ANDed with LabelSelector when LabelSelector is empty, pods spread across zone as 2/1",
incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).
SpreadConstraint(1, "zone", v1.DoNotSchedule, st.MakeLabelSelector().Obj(), nil, nil, nil, []string{"foo"}).
Obj(),
existingPods: []*v1.Pod{
st.MakePod().Name("p1a").Node("node-0").Label("foo", "").Container(pause).Obj(),
st.MakePod().Name("p2a").Node("node-1").Label("foo", "").Container(pause).Obj(),
st.MakePod().Name("p3a").Node("node-2").Label("foo", "").Container(pause).Obj(),
},
fits: true,
nodes: defaultNodes,
candidateNodes: []string{"node-2", "node-3"},
enableMatchLabelKeys: true,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MinDomainsInPodTopologySpread, tt.enableMinDomains)() defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MinDomainsInPodTopologySpread, tt.enableMinDomains)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.NodeInclusionPolicyInPodTopologySpread, tt.enableNodeInclustionPolicy)() defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.NodeInclusionPolicyInPodTopologySpread, tt.enableNodeInclustionPolicy)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MatchLabelKeysInPodTopologySpread, tt.enableMatchLabelKeys)()
testCtx := initTest(t, "pts-predicate") testCtx := initTest(t, "pts-predicate")
cs := testCtx.ClientSet cs := testCtx.ClientSet

View File

@ -449,13 +449,14 @@ func TestPodTopologySpreadScoring(t *testing.T) {
nodes []*v1.Node nodes []*v1.Node
want []string // nodes expected to schedule onto want []string // nodes expected to schedule onto
enableNodeInclustionPolicy bool enableNodeInclustionPolicy bool
enableMatchLabelKeys bool
}{ }{
// note: naming starts at index 0 // note: naming starts at index 0
// the symbol ~X~ means that node is infeasible // the symbol ~X~ means that node is infeasible
{ {
name: "place pod on a ~0~/1/2/3 cluster with MaxSkew=1, node-1 is the preferred fit", name: "place pod on a ~0~/1/2/3 cluster with MaxSkew=1, node-1 is the preferred fit",
incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).
SpreadConstraint(1, "node", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil). SpreadConstraint(1, "node", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p1").Node("node-1").Label("foo", "").Container(pause).Obj(), st.MakePod().Name("p1").Node("node-1").Label("foo", "").Container(pause).Obj(),
@ -472,8 +473,8 @@ func TestPodTopologySpreadScoring(t *testing.T) {
{ {
name: "combined with hardSpread constraint on a ~4~/0/1/2 cluster", name: "combined with hardSpread constraint on a ~4~/0/1/2 cluster",
incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).
SpreadConstraint(1, "node", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil). SpreadConstraint(1, "node", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil). SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p0a").Node("node-0").Label("foo", "").Container(pause).Obj(), st.MakePod().Name("p0a").Node("node-0").Label("foo", "").Container(pause).Obj(),
@ -495,8 +496,8 @@ func TestPodTopologySpreadScoring(t *testing.T) {
name: "soft constraint with two node inclusion Constraints, zone: honor/ignore, node: honor/ignore", name: "soft constraint with two node inclusion Constraints, zone: honor/ignore, node: honor/ignore",
incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).
NodeSelector(map[string]string{"foo": ""}). NodeSelector(map[string]string{"foo": ""}).
SpreadConstraint(1, "zone", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil). SpreadConstraint(1, "zone", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
SpreadConstraint(1, "node", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil). SpreadConstraint(1, "node", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(), st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(),
@ -521,8 +522,8 @@ func TestPodTopologySpreadScoring(t *testing.T) {
name: "soft constraint with two node inclusion Constraints, zone: ignore/ignore, node: honor/honor", name: "soft constraint with two node inclusion Constraints, zone: ignore/ignore, node: honor/honor",
incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause). incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).
NodeSelector(map[string]string{"foo": ""}). NodeSelector(map[string]string{"foo": ""}).
SpreadConstraint(1, "zone", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, &ignorePolicy, nil). SpreadConstraint(1, "zone", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, &ignorePolicy, nil, nil).
SpreadConstraint(1, "node", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, &honorPolicy). SpreadConstraint(1, "node", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, &honorPolicy, nil).
Obj(), Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(), st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(),
@ -540,10 +541,65 @@ func TestPodTopologySpreadScoring(t *testing.T) {
want: []string{"node-3"}, want: []string{"node-3"},
enableNodeInclustionPolicy: true, enableNodeInclustionPolicy: true,
}, },
{
name: "matchLabelKeys ignored when feature gate disabled, node-1 is the preferred fit",
incomingPod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").Container(pause).
SpreadConstraint(1, "node", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, []string{"bar"}).
Obj(),
existingPods: []*v1.Pod{
st.MakePod().Name("p1").Node("node-1").Label("foo", "").Label("bar", "").Container(pause).Obj(),
st.MakePod().Name("p2a").Node("node-2").Label("foo", "").Container(pause).Obj(),
st.MakePod().Name("p2b").Node("node-2").Label("foo", "").Container(pause).Obj(),
st.MakePod().Name("p3a").Node("node-3").Label("foo", "").Label("bar", "").Container(pause).Obj(),
st.MakePod().Name("p3b").Node("node-3").Label("foo", "").Label("bar", "").Container(pause).Obj(),
st.MakePod().Name("p3c").Node("node-3").Label("foo", "").Container(pause).Obj(),
},
fits: true,
nodes: defaultNodes,
want: []string{"node-1"},
enableMatchLabelKeys: false,
},
{
name: "matchLabelKeys ANDed with LabelSelector when LabelSelector isn't empty, node-2 is the preferred fit",
incomingPod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").Container(pause).
SpreadConstraint(1, "node", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, []string{"bar"}).
Obj(),
existingPods: []*v1.Pod{
st.MakePod().Name("p1").Node("node-1").Label("foo", "").Label("bar", "").Container(pause).Obj(),
st.MakePod().Name("p2a").Node("node-2").Label("foo", "").Container(pause).Obj(),
st.MakePod().Name("p2b").Node("node-2").Label("foo", "").Container(pause).Obj(),
st.MakePod().Name("p3a").Node("node-3").Label("foo", "").Label("bar", "").Container(pause).Obj(),
st.MakePod().Name("p3b").Node("node-3").Label("foo", "").Label("bar", "").Container(pause).Obj(),
st.MakePod().Name("p3c").Node("node-3").Label("foo", "").Container(pause).Obj(),
},
fits: true,
nodes: defaultNodes,
want: []string{"node-2"},
enableMatchLabelKeys: true,
},
{
name: "matchLabelKeys ANDed with LabelSelector when LabelSelector is empty, node-1 is the preferred fit",
incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).
SpreadConstraint(1, "node", softSpread, st.MakeLabelSelector().Obj(), nil, nil, nil, []string{"foo"}).
Obj(),
existingPods: []*v1.Pod{
st.MakePod().Name("p1").Node("node-1").Label("foo", "").Container(pause).Obj(),
st.MakePod().Name("p2a").Node("node-2").Label("foo", "").Container(pause).Obj(),
st.MakePod().Name("p2b").Node("node-2").Label("foo", "").Container(pause).Obj(),
st.MakePod().Name("p3a").Node("node-3").Label("foo", "").Container(pause).Obj(),
st.MakePod().Name("p3b").Node("node-3").Label("foo", "").Container(pause).Obj(),
st.MakePod().Name("p3c").Node("node-3").Label("foo", "").Container(pause).Obj(),
},
fits: true,
nodes: defaultNodes,
want: []string{"node-1"},
enableMatchLabelKeys: true,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.NodeInclusionPolicyInPodTopologySpread, tt.enableNodeInclustionPolicy)() defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.NodeInclusionPolicyInPodTopologySpread, tt.enableNodeInclustionPolicy)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MatchLabelKeysInPodTopologySpread, tt.enableMatchLabelKeys)()
testCtx := initTestSchedulerForPriorityTest(t, podtopologyspread.Name) testCtx := initTestSchedulerForPriorityTest(t, podtopologyspread.Name)
defer testutils.CleanupTest(t, testCtx) defer testutils.CleanupTest(t, testCtx)