mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-10-31 18:28:13 +00:00 
			
		
		
		
	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:
		 Aleksandra Malinowska
					Aleksandra Malinowska
				
			
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			 GitHub
						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