hive-c0re: the per-agent resource limit override is gone
The hive no longer sets an agent's CPU or memory cap: caps come from the swarm, else the hive-wide defaults. The container load tab was the only hive UI that set them, and its figures are already exported as OTEL metrics, so with the tab gone nothing wrote meta/resource-limits.json.
Removed: the file's reader and writer, the `hivectl agent set-limits` verb with its host-socket request, the `GET /api/container-resources` and `POST /api/resource-limits/{name}` dashboard endpoints, and the `cpu_quota`/`memory_max` fields of the container view that only the tab read. A leftover resource-limits.json is ignored.
This commit is contained in:
parent
a2540be98b
commit
90e33cbd19
19 changed files with 161 additions and 828 deletions
|
|
@ -311,95 +311,6 @@ fn set_paused_error_response(
|
|||
}
|
||||
}
|
||||
|
||||
/// Form fields for `post_resource_limits`. Both fields are optional strings;
|
||||
/// an empty value clears the per-agent override for that field, falling back
|
||||
/// to the hive-wide default.
|
||||
#[derive(Deserialize, Default, ToSchema)]
|
||||
pub(super) struct ResourceLimitsForm {
|
||||
#[serde(default)]
|
||||
cpu_quota: String,
|
||||
#[serde(default)]
|
||||
memory_max: String,
|
||||
}
|
||||
|
||||
/// Write per-agent CPU/memory limit
|
||||
/// overrides for `name`.
|
||||
///
|
||||
/// An empty `cpu_quota` or `memory_max` field clears that field's override,
|
||||
/// falling back to the hive-wide default. Both empty together removes the
|
||||
/// agent's entry entirely. The new drop-in is written immediately — the
|
||||
/// limits take effect on the next container start or restart. Triggers an
|
||||
/// immediate rescan so `ContainerView.cpu_quota`/`memory_max` update on
|
||||
/// the dashboard via SSE without waiting for the next periodic sweep.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/resource-limits/{name}",
|
||||
params(("name" = String, Path, description = "agent name")),
|
||||
request_body(content = ResourceLimitsForm, content_type = "application/x-www-form-urlencoded"),
|
||||
responses(
|
||||
(status = 200, description = "limits written", body = String),
|
||||
(status = 400, description = "bad agent name"),
|
||||
(status = 404, description = "no such agent"),
|
||||
(status = 422, description = "invalid cpu_quota/memory_max value"),
|
||||
(status = 500, description = "commit or drop-in write failed"),
|
||||
),
|
||||
tag = "lifecycle_ops"
|
||||
)]
|
||||
pub(super) async fn post_resource_limits(
|
||||
State(state): State<AppState>,
|
||||
AxumPath(name): AxumPath<String>,
|
||||
Form(form): Form<ResourceLimitsForm>,
|
||||
) -> Response {
|
||||
let logical = strip_container_prefix(&name);
|
||||
if let Some(reject) = guard_agent_name(&state, &logical).await {
|
||||
return reject;
|
||||
}
|
||||
let ident = match Ident::parse(&logical) {
|
||||
Ok(i) => i,
|
||||
Err(e) => return (StatusCode::BAD_REQUEST, format!("bad agent name: {e}")).into_response(),
|
||||
};
|
||||
let cpu_quota = if form.cpu_quota.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(form.cpu_quota.as_str())
|
||||
};
|
||||
let memory_max = if form.memory_max.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(form.memory_max.as_str())
|
||||
};
|
||||
if let Some(v) = cpu_quota
|
||||
&& let Err(e) = crate::resource_limits::validate_cpu_quota(v)
|
||||
{
|
||||
return (StatusCode::UNPROCESSABLE_ENTITY, e).into_response();
|
||||
}
|
||||
// Returns the value to store, not just an ok/err verdict — see
|
||||
// `validate_memory_max`'s own doc for why that isn't always `v`.
|
||||
let memory_max = match memory_max.map(crate::resource_limits::validate_memory_max) {
|
||||
Some(Err(e)) => return (StatusCode::UNPROCESSABLE_ENTITY, e).into_response(),
|
||||
Some(Ok(v)) => Some(v),
|
||||
None => None,
|
||||
};
|
||||
let limits = crate::resource_limits::AgentLimits {
|
||||
cpu_quota: cpu_quota.map(str::to_owned),
|
||||
memory_max,
|
||||
};
|
||||
if let Err(e) = crate::meta::commit_resource_limits(ident.as_str(), &limits).await {
|
||||
return error_response(&format!("set limits {logical}: {e:#}"));
|
||||
}
|
||||
let agent_dir = crate::paths::agent_runtime_dir(ident.as_str());
|
||||
let hive = state.coord.hive_env();
|
||||
let paths = match crate::coordinator::Coordinator::agent_paths(ident.as_str(), agent_dir) {
|
||||
Ok(p) => p,
|
||||
Err(e) => return error_response(&format!("write_dropins {logical}: {e:#}")),
|
||||
};
|
||||
if let Err(e) = crate::lifecycle::write_dropins(ident.as_str(), &hive, &paths).await {
|
||||
return error_response(&format!("write_dropins {logical}: {e:#}"));
|
||||
}
|
||||
state.coord.rescan_containers_and_emit().await;
|
||||
(StatusCode::OK, "ok").into_response()
|
||||
}
|
||||
|
||||
/// Queue a rebuild DAG for every live agent
|
||||
/// container.
|
||||
#[utoipa::path(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
//! Remaining single-endpoint dashboard handlers: mark-all-read, operator
|
||||
//! compose (`op-send`), spawn-request, hive-wide turn stats, and container
|
||||
//! resources.
|
||||
//! compose (`op-send`), spawn-request and hive-wide turn stats.
|
||||
|
||||
use axum::{
|
||||
extract::{Form, Path as AxumPath, State},
|
||||
|
|
@ -11,7 +10,6 @@ use serde::{Deserialize, Serialize};
|
|||
use utoipa::{IntoParams, ToSchema};
|
||||
|
||||
use super::{AppState, Ident, error_response};
|
||||
use crate::container_stats::ContainerResource;
|
||||
use crate::hive_stats::HiveStats;
|
||||
|
||||
#[derive(Deserialize, IntoParams)]
|
||||
|
|
@ -43,20 +41,6 @@ pub(super) async fn api_stats_hive(
|
|||
.into_response()
|
||||
}
|
||||
|
||||
/// Live per-agent-container CPU + memory load from cgroup v2.
|
||||
///
|
||||
/// Samples CPU over a short interval (~200 ms), so this call briefly
|
||||
/// awaits.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/container-resources",
|
||||
responses((status = 200, description = "live per-container CPU + memory load", body = Vec<ContainerResource>)),
|
||||
tag = "misc_api"
|
||||
)]
|
||||
pub(super) async fn api_container_resources() -> Response {
|
||||
axum::Json(crate::container_stats::gather().await).into_response()
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub(super) struct MarkAllReadBody {
|
||||
marked: u64,
|
||||
|
|
|
|||
|
|
@ -116,7 +116,6 @@ fn app(coord: Arc<Coordinator>) -> axum::Router {
|
|||
.routes(routes!(state_snapshot::api_state))
|
||||
.routes(routes!(state_files::get_state_file))
|
||||
.routes(routes!(misc_api::api_stats_hive))
|
||||
.routes(routes!(misc_api::api_container_resources))
|
||||
.routes(routes!(misc_api::post_mark_all_read))
|
||||
.routes(routes!(misc_api::post_op_send))
|
||||
.routes(routes!(build_logs::get_build_logs_all))
|
||||
|
|
@ -140,7 +139,6 @@ fn app(coord: Arc<Coordinator>) -> axum::Router {
|
|||
.routes(routes!(lifecycle_ops::post_rebuild))
|
||||
.routes(routes!(lifecycle_ops::post_pause))
|
||||
.routes(routes!(lifecycle_ops::post_resume))
|
||||
.routes(routes!(lifecycle_ops::post_resource_limits))
|
||||
.routes(routes!(lifecycle_ops::post_update_all))
|
||||
.routes(routes!(tombstones::post_purge_tombstone))
|
||||
.routes(routes!(meta_inputs::post_meta_update))
|
||||
|
|
@ -372,7 +370,6 @@ mod router_build_probe {
|
|||
.routes(routes!(state_snapshot::api_state))
|
||||
.routes(routes!(state_files::get_state_file))
|
||||
.routes(routes!(misc_api::api_stats_hive))
|
||||
.routes(routes!(misc_api::api_container_resources))
|
||||
.routes(routes!(misc_api::post_mark_all_read))
|
||||
.routes(routes!(misc_api::post_op_send))
|
||||
.routes(routes!(build_logs::get_build_logs_all))
|
||||
|
|
@ -396,7 +393,6 @@ mod router_build_probe {
|
|||
.routes(routes!(lifecycle_ops::post_rebuild))
|
||||
.routes(routes!(lifecycle_ops::post_pause))
|
||||
.routes(routes!(lifecycle_ops::post_resume))
|
||||
.routes(routes!(lifecycle_ops::post_resource_limits))
|
||||
.routes(routes!(lifecycle_ops::post_update_all))
|
||||
.routes(routes!(tombstones::post_purge_tombstone))
|
||||
.routes(routes!(meta_inputs::post_meta_update))
|
||||
|
|
|
|||
|
|
@ -603,8 +603,6 @@ mod tests {
|
|||
status_text: None,
|
||||
status_set_at: None,
|
||||
paused: false,
|
||||
cpu_quota: "200%".to_owned(),
|
||||
memory_max: "4G".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue