Watch
0
0
Fork
You've already forked hyperhive
0

Compare commits

..
42 changed files with 886 additions and 903 deletions

2
Cargo.lock generated
View file

@ -1802,7 +1802,6 @@ dependencies = [
"swarm-matrix-client", "swarm-matrix-client",
"swarm-queue-client", "swarm-queue-client",
"swarm-secret-client", "swarm-secret-client",
"swarm-wire",
"tempfile", "tempfile",
"tokio", "tokio",
"tokio-stream", "tokio-stream",
@ -5036,6 +5035,7 @@ dependencies = [
"reqwest", "reqwest",
"serde", "serde",
"serde_json", "serde_json",
"strum",
"swarm-secret-client", "swarm-secret-client",
"swarm-wire", "swarm-wire",
"thiserror 2.0.18", "thiserror 2.0.18",

View file

@ -18,7 +18,7 @@ export interface AgentStatusSnapshot {
} }
// `"up"` / `"offline"` / `"destroyed"`, wire-spelled by // `"up"` / `"offline"` / `"destroyed"`, wire-spelled by
// `swarm_wire::wanted::AgentState::as_str` — kept as a bare // `swarm_queue_client::wanted::AgentState::as_str` — kept as a bare
// `string | null` rather than a union, same reason `config_pr`'s shape // `string | null` rather than a union, same reason `config_pr`'s shape
// isn't re-derived here: this renders whatever the wire sends, it // isn't re-derived here: this renders whatever the wire sends, it
// doesn't validate the enum client-side. // doesn't validate the enum client-side.

View file

@ -31,8 +31,7 @@ use std::time::Duration;
use anyhow::{Context as _, Result}; use anyhow::{Context as _, Result};
use rusqlite::{Connection, OptionalExtension as _, params}; use rusqlite::{Connection, OptionalExtension as _, params};
use swarm_queue_client::agent_schedules::{Read, read_own}; use swarm_queue_client::agent_schedules::{AgentSchedules, Read, Schedule, read_own};
use swarm_wire::agent_schedules::{AgentSchedules, Schedule};
use tokio::sync::Notify; use tokio::sync::Notify;
use crate::todos::Todos; use crate::todos::Todos;

View file

@ -72,7 +72,7 @@ pub fn spawn() {
async fn run(agent: String) { async fn run(agent: String) {
let client = crate::swarm_queue::client().await; let client = crate::swarm_queue::client().await;
let subject = swarm_wire::agent_icon::subject(&agent); let subject = swarm_queue_client::agent_icon::subject(&agent);
let icon = match read_icon(Path::new(ICON_PATH)) { let icon = match read_icon(Path::new(ICON_PATH)) {
Ok(icon) => icon, Ok(icon) => icon,
Err(e) => { Err(e) => {

View file

@ -33,9 +33,9 @@ use serde::Serialize;
use tokio::sync::broadcast; use tokio::sync::broadcast;
use tokio::time::{Interval, MissedTickBehavior}; use tokio::time::{Interval, MissedTickBehavior};
use swarm_queue_client::wanted::AgentState;
use swarm_wire::agent_inference::Effective; use swarm_wire::agent_inference::Effective;
use swarm_wire::agent_login::Login; use swarm_wire::agent_login::Login;
use swarm_wire::wanted::AgentState;
use crate::events::{Bus, BusEvent, LiveEvent, TurnState}; use crate::events::{Bus, BusEvent, LiveEvent, TurnState};
use crate::login::LoginState; use crate::login::LoginState;
@ -337,8 +337,8 @@ mod tests {
use crate::events::{BusEvent, LiveEvent, TurnState}; use crate::events::{BusEvent, LiveEvent, TurnState};
use crate::login::LoginState; use crate::login::LoginState;
use std::sync::Mutex; use std::sync::Mutex;
use swarm_queue_client::wanted::AgentState;
use swarm_wire::agent_inference::{Effective, Source}; use swarm_wire::agent_inference::{Effective, Source};
use swarm_wire::wanted::AgentState;
use tokio::sync::broadcast; use tokio::sync::broadcast;
use tokio::time::{Duration, Instant}; use tokio::time::{Duration, Instant};

View file

@ -63,7 +63,6 @@ swarm-queue-client = { workspace = true, features = [
] } ] }
swarm-matrix-client.workspace = true swarm-matrix-client.workspace = true
swarm-secret-client.workspace = true swarm-secret-client.workspace = true
swarm-wire.workspace = true
tokio.workspace = true tokio.workspace = true
tokio-stream.workspace = true tokio-stream.workspace = true
tracing.workspace = true tracing.workspace = true

View file

@ -1,7 +1,7 @@
//! Offering each locally-hosted agent's status upward to the swarm. //! Offering each locally-hosted agent's status upward to the swarm.
//! //!
//! Sibling task to [`crate::swarm_status`], not a field folded into it — see //! Sibling task to [`crate::swarm_status`], not a field folded into it — see
//! `swarm_wire::agent_status`'s module doc for why hive status and //! `swarm_queue_client::agent_status`'s module doc for why hive status and
//! agent status live in separate buckets. Same direction, though: a hive //! agent status live in separate buckets. Same direction, though: a hive
//! **offers** what it already knows about its own agents (via //! **offers** what it already knows about its own agents (via
//! [`crate::container_view::read_agent_status_bounded`], the bounded form of //! [`crate::container_view::read_agent_status_bounded`], the bounded form of
@ -9,7 +9,7 @@
//! controller never reaches down to collect it. //! controller never reaches down to collect it.
//! //!
//! **One publish per agent, not one document for the hive.** Each agent //! **One publish per agent, not one document for the hive.** Each agent
//! gets its own key (`swarm_wire::agent_status::key`), so one //! gets its own key (`swarm_queue_client::agent_status::key`), so one
//! agent's status changing does not touch any other agent's key, and a //! agent's status changing does not touch any other agent's key, and a
//! consumer watching one agent reads exactly its own revision history. //! consumer watching one agent reads exactly its own revision history.
@ -127,7 +127,7 @@ async fn publish_all(client: &async_nats::Client, hive: &str) -> Result<usize> {
tracing::warn!(agent = %spec.name, "agent status: unit state unreadable, skipping"); tracing::warn!(agent = %spec.name, "agent status: unit state unreadable, skipping");
continue; continue;
}; };
let payload = swarm_wire::agent_status::AgentStatus { let payload = swarm_queue_client::agent_status::AgentStatus {
status_text, status_text,
status_set_at, status_set_at,
running, running,
@ -139,7 +139,7 @@ async fn publish_all(client: &async_nats::Client, hive: &str) -> Result<usize> {
continue; continue;
} }
}; };
let key = swarm_wire::agent_status::key(hive, &spec.name); let key = swarm_queue_client::agent_status::key(hive, &spec.name);
if let Err(e) = store.put(key, bytes.into()).await { if let Err(e) = store.put(key, bytes.into()).await {
tracing::warn!(agent = %spec.name, error = %e, "agent status: publish failed, skipping"); tracing::warn!(agent = %spec.name, error = %e, "agent status: publish failed, skipping");
continue; continue;

View file

@ -8,7 +8,7 @@
//! a status snapshot has a current value a late reader can always ask //! a status snapshot has a current value a late reader can always ask
//! for; a lifecycle notice ("container crashed at 04:12") does not — miss //! for; a lifecycle notice ("container crashed at 04:12") does not — miss
//! it and there is nothing left to read later that says it happened. See //! it and there is nothing left to read later that says it happened. See
//! [`swarm_queue_client::notices`] for opening the stream this publishes into. //! [`swarm_queue_client::notices`] for the stream this publishes into.
//! //!
//! **Best-effort, never fatal to the caller.** A hive with no queue //! **Best-effort, never fatal to the caller.** A hive with no queue
//! coordinates is a silent no-op (the ordinary case). A hive that cannot //! coordinates is a silent no-op (the ordinary case). A hive that cannot
@ -129,7 +129,7 @@ async fn publish(
.context("serialising the notice")?; .context("serialising the notice")?;
let js = async_nats::jetstream::new(client.clone()); let js = async_nats::jetstream::new(client.clone());
js.publish(swarm_wire::notices::subject(hive), payload.into()) js.publish(swarm_queue_client::notices::subject(hive), payload.into())
.await .await
.context("publishing the notice")? .context("publishing the notice")?
.await .await

View file

@ -1,7 +1,7 @@
//! Reading the caps the swarm sets for this hive's agents. //! Reading the caps the swarm sets for this hive's agents.
//! //!
//! swarm-controller keeps one key per agent in the `agent-limits` bucket //! swarm-controller keeps one key per agent in the `agent-limits` bucket
//! ([`swarm_wire::agent_limits`]). Every [`READ_INTERVAL`] this reads //! ([`swarm_queue_client::agent_limits`]). Every [`READ_INTERVAL`] this reads
//! the key of each agent this hive has and stores what it finds in //! the key of each agent this hive has and stores what it finds in
//! `swarm-resource-limits.json`, where the drop-in reads it. //! `swarm-resource-limits.json`, where the drop-in reads it.
//! A running agent gets a changed cap at its first spawn or rebuild after //! A running agent gets a changed cap at its first spawn or rebuild after
@ -14,8 +14,7 @@ use std::collections::BTreeMap;
use std::time::Duration; use std::time::Duration;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use swarm_queue_client::agent_limits::{self, Read}; use swarm_queue_client::agent_limits::{self, AgentCaps, Read};
use swarm_wire::agent_limits::AgentCaps;
use crate::resource_limits::AgentLimits; use crate::resource_limits::AgentLimits;
use crate::stats::sweep_health::{self, SweepHealth}; use crate::stats::sweep_health::{self, SweepHealth};
@ -128,7 +127,7 @@ mod tests {
use std::collections::BTreeMap; use std::collections::BTreeMap;
use super::validated; use super::validated;
use swarm_wire::agent_limits::AgentCaps; use swarm_queue_client::agent_limits::AgentCaps;
/// `atlas`'s answer with `atlas_key` as the value read for it, over the /// `atlas`'s answer with `atlas_key` as the value read for it, over the
/// hive-wide defaults of 200% / 4G. /// hive-wide defaults of 200% / 4G.

View file

@ -1,7 +1,7 @@
//! Converging this hive onto the agent set the swarm controller declares. //! Converging this hive onto the agent set the swarm controller declares.
//! //!
//! The controller gives each hive its own `hive-wanted-<hive>` bucket (see //! The controller gives each hive its own `hive-wanted-<hive>` bucket (see
//! [`swarm_wire::wanted`]); this reads its own and acts on it. It is //! [`swarm_queue_client::wanted`]); this reads its own and acts on it. It is
//! the **repair** path, not the fast one: a deploy event //! the **repair** path, not the fast one: a deploy event
//! ([`crate::swarm_status`]) is core NATS, so a hive that was down never hears //! ([`crate::swarm_status`]) is core NATS, so a hive that was down never hears
//! it, and until this existed that left the hive on its old shape with nothing //! it, and until this existed that left the hive on its old shape with nothing
@ -33,7 +33,7 @@ use std::collections::{BTreeMap, BTreeSet};
use std::sync::Arc; use std::sync::Arc;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use swarm_wire::wanted::{AgentState, HiveWanted}; use swarm_queue_client::wanted::{AgentState, HiveWanted};
use crate::coordinator::Coordinator; use crate::coordinator::Coordinator;
use crate::power::Wanted; use crate::power::Wanted;
@ -469,7 +469,7 @@ mod tests {
use super::{Converge, PauseConverge, Plan, carries_a_declaration, decide, decide_pause, plan}; use super::{Converge, PauseConverge, Plan, carries_a_declaration, decide, decide_pause, plan};
use crate::power::Wanted; use crate::power::Wanted;
use swarm_wire::wanted::{AgentState, AgentWanted, HiveWanted}; use swarm_queue_client::wanted::{AgentState, AgentWanted, HiveWanted};
fn declaration(agents: &[(&str, AgentState)]) -> HiveWanted { fn declaration(agents: &[(&str, AgentState)]) -> HiveWanted {
HiveWanted { HiveWanted {

View file

@ -7,7 +7,7 @@
//! property of it, so there is nothing for a timestamp to mean here. //! property of it, so there is nothing for a timestamp to mean here.
//! //!
//! **No hive is named on this path**, because the key does not carry one — //! **No hive is named on this path**, because the key does not carry one —
//! see `swarm_wire::agent_icon` for why, and for who writes it. //! see `swarm_queue_client::agent_icon` for why, and for who writes it.
//! //!
//! This reader is also what creates the bucket: the agents that write it are //! This reader is also what creates the bucket: the agents that write it are
//! granted their own key and nothing that could create a stream. //! granted their own key and nothing that could create a stream.

View file

@ -2,7 +2,7 @@
//! one agent. //! one agent.
//! //!
//! Each agent's caps are one value under its own key of `agent-limits` //! Each agent's caps are one value under its own key of `agent-limits`
//! (`swarm_wire::agent_limits`), which this daemon alone writes. The //! (`swarm_queue_client::agent_limits`), which this daemon alone writes. The
//! hive running the agent reads the key; on the hive, a cap replaces the //! hive running the agent reads the key; on the hive, a cap replaces the
//! hive's own answer for that field, and a field left unset stays with the //! hive's own answer for that field, and a field left unset stays with the
//! hive. //! hive.
@ -16,8 +16,7 @@ use axum::extract::{Path, State};
use axum::http::StatusCode; use axum::http::StatusCode;
use problem_details::ProblemDetails; use problem_details::ProblemDetails;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use swarm_queue_client::agent_limits; use swarm_queue_client::agent_limits::{self, AgentCaps};
use swarm_wire::agent_limits::AgentCaps;
use utoipa::ToSchema; use utoipa::ToSchema;
use crate::routes::{AppState, error_problem, wanted_error_status}; use crate::routes::{AppState, error_problem, wanted_error_status};
@ -359,7 +358,7 @@ pub(crate) mod tests {
use axum::extract::{Path, State}; use axum::extract::{Path, State};
use axum::http::StatusCode; use axum::http::StatusCode;
use std::sync::Arc; use std::sync::Arc;
use swarm_wire::wanted::AgentState; use swarm_queue_client::wanted::AgentState;
/// Of the `bad` values, systemd parses `""` (no quota) and `+5%`, and the /// Of the `bad` values, systemd parses `""` (no quota) and `+5%`, and the
/// last adds a directive on a second line. It ignores every other one /// last adds a directive on a second line. It ignores every other one

View file

@ -860,7 +860,7 @@ mod tests {
State(state.clone()), State(state.clone()),
Path(("pr1ma".to_owned(), "atlas".to_owned())), Path(("pr1ma".to_owned(), "atlas".to_owned())),
axum::Json(crate::routes::agents::SetAgentStateRequest { axum::Json(crate::routes::agents::SetAgentStateRequest {
state: swarm_wire::wanted::AgentState::Destroyed, state: swarm_queue_client::wanted::AgentState::Destroyed,
}), }),
) )
.await .await

View file

@ -33,8 +33,8 @@ use std::collections::BTreeSet;
use std::sync::Arc; use std::sync::Arc;
use anyhow::{Context, Result, bail}; use anyhow::{Context, Result, bail};
use swarm_queue_client::wanted::{AgentState, HiveWanted};
use swarm_secret_client::{SecretStore, mtls, queue}; use swarm_secret_client::{SecretStore, mtls, queue};
use swarm_wire::wanted::{AgentState, HiveWanted};
use x509_cert::der::DecodePem as _; use x509_cert::der::DecodePem as _;
use crate::wanted::WantedWriter; use crate::wanted::WantedWriter;
@ -429,7 +429,7 @@ pub fn spawn(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use swarm_wire::wanted::AgentWanted; use swarm_queue_client::wanted::AgentWanted;
const NOW: i64 = 1_790_000_000; const NOW: i64 = 1_790_000_000;
const DAY: i64 = 86_400; const DAY: i64 = 86_400;

View file

@ -1,7 +1,7 @@
//! Swarm-wide view of what each agent last said about itself. //! Swarm-wide view of what each agent last said about itself.
//! //!
//! Sibling of [`crate::status`], not a mode of it — see //! Sibling of [`crate::status`], not a mode of it — see
//! `swarm_wire::agent_status`'s module doc for why the two buckets //! `swarm_queue_client::agent_status`'s module doc for why the two buckets
//! stay separate. Everything about *how* a snapshot is read (queue is the //! stay separate. Everything about *how* a snapshot is read (queue is the
//! store, no cache, freshness derived at read time, rows come from the //! store, no cache, freshness derived at read time, rows come from the
//! roster so an empty bucket cannot render as "every agent is fine") is //! roster so an empty bucket cannot render as "every agent is fine") is
@ -11,7 +11,7 @@
//! **The roster here is agent *names* only** ([`crate::auth::AuthBridge::list_agent_identities`]) — //! **The roster here is agent *names* only** ([`crate::auth::AuthBridge::list_agent_identities`]) —
//! it does not say which hive an agent lives on. That is not a gap this //! it does not say which hive an agent lives on. That is not a gap this
//! module needs to close: a *reporting* agent's hive comes straight out of //! module needs to close: a *reporting* agent's hive comes straight out of
//! its bucket key (`swarm_wire::agent_status::split_key`), and a //! its bucket key (`swarm_queue_client::agent_status::split_key`), and a
//! *never-reported* agent's hive is unknowable regardless of what any //! *never-reported* agent's hive is unknowable regardless of what any
//! roster might claim, so [`AgentStatusRow::hive`] is `None` in exactly //! roster might claim, so [`AgentStatusRow::hive`] is `None` in exactly
//! that one case rather than a second lookup this module would have to //! that one case rather than a second lookup this module would have to
@ -42,7 +42,7 @@ pub struct AgentStatusRow {
pub age_seconds: Option<u64>, pub age_seconds: Option<u64>,
/// The agent's status text + running flag, unopened. `None` exactly /// The agent's status text + running flag, unopened. `None` exactly
/// when `freshness` is `NeverReported`, or when the stored bytes /// when `freshness` is `NeverReported`, or when the stored bytes
/// failed to decode as [`swarm_wire::agent_status::AgentStatus`] /// failed to decode as [`swarm_queue_client::agent_status::AgentStatus`]
/// (logged as a warning naming the agent). /// (logged as a warning naming the agent).
pub snapshot: Option<serde_json::Value>, pub snapshot: Option<serde_json::Value>,
/// The agent's open config PR, if any. Always `None` coming out of /// The agent's open config PR, if any. Always `None` coming out of
@ -130,7 +130,7 @@ impl AgentStatusReader {
.await .await
.context("reading the agent-status bucket's key list")? .context("reading the agent-status bucket's key list")?
{ {
let Some((hive, agent)) = swarm_wire::agent_status::split_key(&key) else { let Some((hive, agent)) = swarm_queue_client::agent_status::split_key(&key) else {
tracing::warn!(%key, "agent-status bucket key doesn't split into hive/agent, skipping"); tracing::warn!(%key, "agent-status bucket key doesn't split into hive/agent, skipping");
continue; continue;
}; };
@ -141,9 +141,10 @@ impl AgentStatusReader {
else { else {
continue; continue;
}; };
let payload = match serde_json::from_slice::<swarm_wire::agent_status::AgentStatus>( let payload = match serde_json::from_slice::<
&entry.value, swarm_queue_client::agent_status::AgentStatus,
) { >(&entry.value)
{
Ok(status) => serde_json::to_value(status).ok(), Ok(status) => serde_json::to_value(status).ok(),
Err(e) => { Err(e) => {
tracing::warn!( tracing::warn!(

View file

@ -242,7 +242,7 @@ mod tests {
State(state.clone()), State(state.clone()),
Path(("pr1ma".to_owned(), "atlas".to_owned())), Path(("pr1ma".to_owned(), "atlas".to_owned())),
axum::Json(crate::routes::agents::SetAgentStateRequest { axum::Json(crate::routes::agents::SetAgentStateRequest {
state: swarm_wire::wanted::AgentState::Destroyed, state: swarm_queue_client::wanted::AgentState::Destroyed,
}), }),
) )
.await .await

View file

@ -191,7 +191,7 @@ pub(crate) struct ArchetypeView {
/// As `PUT /api/agents/{name}/limits` takes them. /// As `PUT /api/agents/{name}/limits` takes them.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[schema(value_type = Option<Object>)] #[schema(value_type = Option<Object>)]
limits: Option<swarm_wire::agent_limits::AgentCaps>, limits: Option<swarm_queue_client::agent_limits::AgentCaps>,
/// As `PUT /api/agents/{name}/inference` takes them. /// As `PUT /api/agents/{name}/inference` takes them.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[schema(value_type = Option<Object>)] #[schema(value_type = Option<Object>)]

View file

@ -259,8 +259,8 @@ pub(crate) struct WorkerDeps {
/// decision the node exists to carry — writing a declaration at all is the /// decision the node exists to carry — writing a declaration at all is the
/// mechanism, `Paused` is the policy — and a test can pin a name where it /// mechanism, `Paused` is the policy — and a test can pin a name where it
/// cannot pin a value written into a KV bucket no unit test has a server for. /// cannot pin a value written into a KV bucket no unit test has a server for.
const NEW_AGENT_WANTED_STATE: swarm_wire::wanted::AgentState = const NEW_AGENT_WANTED_STATE: swarm_queue_client::wanted::AgentState =
swarm_wire::wanted::AgentState::Paused; swarm_queue_client::wanted::AgentState::Paused;
/// Run a claimed node's actual work. Mirrors `hive-c0re/src/job_queue/ /// Run a claimed node's actual work. Mirrors `hive-c0re/src/job_queue/
/// exec.rs::run_node`'s role exactly — the one place a `SwarmNodeKind` /// exec.rs::run_node`'s role exactly — the one place a `SwarmNodeKind`
@ -665,7 +665,7 @@ async fn declare_new_agent(
agent: &str, agent: &str,
) -> hive_jobq::scheduler::Outcome { ) -> hive_jobq::scheduler::Outcome {
use hive_jobq::scheduler::Outcome; use hive_jobq::scheduler::Outcome;
use swarm_wire::wanted::AgentState; use swarm_queue_client::wanted::AgentState;
let existing = match writer.view(hive).await { let existing = match writer.view(hive).await {
Ok(declaration) => declaration.and_then(|d| d.agents.get(agent).map(|a| a.state)), Ok(declaration) => declaration.and_then(|d| d.agents.get(agent).map(|a| a.state)),
@ -1645,7 +1645,7 @@ mod tests {
fn a_new_agent_is_declared_paused() { fn a_new_agent_is_declared_paused() {
assert_eq!( assert_eq!(
super::NEW_AGENT_WANTED_STATE, super::NEW_AGENT_WANTED_STATE,
swarm_wire::wanted::AgentState::Paused swarm_queue_client::wanted::AgentState::Paused
); );
} }

View file

@ -68,7 +68,7 @@ pub(crate) struct SetAgentStateRequest {
/// a value this build does not know is a 400 rather than a field that /// a value this build does not know is a 400 rather than a field that
/// silently does nothing. /// silently does nothing.
#[schema(value_type = String, example = "up")] #[schema(value_type = String, example = "up")]
pub(crate) state: swarm_wire::wanted::AgentState, pub(crate) state: swarm_queue_client::wanted::AgentState,
} }
/// One agent's line in a hive's declaration. /// One agent's line in a hive's declaration.
@ -78,7 +78,9 @@ pub(crate) struct AgentDeclaration {
state: String, state: String,
} }
pub(crate) fn render(declaration: &swarm_wire::wanted::HiveWanted) -> Vec<AgentDeclaration> { pub(crate) fn render(
declaration: &swarm_queue_client::wanted::HiveWanted,
) -> Vec<AgentDeclaration> {
declaration declaration
.agents .agents
.iter() .iter()
@ -221,8 +223,8 @@ pub(crate) async fn set_agent_state(
/// ///
/// Exhaustive for the reason [`revokes_queue_credential`] is. A `Destroyed` /// Exhaustive for the reason [`revokes_queue_credential`] is. A `Destroyed`
/// agent is gone from that hive, and the hive deploys nothing for it. /// agent is gone from that hive, and the hive deploys nothing for it.
fn places_agent(state: swarm_wire::wanted::AgentState) -> bool { fn places_agent(state: swarm_queue_client::wanted::AgentState) -> bool {
use swarm_wire::wanted::AgentState; use swarm_queue_client::wanted::AgentState;
match state { match state {
AgentState::Up | AgentState::Offline | AgentState::Paused => true, AgentState::Up | AgentState::Offline | AgentState::Paused => true,
AgentState::Destroyed => false, AgentState::Destroyed => false,
@ -241,7 +243,7 @@ fn places_agent(state: swarm_wire::wanted::AgentState) -> bool {
async fn refuse_placement<R, D>( async fn refuse_placement<R, D>(
agent: &str, agent: &str,
hive: &str, hive: &str,
here: Option<&swarm_wire::wanted::HiveWanted>, here: Option<&swarm_queue_client::wanted::HiveWanted>,
reserved: Option<&str>, reserved: Option<&str>,
read_roster: impl FnOnce() -> R, read_roster: impl FnOnce() -> R,
snapshot_queued: impl FnOnce() -> Vec<(String, String)>, snapshot_queued: impl FnOnce() -> Vec<(String, String)>,
@ -251,7 +253,7 @@ where
R: std::future::Future<Output = Result<bool, String>>, R: std::future::Future<Output = Result<bool, String>>,
D: std::future::Future< D: std::future::Future<
Output = Result< Output = Result<
Vec<(String, swarm_wire::wanted::HiveWanted)>, Vec<(String, swarm_queue_client::wanted::HiveWanted)>,
problem_details::ProblemDetails, problem_details::ProblemDetails,
>, >,
>, >,
@ -293,13 +295,13 @@ where
/// Whether declaring an agent into `state` ends its queue credential's life. /// Whether declaring an agent into `state` ends its queue credential's life.
/// ///
/// Exhaustive on purpose, like every other match on /// Exhaustive on purpose, like every other match on
/// [`AgentState`][swarm_wire::wanted::AgentState] in this /// [`AgentState`][swarm_queue_client::wanted::AgentState] in this
/// tree: a state added later has to say whether it revokes rather than /// tree: a state added later has to say whether it revokes rather than
/// inheriting "no" from a catch-all. `Offline` and `Paused` deliberately do /// inheriting "no" from a catch-all. `Offline` and `Paused` deliberately do
/// not — both are states an agent comes back from, and revoking on either /// not — both are states an agent comes back from, and revoking on either
/// would mean an agent that could be stopped but never restarted. /// would mean an agent that could be stopped but never restarted.
fn revokes_queue_credential(state: swarm_wire::wanted::AgentState) -> bool { fn revokes_queue_credential(state: swarm_queue_client::wanted::AgentState) -> bool {
use swarm_wire::wanted::AgentState; use swarm_queue_client::wanted::AgentState;
match state { match state {
AgentState::Destroyed => true, AgentState::Destroyed => true,
AgentState::Up | AgentState::Offline | AgentState::Paused => false, AgentState::Up | AgentState::Offline | AgentState::Paused => false,
@ -423,7 +425,7 @@ pub(crate) async fn get_agents_status(
rows.iter().filter_map(|r| r.hive.clone()).collect(); rows.iter().filter_map(|r| r.hive.clone()).collect();
let mut declarations: std::collections::HashMap< let mut declarations: std::collections::HashMap<
String, String,
swarm_wire::wanted::HiveWanted, swarm_queue_client::wanted::HiveWanted,
> = std::collections::HashMap::new(); > = std::collections::HashMap::new();
for hive in hives { for hive in hives {
match writer.view(&hive).await { match writer.view(&hive).await {
@ -709,7 +711,7 @@ pub(crate) async fn create_agent(
/// The settings a `POST /api/agents` body gives, as they are stored. /// The settings a `POST /api/agents` body gives, as they are stored.
struct CreateSettings { struct CreateSettings {
limits: Option<swarm_wire::agent_limits::AgentCaps>, limits: Option<swarm_queue_client::agent_limits::AgentCaps>,
inference: Option<swarm_wire::agent_inference::AgentInference>, inference: Option<swarm_wire::agent_inference::AgentInference>,
permissions: Option<swarm_queue_client::agent_permissions::AgentPermissions>, permissions: Option<swarm_queue_client::agent_permissions::AgentPermissions>,
archetype: Option<swarm_queue_client::agent_archetype::ArchetypeRef>, archetype: Option<swarm_queue_client::agent_archetype::ArchetypeRef>,
@ -792,7 +794,7 @@ impl StoredSettings {
async fn store( async fn store(
client: &async_nats::Client, client: &async_nats::Client,
agent: &str, agent: &str,
limits: Option<&swarm_wire::agent_limits::AgentCaps>, limits: Option<&swarm_queue_client::agent_limits::AgentCaps>,
inference: Option<&swarm_wire::agent_inference::AgentInference>, inference: Option<&swarm_wire::agent_inference::AgentInference>,
permissions: Option<&swarm_queue_client::agent_permissions::AgentPermissions>, permissions: Option<&swarm_queue_client::agent_permissions::AgentPermissions>,
archetype: Option<&swarm_queue_client::agent_archetype::ArchetypeRef>, archetype: Option<&swarm_queue_client::agent_archetype::ArchetypeRef>,
@ -865,7 +867,9 @@ async fn placements_elsewhere<E, Fut>(
read_declared: impl FnOnce() -> Fut, read_declared: impl FnOnce() -> Fut,
) -> Result<Vec<String>, E> ) -> Result<Vec<String>, E>
where where
Fut: std::future::Future<Output = Result<Vec<(String, swarm_wire::wanted::HiveWanted)>, E>>, Fut: std::future::Future<
Output = Result<Vec<(String, swarm_queue_client::wanted::HiveWanted)>, E>,
>,
{ {
let queued = snapshot_queued(); let queued = snapshot_queued();
let declared = read_declared().await?; let declared = read_declared().await?;
@ -882,7 +886,8 @@ async fn declarations_elsewhere(
state: &AppState, state: &AppState,
agent: &str, agent: &str,
hive: &str, hive: &str,
) -> Result<Vec<(String, swarm_wire::wanted::HiveWanted)>, problem_details::ProblemDetails> { ) -> Result<Vec<(String, swarm_queue_client::wanted::HiveWanted)>, problem_details::ProblemDetails>
{
read_declarations(state, state.hives.iter().filter(|h| h.name != hive)) read_declarations(state, state.hives.iter().filter(|h| h.name != hive))
.await .await
.map_err(|(other, e)| { .map_err(|(other, e)| {
@ -900,7 +905,7 @@ async fn declarations_elsewhere(
pub(crate) async fn read_declarations<'a>( pub(crate) async fn read_declarations<'a>(
state: &AppState, state: &AppState,
hives: impl Iterator<Item = &'a HiveEntry>, hives: impl Iterator<Item = &'a HiveEntry>,
) -> Result<Vec<(String, swarm_wire::wanted::HiveWanted)>, (String, anyhow::Error)> { ) -> Result<Vec<(String, swarm_queue_client::wanted::HiveWanted)>, (String, anyhow::Error)> {
let Some(writer) = state.wanted.as_deref() else { let Some(writer) = state.wanted.as_deref() else {
return Ok(Vec::new()); return Ok(Vec::new());
}; };
@ -918,7 +923,7 @@ pub(crate) async fn read_declarations<'a>(
/// Every hive whose declaration places `agent` there. /// Every hive whose declaration places `agent` there.
pub(crate) fn claiming_hives( pub(crate) fn claiming_hives(
agent: &str, agent: &str,
declared: &[(String, swarm_wire::wanted::HiveWanted)], declared: &[(String, swarm_queue_client::wanted::HiveWanted)],
) -> Vec<String> { ) -> Vec<String> {
declared declared
.iter() .iter()
@ -966,7 +971,7 @@ fn queued_placements(
fn placed_elsewhere( fn placed_elsewhere(
agent: &str, agent: &str,
hive: &str, hive: &str,
declared: &[(String, swarm_wire::wanted::HiveWanted)], declared: &[(String, swarm_queue_client::wanted::HiveWanted)],
queued: &[(String, String)], queued: &[(String, String)],
) -> Vec<String> { ) -> Vec<String> {
let declared = declared.iter().filter(|(_, declaration)| { let declared = declared.iter().filter(|(_, declaration)| {
@ -1160,7 +1165,7 @@ pub(crate) async fn get_agent_icon(
[ [
( (
header::CONTENT_TYPE, header::CONTENT_TYPE,
swarm_wire::agent_icon::MEDIA_TYPE.to_owned(), swarm_queue_client::agent_icon::MEDIA_TYPE.to_owned(),
), ),
( (
header::CACHE_CONTROL, header::CACHE_CONTROL,
@ -1701,12 +1706,13 @@ mod tests {
pub(crate) fn declaring( pub(crate) fn declaring(
hive: &str, hive: &str,
agent: &str, agent: &str,
state: swarm_wire::wanted::AgentState, state: swarm_queue_client::wanted::AgentState,
) -> (String, swarm_wire::wanted::HiveWanted) { ) -> (String, swarm_queue_client::wanted::HiveWanted) {
let mut declaration = swarm_wire::wanted::HiveWanted::default(); let mut declaration = swarm_queue_client::wanted::HiveWanted::default();
declaration declaration.agents.insert(
.agents agent.to_owned(),
.insert(agent.to_owned(), swarm_wire::wanted::AgentWanted { state }); swarm_queue_client::wanted::AgentWanted { state },
);
(hive.to_owned(), declaration) (hive.to_owned(), declaration)
} }
@ -1715,7 +1721,7 @@ mod tests {
/// terminal and declared. Only the queue-first order catches it. /// terminal and declared. Only the queue-first order catches it.
#[tokio::test] #[tokio::test]
async fn a_placement_finishing_between_the_two_reads_is_still_caught() { async fn a_placement_finishing_between_the_two_reads_is_still_caught() {
use swarm_wire::wanted::AgentState; use swarm_queue_client::wanted::AgentState;
let reads = std::cell::Cell::new(0); let reads = std::cell::Cell::new(0);
let snapshot_queued = || { let snapshot_queued = || {
reads.set(reads.get() + 1); reads.set(reads.get() + 1);
@ -1744,7 +1750,7 @@ mod tests {
#[test] #[test]
fn a_live_declaration_on_another_hive_is_a_placement() { fn a_live_declaration_on_another_hive_is_a_placement() {
use swarm_wire::wanted::AgentState; use swarm_queue_client::wanted::AgentState;
for state in [AgentState::Up, AgentState::Offline, AgentState::Paused] { for state in [AgentState::Up, AgentState::Offline, AgentState::Paused] {
let declared = [declaring("sec0nd", "atlas", state)]; let declared = [declaring("sec0nd", "atlas", state)];
assert_eq!( assert_eq!(
@ -1761,14 +1767,14 @@ mod tests {
let declared = [declaring( let declared = [declaring(
"sec0nd", "sec0nd",
"atlas", "atlas",
swarm_wire::wanted::AgentState::Destroyed, swarm_queue_client::wanted::AgentState::Destroyed,
)]; )];
assert!(super::placed_elsewhere("atlas", "pr1ma", &declared, &[]).is_empty()); assert!(super::placed_elsewhere("atlas", "pr1ma", &declared, &[]).is_empty());
} }
#[test] #[test]
fn placements_on_the_requested_hive_or_of_other_names_do_not_count() { fn placements_on_the_requested_hive_or_of_other_names_do_not_count() {
use swarm_wire::wanted::AgentState; use swarm_queue_client::wanted::AgentState;
let declared = [ let declared = [
declaring("pr1ma", "atlas", AgentState::Up), declaring("pr1ma", "atlas", AgentState::Up),
declaring("sec0nd", "argus", AgentState::Up), declaring("sec0nd", "argus", AgentState::Up),
@ -1782,7 +1788,7 @@ mod tests {
#[test] #[test]
fn the_hive_placing_an_agent_claims_it_and_no_other_does() { fn the_hive_placing_an_agent_claims_it_and_no_other_does() {
use swarm_wire::wanted::AgentState; use swarm_queue_client::wanted::AgentState;
let declared = [ let declared = [
declaring("pr1ma", "atlas", AgentState::Paused), declaring("pr1ma", "atlas", AgentState::Paused),
declaring("sec0nd", "argus", AgentState::Up), declaring("sec0nd", "argus", AgentState::Up),
@ -1794,7 +1800,7 @@ mod tests {
#[test] #[test]
fn two_hives_placing_one_agent_both_claim_it() { fn two_hives_placing_one_agent_both_claim_it() {
use swarm_wire::wanted::AgentState; use swarm_queue_client::wanted::AgentState;
let declared = [ let declared = [
declaring("pr1ma", "atlas", AgentState::Up), declaring("pr1ma", "atlas", AgentState::Up),
declaring("sec0nd", "atlas", AgentState::Up), declaring("sec0nd", "atlas", AgentState::Up),
@ -1810,7 +1816,7 @@ mod tests {
/// state, `pr1ma`'s included. /// state, `pr1ma`'s included.
async fn refuse_on_pr1ma_with( async fn refuse_on_pr1ma_with(
agent: &str, agent: &str,
declared: Vec<(String, swarm_wire::wanted::HiveWanted)>, declared: Vec<(String, swarm_queue_client::wanted::HiveWanted)>,
roster: Result<bool, String>, roster: Result<bool, String>,
) -> Result<(), problem_details::ProblemDetails> { ) -> Result<(), problem_details::ProblemDetails> {
let here = declared let here = declared
@ -1832,15 +1838,15 @@ mod tests {
/// [`refuse_on_pr1ma_with`] for an `agent` the roster does not hold. /// [`refuse_on_pr1ma_with`] for an `agent` the roster does not hold.
async fn refuse_on_pr1ma( async fn refuse_on_pr1ma(
agent: &str, agent: &str,
declared: Vec<(String, swarm_wire::wanted::HiveWanted)>, declared: Vec<(String, swarm_queue_client::wanted::HiveWanted)>,
) -> Result<(), problem_details::ProblemDetails> { ) -> Result<(), problem_details::ProblemDetails> {
refuse_on_pr1ma_with(agent, declared, Ok(false)).await refuse_on_pr1ma_with(agent, declared, Ok(false)).await
} }
const PLACING: [swarm_wire::wanted::AgentState; 3] = [ const PLACING: [swarm_queue_client::wanted::AgentState; 3] = [
swarm_wire::wanted::AgentState::Up, swarm_queue_client::wanted::AgentState::Up,
swarm_wire::wanted::AgentState::Offline, swarm_queue_client::wanted::AgentState::Offline,
swarm_wire::wanted::AgentState::Paused, swarm_queue_client::wanted::AgentState::Paused,
]; ];
/// The handler checks exactly the placing states; a destroy is never /// The handler checks exactly the placing states; a destroy is never
@ -1851,7 +1857,7 @@ mod tests {
assert!(super::places_agent(state), "{state:?}"); assert!(super::places_agent(state), "{state:?}");
} }
assert!(!super::places_agent( assert!(!super::places_agent(
swarm_wire::wanted::AgentState::Destroyed swarm_queue_client::wanted::AgentState::Destroyed
)); ));
} }
@ -1914,7 +1920,7 @@ mod tests {
/// first declaration and its name is checked. /// first declaration and its name is checked.
#[tokio::test] #[tokio::test]
async fn a_destroyed_declaration_here_does_not_exempt_the_name() { async fn a_destroyed_declaration_here_does_not_exempt_the_name() {
use swarm_wire::wanted::AgentState; use swarm_queue_client::wanted::AgentState;
let err = refuse_on_pr1ma( let err = refuse_on_pr1ma(
"admin", "admin",
vec![declaring("pr1ma", "admin", AgentState::Destroyed)], vec![declaring("pr1ma", "admin", AgentState::Destroyed)],
@ -1947,7 +1953,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn a_first_declaration_of_a_name_placed_on_another_hive_is_refused() { async fn a_first_declaration_of_a_name_placed_on_another_hive_is_refused() {
use swarm_wire::wanted::AgentState; use swarm_queue_client::wanted::AgentState;
let err = refuse_on_pr1ma("atlas", vec![declaring("sec0nd", "atlas", AgentState::Up)]) let err = refuse_on_pr1ma("atlas", vec![declaring("sec0nd", "atlas", AgentState::Up)])
.await .await
.expect_err("a name placed on another hive must be refused"); .expect_err("a name placed on another hive must be refused");
@ -1970,7 +1976,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn changing_the_state_of_an_agent_on_its_own_hive_is_accepted() { async fn changing_the_state_of_an_agent_on_its_own_hive_is_accepted() {
use swarm_wire::wanted::AgentState; use swarm_queue_client::wanted::AgentState;
refuse_on_pr1ma("atlas", vec![declaring("pr1ma", "atlas", AgentState::Up)]) refuse_on_pr1ma("atlas", vec![declaring("pr1ma", "atlas", AgentState::Up)])
.await .await
.expect("an agent's own hive must accept a new state for it"); .expect("an agent's own hive must accept a new state for it");
@ -1980,7 +1986,7 @@ mod tests {
/// connected and with no identity bridge. /// connected and with no identity bridge.
async fn set_on_disconnected_pr1ma( async fn set_on_disconnected_pr1ma(
agent: &str, agent: &str,
wanted_state: swarm_wire::wanted::AgentState, wanted_state: swarm_queue_client::wanted::AgentState,
) -> problem_details::ProblemDetails { ) -> problem_details::ProblemDetails {
let (state, _sched) = state_with_two_hives(); let (state, _sched) = state_with_two_hives();
let state = crate::routes::AppState { let state = crate::routes::AppState {
@ -2004,7 +2010,8 @@ mod tests {
/// declaration before it is written, as it refuses creation. /// declaration before it is written, as it refuses creation.
#[tokio::test] #[tokio::test]
async fn set_agent_state_refuses_when_the_wanted_state_is_unreadable() { async fn set_agent_state_refuses_when_the_wanted_state_is_unreadable() {
let err = set_on_disconnected_pr1ma("atlas", swarm_wire::wanted::AgentState::Up).await; let err =
set_on_disconnected_pr1ma("atlas", swarm_queue_client::wanted::AgentState::Up).await;
assert_eq!( assert_eq!(
err.status, err.status,
Some(axum::http::StatusCode::SERVICE_UNAVAILABLE), Some(axum::http::StatusCode::SERVICE_UNAVAILABLE),
@ -2022,7 +2029,8 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn set_agent_state_does_not_check_a_destroy() { async fn set_agent_state_does_not_check_a_destroy() {
let err = let err =
set_on_disconnected_pr1ma("admin", swarm_wire::wanted::AgentState::Destroyed).await; set_on_disconnected_pr1ma("admin", swarm_queue_client::wanted::AgentState::Destroyed)
.await;
assert_eq!( assert_eq!(
err.status, err.status,
Some(axum::http::StatusCode::SERVICE_UNAVAILABLE), Some(axum::http::StatusCode::SERVICE_UNAVAILABLE),
@ -2189,7 +2197,7 @@ mod tests {
#[test] #[test]
fn declaring_an_agent_destroyed_revokes_its_queue_credential() { fn declaring_an_agent_destroyed_revokes_its_queue_credential() {
assert!(super::revokes_queue_credential( assert!(super::revokes_queue_credential(
swarm_wire::wanted::AgentState::Destroyed swarm_queue_client::wanted::AgentState::Destroyed
)); ));
} }
@ -2199,7 +2207,7 @@ mod tests {
/// a state added later shows up here as well as in the match itself. /// a state added later shows up here as well as in the match itself.
#[test] #[test]
fn no_state_an_agent_comes_back_from_revokes() { fn no_state_an_agent_comes_back_from_revokes() {
use swarm_wire::wanted::AgentState; use swarm_queue_client::wanted::AgentState;
for state in [AgentState::Up, AgentState::Offline, AgentState::Paused] { for state in [AgentState::Up, AgentState::Offline, AgentState::Paused] {
assert!( assert!(
!super::revokes_queue_credential(state), !super::revokes_queue_credential(state),
@ -2342,7 +2350,7 @@ mod tests {
axum::extract::State(state), axum::extract::State(state),
axum::extract::Path(("pr1ma".to_owned(), "atlas".to_owned())), axum::extract::Path(("pr1ma".to_owned(), "atlas".to_owned())),
axum::Json(SetAgentStateRequest { axum::Json(SetAgentStateRequest {
state: swarm_wire::wanted::AgentState::Paused, state: swarm_queue_client::wanted::AgentState::Paused,
}), }),
) )
.await; .await;
@ -2387,7 +2395,7 @@ mod tests {
axum::extract::State(state), axum::extract::State(state),
axum::extract::Path(("not-a-hive".to_owned(), "atlas".to_owned())), axum::extract::Path(("not-a-hive".to_owned(), "atlas".to_owned())),
axum::Json(SetAgentStateRequest { axum::Json(SetAgentStateRequest {
state: swarm_wire::wanted::AgentState::Paused, state: swarm_queue_client::wanted::AgentState::Paused,
}), }),
) )
.await; .await;

View file

@ -7,7 +7,7 @@
//! schedule exists for all of its agents or for none of them. //! schedule exists for all of its agents or for none of them.
//! //!
//! Each agent reads only its own key of `agent-schedules` //! Each agent reads only its own key of `agent-schedules`
//! (`swarm_wire::agent_schedules`), which holds the schedules naming //! (`swarm_queue_client::agent_schedules`), which holds the schedules naming
//! it, without their names or other agents. That key is a projection of the //! it, without their names or other agents. That key is a projection of the
//! definitions: after a change this daemon rewrites the key of each agent the //! definitions: after a change this daemon rewrites the key of each agent the
//! change touched, and [`spawn_reconcile`] rewrites every agent's key every //! change touched, and [`spawn_reconcile`] rewrites every agent's key every
@ -30,8 +30,7 @@ use serde::{Deserialize, Serialize};
use swarm_controller_api::schedules::{ use swarm_controller_api::schedules::{
AddScheduleRequest, EditScheduleRequest, IntervalEdit, ScheduleView, AddScheduleRequest, EditScheduleRequest, IntervalEdit, ScheduleView,
}; };
use swarm_queue_client::agent_schedules; use swarm_queue_client::agent_schedules::{self, AgentSchedules, Schedule};
use swarm_wire::agent_schedules::{AgentSchedules, Schedule};
use crate::agent_renewal::unix_now; use crate::agent_renewal::unix_now;
use crate::routes::{AppState, error_problem}; use crate::routes::{AppState, error_problem};

Some files were not shown because too many files have changed in this diff Show more