diff --git a/pkg/scheduler/extender.go b/pkg/scheduler/extender.go index ce44e45e87f..bc33aaafb1c 100644 --- a/pkg/scheduler/extender.go +++ b/pkg/scheduler/extender.go @@ -383,6 +383,16 @@ func (h *HTTPExtender) IsBinder() bool { return h.bindVerb != "" } +// IsPrioritizer returns whether this extender is configured for the Prioritize method. +func (h *HTTPExtender) IsPrioritizer() bool { + return h.prioritizeVerb != "" +} + +// IsFilter returns whether this extender is configured for the Filter method. +func (h *HTTPExtender) IsFilter() bool { + return h.filterVerb != "" +} + // Helper function to send messages to the extender func (h *HTTPExtender) send(action string, args interface{}, result interface{}) error { out, err := json.Marshal(args) diff --git a/pkg/scheduler/extender_test.go b/pkg/scheduler/extender_test.go index 44c3daee70c..7382fab92ff 100644 --- a/pkg/scheduler/extender_test.go +++ b/pkg/scheduler/extender_test.go @@ -93,6 +93,7 @@ func TestSchedulerWithExtenders(t *testing.T) { registerPlugins: []tf.RegisterPluginFunc{ tf.RegisterFilterPlugin("TrueFilter", tf.NewTrueFilterPlugin), tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New), + tf.RegisterScorePlugin("EqualPrioritizerPlugin", tf.NewEqualPrioritizerPlugin(), 1), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), }, extenders: []tf.FakeExtender{ @@ -245,6 +246,7 @@ func TestSchedulerWithExtenders(t *testing.T) { // because of the errors from errorPredicateExtender. registerPlugins: []tf.RegisterPluginFunc{ tf.RegisterFilterPlugin("TrueFilter", tf.NewTrueFilterPlugin), + tf.RegisterScorePlugin("EqualPrioritizerPlugin", tf.NewEqualPrioritizerPlugin(), 1), tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), }, @@ -268,6 +270,49 @@ func TestSchedulerWithExtenders(t *testing.T) { }, name: "test 9", }, + { + registerPlugins: []tf.RegisterPluginFunc{ + tf.RegisterFilterPlugin("TrueFilter", tf.NewTrueFilterPlugin), + tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New), + tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), + }, + extenders: []tf.FakeExtender{ + { + ExtenderName: "FakeExtender1", + Predicates: []tf.FitPredicate{tf.TruePredicateExtender}, + }, + { + ExtenderName: "FakeExtender2", + Predicates: []tf.FitPredicate{tf.Node1PredicateExtender}, + }, + }, + nodes: []string{"node1", "node2"}, + expectedResult: ScheduleResult{ + SuggestedHost: "node1", + EvaluatedNodes: 2, + FeasibleNodes: 1, + }, + name: "test 10 - no scoring, extender filters configured, multiple feasible nodes are evaluated", + }, + { + registerPlugins: []tf.RegisterPluginFunc{ + tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New), + tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), + }, + extenders: []tf.FakeExtender{ + { + ExtenderName: "FakeExtender1", + Binder: func() error { return nil }, + }, + }, + nodes: []string{"node1", "node2"}, + expectedResult: ScheduleResult{ + SuggestedHost: "node1", + EvaluatedNodes: 1, + FeasibleNodes: 1, + }, + name: "test 11 - no scoring, no prefilters or extender filters configured, a single feasible node is evaluated", + }, } for _, test := range tests { diff --git a/pkg/scheduler/framework/extender.go b/pkg/scheduler/framework/extender.go index 269ee33b368..b0b937c36b3 100644 --- a/pkg/scheduler/framework/extender.go +++ b/pkg/scheduler/framework/extender.go @@ -50,6 +50,12 @@ type Extender interface { // this pod is managed by this extender. IsInterested(pod *v1.Pod) bool + // IsPrioritizer returns whether this extender is configured for the Prioritize method. + IsPrioritizer() bool + + // IsFilter returns whether this extender is configured for the Filter method. + IsFilter() bool + // ProcessPreemption returns nodes with their victim pods processed by extender based on // given: // 1. Pod to schedule diff --git a/pkg/scheduler/schedule_one.go b/pkg/scheduler/schedule_one.go index a6117b56130..ae4be17bb63 100644 --- a/pkg/scheduler/schedule_one.go +++ b/pkg/scheduler/schedule_one.go @@ -546,6 +546,29 @@ func (sched *Scheduler) evaluateNominatedNode(ctx context.Context, pod *v1.Pod, return feasibleNodes, nil } +// hasScoring checks if scoring nodes is configured. +func (sched *Scheduler) hasScoring(fwk framework.Framework) bool { + if fwk.HasScorePlugins() { + return true + } + for _, extender := range sched.Extenders { + if extender.IsPrioritizer() { + return true + } + } + return false +} + +// hasExtenderFilters checks if any extenders filter nodes. +func (sched *Scheduler) hasExtenderFilters() bool { + for _, extender := range sched.Extenders { + if extender.IsFilter() { + return true + } + } + return false +} + // findNodesThatPassFilters finds the nodes that fit the filter plugins. func (sched *Scheduler) findNodesThatPassFilters( ctx context.Context, @@ -556,6 +579,9 @@ func (sched *Scheduler) findNodesThatPassFilters( nodes []*framework.NodeInfo) ([]*framework.NodeInfo, error) { numAllNodes := len(nodes) numNodesToFind := sched.numFeasibleNodesToFind(fwk.PercentageOfNodesToScore(), int32(numAllNodes)) + if !sched.hasExtenderFilters() && !sched.hasScoring(fwk) { + numNodesToFind = 1 + } // Create feasible list with enough space to avoid growing it // and allow assigning. diff --git a/pkg/scheduler/schedule_one_test.go b/pkg/scheduler/schedule_one_test.go index ce8632cd747..4d9d1cc4be2 100644 --- a/pkg/scheduler/schedule_one_test.go +++ b/pkg/scheduler/schedule_one_test.go @@ -92,6 +92,8 @@ type fakeExtender struct { ignorable bool gotBind bool errBind bool + isPrioritizer bool + isFilter bool } func (f *fakeExtender) Name() string { @@ -144,6 +146,14 @@ func (f *fakeExtender) IsInterested(pod *v1.Pod) bool { return pod != nil && pod.Name == f.interestedPodName } +func (f *fakeExtender) IsPrioritizer() bool { + return f.isPrioritizer +} + +func (f *fakeExtender) IsFilter() bool { + return f.isFilter +} + type falseMapPlugin struct{} func newFalseMapPlugin() frameworkruntime.PluginFactory { @@ -1823,6 +1833,7 @@ func TestSchedulerSchedulePod(t *testing.T) { registerPlugins: []tf.RegisterPluginFunc{ tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New), tf.RegisterFilterPlugin("TrueFilter", tf.NewTrueFilterPlugin), + tf.RegisterScorePlugin("EqualPrioritizerPlugin", tf.NewEqualPrioritizerPlugin(), 1), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), }, nodes: []string{"node1", "node2"}, @@ -1940,6 +1951,7 @@ func TestSchedulerSchedulePod(t *testing.T) { tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New), tf.RegisterPreFilterPlugin(volumebinding.Name, frameworkruntime.FactoryAdapter(fts, volumebinding.New)), tf.RegisterFilterPlugin("TrueFilter", tf.NewTrueFilterPlugin), + tf.RegisterScorePlugin("EqualPrioritizerPlugin", tf.NewEqualPrioritizerPlugin(), 1), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), }, nodes: []string{"node1", "node2"}, @@ -2053,6 +2065,7 @@ func TestSchedulerSchedulePod(t *testing.T) { "PreFilter", "Filter", ), + tf.RegisterScorePlugin("EqualPrioritizerPlugin", tf.NewEqualPrioritizerPlugin(), 1), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), }, nodes: []string{"node1", "node2", "node3"}, @@ -2355,6 +2368,7 @@ func TestSchedulerSchedulePod(t *testing.T) { }, }, nil }, "PreFilter", "Filter"), + tf.RegisterScorePlugin("EqualPrioritizerPlugin", tf.NewEqualPrioritizerPlugin(), 1), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), }, nodes: []string{"node1", "node2", "node3"}, @@ -2377,6 +2391,33 @@ func TestSchedulerSchedulePod(t *testing.T) { pod: st.MakePod().Name("ignore").UID("ignore").Obj(), wantNodes: sets.New("node1", "node2"), }, + { + name: "test without score plugin no extra nodes are evaluated", + registerPlugins: []tf.RegisterPluginFunc{ + tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New), + tf.RegisterFilterPlugin("TrueFilter", tf.NewTrueFilterPlugin), + tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), + }, + nodes: []string{"node1", "node2", "node3"}, + pod: st.MakePod().Name("pod1").UID("pod1").Obj(), + wantNodes: sets.New("node1", "node2", "node3"), + wantEvaluatedNodes: ptr.To[int32](1), + }, + { + name: "test no score plugin, prefilter plugin returning 2 nodes", + registerPlugins: []tf.RegisterPluginFunc{ + tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New), + tf.RegisterPreFilterPlugin( + "FakePreFilter", + tf.NewFakePreFilterPlugin("FakePreFilter", &framework.PreFilterResult{NodeNames: sets.New("node1", "node2")}, nil), + ), + tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), + }, + nodes: []string{"node1", "node2", "node3"}, + pod: st.MakePod().Name("test-prefilter").UID("test-prefilter").Obj(), + wantNodes: sets.New("node1", "node2"), + wantEvaluatedNodes: ptr.To[int32](2), + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { @@ -3249,6 +3290,7 @@ func TestFairEvaluationForNodes(t *testing.T) { []tf.RegisterPluginFunc{ tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New), tf.RegisterFilterPlugin("TrueFilter", tf.NewTrueFilterPlugin), + tf.RegisterScorePlugin("EqualPrioritizerPlugin", tf.NewEqualPrioritizerPlugin(), 1), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), }, "", @@ -3327,6 +3369,7 @@ func TestPreferNominatedNodeFilterCallCounts(t *testing.T) { registerPlugins := []tf.RegisterPluginFunc{ tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New), registerFakeFilterFunc, + tf.RegisterScorePlugin("EqualPrioritizerPlugin", tf.NewEqualPrioritizerPlugin(), 1), tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New), } fwk, err := tf.NewFramework( diff --git a/pkg/scheduler/testing/framework/fake_extender.go b/pkg/scheduler/testing/framework/fake_extender.go index 1a525368113..4073bd9c98c 100644 --- a/pkg/scheduler/testing/framework/fake_extender.go +++ b/pkg/scheduler/testing/framework/fake_extender.go @@ -149,6 +149,7 @@ type FakeExtender struct { FilteredNodes []*framework.NodeInfo UnInterested bool Ignorable bool + Binder func() error // Cached node information for fake extender CachedNodeNameToInfo map[string]*framework.NodeInfo @@ -361,6 +362,9 @@ func (f *FakeExtender) Prioritize(pod *v1.Pod, nodes []*framework.NodeInfo) (*ex // Bind implements the extender Bind function. func (f *FakeExtender) Bind(binding *v1.Binding) error { + if f.Binder != nil { + return f.Binder() + } if len(f.FilteredNodes) != 0 { for _, node := range f.FilteredNodes { if node.Node().Name == binding.Target.Name { @@ -380,6 +384,16 @@ func (f *FakeExtender) IsBinder() bool { return true } +// IsPrioritizer returns true if there are any prioritizers. +func (f *FakeExtender) IsPrioritizer() bool { + return len(f.Prioritizers) > 0 +} + +// IsFilter returns true if there are any filters. +func (f *FakeExtender) IsFilter() bool { + return len(f.Predicates) > 0 +} + // IsInterested returns a bool indicating whether this extender is interested in this Pod. func (f *FakeExtender) IsInterested(pod *v1.Pod) bool { return !f.UnInterested diff --git a/pkg/scheduler/testing/framework/fake_plugins.go b/pkg/scheduler/testing/framework/fake_plugins.go index 01916b26540..ccff22bd5ae 100644 --- a/pkg/scheduler/testing/framework/fake_plugins.go +++ b/pkg/scheduler/testing/framework/fake_plugins.go @@ -280,3 +280,13 @@ func NewFakePreScoreAndScorePlugin(name string, score int64, preScoreStatus, sco }, nil } } + +// NewEqualPrioritizerPlugin returns a factory function to build equalPrioritizerPlugin. +func NewEqualPrioritizerPlugin() frameworkruntime.PluginFactory { + return func(_ context.Context, _ runtime.Object, _ framework.Handle) (framework.Plugin, error) { + return &FakePreScoreAndScorePlugin{ + name: "EqualPrioritizerPlugin", + score: 1, + }, nil + } +}