hive-c0re: a deploy request for a rev applied/main already contains is a no-op
advance_applied_main refused every rev that does not descend from applied/main, including an ancestor of it. Ancestors arrive in normal operation: swarm-controller queues each merge's TriggerDeploy as an independent node with no resource or edge between them and spawns each runnable node on its own task, so two merges of one agent can publish in either order; the meta-update cascade fast-forwards applied/main to the forge's main on its own; and the webhook handler does not dedup a redelivered merge event. Since a refusal now logs at ERROR and fires the `hh-config-rev-refused` alert, each of those would page as a refusal. An ancestor is now RevAdvance::Superseded, logged at info, nothing queued. Only a rev that has diverged (neither descendant nor ancestor) is refused. Refs #4851
This commit is contained in:
parent
8c222f8910
commit
29d956140e
3 changed files with 31 additions and 6 deletions
|
|
@ -44,10 +44,12 @@ No MCP tool exists for config changes: opening the PR IS the request.
|
||||||
deploy subject. With no such hive, or more than one, it deploys
|
deploy subject. With no such hive, or more than one, it deploys
|
||||||
nothing and logs a warning naming them.
|
nothing and logs a warning naming them.
|
||||||
5. If the hive's `applied/main` already is that commit, it does nothing.
|
5. If the hive's `applied/main` already is that commit, it does nothing.
|
||||||
Otherwise it fetches the config repo's `main`, requires the commit to
|
Otherwise it fetches the config repo's `main`. A commit that
|
||||||
descend from `applied/main`, fast-forwards `applied/main` to it, and
|
`applied/main` already contains is a no-op too: requests for two merges
|
||||||
queues a rebuild. A failure before the rebuild (the fetch, or a commit
|
can arrive in either order. A commit that descends from `applied/main`
|
||||||
that doesn't descend) deploys nothing and logs `swarm events: rev not
|
is fast-forwarded to and a rebuild queued. A failure before the rebuild
|
||||||
|
(the fetch, or a commit that has diverged from `applied/main`) deploys
|
||||||
|
nothing and logs `swarm events: rev not
|
||||||
applied; nothing queued` at error, which fires the Grafana alert
|
applied; nothing queued` at error, which fires the Grafana alert
|
||||||
`config rev refused`. An agent with no container on that hive yet ignores
|
`config rev refused`. An agent with no container on that hive yet ignores
|
||||||
the commit; its first deploy builds what it seeds.
|
the commit; its first deploy builds what it seeds.
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,11 @@ pub(crate) enum RevAdvance {
|
||||||
AlreadyApplied,
|
AlreadyApplied,
|
||||||
/// `applied/main` was fast-forwarded to the rev.
|
/// `applied/main` was fast-forwarded to the rev.
|
||||||
Advanced,
|
Advanced,
|
||||||
|
/// The rev is an ancestor of `applied/main`, so a later commit already
|
||||||
|
/// carries it and there is nothing to deploy. Deploy requests are not
|
||||||
|
/// ordered: two merges' requests can arrive in either order, and a
|
||||||
|
/// meta-update cascade fast-forwards `main` to the forge's on its own.
|
||||||
|
Superseded,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fast-forward `agent`'s `applied/main` to `rev`, a commit the swarm asks
|
/// Fast-forward `agent`'s `applied/main` to `rev`, a commit the swarm asks
|
||||||
|
|
@ -40,8 +45,8 @@ pub(crate) enum RevAdvance {
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Returns an error if the forge fetch fails, if `rev` does not descend from
|
/// Returns an error if the forge fetch fails, if `rev` has diverged from
|
||||||
/// `applied/main`, or if `main` moved during the fast-forward. `main` does not
|
/// `applied/main` (neither descendant nor ancestor), or if `main` moved during the fast-forward. `main` does not
|
||||||
/// move in any of these.
|
/// move in any of these.
|
||||||
pub(crate) async fn advance_applied_to_rev(agent: &str, rev: &str) -> Result<RevAdvance> {
|
pub(crate) async fn advance_applied_to_rev(agent: &str, rev: &str) -> Result<RevAdvance> {
|
||||||
advance_applied_main(
|
advance_applied_main(
|
||||||
|
|
@ -75,6 +80,12 @@ async fn advance_applied_main<T>(
|
||||||
return Ok(RevAdvance::AlreadyApplied);
|
return Ok(RevAdvance::AlreadyApplied);
|
||||||
}
|
}
|
||||||
if !descends {
|
if !descends {
|
||||||
|
let contained = lifecycle::git_is_ancestor(applied_dir, rev, &main)
|
||||||
|
.await
|
||||||
|
.map_err(|e| anyhow::anyhow!("ancestry check {rev}..{main}: {e:#}"))?;
|
||||||
|
if contained {
|
||||||
|
return Ok(RevAdvance::Superseded);
|
||||||
|
}
|
||||||
bail!(
|
bail!(
|
||||||
"{rev} does not descend from applied/main ({main}); fast-forwarding to it would \
|
"{rev} does not descend from applied/main ({main}); fast-forwarding to it would \
|
||||||
discard commits"
|
discard commits"
|
||||||
|
|
@ -211,6 +222,14 @@ mod tests {
|
||||||
assert_eq!(main_of(&dir).await, b);
|
assert_eq!(main_of(&dir).await, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn an_ancestor_of_main_is_superseded_not_refused() {
|
||||||
|
let (dir, [a, b, _]) = repo().await;
|
||||||
|
let outcome = advance_applied_main(dir.path(), &a, async { anyhow::Ok(()) }).await;
|
||||||
|
assert_eq!(outcome.expect("no-op"), RevAdvance::Superseded);
|
||||||
|
assert_eq!(main_of(&dir).await, b);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn a_descendant_rev_fast_forwards_main() {
|
async fn a_descendant_rev_fast_forwards_main() {
|
||||||
let (dir, [a, b, _]) = repo().await;
|
let (dir, [a, b, _]) = repo().await;
|
||||||
|
|
|
||||||
|
|
@ -328,6 +328,10 @@ async fn apply_rev(agent: &str, rev: &str) -> bool {
|
||||||
tracing::info!(%agent, %rev, "swarm events: deploy requested for the applied rev; nothing queued");
|
tracing::info!(%agent, %rev, "swarm events: deploy requested for the applied rev; nothing queued");
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
Ok(crate::actions::RevAdvance::Superseded) => {
|
||||||
|
tracing::info!(%agent, %rev, "swarm events: deploy requested for a rev applied/main already contains; nothing queued");
|
||||||
|
false
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!(%agent, %rev, error = %format!("{e:#}"), "swarm events: rev not applied; nothing queued");
|
tracing::error!(%agent, %rev, error = %format!("{e:#}"), "swarm events: rev not applied; nothing queued");
|
||||||
false
|
false
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue