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