swarmctl shares the create-agent, inference and permissions shapes with the controller
This commit is contained in:
parent
249aab6642
commit
0831b85b73
1 changed files with 675 additions and 434 deletions
|
|
@ -15,10 +15,9 @@ use axum::Json;
|
|||
use axum::extract::{Path, State};
|
||||
use axum::http::StatusCode;
|
||||
use problem_details::ProblemDetails;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use swarm_controller_api::limits::Caps;
|
||||
use swarm_queue_client::agent_limits;
|
||||
use swarm_wire::agent_limits::AgentCaps;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::routes::{AppState, error_problem, wanted_error_status};
|
||||
use crate::schedules::{live, queue, read_or_default, refused, write_revision};
|
||||
|
|
@ -27,45 +26,19 @@ use crate::schedules::{live, queue, read_or_default, refused, write_revision};
|
|||
/// `wanted::MAX_ATTEMPTS`.
|
||||
const MAX_ATTEMPTS: usize = 5;
|
||||
|
||||
/// One agent's caps. A field left out is not capped by the swarm.
|
||||
///
|
||||
/// Each value is stored as given, and only in a form systemd applies: a value
|
||||
/// systemd ignores ("Invalid …, ignoring") would leave the agent uncapped,
|
||||
/// since a swarm cap replaces the hive's answer.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Caps {
|
||||
/// systemd `CPUQuota=`: digits, optionally `.` and one or two digits, then
|
||||
/// `%`; above zero and at most `21474836.47%`. `"100%"` is one core.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[schema(example = "400%")]
|
||||
cpu_quota: Option<String>,
|
||||
/// systemd `MemoryMax=`: `infinity`; a percentage of physical memory
|
||||
/// written as for `cpu_quota`, above zero and at most `100%`; or a size:
|
||||
/// digits, optionally `.` and digits, then nothing, `B` or one of
|
||||
/// `K`/`M`/`G`/`T` (1024-based, upper case), at least one byte and below
|
||||
/// 2^64 - 1 bytes.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[schema(example = "8G")]
|
||||
memory_max: Option<String>,
|
||||
/// The caps to store, or why they are refused.
|
||||
pub(crate) fn into_stored(caps: &Caps) -> Result<AgentCaps, String> {
|
||||
Ok(AgentCaps {
|
||||
cpu_quota: caps.cpu_quota.as_deref().map(cpu_quota).transpose()?,
|
||||
memory_max: caps.memory_max.as_deref().map(memory_max).transpose()?,
|
||||
})
|
||||
}
|
||||
|
||||
impl Caps {
|
||||
/// The value to store, or why it is refused.
|
||||
pub(crate) fn into_stored(self) -> Result<AgentCaps, String> {
|
||||
Ok(AgentCaps {
|
||||
cpu_quota: self.cpu_quota.as_deref().map(cpu_quota).transpose()?,
|
||||
memory_max: self.memory_max.as_deref().map(memory_max).transpose()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AgentCaps> for Caps {
|
||||
fn from(v: AgentCaps) -> Self {
|
||||
Self {
|
||||
cpu_quota: v.cpu_quota,
|
||||
memory_max: v.memory_max,
|
||||
}
|
||||
/// The stored caps as the API shows them.
|
||||
fn view(v: AgentCaps) -> Caps {
|
||||
Caps {
|
||||
cpu_quota: v.cpu_quota,
|
||||
memory_max: v.memory_max,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -213,7 +186,7 @@ pub(crate) async fn get_agent_limits(
|
|||
let (current, _, _) = read_or_default::<AgentCaps>(&store, &agent)
|
||||
.await
|
||||
.map_err(|e| queue_problem(&e))?;
|
||||
Ok(Json(current.into()))
|
||||
Ok(Json(view(current)))
|
||||
}
|
||||
|
||||
/// Set the CPU and memory caps the swarm declares for one agent.
|
||||
|
|
@ -244,9 +217,7 @@ pub(crate) async fn put_agent_limits(
|
|||
Json(req): Json<Caps>,
|
||||
) -> Result<Json<Caps>, ProblemDetails> {
|
||||
let agent = agent_name(&name).map_err(refused)?;
|
||||
let value = req
|
||||
.into_stored()
|
||||
.map_err(|why| error_problem(StatusCode::BAD_REQUEST, &why))?;
|
||||
let value = into_stored(&req).map_err(|why| error_problem(StatusCode::BAD_REQUEST, &why))?;
|
||||
let client = queue(&state).map_err(refused)?;
|
||||
if !live(&state).await?.contains(&agent) {
|
||||
return Err(error_problem(
|
||||
|
|
@ -255,7 +226,7 @@ pub(crate) async fn put_agent_limits(
|
|||
));
|
||||
}
|
||||
store(&client, &agent, &value).await?;
|
||||
Ok(Json(value.into()))
|
||||
Ok(Json(view(value)))
|
||||
}
|
||||
|
||||
/// What [`store`] replaced, or `None` when it wrote nothing because the caps
|
||||
|
|
@ -785,10 +756,7 @@ pub(crate) mod tests {
|
|||
let nats = TestNats::start();
|
||||
let client = nats.client().await;
|
||||
let caps = |json: &str| {
|
||||
serde_json::from_str::<Caps>(json)
|
||||
.expect("caps")
|
||||
.into_stored()
|
||||
.expect("valid")
|
||||
super::into_stored(&serde_json::from_str::<Caps>(json).expect("caps")).expect("valid")
|
||||
};
|
||||
|
||||
let first = super::store(&client, "atlas", &caps(CAPPED))
|
||||
|
|
@ -814,10 +782,7 @@ pub(crate) mod tests {
|
|||
let nats = TestNats::start();
|
||||
let client = nats.client().await;
|
||||
let caps = |json: &str| {
|
||||
serde_json::from_str::<Caps>(json)
|
||||
.expect("caps")
|
||||
.into_stored()
|
||||
.expect("valid")
|
||||
super::into_stored(&serde_json::from_str::<Caps>(json).expect("caps")).expect("valid")
|
||||
};
|
||||
let other = caps(r#"{"cpu_quota":"100%"}"#);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue