feat(#3139): tell a container that gave up from one stopped on purpose
is_running collapsed every non-active state into false, so a container that exhausted its bounded restarts read as plain "down" -- indistinguishable from one an operator stopped deliberately. Bounding the restarts made that gap sharper: a slow-failing agent used to grind on visibly, now it can stop quietly. Adds UnitState + unit_state() beside is_running rather than widening it. is_running has ~8 call sites and nearly all are reconcile/power logic asking "is it up? if not, start it" -- a question with two answers. Only the view builder needs more, and it gets both facts from one systemctl call, since is-active prints the state when not passed --quiet. Surfaces as a flat failed flag on ContainerView and AgentStatusRow, matching the shape those types already document: independent, orthogonally-observed facts rather than a state machine. serde(default) keeps it order-independent with the frontend half. No behaviour change: nothing acts on the flag, per the ruling.
This commit is contained in:
parent
4fd25b7f45
commit
cae2cf8df6
6 changed files with 128 additions and 2 deletions
|
|
@ -180,3 +180,42 @@ async fn update_ref_cas_refuses_stale_expectation() {
|
|||
"ref moved even though the CAS failed"
|
||||
);
|
||||
}
|
||||
|
||||
/// `systemctl is-active` prints the state with a trailing newline, which is
|
||||
/// the whole reason this parser trims rather than comparing raw.
|
||||
#[test]
|
||||
fn unit_state_reads_what_is_active_prints() {
|
||||
assert_eq!(UnitState::from_is_active("active\n"), UnitState::Active);
|
||||
assert_eq!(UnitState::from_is_active("failed\n"), UnitState::Failed);
|
||||
assert_eq!(UnitState::from_is_active("inactive\n"), UnitState::Other);
|
||||
// No trailing newline, in case the capture ever changes shape.
|
||||
assert_eq!(UnitState::from_is_active("failed"), UnitState::Failed);
|
||||
}
|
||||
|
||||
/// The asymmetry that matters: an unrecognised state must fall to `Other`,
|
||||
/// never to `Failed`.
|
||||
///
|
||||
/// `Failed` is what the dashboard will render as *this agent gave up*, so a
|
||||
/// wrong `Failed` invents an incident, while a wrong `Other` merely fails to
|
||||
/// distinguish one from a deliberate stop — the behaviour we have today.
|
||||
/// Guessing in the safe direction is the property, not an implementation
|
||||
/// detail of the `match`.
|
||||
#[test]
|
||||
fn an_unknown_state_is_never_reported_as_failed() {
|
||||
for s in [
|
||||
"activating",
|
||||
"deactivating",
|
||||
"reloading",
|
||||
"maintenance",
|
||||
"unknown",
|
||||
"",
|
||||
"Failed", // capitalised: systemd prints lowercase, so this is not a state
|
||||
"failed*", // `is-active` never emits this; a substring match would take it
|
||||
] {
|
||||
assert_eq!(
|
||||
UnitState::from_is_active(s),
|
||||
UnitState::Other,
|
||||
"{s:?} must not be read as a give-up"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue