manta_server/server/handlers/
power.rs

1//! Power endpoints.
2//!
3//! - `POST /api/v1/power` starts a PCS power transition and returns
4//!   immediately with `{ transition_id, operation }`. The CLI then
5//!   polls the next endpoint until the transition reports `completed`.
6//! - `GET /api/v1/power/transitions/{id}` returns the current snapshot
7//!   of the named transition (status + task counts + per-task detail).
8
9use axum::{Json, extract::Path, http::StatusCode, response::IntoResponse};
10
11use super::{ErrorResponse, RequestCtx, SiteHeader, to_handler_error};
12use crate::service;
13
14// ---------------------------------------------------------------------------
15// POST /api/v1/power — Power on/off/reset nodes or cluster
16// ---------------------------------------------------------------------------
17
18pub use manta_shared::types::api::power::{
19  PowerAction, PowerRequest, PowerTargetType,
20};
21
22/// `POST /api/v1/power` — start a PCS power transition (on / off /
23/// reset) against nodes or all members of a cluster and return the
24/// transition id **immediately**. Does not block until the
25/// transition completes — the CLI is responsible for polling
26/// `GET /power/transitions/{id}` until the snapshot reports
27/// `transitionStatus = "completed"`.
28///
29/// Returns a `TransitionStartOutput` (`{ transitionID, operation }`)
30/// as JSON. Callers can hand the `transitionID` to
31/// [`get_power_transition`].
32#[utoipa::path(post, path = "/power", tag = "power",
33  params(SiteHeader),
34  request_body = PowerRequest,
35  security(("bearerAuth" = [])),
36  responses(
37    // TransitionStartOutput lives in manta-backend-dispatcher (third-party,
38    // no ToSchema) — kept as Value until upstream derives it.
39    (status = 200, description = "PCS transition started; returns TransitionStartOutput", body = serde_json::Value),
40    (status = 400, description = "Bad request",            body = ErrorResponse),
41    (status = 401, description = "Unauthorized",           body = ErrorResponse),
42    (status = 500, description = "Internal error",         body = ErrorResponse),
43  )
44)]
45#[tracing::instrument(skip_all)]
46pub async fn post_power(
47  ctx: RequestCtx,
48  Json(body): Json<PowerRequest>,
49) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
50  tracing::info!(
51    "post_power action={:?} target_type={:?}",
52    body.action,
53    body.target_type
54  );
55  let infra = ctx.infra();
56
57  let xnames = service::power::resolve_target_xnames(
58    &infra,
59    &ctx.token,
60    body.target_type,
61    &body.host_expression,
62  )
63  .await
64  .map_err(to_handler_error)?;
65
66  let params = service::power::ApplyPowerParams {
67    action: body.action,
68    xnames,
69    force: body.force,
70  };
71  let result = service::power::apply_power(&infra, &ctx.token, &params)
72    .await
73    .map_err(to_handler_error)?;
74
75  Ok(Json(result))
76}
77
78// ---------------------------------------------------------------------------
79// GET /api/v1/power/transitions/{id} — Snapshot a PCS transition
80// ---------------------------------------------------------------------------
81
82/// `GET /api/v1/power/transitions/{id}` — fetch the current snapshot
83/// of a PCS power transition (status, task counts, per-task detail).
84/// Called by the CLI's poll loop after `POST /power` returns the id.
85#[utoipa::path(get, path = "/power/transitions/{id}", tag = "power",
86  params(SiteHeader),
87  security(("bearerAuth" = [])),
88  responses(
89    // TransitionResponse lives in manta-backend-dispatcher (third-party,
90    // no ToSchema) — kept as Value until upstream derives it.
91    (status = 200, description = "Transition snapshot",  body = serde_json::Value),
92    (status = 401, description = "Unauthorized",         body = ErrorResponse),
93    (status = 404, description = "Unknown transition id",body = ErrorResponse),
94    (status = 500, description = "Internal error",       body = ErrorResponse),
95  )
96)]
97#[tracing::instrument(skip_all)]
98pub async fn get_power_transition(
99  ctx: RequestCtx,
100  Path(id): Path<String>,
101) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
102  tracing::debug!("get_power_transition id={id}");
103  let infra = ctx.infra();
104  let snapshot = service::power::get_power_transition(&infra, &ctx.token, &id)
105    .await
106    .map_err(to_handler_error)?;
107  Ok(Json(snapshot))
108}