Skip to content

fix(podgrouper): skip WorkloadRunner wrapper when selecting the grouping plugin - #2067

Merged
davidLif merged 5 commits into
mainfrom
claude/workloadrunner-podgroup-grouping-a1cf19
Aug 13, 2026
Merged

fix(podgrouper): skip WorkloadRunner wrapper when selecting the grouping plugin#2067
davidLif merged 5 commits into
mainfrom
claude/workloadrunner-podgroup-grouping-a1cf19

Conversation

@gshaibi

@gshaibi gshaibi commented Aug 12, 2026

Copy link
Copy Markdown
Member

Description

Wrapping a workload in a run.ai WorkloadRunner adds a level to the owner chain:

direct:   Pod -> PodClique -> PodCliqueSet -> DynamoGraphDeployment
wrapped:  Pod -> PodClique -> PodCliqueSet -> DynamoGraphDeployment -> WorkloadRunner

GetPGMetadata selects the plugin by the top owner's GVK, and there was no entry for WorkloadRunner, so every wrapped workload fell through to defaultGrouper. The result is one flat PodGroup with minMember: 1 carrying group-wide topology constraints, instead of the per-clique gangs the wrapped kind's plugin would build. With kai.scheduler/topology-required-placement: kubernetes.io/hostname that forces unrelated components onto a single node, leaving GPU workers unschedulable even when capacity exists elsewhere. The same workload applied directly, without the wrapper, groups correctly.

Two changes are needed, and either one alone is a no-op:

  1. Register WorkloadRunner as a skip-top-owner kind, matching the existing legacy run.ai wrappers. Uses Version: "*", as those do.

  2. Make skipTopOwnerGrouper resolve through the hub. It previously looked up the next owner via a raw exact-GVK map lookup, bypassing both the hub's wildcard-version fallback and the Karta fallback hub. Since the DGD is registered with Version: "*", a WorkloadRunner -> DGD chain missed that lookup and dropped straight back to defaultGrouper — the same flat PodGroup, via a second route. NewSkipTopOwnerGrouper now takes a GrouperResolver; the hub passes a closure over GetPodGrouperPlugin (bound lazily, since the Karta fallback is assigned after construction, and to avoid the hub -> skiptopowner import cycle).

After both, the wrapped path resolves identically to the direct one:

WorkloadRunner --skip--> DGD --skip--> PodCliqueSet --> GroveGrouper

Chained skips are newly reachable. The recursion is bounded: each hop drops one owner, and it bottoms out at the pod, which resolves to the PodJob grouper rather than back to the skip grouper.

Separately, this adds missing RBAC for nvidia.com/dynamographdeployments. A plugin was registered for the DGD but no permission was ever granted. In a stock install the chain walk 403s at the DGD level and handleGetOwnerError silently truncates to PodCliqueSet — grouping still looks right, so it is invisible, but the DGD-level plugin never runs and its labels/annotations are never propagated onto the PodGroup.

Related Issues

Related to #856 (plugin matching falling back through the owner chain) — that issue is closed, so this does not auto-close it.

Checklist

  • Self-reviewed
  • Added/updated tests (if needed)
  • Updated documentation (if needed)
  • Added a changelog fragment via make changelog (or applied the skip-changelog label). Do not edit CHANGELOG.md directly — pending fragments are folded into it at release time.

Breaking Changes

None. NewSkipTopOwnerGrouper's signature changed from a plugin map to a GrouperResolver, but the constructor returns an unexported type and has a single in-tree caller.

Behavior change worth noting: an owner that previously fell through to defaultGrouper inside the skip path can now resolve to a wildcard-version or Karta-backed plugin. That is the intent of the fix, and it only affects chains that pass through a skip-top-owner kind.

Additional Notes

Reviewer guidance — the second change is the load-bearing one. I verified this rather than assuming it: reverting the resolver to exact-only lookup makes the new chained test fail (Expected <empty> to equal "Grove Grouper") while change 1 is fully in place. A backport that takes only the WorkloadRunner table entry would ship a fix that changes nothing.

Tests added:

  • skiptopowner — the chained case [PodClique, PodCliqueSet, DGD, WorkloadRunner] must land on the Grove grouper, plus label propagation across both skipped wrapper levels. The DGD fixture is v1beta1 against a Version: "*" registration, so it also covers wildcard resolution through the skip path.
  • hubWorkloadRunner resolves to the skip-top-owner grouper, at both a known version and an unknown one via the wildcard.

make validate and go test ./pkg/podgrouper/... pass; RBAC was regenerated with make manifests.

A backport to v0.16 should follow, and the two changes above are sufficient for the reported case: the non-wrapped path already produces correct per-clique PodGroups on the affected cluster, so the DGD resolves there and the wrapper level is the entire delta.

Separately, v0.16 still registers the DGD with an exact v1alpha1 where main uses Version: "*" (widened in #1870, which shipped in v0.17.0 only). Carrying that over is worthwhile hardening, but it fixes a different symptom: a v1beta1 DGD on v0.16 would break the non-wrapped path too, which is not what was reported. Best kept as its own change. v0.16 also has no Karta fallback hub, so the resolver change there should stay wildcard-only.

…ing plugin

Wrapping a workload in a run.ai WorkloadRunner adds a level to the owner
chain (Pod -> ... -> DynamoGraphDeployment -> WorkloadRunner). Plugin
selection keys on the top owner's GVK, and WorkloadRunner had no entry,
so every wrapped workload fell through to the default grouper: a single
flat PodGroup with minMember 1 and group-wide topology constraints,
instead of the per-clique gangs the wrapped kind's plugin would build.

Register WorkloadRunner as a skip-top-owner kind, matching the legacy
run.ai wrappers.

That alone is not enough. skipTopOwnerGrouper resolved the next owner
through a raw exact-GVK map lookup, bypassing the hub's wildcard-version
fallback and the Karta fallback hub. The DGD is registered with a
wildcard version, so a WorkloadRunner -> DGD chain missed and dropped
back to the default grouper anyway. Resolve through the hub instead, so
a skipped owner picks the same plugin a top owner would.

Chained skips are now reachable, so guard the empty owner list that the
existing slice arithmetic would panic on.

Also add the missing RBAC for dynamographdeployments. Without it the
chain walk 403s at the DGD level and silently truncates, which happens
to still group correctly via PodCliqueSet but never propagates the DGD's
labels and annotations onto the PodGroup.

Signed-off-by: gshaibi <gshaibi@nvidia.com>
@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown

Total coverage: 54.6% -> 54.6% (delta 0.00%)

Merging this branch will increase overall coverage

Impacted Packages Coverage Δ 🤖
github.com/kai-scheduler/KAI-scheduler/pkg/podgrouper/podgrouper/hub 97.50% (+0.13%) 👍
github.com/kai-scheduler/KAI-scheduler/pkg/podgrouper/podgrouper/plugins/skiptopowner 87.50% (+0.20%) 👍

Coverage by file

Changed files (no unit tests)

Changed File Coverage Δ Total Covered Missed 🤖
github.com/kai-scheduler/KAI-scheduler/pkg/podgrouper/podgrouper/hub/hub.go 97.50% (+0.13%) 40 (+2) 39 (+2) 1 👍
github.com/kai-scheduler/KAI-scheduler/pkg/podgrouper/podgrouper/plugins/skiptopowner/skiptopowner.go 87.50% (+0.20%) 64 (+1) 56 (+1) 8 👍

Please note that the "Total", "Covered", and "Missed" counts above refer to code statements instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code.

Changed unit test files

  • github.com/kai-scheduler/KAI-scheduler/pkg/podgrouper/podgrouper/hub/hub_test.go
  • github.com/kai-scheduler/KAI-scheduler/pkg/podgrouper/podgrouper/plugins/skiptopowner/skiptopowner_test.go

…osure

Both new branches were untested: the len(otherOwners) == 0 fallback in
skipTopOwnerGrouper and the closure that resolves a skipped owner through
the hub. The hub case exercises the real wiring end to end rather than a
stubbed resolver.

Signed-off-by: gshaibi <gshaibi@nvidia.com>
The recursion always bottoms out at the pod, which resolves to the PodJob
grouper rather than back to this one, so the owner list is never exhausted
here. Carrying a branch for a state no caller can produce is noise.

Signed-off-by: gshaibi <gshaibi@nvidia.com>
Signed-off-by: gshaibi <gshaibi@nvidia.com>
… filled

Signed-off-by: gshaibi <gshaibi@nvidia.com>
@davidLif
davidLif added this pull request to the merge queue Aug 13, 2026
Merged via the queue into main with commit cea080f Aug 13, 2026
18 checks passed
@davidLif
davidLif deleted the claude/workloadrunner-podgroup-grouping-a1cf19 branch August 13, 2026 12:37
@KaiPilotBot

Copy link
Copy Markdown
Collaborator

📝 Changelog fragment recorded

Thanks! This PR added the changelog fragment(s) below. Pending fragments are folded into CHANGELOG.md at release time, so it was intentionally not modified by this PR — your entry will appear in the next release:

@KaiPilotBot

Copy link
Copy Markdown
Collaborator

Backport failed for v0.16, because it was unable to cherry-pick the commit(s).

Please cherry-pick the changes locally and resolve any conflicts.

git fetch origin v0.16
git worktree add -d .worktree/backport-2067-to-v0.16 origin/v0.16
cd .worktree/backport-2067-to-v0.16
git switch --create backport-2067-to-v0.16
git cherry-pick -x ef66b0969db564af9d9bff1580917938558d83db 74091065123ec6905f4e021c05bfa1458b18b927 3ea43fbd7bad6032b55075d2999e8ca8bb654976 9f16afde714470db82565066d3961f769ac86c44 35b8090575db968d2c229d2583a319d851fba7fa

@KaiPilotBot

Copy link
Copy Markdown
Collaborator

Successfully created backport PR for v0.17:

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants