mind-control: a brake hold stops mind-stream@ with the Mind's other units
The hold's unit list is one owner (held_templates); the operator line is printed from stop_args. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
ee72561716
commit
8a324186cf
3 changed files with 45 additions and 32 deletions
|
|
@ -28,12 +28,12 @@ use std::path::Path;
|
|||
|
||||
use anyhow::{Result, anyhow, bail};
|
||||
use chrono::{DateTime, Utc};
|
||||
use eureka_pipeline::{RunTurn, Slug};
|
||||
use eureka_pipeline::Slug;
|
||||
|
||||
use crate::control::{
|
||||
ControlWriter, Effective, FileSource, SwarmWriter, control_path, derive, derive_swarm, load_state, load_swarm_state, swarm_path,
|
||||
};
|
||||
use crate::launch::{StopScope, unit};
|
||||
use crate::launch::{StopScope, stop_args};
|
||||
|
||||
const USAGE: &str =
|
||||
"usage: mind-control [--root <dir>] --instance <slug> show | check | brake hold | brake release | mind-control [--root <dir>] swarm show | brake hold | brake release";
|
||||
|
|
@ -110,7 +110,7 @@ pub fn run(
|
|||
let stopped = stop_units(StopScope::Mind(&instance));
|
||||
match (written, stopped) {
|
||||
(Ok(()), Ok(())) => {
|
||||
writeln!(out, "units: stopped {} {}", unit(&instance, RunTurn::SelfRun)?, unit(&instance, RunTurn::PersonaTurn)?)?;
|
||||
writeln!(out, "units: stopped {}", stop_args(StopScope::Mind(&instance))?[2..].join(" "))?;
|
||||
Ok(())
|
||||
}
|
||||
outcome => held(outcome),
|
||||
|
|
@ -151,7 +151,7 @@ fn swarm(
|
|||
let written = SwarmWriter::open(&path).and_then(|mut writer| writer.set_brake(false, now, set_by));
|
||||
let stopped = stop_units(StopScope::Swarm);
|
||||
if written.is_ok() && stopped.is_ok() {
|
||||
writeln!(out, "units: stopped every mind-self@ and mind-persona@ unit")?;
|
||||
writeln!(out, "units: stopped every mind-self@, mind-persona@ and mind-stream@ unit")?;
|
||||
}
|
||||
held((written, stopped))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,15 +124,23 @@ fn template(turn: RunTurn) -> &'static str {
|
|||
}
|
||||
}
|
||||
|
||||
/// The unit of one turn: the template by turn, then the instance, held to the
|
||||
/// leaf's grammar first so it cannot escape the name. The error names nothing
|
||||
/// of the value. Start and alive both name the unit here.
|
||||
pub(crate) fn unit(instance: &Slug, turn: RunTurn) -> Result<String> {
|
||||
/// The stream's unit template: not a run slot, so only a brake hold names it.
|
||||
const STREAM_TEMPLATE: &str = "mind-stream";
|
||||
|
||||
/// A template's unit for one instance, the instance held to the leaf's grammar
|
||||
/// first so it cannot escape the name. The error names nothing of the value.
|
||||
fn named(template: &str, instance: &Slug) -> Result<String> {
|
||||
instance.validate_slug().map_err(|_| anyhow!("the instance is not a unit name"))?;
|
||||
Ok(format!("{}@{}.service", template(turn), instance.0))
|
||||
Ok(format!("{template}@{}.service", instance.0))
|
||||
}
|
||||
|
||||
/// What one stop reaches: the two units of one Mind, or every Mind's.
|
||||
/// The unit of one turn: the template by turn, then the instance. Start and
|
||||
/// alive both name the unit here.
|
||||
pub(crate) fn unit(instance: &Slug, turn: RunTurn) -> Result<String> {
|
||||
named(template(turn), instance)
|
||||
}
|
||||
|
||||
/// What one stop reaches: the three units of one Mind, or every Mind's.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum StopScope<'a> {
|
||||
Mind(&'a Slug),
|
||||
|
|
@ -141,22 +149,25 @@ pub enum StopScope<'a> {
|
|||
|
||||
/// The arguments of the one synchronous stop. A Mind's: the exact unit `unit()`
|
||||
/// names for each turn of this instance, the same names start and alive use,
|
||||
/// with no glob (a prefix glob also matches another instance, `eureka*` matches
|
||||
/// and its stream's, with no glob (a prefix glob also matches another instance, `eureka*` matches
|
||||
/// `eureka-two`). The swarm's: each template's glob over the loaded units,
|
||||
/// which is exactly every running Mind and exits 0 when none is loaded. No
|
||||
/// `--no-block`: `systemctl stop` returns when the units are down. The
|
||||
/// instance is validated by `unit()`.
|
||||
pub(crate) fn stop_args(scope: StopScope<'_>) -> Result<Vec<String>> {
|
||||
let mut args = vec!["stop".to_string(), "--".to_string()];
|
||||
for turn in [RunTurn::SelfRun, RunTurn::PersonaTurn] {
|
||||
args.push(match scope {
|
||||
StopScope::Mind(instance) => unit(instance, turn)?,
|
||||
StopScope::Swarm => format!("{}@*.service", template(turn)),
|
||||
});
|
||||
}
|
||||
args.extend(held_templates().into_iter().map(|template| match scope {
|
||||
StopScope::Mind(instance) => named(template, instance),
|
||||
StopScope::Swarm => Ok(format!("{template}@*.service")),
|
||||
}).collect::<Result<Vec<_>>>()?);
|
||||
Ok(args)
|
||||
}
|
||||
|
||||
/// The templates a hold stops: both run slots and the stream.
|
||||
fn held_templates() -> [&'static str; 3] {
|
||||
[template(RunTurn::SelfRun), template(RunTurn::PersonaTurn), STREAM_TEMPLATE]
|
||||
}
|
||||
|
||||
impl SystemdLauncher {
|
||||
/// Stops the units of one Mind, or of every Mind, and waits for them. An
|
||||
/// inherent method, not on `Launcher`: only a brake hold calls it, through
|
||||
|
|
@ -173,10 +184,10 @@ impl SystemdLauncher {
|
|||
}
|
||||
let states = match scope {
|
||||
StopScope::Mind(instance) => {
|
||||
let states = [RunTurn::SelfRun, RunTurn::PersonaTurn]
|
||||
let states = held_templates()
|
||||
.into_iter()
|
||||
.map(|turn| {
|
||||
let unit = unit(instance, turn)?;
|
||||
.map(|template| {
|
||||
let unit = named(template, instance)?;
|
||||
let state = self.unit_state(&unit);
|
||||
Ok(format!("{unit} is {state}"))
|
||||
})
|
||||
|
|
@ -1486,10 +1497,12 @@ mod tests {
|
|||
launcher.start(&instance, turn).unwrap();
|
||||
started.push(spy.calls.borrow().last().unwrap().last().unwrap().clone());
|
||||
}
|
||||
assert_eq!(args[2..], started[..], "stop names exactly the units start names, for both turns");
|
||||
assert_eq!(args[2..4], started[..], "stop names exactly the units start names, for both turns");
|
||||
assert_eq!(args[4], format!("mind-stream@{name}.service"), "and the stream of that instance");
|
||||
assert_eq!(args.len(), 5);
|
||||
assert!(args[2..].iter().all(|unit| !unit.contains(['*', '?', '['])), "a glob can match another instance or none: {args:?}");
|
||||
}
|
||||
assert_eq!(stop_args(StopScope::Mind(&eureka())).unwrap(), ["stop", "--", "mind-self@eureka.service", "mind-persona@eureka.service"]);
|
||||
assert_eq!(stop_args(StopScope::Mind(&eureka())).unwrap(), ["stop", "--", "mind-self@eureka.service", "mind-persona@eureka.service", "mind-stream@eureka.service"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1503,7 +1516,7 @@ mod tests {
|
|||
fn the_swarm_stop_is_one_stop_of_both_templates_globs() {
|
||||
let (launcher, spy) = spied();
|
||||
launcher.stop(StopScope::Swarm).unwrap();
|
||||
assert_eq!(*spy.calls.borrow(), vec![vec!["stop", "--", "mind-self@*.service", "mind-persona@*.service"]]);
|
||||
assert_eq!(*spy.calls.borrow(), vec![vec!["stop", "--", "mind-self@*.service", "mind-persona@*.service", "mind-stream@*.service"]]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1535,12 +1548,12 @@ mod tests {
|
|||
let (launcher, spy) = spied();
|
||||
spy.success.set(false);
|
||||
spy.code.set(Some(5));
|
||||
spy.states.borrow_mut().extend([("mind-self@eureka.service", "inactive\n"), ("mind-persona@eureka.service", "active\n")]);
|
||||
spy.states.borrow_mut().extend([("mind-self@eureka.service", "inactive\n"), ("mind-persona@eureka.service", "active\n"), ("mind-stream@eureka.service", "failed\n")]);
|
||||
let shown = format!("{:#}", launcher.stop(StopScope::Mind(&eureka())).unwrap_err());
|
||||
assert!(shown.contains("exit status 5"), "{shown}");
|
||||
assert!(shown.contains("mind-self@eureka.service is inactive") && shown.contains("mind-persona@eureka.service is active"), "{shown}");
|
||||
assert!(shown.contains("mind-self@eureka.service is inactive") && shown.contains("mind-persona@eureka.service is active") && shown.contains("mind-stream@eureka.service is failed"), "{shown}");
|
||||
let asked: Vec<Vec<String>> = spy.calls.borrow().iter().skip(1).cloned().collect();
|
||||
assert_eq!(asked, [["is-active", "mind-self@eureka.service"], ["is-active", "mind-persona@eureka.service"]], "one query per unit, after the one stop");
|
||||
assert_eq!(asked, [["is-active", "mind-self@eureka.service"], ["is-active", "mind-persona@eureka.service"], ["is-active", "mind-stream@eureka.service"]], "one query per unit, after the one stop");
|
||||
// A state word outside the three, and anything systemctl prints with it, is not carried.
|
||||
spy.states.borrow_mut().clear();
|
||||
*spy.stdout.borrow_mut() = "canary-output-7f3a91\n".to_string();
|
||||
|
|
|
|||
|
|
@ -275,7 +275,7 @@ fn brake_hold_stops_once_with_the_instance_and_the_store_already_held() {
|
|||
result.unwrap();
|
||||
assert_eq!(calls, [Some("eureka".to_string())]);
|
||||
assert!(seen_at_stop.contains("BrakeHeld"), "the write comes before the stop: {seen_at_stop}");
|
||||
assert!(out.contains("units: stopped mind-self@eureka.service mind-persona@eureka.service"), "{out}");
|
||||
assert!(out.contains("units: stopped mind-self@eureka.service mind-persona@eureka.service mind-stream@eureka.service"), "{out}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -362,8 +362,8 @@ fn hold_through_the_binary(exit: i32) -> (std::process::Output, Vec<String>, tem
|
|||
fn the_binary_hold_runs_systemctl_stop_on_exactly_the_units_it_starts() {
|
||||
let (output, recorded, _root) = hold_through_the_binary(0);
|
||||
assert_eq!(output.status.code(), Some(0), "{}", String::from_utf8_lossy(&output.stderr));
|
||||
assert_eq!(recorded, ["stop", "--", "mind-self@eureka.service", "mind-persona@eureka.service"], "a stop that succeeds asks no state");
|
||||
assert!(String::from_utf8_lossy(&output.stdout).contains("units: stopped mind-self@eureka.service mind-persona@eureka.service"));
|
||||
assert_eq!(recorded, ["stop", "--", "mind-self@eureka.service", "mind-persona@eureka.service", "mind-stream@eureka.service"], "a stop that succeeds asks no state");
|
||||
assert!(String::from_utf8_lossy(&output.stdout).contains("units: stopped mind-self@eureka.service mind-persona@eureka.service mind-stream@eureka.service"));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
|
|
@ -373,11 +373,11 @@ fn a_partial_stop_names_each_units_state_and_keeps_the_brake_held_with_exit_2()
|
|||
assert_eq!(output.status.code(), Some(2));
|
||||
assert_eq!(
|
||||
recorded,
|
||||
["stop", "--", "mind-self@eureka.service", "mind-persona@eureka.service", "is-active", "mind-self@eureka.service", "is-active", "mind-persona@eureka.service"]
|
||||
["stop", "--", "mind-self@eureka.service", "mind-persona@eureka.service", "mind-stream@eureka.service", "is-active", "mind-self@eureka.service", "is-active", "mind-persona@eureka.service", "is-active", "mind-stream@eureka.service"]
|
||||
);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
assert!(stderr.contains("exit status 5") && stderr.contains("the brake is held"), "{stderr}");
|
||||
assert!(stderr.contains("mind-self@eureka.service is inactive") && stderr.contains("mind-persona@eureka.service is active"), "{stderr}");
|
||||
assert!(stderr.contains("mind-self@eureka.service is inactive") && stderr.contains("mind-persona@eureka.service is active") && stderr.contains("mind-stream@eureka.service is active"), "{stderr}");
|
||||
assert!(!stderr.contains("were not stopped"), "{stderr}");
|
||||
assert!(!String::from_utf8_lossy(&output.stdout).contains("stopped"));
|
||||
assert!(shown(root.path()).contains("BrakeHeld"));
|
||||
|
|
@ -475,7 +475,7 @@ fn swarm_brake_hold_stops_once_with_the_swarm_scope_and_the_swarm_store_already_
|
|||
result.unwrap();
|
||||
assert_eq!(calls, [None], "one stop, for the swarm");
|
||||
assert!(seen_at_stop.contains("BrakeHeld"), "the write comes before the stop: {seen_at_stop}");
|
||||
assert!(out.contains("units: stopped every mind-self@ and mind-persona@ unit"), "{out}");
|
||||
assert!(out.contains("units: stopped every mind-self@, mind-persona@ and mind-stream@ unit"), "{out}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Reference in a new issue