Skip to content

Don't prefer a flavor with no preemption candidates over one that fits - #13616

Merged
kubernetes-prow[bot] merged 3 commits into
kubernetes-sigs:mainfrom
YQ-Wang:feat/13607-flavor-no-candidates
Aug 6, 2026
Merged

kubernetes-prow[bot] merged 3 commits into
kubernetes-sigs:mainfrom
YQ-Wang:feat/13607-flavor-no-candidates

Conversation

@YQ-Wang

@YQ-Wang YQ-Wang commented Jul 29, 2026 •

Copy link
Copy Markdown
Contributor

What type of PR is this?

/kind bug

What this PR does / why we need it:

With flavorFungibility.preference: PreemptionOverBorrowing, isPreferred compares borrowingLevel before preemptionMode. That lets a flavor whose preemption simulation found no candidates outrank a later flavor that fits, purely because its quota is sourceable at a shallower level in the cohort tree.

The first flavor cannot admit the workload in that scheduling attempt, and the fitting flavor loses the comparison because borrowing depth is considered first, so the workload stays pending indefinitely while quota sits idle. Every cycle repeats the same decision.

This PR ranks noPreemptionCandidates below all viable modes before applying the configured preference, keeping noFit as the worst mode.

The change only affects PreemptionOverBorrowing. The default preference and BorrowingOverPreemption already compare preemptionMode first, so they are unaffected.

Which issue(s) this PR fixes:

Fixes #13607

Special notes for your reviewer:

/cc @amy

Tests are added at three levels, and all of them fail without the fix:

  • TestIsPreferred covers the new ordering directly, including that noPreemptionCandidates still beats noFit and that borrowing level still breaks ties between two such flavors.
  • A TestHierarchical case pins the first flavor to NoCandidates at borrowing level 1 through a fake oracle, while exercising the flavor assigner’s real selection path against a fitting second flavor.
  • A TestSchedule case reproduces the issue end to end and asserts the workload is admitted into the second flavor. It needs a two-level cohort: with a flat cohort both flavors land at the same borrowing level, the existing tie-break on preemptionMode fires, and the bug does not appear.

The middle case may look redundant next to the end-to-end one, but it guards against a specific kind of drift. TestSchedule derives its borrowing levels from the real oracle and real cohort-height math, so if a future change moved the first flavor to the same level as the second, the levels would tie, the pre-existing tie-break would select the fitting flavor, and the test would keep passing without exercising this bug at all. Pinning the oracle result keeps one test anchored to the ordering itself. Happy to drop it if you would rather not carry the extra layer.

One thing worth flagging, which I left out of scope. The docs and the KEP describe PreemptionOverBorrowing over a binary borrow key:

(Fit, NoBorrow) > (Preempt, NoBorrow) > (Fit, Borrow) > (Preempt, Borrow)

The implementation compares the full borrowingLevel depth first, so in a hierarchical cohort (preempt, level 1) still outranks (fit, level 2) even when preemption candidates do exist. That is not starvation, since preemption makes progress, so I kept this PR narrow. Happy to follow up if you would like the graded comparison reconciled with the documented ordering.

AI assistance was used to help implement and review this change. The author reviewed and tested the resulting code and takes responsibility for it.

Does this PR introduce a user-facing change?

Scheduling: Fixed a bug where PreemptionOverBorrowing selected a ResourceFlavor with no preemption candidates instead of another flavor that could admit the Workload.

Summary by CodeRabbit

Summary by CodeRabbit

  • Bug Fixes
    • Improved flavor selection when the preferred flavor has no eligible preemption candidates.
    • The scheduler now skips unavailable flavors and can select a viable fallback flavor.
    • Preserved existing handling for flavors that cannot fit workloads.
  • Tests
    • Added coverage for borrowing and preemption preferences, hierarchical flavor selection, and fallback scheduling scenarios.

With flavorFungibility.preference: PreemptionOverBorrowing, isPreferred compares
borrowingLevel before preemptionMode. A flavor whose preemption simulation found
no candidates could therefore outrank a later flavor that fits, purely because
its quota was sourceable at a shallower level in the cohort tree. Admission in
that flavor is impossible for the scheduling attempt, so the workload stayed
pending indefinitely while the fitting flavor sat idle.

Rank noPreemptionCandidates below all viable modes before applying the
configured preference, keeping noFit as the worst mode. This is a no-op for the
default preference and for BorrowingOverPreemption, which already compare
preemptionMode first.

Signed-off-by: Yiqing Wang <yiqing@wangemail.com>
@kubernetes-prow
kubernetes-prow Bot requested a review from amy July 29, 2026 06:59
@kubernetes-prow kubernetes-prow Bot added release-note Denotes a PR that will be considered when it comes time to generate release notes. kind/bug Categorizes issue or PR as related to a bug. labels Jul 29, 2026
@netlify

netlify Bot commented Jul 29, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for kubernetes-sigs-kueue canceled.

Name Link
🔨 Latest commit f373baa
🔍 Latest deploy log https://app.netlify.com/projects/kubernetes-sigs-kueue/deploys/6a740fa04373a50008e14cca

@kubernetes-prow kubernetes-prow Bot added cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. labels Jul 29, 2026
@kubernetes-prow

Copy link
Copy Markdown

Hi @YQ-Wang. Thanks for your PR.

I'm waiting for a kubernetes-sigs member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work.

Tip

We noticed you've done this a few times! Consider joining the org to skip this step and gain /lgtm and other bot rights. We recommend asking approvers on your previous PRs to sponsor you.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

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-sigs/prow repository.

@kubernetes-prow kubernetes-prow Bot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Jul 29, 2026
@coderabbitai

coderabbitai Bot commented Jul 29, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The flavor assigner now ranks viable modes with preemption candidates above modes without candidates. Tests cover hierarchical selection, comparison behavior, and scheduling fallback to a deeper flavor.

Changes

Flavor preemption preference

Layer / File(s) Summary
No-candidate preference ordering
pkg/scheduler/flavorassigner/flavorassigner.go, pkg/scheduler/flavorassigner/flavorassigner_test.go
isPreferred prioritizes modes with preemption candidates. Hierarchical and table-driven tests cover noPreemptionCandidates, noFit, and fit comparisons.
Scheduler flavor fallback
pkg/scheduler/scheduler_test.go
A scheduling scenario verifies that a workload skips an exhausted flavor without preemption candidates and is admitted using the next flavor.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • kubernetes-sigs/kueue#13800: Both PRs modify flavor-assignment behavior, but this PR addresses preemption-candidate preference while the other addresses failed TAS placements.

Suggested reviewers: amy

Sequence Diagram(s)

sequenceDiagram
  participant Scheduler
  participant FlavorAssigner
  participant ClusterQueue
  Scheduler->>FlavorAssigner: Compare hierarchical flavor modes
  FlavorAssigner->>ClusterQueue: Evaluate preemption candidates and fit
  ClusterQueue-->>FlavorAssigner: Return mode status
  FlavorAssigner-->>Scheduler: Prefer the next viable flavor
  Scheduler->>ClusterQueue: Admit workload on selected flavor
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The implementation and regression tests address all stated objectives, including viable-flavor ordering and starvation prevention [#13607].
Out of Scope Changes check ✅ Passed The implementation and added tests remain within the linked issue scope of flavor selection and regression coverage.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: ranking flavors with no preemption candidates below flavors that fit.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

The flavor is fully evaluated and then out-ranked, not skipped. "Skipping" a
flavor already means something else in the flavor assigner: the nomination
mapping and allowed-flavor annotation paths that continue before the flavor is
evaluated at all.

Signed-off-by: Yiqing Wang <yiqing@wangemail.com>
@mimowo

mimowo commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

/ok-to-test

@kubernetes-prow kubernetes-prow Bot added ok-to-test Indicates a non-member PR verified by an org member that is safe to test. and removed needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. labels Jul 29, 2026
@amy

amy commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

/assign

@olekzabl olekzabl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

/lgtm

Comment on lines +496 to +504
// A flavor without preemption candidates cannot be admitted in this
// scheduling attempt. Rank it below viable modes before applying the
// configured fungibility preference, while retaining noFit as the worst mode.
aHasNoCandidates := a.preemptionMode == noPreemptionCandidates
bHasNoCandidates := b.preemptionMode == noPreemptionCandidates
if aHasNoCandidates != bHasNoCandidates {
return !aHasNoCandidates
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: am I right that this would be equivalent?
(Just cloning the above block handling noFit).

Suggested change
// A flavor without preemption candidates cannot be admitted in this
// scheduling attempt. Rank it below viable modes before applying the
// configured fungibility preference, while retaining noFit as the worst mode.
aHasNoCandidates := a.preemptionMode == noPreemptionCandidates
bHasNoCandidates := b.preemptionMode == noPreemptionCandidates
if aHasNoCandidates != bHasNoCandidates {
return !aHasNoCandidates
}
if a.preemptionMode == noPreemptionCandidates {
return false
}
if b.preemptionMode == noPreemptionCandidates {
return true
}

In my feeling, this version would read naturally enough to not even need the // comment.

WDYT?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this is not equivalent, because when a.preemptionMode = b.preemptionMode = noPreemptionCandidates it would return false, but before it would perform downstream checks.

Worth checking if we have unit tests for that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, TestIsPreferred already covers two noPreemptionCandidates modes in both borrowing-level orderings. The lower borrowing level is preferred, and the reverse is not. The suggested sequential checks would fail the first case, so this behavior is explicitly protected.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, right. Then it's fine as it is.

wantMode: Fit,
wantAssigment: rfMap{corev1.ResourceCPU: "two"},
},
"Select deeper flavor that fits when shallower flavor has no preemption candidates": {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will it be obvious for future readers what "deeper flavor" or "shallower flavor" means?
(So far, in whole Kueue code, I see very little occurences of "shallow"/"deep" referring to Cohort hierarchy)

How about "deeper-borrowing" and "shallower-borrowing"? That'd make a stronger hint.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I updated this to use deeper-borrowing and shallower-borrowing, and made the matching TestIsPreferred case consistent.

Comment on lines +4133 to +4139
if testClusterQueueFlavors == nil {
testClusterQueueFlavors = []kueue.FlavorQuotas{
*utiltestingapi.MakeFlavorQuotas("one").Resource(corev1.ResourceCPU, "0").Obj(),
*utiltestingapi.MakeFlavorQuotas("two").Resource(corev1.ResourceCPU, "0").Obj(),
*utiltestingapi.MakeFlavorQuotas("three").Resource(corev1.ResourceCPU, "0").Obj(),
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I'd put it explicitly on every test case. Possibly using a shared var / builder func if you like.
So that, closer to table tests philosophy, all test inputs are clearly listed in one place.
Especially in this case, where you have just 2 test cases relying on this defaulting.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, testClusterQueueFlavors is now explicit in every case

@kubernetes-prow kubernetes-prow Bot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Aug 5, 2026
@kubernetes-prow

Copy link
Copy Markdown

LGTM label has been added.

DetailsGit tree hash: a22eeceff13bf9eeaa5a6143eed10ba5846fdc64

@mimowo mimowo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/approve
/cherrypick release-0.19
/cherrypick release-0.18
Thank you 👍 I will just let you consider addressing the comments from @olekzabl . I suppose they are not blocking, but please double check.

/hold

@kubernetes-prow kubernetes-prow Bot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Aug 5, 2026
@kubernetes-prow

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: mimowo, YQ-Wang

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@kubernetes-prow kubernetes-prow Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Aug 5, 2026
Signed-off-by: Yiqing Wang <yiqing@wangemail.com>
@kubernetes-prow kubernetes-prow Bot removed the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Aug 6, 2026
@kubernetes-prow
kubernetes-prow Bot requested a review from olekzabl August 6, 2026 04:37
@YQ-Wang
YQ-Wang requested a review from mimowo August 6, 2026 04:41
@mimowo

mimowo commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

/unhold
/lgtm

@kubernetes-prow kubernetes-prow Bot removed the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Aug 6, 2026
@kubernetes-prow kubernetes-prow Bot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Aug 6, 2026
@kubernetes-prow

Copy link
Copy Markdown

LGTM label has been added.

DetailsGit tree hash: bc4a35fe2eb967d971ee802ed79cc0ed91bcfaa5

@kubernetes-prow
kubernetes-prow Bot merged commit cc7899e into kubernetes-sigs:main Aug 6, 2026
56 checks passed
@kubernetes-prow kubernetes-prow Bot added this to the v0.20 milestone Aug 6, 2026
@mimowo

mimowo commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

/cherrypick release-0.19
/cherrypick release-0.18

@k8s-infra-cherrypick-robot

Copy link
Copy Markdown
Contributor

@mimowo: #13616 failed to apply on top of branch "release-0.18":

Applying: Don't prefer a flavor with no preemption candidates over one that fits
Using index info to reconstruct a base tree...
M	pkg/scheduler/flavorassigner/flavorassigner.go
M	pkg/scheduler/flavorassigner/flavorassigner_test.go
M	pkg/scheduler/scheduler_test.go
Falling back to patching base and 3-way merge...
Auto-merging pkg/scheduler/flavorassigner/flavorassigner.go
Auto-merging pkg/scheduler/flavorassigner/flavorassigner_test.go
CONFLICT (content): Merge conflict in pkg/scheduler/flavorassigner/flavorassigner_test.go
Auto-merging pkg/scheduler/scheduler_test.go
error: Failed to merge in the changes.
hint: Use 'git am --show-current-patch=diff' to see the failed patch
hint: When you have resolved this problem, run "git am --continue".
hint: If you prefer to skip this patch, run "git am --skip" instead.
hint: To restore the original branch and stop patching, run "git am --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
Patch failed at 0001 Don't prefer a flavor with no preemption candidates over one that fits

Details

In response to this:

/cherrypick release-0.19
/cherrypick release-0.18

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-sigs/prow repository.

@k8s-infra-cherrypick-robot

Copy link
Copy Markdown
Contributor

@mimowo: new pull request created: #13896

Details

In response to this:

/cherrypick release-0.19
/cherrypick release-0.18

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-sigs/prow repository.

@mimowo

mimowo commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

@YQ-Wang could you please prepare the CP for 0.18?

@mimowo

mimowo commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

/release-note-edit

Scheduling: Fixed a bug where PreemptionOverBorrowing selected a ResourceFlavor with no preemption candidates instead of another flavor that could admit the Workload.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. kind/bug Categorizes issue or PR as related to a bug. lgtm "Looks good to me", indicates that a PR is ready to be merged. ok-to-test Indicates a non-member PR verified by an org member that is safe to test. release-note Denotes a PR that will be considered when it comes time to generate release notes. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

6 participants