manta_server/server/handlers/
node.rs

1//! Node CRUD handlers.
2//!
3//! - `GET    /api/v1/nodes`        → [`get_nodes`] — wraps
4//!   `service::node::get_nodes`.
5//! - `POST   /api/v1/nodes`        → [`add_node`].
6//! - `DELETE /api/v1/nodes/{id}`   → [`delete_node`].
7//!
8//! `get_nodes` accepts an xname expression query parameter and an
9//! optional `status` filter; the `include_siblings` flag fans out
10//! to neighbour nodes sharing the same chassis.
11
12use axum::{
13  Json,
14  extract::{Path, Query},
15  http::StatusCode,
16  response::IntoResponse,
17};
18
19use super::{ErrorResponse, RequestCtx, SiteHeader, to_handler_error};
20use crate::service;
21
22// ---------------------------------------------------------------------------
23// GET /api/v1/nodes
24// ---------------------------------------------------------------------------
25
26pub use manta_shared::types::api::queries::NodesQuery;
27
28/// GET /nodes — fetch node details for a given xname expression.
29#[utoipa::path(get, path = "/nodes", tag = "nodes",
30  params(NodesQuery, SiteHeader),
31  security(("bearerAuth" = [])),
32  responses(
33    (status = 200, description = "Node details",  body = Vec<manta_shared::types::dto::NodeDetails>),
34    (status = 400, description = "Bad request",   body = ErrorResponse),
35    (status = 401, description = "Unauthorized",  body = ErrorResponse),
36    (status = 500, description = "Internal error", body = ErrorResponse),
37  )
38)]
39#[tracing::instrument(skip_all)]
40pub async fn get_nodes(
41  ctx: RequestCtx,
42  Query(q): Query<NodesQuery>,
43) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
44  let infra = ctx.infra();
45
46  let params = service::node::GetNodesParams {
47    host_expression: q.xname,
48    include_siblings: q.include_siblings.unwrap_or(false),
49    status_filter: q.status,
50  };
51
52  let nodes = service::node::get_nodes(&infra, &ctx.token, &params)
53    .await
54    .map_err(to_handler_error)?;
55
56  Ok(Json(nodes))
57}
58
59// ---------------------------------------------------------------------------
60// DELETE /api/v1/nodes/{id}
61// ---------------------------------------------------------------------------
62
63/// DELETE /nodes/{id} — remove a node from HSM by xname or NID.
64#[utoipa::path(delete, path = "/nodes/{id}", tag = "nodes",
65  params(("id" = String, Path, description = "Node xname or NID"), SiteHeader),
66  security(("bearerAuth" = [])),
67  responses(
68    (status = 204, description = "Node removed"),
69    (status = 401, description = "Unauthorized", body = ErrorResponse),
70    (status = 404, description = "Not found",    body = ErrorResponse),
71    (status = 500, description = "Internal error", body = ErrorResponse),
72  )
73)]
74#[tracing::instrument(skip_all)]
75pub async fn delete_node(
76  ctx: RequestCtx,
77  Path(id): Path<String>,
78) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
79  tracing::info!("delete_node id={}", id);
80  let infra = ctx.infra();
81
82  service::node::delete_node(&infra, &ctx.token, &id)
83    .await
84    .map_err(to_handler_error)?;
85
86  Ok(StatusCode::NO_CONTENT)
87}
88
89// ---------------------------------------------------------------------------
90// POST /api/v1/nodes
91// ---------------------------------------------------------------------------
92
93pub use manta_shared::types::api::node::AddNodeRequest;
94
95/// POST /nodes — register a new node in HSM and add it to a group.
96#[utoipa::path(post, path = "/nodes", tag = "nodes",
97  params(SiteHeader),
98  request_body = AddNodeRequest,
99  security(("bearerAuth" = [])),
100  responses(
101    (status = 201, description = "Node registered",  body = manta_shared::types::api::responses::AddNodeResponse),
102    (status = 400, description = "Bad request",      body = ErrorResponse),
103    (status = 401, description = "Unauthorized",     body = ErrorResponse),
104    (status = 500, description = "Internal error",   body = ErrorResponse),
105  )
106)]
107#[tracing::instrument(skip_all)]
108pub async fn add_node(
109  ctx: RequestCtx,
110  Json(body): Json<AddNodeRequest>,
111) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
112  tracing::info!("add_node id={} group={}", body.id, body.group);
113  let infra = ctx.infra();
114
115  service::node::add_node(
116    &infra,
117    &ctx.token,
118    &body.id,
119    &body.group,
120    body.enabled,
121    body.arch,
122    None, // hardware_file_path not applicable via HTTP
123  )
124  .await
125  .map_err(to_handler_error)?;
126
127  Ok((
128    StatusCode::CREATED,
129    Json(serde_json::json!({ "id": body.id })),
130  ))
131}