Skip to content

fix(k8s): recover a node the controller has forgotten - #809

Open
pre wants to merge 2 commits into
ROCm:mainfrom
silogen:pr/node-reregister
Open

fix(k8s): recover a node the controller has forgotten#809
pre wants to merge 2 commits into
ROCm:mainfrom
silogen:pr/node-reregister

Conversation

@pre

@pre pre commented Sep 2, 2026

Copy link
Copy Markdown

Related:

Motivation

Two faults that both come from a controller answering NotFound.

A node the controller has forgotten never comes back. The operator registers
each Kubernetes node once, at startup, then only sends heartbeats. If the
controller loses its record of a node, through spur node remove or through a
rebuild from an older state, every later heartbeat fails with NotFound and the
operator merely logs it. The node stays missing from sinfo until somebody
restarts the operator, and the cluster quietly runs at reduced capacity.

A refused launch is reported as an unreachable agent. When an agent answers
NotFound or FailedPrecondition, the controller maps it to Unreachable. The
user sees "agent unreachable" although the agent answered, and the real reason is
lost. That sends anyone debugging it towards the network instead of the job.

Technical Details

The operator keeps the RegisterAgentRequest it built at startup. When a heartbeat
comes back NotFound, it registers again from that stored request and logs that it
did so. Nothing else changes: any other error keeps the existing behaviour.

On the controller side, tonic::Code::NotFound and tonic::Code::FailedPrecondition
now map to DispatchError::AgentRejected and carry the agent's own message, rather
than falling through to Unreachable. The two codes mean the agent answered and
declined, which is the definition of a rejection.

The operator's "no SpurJob carries this job id" path is also made explicit, so
the rejection that reaches the user says what actually happened instead of
deadline_exceeded, which the controller used to read as unreachable.

Related:

Test Plan

  1. With the operator running, remove a node from the controller with
    spur node remove, then wait for one heartbeat. Do not restart the operator.
  2. Check sinfo.
  3. Submit a job through the CLI that the agent will refuse, and read the message
    the user gets and the reason in the controller log.

Test Result

Environment: three node RKE2 cluster on cloud VMs, 8 vCPU and 96 GiB each, no GPU.

  • spur node remove took a node away and sinfo showed 2. One heartbeat later the
    operator logged that the controller did not know the node and that it registered
    it again, and sinfo showed 3. The operator was not restarted.
  • Before the change the node stayed missing and the operator logged the failed
    heartbeat on every cycle.
  • A CLI job the agent refused now reads 1 agent rejected launch, and the
    controller log holds the agent's reason. Before the change it read
    "agent unreachable".
  • cargo clippy --workspace --exclude spur-ffi --all-targets --locked reports
    nothing; cargo test --locked passes 3253 tests.

Submission Checklist

pre added 2 commits September 2, 2026 15:23
The node watcher registers a node on its initial list and when its
resources change. A controller that lost its state, for example a Raft
cluster that was built again, is neither event, so every node stayed
unknown, sinfo reported none, and the cluster scheduled nothing until the
operator was restarted by hand.

The heartbeat already holds the RegisterAgentRequest of each tracked node.
A heartbeat that comes back NOT_FOUND now registers the node again from
that stored request.
A job from the Slurm-compatible CLI in Pod mode waited in the queue with
"JobLaunchFailure (dispatch confirmation failed (0/1 confirmed): 1 agent
unreachable)". The operator was running and reachable. It refused the
launch because no SpurJob carries the job id, which only kubectl apply of
a SpurJob creates. The operator hid the refusal behind its own retry
budget and answered DEADLINE_EXCEEDED, which the controller reads as a
transport failure.

The operator now answers NOT_FOUND with what is missing when the budget
runs out, and the controller classifies NOT_FOUND and FAILED_PRECONDITION
as an explicit rejection. The queue reason now says the agent rejected the
launch, and the operator's message reaches the controller log.
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