refactor(swarm-queue-client): share the hive-status bucket's name and shape
The bucket has two ends in two crates: a hive writes its own key, the controller reads every key. `swarm-controller` declared the name as a private const with a doc comment arguing that "reader and writer must name the same bucket" — an argument the writer, in another crate, could not obey. The name is the mild half. Both ends do get-or-create, because either may come up first on a fresh swarm and neither can assume the other has run. Two `Config`s that drift means whichever end created the bucket wins and the other's `get_key_value` succeeds against a bucket it did not ask for: no error, no log, just a retention policy nobody chose. Sharing the constructor gives that race one outcome. Behind a default-off `kv` feature, so the crate's other consumer — the auth-callout responder, which speaks the connect and nothing else — still pulls neither `jetstream` nor `kv`. That was the actual reason the feature was excluded when this crate was extracted; the flag preserves it. The surface is deliberately narrow: one bucket's name and creation config, not a general KV facade.
This commit is contained in:
parent
6712cdb796
commit
22659234c4
6 changed files with 119 additions and 41 deletions
|
|
@ -38,13 +38,6 @@ use utoipa::ToSchema;
|
|||
|
||||
use crate::HiveEntry;
|
||||
|
||||
/// The KV bucket hives publish their snapshots into.
|
||||
///
|
||||
/// A constant and not an option: reader and writer must name the same
|
||||
/// bucket, and an option is a way for two deployments to disagree about
|
||||
/// which one that is. Nothing about a bucket name is site-specific.
|
||||
pub const BUCKET: &str = "hive-status";
|
||||
|
||||
/// Default age past which a snapshot is reported stale.
|
||||
///
|
||||
/// A threshold is a statement about how often hives offer, and that
|
||||
|
|
@ -155,34 +148,16 @@ impl StatusReader {
|
|||
|
||||
/// The bucket handle, created on first use if nothing has made it yet.
|
||||
///
|
||||
/// Whichever side arrives first creates it, and both sides want the
|
||||
/// same shape, so this is a race with one outcome. `history: 1` is
|
||||
/// the shape: the aggregate reads *the last thing each hive said*,
|
||||
/// and retaining more would be storage bought for a query nobody
|
||||
/// makes.
|
||||
/// The name and the creation config come from
|
||||
/// [`swarm_queue_client::status`] rather than from here: the hive that
|
||||
/// writes this bucket opens it with the same call, and a bucket both
|
||||
/// ends may create is one both ends have to describe identically.
|
||||
///
|
||||
/// Whichever side arrives first creates it, and both sides ask for the
|
||||
/// same shape, so this is a race with one outcome.
|
||||
async fn store(&self) -> Result<&async_nats::jetstream::kv::Store> {
|
||||
self.store
|
||||
.get_or_try_init(|| async {
|
||||
let js = async_nats::jetstream::new(self.client.clone());
|
||||
match js.get_key_value(BUCKET).await {
|
||||
Ok(store) => Ok(store),
|
||||
Err(e) => {
|
||||
tracing::info!(
|
||||
bucket = BUCKET,
|
||||
reason = %e,
|
||||
"status bucket not available, creating it"
|
||||
);
|
||||
js.create_key_value(async_nats::jetstream::kv::Config {
|
||||
bucket: BUCKET.to_owned(),
|
||||
description: "Last status snapshot offered by each hive".to_owned(),
|
||||
history: 1,
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.with_context(|| format!("creating the {BUCKET} bucket"))
|
||||
}
|
||||
}
|
||||
})
|
||||
.get_or_try_init(|| swarm_queue_client::status::open_or_create(&self.client))
|
||||
.await
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue