-
Notifications
You must be signed in to change notification settings - Fork 38.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix the inaccurate status when a plugin internal status is found #105727
Conversation
@chendave: This issue is currently awaiting triage. If a SIG or subproject determines this is a relevant issue, they will accept it by applying the The Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
/sig scheduling |
/release-note-none |
if err != nil { | ||
return nil, nil, err | ||
} | ||
return append(nonViolatingCandidates.get(), violatingCandidates.get()...), nodeStatuses, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes in this function seem unnecessary.
Would it be possible to check if any of the nodeStatuses is an Error somewhere else? Before they are converted into a FitError
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
okay, leave this unchanged, maybe we can check it right after the calling of DryRunPreemption
.
@@ -565,10 +569,17 @@ func (ev *Evaluator) DryRunPreemption(ctx context.Context, pod *v1.Pod, potentia | |||
if status.IsSuccess() && len(pods) == 0 { | |||
status = framework.AsStatus(fmt.Errorf("expected at least one victim pod on node %q", nodeInfoCopy.Node().Name)) | |||
} | |||
if status.Code() == framework.Error { | |||
err = status.AsError() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that is not thread safe, but before fixing, think of my other comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just out of curiosity, if we don't mind which Error
status is eventually returned, send back any one of them is acceptable, then the coding like this should be fine, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might want to report the unschedulable statuses to the logs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do this inside the lock below
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@alculquicondor updated, pls take another look, thanks! |
for node, status := range unschedulableNodeStatus { | ||
nodeStatuses[node] = status | ||
var errMsg []string | ||
for _, nodeStatus := range nodeStatuses { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is it that you want to achieve? Fail the entire scheduling cycle if we find any error? Or is logging with ErrorS enough?
If it's the latter, you could log right here. If the former, let's discuss, because that might be a user facing change of behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want to keep the original logic, not fail the entire scheduling cycle, but just make sure the Error
status is not hidden and not converted to FitError
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pls let me know is it good to squash as is? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's the latter, you could log right here.
the logs will eventually print out here if it's a Error
, so I think we needn't log it twice.
kubernetes/pkg/scheduler/scheduler.go
Lines 461 to 465 in 9804a83
if status.Code() == framework.Error { | |
klog.ErrorS(nil, "Status after running PostFilter plugins for pod", "pod", klog.KObj(pod), "status", status) | |
} else { | |
klog.V(5).InfoS("Status after running PostFilter plugins for pod", "pod", klog.KObj(pod), "status", status) | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But at that point the scheduling cycle is considered failed.
IIUC, the "errors" are converted into a FitError if no node passed the preemption logic. However, with your change, any error will fail the entire preemption even if there is a node that passes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
kubernetes/pkg/scheduler/framework/preemption/preemption.go
Lines 145 to 148 in d5de03f
candidates, nodeToStatusMap, status := ev.findCandidates(ctx, pod, m) | |
if !status.IsSuccess() { | |
return nil, status | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh... yes, I miss that, updated the logic to make sure we can still get back any nominated node even there is Error
on other nodes.
And testcases updated to reflect the change as well.
// Return a FitError only when there are no candidates that fit the pod. | ||
if len(candidates) == 0 { | ||
// Return a FitError only when there are no candidates that fit the pod and status is success | ||
if status.IsSuccess() && len(candidates) == 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
right, this is not needed.
@@ -215,10 +216,19 @@ func (ev *Evaluator) findCandidates(ctx context.Context, pod *v1.Pod, m framewor | |||
klog.Infof("from a pool of %d nodes (offset: %d, sample %d nodes: %v), ~%d candidates will be chosen", len(potentialNodes), offset, len(sample), sample, numCandidates) | |||
} | |||
candidates, nodeStatuses := ev.DryRunPreemption(ctx, pod, potentialNodes, pdbs, offset, numCandidates) | |||
for node, status := range unschedulableNodeStatus { | |||
nodeStatuses[node] = status | |||
var errMsg []string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make it var errs []error
Then you can use status.AsError
in the loop
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
return &TestPlugin{name: "test-plugin"}, nil | ||
} | ||
|
||
type TestPlugin struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Give it a more informative name and a comment for how it fails
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
comments added, but I am struggle to make it more informative. :(
@@ -299,8 +373,15 @@ func TestPostFilter(t *testing.T) { | |||
} | |||
|
|||
gotResult, gotStatus := p.PostFilter(context.TODO(), state, tt.pod, tt.filteredNodesStatuses) | |||
if diff := cmp.Diff(tt.wantStatus, gotStatus); diff != "" { | |||
t.Errorf("Unexpected status (-want, +got):\n%s", diff) | |||
// As we cannot compare two errors directly, we lack the equal method for how to compare two errors, so we just need to compare the reasons. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can make it work with errors.Aggregate
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have tried with this but still doesn't work,
cmp.Diff(utilerrors.NewAggregate([]error{tt.wantStatus.AsError()}), utilerrors.NewAggregate([]error{gotStatus.AsError()}), cmpopts.EquateErrors())
I think both the code and the reasons match should be enough.
/retest |
Just to make sure I understand the purpose of this change, the idea to make sure that the ErrorS branch gets executed rather than the InfoS, right? Is it really worth iterating over all the node statues to do that, why not just log the error in the place where it happened in DryRunPreemption. Also, I wouldn't consider this a bug, just a cleanup. |
thanks for the comments! this is one of the purpose, the other is I am trying to align with the main scheduler path, where an internal error is different with the
If you think it's fine to do that then there is no point to do this change, we can close this one. |
/remove-kind bug This is fine with me. Leaving LGTM to @ahg-g |
/hold
I don't think we should do this because we will be iterating over all the candidates every time, and in reality we will rarely face errors here. Simply log the error in DryRunPreemption. |
Make sense, I took some change from here: a664b8a, so the error there just looks like a flag, and only loop the candidates to collect all the errors when the flag is set.
I don't think log is really needed as this is already done there, the problem is that the returned status is not an Error when an error was happened in current code base, this is another reason for the change. kubernetes/pkg/scheduler/scheduler.go Lines 461 to 465 in 9804a83
If this change still doesn't look like a right approach, let's just close it instead. |
I think the previous approach was better.
I think this is a valid thing to fix. |
for _, nodeStatus := range nodeStatuses { | ||
if nodeStatus.Code() == framework.Error { | ||
errs = append(errs, nodeStatus.AsError()) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do this in DryRunPreemption instead of iterating again here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
Calculating the candidates is more expensive, but those extra iterations do add up at scale; we should avoid doing those especially when they usually end up being a no-op. We could create the error while iterating in DryRunPreemption. |
153c68d
to
c1261e9
Compare
Done. |
nodeStatuses[node] = status | ||
candidates, nodeStatuses, err := ev.DryRunPreemption(ctx, pod, potentialNodes, pdbs, offset, numCandidates) | ||
if err != nil { | ||
status = framework.AsStatus(fmt.Errorf("found error: %v", err.Error())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
framework.AsStatus(err)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
although I am not quite sure why findCandidates
return a status in the first place, should simply return an error, but lets leave that for another day.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
findCandidates
is not an exported method, no testcase is depending on that, no big change is needed, so add one more commit to address this.
c1261e9
to
cff78f5
Compare
/approve |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: AaronWashington, alculquicondor, chendave The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
Currently, the status code returned is `Unschedulable` when an internal error found, the `Unschedulable` status is built from a `FitError` which means no fit nodes found without a internal error. Instead of build an Unschedulable status from the `FitError`, return the Error status directly. Signed-off-by: Dave Chen <dave.chen@arm.com>
cff78f5
to
468a600
Compare
squash done |
/lgtm |
/unhold |
Currently, the status code returned is
Unschedulable
when an internal plugin error found, the returnedUnschedulable
status is built from aFitError
which means no fit nodes found but should not an internal plugin error.Instead of build an
Unschedulable
status from theFitError
, return theError
status directly will bring us two things,Reveal the failure point directly with
klog.ErrorS
instead of increasing log level and checking with theklog.InfoS
.kubernetes/pkg/scheduler/scheduler.go
Lines 461 to 465 in 9804a83
Avoid to come up a
FitError
and buildUnschedulable
status based on theFitError
but return theError
status directly, this is aligning with the main scheduler path,kubernetes/pkg/scheduler/generic_scheduler.go
Lines 106 to 118 in 9804a83
/kind bug
What this PR does / why we need it:
Which issue(s) this PR fixes:
Fixes #
Special notes for your reviewer:
Does this PR introduce a user-facing change?
Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.: