Skip to main content

manta_server/server/handlers/
kernel_parameters.rs

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