pin the declared closure once per rebuild and seed the marker from the running closure
This commit is contained in:
parent
47c2acb640
commit
4356f3840c
1 changed files with 456 additions and 95 deletions
|
|
@ -45,6 +45,12 @@ pub(super) trait Host: Sync {
|
|||
/// The record the swarm's builder wrote for the agent; `None` when it wrote
|
||||
/// none.
|
||||
async fn closure_record(&self, name: &str) -> Result<Option<AgentClosure>>;
|
||||
/// Pin `store_path` as the closure this rebuild of the agent deploys.
|
||||
fn pin_closure(&self, name: &str, store_path: &str);
|
||||
/// The store path pinned for the agent's rebuild.
|
||||
fn pinned_closure(&self, name: &str) -> Option<String>;
|
||||
/// Drop the agent's pin.
|
||||
fn unpin_closure(&self, name: &str);
|
||||
/// Download `store_path` from the swarm's cache into the store, never
|
||||
/// building it.
|
||||
async fn realise_closure(&self, name: &str, store_path: &str) -> Result<()>;
|
||||
|
|
@ -109,6 +115,18 @@ impl Host for RealHost {
|
|||
crate::attic::closure::record(name).await
|
||||
}
|
||||
|
||||
fn pin_closure(&self, name: &str, store_path: &str) {
|
||||
crate::attic::closure::pin(name, store_path);
|
||||
}
|
||||
|
||||
fn pinned_closure(&self, name: &str) -> Option<String> {
|
||||
crate::attic::closure::pinned(name)
|
||||
}
|
||||
|
||||
fn unpin_closure(&self, name: &str) {
|
||||
crate::attic::closure::unpin(name);
|
||||
}
|
||||
|
||||
async fn realise_closure(&self, name: &str, store_path: &str) -> Result<()> {
|
||||
crate::priv_client::realise_closure(name, store_path).await
|
||||
}
|
||||
|
|
@ -471,8 +489,13 @@ fn run_set_wanted(coord: &Arc<Coordinator>, agent: &str, up: bool) -> Result<()>
|
|||
}
|
||||
|
||||
/// The rebuild's preamble: runtime-dir prep, the archetype and agent-set
|
||||
/// refresh. Reads nothing from the forge: what a rebuild deploys is the swarm's
|
||||
/// declared closure, read by `Prebuild` and `Swap`.
|
||||
/// refresh, and the closure the rebuild deploys. The swarm's record is read
|
||||
/// once, here, and its store path pinned: `Prebuild` and `Swap` use the pin, so
|
||||
/// a record that changes meanwhile cannot make `Swap` pull another closure than
|
||||
/// `Prebuild` fetched, and the marker names what was applied. The next
|
||||
/// auto-update pass picks up the newer record. Reads nothing from the forge.
|
||||
///
|
||||
/// Fails, before anything is stopped, when the swarm has no closure to deploy.
|
||||
async fn run_meta_sync(host: &impl Host, name: &str) -> Result<()> {
|
||||
// Runs while the agent is still up — the runtime dir and MCP listener
|
||||
// already exist. Use the pure path accessor; no need to re-register the
|
||||
|
|
@ -486,6 +509,10 @@ async fn run_meta_sync(host: &impl Host, name: &str) -> Result<()> {
|
|||
}
|
||||
host.prepare_rebuild_dirs(name, &paths).await?;
|
||||
host.refresh_agent_set().await?;
|
||||
let store_path = declared_closure(host, name)
|
||||
.await
|
||||
.map_err(|why| without_closure(name, "meta-sync", &why))?;
|
||||
host.pin_closure(name, &store_path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -496,7 +523,7 @@ async fn run_meta_sync(host: &impl Host, name: &str) -> Result<()> {
|
|||
/// is already down: there is no uptime to protect, and `Swap` fails the same way.
|
||||
async fn run_prebuild(host: &impl Host, name: &str) -> Result<()> {
|
||||
if host.is_running(name).await {
|
||||
let closure = closure_for(host, name, "prebuild").await?;
|
||||
let closure = realise_pinned(host, name, "prebuild").await?;
|
||||
tracing::info!(%name, "prebuild: substituted {closure}");
|
||||
}
|
||||
Ok(())
|
||||
|
|
@ -512,18 +539,46 @@ enum NoClosure {
|
|||
}
|
||||
|
||||
/// The store path of the closure the swarm declares for `name`, downloaded into
|
||||
/// the store. Whatever `internal/meta` commit it was built from, it is the one
|
||||
/// deployed.
|
||||
/// the store, for a deploy that has no `MetaSync` to pin it first (a create).
|
||||
/// Whatever `internal/meta` commit it was built from, it is the one deployed.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`without_closure`]'s refusal, after logging it.
|
||||
async fn closure_for(host: &impl Host, name: &str, step: &str) -> Result<String> {
|
||||
pull_closure(host, name)
|
||||
.await
|
||||
.map_err(|why| without_closure(name, step, &why))
|
||||
async {
|
||||
let store_path = declared_closure(host, name).await?;
|
||||
realise(host, name, &store_path).await?;
|
||||
Ok(store_path)
|
||||
}
|
||||
.await
|
||||
.map_err(|why| without_closure(name, step, &why))
|
||||
}
|
||||
|
||||
async fn pull_closure(host: &impl Host, name: &str) -> Result<String, NoClosure> {
|
||||
/// Download the closure `MetaSync` pinned for the agent's rebuild.
|
||||
///
|
||||
/// # Errors
|
||||
/// No closure was pinned, or the download failed; the latter is logged by
|
||||
/// [`without_closure`].
|
||||
async fn realise_pinned(host: &impl Host, name: &str, step: &str) -> Result<String> {
|
||||
let Some(store_path) = host.pinned_closure(name) else {
|
||||
let why = NoClosure::Unpullable("no closure was pinned for this rebuild".to_owned());
|
||||
return Err(without_closure(name, step, &why));
|
||||
};
|
||||
realise(host, name, &store_path)
|
||||
.await
|
||||
.map_err(|why| without_closure(name, step, &why))?;
|
||||
Ok(store_path)
|
||||
}
|
||||
|
||||
async fn realise(host: &impl Host, name: &str, store_path: &str) -> Result<(), NoClosure> {
|
||||
host.realise_closure(name, store_path)
|
||||
.await
|
||||
.map_err(|e| NoClosure::Unpullable(format!("{e:#}")))
|
||||
}
|
||||
|
||||
/// The store path of the record the swarm's builder wrote for `name`, when the
|
||||
/// builder pushed its closure.
|
||||
async fn declared_closure(host: &impl Host, name: &str) -> Result<String, NoClosure> {
|
||||
let record = host
|
||||
.closure_record(name)
|
||||
.await
|
||||
|
|
@ -534,9 +589,6 @@ async fn pull_closure(host: &impl Host, name: &str) -> Result<String, NoClosure>
|
|||
"the builder did not push the closure".to_owned(),
|
||||
));
|
||||
}
|
||||
host.realise_closure(name, &record.store_path)
|
||||
.await
|
||||
.map_err(|e| NoClosure::Unpullable(format!("{e:#}")))?;
|
||||
Ok(record.store_path)
|
||||
}
|
||||
|
||||
|
|
@ -574,7 +626,7 @@ async fn run_swap(
|
|||
let agent_dir = crate::paths::agent_runtime_dir(name);
|
||||
let hive = coord.hive_env();
|
||||
let paths = Coordinator::agent_paths(name, agent_dir)?;
|
||||
let closure = closure_for(host, name, "swap").await?;
|
||||
let closure = realise_pinned(host, name, "swap").await?;
|
||||
tracing::info!(%name, "swap: applying {closure}");
|
||||
let result = host
|
||||
.swap_update(name, &hive, &paths, &closure, id.get())
|
||||
|
|
@ -582,10 +634,11 @@ async fn run_swap(
|
|||
// The marker names what was applied; a failed write is not a failed swap
|
||||
// (the container runs the closure), it only means the next comparison
|
||||
// sees the declaration as new.
|
||||
if result.is_ok()
|
||||
&& let Err(e) = host.mark_deployed(name, &closure)
|
||||
{
|
||||
tracing::warn!(%name, error = ?e, "write the deployed-closure marker failed");
|
||||
if result.is_ok() {
|
||||
if let Err(e) = host.mark_deployed(name, &closure) {
|
||||
tracing::warn!(%name, error = ?e, "write the deployed-closure marker failed");
|
||||
}
|
||||
host.unpin_closure(name);
|
||||
}
|
||||
// On success the Ok-only bookkeeping tail (forge
|
||||
// sync, kick, rescan, snapshot) runs in the sibling `RebuildBookkeeping` node,
|
||||
|
|
|
|||
Loading…
Reference in a new issue