Merge pull request #86022 from Huang-Wei/sched-reserve-multi-errs

Return all scheduler predicate failures instead of the first one
This commit is contained in:
Kubernetes Prow Robot 2019-12-09 14:00:26 -08:00 committed by GitHub
commit d842c194c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 291 additions and 94 deletions

View File

@ -103,15 +103,14 @@ var unresolvablePredicateFailureErrors = map[PredicateFailureReason]struct{}{
ErrVolumeBindConflict: {},
}
// UnresolvablePredicateExists checks if there is at least one unresolvable predicate failure reason, if true
// returns the first one in the list.
func UnresolvablePredicateExists(reasons []PredicateFailureReason) PredicateFailureReason {
// UnresolvablePredicateExists checks if there is at least one unresolvable predicate failure reason.
func UnresolvablePredicateExists(reasons []PredicateFailureReason) bool {
for _, r := range reasons {
if _, ok := unresolvablePredicateFailureErrors[r]; ok {
return r
return true
}
}
return nil
return false
}
// InsufficientResourceError is an error type that indicates what kind of resource limit is

View File

@ -96,7 +96,9 @@ func (f *FitError) Error() string {
}
for _, status := range f.FilteredNodesStatuses {
reasons[status.Message()]++
for _, reason := range status.Reasons() {
reasons[reason]++
}
}
sortReasonsHistogram := func() []string {
@ -1207,7 +1209,7 @@ func nodesWherePreemptionMightHelp(nodeNameToInfo map[string]*schedulernodeinfo.
// to rely less on such assumptions in the code when checking does not impose
// significant overhead.
// Also, we currently assume all failures returned by extender as resolvable.
if predicates.UnresolvablePredicateExists(failedPredicates) == nil {
if !predicates.UnresolvablePredicateExists(failedPredicates) {
klog.V(3).Infof("Node %v is a potential node for preemption.", name)
potentialNodes = append(potentialNodes, node.Node())
}

View File

@ -33,9 +33,7 @@ import (
)
var (
defaultNamespace = ""
unschedulable = framework.NewStatus(framework.Unschedulable, predicates.ErrPodAffinityNotMatch.GetReason())
unschedulableAndUnresolvable = framework.NewStatus(framework.UnschedulableAndUnresolvable, predicates.ErrPodAffinityRulesNotMatch.GetReason())
defaultNamespace = ""
)
func createPodWithAffinityTerms(namespace, nodeName string, labels map[string]string, affinity, antiAffinity []v1.PodAffinityTerm) *v1.Pod {
@ -135,10 +133,14 @@ func TestSingleNode(t *testing.T) {
Namespaces: []string{"DiffNameSpace"},
},
}, nil),
pods: []*v1.Pod{{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabel, Namespace: "ns"}}},
node: &node1,
name: "Does not satisfy the PodAffinity with labelSelector because of diff Namespace",
wantStatus: unschedulableAndUnresolvable,
pods: []*v1.Pod{{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabel, Namespace: "ns"}}},
node: &node1,
name: "Does not satisfy the PodAffinity with labelSelector because of diff Namespace",
wantStatus: framework.NewStatus(
framework.UnschedulableAndUnresolvable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrPodAffinityRulesNotMatch.GetReason(),
),
},
{
pod: createPodWithAffinityTerms(defaultNamespace, "", podLabel,
@ -155,10 +157,14 @@ func TestSingleNode(t *testing.T) {
},
},
}, nil),
pods: []*v1.Pod{{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabel}}},
node: &node1,
name: "Doesn't satisfy the PodAffinity because of unmatching labelSelector with the existing pod",
wantStatus: unschedulableAndUnresolvable,
pods: []*v1.Pod{{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabel}}},
node: &node1,
name: "Doesn't satisfy the PodAffinity because of unmatching labelSelector with the existing pod",
wantStatus: framework.NewStatus(
framework.UnschedulableAndUnresolvable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrPodAffinityRulesNotMatch.GetReason(),
),
},
{
pod: createPodWithAffinityTerms(defaultNamespace, "", podLabel2,
@ -230,10 +236,14 @@ func TestSingleNode(t *testing.T) {
TopologyKey: "region",
},
}, nil),
pods: []*v1.Pod{{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabel}}},
node: &node1,
name: "The labelSelector requirements(items of matchExpressions) are ANDed, the pod cannot schedule onto the node because one of the matchExpression item don't match.",
wantStatus: unschedulableAndUnresolvable,
pods: []*v1.Pod{{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabel}}},
node: &node1,
name: "The labelSelector requirements(items of matchExpressions) are ANDed, the pod cannot schedule onto the node because one of the matchExpression item don't match.",
wantStatus: framework.NewStatus(
framework.UnschedulableAndUnresolvable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrPodAffinityRulesNotMatch.GetReason(),
),
},
{
pod: createPodWithAffinityTerms(defaultNamespace, "", podLabel2,
@ -349,10 +359,14 @@ func TestSingleNode(t *testing.T) {
TopologyKey: "zone",
},
}),
pods: []*v1.Pod{{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabel}}},
node: &node1,
name: "satisfies the PodAffinity but doesn't satisfy the PodAntiAffinity with the existing pod",
wantStatus: unschedulable,
pods: []*v1.Pod{{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabel}}},
node: &node1,
name: "satisfies the PodAffinity but doesn't satisfy the PodAntiAffinity with the existing pod",
wantStatus: framework.NewStatus(
framework.Unschedulable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrPodAntiAffinityRulesNotMatch.GetReason(),
),
},
{
pod: createPodWithAffinityTerms(defaultNamespace, "", podLabel,
@ -401,9 +415,13 @@ func TestSingleNode(t *testing.T) {
},
}),
},
node: &node1,
name: "satisfies the PodAffinity and PodAntiAffinity but doesn't satisfy PodAntiAffinity symmetry with the existing pod",
wantStatus: unschedulable,
node: &node1,
name: "satisfies the PodAffinity and PodAntiAffinity but doesn't satisfy PodAntiAffinity symmetry with the existing pod",
wantStatus: framework.NewStatus(
framework.Unschedulable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrExistingPodsAntiAffinityRulesNotMatch.GetReason(),
),
},
{
pod: createPodWithAffinityTerms(defaultNamespace, "", podLabel,
@ -421,10 +439,14 @@ func TestSingleNode(t *testing.T) {
TopologyKey: "region",
},
}, nil),
pods: []*v1.Pod{{Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabel}}},
node: &node1,
name: "pod matches its own Label in PodAffinity and that matches the existing pod Labels",
wantStatus: unschedulableAndUnresolvable,
pods: []*v1.Pod{{Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabel}}},
node: &node1,
name: "pod matches its own Label in PodAffinity and that matches the existing pod Labels",
wantStatus: framework.NewStatus(
framework.UnschedulableAndUnresolvable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrPodAffinityRulesNotMatch.GetReason(),
),
},
{
pod: &v1.Pod{
@ -449,9 +471,13 @@ func TestSingleNode(t *testing.T) {
},
}),
},
node: &node1,
name: "verify that PodAntiAffinity from existing pod is respected when pod has no AntiAffinity constraints. doesn't satisfy PodAntiAffinity symmetry with the existing pod",
wantStatus: unschedulable,
node: &node1,
name: "verify that PodAntiAffinity from existing pod is respected when pod has no AntiAffinity constraints. doesn't satisfy PodAntiAffinity symmetry with the existing pod",
wantStatus: framework.NewStatus(
framework.Unschedulable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrExistingPodsAntiAffinityRulesNotMatch.GetReason(),
),
},
{
pod: &v1.Pod{
@ -521,9 +547,13 @@ func TestSingleNode(t *testing.T) {
},
}),
},
node: &node1,
name: "satisfies the PodAntiAffinity with existing pod but doesn't satisfy PodAntiAffinity symmetry with incoming pod",
wantStatus: unschedulable,
node: &node1,
name: "satisfies the PodAntiAffinity with existing pod but doesn't satisfy PodAntiAffinity symmetry with incoming pod",
wantStatus: framework.NewStatus(
framework.Unschedulable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrPodAntiAffinityRulesNotMatch.GetReason(),
),
},
{
pod: createPodWithAffinityTerms(defaultNamespace, "", podLabel, nil,
@ -567,9 +597,13 @@ func TestSingleNode(t *testing.T) {
},
}),
},
node: &node1,
wantStatus: unschedulable,
name: "PodAntiAffinity symmetry check a1: incoming pod and existing pod partially match each other on AffinityTerms",
node: &node1,
wantStatus: framework.NewStatus(
framework.Unschedulable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrPodAntiAffinityRulesNotMatch.GetReason(),
),
name: "PodAntiAffinity symmetry check a1: incoming pod and existing pod partially match each other on AffinityTerms",
},
{
pod: createPodWithAffinityTerms(defaultNamespace, "", podLabel2, nil,
@ -613,9 +647,13 @@ func TestSingleNode(t *testing.T) {
},
}),
},
node: &node1,
wantStatus: unschedulable,
name: "PodAntiAffinity symmetry check a2: incoming pod and existing pod partially match each other on AffinityTerms",
node: &node1,
wantStatus: framework.NewStatus(
framework.Unschedulable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrExistingPodsAntiAffinityRulesNotMatch.GetReason(),
),
name: "PodAntiAffinity symmetry check a2: incoming pod and existing pod partially match each other on AffinityTerms",
},
{
pod: createPodWithAffinityTerms(defaultNamespace, "", map[string]string{"abc": "", "xyz": ""}, nil,
@ -670,9 +708,13 @@ func TestSingleNode(t *testing.T) {
},
}),
},
node: &node1,
wantStatus: unschedulable,
name: "PodAntiAffinity symmetry check b1: incoming pod and existing pod partially match each other on AffinityTerms",
node: &node1,
wantStatus: framework.NewStatus(
framework.Unschedulable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrExistingPodsAntiAffinityRulesNotMatch.GetReason(),
),
name: "PodAntiAffinity symmetry check b1: incoming pod and existing pod partially match each other on AffinityTerms",
},
{
pod: createPodWithAffinityTerms(defaultNamespace, "", map[string]string{"def": "", "xyz": ""}, nil,
@ -727,9 +769,13 @@ func TestSingleNode(t *testing.T) {
},
}),
},
node: &node1,
wantStatus: unschedulable,
name: "PodAntiAffinity symmetry check b2: incoming pod and existing pod partially match each other on AffinityTerms",
node: &node1,
wantStatus: framework.NewStatus(
framework.Unschedulable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrExistingPodsAntiAffinityRulesNotMatch.GetReason(),
),
name: "PodAntiAffinity symmetry check b2: incoming pod and existing pod partially match each other on AffinityTerms",
},
}
@ -798,8 +844,16 @@ func TestMultipleNodes(t *testing.T) {
{ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: labelRgChinaAzAz1}},
{ObjectMeta: metav1.ObjectMeta{Name: "machine3", Labels: labelRgIndia}},
},
wantStatuses: []*framework.Status{nil, nil, unschedulableAndUnresolvable},
name: "A pod can be scheduled onto all the nodes that have the same topology key & label value with one of them has an existing pod that matches the affinity rules",
wantStatuses: []*framework.Status{
nil,
nil,
framework.NewStatus(
framework.UnschedulableAndUnresolvable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrPodAffinityRulesNotMatch.GetReason(),
),
},
name: "A pod can be scheduled onto all the nodes that have the same topology key & label value with one of them has an existing pod that matches the affinity rules",
},
{
pod: createPodWithAffinityTerms(defaultNamespace, "", map[string]string{"foo": "bar", "service": "securityscan"},
@ -861,8 +915,19 @@ func TestMultipleNodes(t *testing.T) {
{ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: map[string]string{"region": "r1", "hostname": "nodeA"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: map[string]string{"region": "r1", "hostname": "nodeB"}}},
},
wantStatuses: []*framework.Status{unschedulable, unschedulable},
name: "NodeA and nodeB have same topologyKey and label value. NodeA has an existing pod that matches the inter pod affinity rule. The pod can not be scheduled onto nodeA and nodeB.",
wantStatuses: []*framework.Status{
framework.NewStatus(
framework.Unschedulable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrPodAntiAffinityRulesNotMatch.GetReason(),
),
framework.NewStatus(
framework.Unschedulable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrPodAntiAffinityRulesNotMatch.GetReason(),
),
},
name: "NodeA and nodeB have same topologyKey and label value. NodeA has an existing pod that matches the inter pod affinity rule. The pod can not be scheduled onto nodeA and nodeB.",
},
{
pod: createPodWithAffinityTerms(defaultNamespace, "", nil, nil,
@ -899,8 +964,19 @@ func TestMultipleNodes(t *testing.T) {
{ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: map[string]string{"region": "r1", "zone": "z1", "hostname": "nodeA"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: map[string]string{"region": "r1", "zone": "z2", "hostname": "nodeB"}}},
},
wantStatuses: []*framework.Status{unschedulable, unschedulable},
name: "This test ensures that anti-affinity matches a pod when any term of the anti-affinity rule matches a pod.",
wantStatuses: []*framework.Status{
framework.NewStatus(
framework.Unschedulable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrPodAntiAffinityRulesNotMatch.GetReason(),
),
framework.NewStatus(
framework.Unschedulable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrPodAntiAffinityRulesNotMatch.GetReason(),
),
},
name: "This test ensures that anti-affinity matches a pod when any term of the anti-affinity rule matches a pod.",
},
{
pod: createPodWithAffinityTerms(defaultNamespace, "", nil, nil,
@ -926,8 +1002,20 @@ func TestMultipleNodes(t *testing.T) {
{ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: labelRgChinaAzAz1}},
{ObjectMeta: metav1.ObjectMeta{Name: "nodeC", Labels: labelRgIndia}},
},
wantStatuses: []*framework.Status{unschedulable, unschedulable, nil},
name: "NodeA and nodeB have same topologyKey and label value. NodeA has an existing pod that matches the inter pod affinity rule. The pod can not be scheduled onto nodeA and nodeB but can be scheduled onto nodeC",
wantStatuses: []*framework.Status{
framework.NewStatus(
framework.Unschedulable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrPodAntiAffinityRulesNotMatch.GetReason(),
),
framework.NewStatus(
framework.Unschedulable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrPodAntiAffinityRulesNotMatch.GetReason(),
),
nil,
},
name: "NodeA and nodeB have same topologyKey and label value. NodeA has an existing pod that matches the inter pod affinity rule. The pod can not be scheduled onto nodeA and nodeB but can be scheduled onto nodeC",
},
{
pod: createPodWithAffinityTerms("NS1", "", map[string]string{"foo": "123"}, nil,
@ -974,8 +1062,20 @@ func TestMultipleNodes(t *testing.T) {
{ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: labelRgChinaAzAz1}},
{ObjectMeta: metav1.ObjectMeta{Name: "nodeC", Labels: labelRgIndia}},
},
wantStatuses: []*framework.Status{unschedulable, unschedulable, nil},
name: "NodeA and nodeB have same topologyKey and label value. NodeA has an existing pod that matches the inter pod affinity rule. The pod can not be scheduled onto nodeA, nodeB, but can be scheduled onto nodeC (NodeC has an existing pod that match the inter pod affinity rule but in different namespace)",
wantStatuses: []*framework.Status{
framework.NewStatus(
framework.Unschedulable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrPodAntiAffinityRulesNotMatch.GetReason(),
),
framework.NewStatus(
framework.Unschedulable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrPodAntiAffinityRulesNotMatch.GetReason(),
),
nil,
},
name: "NodeA and nodeB have same topologyKey and label value. NodeA has an existing pod that matches the inter pod affinity rule. The pod can not be scheduled onto nodeA, nodeB, but can be scheduled onto nodeC (NodeC has an existing pod that match the inter pod affinity rule but in different namespace)",
},
{
pod: &v1.Pod{
@ -1072,8 +1172,19 @@ func TestMultipleNodes(t *testing.T) {
{ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: map[string]string{"region": "r1", "zone": "z1", "hostname": "nodeA"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: map[string]string{"region": "r1", "zone": "z2", "hostname": "nodeB"}}},
},
wantStatuses: []*framework.Status{unschedulable, unschedulable},
name: "Test existing pod's anti-affinity: incoming pod wouldn't considered as a fit as it violates each existingPod's terms on all nodes",
wantStatuses: []*framework.Status{
framework.NewStatus(
framework.Unschedulable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrExistingPodsAntiAffinityRulesNotMatch.GetReason(),
),
framework.NewStatus(
framework.Unschedulable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrExistingPodsAntiAffinityRulesNotMatch.GetReason(),
),
},
name: "Test existing pod's anti-affinity: incoming pod wouldn't considered as a fit as it violates each existingPod's terms on all nodes",
},
{
pod: createPodWithAffinityTerms(defaultNamespace, "", nil, nil,
@ -1119,8 +1230,19 @@ func TestMultipleNodes(t *testing.T) {
{ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: map[string]string{"region": "r1", "zone": "z1", "hostname": "nodeA"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: map[string]string{"region": "r1", "zone": "z2", "hostname": "nodeB"}}},
},
wantStatuses: []*framework.Status{unschedulable, unschedulable},
name: "Test incoming pod's anti-affinity: incoming pod wouldn't considered as a fit as it at least violates one anti-affinity rule of existingPod",
wantStatuses: []*framework.Status{
framework.NewStatus(
framework.Unschedulable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrPodAntiAffinityRulesNotMatch.GetReason(),
),
framework.NewStatus(
framework.Unschedulable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrPodAntiAffinityRulesNotMatch.GetReason(),
),
},
name: "Test incoming pod's anti-affinity: incoming pod wouldn't considered as a fit as it at least violates one anti-affinity rule of existingPod",
},
{
pod: &v1.Pod{
@ -1157,8 +1279,15 @@ func TestMultipleNodes(t *testing.T) {
{ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: map[string]string{"region": "r1", "zone": "z1", "hostname": "nodeA"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: map[string]string{"region": "r1", "zone": "z2", "hostname": "nodeB"}}},
},
wantStatuses: []*framework.Status{unschedulable, nil},
name: "Test existing pod's anti-affinity: only when labelSelector and topologyKey both match, it's counted as a single term match - case when one term has invalid topologyKey",
wantStatuses: []*framework.Status{
framework.NewStatus(
framework.Unschedulable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrExistingPodsAntiAffinityRulesNotMatch.GetReason(),
),
nil,
},
name: "Test existing pod's anti-affinity: only when labelSelector and topologyKey both match, it's counted as a single term match - case when one term has invalid topologyKey",
},
{
pod: createPodWithAffinityTerms(defaultNamespace, "", nil, nil,
@ -1198,8 +1327,15 @@ func TestMultipleNodes(t *testing.T) {
{ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: map[string]string{"region": "r1", "zone": "z1", "hostname": "nodeA"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: map[string]string{"region": "r1", "zone": "z2", "hostname": "nodeB"}}},
},
wantStatuses: []*framework.Status{unschedulable, nil},
name: "Test incoming pod's anti-affinity: only when labelSelector and topologyKey both match, it's counted as a single term match - case when one term has invalid topologyKey",
wantStatuses: []*framework.Status{
framework.NewStatus(
framework.Unschedulable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrPodAntiAffinityRulesNotMatch.GetReason(),
),
nil,
},
name: "Test incoming pod's anti-affinity: only when labelSelector and topologyKey both match, it's counted as a single term match - case when one term has invalid topologyKey",
},
{
pod: &v1.Pod{
@ -1236,8 +1372,19 @@ func TestMultipleNodes(t *testing.T) {
{ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: map[string]string{"region": "r1", "zone": "z1", "hostname": "nodeA"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: map[string]string{"region": "r1", "zone": "z2", "hostname": "nodeB"}}},
},
wantStatuses: []*framework.Status{unschedulable, unschedulable},
name: "Test existing pod's anti-affinity: only when labelSelector and topologyKey both match, it's counted as a single term match - case when all terms have valid topologyKey",
wantStatuses: []*framework.Status{
framework.NewStatus(
framework.Unschedulable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrExistingPodsAntiAffinityRulesNotMatch.GetReason(),
),
framework.NewStatus(
framework.Unschedulable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrExistingPodsAntiAffinityRulesNotMatch.GetReason(),
),
},
name: "Test existing pod's anti-affinity: only when labelSelector and topologyKey both match, it's counted as a single term match - case when all terms have valid topologyKey",
},
{
pod: createPodWithAffinityTerms(defaultNamespace, "", nil, nil,
@ -1277,8 +1424,19 @@ func TestMultipleNodes(t *testing.T) {
{ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: map[string]string{"region": "r1", "zone": "z1", "hostname": "nodeA"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: map[string]string{"region": "r1", "zone": "z2", "hostname": "nodeB"}}},
},
wantStatuses: []*framework.Status{unschedulable, unschedulable},
name: "Test incoming pod's anti-affinity: only when labelSelector and topologyKey both match, it's counted as a single term match - case when all terms have valid topologyKey",
wantStatuses: []*framework.Status{
framework.NewStatus(
framework.Unschedulable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrPodAntiAffinityRulesNotMatch.GetReason(),
),
framework.NewStatus(
framework.Unschedulable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrPodAntiAffinityRulesNotMatch.GetReason(),
),
},
name: "Test incoming pod's anti-affinity: only when labelSelector and topologyKey both match, it's counted as a single term match - case when all terms have valid topologyKey",
},
{
pod: &v1.Pod{
@ -1341,8 +1499,20 @@ func TestMultipleNodes(t *testing.T) {
{ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: map[string]string{"region": "r1", "zone": "z2", "hostname": "nodeB"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "nodeC", Labels: map[string]string{"region": "r1", "zone": "z3", "hostname": "nodeC"}}},
},
wantStatuses: []*framework.Status{unschedulable, unschedulable, nil},
name: "Test existing pod's anti-affinity: existingPod on nodeA and nodeB has at least one anti-affinity term matches incoming pod, so incoming pod can only be scheduled to nodeC",
wantStatuses: []*framework.Status{
framework.NewStatus(
framework.Unschedulable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrExistingPodsAntiAffinityRulesNotMatch.GetReason(),
),
framework.NewStatus(
framework.Unschedulable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrExistingPodsAntiAffinityRulesNotMatch.GetReason(),
),
nil,
},
name: "Test existing pod's anti-affinity: existingPod on nodeA and nodeB has at least one anti-affinity term matches incoming pod, so incoming pod can only be scheduled to nodeC",
},
{
pod: createPodWithAffinityTerms(defaultNamespace, "", nil,
@ -1429,8 +1599,19 @@ func TestMultipleNodes(t *testing.T) {
{ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: map[string]string{"region": "r1", "zone": "z1", "hostname": "nodeA"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: map[string]string{"region": "r1", "zone": "z2", "hostname": "nodeB"}}},
},
wantStatuses: []*framework.Status{unschedulableAndUnresolvable, unschedulableAndUnresolvable},
name: "Test incoming pod's affinity: firstly check if all affinityTerms match, and then check if all topologyKeys match, and the match logic should be satisfied on the same pod",
wantStatuses: []*framework.Status{
framework.NewStatus(
framework.UnschedulableAndUnresolvable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrPodAffinityRulesNotMatch.GetReason(),
),
framework.NewStatus(
framework.UnschedulableAndUnresolvable,
predicates.ErrPodAffinityNotMatch.GetReason(),
predicates.ErrPodAffinityRulesNotMatch.GetReason(),
),
},
name: "Test incoming pod's affinity: firstly check if all affinityTerms match, and then check if all topologyKeys match, and the match logic should be satisfied on the same pod",
},
}

View File

@ -41,12 +41,17 @@ func PredicateResultToFrameworkStatus(reasons []predicates.PredicateFailureReaso
return nil
}
if r := predicates.UnresolvablePredicateExists(reasons); r != nil {
return framework.NewStatus(framework.UnschedulableAndUnresolvable, r.GetReason())
code := framework.Unschedulable
if predicates.UnresolvablePredicateExists(reasons) {
code = framework.UnschedulableAndUnresolvable
}
// We will just use the first reason.
return framework.NewStatus(framework.Unschedulable, reasons[0].GetReason())
// We will keep all failure reasons.
var failureReasons []string
for _, reason := range reasons {
failureReasons = append(failureReasons, reason.GetReason())
}
return framework.NewStatus(code, failureReasons...)
}
// ErrorToFrameworkStatus converts an error to a framework status.

View File

@ -54,7 +54,7 @@ func TestPredicateResultToFrameworkStatus(t *testing.T) {
{
name: "Unschedulable and Unresolvable",
reasons: []predicates.PredicateFailureReason{predicates.ErrDiskConflict, predicates.ErrNodeSelectorNotMatch},
wantStatus: framework.NewStatus(framework.UnschedulableAndUnresolvable, "node(s) didn't match node selector"),
wantStatus: framework.NewStatus(framework.UnschedulableAndUnresolvable, "node(s) had no available disk", "node(s) didn't match node selector"),
},
}
for _, tt := range tests {

View File

@ -109,8 +109,12 @@ func TestNodeResourcesFit(t *testing.T) {
pod: newResourcePod(schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1}),
nodeInfo: schedulernodeinfo.NewNodeInfo(
newResourcePod(schedulernodeinfo.Resource{MilliCPU: 10, Memory: 20})),
name: "too many resources fails",
wantStatus: framework.NewStatus(framework.Unschedulable, predicates.NewInsufficientResourceError(v1.ResourceCPU, 2, 10, 10).GetReason()),
name: "too many resources fails",
wantStatus: framework.NewStatus(
framework.Unschedulable,
predicates.NewInsufficientResourceError(v1.ResourceCPU, 2, 10, 10).GetReason(),
predicates.NewInsufficientResourceError(v1.ResourceMemory, 2, 10, 10).GetReason(),
),
},
{
pod: newResourceInitPod(newResourcePod(schedulernodeinfo.Resource{MilliCPU: 1, Memory: 1}), schedulernodeinfo.Resource{MilliCPU: 3, Memory: 1}),

View File

@ -1031,7 +1031,7 @@ func TestRejectWaitingPod(t *testing.T) {
f.RejectWaitingPod(pod.UID)
}()
permitStatus := f.RunPermitPlugins(context.Background(), nil, pod, "")
if permitStatus.message != "pod \"pod\" rejected while waiting at permit: removed" {
if permitStatus.Message() != "pod \"pod\" rejected while waiting at permit: removed" {
t.Fatalf("RejectWaitingPod failed, permitStatus: %v", permitStatus)
}
}

View File

@ -22,6 +22,7 @@ import (
"context"
"errors"
"math"
"strings"
"time"
v1 "k8s.io/api/core/v1"
@ -93,12 +94,11 @@ const (
)
// Status indicates the result of running a plugin. It consists of a code and a
// message. When the status code is not `Success`, the status message should
// explain why.
// message. When the status code is not `Success`, the reasons should explain why.
// NOTE: A nil Status is also considered as Success.
type Status struct {
code Code
message string
reasons []string
}
// Code returns code of the Status.
@ -109,12 +109,17 @@ func (s *Status) Code() Code {
return s.code
}
// Message returns message of the Status.
// Message returns a concatenated message on reasons of the Status.
func (s *Status) Message() string {
if s == nil {
return ""
}
return s.message
return strings.Join(s.reasons, ", ")
}
// Reasons returns reasons of the Status.
func (s *Status) Reasons() []string {
return s.reasons
}
// IsSuccess returns true if and only if "Status" is nil or Code is "Success".
@ -128,19 +133,20 @@ func (s *Status) IsUnschedulable() bool {
return code == Unschedulable || code == UnschedulableAndUnresolvable
}
// AsError returns an "error" object with the same message as that of the Status.
// AsError returns nil if the status is a success; otherwise returns an "error" object
// with a concatenated message on reasons of the Status.
func (s *Status) AsError() error {
if s.IsSuccess() {
return nil
}
return errors.New(s.message)
return errors.New(s.Message())
}
// NewStatus makes a Status out of the given arguments and returns its pointer.
func NewStatus(code Code, msg string) *Status {
func NewStatus(code Code, reasons ...string) *Status {
return &Status{
code: code,
message: msg,
reasons: reasons,
}
}