Skip to content

Draft of the DRA support - #771

Closed
bart0sh wants to merge 110 commits into
containers:mainfrom
bart0sh:DRA
Closed

Draft of the DRA support#771
bart0sh wants to merge 110 commits into
containers:mainfrom
bart0sh:DRA

Conversation

@bart0sh

@bart0sh bart0sh commented Aug 28, 2026

Copy link
Copy Markdown

No description provided.

bart0sh and others added 30 commits August 26, 2026 19:09
Introduces docs/dra/pct-design.md — the first draft of a design
for exposing nri-plugins Priority Core Turbo (Intel Speed Select
SST-CP + SST-TF) capacity as a Kubernetes Dynamic Resource
Allocation resource. Scope is intentionally narrow at this stage:
PCT priority only, topology-aware policy only.

The design premise is that kube-scheduler needs visibility into
per-punit HP-CPU headroom to avoid bin-packing HP pods onto
SST-TF-ineligible nodes where they would fall back silently to
lower-bucket turbo. Assumed KEP baseline: KEP-5075 required,
KEP-5517 required, KEP-5941 optional stretch. The core proposal
is Model B — one DRA device per SST-TF punit with hp-cpus and
lp-cpus consumable capacities — with an opt-in path to Model C
(per-CPU children under a shared counter) when KEP-5941 lands.

Draft state: carries four open decisions (cpuClass scope,
KEP-5941 target level, config-key naming, driver name) that
later commits resolve while broadening scope beyond PCT.
CLAUDE.md is auto-loaded into every session, so it should stay short
and repo-specific. Move accumulated DRA landscape / KEP details / PR
docs/dra/{landscape,pr-536-analysis,design,plan}.md, keeping only a
pointer list in CLAUDE.md. Also add the PCT allocator to the "primary
areas of interest" list.
Reference notes for anyone doing DRA-related work in this repo: KEP-5517
(Node Allocatable Resources) API and behavior; a map of related DRA KEPs
(consumable capacity, shared consumable capacity, node allocatable,
list-type attributes, standard numaNode, etc.); architectural summaries of
the SIG-node reference implementations (dra-driver-cpu and
dra-example-driver) with the specific patterns we borrow vs. those we do
not; and the current A/B/C design options being weighed for nri-plugins
DRA integration.

Not auto-loaded; CLAUDE.md points at it when a task touches DRA.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Captures what klihub's DRA-driver prototype (containers#536)
does and how, so future DRA work in this repo starts from an informed
baseline. Covers the CDI + env-var bridge (DRA_CPU<N>=1 injected into
containers, parsed at NRI CreateContainer) that remains a valid
mechanism; the prototype scars that KEP-5517 supersedes (getClaimedCPUs
env-var-parsing for accounting, mirror-in-spec.cpu workaround); and a
table mapping which pieces to reuse vs. drop when building on this.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Renamed pct-design.md -> design.md and rewrote to reflect a series of
design decisions made in review:

- Scope broadened from "PCT priority only" to "full cpuClass surface"
  (EPP, freqGovernor, uncore min/max, disabledCstates, PCT priority,
  SST CLOS binding). PCT-tier admission-time enforcement remains the
  concrete v1 value proposition; other cpuClass fields become device
  attributes.
- v1 targets the topology-aware policy only. Balloons is a v2 target
  with a separate driver (nri.balloons.cpu) sharing implementation
  code with topology-aware (nri.topology-aware.cpu).
- Device model: per-(cpuClass x punit) devices with consumable
  capacity (KEP-5075) and NodeAllocatableResources.mapping (KEP-5517).
  Model C (KEP-5941 shared counters) supported as opt-in when the
  upstream KEP lands.
- Resolved: multi-class-per-tier overcommit via cpuClass.dra.publish
  (default true) and refuse-to-start Configure-time validation.
- Resolved: per-policy dra config block under each policy CR.
- Resolved: driver naming per policy; v2 code-sharing verification
  checklist.
- Documented as open: class-derived attribute freshness (options A-F,
  recommendation F). Reference-driver context added: neither
  dra-driver-cpu nor dra-example-driver supports live reconfigure;
  restart-the-DaemonSet is their workaround, not open to nri-plugins.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ten-step landable-PR plan for the topology-aware DRA integration
described in design.md, ordered so the first five steps need no live
cluster (unit-testable), and the last three (wire-up, Helm, e2e) are
the cluster-dependent tail.

Each step is scoped to be independently reviewable with a clear
rationale, files touched, verification approach, and risk assessment.
Step 6 includes a detailed Imports & deps subsection tiering the
Kubernetes helper packages needed to build a DRA driver (must-have,
should-use, not-adopted, deferred to v2) so the imports story is not
re-litigated at PR review time. Cross-cutting concerns (feature-gate
probing, testing philosophy) and explicit "not part of v1" carve-outs
(balloons, Model C, class-freshness resolution, controller/webhook
binaries) close out the doc.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Convert bare identifiers and paths across CLAUDE.md and docs/dra/*.md
to markdown links so all cross-references render as clickable on
GitHub:

- KEP-NNNN mentions link to their kubernetes/enhancements issue
- PR containers#536 mentions link to containers#536
- Cross-doc references (design.md, landscape.md, pr-536-analysis.md,
  plan.md) use markdown links
- Repo-internal directories in CLAUDE.md link to the repo tree paths
- External-repo file references (dra-driver-cpu, dra-example-driver,
  kubernetes/kubernetes) link to specific blob/tree URLs; file:line
  references use #Lnnn anchors

Also fixes two stale filename references (pct-design.md ->
design.md) missed during the earlier rename.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
That directory does not exist. Topology-aware policy code lives at
cmd/plugins/topology-aware/policy/, alongside the plugin entry point.
Reworded the bullet to point at the actual policy directory instead
of a non-existent one.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the four-bullet "Takeaway for future DRA work" section with a
three-tier reuse breakdown that names concrete commits and files:

- Tier 1 — direct cherry-pick: six PR containers#536 commits that are pure
  refactors or additive utilities, orthogonal to the DRA design.
- Tier 2 — salvage-and-adapt: named files/functions where the
  mechanism is correct but the surrounding shape (package layout,
  env-var naming, driver name) changes.
- Tier 3 — learn-from-only: files whose semantics moved under the
  design changes (device schema, per-(class x punit) accounting,
  obsolete user-facing workarounds).

Motivation: the previous binary "reusable vs obsolete" framing understated
the middle tier. Future implementers now have a concrete
commit-and-file map instead of an abstract "reusable pieces" hint.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both reference drivers (dra-driver-cpu, dra-example-driver) use the
upstream CDI Go library rather than hand-rolling YAML. PR containers#536 hand-
rolled its writer, but that carries no atomic-write guarantee, no spec
validation, and no version tracking. Not a dep in nri-plugins today.

- plan.md step 7: CDI writer bullet points at the library instead of
  reusing PR containers#536's shape. Names the specific API surface
  (cdiapi.Cache.WriteSpec, RemoveSpec; specs-go types;
  GenerateTransientSpecName for per-claim filenames) and points at
  dra-driver-cpu's cdi.go as the pattern source.
- plan.md step 6 Imports & deps: adds the three library packages
  (pkg/cdi, specs-go, pkg/parser) to the Must-have tier.
- pr-536-analysis.md: moves the CDI writer bullet from Tier 2
  (salvage-and-adapt) to Tier 3 (learn-from-only), with a note
  explaining what to import instead.
- landscape.md: one-line entry under Other reference code linking
  to cncf-tags/container-device-interface with a sentence on why
  drivers should import it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Detailed TDD-ordered plan for the first landable PR of the DRA
integration: lift Kubernetes client bootstrap out of pkg/agent into a
new pkg/kubernetes/client package, and move the existing
pkg/agent/watch package to pkg/kubernetes/watch verbatim (git mv,
history preserved).

The plan is derived from three commits on the pr-536-dra branch
(5dcb66d, 42ec102, 8814064) — re-derived cleanly with tests added,
since PR containers#536 shipped without tests. Deliberately does NOT adopt PR
containers#536's parallel pkg/kubernetes/watch ObjectClient redesign; that
API-change work is out of scope for a "pure refactor" step 1.

Structure: 9 tasks, each with Files: blocks and tests-first
checkboxes. Task 1-2 establish client package + options + methods.
Task 3 adds content-type options with a two-pass retry-when-config-
not-set mechanism (order-independence). Task 4 moves the watch
package and adds minimum tests it currently lacks. Tasks 5-6 reserved
for numbering stability across review rounds. Task 7 rewires
pkg/agent/agent.go to use the new client, removes httpCli field,
adds four getters (NodeName, KubeClient, KubeConfig string,
RestConfig). Tasks 8-9 are acceptance and doc updates.

Plan went through two rounds of plan-review agent auto-review;
final verdict "APPROVE with minor polish" — all 15 first-round
findings addressed, plus 8 second-round polish items applied.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First task of the DRA v1 step 1 refactor (see docs/plans/20260820-
dra-step1-kubernetes-client-watch-lift.md). Introduces the
pkg/kubernetes/client package that will replace the inline kube-
client bootstrap currently in pkg/agent/agent.go.

Scope of this commit: package skeleton only — the Client struct,
functional Option type, and the two rest-config helpers
(GetConfigForFile, InClusterConfig) wrapping clientcmd.
BuildConfigFromFlags and rest.InClusterConfig respectively. Tests
first per TDD: four cases cover success, missing file, malformed
file, and not-in-cluster (guarded by KUBERNETES_SERVICE_HOST for CI
that happens to run inside a Pod). Fixture kubeconfig points at
example.com — clientcmd only parses, so no network is required.

Follow-up tasks add New() with the option catalog, Client methods
(RestConfig deep-copy, HttpClient, K8sClient, Close), and the
content-type options with retry-when-config-not-set. Then
pkg/agent/agent.go is rewired to use the new package.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Second task of the DRA v1 step 1 refactor (see docs/plans/20260820-
dra-step1-kubernetes-client-watch-lift.md).

Adds the New() constructor with functional options and the Client's
method surface:

- Options: WithKubeConfig, WithInClusterConfig,
  WithKubeOrInClusterConfig, WithRestConfig (deep-copies input via
  rest.CopyConfig), WithHttpClient.
- Methods: RestConfig (returns rest.CopyConfig(c.cfg) — top-level and
  value-struct fields safe to overwrite; nested map/slice contents
  share storage per upstream convention), HttpClient, K8sClient,
  Close (idempotent, nil-safe).
- errRetryWhenConfigSet sentinel prepared for Task 3's content-type
  options.
- New() falls back to WithInClusterConfig if no option set a config.

Tests: 11 new cases covering all option paths (kubeconfig success/
missing, in-cluster fallback, RestConfig acceptance, HttpClient
injection), rest.CopyConfig semantics on both directions, and
Close idempotency. In-cluster paths guarded by KUBERNETES_SERVICE_HOST
so tests skip cleanly when the CI happens to run inside a Pod.

Plan updated in place: earlier promise of "safe to mutate at any
depth" was aspirational — rest.CopyConfig shares nested map/slice
storage (Impersonate.Extra, TLSClientConfig.CAData, etc.). Matched
to upstream contract and to what dra-driver-cpu and PR containers#536's
WithRestConfig side expect.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Third task of the DRA v1 step 1 refactor (see docs/plans/20260820-
dra-step1-kubernetes-client-watch-lift.md). Adds the two content-type
options and their constants, exercising the retry-when-config-not-set
mechanism already prepared in Task 2's New() implementation.

Additions:

- ContentTypeJSON and ContentTypeProtobuf constants.
- WithAcceptContentTypes(...string) — joins values with commas, sets
  cfg.AcceptContentTypes.
- WithContentType(string) — sets cfg.ContentType.
- Both return errRetryWhenConfigSet when c.cfg is nil so New()'s
  two-pass logic re-applies them after a config-source option runs.

Tests (5 new cases):

- Basic content-type application (assert config field is set as
  expected).
- Order-independence: config-source-first vs content-type-first yields
  identical clients — verifies the retry mechanism.
- Content-type-only-no-config-source: falls back to WithInClusterConfig
  which errors outside a cluster; guarded by KUBERNETES_SERVICE_HOST.
- Multiple retries: two WithAcceptContentTypes deferred, retry list
  ordering preserved (last write wins within the retry list).

Semantics note documented in the multi-retry test: retry-list options
apply AFTER all non-retry options, so callers should not mix retry-
required and non-retry setters for the same field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fourth task of the DRA v1 step 1 refactor (see docs/plans/20260820-
dra-step1-kubernetes-client-watch-lift.md). Relocates the watch
package to make it available to future consumers outside the agent
(the DRA driver in later plan steps).

Changes:

- git mv pkg/agent/watch/{watch,object,file}.go
  pkg/kubernetes/watch/ — preserves history via git rename detection.
- No API change: Object(ctx, ns, name, CreateFn) and File(path,
  UnmarshalFn) signatures unchanged; ObjectWatch.Stop(),
  FileWatch.Stop(), and the type-alias/const surface (Interface,
  Event, EventType, Added/Modified/Deleted/Bookmark/Error) unchanged.
- Logger tag changed from logger.Get("agent") to logger.Get("watch")
  so operator log-grep matches the new package location.
- Import path in pkg/agent/agent.go:36 updated from
  pkg/agent/watch to pkg/kubernetes/watch. Plan-deviation note:
  this update was scheduled for Task 7 but is included here to keep
  the tree compiling between per-task commits. Task 7 no longer needs
  to touch that import.

Tests (new, since the moved package shipped without any):

- Type-alias assertions: compile-time verification that Interface,
  EventType, Event are true aliases of k8s.io/apimachinery/pkg/watch
  types.
- Event-type constant equality: each re-exported const equals its
  upstream value.
- Object watch happy path: events pushed through the fake Interface
  from a CreateFn arrive on ResultChan in order.
- Object.Stop() idempotency: two calls do not panic.
- File watch Create-vs-Write distinction: defensive guard that Create
  fsnotify events emit Added and Write events emit Modified — PR
  containers#536's parallel implementation ships a copy-paste bug emitting
  Added for both, and this test guards against re-introducing it.
  Uses O_WRONLY|O_APPEND for the modify step so we get a pure Write
  event, not the O_CREATE|O_TRUNC that os.WriteFile does.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Load-bearing task of the DRA v1 step 1 refactor (see docs/plans/
20260820-dra-step1-kubernetes-client-watch-lift.md Task 7). Replaces
the inline Kubernetes client bootstrap with the new pkg/kubernetes/
client wrapper, and exposes agent state that future consumers (the
DRA driver in later plan steps) need.

Changes:

- Agent.httpCli field removed. All uses replaced with
  a.k8sCli.HttpClient() (where a.k8sCli is now *client.Client).
- Agent.k8sCli field type changed from *k8sclient.Clientset to
  *client.Client. Existing use at a.k8sCli.CoreV1().Nodes().Watch(...)
  keeps compiling because *client.Client embeds *kubernetes.Clientset.
- setupClients: replaced ~25 lines of inline REST config resolution
  and Clientset construction with client.New(
  client.WithKubeOrInClusterConfig(a.kubeConfig)). Passes
  a.k8sCli.HttpClient() and a.k8sCli.RestConfig() to
  ConfigInterface.SetKubeClient — the callback signature is unchanged,
  so downstream consumers are unaffected.
- configure() NRT client init: uses a.k8sCli.RestConfig() and
  a.k8sCli.HttpClient() instead of a.getRESTConfig() + a.httpCli.
  Guards on a.k8sCli == nil to avoid a nil-panic if configure is
  invoked before setupClients completes.
- cleanupClients: collapsed to a.k8sCli.Close() (nil-safe from the
  client package) + nil-out of a.k8sCli and a.nrtCli.
- getRESTConfig: removed entirely. Its logic now lives inside
  client.New's option chain (WithKubeOrInClusterConfig).
- Removed unused imports "k8s.io/client-go/tools/clientcmd" and
  k8sclient "k8s.io/client-go/kubernetes".

Getters added on *Agent (public surface):

- NodeName() string — the kubernetes node name.
- KubeClient() *client.Client — the shared client wrapper; nil
  before setupClients has run successfully.
- KubeConfig() string — the kubeconfig file path (or empty for
  in-cluster). Matches PR containers#536 commit 8814064's signature.
- RestConfig() *rest.Config — shortcut for
  KubeClient().RestConfig(); nil-safe before setupClients.

Tests (new pkg/agent/agent_test.go):

- Getters return zero values on a freshly-constructed Agent (before
  setupClients).
- Getters return non-nil values once a.k8sCli is populated with a
  real client.Client built from the fixture kubeconfig
  (../kubernetes/client/testdata/kubeconfig-example.yaml).

Preexisting failure in pkg/sysfs (unrelated: panics in Ginkgo-based
cache-discovery and cluster-CPUSet tests) verified via stash-and-
retest before applying — not introduced by this change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Step 1 of docs/dra/plan.md landed as commits 022b876...f595440 on
the DRA branch. Added a "Landed:" pointer in plan.md's Step 1 section
linking to the archived plan file.

Moved docs/plans/20260820-dra-step1-kubernetes-client-watch-lift.md
to docs/plans/completed/ per the plan's own Task 9.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan for pkg/resmgr/dra/ package skeleton — the structural placeholder
that enforces resolved decision 6 (no policy-binary imports). Three
production files (doc.go, deps.go, plugin.go) plus a test file. Plan
went through auto-review; three important items applied: license
headers, Deps description consistency (empty struct, no interfaces yet),
and TestNoCmdPluginsImport implementation details.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Step 2 of the DRA integration plan (docs/dra/plan.md). Creates the
pkg/resmgr/dra package as a structural placeholder — physically
separating the shared DRA kubelet-plugin code from any policy binary
and enforcing the code-sharing contract (design.md resolved decision
6).

Four files:

- doc.go: package comment with the import-boundary contract
- deps.go: Deps struct (empty; fields added in Step 6)
- plugin.go: Plugin struct + New() constructor returning
  errNotImplemented
- plugin_test.go: two tests — TestNew_ReturnsNotImplemented verifies
  the sentinel error, TestNoCmdPluginsImport runs go list -deps ./...
  and fails if any transitive dep is from cmd/

No functional DRA code is introduced in this step. No existing files
are modified.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Prevent go test -c compiled test binaries from being accidentally
staged. Caught when pkg/resmgr/dra/dra.test was included in the
previous commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Step 2 of docs/dra/plan.md landed as commits a1244e3, 404ff24.
Added Landed: pointer in plan.md's Step 2 section.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan for cpuClass.dra.publish config field + ValidateCPUClassesForDRA.
The plan went through two rounds of auto-review; all blockers from
pass 1 addressed and two documentation-sync items added in pass 2.

Key design decisions captured:
- ValidateCPUClassesForDRA(classes, sharedCounters) — no []Punit
  parameter in v1 since CPUClass carries no punit affinity; tier-only
  validation is the static maximum. Per-punit deferred to Step 5.
- Non-PCT classes exempt from tier check, still published by Step 5.
- Assoc-only tiers keyed by SstClosID string label.
- CPUClassDRA type stays in config API; validation function goes in
  pkg/resmgr/cpuclass/dra.go (extends in Step 5).
- Deep copy via make generate, four CRD YAML files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Step 3 of the DRA integration plan (docs/dra/plan.md).

Config API (pkg/apis/config/v1alpha1/resmgr/policy/cpuclass.go):

- Add CPUClassDRA struct (Publish *bool, +k8s:deepcopy-gen=true)
- Add DRA *CPUClassDRA field to CPUClass with json:"dra,omitempty"
- Add DRAPublish() bool getter (nil DRAConfig -> true, nil Publish ->
  true, otherwise *DRA.Publish)

DRA validation (pkg/resmgr/cpuclass/dra.go):

- Add ValidateCPUClassesForDRA(classes, sharedCounters bool) error.
  Groups DRA-published PCT classes by tier label ("pctPriority=high",
  "pctPriority=low", "closID=<N>"). If any tier has >1 published class
  and sharedCounters is false, returns an error naming the tier, the
  conflicting classes (sorted), and the two resolutions. Non-PCT
  classes are exempt from the check. Called at driver Configure time,
  not at config load time.

Regenerated (make generate):
- pkg/apis/config/.../zz_generated.deepcopy.go: DeepCopyInto/DeepCopy
  for CPUClassDRA, updated CPUClass.DeepCopyInto for the DRA field
- All four CRD YAML files (config/crd/bases/ + deployment/helm/ copies)

Tests:
- TestCPUClassDRAPublish (table-driven, 4 cases)
- TestCPUClassDRADeepCopy (pointer independence check)
- TestValidateCPUClassesForDRA (table-driven, 13 cases: empty list,
  non-PCT exempt, single HP/LP, HP+LP ok, assoc-only same/different
  CLOS, two HP both published, sharedCounters bypass, one opted out,
  >2 conflicting classes, mixed PCT+non-PCT)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Step 3 of docs/dra/plan.md landed as commit a0fd7f1.

Updates:
- plan.md Step 3: update Actions to reflect final implementation
  (ValidateCPUClassesForDRA in pkg/resmgr/cpuclass/dra.go, no []Punit
  parameter, tier-only classification, non-PCT exempt, add Landed
  pointer).
- plan.md Step 5: note that dra.go already exists after Step 3 and
  must be extended; use DRAPublish() getter not DRA.Publish directly.
- design.md §"v1 approach": document that validation is tier-only
  in v1 (CPUClass has no punit affinity), link to
  ValidateCPUClassesForDRA, explain non-PCT class exemption and
  per-punit deferral to Step 5.
- Move step 3 plan to docs/plans/completed/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pkg/sysfs/testdata/, pkg/resmgr/lib/memory/testdata/, and
pkg/topology/testdata/ contain mock /sys directory trees used by
test suites as fake hardware topology. They are not tracked in
upstream (containers/nri-plugins) either. Adding them to .gitignore
so they don't show up as untracked files after running the test suite.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan for pct.Allocator.PickHpCpus / ReleaseHpCpus + capacity helpers.
Went through two rounds of auto-review; two sets of blockers resolved:

Pass 1: re-keyed from punit index to (pkgID, punitID); separated
hpDRAUsed from hpUsed to prevent clearHpUsage clobber; added
Punits() []PunitInfo as the exported surface for Step 5.

Pass 2: corrected hpReserveCpus description (union into 'used' before
MaxHpCpus subtraction, not exclude from 'free'); fixed hpInUseCpus
loop to range over punits not hpUsed; dropped PunitInfo.Idx; split
Task 2 into 2a (additive) and 2b (hint-machinery changes); added
TestHpReserveRoomWithDRAHolds and non-vacuous hpInUseCpus fixture.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Step 4 of the DRA integration plan (docs/dra/plan.md).

New exported surface on pct.Allocator:

- PunitInfo{PkgID, PunitID, HPCapacity, NonHPCapacity} — snapshot
  type returned by Punits(). Step 5 (DRADevices) uses HPCapacity and
  NonHPCapacity to set RequestPolicy.max values for DRA devices.
- Punits() []PunitInfo — per-punit capacity snapshot. Returns nil
  when the allocator is inactive.
- PickHpCpus(pkgID, punitID, n int, held cpuset.CPUSet) — selects n
  HP-eligible CPUs from the named punit. Enforces GuaranteedHpCpus
  cap. Writes to hpDRAUsed (not hpUsed), so clearHpUsage called from
  the non-DRA UseClass path cannot evict DRA holds.
- ReleaseHpCpus(pkgID, punitID int, cpus cpuset.CPUSet) — idempotent
  release; ignores unknown punits and CPUs not in hpDRAUsed.

New Allocator state:

- hpDRAUsed map[int]cpuset.CPUSet — DRA-claimed HP CPUs, index-keyed
  like hpUsed. Separate map prevents the non-DRA clearHpUsage path
  from evicting DRA holds.

Hint machinery updates:

- hpInUseCpus: loop now ranges over punits (not hpUsed) so DRA-only
  punits are not skipped; unions hpUsed[i] and hpDRAUsed[i].
- hpReserveCpus: unions hpUsed[i] and hpDRAUsed[i] before subtracting
  from MaxHpCpus, so DRA holds reduce reported HP room and prevent
  hint-driven HP over-subscription.

Private helpers (used by Punits()):

- punitIdxByID(pkgID, punitID) — (PkgID, PunitID) → index lookup.
- punitHPCapacity(idx) — GuaranteedHpCpus or 0 if ineligible.
- punitNonHPCapacity(idx) — non-HP allocatable CPUs with
  allowed.Size()>0 guard.

Tests (8 new, all in pct_test.go):

TestPunitHPCapacity, TestPunitNonHPCapacity, TestAllocatorPunits,
TestPickHpCpus, TestReleaseHpCpus — cover success, error, edge cases.
TestHpDRAUsedIsolation — non-HP UseClass overlapping a DRA-held CPU
  does not evict it from hpDRAUsed; DRA holds visible in hpInUseCpus
  on a DRA-only punit (non-vacuous fixture).
TestHpReserveRoomWithDRAHolds — DRA holds reduce hpReserveCpus room.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Landed as commit 5c4c04f. Updated plan.md Step 4 with Landed pointer
and the key deviations from the original spec: (pkgID,punitID) keying,
hpDRAUsed separate map, PunitInfo/Punits() export for Step 5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan for cpuclass.Handler.DRADevices() — emits []resapi.Device in
Model B shape per (published class × punit). Went through three rounds
of auto-review; all blockers resolved.

Key decisions captured:
- API field names from k8s.io/api@v0.36.0: NodeAllocatableResourceMappings
  + {CapacityKey, AllocationMultiplier} (not the KEP draft names).
- go.mod bump to v0.36.3 as standalone Task 1.
- buildDRADevices() pure builder + Handler.DRADevices() thin wrapper
  for testability without SST/sysfs fakes.
- pct.Allocator.IsHPClass() exported in Task 2.
- Topology ints (nri/packageID, nri/punitID) always emitted even when 0.
- Device name: sanitizeBase(63-suffix-disambig), per-class base
  pre-computed; dedup seen-map keyed on className not base.
- Frequency attrs and numaNode explicitly deferred with rationale.
- Non-SST-TF (no punits) and non-PCT class overlap as known v1 limits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bart0sh and others added 29 commits August 26, 2026 19:22
Covers RBAC, host mounts, and DeviceClass wiring for the topology-aware
chart so the already-landed DRA driver can register with kubelet.
Plan for the e2e test proving the full DRA claim path for the
topology-aware policy (ResourceClaim -> CDI -> CLOS association),
including the harness provisioning change needed to enable the
DRAConsumableCapacity/DRANodeAllocatableResources alpha gates, since
the e2e VM currently runs a bare kubeadm init with no gate support.
Reviewed and revised through four automated plan-review passes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Step 10 (the last step of the DRA v1 plan) has landed; move its plan
file alongside steps 1-9's, per this branch's established convention.
…ager/kubelet debug logging

Independent of k8s_feature_gates -- either can be set alone or together;
provision.yaml writes the kubeadm --config file whenever at least one is
non-empty. Verified live: kubelet and kube-scheduler both emit DRA-specific
log lines (dra_plugin_manager, PreFilter/PreBind.DynamicResources,
nodeallocatabledynamicresources.go's "Patched pod status with
NodeAllocatableResourceClaimStatuses") at --v=5 on a gated VM.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
k8s.io/api v0.37 (Kubernetes 1.37) tombstones Device.NodeAllocatableResourceMappings
(CapacityKey/AllocationMultiplier), replacing it with the differently-shaped
Device.NodeAllocatableResources (Mapping/Overhead). A binary built against
k8s.io/api v0.36 sending the old field name gets it silently pruned by a 1.37
API server, so KEP-5517 support silently no-oped there even with the gate on.

Bump k8s.io/api, apimachinery, client-go, kubelet, dynamic-resource-allocation
to v0.37.0; rework dra.go's device construction to the new
NodeAllocatableResources/Mapping/CapacityMultiplier shape; add
Plugin.WatchHealthStatus (new kubeletplugin.DRAPlugin interface method in
v0.37; this driver doesn't do per-device health reporting, so it returns
ErrHealthNotSupported); update the e2e test's feature-gate probe to check the
new field name.

This moves the effective floor from Kubernetes 1.36 to 1.37 -- a single
compiled binary can only speak one wire-shape for this field. Verified live
against a real Kubernetes 1.37.0 cluster: Test verdict: PASS, full scheduler
DynamicResources flow observable at --v=5.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… bump

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…s.Mapping

Kubernetes 1.37 independently tombstones
NodeAllocatableResourceClaimStatus.Resources map[ResourceName]Quantity on the
pod-status side (separate from the Device-side rename fixed in 38f6959),
replacing it with Mapping []NodeAllocatableMappedResources{Name, Quantity} --
a list, not a map. Update wait-node-allocatable-claim-status's assertion to
match. Verified live: Test verdict: PASS against a real Kubernetes 1.37.0
gated VM, full scheduler DynamicResources flow observable at --v=5.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… 1.37

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@bart0sh bart0sh closed this Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant