mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-27 13:37:30 +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:
parent
54bcbc3c75
commit
dd1e617ba0
@ -383,6 +383,16 @@ func (h *HTTPExtender) IsBinder() bool {
|
|||||||
return h.bindVerb != ""
|
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
|
// Helper function to send messages to the extender
|
||||||
func (h *HTTPExtender) send(action string, args interface{}, result interface{}) error {
|
func (h *HTTPExtender) send(action string, args interface{}, result interface{}) error {
|
||||||
out, err := json.Marshal(args)
|
out, err := json.Marshal(args)
|
||||||
|
@ -93,6 +93,7 @@ func TestSchedulerWithExtenders(t *testing.T) {
|
|||||||
registerPlugins: []tf.RegisterPluginFunc{
|
registerPlugins: []tf.RegisterPluginFunc{
|
||||||
tf.RegisterFilterPlugin("TrueFilter", tf.NewTrueFilterPlugin),
|
tf.RegisterFilterPlugin("TrueFilter", tf.NewTrueFilterPlugin),
|
||||||
tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New),
|
tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New),
|
||||||
|
tf.RegisterScorePlugin("EqualPrioritizerPlugin", tf.NewEqualPrioritizerPlugin(), 1),
|
||||||
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
|
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
|
||||||
},
|
},
|
||||||
extenders: []tf.FakeExtender{
|
extenders: []tf.FakeExtender{
|
||||||
@ -245,6 +246,7 @@ func TestSchedulerWithExtenders(t *testing.T) {
|
|||||||
// because of the errors from errorPredicateExtender.
|
// because of the errors from errorPredicateExtender.
|
||||||
registerPlugins: []tf.RegisterPluginFunc{
|
registerPlugins: []tf.RegisterPluginFunc{
|
||||||
tf.RegisterFilterPlugin("TrueFilter", tf.NewTrueFilterPlugin),
|
tf.RegisterFilterPlugin("TrueFilter", tf.NewTrueFilterPlugin),
|
||||||
|
tf.RegisterScorePlugin("EqualPrioritizerPlugin", tf.NewEqualPrioritizerPlugin(), 1),
|
||||||
tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New),
|
tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New),
|
||||||
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
|
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
|
||||||
},
|
},
|
||||||
@ -268,6 +270,49 @@ func TestSchedulerWithExtenders(t *testing.T) {
|
|||||||
},
|
},
|
||||||
name: "test 9",
|
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 {
|
for _, test := range tests {
|
||||||
|
@ -50,6 +50,12 @@ type Extender interface {
|
|||||||
// this pod is managed by this extender.
|
// this pod is managed by this extender.
|
||||||
IsInterested(pod *v1.Pod) bool
|
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
|
// ProcessPreemption returns nodes with their victim pods processed by extender based on
|
||||||
// given:
|
// given:
|
||||||
// 1. Pod to schedule
|
// 1. Pod to schedule
|
||||||
|
@ -546,6 +546,29 @@ func (sched *Scheduler) evaluateNominatedNode(ctx context.Context, pod *v1.Pod,
|
|||||||
return feasibleNodes, nil
|
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.
|
// findNodesThatPassFilters finds the nodes that fit the filter plugins.
|
||||||
func (sched *Scheduler) findNodesThatPassFilters(
|
func (sched *Scheduler) findNodesThatPassFilters(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
@ -556,6 +579,9 @@ func (sched *Scheduler) findNodesThatPassFilters(
|
|||||||
nodes []*framework.NodeInfo) ([]*framework.NodeInfo, error) {
|
nodes []*framework.NodeInfo) ([]*framework.NodeInfo, error) {
|
||||||
numAllNodes := len(nodes)
|
numAllNodes := len(nodes)
|
||||||
numNodesToFind := sched.numFeasibleNodesToFind(fwk.PercentageOfNodesToScore(), int32(numAllNodes))
|
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
|
// Create feasible list with enough space to avoid growing it
|
||||||
// and allow assigning.
|
// and allow assigning.
|
||||||
|
@ -92,6 +92,8 @@ type fakeExtender struct {
|
|||||||
ignorable bool
|
ignorable bool
|
||||||
gotBind bool
|
gotBind bool
|
||||||
errBind bool
|
errBind bool
|
||||||
|
isPrioritizer bool
|
||||||
|
isFilter bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeExtender) Name() string {
|
func (f *fakeExtender) Name() string {
|
||||||
@ -144,6 +146,14 @@ func (f *fakeExtender) IsInterested(pod *v1.Pod) bool {
|
|||||||
return pod != nil && pod.Name == f.interestedPodName
|
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{}
|
type falseMapPlugin struct{}
|
||||||
|
|
||||||
func newFalseMapPlugin() frameworkruntime.PluginFactory {
|
func newFalseMapPlugin() frameworkruntime.PluginFactory {
|
||||||
@ -1823,6 +1833,7 @@ func TestSchedulerSchedulePod(t *testing.T) {
|
|||||||
registerPlugins: []tf.RegisterPluginFunc{
|
registerPlugins: []tf.RegisterPluginFunc{
|
||||||
tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New),
|
tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New),
|
||||||
tf.RegisterFilterPlugin("TrueFilter", tf.NewTrueFilterPlugin),
|
tf.RegisterFilterPlugin("TrueFilter", tf.NewTrueFilterPlugin),
|
||||||
|
tf.RegisterScorePlugin("EqualPrioritizerPlugin", tf.NewEqualPrioritizerPlugin(), 1),
|
||||||
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
|
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
|
||||||
},
|
},
|
||||||
nodes: []string{"node1", "node2"},
|
nodes: []string{"node1", "node2"},
|
||||||
@ -1940,6 +1951,7 @@ func TestSchedulerSchedulePod(t *testing.T) {
|
|||||||
tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New),
|
tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New),
|
||||||
tf.RegisterPreFilterPlugin(volumebinding.Name, frameworkruntime.FactoryAdapter(fts, volumebinding.New)),
|
tf.RegisterPreFilterPlugin(volumebinding.Name, frameworkruntime.FactoryAdapter(fts, volumebinding.New)),
|
||||||
tf.RegisterFilterPlugin("TrueFilter", tf.NewTrueFilterPlugin),
|
tf.RegisterFilterPlugin("TrueFilter", tf.NewTrueFilterPlugin),
|
||||||
|
tf.RegisterScorePlugin("EqualPrioritizerPlugin", tf.NewEqualPrioritizerPlugin(), 1),
|
||||||
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
|
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
|
||||||
},
|
},
|
||||||
nodes: []string{"node1", "node2"},
|
nodes: []string{"node1", "node2"},
|
||||||
@ -2053,6 +2065,7 @@ func TestSchedulerSchedulePod(t *testing.T) {
|
|||||||
"PreFilter",
|
"PreFilter",
|
||||||
"Filter",
|
"Filter",
|
||||||
),
|
),
|
||||||
|
tf.RegisterScorePlugin("EqualPrioritizerPlugin", tf.NewEqualPrioritizerPlugin(), 1),
|
||||||
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
|
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
|
||||||
},
|
},
|
||||||
nodes: []string{"node1", "node2", "node3"},
|
nodes: []string{"node1", "node2", "node3"},
|
||||||
@ -2355,6 +2368,7 @@ func TestSchedulerSchedulePod(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}, "PreFilter", "Filter"),
|
}, "PreFilter", "Filter"),
|
||||||
|
tf.RegisterScorePlugin("EqualPrioritizerPlugin", tf.NewEqualPrioritizerPlugin(), 1),
|
||||||
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
|
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
|
||||||
},
|
},
|
||||||
nodes: []string{"node1", "node2", "node3"},
|
nodes: []string{"node1", "node2", "node3"},
|
||||||
@ -2377,6 +2391,33 @@ func TestSchedulerSchedulePod(t *testing.T) {
|
|||||||
pod: st.MakePod().Name("ignore").UID("ignore").Obj(),
|
pod: st.MakePod().Name("ignore").UID("ignore").Obj(),
|
||||||
wantNodes: sets.New("node1", "node2"),
|
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 {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
@ -3249,6 +3290,7 @@ func TestFairEvaluationForNodes(t *testing.T) {
|
|||||||
[]tf.RegisterPluginFunc{
|
[]tf.RegisterPluginFunc{
|
||||||
tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New),
|
tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New),
|
||||||
tf.RegisterFilterPlugin("TrueFilter", tf.NewTrueFilterPlugin),
|
tf.RegisterFilterPlugin("TrueFilter", tf.NewTrueFilterPlugin),
|
||||||
|
tf.RegisterScorePlugin("EqualPrioritizerPlugin", tf.NewEqualPrioritizerPlugin(), 1),
|
||||||
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
|
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
|
||||||
},
|
},
|
||||||
"",
|
"",
|
||||||
@ -3327,6 +3369,7 @@ func TestPreferNominatedNodeFilterCallCounts(t *testing.T) {
|
|||||||
registerPlugins := []tf.RegisterPluginFunc{
|
registerPlugins := []tf.RegisterPluginFunc{
|
||||||
tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New),
|
tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New),
|
||||||
registerFakeFilterFunc,
|
registerFakeFilterFunc,
|
||||||
|
tf.RegisterScorePlugin("EqualPrioritizerPlugin", tf.NewEqualPrioritizerPlugin(), 1),
|
||||||
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
|
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
|
||||||
}
|
}
|
||||||
fwk, err := tf.NewFramework(
|
fwk, err := tf.NewFramework(
|
||||||
|
@ -149,6 +149,7 @@ type FakeExtender struct {
|
|||||||
FilteredNodes []*framework.NodeInfo
|
FilteredNodes []*framework.NodeInfo
|
||||||
UnInterested bool
|
UnInterested bool
|
||||||
Ignorable bool
|
Ignorable bool
|
||||||
|
Binder func() error
|
||||||
|
|
||||||
// Cached node information for fake extender
|
// Cached node information for fake extender
|
||||||
CachedNodeNameToInfo map[string]*framework.NodeInfo
|
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.
|
// Bind implements the extender Bind function.
|
||||||
func (f *FakeExtender) Bind(binding *v1.Binding) error {
|
func (f *FakeExtender) Bind(binding *v1.Binding) error {
|
||||||
|
if f.Binder != nil {
|
||||||
|
return f.Binder()
|
||||||
|
}
|
||||||
if len(f.FilteredNodes) != 0 {
|
if len(f.FilteredNodes) != 0 {
|
||||||
for _, node := range f.FilteredNodes {
|
for _, node := range f.FilteredNodes {
|
||||||
if node.Node().Name == binding.Target.Name {
|
if node.Node().Name == binding.Target.Name {
|
||||||
@ -380,6 +384,16 @@ func (f *FakeExtender) IsBinder() bool {
|
|||||||
return true
|
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.
|
// IsInterested returns a bool indicating whether this extender is interested in this Pod.
|
||||||
func (f *FakeExtender) IsInterested(pod *v1.Pod) bool {
|
func (f *FakeExtender) IsInterested(pod *v1.Pod) bool {
|
||||||
return !f.UnInterested
|
return !f.UnInterested
|
||||||
|
@ -280,3 +280,13 @@ func NewFakePreScoreAndScorePlugin(name string, score int64, preScoreStatus, sco
|
|||||||
}, nil
|
}, 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user