[proto]: bolt a DRA driver frontend on the topology-aware policy. - #536
[proto]: bolt a DRA driver frontend on the topology-aware policy.#536klihub wants to merge 15 commits into
Conversation
d10467e to
0f3a301
Compare
66c2519 to
8527808
Compare
8527808 to
f96ea65
Compare
776684c to
7ce62a1
Compare
7ce62a1 to
a3b4047
Compare
| cs.sharable = cs.sharable.Union(all.SharableCPUs().Intersection(cpus)) | ||
| cs.reserved = cs.reserved.Union(all.ReservedCPUs().Intersection(cpus)) | ||
| cs.claimed = cs.claimed.Difference(cpus) | ||
| } |
There was a problem hiding this comment.
After a Pod with DRA claim is terminated, general workloads do not gain access to the released Unclaimed CPUs. Considering the scenario, where a DRA claimed pod access to core 1, and the other generic shared workloads has access to 2-16 cores. After terminating the claimed Pod, the internal shared pool gets updated, but the generic workloads still has access to only 2-16 cores.
During Pod termination, StopContainer is invoked first followed by NodeUnprepareResources function. This 'unclaimCPUs' function falls under 'NodeUnprepareResources' flow, it unclaims and release the claimed CPUs through updateSharedAllocation for remaining workloads. This update happens internally, but is not reflected in the end container.
Releasing the claimed CPUs back to shared/reserved pool in function 'ReleaseCPU' (part of the StopContainer flow) seems to work, the general workloads can access full shared pool of 0-16 upon a claimed pod termination. But, not sure if this would be a valid placement.
There was a problem hiding this comment.
@vigashinikesavan It was a long while ago when we rolled this early prototype, so it took me a while to pick up where we left off. I tried a few simple cases and indeed once you release a claim, if you have burstable or a best-effort container which would be eligible to run on the freed up CPUs (if they run in a shared pool where the CPU was returned to), their effective cgroup cpuset might not get correctly updated.(.. immediately.)
But based on a quick look at the code, and my few simple tests, this does not happen as/because of what you state above. Instead what happens is that we do update the remaining containers correctly, but because the update did not happen in the context of a normal resource allocation or release (CreateContainer or StopContainer) the cpuset/resource updates we did for the containers do not get propagated back to the runtime as part of an NRI response but instead they stay cached in the plugin, until the next container creation or release. You can test this easily when you are in the incorrect state, by creating for instance a new best effort container, which will then flush all the collected pending updates in the response, including the ones collected during the release/unprepare of the claim. This will then cause the containers running in the shared pools to finally get correctly updated. What we should do in this situation is to update the affected containers using an unsolicited container update, but the current code does not do it.
Anyway, this was an early prototype to see how far we could get with what was available at the time we rolled this draft PR. This needs to be updated/reworked pretty much, because a lot has happened since on the DRA front, and the approach taken in this proto is now outdated.
There was a problem hiding this comment.
Got it. Thanks for the clarification.
f492576 to
25c5540
Compare
6b292c3 to
2c08fe3
Compare
A dear child has many names... Signed-off-by: Krisztian Litkey <krisztian.litkey@intel.com>
AsYaml can be used to produced YAML-formatted log blocks with
something like this:
logger.DebugBlock(" <my-obj> ", "%s", log.AsYaml(my-obj))
Signed-off-by: Krisztian Litkey <krisztian.litkey@intel.com>
Split out K8s client setup code from agent to make it more generally available to any kind of plugin, not just resource management ones. Signed-off-by: Krisztian Litkey <krisztian.litkey@intel.com>
Allow setting the content types a client accepts and the type it uses on the wire. Provide constants for JSON and protobuf content types. Signed-off-by: Krisztian Litkey <krisztian.litkey@intel.com>
Split out K8s watch wrapper setup code from agent to make it generally available to any kind of plugin, not just resource management ones. Signed-off-by: Krisztian Litkey <krisztian.litkey@intel.com>
Signed-off-by: Krisztian Litkey <krisztian.litkey@intel.com>
Add the necessary RBAC rules (access resource slices and claims) and kubelet host mounts (plugin and plugin registry directories) to the topology-aware policy Helm chart. Add a device class for native.cpu DRA driver. Signed-off-by: Krisztian Litkey <krisztian.litkey@intel.com>
Add an interface for caching policy agnostic data, similar to
but simpler than {Get,Set}PolicyData(). Add an interface for
querying the full container environment variable list.
Signed-off-by: Krisztian Litkey <krisztian.litkey@intel.com>
Sort caches by level, kind, and id to enumerate caches globally. Add a common DRA device representation for CPUs. Signed-off-by: Krisztian Litkey <krisztian.litkey@intel.com>
Signed-off-by: Krisztian Litkey <krisztian.litkey@intel.com>
Add the necessary plumbing to allow implementations act as a CPU DRA driver. In partice, add an option for publishing CPUs as DRA devices and interfaces to allocate and release CPUs as claims are prepared and unprepared. Signed-off-by: Krisztian Litkey <krisztian.litkey@intel.com>
Register as a 'native.cpu' DRA resource driver/plugin. Provide DRA CPU device publishing for policy implementations. Generate CDI Spec for the published CPU devices. Hook the active policy in for CPUs allocation and release for claim preparation and un- preparation. Signed-off-by: Krisztian Litkey <krisztian.litkey@intel.com>
Flush pending changes immediately using an unsolicited update to containers instead of waiting for the next NRI event to do so. Signed-off-by: Krisztian Litkey <krisztian.litkey@intel.com>
Publish CPUs as DRA devices. Use a sledgehammer to bolt allocation and release of DRA-claimed CPUs on top of the current implementation. Signed-off-by: Krisztian Litkey <krisztian.litkey@intel.com>
Signed-off-by: Krisztian Litkey <krisztian.litkey@intel.com>
2c08fe3 to
9deb65b
Compare
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>
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>
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>
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>
This prototype patch set bolts a DRA allocation frontend on top of the existing topology aware resource policy plugin. The main intention with of this patch set is
Notes:
This patched NRI plugin, especially in its current state and form, is not a proposal for a first real DRA-based CPU driver.
If you want to play around with this (for instance modify the exposed CPU abstraction), the easiest way is to
helm install --devel -n kube-system test oci://ghcr.io/$YOUR_GITHUB_USERID/nri-plugins/helm-charts/nri-resource-policy-topology-aware --version v0.9-dra-driver-unstableYou can then test if things work with something like