mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 10:51:29 +00:00
feature(scheduler): won't run Score if PreScore returned a Skip status (#115652)
* allow preScore to return skip status to skip running the corresponding score extension * add test case for all skipped * add test case for select host * update plugin status * skip score when all plugins are skipped * update
This commit is contained in:
parent
436ca94642
commit
f5a69ffda9
@ -52,6 +52,8 @@ type CycleState struct {
|
||||
recordPluginMetrics bool
|
||||
// SkipFilterPlugins are plugins that will be skipped in the Filter extension point.
|
||||
SkipFilterPlugins sets.Set[string]
|
||||
// SkipScorePlugins are plugins that will be skipped in the Score extension point.
|
||||
SkipScorePlugins sets.Set[string]
|
||||
}
|
||||
|
||||
// NewCycleState initializes a new CycleState and returns its pointer.
|
||||
@ -88,6 +90,7 @@ func (c *CycleState) Clone() *CycleState {
|
||||
})
|
||||
copy.recordPluginMetrics = c.recordPluginMetrics
|
||||
copy.SkipFilterPlugins = c.SkipFilterPlugins
|
||||
copy.SkipScorePlugins = c.SkipScorePlugins
|
||||
|
||||
return copy
|
||||
}
|
||||
|
@ -94,6 +94,7 @@ const (
|
||||
// Skip is used in the following scenarios:
|
||||
// - when a Bind plugin chooses to skip binding.
|
||||
// - when a PreFilter plugin returns Skip so that coupled Filter plugin/PreFilterExtensions() will be skipped.
|
||||
// - when a PreScore plugin returns Skip so that coupled Score plugin will be skipped.
|
||||
Skip
|
||||
)
|
||||
|
||||
@ -411,6 +412,8 @@ type PreScorePlugin interface {
|
||||
// PreScore is called by the scheduling framework after a list of nodes
|
||||
// passed the filtering phase. All prescore plugins must return success or
|
||||
// the pod will be rejected
|
||||
// When it returns Skip status, other fields in status are just ignored,
|
||||
// and coupled Score plugin will be skipped in this scheduling cycle.
|
||||
PreScore(ctx context.Context, state *CycleState, pod *v1.Pod, nodes []*v1.Node) *Status
|
||||
}
|
||||
|
||||
|
@ -887,7 +887,9 @@ func addNominatedPods(ctx context.Context, fh framework.Handle, pod *v1.Pod, sta
|
||||
}
|
||||
|
||||
// RunPreScorePlugins runs the set of configured pre-score plugins. If any
|
||||
// of these plugins returns any status other than "Success", the given pod is rejected.
|
||||
// of these plugins returns any status other than Success/Skip, the given pod is rejected.
|
||||
// When it returns Skip status, other fields in status are just ignored,
|
||||
// and coupled Score plugin will be skipped in this scheduling cycle.
|
||||
func (f *frameworkImpl) RunPreScorePlugins(
|
||||
ctx context.Context,
|
||||
state *framework.CycleState,
|
||||
@ -898,13 +900,18 @@ func (f *frameworkImpl) RunPreScorePlugins(
|
||||
defer func() {
|
||||
metrics.FrameworkExtensionPointDuration.WithLabelValues(preScore, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime))
|
||||
}()
|
||||
skipPlugins := sets.New[string]()
|
||||
for _, pl := range f.preScorePlugins {
|
||||
status = f.runPreScorePlugin(ctx, pl, state, pod, nodes)
|
||||
if status.IsSkip() {
|
||||
skipPlugins.Insert(pl.Name())
|
||||
continue
|
||||
}
|
||||
if !status.IsSuccess() {
|
||||
return framework.AsStatus(fmt.Errorf("running PreScore plugin %q: %w", pl.Name(), status.AsError()))
|
||||
}
|
||||
}
|
||||
|
||||
state.SkipScorePlugins = skipPlugins
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -928,17 +935,24 @@ func (f *frameworkImpl) RunScorePlugins(ctx context.Context, state *framework.Cy
|
||||
metrics.FrameworkExtensionPointDuration.WithLabelValues(score, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime))
|
||||
}()
|
||||
allNodePluginScores := make([]framework.NodePluginScores, len(nodes))
|
||||
pluginToNodeScores := make(map[string]framework.NodeScoreList, len(f.scorePlugins))
|
||||
numPlugins := len(f.scorePlugins) - state.SkipScorePlugins.Len()
|
||||
plugins := make([]framework.ScorePlugin, 0, numPlugins)
|
||||
pluginToNodeScores := make(map[string]framework.NodeScoreList, numPlugins)
|
||||
for _, pl := range f.scorePlugins {
|
||||
if state.SkipScorePlugins.Has(pl.Name()) {
|
||||
continue
|
||||
}
|
||||
plugins = append(plugins, pl)
|
||||
pluginToNodeScores[pl.Name()] = make(framework.NodeScoreList, len(nodes))
|
||||
}
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
errCh := parallelize.NewErrorChannel()
|
||||
|
||||
if len(plugins) > 0 {
|
||||
// Run Score method for each node in parallel.
|
||||
f.Parallelizer().Until(ctx, len(nodes), func(index int) {
|
||||
for _, pl := range f.scorePlugins {
|
||||
for _, pl := range plugins {
|
||||
nodeName := nodes[index].Name
|
||||
s, status := f.runScorePlugin(ctx, pl, state, pod, nodeName)
|
||||
if !status.IsSuccess() {
|
||||
@ -955,10 +969,11 @@ func (f *frameworkImpl) RunScorePlugins(ctx context.Context, state *framework.Cy
|
||||
if err := errCh.ReceiveError(); err != nil {
|
||||
return nil, framework.AsStatus(fmt.Errorf("running Score plugins: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
// Run NormalizeScore method for each ScorePlugin in parallel.
|
||||
f.Parallelizer().Until(ctx, len(f.scorePlugins), func(index int) {
|
||||
pl := f.scorePlugins[index]
|
||||
f.Parallelizer().Until(ctx, len(plugins), func(index int) {
|
||||
pl := plugins[index]
|
||||
if pl.ScoreExtensions() == nil {
|
||||
return
|
||||
}
|
||||
@ -979,10 +994,10 @@ func (f *frameworkImpl) RunScorePlugins(ctx context.Context, state *framework.Cy
|
||||
f.Parallelizer().Until(ctx, len(nodes), func(index int) {
|
||||
nodePluginScores := framework.NodePluginScores{
|
||||
Name: nodes[index].Name,
|
||||
Scores: make([]framework.PluginScore, len(f.scorePlugins)),
|
||||
Scores: make([]framework.PluginScore, len(plugins)),
|
||||
}
|
||||
|
||||
for i, pl := range f.scorePlugins {
|
||||
for i, pl := range plugins {
|
||||
weight := f.scorePluginWeight[pl.Name()]
|
||||
nodeScoreList := pluginToNodeScores[pl.Name()]
|
||||
score := nodeScoreList[index].Score
|
||||
|
@ -1061,6 +1061,131 @@ func TestPreEnqueuePlugins(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunPreScorePlugins(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
plugins []*TestPlugin
|
||||
wantSkippedPlugins sets.Set[string]
|
||||
wantStatusCode framework.Code
|
||||
}{
|
||||
{
|
||||
name: "all PreScorePlugins returned success",
|
||||
plugins: []*TestPlugin{
|
||||
{
|
||||
name: "success1",
|
||||
},
|
||||
{
|
||||
name: "success2",
|
||||
},
|
||||
},
|
||||
wantStatusCode: framework.Success,
|
||||
},
|
||||
{
|
||||
name: "one PreScore plugin returned success, but another PreScore plugin returned non-success",
|
||||
plugins: []*TestPlugin{
|
||||
{
|
||||
name: "success",
|
||||
},
|
||||
{
|
||||
name: "error",
|
||||
inj: injectedResult{PreScoreStatus: int(framework.Error)},
|
||||
},
|
||||
},
|
||||
wantStatusCode: framework.Error,
|
||||
},
|
||||
{
|
||||
name: "one PreScore plugin returned skip, but another PreScore plugin returned non-success",
|
||||
plugins: []*TestPlugin{
|
||||
{
|
||||
name: "skip",
|
||||
inj: injectedResult{PreScoreStatus: int(framework.Skip)},
|
||||
},
|
||||
{
|
||||
name: "error",
|
||||
inj: injectedResult{PreScoreStatus: int(framework.Error)},
|
||||
},
|
||||
},
|
||||
wantStatusCode: framework.Error,
|
||||
},
|
||||
{
|
||||
name: "all PreScore plugins returned skip",
|
||||
plugins: []*TestPlugin{
|
||||
{
|
||||
name: "skip1",
|
||||
inj: injectedResult{PreScoreStatus: int(framework.Skip)},
|
||||
},
|
||||
{
|
||||
name: "skip2",
|
||||
inj: injectedResult{PreScoreStatus: int(framework.Skip)},
|
||||
},
|
||||
{
|
||||
name: "skip3",
|
||||
inj: injectedResult{PreScoreStatus: int(framework.Skip)},
|
||||
},
|
||||
},
|
||||
wantSkippedPlugins: sets.New("skip1", "skip2", "skip3"),
|
||||
wantStatusCode: framework.Success,
|
||||
},
|
||||
{
|
||||
name: "some PreScore plugins returned skip",
|
||||
plugins: []*TestPlugin{
|
||||
{
|
||||
name: "skip1",
|
||||
inj: injectedResult{PreScoreStatus: int(framework.Skip)},
|
||||
},
|
||||
{
|
||||
name: "success1",
|
||||
},
|
||||
{
|
||||
name: "skip2",
|
||||
inj: injectedResult{PreScoreStatus: int(framework.Skip)},
|
||||
},
|
||||
{
|
||||
name: "success2",
|
||||
},
|
||||
},
|
||||
wantSkippedPlugins: sets.New("skip1", "skip2"),
|
||||
wantStatusCode: framework.Success,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := make(Registry)
|
||||
enabled := make([]config.Plugin, len(tt.plugins))
|
||||
for i, p := range tt.plugins {
|
||||
p := p
|
||||
enabled[i].Name = p.name
|
||||
r.Register(p.name, func(_ runtime.Object, fh framework.Handle) (framework.Plugin, error) {
|
||||
return p, nil
|
||||
})
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
f, err := newFrameworkWithQueueSortAndBind(
|
||||
r,
|
||||
config.KubeSchedulerProfile{Plugins: &config.Plugins{PreScore: config.PluginSet{Enabled: enabled}}},
|
||||
ctx.Done(),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create framework for testing: %v", err)
|
||||
}
|
||||
|
||||
state := framework.NewCycleState()
|
||||
status := f.RunPreScorePlugins(ctx, state, nil, nil)
|
||||
if status.Code() != tt.wantStatusCode {
|
||||
t.Errorf("wrong status code. got: %v, want: %v", status, tt.wantStatusCode)
|
||||
}
|
||||
skipped := state.SkipScorePlugins
|
||||
if d := cmp.Diff(skipped, tt.wantSkippedPlugins); d != "" {
|
||||
t.Errorf("wrong skip score plugins. got: %v, want: %v, diff: %s", skipped, tt.wantSkippedPlugins, d)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunScorePlugins(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@ -1068,6 +1193,7 @@ func TestRunScorePlugins(t *testing.T) {
|
||||
plugins *config.Plugins
|
||||
pluginConfigs []config.PluginConfig
|
||||
want []framework.NodePluginScores
|
||||
skippedPlugins sets.Set[string]
|
||||
// If err is true, we expect RunScorePlugin to fail.
|
||||
err bool
|
||||
}{
|
||||
@ -1345,6 +1471,70 @@ func TestRunScorePlugins(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "one success plugin, one skip plugin",
|
||||
plugins: buildScoreConfigDefaultWeights(scorePlugin1, scoreWithNormalizePlugin1),
|
||||
pluginConfigs: []config.PluginConfig{
|
||||
{
|
||||
Name: scorePlugin1,
|
||||
Args: &runtime.Unknown{
|
||||
Raw: []byte(`{ "scoreRes": 1 }`),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: scoreWithNormalizePlugin1,
|
||||
Args: &runtime.Unknown{
|
||||
Raw: []byte(`{ "scoreStatus": 1 }`), // To make sure this plugin isn't called, set error as an injected result.
|
||||
},
|
||||
},
|
||||
},
|
||||
skippedPlugins: sets.New(scoreWithNormalizePlugin1),
|
||||
want: []framework.NodePluginScores{
|
||||
{
|
||||
Name: "node1",
|
||||
Scores: []framework.PluginScore{
|
||||
{
|
||||
Name: scorePlugin1,
|
||||
Score: 1,
|
||||
},
|
||||
},
|
||||
TotalScore: 1,
|
||||
},
|
||||
{
|
||||
Name: "node2",
|
||||
Scores: []framework.PluginScore{
|
||||
{
|
||||
Name: scorePlugin1,
|
||||
Score: 1,
|
||||
},
|
||||
},
|
||||
TotalScore: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all plugins are skipped in prescore",
|
||||
plugins: buildScoreConfigDefaultWeights(scorePlugin1),
|
||||
pluginConfigs: []config.PluginConfig{
|
||||
{
|
||||
Name: scorePlugin1,
|
||||
Args: &runtime.Unknown{
|
||||
Raw: []byte(`{ "scoreStatus": 1 }`), // To make sure this plugin isn't called, set error as an injected result.
|
||||
},
|
||||
},
|
||||
},
|
||||
skippedPlugins: sets.New(scorePlugin1),
|
||||
want: []framework.NodePluginScores{
|
||||
{
|
||||
Name: "node1",
|
||||
Scores: []framework.PluginScore{},
|
||||
},
|
||||
{
|
||||
Name: "node2",
|
||||
Scores: []framework.PluginScore{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@ -1361,6 +1551,8 @@ func TestRunScorePlugins(t *testing.T) {
|
||||
t.Fatalf("Failed to create framework for testing: %v", err)
|
||||
}
|
||||
|
||||
state := framework.NewCycleState()
|
||||
state.SkipScorePlugins = tt.skippedPlugins
|
||||
res, status := f.RunScorePlugins(ctx, state, pod, nodes)
|
||||
|
||||
if tt.err {
|
||||
|
@ -2016,6 +2016,21 @@ func TestSchedulerSchedulePod(t *testing.T) {
|
||||
wantNodes: sets.NewString("node2", "node3"),
|
||||
wantEvaluatedNodes: pointer.Int32(3),
|
||||
},
|
||||
{
|
||||
name: "test all prescore plugins return skip",
|
||||
registerPlugins: []st.RegisterPluginFunc{
|
||||
st.RegisterQueueSortPlugin(queuesort.Name, queuesort.New),
|
||||
st.RegisterFilterPlugin("TrueFilter", st.NewTrueFilterPlugin),
|
||||
st.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
|
||||
st.RegisterPluginAsExtensions("FakePreScoreAndScorePlugin", st.NewFakePreScoreAndScorePlugin("FakePreScoreAndScorePlugin", 0,
|
||||
framework.NewStatus(framework.Skip, "fake skip"),
|
||||
framework.NewStatus(framework.Error, "this score function shouldn't be executed because this plugin returned Skip in the PreScore"),
|
||||
), "PreScore", "Score"),
|
||||
},
|
||||
nodes: []string{"node1", "node2"},
|
||||
pod: st.MakePod().Name("ignore").UID("ignore").Obj(),
|
||||
wantNodes: sets.NewString("node1", "node2"),
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
@ -2514,6 +2529,70 @@ func Test_prioritizeNodes(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "plugin which returned skip in preScore shouldn't be executed in the score phase",
|
||||
pod: &v1.Pod{},
|
||||
nodes: []*v1.Node{makeNode("node1", 1000, schedutil.DefaultMemoryRequest*10), makeNode("node2", 1000, schedutil.DefaultMemoryRequest*10)},
|
||||
pluginRegistrations: []st.RegisterPluginFunc{
|
||||
st.RegisterQueueSortPlugin(queuesort.Name, queuesort.New),
|
||||
st.RegisterScorePlugin(noderesources.BalancedAllocationName, frameworkruntime.FactoryAdapter(feature.Features{}, noderesources.NewBalancedAllocation), 1),
|
||||
st.RegisterScorePlugin("Node2Prioritizer", st.NewNode2PrioritizerPlugin(), 1),
|
||||
st.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
|
||||
st.RegisterPluginAsExtensions("FakePreScoreAndScorePlugin", st.NewFakePreScoreAndScorePlugin("FakePreScoreAndScorePlugin", 0,
|
||||
framework.NewStatus(framework.Skip, "fake skip"),
|
||||
framework.NewStatus(framework.Error, "this score function shouldn't be executed because this plugin returned Skip in the PreScore"),
|
||||
), "PreScore", "Score"),
|
||||
},
|
||||
extenders: nil,
|
||||
want: []framework.NodePluginScores{
|
||||
{
|
||||
Name: "node1",
|
||||
Scores: []framework.PluginScore{
|
||||
{
|
||||
Name: "Node2Prioritizer",
|
||||
Score: 10,
|
||||
},
|
||||
{
|
||||
Name: "NodeResourcesBalancedAllocation",
|
||||
Score: 100,
|
||||
},
|
||||
},
|
||||
TotalScore: 110,
|
||||
},
|
||||
{
|
||||
Name: "node2",
|
||||
Scores: []framework.PluginScore{
|
||||
{
|
||||
Name: "Node2Prioritizer",
|
||||
Score: 100,
|
||||
},
|
||||
{
|
||||
Name: "NodeResourcesBalancedAllocation",
|
||||
Score: 100,
|
||||
},
|
||||
},
|
||||
TotalScore: 200,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all score plugins are skipped",
|
||||
pod: &v1.Pod{},
|
||||
nodes: []*v1.Node{makeNode("node1", 1000, schedutil.DefaultMemoryRequest*10), makeNode("node2", 1000, schedutil.DefaultMemoryRequest*10)},
|
||||
pluginRegistrations: []st.RegisterPluginFunc{
|
||||
st.RegisterQueueSortPlugin(queuesort.Name, queuesort.New),
|
||||
st.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
|
||||
st.RegisterPluginAsExtensions("FakePreScoreAndScorePlugin", st.NewFakePreScoreAndScorePlugin("FakePreScoreAndScorePlugin", 0,
|
||||
framework.NewStatus(framework.Skip, "fake skip"),
|
||||
framework.NewStatus(framework.Error, "this score function shouldn't be executed because this plugin returned Skip in the PreScore"),
|
||||
), "PreScore", "Score"),
|
||||
},
|
||||
extenders: nil,
|
||||
want: []framework.NodePluginScores{
|
||||
{Name: "node1", Scores: []framework.PluginScore{}},
|
||||
{Name: "node2", Scores: []framework.PluginScore{}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@ -2537,7 +2616,6 @@ func Test_prioritizeNodes(t *testing.T) {
|
||||
}
|
||||
|
||||
state := framework.NewCycleState()
|
||||
fwk.RunPreScorePlugins(ctx, state, test.pod, test.nodes)
|
||||
var extenders []framework.Extender
|
||||
for ii := range test.extenders {
|
||||
extenders = append(extenders, &test.extenders[ii])
|
||||
|
@ -245,3 +245,38 @@ func NewFakePermitPlugin(status *framework.Status, timeout time.Duration) framew
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
type FakePreScoreAndScorePlugin struct {
|
||||
name string
|
||||
score int64
|
||||
preScoreStatus *framework.Status
|
||||
scoreStatus *framework.Status
|
||||
}
|
||||
|
||||
// Name returns name of the plugin.
|
||||
func (pl *FakePreScoreAndScorePlugin) Name() string {
|
||||
return pl.name
|
||||
}
|
||||
|
||||
func (pl *FakePreScoreAndScorePlugin) Score(ctx context.Context, state *framework.CycleState, p *v1.Pod, nodeName string) (int64, *framework.Status) {
|
||||
return pl.score, pl.scoreStatus
|
||||
}
|
||||
|
||||
func (pl *FakePreScoreAndScorePlugin) ScoreExtensions() framework.ScoreExtensions {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pl *FakePreScoreAndScorePlugin) PreScore(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodes []*v1.Node) *framework.Status {
|
||||
return pl.preScoreStatus
|
||||
}
|
||||
|
||||
func NewFakePreScoreAndScorePlugin(name string, score int64, preScoreStatus, scoreStatus *framework.Status) frameworkruntime.PluginFactory {
|
||||
return func(_ runtime.Object, _ framework.Handle) (framework.Plugin, error) {
|
||||
return &FakePreScoreAndScorePlugin{
|
||||
name: name,
|
||||
score: score,
|
||||
preScoreStatus: preScoreStatus,
|
||||
scoreStatus: scoreStatus,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user