feature(scheduler): implement matchLabelKeys in PodAffinity and PodAntiAffinity

This commit is contained in:
Kensei Nakada
2023-02-26 04:25:59 +00:00
parent dc8b57d8a7
commit d5d3c26337
81 changed files with 4922 additions and 1112 deletions

View File

@@ -10198,7 +10198,206 @@ func TestValidatePod(t *testing.T) {
DNSPolicy: core.DNSClusterFirst,
},
},
"MatchLabelKeys/MismatchLabelKeys in required PodAffinity": {
ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
Spec: core.PodSpec{
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst,
Affinity: &core.Affinity{
PodAffinity: &core.PodAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
{
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "key",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"value1", "value2"},
},
{
Key: "key2",
Operator: metav1.LabelSelectorOpIn,
Values: []string{"value1"},
},
{
Key: "key3",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"value1"},
},
},
},
TopologyKey: "k8s.io/zone",
MatchLabelKeys: []string{"key2"},
MismatchLabelKeys: []string{"key3"},
},
},
},
},
},
},
"MatchLabelKeys/MismatchLabelKeys in preferred PodAffinity": {
ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
Spec: core.PodSpec{
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst,
Affinity: &core.Affinity{
PodAffinity: &core.PodAffinity{
PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
{
Weight: 10,
PodAffinityTerm: core.PodAffinityTerm{
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "key",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"value1", "value2"},
},
{
Key: "key2",
Operator: metav1.LabelSelectorOpIn,
Values: []string{"value1"},
},
{
Key: "key3",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"value1"},
},
},
},
TopologyKey: "k8s.io/zone",
MatchLabelKeys: []string{"key2"},
MismatchLabelKeys: []string{"key3"},
},
},
},
},
},
},
},
"MatchLabelKeys/MismatchLabelKeys in required PodAntiAffinity": {
ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
Spec: core.PodSpec{
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst,
Affinity: &core.Affinity{
PodAntiAffinity: &core.PodAntiAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
{
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "key",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"value1", "value2"},
},
{
Key: "key2",
Operator: metav1.LabelSelectorOpIn,
Values: []string{"value1"},
},
{
Key: "key3",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"value1"},
},
},
},
TopologyKey: "k8s.io/zone",
MatchLabelKeys: []string{"key2"},
MismatchLabelKeys: []string{"key3"},
},
},
},
},
},
},
"MatchLabelKeys/MismatchLabelKeys in preferred PodAntiAffinity": {
ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
Spec: core.PodSpec{
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst,
Affinity: &core.Affinity{
PodAntiAffinity: &core.PodAntiAffinity{
PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
{
Weight: 10,
PodAffinityTerm: core.PodAffinityTerm{
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "key",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"value1", "value2"},
},
{
Key: "key2",
Operator: metav1.LabelSelectorOpIn,
Values: []string{"value1"},
},
{
Key: "key3",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"value1"},
},
},
},
TopologyKey: "k8s.io/zone",
MatchLabelKeys: []string{"key2"},
MismatchLabelKeys: []string{"key3"},
},
},
},
},
},
},
},
"LabelSelector can have the same key as MismatchLabelKeys": {
// Note: On the contrary, in case of matchLabelKeys, keys in matchLabelKeys are not allowed to be specified in labelSelector by users.
ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
Spec: core.PodSpec{
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst,
Affinity: &core.Affinity{
PodAffinity: &core.PodAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
{
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "key",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"value1", "value2"},
},
{
// This is the same key as in MismatchLabelKeys
// but it's allowed.
Key: "key2",
Operator: metav1.LabelSelectorOpIn,
Values: []string{"value1"},
},
{
Key: "key2",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"value1"},
},
},
},
TopologyKey: "k8s.io/zone",
MismatchLabelKeys: []string{"key2"},
},
},
},
},
},
},
}
for k, v := range successCases {
t.Run(k, func(t *testing.T) {
if errs := ValidatePodCreate(&v, PodValidationOptions{}); len(errs) != 0 {
@@ -10657,6 +10856,510 @@ func TestValidatePod(t *testing.T) {
}),
},
},
"invalid soft pod affinity, key in MatchLabelKeys isn't correctly defined": {
expectedError: "prefix part must be non-empty",
spec: core.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
},
Spec: validPodSpec(&core.Affinity{
PodAffinity: &core.PodAffinity{
PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
{
Weight: 10,
PodAffinityTerm: core.PodAffinityTerm{
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "key",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"value1", "value2"},
},
},
},
TopologyKey: "k8s.io/zone",
MatchLabelKeys: []string{"/simple"},
},
},
},
},
}),
},
},
"invalid hard pod affinity, key in MatchLabelKeys isn't correctly defined": {
expectedError: "prefix part must be non-empty",
spec: core.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
},
Spec: validPodSpec(&core.Affinity{
PodAffinity: &core.PodAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
{
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "key",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"value1", "value2"},
},
},
},
TopologyKey: "k8s.io/zone",
MatchLabelKeys: []string{"/simple"},
},
},
},
}),
},
},
"invalid soft pod anti-affinity, key in MatchLabelKeys isn't correctly defined": {
expectedError: "prefix part must be non-empty",
spec: core.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
},
Spec: validPodSpec(&core.Affinity{
PodAntiAffinity: &core.PodAntiAffinity{
PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
{
Weight: 10,
PodAffinityTerm: core.PodAffinityTerm{
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "key",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"value1", "value2"},
},
},
},
TopologyKey: "k8s.io/zone",
MatchLabelKeys: []string{"/simple"},
},
},
},
},
}),
},
},
"invalid hard pod anti-affinity, key in MatchLabelKeys isn't correctly defined": {
expectedError: "prefix part must be non-empty",
spec: core.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
},
Spec: validPodSpec(&core.Affinity{
PodAntiAffinity: &core.PodAntiAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
{
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "key",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"value1", "value2"},
},
},
},
TopologyKey: "k8s.io/zone",
MatchLabelKeys: []string{"/simple"},
},
},
},
}),
},
},
"invalid soft pod affinity, key in MismatchLabelKeys isn't correctly defined": {
expectedError: "prefix part must be non-empty",
spec: core.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
},
Spec: validPodSpec(&core.Affinity{
PodAffinity: &core.PodAffinity{
PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
{
Weight: 10,
PodAffinityTerm: core.PodAffinityTerm{
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "key",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"value1", "value2"},
},
},
},
TopologyKey: "k8s.io/zone",
MismatchLabelKeys: []string{"/simple"},
},
},
},
},
}),
},
},
"invalid hard pod affinity, key in MismatchLabelKeys isn't correctly defined": {
expectedError: "prefix part must be non-empty",
spec: core.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
},
Spec: validPodSpec(&core.Affinity{
PodAffinity: &core.PodAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
{
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "key",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"value1", "value2"},
},
},
},
TopologyKey: "k8s.io/zone",
MismatchLabelKeys: []string{"/simple"},
},
},
},
}),
},
},
"invalid soft pod anti-affinity, key in MismatchLabelKeys isn't correctly defined": {
expectedError: "prefix part must be non-empty",
spec: core.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
},
Spec: validPodSpec(&core.Affinity{
PodAntiAffinity: &core.PodAntiAffinity{
PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
{
Weight: 10,
PodAffinityTerm: core.PodAffinityTerm{
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "key",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"value1", "value2"},
},
},
},
TopologyKey: "k8s.io/zone",
MismatchLabelKeys: []string{"/simple"},
},
},
},
},
}),
},
},
"invalid hard pod anti-affinity, key in MismatchLabelKeys isn't correctly defined": {
expectedError: "prefix part must be non-empty",
spec: core.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
},
Spec: validPodSpec(&core.Affinity{
PodAntiAffinity: &core.PodAntiAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
{
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "key",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"value1", "value2"},
},
},
},
TopologyKey: "k8s.io/zone",
MismatchLabelKeys: []string{"/simple"},
},
},
},
}),
},
},
"invalid soft pod affinity, key exists in both matchLabelKeys and labelSelector": {
expectedError: "exists in both matchLabelKeys and labelSelector",
spec: core.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
Labels: map[string]string{"key": "value1"},
},
Spec: validPodSpec(&core.Affinity{
PodAffinity: &core.PodAffinity{
PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
{
Weight: 10,
PodAffinityTerm: core.PodAffinityTerm{
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
// This one should be created from MatchLabelKeys.
{
Key: "key",
Operator: metav1.LabelSelectorOpIn,
Values: []string{"value1"},
},
{
Key: "key",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"value2"},
},
},
},
TopologyKey: "k8s.io/zone",
MatchLabelKeys: []string{"key"},
},
},
},
},
}),
},
},
"invalid hard pod affinity, key exists in both matchLabelKeys and labelSelector": {
expectedError: "exists in both matchLabelKeys and labelSelector",
spec: core.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
Labels: map[string]string{"key": "value1"},
},
Spec: validPodSpec(&core.Affinity{
PodAffinity: &core.PodAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
{
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
// This one should be created from MatchLabelKeys.
{
Key: "key",
Operator: metav1.LabelSelectorOpIn,
Values: []string{"value1"},
},
{
Key: "key",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"value2"},
},
},
},
TopologyKey: "k8s.io/zone",
MatchLabelKeys: []string{"key"},
},
},
},
}),
},
},
"invalid soft pod anti-affinity, key exists in both matchLabelKeys and labelSelector": {
expectedError: "exists in both matchLabelKeys and labelSelector",
spec: core.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
Labels: map[string]string{"key": "value1"},
},
Spec: validPodSpec(&core.Affinity{
PodAntiAffinity: &core.PodAntiAffinity{
PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
{
Weight: 10,
PodAffinityTerm: core.PodAffinityTerm{
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
// This one should be created from MatchLabelKeys.
{
Key: "key",
Operator: metav1.LabelSelectorOpIn,
Values: []string{"value1"},
},
{
Key: "key",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"value2"},
},
},
},
TopologyKey: "k8s.io/zone",
MatchLabelKeys: []string{"key"},
},
},
},
},
}),
},
},
"invalid hard pod anti-affinity, key exists in both matchLabelKeys and labelSelector": {
expectedError: "exists in both matchLabelKeys and labelSelector",
spec: core.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
Labels: map[string]string{"key": "value1"},
},
Spec: validPodSpec(&core.Affinity{
PodAntiAffinity: &core.PodAntiAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
{
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
// This one should be created from MatchLabelKeys.
{
Key: "key",
Operator: metav1.LabelSelectorOpIn,
Values: []string{"value1"},
},
{
Key: "key",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"value2"},
},
},
},
TopologyKey: "k8s.io/zone",
MatchLabelKeys: []string{"key"},
},
},
},
}),
},
},
"invalid soft pod affinity, key exists in both MatchLabelKeys and MismatchLabelKeys": {
expectedError: "exists in both matchLabelKeys and mismatchLabelKeys",
spec: core.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
},
Spec: validPodSpec(&core.Affinity{
PodAffinity: &core.PodAffinity{
PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
{
Weight: 10,
PodAffinityTerm: core.PodAffinityTerm{
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "key",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"value1", "value2"},
},
},
},
TopologyKey: "k8s.io/zone",
MatchLabelKeys: []string{"samekey"},
MismatchLabelKeys: []string{"samekey"},
},
},
},
},
}),
},
},
"invalid hard pod affinity, key exists in both MatchLabelKeys and MismatchLabelKeys": {
expectedError: "exists in both matchLabelKeys and mismatchLabelKeys",
spec: core.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
},
Spec: validPodSpec(&core.Affinity{
PodAffinity: &core.PodAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
{
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "key",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"value1", "value2"},
},
},
},
TopologyKey: "k8s.io/zone",
MatchLabelKeys: []string{"samekey"},
MismatchLabelKeys: []string{"samekey"},
},
},
},
}),
},
},
"invalid soft pod anti-affinity, key exists in both MatchLabelKeys and MismatchLabelKeys": {
expectedError: "exists in both matchLabelKeys and mismatchLabelKeys",
spec: core.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
},
Spec: validPodSpec(&core.Affinity{
PodAntiAffinity: &core.PodAntiAffinity{
PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
{
Weight: 10,
PodAffinityTerm: core.PodAffinityTerm{
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "key",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"value1", "value2"},
},
},
},
TopologyKey: "k8s.io/zone",
MatchLabelKeys: []string{"samekey"},
MismatchLabelKeys: []string{"samekey"},
},
},
},
},
}),
},
},
"invalid hard pod anti-affinity, key exists in both MatchLabelKeys and MismatchLabelKeys": {
expectedError: "exists in both matchLabelKeys and mismatchLabelKeys",
spec: core.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
},
Spec: validPodSpec(&core.Affinity{
PodAntiAffinity: &core.PodAntiAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
{
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "key",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"value1", "value2"},
},
},
},
TopologyKey: "k8s.io/zone",
MatchLabelKeys: []string{"samekey"},
MismatchLabelKeys: []string{"samekey"},
},
},
},
}),
},
},
"invalid toleration key": {
expectedError: "spec.tolerations[0].key",
spec: core.Pod{
@@ -11143,7 +11846,7 @@ func TestValidatePod(t *testing.T) {
}
for k, v := range errorCases {
t.Run(k, func(t *testing.T) {
if errs := ValidatePodCreate(&v.spec, PodValidationOptions{AllowInvalidPodDeletionCost: false}); len(errs) == 0 {
if errs := ValidatePodCreate(&v.spec, PodValidationOptions{}); len(errs) == 0 {
t.Errorf("expected failure")
} else if v.expectedError == "" {
t.Errorf("missing expectedError, got %q", errs.ToAggregate().Error())
@@ -21756,11 +22459,13 @@ func TestValidateTopologySpreadConstraints(t *testing.T) {
WhenUnsatisfiable: core.DoNotSchedule,
MatchLabelKeys: []string{"foo"},
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{{
Key: "foo",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"value1", "value2"},
}},
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "foo",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"value1", "value2"},
},
},
},
}},
wantFieldErrors: field.ErrorList{field.Invalid(fieldPathMatchLabelKeys.Index(0), "foo", "exists in both matchLabelKeys and labelSelector")},