download an agent's cached closure in prebuild, building locally when that fails
This commit is contained in:
parent
b845630023
commit
c16cf1756f
4 changed files with 1032 additions and 81 deletions
62
hive-c0re/src/attic/closure.rs
Normal file
62
hive-c0re/src/attic/closure.rs
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
//! The closure the swarm built for an agent at a given `internal/meta` commit,
|
||||
//! as a rebuild's `Prebuild` downloads it instead of building.
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use swarm_wire::agent_closures::AgentClosure;
|
||||
|
||||
use super::probe::{self, Unpullable};
|
||||
|
||||
/// The store path of the closure `agent`'s record names, when the record is a
|
||||
/// build of `rev` that the builder pushed to the cache.
|
||||
///
|
||||
/// # Errors
|
||||
/// No record, one for another commit, or one the builder did not push; or the
|
||||
/// queue cannot be reached. The message is what a deploy log says.
|
||||
pub async fn path_for(agent: &str, rev: &str) -> Result<String> {
|
||||
let client = crate::swarm_queue::client()
|
||||
.await
|
||||
.ok_or_else(|| anyhow!("this hive has no connection to the swarm queue"))?;
|
||||
let record = probe::read_record(&client, agent).await?;
|
||||
path_in(record.as_ref(), rev)
|
||||
}
|
||||
|
||||
fn path_in(record: Option<&AgentClosure>, rev: &str) -> Result<String> {
|
||||
probe::current_record(record, Some(rev))
|
||||
.map(|record| record.store_path.clone())
|
||||
.map_err(|Unpullable { reason, detail }| anyhow!("{}: {detail}", reason.label()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const REV: &str = "a1b2c3";
|
||||
const PATH: &str = "/nix/store/0123456789abcdfghijklmnpqrsvwxyz-nixos-system-atlas";
|
||||
|
||||
fn record(rev: &str, cached: bool) -> AgentClosure {
|
||||
AgentClosure {
|
||||
meta_rev: rev.to_owned(),
|
||||
store_path: PATH.to_owned(),
|
||||
system: "x86_64-linux".to_owned(),
|
||||
built_unix: 1,
|
||||
cached,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_pushed_build_of_the_rev_names_its_path() {
|
||||
assert_eq!(path_in(Some(&record(REV, true)), REV).expect("path"), PATH);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn anything_else_names_why_not() {
|
||||
for (record, why) in [
|
||||
(None, "no-record"),
|
||||
(Some(record("other", true)), "rev-mismatch"),
|
||||
(Some(record(REV, false)), "not-cached"),
|
||||
] {
|
||||
let err = path_in(record.as_ref(), REV).expect_err(why);
|
||||
assert!(format!("{err}").starts_with(why), "{err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
//! The swarm's binary cache, as this hive sees it: the pull credentials it is
|
||||
//! given ([`reader`]), the narinfo check ([`narinfo`]), and the probe that
|
||||
//! reports per agent whether the latest closure could be pulled ([`probe`]).
|
||||
//! given ([`reader`]), the narinfo check ([`narinfo`]), the probe that reports
|
||||
//! per agent whether the latest closure could be pulled ([`probe`]), and the
|
||||
//! lookup a rebuild uses to download that closure ([`closure`]).
|
||||
|
||||
pub mod closure;
|
||||
pub mod narinfo;
|
||||
pub mod probe;
|
||||
pub mod reader;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
//!
|
||||
//! Every [`PASS_INTERVAL`] each agent's record in `agent-closures` is looked up
|
||||
//! and its narinfo fetched with the pull token and checked against the cache's
|
||||
//! public key. Nothing is downloaded and nothing is deployed from it.
|
||||
//! public key. Nothing is downloaded here; a rebuild downloads through
|
||||
//! [`super::closure`].
|
||||
//!
|
||||
//! The verdict is the first of these that fails, in this order:
|
||||
//! no credentials (`no-credentials`), no record (`no-record`), a record built
|
||||
|
|
@ -82,20 +83,12 @@ impl Cache for Http {
|
|||
}
|
||||
}
|
||||
|
||||
/// Whether the closure `record` names can be pulled with `facts`. `head` is the
|
||||
/// `internal/meta` commit a deploy would build from.
|
||||
pub(crate) async fn judge(
|
||||
facts: Option<&Facts>,
|
||||
record: Option<&AgentClosure>,
|
||||
/// `record` when it is the latest build of the `internal/meta` commit `head`
|
||||
/// and the builder pushed it to the cache.
|
||||
pub(crate) fn current_record<'a>(
|
||||
record: Option<&'a AgentClosure>,
|
||||
head: Option<&str>,
|
||||
cache: &impl Cache,
|
||||
) -> Result<(), Unpullable> {
|
||||
let facts = facts.ok_or_else(|| {
|
||||
no(
|
||||
Reason::NoCredentials,
|
||||
"the swarm has given this hive no cache facts",
|
||||
)
|
||||
})?;
|
||||
) -> Result<&'a AgentClosure, Unpullable> {
|
||||
let record =
|
||||
record.ok_or_else(|| no(Reason::NoRecord, "no build is recorded for the agent"))?;
|
||||
if head != Some(record.meta_rev.as_str()) {
|
||||
|
|
@ -114,6 +107,24 @@ pub(crate) async fn judge(
|
|||
"the builder did not push the closure",
|
||||
));
|
||||
}
|
||||
Ok(record)
|
||||
}
|
||||
|
||||
/// Whether the closure `record` names can be pulled with `facts`. `head` is the
|
||||
/// `internal/meta` commit a deploy would build from.
|
||||
pub(crate) async fn judge(
|
||||
facts: Option<&Facts>,
|
||||
record: Option<&AgentClosure>,
|
||||
head: Option<&str>,
|
||||
cache: &impl Cache,
|
||||
) -> Result<(), Unpullable> {
|
||||
let facts = facts.ok_or_else(|| {
|
||||
no(
|
||||
Reason::NoCredentials,
|
||||
"the swarm has given this hive no cache facts",
|
||||
)
|
||||
})?;
|
||||
let record = current_record(record, head)?;
|
||||
let text = match cache.narinfo(facts, &record.store_path).await {
|
||||
Ok(Some(text)) => text,
|
||||
Ok(None) => return Err(no(Reason::NotCached, "the cache does not hold the narinfo")),
|
||||
|
|
@ -195,7 +206,10 @@ async fn pass(client: &async_nats::Client) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn read_record(client: &async_nats::Client, agent: &str) -> Result<Option<AgentClosure>> {
|
||||
pub(crate) async fn read_record(
|
||||
client: &async_nats::Client,
|
||||
agent: &str,
|
||||
) -> Result<Option<AgentClosure>> {
|
||||
match agent_closures::read(client, agent)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!(e))
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
//! ([`swarm_secret_client::attic`]), read here under the hive's own identity.
|
||||
//!
|
||||
//! [`Facts::publish`] puts them in `/run/hyperhive/attic/` as a netrc, the
|
||||
//! substituter URL and the public key, where nix and the root helper read them.
|
||||
//! substituter URL and the public key, which the pull probe reads. The root helper
|
||||
//! reads its own copies, kept by a root unit.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue