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:
Kubernetes Prow Robot
2023-01-05 12:53:57 -08:00
committed by GitHub
5 changed files with 120 additions and 104 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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) {

View File

@@ -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 {