Skip to content

feat(node-agent): advertise the RDMA extended resource from a simulator - #734

Draft
giuliocalzo wants to merge 4 commits into
NVIDIA:mainfrom
giuliocalzo:feat/node-agent-rdma-plugin
Draft

feat(node-agent): advertise the RDMA extended resource from a simulator#734
giuliocalzo wants to merge 4 commits into
NVIDIA:mainfrom
giuliocalzo:feat/node-agent-rdma-plugin

Conversation

@giuliocalzo

@giuliocalzo giuliocalzo commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

Mock InfiniBand nodes did not report rdma/ib, so a workload gated on that resource stayed Pending however complete the node's IB surface was. On real hardware the Network Operator's k8s-rdma-shared-dev-plugin advertises it; that plugin cannot run against the mock, because it is a Go binary whose syscalls the LD_PRELOAD sysfs redirect never sees, and it discovers HCAs through netdevs the mock does not render.

A new node-agent simulator, internal/agent/rdmaplugin, stands in for it — the bridge role pcibus.Apply's NFD feature file plays for PCI presence:

  • Profiles declare the resource under infiniband.rdma_resource; all five IB-enabled profiles ship rdma/ib = 64. Upstream that count is rdmaHcaMax, a pod-sharing limit rather than a device count, so a 4-GPU GB300 compute tray reports 64 while carrying 4 HCAs.
  • Apply patches .status.capacity and Revoke withdraws the key, because kubelet never removes capacity it does not own. The simulator is an Applier only — no daemon, no re-assertion ticker — so the resource is republished on the next reconcile rather than on a clock of its own.
  • It converges rather than only materializing. Renaming or dropping the block withdraws the previous key instead of stranding it, and a node already reporting the right value costs one GET rather than a write, which is what makes re-running Apply cheap.
  • The node-agent container gains NODE_NAME, without which the compiled State carries no node identity and there is nothing to patch. This also fixes state.Node.NodeName being empty for every simulator in that container, same as feat(node-agent): Node Agent CDI Simulator #728.
  • Validation moved out of chart helpers into Stage, so it also covers gpu.customConfig and the Control Plane source that will replace the file source.

MEP-0003's simulator and InfiniBand surface tables gain the package, plus a note on why a node resource with no other publisher is owned by a simulator while node labels stay delegated to NFD and GFD. Happy to drop those edits if @roma-glushko would rather fold this into ibhca.

Known limitations

  • Pods requesting the resource schedule and run but get no device injection: the mock's IB surface reaches containers through the hostPath tree and LD_PRELOAD shims, not /dev/infiniband char devices.
  • Advertising needs patch on nodes/status, which the chart's ClusterRole now grants.
  • The DaemonSet's terminationGracePeriodSeconds: 2 is below nodeAgent.shutdownTimeout: 30s. Revoke is one GET plus one PATCH so it normally lands well inside 2s, but a SIGKILL at the grace boundary would leave a stale rdma/ib key on the node. Left alone here since raising it affects every container and slows rollouts.

Test plan

  • make test — green outside the sandbox; pkg/gpu/allocwatch and pkg/network/mockib/daemon fail inside it only because unix-socket bind is blocked
  • make lint-fix — 0 issues
  • make helm-tests — 175 tests, 15 snapshots, snapshots refreshed for the new NODE_NAME env and the profile blocks
  • internal/agent/rdmaplugin unit tests drive a fake node API that applies the patches it receives, so they assert on resulting capacity: advertise, no-op when already correct, update on count change, withdraw on rename/removal, revoke idempotently, tolerate a deleted node, republish after an external removal, and reject an unqualified name, a non-positive count or an empty NODE_NAME
  • internal/agent/source sweeps every chart profile, so an IB profile that forgets the block fails CI
  • Live check on a Kind cluster: kubectl get node -o jsonpath='{.status.capacity}' shows rdma/ib: 64 and is gone after helm uninstall

@giuliocalzo
giuliocalzo force-pushed the feat/node-agent-rdma-plugin branch from da7d63e to 6470627 Compare August 25, 2026 16:13
@roma-glushko

Copy link
Copy Markdown
Member

@giuliocalzo let's consider whether Mokka Control Plane is the right place for this functionality. The Control Plane has access to K8s by definition, so it seems like a better place. I would like to keep node agent K8s agnostic as much as possible.

Let me know what do you think?

@giuliocalzo

Copy link
Copy Markdown
Collaborator Author

@giuliocalzo let's consider whether Mokka Control Plane is the right place for this functionality. The Control Plane has access to K8s by definition, so it seems like a better place. I would like to keep node agent K8s agnostic as much as possible.

Let me know what do you think?

@roma-glushko this feature is core one, very likely of internal/agent/pcibus/pcibus.go the agent should apply itself without the needs of the Control Plane

@giuliocalzo
giuliocalzo force-pushed the feat/node-agent-rdma-plugin branch 3 times, most recently from c79f639 to 6e57a63 Compare August 26, 2026 09:42
@giuliocalzo
giuliocalzo force-pushed the feat/node-agent-rdma-plugin branch 4 times, most recently from 0feed7b to 5ff1c28 Compare August 26, 2026 12:46
Real InfiniBand nodes carry rdma/ib in their capacity, published by the
Network Operator's k8s-rdma-shared-dev-plugin. A workload gated on that
resource therefore stays Pending on a mock node however complete its IB
surface is.

That plugin cannot run here: it is a Go binary, so the LD_PRELOAD sysfs
redirect never sees its syscalls, and it discovers HCAs through netdevs the
mock does not render. A node-agent simulator stands in for it and publishes
the resource itself — the bridge role pcibus.Apply's NFD feature file plays
for PCI presence.

Profiles declare it under infiniband.rdma_resource; all five IB-enabled
profiles ship rdma/ib = 64. Upstream that count is rdmaHcaMax, a pod-sharing
limit rather than a device count, so a 4-GPU GB300 compute tray reports 64
while carrying 4 HCAs.

Apply patches .status.capacity and Revoke withdraws the key, since kubelet
never removes capacity it does not own. Renaming or removing the block
withdraws the previous key rather than stranding it, and a node already
reporting the right value costs one GET instead of a write, so re-running
Apply on every reconcile is cheap. Each Apply logs what the node ended up
advertising, write-free runs included: reconciles fire only on config change,
so silence would be indistinguishable from a simulator that never ran.

The node-agent container gains NODE_NAME, without which the compiled State
carries no node identity and there is nothing to patch. Validation lives in
Stage rather than in chart helpers, so it also covers gpu.customConfig and the
Control Plane source that will replace the file source.

MEP-0003's simulator and InfiniBand surface tables gain the package, with a
note on why a node resource with no other publisher is owned by a simulator
while node labels stay delegated to NFD and GFD.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
Registering the rdmaplugin simulator builds a full client-go clientset,
which widens govulncheck's reachable call graph until the vulnerable
x/text normalization symbols become reachable through
rest.Request.Stream (GO-2026-5970, unbounded loop on invalid input).

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
The workflow asked the action for `latest` while `make tools` pins v2.12.2,
so the two agreed only until a release changed a linter's mind. v2.13.1 no
longer reports the deprecated grpc.DialContext calls in pkg/gpu/allocwatch,
which made the //nolint:staticcheck directives guarding them unused and
failed nolintlint on every PR — code that lints clean locally.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
@giuliocalzo
giuliocalzo force-pushed the feat/node-agent-rdma-plugin branch from 5ff1c28 to 7e9baa6 Compare August 26, 2026 13:07
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.

2 participants