Repository navigation
feat(clustered): resume a preempted gang when Kueue readmits it #8258
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ package clustered | |
| import ( | ||
| "context" | ||
| "fmt" | ||
| "strings" | ||
|
|
||
| "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||
| "sigs.k8s.io/controller-runtime/pkg/client" | ||
|
|
@@ -29,6 +30,10 @@ const ( | |
| evictedByPreemption = "Preempted" | ||
| evictedByMaximumExecutionTime = "DeactivatedDueToMaximumExecutionTimeExceeded" | ||
|
|
||
| // evictedByDeactivationPrefix starts every Evicted reason Kueue gives when it deactivates | ||
| // a Workload, e.g. "DeactivatedDueToRequeuingLimitExceeded". | ||
| evictedByDeactivationPrefix = "Deactivated" | ||
|
|
||
| quotaReservedInadmissible = "Inadmissible" | ||
| ) | ||
|
|
||
|
|
@@ -94,7 +99,15 @@ func readWorkloadCondition(wl *unstructured.Unstructured, conditionType string) | |
| func classifyEviction(wl *unstructured.Unstructured) gang.Eviction { | ||
| eviction := gang.Eviction{Source: evictionSource} | ||
| evicted, ok := readWorkloadCondition(wl, workloadConditionEvicted) | ||
| if !ok || evicted.status != "True" { | ||
| evictedNow := ok && evicted.status == "True" | ||
| // Deactivating a Workload that is already evicted, e.g. one waiting for re-admission | ||
| // after a preemption, can leave the earlier reason in place: the deactivation is the | ||
| // cause now. | ||
| if isInactive(wl) && !(evictedNow && strings.HasPrefix(evicted.reason, evictedByDeactivationPrefix)) { | ||
| eviction.Reason = evictedByDeactivationPrefix | ||
| return eviction | ||
| } | ||
| if !evictedNow { | ||
| return eviction | ||
| } | ||
| eviction.Reason = evicted.reason | ||
|
|
@@ -111,6 +124,29 @@ func classifyEviction(wl *unstructured.Unstructured) gang.Eviction { | |
| return eviction | ||
| } | ||
|
|
||
| // awaitsReadmission reports whether a gang Kueue took back was preempted, and so will be | ||
| // readmitted once there is room again. Any other eviction, such as node failures or a | ||
| // PodsReady timeout, may recur, so it is not worth waiting for; nor is a deactivated | ||
| // Workload or a missing one. A JobSet can be suspended before Kueue has written the | ||
| // Evicted condition, which is waited out until the condition says why. | ||
| func awaitsReadmission(wl *unstructured.Unstructured) bool { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we wait only on Preempted? Node failures and PodsReady timeouts aren't preemptions and a gang that keeps failing to get ready shouldnt wait forever. One thing to keep. if a jobset is suspended but the Evicted condition isn't true yet, report waiting for this poll rather than ending the attempt, so we dont race Kueue writing the condition.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done, only The same race exists with the setting off, where it predates this PR: if the JobSet informer sees the suspend before the Workload cache has
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lets keep that a follow up PR |
||
| if wl == nil || isInactive(wl) { | ||
| return false | ||
| } | ||
| evicted, ok := readWorkloadCondition(wl, workloadConditionEvicted) | ||
| return !ok || evicted.status != "True" || evicted.reason == evictedByPreemption | ||
| } | ||
|
|
||
| // isInactive reports whether the Workload has been deactivated (spec.active: false), which | ||
| // Kueue does on its own limits and an operator does by hand. | ||
| func isInactive(wl *unstructured.Unstructured) bool { | ||
| if wl == nil { | ||
| return false | ||
| } | ||
| active, found, err := unstructured.NestedBool(wl.Object, "spec", "active") | ||
| return err == nil && found && !active | ||
| } | ||
|
|
||
| // missingLocalQueue reports the LocalQueue the JobSet names when Kueue cannot admit it because | ||
| // that queue does not exist. Only that case is a user error: a queue that exists but is stopped | ||
| // or inactive is the platform's, so the gang keeps waiting. The message is Kueue's own | ||
|
|
||
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.
nit: this also applies with
resume_after_preemptionoff: a deactivated Workload with a stale Preempted reason becomes a system retry instead of a user retry. I agree with the change. Could you add a test for it with the setting off and mention it in the description?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.
Added a test with the setting off and mentioned it in the description.