Skip to content

Commit b339689

Browse files
authored
Merge pull request kubernetes#82034 from alculquicondor/feat/unschedulable_unresolvable
[Framework] Add UnschedulableAndUnresolvable status code
2 parents afa979c + 3c1f8a8 commit b339689

File tree

5 files changed

+94
-29
lines changed

5 files changed

+94
-29
lines changed

pkg/scheduler/core/generic_scheduler.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ func (g *genericScheduler) Preempt(pod *v1.Pod, scheduleErr error) (*v1.Node, []
332332
if len(allNodes) == 0 {
333333
return nil, nil, nil, ErrNoNodesAvailable
334334
}
335-
potentialNodes := nodesWherePreemptionMightHelp(allNodes, fitError.FailedPredicates)
335+
potentialNodes := nodesWherePreemptionMightHelp(allNodes, fitError)
336336
if len(potentialNodes) == 0 {
337337
klog.V(3).Infof("Preemption will not help schedule pod %v/%v on any node.", pod.Namespace, pod.Name)
338338
// In this case, we should clean-up any existing nominated node name of the pod.
@@ -509,7 +509,7 @@ func (g *genericScheduler) findNodesThatFit(pluginContext *framework.PluginConte
509509
if !status.IsSuccess() {
510510
predicateResultLock.Lock()
511511
filteredNodesStatuses[nodeName] = status
512-
if status.Code() != framework.Unschedulable {
512+
if !status.IsUnschedulable() {
513513
errs[status.Message()]++
514514
}
515515
predicateResultLock.Unlock()
@@ -1168,10 +1168,13 @@ func unresolvablePredicateExists(failedPredicates []predicates.PredicateFailureR
11681168

11691169
// nodesWherePreemptionMightHelp returns a list of nodes with failed predicates
11701170
// that may be satisfied by removing pods from the node.
1171-
func nodesWherePreemptionMightHelp(nodes []*v1.Node, failedPredicatesMap FailedPredicateMap) []*v1.Node {
1171+
func nodesWherePreemptionMightHelp(nodes []*v1.Node, fitErr *FitError) []*v1.Node {
11721172
potentialNodes := []*v1.Node{}
11731173
for _, node := range nodes {
1174-
failedPredicates, _ := failedPredicatesMap[node.Name]
1174+
if fitErr.FilteredNodesStatuses[node.Name].Code() == framework.UnschedulableAndUnresolvable {
1175+
continue
1176+
}
1177+
failedPredicates, _ := fitErr.FailedPredicates[node.Name]
11751178
// If we assume that scheduler looks at all nodes and populates the failedPredicateMap
11761179
// (which is the case today), the !found case should never happen, but we'd prefer
11771180
// to rely less on such assumptions in the code when checking does not impose

pkg/scheduler/core/generic_scheduler_test.go

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ var emptyFramework, _ = framework.NewFramework(EmptyPluginRegistry, nil, []sched
144144
// FakeFilterPlugin is a test filter plugin used by default scheduler.
145145
type FakeFilterPlugin struct {
146146
numFilterCalled int32
147-
failFilter bool
147+
returnCode framework.Code
148148
}
149149

150150
var filterPlugin = &FakeFilterPlugin{}
@@ -157,19 +157,19 @@ func (fp *FakeFilterPlugin) Name() string {
157157
// reset is used to reset filter plugin.
158158
func (fp *FakeFilterPlugin) reset() {
159159
fp.numFilterCalled = 0
160-
fp.failFilter = false
160+
fp.returnCode = framework.Success
161161
}
162162

163163
// Filter is a test function that returns an error or nil, depending on the
164164
// value of "failFilter".
165165
func (fp *FakeFilterPlugin) Filter(pc *framework.PluginContext, pod *v1.Pod, nodeName string) *framework.Status {
166166
atomic.AddInt32(&fp.numFilterCalled, 1)
167167

168-
if fp.failFilter {
169-
return framework.NewStatus(framework.Unschedulable, fmt.Sprintf("injecting failure for pod %v", pod.Name))
168+
if fp.returnCode == framework.Success {
169+
return nil
170170
}
171171

172-
return nil
172+
return framework.NewStatus(fp.returnCode, fmt.Sprintf("injecting failure for pod %v", pod.Name))
173173
}
174174

175175
// NewFilterPlugin is the factory for filter plugin.
@@ -282,7 +282,7 @@ func TestGenericScheduler(t *testing.T) {
282282
pod *v1.Pod
283283
pods []*v1.Pod
284284
buildPredMeta bool // build predicates metadata or not
285-
failFilter bool
285+
filterReturnCode framework.Code
286286
expectedHosts sets.String
287287
expectsErr bool
288288
wErr error
@@ -598,14 +598,14 @@ func TestGenericScheduler(t *testing.T) {
598598
wErr: nil,
599599
},
600600
{
601-
name: "test with failed filter plugin",
602-
predicates: map[string]algorithmpredicates.FitPredicate{"true": truePredicate},
603-
prioritizers: []priorities.PriorityConfig{{Function: numericPriority, Weight: 1}},
604-
nodes: []string{"3"},
605-
pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "test-filter", UID: types.UID("test-filter")}},
606-
expectedHosts: nil,
607-
failFilter: true,
608-
expectsErr: true,
601+
name: "test with filter plugin returning Unschedulable status",
602+
predicates: map[string]algorithmpredicates.FitPredicate{"true": truePredicate},
603+
prioritizers: []priorities.PriorityConfig{{Function: numericPriority, Weight: 1}},
604+
nodes: []string{"3"},
605+
pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "test-filter", UID: types.UID("test-filter")}},
606+
expectedHosts: nil,
607+
filterReturnCode: framework.Unschedulable,
608+
expectsErr: true,
609609
wErr: &FitError{
610610
Pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "test-filter", UID: types.UID("test-filter")}},
611611
NumAllNodes: 1,
@@ -615,10 +615,28 @@ func TestGenericScheduler(t *testing.T) {
615615
},
616616
},
617617
},
618+
{
619+
name: "test with filter plugin returning UnschedulableAndUnresolvable status",
620+
predicates: map[string]algorithmpredicates.FitPredicate{"true": truePredicate},
621+
prioritizers: []priorities.PriorityConfig{{Function: numericPriority, Weight: 1}},
622+
nodes: []string{"3"},
623+
pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "test-filter", UID: types.UID("test-filter")}},
624+
expectedHosts: nil,
625+
filterReturnCode: framework.UnschedulableAndUnresolvable,
626+
expectsErr: true,
627+
wErr: &FitError{
628+
Pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "test-filter", UID: types.UID("test-filter")}},
629+
NumAllNodes: 1,
630+
FailedPredicates: FailedPredicateMap{},
631+
FilteredNodesStatuses: framework.NodeToStatusMap{
632+
"3": framework.NewStatus(framework.UnschedulableAndUnresolvable, "injecting failure for pod test-filter"),
633+
},
634+
},
635+
},
618636
}
619637
for _, test := range tests {
620638
t.Run(test.name, func(t *testing.T) {
621-
filterPlugin.failFilter = test.failFilter
639+
filterPlugin.returnCode = test.filterReturnCode
622640

623641
cache := internalcache.New(time.Duration(0), wait.NeverStop)
624642
for _, pod := range test.pods {
@@ -1561,14 +1579,15 @@ func TestPickOneNodeForPreemption(t *testing.T) {
15611579

15621580
func TestNodesWherePreemptionMightHelp(t *testing.T) {
15631581
// Prepare 4 node names.
1564-
nodeNames := []string{}
1582+
nodeNames := make([]string, 0, 4)
15651583
for i := 1; i < 5; i++ {
15661584
nodeNames = append(nodeNames, fmt.Sprintf("machine%d", i))
15671585
}
15681586

15691587
tests := []struct {
15701588
name string
15711589
failedPredMap FailedPredicateMap
1590+
nodesStatuses framework.NodeToStatusMap
15721591
expected map[string]bool // set of expected node names. Value is ignored.
15731592
}{
15741593
{
@@ -1652,11 +1671,41 @@ func TestNodesWherePreemptionMightHelp(t *testing.T) {
16521671
},
16531672
expected: map[string]bool{"machine1": true, "machine3": true, "machine4": true},
16541673
},
1674+
{
1675+
name: "UnschedulableAndUnresolvable status should be skipped but Unschedulable should be tried",
1676+
failedPredMap: FailedPredicateMap{},
1677+
nodesStatuses: framework.NodeToStatusMap{
1678+
"machine2": framework.NewStatus(framework.UnschedulableAndUnresolvable, ""),
1679+
"machine3": framework.NewStatus(framework.Unschedulable, ""),
1680+
"machine4": framework.NewStatus(framework.UnschedulableAndUnresolvable, ""),
1681+
},
1682+
expected: map[string]bool{"machine1": true, "machine3": true},
1683+
},
1684+
{
1685+
name: "Failed predicates and statuses should be evaluated",
1686+
failedPredMap: FailedPredicateMap{
1687+
"machine1": []algorithmpredicates.PredicateFailureReason{algorithmpredicates.ErrPodAffinityNotMatch},
1688+
"machine2": []algorithmpredicates.PredicateFailureReason{algorithmpredicates.ErrPodAffinityNotMatch},
1689+
"machine3": []algorithmpredicates.PredicateFailureReason{algorithmpredicates.ErrPodNotMatchHostName},
1690+
"machine4": []algorithmpredicates.PredicateFailureReason{algorithmpredicates.ErrPodNotMatchHostName},
1691+
},
1692+
nodesStatuses: framework.NodeToStatusMap{
1693+
"machine1": framework.NewStatus(framework.Unschedulable, ""),
1694+
"machine2": framework.NewStatus(framework.UnschedulableAndUnresolvable, ""),
1695+
"machine3": framework.NewStatus(framework.Unschedulable, ""),
1696+
"machine4": framework.NewStatus(framework.UnschedulableAndUnresolvable, ""),
1697+
},
1698+
expected: map[string]bool{"machine1": true},
1699+
},
16551700
}
16561701

16571702
for _, test := range tests {
16581703
t.Run(test.name, func(t *testing.T) {
1659-
nodes := nodesWherePreemptionMightHelp(makeNodeList(nodeNames), test.failedPredMap)
1704+
fitErr := FitError{
1705+
FailedPredicates: test.failedPredMap,
1706+
FilteredNodesStatuses: test.nodesStatuses,
1707+
}
1708+
nodes := nodesWherePreemptionMightHelp(makeNodeList(nodeNames), &fitErr)
16601709
if len(test.expected) != len(nodes) {
16611710
t.Errorf("number of nodes is not the same as expected. exptectd: %d, got: %d. Nodes: %v", len(test.expected), len(nodes), nodes)
16621711
}

pkg/scheduler/framework/v1alpha1/framework.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ func (f *framework) RunPreFilterPlugins(
292292
for _, pl := range f.preFilterPlugins {
293293
status := pl.PreFilter(pc, pod)
294294
if !status.IsSuccess() {
295-
if status.Code() == Unschedulable {
295+
if status.IsUnschedulable() {
296296
msg := fmt.Sprintf("rejected by %q at prefilter: %v", pl.Name(), status.Message())
297297
klog.V(4).Infof(msg)
298298
return NewStatus(status.Code(), msg)
@@ -315,7 +315,7 @@ func (f *framework) RunFilterPlugins(pc *PluginContext,
315315
for _, pl := range f.filterPlugins {
316316
status := pl.Filter(pc, pod, nodeName)
317317
if !status.IsSuccess() {
318-
if status.Code() != Unschedulable {
318+
if !status.IsUnschedulable() {
319319
errMsg := fmt.Sprintf("error while running %q filter plugin for pod %q: %v",
320320
pl.Name(), pod.Name, status.Message())
321321
klog.Error(errMsg)
@@ -433,7 +433,7 @@ func (f *framework) RunPreBindPlugins(
433433
for _, pl := range f.preBindPlugins {
434434
status := pl.PreBind(pc, pod, nodeName)
435435
if !status.IsSuccess() {
436-
if status.Code() == Unschedulable {
436+
if status.IsUnschedulable() {
437437
msg := fmt.Sprintf("rejected by %q at prebind: %v", pl.Name(), status.Message())
438438
klog.V(4).Infof(msg)
439439
return NewStatus(status.Code(), msg)
@@ -513,7 +513,7 @@ func (f *framework) RunPermitPlugins(
513513
for _, pl := range f.permitPlugins {
514514
status, d := pl.Permit(pc, pod, nodeName)
515515
if !status.IsSuccess() {
516-
if status.Code() == Unschedulable {
516+
if status.IsUnschedulable() {
517517
msg := fmt.Sprintf("rejected by %q at permit: %v", pl.Name(), status.Message())
518518
klog.V(4).Infof(msg)
519519
return NewStatus(status.Code(), msg)
@@ -547,7 +547,7 @@ func (f *framework) RunPermitPlugins(
547547
return NewStatus(Unschedulable, msg)
548548
case s := <-w.s:
549549
if !s.IsSuccess() {
550-
if s.Code() == Unschedulable {
550+
if s.IsUnschedulable() {
551551
msg := fmt.Sprintf("rejected while waiting at permit: %v", s.Message())
552552
klog.V(4).Infof(msg)
553553
return NewStatus(s.Code(), msg)

pkg/scheduler/framework/v1alpha1/interface.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,16 @@ const (
5353
Success Code = iota
5454
// Error is used for internal plugin errors, unexpected input, etc.
5555
Error
56-
// Unschedulable is used when a plugin finds a pod unschedulable.
56+
// Unschedulable is used when a plugin finds a pod unschedulable. The scheduler might attempt to
57+
// preempt other pods to get this pod scheduled. Use UnschedulableAndUnresolvable to make the
58+
// scheduler skip preemption.
5759
// The accompanying status message should explain why the pod is unschedulable.
5860
Unschedulable
61+
// UnschedulableAndUnresolvable is used when a (pre-)filter plugin finds a pod unschedulable and
62+
// preemption would not change anything. Plugins should return Unschedulable if it is possible
63+
// that the pod can get scheduled with preemption.
64+
// The accompanying status message should explain why the pod is unschedulable.
65+
UnschedulableAndUnresolvable
5966
// Wait is used when a permit plugin finds a pod scheduling should wait.
6067
Wait
6168
// Skip is used when a bind plugin chooses to skip binding.
@@ -100,6 +107,12 @@ func (s *Status) IsSuccess() bool {
100107
return s.Code() == Success
101108
}
102109

110+
// IsUnschedulable returns true if "Status" is Unschedulable (Unschedulable or UnschedulableAndUnresolvable).
111+
func (s *Status) IsUnschedulable() bool {
112+
code := s.Code()
113+
return code == Unschedulable || code == UnschedulableAndUnresolvable
114+
}
115+
103116
// AsError returns an "error" object with the same message as that of the Status.
104117
func (s *Status) AsError() error {
105118
if s.IsSuccess() {

pkg/scheduler/scheduler.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,7 @@ func (sched *Scheduler) scheduleOne() {
615615
permitStatus := fwk.RunPermitPlugins(pluginContext, assumedPod, scheduleResult.SuggestedHost)
616616
if !permitStatus.IsSuccess() {
617617
var reason string
618-
if permitStatus.Code() == framework.Unschedulable {
618+
if permitStatus.IsUnschedulable() {
619619
metrics.PodScheduleFailures.Inc()
620620
reason = v1.PodReasonUnschedulable
621621
} else {
@@ -635,7 +635,7 @@ func (sched *Scheduler) scheduleOne() {
635635
preBindStatus := fwk.RunPreBindPlugins(pluginContext, assumedPod, scheduleResult.SuggestedHost)
636636
if !preBindStatus.IsSuccess() {
637637
var reason string
638-
if preBindStatus.Code() == framework.Unschedulable {
638+
if preBindStatus.IsUnschedulable() {
639639
metrics.PodScheduleFailures.Inc()
640640
reason = v1.PodReasonUnschedulable
641641
} else {

0 commit comments

Comments
 (0)