Merge pull request #114699 from kerthcet/feat/distinguish-unschedulabel-with-unresolvable
Distinguish between Unschedulable and UnschedulableAndUnresolvable in scheduler's PostFilter
This commit is contained in:
@@ -79,13 +79,14 @@ const (
|
||||
// Error is used for internal plugin errors, unexpected input, etc.
|
||||
Error
|
||||
// Unschedulable is used when a plugin finds a pod unschedulable. The scheduler might attempt to
|
||||
// preempt other pods to get this pod scheduled. Use UnschedulableAndUnresolvable to make the
|
||||
// scheduler skip preemption.
|
||||
// run other postFilter plugins like preemption to get this pod scheduled.
|
||||
// Use UnschedulableAndUnresolvable to make the scheduler skipping other postFilter plugins.
|
||||
// The accompanying status message should explain why the pod is unschedulable.
|
||||
Unschedulable
|
||||
// UnschedulableAndUnresolvable is used when a plugin finds a pod unschedulable and
|
||||
// preemption would not change anything. Plugins should return Unschedulable if it is possible
|
||||
// that the pod can get scheduled with preemption.
|
||||
// other postFilter plugins like preemption would not change anything.
|
||||
// Plugins should return Unschedulable if it is possible that the pod can get scheduled
|
||||
// after running other postFilter plugins.
|
||||
// The accompanying status message should explain why the pod is unschedulable.
|
||||
UnschedulableAndUnresolvable
|
||||
// Wait is used when a Permit plugin finds a pod scheduling should wait.
|
||||
@@ -97,15 +98,6 @@ const (
|
||||
// This list should be exactly the same as the codes iota defined above in the same order.
|
||||
var codes = []string{"Success", "Error", "Unschedulable", "UnschedulableAndUnresolvable", "Wait", "Skip"}
|
||||
|
||||
// statusPrecedence defines a map from status to its precedence, larger value means higher precedent.
|
||||
var statusPrecedence = map[Code]int{
|
||||
Error: 3,
|
||||
UnschedulableAndUnresolvable: 2,
|
||||
Unschedulable: 1,
|
||||
// Any other statuses we know today, `Skip` or `Wait`, will take precedence over `Success`.
|
||||
Success: -1,
|
||||
}
|
||||
|
||||
func (c Code) String() string {
|
||||
return codes[c]
|
||||
}
|
||||
@@ -255,7 +247,10 @@ func (s *Status) Equal(x *Status) bool {
|
||||
if !cmp.Equal(s.err, x.err, cmpopts.EquateErrors()) {
|
||||
return false
|
||||
}
|
||||
return cmp.Equal(s.reasons, x.reasons)
|
||||
if !cmp.Equal(s.reasons, x.reasons) {
|
||||
return false
|
||||
}
|
||||
return cmp.Equal(s.failedPlugin, x.failedPlugin)
|
||||
}
|
||||
|
||||
// NewStatus makes a Status out of the given arguments and returns its pointer.
|
||||
@@ -278,36 +273,6 @@ func AsStatus(err error) *Status {
|
||||
}
|
||||
}
|
||||
|
||||
// PluginToStatus maps plugin name to status it returned.
|
||||
type PluginToStatus map[string]*Status
|
||||
|
||||
// Merge merges the statuses in the map into one. The resulting status code have the following
|
||||
// precedence: Error, UnschedulableAndUnresolvable, Unschedulable.
|
||||
func (p PluginToStatus) Merge() *Status {
|
||||
if len(p) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
finalStatus := NewStatus(Success)
|
||||
for _, s := range p {
|
||||
if statusPrecedence[s.Code()] > statusPrecedence[finalStatus.code] {
|
||||
finalStatus.code = s.Code()
|
||||
// Same as code, we keep the most relevant failedPlugin in the returned Status.
|
||||
finalStatus.failedPlugin = s.FailedPlugin()
|
||||
}
|
||||
|
||||
reasons := s.Reasons()
|
||||
if finalStatus.err == nil {
|
||||
finalStatus.err = s.err
|
||||
reasons = s.reasons
|
||||
}
|
||||
for _, r := range reasons {
|
||||
finalStatus.AppendReason(r)
|
||||
}
|
||||
}
|
||||
return finalStatus
|
||||
}
|
||||
|
||||
// WaitingPod represents a pod currently waiting in the permit phase.
|
||||
type WaitingPod interface {
|
||||
// GetPod returns a reference to the waiting pod.
|
||||
|
@@ -139,43 +139,6 @@ func assertStatusCode(t *testing.T, code Code, value int) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginToStatusMerge(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
statusMap PluginToStatus
|
||||
wantCode Code
|
||||
}{
|
||||
{
|
||||
name: "merge Error and Unschedulable statuses",
|
||||
statusMap: PluginToStatus{"p1": NewStatus(Error), "p2": NewStatus(Unschedulable)},
|
||||
wantCode: Error,
|
||||
},
|
||||
{
|
||||
name: "merge Success and Unschedulable statuses",
|
||||
statusMap: PluginToStatus{"p1": NewStatus(Success), "p2": NewStatus(Unschedulable)},
|
||||
wantCode: Unschedulable,
|
||||
},
|
||||
{
|
||||
name: "merge Success, UnschedulableAndUnresolvable and Unschedulable statuses",
|
||||
statusMap: PluginToStatus{"p1": NewStatus(Success), "p2": NewStatus(UnschedulableAndUnresolvable), "p3": NewStatus(Unschedulable)},
|
||||
wantCode: UnschedulableAndUnresolvable,
|
||||
},
|
||||
{
|
||||
name: "merge nil status",
|
||||
wantCode: Success,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
gotStatus := test.statusMap.Merge()
|
||||
if test.wantCode != gotStatus.Code() {
|
||||
t.Errorf("wantCode %v, gotCode %v", test.wantCode, gotStatus.Code())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreFilterResultMerge(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
receiver *PreFilterResult
|
||||
|
@@ -752,30 +752,39 @@ func (f *frameworkImpl) runFilterPlugin(ctx context.Context, pl framework.Filter
|
||||
}
|
||||
|
||||
// RunPostFilterPlugins runs the set of configured PostFilter plugins until the first
|
||||
// Success or Error is met, otherwise continues to execute all plugins.
|
||||
// Success, Error or UnschedulableAndUnresolvable is met; otherwise continues to execute all plugins.
|
||||
func (f *frameworkImpl) RunPostFilterPlugins(ctx context.Context, state *framework.CycleState, pod *v1.Pod, filteredNodeStatusMap framework.NodeToStatusMap) (_ *framework.PostFilterResult, status *framework.Status) {
|
||||
startTime := time.Now()
|
||||
defer func() {
|
||||
metrics.FrameworkExtensionPointDuration.WithLabelValues(postFilter, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime))
|
||||
}()
|
||||
|
||||
statuses := make(framework.PluginToStatus)
|
||||
// `result` records the last meaningful(non-noop) PostFilterResult.
|
||||
var result *framework.PostFilterResult
|
||||
var reasons []string
|
||||
var failedPlugin string
|
||||
for _, pl := range f.postFilterPlugins {
|
||||
r, s := f.runPostFilterPlugin(ctx, pl, state, pod, filteredNodeStatusMap)
|
||||
if s.IsSuccess() {
|
||||
return r, s
|
||||
} else if s.Code() == framework.UnschedulableAndUnresolvable {
|
||||
return r, s.WithFailedPlugin(pl.Name())
|
||||
} else if !s.IsUnschedulable() {
|
||||
// Any status other than Success or Unschedulable is Error.
|
||||
return nil, framework.AsStatus(s.AsError())
|
||||
// Any status other than Success, Unschedulable or UnschedulableAndUnresolvable is Error.
|
||||
return nil, framework.AsStatus(s.AsError()).WithFailedPlugin(pl.Name())
|
||||
} else if r != nil && r.Mode() != framework.ModeNoop {
|
||||
result = r
|
||||
}
|
||||
statuses[pl.Name()] = s
|
||||
|
||||
reasons = append(reasons, s.Reasons()...)
|
||||
// Record the first failed plugin unless we proved that
|
||||
// the latter is more relevant.
|
||||
if len(failedPlugin) == 0 {
|
||||
failedPlugin = pl.Name()
|
||||
}
|
||||
}
|
||||
|
||||
return result, statuses.Merge()
|
||||
return result, framework.NewStatus(framework.Unschedulable, reasons...).WithFailedPlugin(failedPlugin)
|
||||
}
|
||||
|
||||
func (f *frameworkImpl) runPostFilterPlugin(ctx context.Context, pl framework.PostFilterPlugin, state *framework.CycleState, pod *v1.Pod, filteredNodeStatusMap framework.NodeToStatusMap) (*framework.PostFilterResult, *framework.Status) {
|
||||
|
@@ -1676,6 +1676,48 @@ func TestPostFilterPlugins(t *testing.T) {
|
||||
},
|
||||
wantStatus: framework.NewStatus(framework.Success, injectReason),
|
||||
},
|
||||
{
|
||||
name: "plugin1 failed to make a Pod schedulable, followed by plugin2 which makes the Pod schedulable",
|
||||
plugins: []*TestPlugin{
|
||||
{
|
||||
name: "TestPlugin1",
|
||||
inj: injectedResult{PostFilterStatus: int(framework.Error)},
|
||||
},
|
||||
{
|
||||
name: "TestPlugin2",
|
||||
inj: injectedResult{PostFilterStatus: int(framework.Success)},
|
||||
},
|
||||
},
|
||||
wantStatus: framework.AsStatus(fmt.Errorf(injectReason)).WithFailedPlugin("TestPlugin1"),
|
||||
},
|
||||
{
|
||||
name: "plugin1 failed to make a Pod schedulable, followed by plugin2 which makes the Pod unresolvable",
|
||||
plugins: []*TestPlugin{
|
||||
{
|
||||
name: "TestPlugin1",
|
||||
inj: injectedResult{PostFilterStatus: int(framework.Unschedulable)},
|
||||
},
|
||||
{
|
||||
name: "TestPlugin2",
|
||||
inj: injectedResult{PostFilterStatus: int(framework.UnschedulableAndUnresolvable)},
|
||||
},
|
||||
},
|
||||
wantStatus: framework.NewStatus(framework.UnschedulableAndUnresolvable, injectReason).WithFailedPlugin("TestPlugin2"),
|
||||
},
|
||||
{
|
||||
name: "both plugins failed to make a Pod schedulable",
|
||||
plugins: []*TestPlugin{
|
||||
{
|
||||
name: "TestPlugin1",
|
||||
inj: injectedResult{PostFilterStatus: int(framework.Unschedulable)},
|
||||
},
|
||||
{
|
||||
name: "TestPlugin2",
|
||||
inj: injectedResult{PostFilterStatus: int(framework.Unschedulable)},
|
||||
},
|
||||
},
|
||||
wantStatus: framework.NewStatus(framework.Unschedulable, []string{injectReason, injectReason}...).WithFailedPlugin("TestPlugin1"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
Reference in New Issue
Block a user