wanted: converge a destroyed declaration by tearing the container down
This commit is contained in:
parent
e82a735745
commit
6fd5bc1c4f
2 changed files with 61 additions and 8 deletions
|
|
@ -212,6 +212,7 @@ async fn converge(coord: &Arc<Coordinator>, declared: &HiveWanted) -> Result<()>
|
|||
deploy = plan.deploy.len(),
|
||||
start = plan.start.len(),
|
||||
stop = plan.stop.len(),
|
||||
destroy = plan.destroy.len(),
|
||||
"wanted state: converging"
|
||||
);
|
||||
|
||||
|
|
@ -234,8 +235,16 @@ async fn converge(coord: &Arc<Coordinator>, declared: &HiveWanted) -> Result<()>
|
|||
.await
|
||||
.context("queueing the declared stops")?;
|
||||
}
|
||||
// No purge: this issue's own scope is tearing the container down, not
|
||||
// wiping its persistent state. `actions::destroy` submits its own DAG
|
||||
// node and emits its own queue snapshot, so there is nothing to await
|
||||
// or fold into `queued` here — same fire-and-forget contract every other
|
||||
// lifecycle op in that module has.
|
||||
for agent in &plan.destroy {
|
||||
crate::actions::destroy(coord, agent, false);
|
||||
}
|
||||
if queued {
|
||||
// The power ops emit their own; the first deploys do not.
|
||||
// The power ops and destroy emit their own; the first deploys do not.
|
||||
coord.emit_rebuild_queue_snapshot();
|
||||
}
|
||||
Ok(())
|
||||
|
|
@ -247,6 +256,7 @@ struct Plan {
|
|||
deploy: Vec<String>,
|
||||
start: Vec<String>,
|
||||
stop: Vec<String>,
|
||||
destroy: Vec<String>,
|
||||
}
|
||||
|
||||
/// Turn one declaration into that plan — pure, so what the loop does with a
|
||||
|
|
@ -274,6 +284,7 @@ fn plan(
|
|||
Converge::Deploy => plan.deploy.push(agent.clone()),
|
||||
Converge::Start => plan.start.push(agent.clone()),
|
||||
Converge::Stop => plan.stop.push(agent.clone()),
|
||||
Converge::Destroy => plan.destroy.push(agent.clone()),
|
||||
Converge::Nothing => {}
|
||||
}
|
||||
}
|
||||
|
|
@ -288,6 +299,10 @@ enum Converge {
|
|||
Deploy,
|
||||
Start,
|
||||
Stop,
|
||||
/// Declared destroyed, and this hive still has a container for it — run
|
||||
/// the teardown once. Irreversible, so unlike `Start`/`Stop` there is no
|
||||
/// intent to compare against: presence alone decides it.
|
||||
Destroy,
|
||||
/// The hive already agrees with the declaration.
|
||||
Nothing,
|
||||
}
|
||||
|
|
@ -305,9 +320,11 @@ fn decide(state: AgentState, present: bool, intent: Option<Wanted>) -> Converge
|
|||
Converge::Start
|
||||
}
|
||||
}
|
||||
// Declared offline and not here: creating a container in order to
|
||||
// leave it stopped is not what the declaration asks for.
|
||||
AgentState::Offline if !present => Converge::Nothing,
|
||||
// Declared offline/destroyed and not here: creating a container in
|
||||
// order to leave it stopped, or to immediately re-destroy it, is not
|
||||
// what either declaration asks for — absence plus a stopped-or-gone
|
||||
// declaration is agreement, not a repair to queue.
|
||||
AgentState::Offline | AgentState::Destroyed if !present => Converge::Nothing,
|
||||
AgentState::Offline => {
|
||||
if intent == Some(Wanted::Offline) {
|
||||
Converge::Nothing
|
||||
|
|
@ -315,6 +332,10 @@ fn decide(state: AgentState, present: bool, intent: Option<Wanted>) -> Converge
|
|||
Converge::Stop
|
||||
}
|
||||
}
|
||||
// Power intent plays no part here — a destroyed agent has no
|
||||
// running/stopped distinction left to converge to, only present or
|
||||
// not.
|
||||
AgentState::Destroyed => Converge::Destroy,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -364,6 +385,7 @@ mod tests {
|
|||
deploy: vec![],
|
||||
start: vec!["adopted".to_owned()],
|
||||
stop: vec![],
|
||||
destroy: vec![],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -432,6 +454,29 @@ mod tests {
|
|||
assert_eq!(decide(AgentState::Offline, false, None), Converge::Nothing);
|
||||
}
|
||||
|
||||
/// A present agent declared destroyed gets torn down — the whole point of
|
||||
/// this state. Intent is irrelevant, unlike `Up`/`Offline`: even an
|
||||
/// agent whose power intent is `Up` still gets destroyed, since a
|
||||
/// terminal declaration outranks a steady-state one.
|
||||
#[test]
|
||||
fn a_present_agent_declared_destroyed_is_destroyed() {
|
||||
assert_eq!(decide(AgentState::Destroyed, true, None), Converge::Destroy);
|
||||
assert_eq!(
|
||||
decide(AgentState::Destroyed, true, Some(Wanted::Up)),
|
||||
Converge::Destroy
|
||||
);
|
||||
}
|
||||
|
||||
/// Already gone: destroying an absent agent would be acting on nothing.
|
||||
/// Its control is the present case above, which must destroy.
|
||||
#[test]
|
||||
fn an_absent_agent_declared_destroyed_is_left_absent() {
|
||||
assert_eq!(
|
||||
decide(AgentState::Destroyed, false, None),
|
||||
Converge::Nothing
|
||||
);
|
||||
}
|
||||
|
||||
/// The watch's half of "absence is not a deletion order". `Put` is the
|
||||
/// control: without it this would pass on a function that refused
|
||||
/// everything, which would silently stop the fast path converging at all.
|
||||
|
|
@ -456,7 +501,7 @@ mod tests {
|
|||
/// pass forever without measuring anything.
|
||||
#[test]
|
||||
fn every_state_this_build_knows_is_covered_above() {
|
||||
for state in [AgentState::Up, AgentState::Offline] {
|
||||
for state in [AgentState::Up, AgentState::Offline, AgentState::Destroyed] {
|
||||
let seen = [true, false].iter().any(|present| {
|
||||
decide(state, *present, None) != Converge::Nothing
|
||||
|| decide(state, *present, Some(Wanted::Up)) != Converge::Nothing
|
||||
|
|
|
|||
|
|
@ -91,6 +91,11 @@ pub enum AgentState {
|
|||
Up,
|
||||
/// Exists on the hive and is not running.
|
||||
Offline,
|
||||
/// Torn down entirely — the hive runs the destroy template once, then
|
||||
/// this agent drops out of the declared set rather than staying as a
|
||||
/// third steady state. Irreversible: there is no state that brings a
|
||||
/// destroyed agent back short of a fresh deploy.
|
||||
Destroyed,
|
||||
}
|
||||
|
||||
impl AgentState {
|
||||
|
|
@ -103,6 +108,7 @@ impl AgentState {
|
|||
match self {
|
||||
AgentState::Up => "up",
|
||||
AgentState::Offline => "offline",
|
||||
AgentState::Destroyed => "destroyed",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -211,10 +217,12 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn the_known_states_decode_and_round_trip() {
|
||||
let doc = r#"{"agents":{"a":{"state":"up"},"b":{"state":"offline"}}}"#;
|
||||
let doc =
|
||||
r#"{"agents":{"a":{"state":"up"},"b":{"state":"offline"},"c":{"state":"destroyed"}}}"#;
|
||||
let decoded: HiveWanted = serde_json::from_str(doc).expect("decodes");
|
||||
assert_eq!(decoded.agents["a"].state, AgentState::Up);
|
||||
assert_eq!(decoded.agents["b"].state, AgentState::Offline);
|
||||
assert_eq!(decoded.agents["c"].state, AgentState::Destroyed);
|
||||
assert_eq!(serde_json::to_string(&decoded).expect("serialises"), doc);
|
||||
}
|
||||
|
||||
|
|
@ -253,10 +261,10 @@ mod tests {
|
|||
/// to compile here rather than quietly going untested.
|
||||
#[test]
|
||||
fn as_str_matches_the_serde_spelling() {
|
||||
let every = [AgentState::Up, AgentState::Offline];
|
||||
let every = [AgentState::Up, AgentState::Offline, AgentState::Destroyed];
|
||||
for state in every {
|
||||
match state {
|
||||
AgentState::Up | AgentState::Offline => {}
|
||||
AgentState::Up | AgentState::Offline | AgentState::Destroyed => {}
|
||||
}
|
||||
assert_eq!(
|
||||
serde_json::to_string(&state).expect("serialises"),
|
||||
|
|
|
|||
Loading…
Reference in a new issue