Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8bd0f7180 | ||
|
|
1739716fa2 |
5 changed files with 180 additions and 108 deletions
|
|
@ -76,12 +76,15 @@ container build:
|
||||||
|
|
||||||
The power ops write the durable `wanted` intent via a head `SetWanted`
|
The power ops write the durable `wanted` intent via a head `SetWanted`
|
||||||
node (not a pre-submit side effect) — it holds the agent lease, so
|
node (not a pre-submit side effect) — it holds the agent lease, so
|
||||||
intent-write + reconcile is atomic per-agent.
|
intent-write + reconcile is atomic per-agent. `restart` takes an agent
|
||||||
|
*list*: a hive-wide `hivectl restart` / `restart-all` is ONE DAG with a
|
||||||
|
per-agent restart subgraph each (independent roots, run concurrently on
|
||||||
|
their own leases), not N separate DAGs.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
rebuild(a): Prebuild(a) → StopForUpdate(a) → Swap(a) →(after-any) Reconcile(a)
|
rebuild(a): Prebuild(a) → StopForUpdate(a) → Swap(a) →(after-any) Reconcile(a)
|
||||||
graceful-stop(a): SetWanted(a,Off) → Signal(a) → Drain(a) → Reconcile(a)
|
graceful-stop(a): SetWanted(a,Off) → Signal(a) → Drain(a) → Reconcile(a)
|
||||||
restart(a): SetWanted(a,Up) → StopForUpdate(a) → Reconcile(a)
|
restart(a..): per agent: SetWanted(a,Up) → StopForUpdate(a) → Reconcile(a) (N subgraphs, 1 DAG)
|
||||||
graceful-restart(a): SetWanted(a,Up) → Signal(a) → Drain(a) → StopForUpdate(a) → Reconcile(a)
|
graceful-restart(a): SetWanted(a,Up) → Signal(a) → Drain(a) → StopForUpdate(a) → Reconcile(a)
|
||||||
start(a): SetWanted(a,Up) → Reconcile(a) (stale rev ⇒ stale-start below)
|
start(a): SetWanted(a,Up) → Reconcile(a) (stale rev ⇒ stale-start below)
|
||||||
stale-start(a): SetWanted(a,Up) → «rebuild subgraph» (prebuild noops — agent is down)
|
stale-start(a): SetWanted(a,Up) → «rebuild subgraph» (prebuild noops — agent is down)
|
||||||
|
|
|
||||||
|
|
@ -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))
|
submit_and_emit(coord, templates::rebuild(agent, source, reason, None, true))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Restart: mechanical stop + converge to `wanted = Up`. The intent
|
/// Restart a single agent: mechanical stop + converge to `wanted = Up`.
|
||||||
/// write is the template's head `SetWanted(Up)` node — it matters when
|
/// The intent write is the template's head `SetWanted(Up)` node — it
|
||||||
/// `wanted` drifted `Offline` under a running agent (an operator asking
|
/// matters when `wanted` drifted `Offline` under a running agent (an
|
||||||
/// for a restart plainly wants it running, not a stop).
|
/// 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 {
|
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
|
/// 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))
|
submit_and_emit(coord, templates::graceful_stop(agent, source, reason))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Graceful restart: signal → drain → mechanical stop → reconcile (starts
|
/// Graceful restart of a single agent: signal → drain → mechanical stop →
|
||||||
/// it back up) — one atomic DAG, no client-side "await the stop DAG then
|
/// reconcile (starts it back up) — one atomic DAG, no client-side "await
|
||||||
/// submit a start DAG" split. The head `SetWanted(Up)` node writes the
|
/// the stop DAG then submit a start DAG" split. The head `SetWanted(Up)`
|
||||||
/// intent as part of the DAG.
|
/// node writes the intent as part of the DAG. Thin wrapper over
|
||||||
|
/// [`restart_many`] with `graceful = true`.
|
||||||
pub fn graceful_restart(
|
pub fn graceful_restart(
|
||||||
coord: &Arc<Coordinator>,
|
coord: &Arc<Coordinator>,
|
||||||
agent: &str,
|
agent: &str,
|
||||||
source: Source,
|
source: Source,
|
||||||
reason: String,
|
reason: String,
|
||||||
) -> u64 {
|
) -> 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.
|
/// Perm change: commit the JSON file(s) then rebuild.
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,11 @@
|
||||||
//! `Vec<Node>` + `deps`).
|
//! `Vec<Node>` + `deps`).
|
||||||
//!
|
//!
|
||||||
//! Every node carries its own `agent` (there is no DAG-level agent) — the
|
//! Every node carries its own `agent` (there is no DAG-level agent) — the
|
||||||
//! `node` helper stamps the template's agent onto each. Today's templates
|
//! `node` helper stamps the template's agent onto each. `restart` takes an
|
||||||
//! are single-agent (every node shares one agent); a future multi-agent
|
//! agent *list* and stamps each agent onto its own subgraph, so a hive-wide
|
||||||
//! template would stamp different agents per subgraph.
|
//! `hivectl restart` is ONE DAG with N independent per-agent subgraphs
|
||||||
|
//! (each a root chain, run concurrently on its own lease) rather than N
|
||||||
|
//! separate DAGs. The other templates are still single-agent.
|
||||||
//!
|
//!
|
||||||
//! The power ops write the durable `wanted` intent via a head
|
//! The power ops write the durable `wanted` intent via a head
|
||||||
//! `SetWanted(w)` node (not a pre-submit side effect); it holds the agent
|
//! `SetWanted(w)` node (not a pre-submit side effect); it holds the agent
|
||||||
|
|
@ -146,37 +148,36 @@ pub fn graceful_stop(agent: &str, source: Source, reason: String) -> DagSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Restart: write `wanted = Up` (head `SetWanted` node), mechanical stop,
|
/// Restart one or more agents in a **single** DAG. Each agent gets an
|
||||||
/// then converge — a stop + start like the old `lifecycle::restart`
|
/// independent subgraph — a head `SetWanted(Up)` (a root: no cross-agent
|
||||||
/// regardless of prior intent drift.
|
/// dep) then its restart chain — so all agents' restarts run concurrently,
|
||||||
pub fn restart(agent: &str, source: Source, reason: String) -> DagSpec {
|
/// each taking its own agent lease. `graceful` inserts `Signal → Drain`
|
||||||
DagSpec {
|
/// before the mechanical `StopForUpdate` (each agent's subgraph is still one
|
||||||
template: Template::Restart,
|
/// atomic restart). One agent = the ordinary single-agent restart; many =
|
||||||
source,
|
/// a hive-wide `hivectl restart` as one DAG instead of N separate ones.
|
||||||
reason,
|
pub fn restart(agents: &[String], graceful: bool, source: Source, reason: String) -> DagSpec {
|
||||||
parent_id: None,
|
let mut nodes = Vec::new();
|
||||||
approval_id: None,
|
for agent in agents {
|
||||||
inputs: Vec::new(),
|
let base = u32::try_from(nodes.len()).unwrap_or(u32::MAX);
|
||||||
perm_payload: None,
|
// Head of this agent's subgraph — a root (empty deps), so the N
|
||||||
transient: Some(TransientKind::Restarting),
|
// per-agent subgraphs are independent and run concurrently.
|
||||||
nodes: vec![
|
nodes.push(node(agent, NodeKind::SetWanted { up: true }, Vec::new()));
|
||||||
node(agent, NodeKind::SetWanted { up: true }, Vec::new()),
|
if graceful {
|
||||||
node(agent, NodeKind::StopForUpdate, after_ok(0)),
|
nodes.push(node(agent, NodeKind::Signal, after_ok(base)));
|
||||||
node(agent, NodeKind::Reconcile, after_ok(1)),
|
nodes.push(node(agent, NodeKind::Drain, after_ok(base + 1)));
|
||||||
],
|
nodes.push(node(agent, NodeKind::StopForUpdate, after_ok(base + 2)));
|
||||||
|
nodes.push(node(agent, NodeKind::Reconcile, after_ok(base + 3)));
|
||||||
|
} else {
|
||||||
|
nodes.push(node(agent, NodeKind::StopForUpdate, after_ok(base)));
|
||||||
|
nodes.push(node(agent, NodeKind::Reconcile, after_ok(base + 1)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Graceful restart: write `wanted = Up` (head `SetWanted`), signal →
|
|
||||||
/// drain → mechanical stop → converge. One atomic DAG start to finish
|
|
||||||
/// (no client- or server-side "submit one DAG, await it, submit the next"
|
|
||||||
/// composition): the `Drain` node is the same bounded harness-checkpoint
|
|
||||||
/// wait `graceful_stop` uses, then `StopForUpdate` (mechanical, ignores
|
|
||||||
/// `wanted`) and the tail `Reconcile` (converges to `wanted = Up`, i.e.
|
|
||||||
/// starts it back up) chain exactly like `restart`'s tail.
|
|
||||||
pub fn graceful_restart(agent: &str, source: Source, reason: String) -> DagSpec {
|
|
||||||
DagSpec {
|
DagSpec {
|
||||||
template: Template::GracefulRestart,
|
template: if graceful {
|
||||||
|
Template::GracefulRestart
|
||||||
|
} else {
|
||||||
|
Template::Restart
|
||||||
|
},
|
||||||
source,
|
source,
|
||||||
reason,
|
reason,
|
||||||
parent_id: None,
|
parent_id: None,
|
||||||
|
|
@ -184,13 +185,7 @@ pub fn graceful_restart(agent: &str, source: Source, reason: String) -> DagSpec
|
||||||
inputs: Vec::new(),
|
inputs: Vec::new(),
|
||||||
perm_payload: None,
|
perm_payload: None,
|
||||||
transient: Some(TransientKind::Restarting),
|
transient: Some(TransientKind::Restarting),
|
||||||
nodes: vec![
|
nodes,
|
||||||
node(agent, NodeKind::SetWanted { up: true }, Vec::new()),
|
|
||||||
node(agent, NodeKind::Signal, after_ok(0)),
|
|
||||||
node(agent, NodeKind::Drain, after_ok(1)),
|
|
||||||
node(agent, NodeKind::StopForUpdate, after_ok(2)),
|
|
||||||
node(agent, NodeKind::Reconcile, after_ok(3)),
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,12 @@ fn distinct_submits_never_collapse() {
|
||||||
let b = submit(&q, rebuild("agent-b", "r"));
|
let b = submit(&q, rebuild("agent-b", "r"));
|
||||||
let c = submit(
|
let c = submit(
|
||||||
&q,
|
&q,
|
||||||
templates::restart("agent-a", Source::Manual, "r".to_owned()),
|
templates::restart(
|
||||||
|
&["agent-a".to_owned()],
|
||||||
|
false,
|
||||||
|
Source::Manual,
|
||||||
|
"r".to_owned(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
assert_ne!(a, b);
|
assert_ne!(a, b);
|
||||||
assert_ne!(a, c);
|
assert_ne!(a, c);
|
||||||
|
|
@ -200,7 +205,12 @@ fn lease_serializes_two_lifecycle_dags_for_same_agent() {
|
||||||
let q = JobQueue::new(4);
|
let q = JobQueue::new(4);
|
||||||
let restart = submit(
|
let restart = submit(
|
||||||
&q,
|
&q,
|
||||||
templates::restart("agent-a", Source::Manual, "restart".to_owned()),
|
templates::restart(
|
||||||
|
&["agent-a".to_owned()],
|
||||||
|
false,
|
||||||
|
Source::Manual,
|
||||||
|
"restart".to_owned(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
let stop = submit(
|
let stop = submit(
|
||||||
&q,
|
&q,
|
||||||
|
|
@ -284,16 +294,60 @@ fn agents_do_not_contend_on_each_others_leases() {
|
||||||
let q = JobQueue::new(4);
|
let q = JobQueue::new(4);
|
||||||
submit(
|
submit(
|
||||||
&q,
|
&q,
|
||||||
templates::restart("agent-a", Source::Manual, "r".to_owned()),
|
templates::restart(
|
||||||
|
&["agent-a".to_owned()],
|
||||||
|
false,
|
||||||
|
Source::Manual,
|
||||||
|
"r".to_owned(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
submit(
|
submit(
|
||||||
&q,
|
&q,
|
||||||
templates::restart("agent-b", Source::Manual, "r".to_owned()),
|
templates::restart(
|
||||||
|
&["agent-b".to_owned()],
|
||||||
|
false,
|
||||||
|
Source::Manual,
|
||||||
|
"r".to_owned(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
let claims = q.claim_ready();
|
let claims = q.claim_ready();
|
||||||
assert_eq!(claims.len(), 2, "different agents run concurrently");
|
assert_eq!(claims.len(), 2, "different agents run concurrently");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multi_agent_restart_is_one_dag_with_concurrent_per_agent_subgraphs() {
|
||||||
|
let q = JobQueue::new(4);
|
||||||
|
let id = submit(
|
||||||
|
&q,
|
||||||
|
templates::restart(
|
||||||
|
&["agent-a".to_owned(), "agent-b".to_owned()],
|
||||||
|
false,
|
||||||
|
Source::Manual,
|
||||||
|
"hive-wide".to_owned(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
// A hive-wide restart is ONE DAG, not one-per-agent.
|
||||||
|
assert_eq!(q.snapshot().len(), 1);
|
||||||
|
// Each agent's subgraph head (SetWanted) is a root, so both are
|
||||||
|
// claimable at once — each takes its OWN agent's lease (no contention
|
||||||
|
// across distinct agents), all inside the single DAG.
|
||||||
|
let claims = q.claim_ready();
|
||||||
|
assert!(claims.iter().all(|c| c.dag_id == id));
|
||||||
|
let mut heads: Vec<(&str, &str, bool)> = claims
|
||||||
|
.iter()
|
||||||
|
.map(|c| (c.agent.as_str(), c.kind.as_str(), c.lease_acquired))
|
||||||
|
.collect();
|
||||||
|
heads.sort_unstable();
|
||||||
|
assert_eq!(
|
||||||
|
heads,
|
||||||
|
vec![
|
||||||
|
("agent-a", "set_wanted", true),
|
||||||
|
("agent-b", "set_wanted", true),
|
||||||
|
],
|
||||||
|
"both per-agent subgraphs start concurrently, each acquiring its own lease"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// ---- failure: cancel-downstream + AfterAny ----
|
// ---- failure: cancel-downstream + AfterAny ----
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -514,7 +568,12 @@ fn terminal_dag_reported_exactly_once_and_lease_released() {
|
||||||
let q = JobQueue::new(1);
|
let q = JobQueue::new(1);
|
||||||
let id = submit(
|
let id = submit(
|
||||||
&q,
|
&q,
|
||||||
templates::restart("agent-a", Source::Manual, "r".to_owned()),
|
templates::restart(
|
||||||
|
&["agent-a".to_owned()],
|
||||||
|
false,
|
||||||
|
Source::Manual,
|
||||||
|
"r".to_owned(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
// restart = SetWanted → StopForUpdate → Reconcile; not terminal until
|
// restart = SetWanted → StopForUpdate → Reconcile; not terminal until
|
||||||
// the last node completes.
|
// the last node completes.
|
||||||
|
|
@ -622,7 +681,12 @@ fn trim_keeps_terminal_parent_with_live_children() {
|
||||||
);
|
);
|
||||||
let lock = claim_one(&q);
|
let lock = claim_one(&q);
|
||||||
q.complete_node(meta, lock.node_id, Ok(()));
|
q.complete_node(meta, lock.node_id, Ok(()));
|
||||||
let mut child_spec = templates::restart("agent-x", Source::MetaUpdate, "cascade".to_owned());
|
let mut child_spec = templates::restart(
|
||||||
|
&["agent-x".to_owned()],
|
||||||
|
false,
|
||||||
|
Source::MetaUpdate,
|
||||||
|
"cascade".to_owned(),
|
||||||
|
);
|
||||||
child_spec.parent_id = Some(meta);
|
child_spec.parent_id = Some(meta);
|
||||||
let child = submit(&q, child_spec);
|
let child = submit(&q, child_spec);
|
||||||
// Churn > MAX_HISTORY_PER_TEMPLATE terminal meta_update DAGs.
|
// Churn > MAX_HISTORY_PER_TEMPLATE terminal meta_update DAGs.
|
||||||
|
|
|
||||||
|
|
@ -552,28 +552,29 @@ fn submit_single(coord: &Arc<Coordinator>, name: &str, verb: Verb) -> HostRespon
|
||||||
HostResponse::queued(vec![id])
|
HostResponse::queued(vec![id])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Restart every container by submitting one restart DAG per agent —
|
/// Restart every container in **one** DAG — a per-agent restart subgraph
|
||||||
/// each serializes on its own lease, so unrelated agents' restarts
|
/// each, running concurrently on their own leases (so unrelated agents'
|
||||||
/// overlap while nothing races an in-flight rebuild. Returns once all
|
/// restarts overlap while nothing races an in-flight rebuild). Returns
|
||||||
/// are queued; per-agent results surface on the queue.
|
/// once queued; per-node progress surfaces on the single DAG.
|
||||||
async fn handle_restart_all(coord: &Arc<Coordinator>) -> Result<HostResponse> {
|
async fn handle_restart_all(coord: &Arc<Coordinator>) -> Result<HostResponse> {
|
||||||
tracing::info!("restart-all");
|
tracing::info!("restart-all");
|
||||||
let agents = lifecycle::list().await?;
|
let containers = lifecycle::list().await?;
|
||||||
let mut ok_agents: Vec<String> = Vec::new();
|
let agents: Vec<String> = containers
|
||||||
let mut queued: Vec<u64> = Vec::new();
|
.iter()
|
||||||
for agent in &agents {
|
.filter_map(|a| a.strip_prefix(lifecycle::AGENT_PREFIX).map(str::to_owned))
|
||||||
let Some(logical) = agent.strip_prefix(lifecycle::AGENT_PREFIX) else {
|
.collect();
|
||||||
continue;
|
let queued = if agents.is_empty() {
|
||||||
};
|
Vec::new()
|
||||||
queued.push(crate::job_queue::submit::restart(
|
} else {
|
||||||
|
vec![crate::job_queue::submit::restart_many(
|
||||||
coord,
|
coord,
|
||||||
logical,
|
&agents,
|
||||||
|
false,
|
||||||
crate::job_queue::Source::Manual,
|
crate::job_queue::Source::Manual,
|
||||||
"manual restart via hivectl restart-all".to_owned(),
|
"manual restart via hivectl restart-all".to_owned(),
|
||||||
));
|
)]
|
||||||
ok_agents.push(logical.to_owned());
|
};
|
||||||
}
|
let mut resp = HostResponse::list(agents);
|
||||||
let mut resp = HostResponse::list(ok_agents);
|
|
||||||
resp.queued_dags = Some(queued);
|
resp.queued_dags = Some(queued);
|
||||||
Ok(resp)
|
Ok(resp)
|
||||||
}
|
}
|
||||||
|
|
@ -718,16 +719,14 @@ async fn handle_start(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Restart containers hive-wide (`hivectl restart`) — the DAG-based
|
/// Restart containers hive-wide (`hivectl restart`) — the DAG-based
|
||||||
/// sibling of [`handle_stop`]/[`handle_start`], replacing the old
|
/// sibling of [`handle_stop`]/[`handle_start`]. All targeted agents ride
|
||||||
/// client-side stop-then-start composition (issue tracker "dagify hivectl
|
/// **one** DAG (a per-agent restart subgraph each: `SetWanted → [Signal →
|
||||||
/// commands"). Each targeted agent gets exactly one atomic DAG submitted
|
/// Drain →] StopForUpdate → Reconcile`, independent roots that run
|
||||||
/// up front: the `Restart` template (mechanical stop + reconcile) in the
|
/// concurrently on their own leases), not N separate DAGs — a hive-wide
|
||||||
/// common case, or `GracefulRestart` (signal → drain → mechanical stop →
|
/// restart is one job. `graceful` prepends signal→drain per agent. No
|
||||||
/// reconcile) with `graceful` set — no "submit a DAG, wait for it, submit
|
/// client-side stop-then-start composition, so a dropped `hivectl`
|
||||||
/// another" composition on either path, so a dropped `hivectl` connection
|
/// connection never strands an agent. Infra containers have no lease/DAG
|
||||||
/// never strands an agent, the same way `handle_restart_all` already
|
/// and restart synchronously (stop then start).
|
||||||
/// avoids it. Infra containers have no lease/DAG and restart
|
|
||||||
/// synchronously (stop then start).
|
|
||||||
async fn handle_restart_scoped(
|
async fn handle_restart_scoped(
|
||||||
coord: &Arc<Coordinator>,
|
coord: &Arc<Coordinator>,
|
||||||
scope: &LifecycleScope,
|
scope: &LifecycleScope,
|
||||||
|
|
@ -740,30 +739,25 @@ async fn handle_restart_scoped(
|
||||||
let mut errors: Vec<String> = Vec::new();
|
let mut errors: Vec<String> = Vec::new();
|
||||||
let mut queued: Vec<u64> = Vec::new();
|
let mut queued: Vec<u64> = Vec::new();
|
||||||
|
|
||||||
// One atomic DAG per agent, submitted up front — `Restart`
|
// One DAG for all targeted agents — a per-agent restart subgraph each
|
||||||
// (mechanical stop + reconcile) or, with `--graceful`,
|
// (`SetWanted → [Signal → Drain →] StopForUpdate → Reconcile`),
|
||||||
// `GracefulRestart` (signal → drain → mechanical stop → reconcile).
|
// independent roots that run concurrently on their own leases. A
|
||||||
// No client- or server-side "submit a stop DAG, await it, then
|
// hive-wide `hivectl restart` is now a single DAG, not N. No
|
||||||
// submit a start DAG" composition: that window is exactly the
|
// client-side stop-then-start composition — the whole restart survives
|
||||||
// dropped-connection gap this DAG-based path exists to close.
|
// a dropped connection because the DAG owns it.
|
||||||
for agent in &agents {
|
if !agents.is_empty() {
|
||||||
let id = if graceful {
|
queued.push(crate::job_queue::submit::restart_many(
|
||||||
crate::job_queue::submit::graceful_restart(
|
coord,
|
||||||
coord,
|
&agents,
|
||||||
agent,
|
graceful,
|
||||||
crate::job_queue::Source::Manual,
|
crate::job_queue::Source::Manual,
|
||||||
"manual via hivectl restart --graceful".to_owned(),
|
if graceful {
|
||||||
)
|
"manual via hivectl restart --graceful".to_owned()
|
||||||
} else {
|
} else {
|
||||||
crate::job_queue::submit::restart(
|
"manual restart via hivectl restart".to_owned()
|
||||||
coord,
|
},
|
||||||
agent,
|
));
|
||||||
crate::job_queue::Source::Manual,
|
ok_items.extend(agents.iter().cloned());
|
||||||
"manual restart via hivectl restart".to_owned(),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
queued.push(id);
|
|
||||||
ok_items.push(agent.clone());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for &container in &infra {
|
for &container in &infra {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue