manta_server/server/handlers/
kernel_parameters.rs

1//! Kernel-parameter handlers.
2//!
3//! - `GET    /api/v1/kernel-parameters`         → [`get_kernel_parameters`]
4//! - `POST   /api/v1/kernel-parameters/apply`   → [`apply_kernel_parameters`]
5//! - `POST   /api/v1/kernel-parameters/add`     → [`add_kernel_parameters`]
6//! - `DELETE /api/v1/kernel-parameters`         → [`delete_kernel_parameters`]
7//!
8//! All wrap `crate::service::kernel_parameters::*`. Target nodes can
9//! be supplied as an xname expression or an HSM group name; the
10//! shared [`super::resolve_xnames_from_request`] helper does the
11//! resolution.
12
13use axum::{Json, extract::Query, http::StatusCode, response::IntoResponse};
14
15use super::{
16  ErrorResponse, RequestCtx, SiteHeader, resolve_xnames_from_request,
17  serialize_or_500, to_handler_error,
18};
19use crate::service;
20
21// ---------------------------------------------------------------------------
22// GET /api/v1/kernel-parameters
23// ---------------------------------------------------------------------------
24
25pub use manta_shared::types::api::queries::KernelParametersQuery;
26
27/// GET /kernel-parameters — fetch BSS kernel parameters for a group or node list.
28#[utoipa::path(get, path = "/kernel-parameters", tag = "kernel-parameters",
29  params(KernelParametersQuery, SiteHeader),
30  security(("bearerAuth" = [])),
31  responses(
32    (status = 200, description = "Kernel parameters", body = Vec<manta_backend_dispatcher::types::bss::BootParameters>),
33    (status = 401, description = "Unauthorized",      body = ErrorResponse),
34    (status = 500, description = "Internal error",    body = ErrorResponse),
35  )
36)]
37#[tracing::instrument(skip_all)]
38pub async fn get_kernel_parameters(
39  ctx: RequestCtx,
40  Query(q): Query<KernelParametersQuery>,
41) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
42  let infra = ctx.infra();
43
44  let params = service::kernel_parameters::GetKernelParametersParams {
45    group_name: q.hsm_group,
46    nodes: q.nodes,
47    settings_group_name: None,
48  };
49
50  let kernel_params = service::kernel_parameters::get_kernel_parameters(
51    &infra, &ctx.token, &params,
52  )
53  .await
54  .map_err(to_handler_error)?;
55
56  Ok(Json(kernel_params))
57}
58
59// ---------------------------------------------------------------------------
60// POST /api/v1/kernel-parameters/apply — Apply kernel parameter changes
61// ---------------------------------------------------------------------------
62
63pub use manta_shared::types::api::kernel_parameters::{
64  ApplyKernelParametersRequest, KernelParamOp,
65};
66
67/// `POST /api/v1/kernel-parameters/apply` — add, replace, or delete kernel parameters on nodes.
68#[utoipa::path(post, path = "/kernel-parameters/apply", tag = "kernel-parameters",
69  params(SiteHeader),
70  request_body = ApplyKernelParametersRequest,
71  security(("bearerAuth" = [])),
72  responses(
73    // dry_run/real result union — kept as Value until the union shape is formalised
74    (status = 200, description = "Kernel parameters applied or preview", body = serde_json::Value),
75    (status = 400, description = "Bad request",                          body = ErrorResponse),
76    (status = 401, description = "Unauthorized",                         body = ErrorResponse),
77    (status = 500, description = "Internal error",                       body = ErrorResponse),
78  )
79)]
80#[tracing::instrument(skip_all)]
81pub async fn apply_kernel_parameters(
82  ctx: RequestCtx,
83  Json(body): Json<ApplyKernelParametersRequest>,
84) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
85  let infra = ctx.infra();
86
87  let xnames = resolve_xnames_from_request(
88    &infra,
89    &ctx.token,
90    body.xnames_expression.as_deref(),
91    body.hsm_group.as_deref(),
92  )
93  .await?;
94
95  tracing::info!(
96    "apply_kernel_parameters xnames={:?} op={:?} dry_run={}",
97    xnames,
98    body.operation,
99    body.dry_run
100  );
101
102  let operation = match body.operation {
103    KernelParamOp::Add => {
104      service::kernel_parameters::KernelParamOperation::Add {
105        params: &body.params,
106        overwrite: body.overwrite,
107      }
108    }
109    KernelParamOp::Apply => {
110      service::kernel_parameters::KernelParamOperation::Apply {
111        params: &body.params,
112      }
113    }
114    KernelParamOp::Delete => {
115      service::kernel_parameters::KernelParamOperation::Delete {
116        params: &body.params,
117      }
118    }
119  };
120
121  let changeset = service::kernel_parameters::prepare_kernel_params_changes(
122    &infra, &ctx.token, &xnames, &operation,
123  )
124  .await
125  .map_err(to_handler_error)?;
126
127  if body.dry_run {
128    return Ok((StatusCode::OK, Json(serialize_or_500(&changeset)?)));
129  }
130
131  let images_to_project = service::kernel_parameters::build_images_to_project(
132    &changeset,
133    body.project_sbps,
134  );
135
136  service::kernel_parameters::apply_kernel_params_changes(
137    &infra,
138    &ctx.token,
139    &changeset,
140    &images_to_project,
141  )
142  .await
143  .map_err(to_handler_error)?;
144
145  Ok((
146    StatusCode::OK,
147    Json(serde_json::json!({
148      "applied": true,
149      "has_changes": changeset.has_changes,
150      "xnames_to_reboot": changeset.xnames_to_reboot,
151    })),
152  ))
153}
154
155// ---------------------------------------------------------------------------
156// POST /api/v1/kernel-parameters/add
157// ---------------------------------------------------------------------------
158
159pub use manta_shared::types::api::kernel_parameters::AddKernelParametersRequest;
160
161/// `POST /api/v1/kernel-parameters/add` — merge new kernel parameters into existing node BSS entries.
162#[utoipa::path(post, path = "/kernel-parameters/add", tag = "kernel-parameters",
163  params(SiteHeader),
164  request_body = AddKernelParametersRequest,
165  security(("bearerAuth" = [])),
166  responses(
167    // dry_run/real result union — kept as Value until the union shape is formalised
168    (status = 200, description = "Parameters added or preview", body = serde_json::Value),
169    (status = 400, description = "Bad request",                 body = ErrorResponse),
170    (status = 401, description = "Unauthorized",                body = ErrorResponse),
171    (status = 500, description = "Internal error",              body = ErrorResponse),
172  )
173)]
174#[tracing::instrument(skip_all)]
175pub async fn add_kernel_parameters(
176  ctx: RequestCtx,
177  Json(body): Json<AddKernelParametersRequest>,
178) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
179  let infra = ctx.infra();
180  let xnames = resolve_xnames_from_request(
181    &infra,
182    &ctx.token,
183    body.xnames_expression.as_deref(),
184    body.hsm_group.as_deref(),
185  )
186  .await?;
187
188  tracing::info!(
189    "add_kernel_parameters xnames={:?} dry_run={}",
190    xnames,
191    body.dry_run
192  );
193
194  let operation = service::kernel_parameters::KernelParamOperation::Add {
195    params: &body.params,
196    overwrite: body.overwrite,
197  };
198
199  let changeset = service::kernel_parameters::prepare_kernel_params_changes(
200    &infra, &ctx.token, &xnames, &operation,
201  )
202  .await
203  .map_err(to_handler_error)?;
204
205  if body.dry_run {
206    return Ok((StatusCode::OK, Json(serialize_or_500(&changeset)?)));
207  }
208
209  let images_to_project = service::kernel_parameters::build_images_to_project(
210    &changeset,
211    body.project_sbps,
212  );
213
214  service::kernel_parameters::apply_kernel_params_changes(
215    &infra,
216    &ctx.token,
217    &changeset,
218    &images_to_project,
219  )
220  .await
221  .map_err(to_handler_error)?;
222
223  Ok((
224    StatusCode::OK,
225    Json(serde_json::json!({
226      "applied": true,
227      "has_changes": changeset.has_changes,
228      "xnames_to_reboot": changeset.xnames_to_reboot,
229    })),
230  ))
231}
232
233// ---------------------------------------------------------------------------
234// DELETE /api/v1/kernel-parameters
235// ---------------------------------------------------------------------------
236
237pub use manta_shared::types::api::kernel_parameters::DeleteKernelParametersRequest;
238
239/// `DELETE /api/v1/kernel-parameters` — remove named kernel parameters from node BSS entries.
240#[utoipa::path(delete, path = "/kernel-parameters", tag = "kernel-parameters",
241  params(SiteHeader),
242  request_body = DeleteKernelParametersRequest,
243  security(("bearerAuth" = [])),
244  responses(
245    // dry_run/real result union — kept as Value until the union shape is formalised
246    (status = 200, description = "Parameters removed or preview", body = serde_json::Value),
247    (status = 400, description = "Bad request",                   body = ErrorResponse),
248    (status = 401, description = "Unauthorized",                  body = ErrorResponse),
249    (status = 500, description = "Internal error",                body = ErrorResponse),
250  )
251)]
252#[tracing::instrument(skip_all)]
253pub async fn delete_kernel_parameters(
254  ctx: RequestCtx,
255  Json(body): Json<DeleteKernelParametersRequest>,
256) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
257  let infra = ctx.infra();
258  let xnames = resolve_xnames_from_request(
259    &infra,
260    &ctx.token,
261    body.xnames_expression.as_deref(),
262    body.hsm_group.as_deref(),
263  )
264  .await?;
265
266  tracing::info!(
267    "delete_kernel_parameters xnames={:?} dry_run={}",
268    xnames,
269    body.dry_run
270  );
271
272  let operation = service::kernel_parameters::KernelParamOperation::Delete {
273    params: &body.params,
274  };
275
276  let changeset = service::kernel_parameters::prepare_kernel_params_changes(
277    &infra, &ctx.token, &xnames, &operation,
278  )
279  .await
280  .map_err(to_handler_error)?;
281
282  if body.dry_run {
283    return Ok((StatusCode::OK, Json(serialize_or_500(&changeset)?)));
284  }
285
286  service::kernel_parameters::apply_kernel_params_changes(
287    &infra,
288    &ctx.token,
289    &changeset,
290    &std::collections::HashMap::new(),
291  )
292  .await
293  .map_err(to_handler_error)?;
294
295  Ok((
296    StatusCode::OK,
297    Json(serde_json::json!({
298      "applied": true,
299      "has_changes": changeset.has_changes,
300      "xnames_to_reboot": changeset.xnames_to_reboot,
301    })),
302  ))
303}