feat(#2448): emit one multi-agent DAG for hivectl restart / restart-all

A hive-wide restart was N separate single-agent DAGs (one submit::restart
per agent). Now that agent is per-node (#2445), make it ONE DAG with a
per-agent restart subgraph each.

- templates::restart takes an agent list: each agent gets an independent
  subgraph (a head SetWanted(Up) root, then its restart chain), so the N
  subgraphs run concurrently on their own leases. One agent = the ordinary
  single-agent restart; unifies the old restart + graceful_restart fns.
- submit::restart / graceful_restart stay as single-agent wrappers over
  the new submit::restart_many(agents, graceful).
- server.rs handle_restart_all + handle_restart_scoped submit one
  restart_many call instead of looping per agent. Infra containers
  unchanged (no lease/DAG, synchronous).

Scope: restart + restart-all only. Broad stop+start is the same pattern
(stop/start templates take agent lists) — a follow-up increment.
This commit is contained in:
atlas 2026-07-14 23:00:10 +02:00 committed by mara
commit 1739716fa2
3 changed files with 105 additions and 100 deletions

View file

@ -27,12 +27,27 @@ pub fn rebuild(coord: &Arc<Coordinator>, agent: &str, source: Source, reason: St
submit_and_emit(coord, templates::rebuild(agent, source, reason, None, true))
}
/// Restart: mechanical stop + converge to `wanted = Up`. The intent
/// write is the template's head `SetWanted(Up)` node — it matters when
/// `wanted` drifted `Offline` under a running agent (an operator asking
/// for a restart plainly wants it running, not a stop).
/// Restart a single agent: mechanical stop + converge to `wanted = Up`.
/// The intent write is the template's head `SetWanted(Up)` node — it
/// matters when `wanted` drifted `Offline` under a running agent (an
/// operator asking for a restart plainly wants it running, not a stop).
/// Thin wrapper over [`restart_many`] with a one-agent slice.
pub fn restart(coord: &Arc<Coordinator>, agent: &str, source: Source, reason: String) -> u64 {
submit_and_emit(coord, templates::restart(agent, source, reason))
restart_many(coord, &[agent.to_owned()], false, source, reason)
}
/// Restart `agents` (one or many) in a **single** DAG — one per-agent
/// subgraph each, running concurrently on their own leases. `graceful`
/// prepends the signal→drain quiesce per agent. The whole hive-wide
/// `hivectl restart` / `restart-all` is now one DAG instead of N.
pub fn restart_many(
coord: &Arc<Coordinator>,
agents: &[String],
graceful: bool,
source: Source,
reason: String,
) -> u64 {
submit_and_emit(coord, templates::restart(agents, graceful, source, reason))
}
/// Start: `SetWanted(Up)` (a DAG node now) then reconcile. A stale rev
@ -67,17 +82,18 @@ pub fn graceful_stop(coord: &Arc<Coordinator>, agent: &str, source: Source, reas
submit_and_emit(coord, templates::graceful_stop(agent, source, reason))
}
/// Graceful restart: signal → drain → mechanical stop → reconcile (starts
/// it back up) — one atomic DAG, no client-side "await the stop DAG then
/// submit a start DAG" split. The head `SetWanted(Up)` node writes the
/// intent as part of the DAG.
/// Graceful restart of a single agent: signal → drain → mechanical stop →
/// reconcile (starts it back up) — one atomic DAG, no client-side "await
/// the stop DAG then submit a start DAG" split. The head `SetWanted(Up)`
/// node writes the intent as part of the DAG. Thin wrapper over
/// [`restart_many`] with `graceful = true`.
pub fn graceful_restart(
coord: &Arc<Coordinator>,
agent: &str,
source: Source,
reason: String,
) -> u64 {
submit_and_emit(coord, templates::graceful_restart(agent, source, reason))
restart_many(coord, &[agent.to_owned()], true, source, reason)
}
/// Perm change: commit the JSON file(s) then rebuild.