Scheduler first fit (#123384)
* Don't evaluate extra nodes if there's no score plugin defined * Fix existing unit test (add no op scoring plugin) * Add unit tests for no score plugin scenario * address review comments * add a test with non-filter, non-scoring extender
This commit is contained in:
		
				
					committed by
					
						
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							54bcbc3c75
						
					
				
				
					commit
					dd1e617ba0
				
			@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
 
 | 
			
		||||
@@ -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(
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user