Compare commits
42 changed files with 886 additions and 903 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
||||||
|
|
|
||||||
|
|
@ -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};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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!(
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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>)]
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
Loading…
Reference in a new issue