manta_server/server/handlers/
kernel_parameters.rs

1//! Kernel-parameters handlers (get/apply/add/delete).
2
3use axum::{Json, extract::Query, http::StatusCode, response::IntoResponse};
4use serde::Deserialize;
5use utoipa::{IntoParams, ToSchema};
6
7use super::{
8  ErrorResponse, RequestCtx, SiteHeader, default_true,
9  resolve_xnames_from_request, serialize_or_500, to_handler_error,
10};
11use crate::service;
12
13// ---------------------------------------------------------------------------
14// GET /api/v1/kernel-parameters
15// ---------------------------------------------------------------------------
16
17/// Query parameters for `GET /kernel-parameters`.
18#[derive(Deserialize, IntoParams)]
19pub struct KernelParametersQuery {
20  /// HSM group whose members' kernel parameters should be returned.
21  pub hsm_group: Option<String>,
22  /// Explicit comma-separated xnames; mutually exclusive with
23  /// `hsm_group`.
24  pub nodes: Option<String>,
25}
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 = serde_json::Value),
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    hsm_group: q.hsm_group,
46    nodes: q.nodes,
47    settings_hsm_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
63/// Which kernel-parameter mutation to perform (`add`, `apply`, or `delete`).
64#[derive(Debug, Deserialize, ToSchema)]
65#[serde(rename_all = "lowercase")]
66pub enum KernelParamOp {
67  /// Merge new parameters into the existing set.
68  Add,
69  /// Replace the entire parameter set.
70  Apply,
71  /// Remove named parameters from the existing set.
72  Delete,
73}
74
75/// Request body for `POST /kernel-parameters/apply`.
76#[derive(Deserialize, ToSchema)]
77pub struct ApplyKernelParametersRequest {
78  /// Hosts expression (xnames, nids, or hostlist notation); mutually exclusive with `hsm_group`.
79  pub xnames_expression: Option<String>,
80  /// Target HSM group; all members are resolved to xnames.
81  pub hsm_group: Option<String>,
82  /// Which mutation to perform: add, apply (replace), or delete.
83  pub operation: KernelParamOp,
84  /// Space-separated kernel parameter key=value pairs.
85  pub params: String,
86  /// Only relevant for the `add` operation.
87  #[serde(default)]
88  pub overwrite: bool,
89  /// Whether to project SBPS images (default true).
90  #[serde(default = "default_true")]
91  pub project_sbps: bool,
92  /// When true, returns the computed changeset without applying it.
93  #[serde(default)]
94  pub dry_run: bool,
95}
96
97/// `POST /api/v1/kernel-parameters/apply` — add, replace, or delete kernel parameters on nodes.
98#[utoipa::path(post, path = "/kernel-parameters/apply", tag = "kernel-parameters",
99  params(SiteHeader),
100  request_body = ApplyKernelParametersRequest,
101  security(("bearerAuth" = [])),
102  responses(
103    (status = 200, description = "Kernel parameters applied or preview", body = serde_json::Value),
104    (status = 400, description = "Bad request",                          body = ErrorResponse),
105    (status = 401, description = "Unauthorized",                         body = ErrorResponse),
106    (status = 500, description = "Internal error",                       body = ErrorResponse),
107  )
108)]
109#[tracing::instrument(skip_all)]
110pub async fn apply_kernel_parameters(
111  ctx: RequestCtx,
112  Json(body): Json<ApplyKernelParametersRequest>,
113) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
114  let infra = ctx.infra();
115
116  let xnames = resolve_xnames_from_request(
117    infra.backend,
118    &ctx.token,
119    body.xnames_expression.as_deref(),
120    body.hsm_group.as_deref(),
121  )
122  .await?;
123
124  tracing::info!(
125    "apply_kernel_parameters xnames={:?} op={:?} dry_run={}",
126    xnames,
127    body.operation,
128    body.dry_run
129  );
130
131  let operation = match body.operation {
132    KernelParamOp::Add => {
133      service::kernel_parameters::KernelParamOperation::Add {
134        params: &body.params,
135        overwrite: body.overwrite,
136      }
137    }
138    KernelParamOp::Apply => {
139      service::kernel_parameters::KernelParamOperation::Apply {
140        params: &body.params,
141      }
142    }
143    KernelParamOp::Delete => {
144      service::kernel_parameters::KernelParamOperation::Delete {
145        params: &body.params,
146      }
147    }
148  };
149
150  let changeset = service::kernel_parameters::prepare_kernel_params_changes(
151    &infra, &ctx.token, &xnames, &operation,
152  )
153  .await
154  .map_err(to_handler_error)?;
155
156  if body.dry_run {
157    return Ok((StatusCode::OK, Json(serialize_or_500(&changeset)?)));
158  }
159
160  let images_to_project = service::kernel_parameters::build_images_to_project(
161    &changeset,
162    body.project_sbps,
163  );
164
165  service::kernel_parameters::apply_kernel_params_changes(
166    &infra,
167    &ctx.token,
168    &changeset,
169    &images_to_project,
170  )
171  .await
172  .map_err(to_handler_error)?;
173
174  Ok((
175    StatusCode::OK,
176    Json(serde_json::json!({
177      "applied": true,
178      "has_changes": changeset.has_changes,
179      "xnames_to_reboot": changeset.xnames_to_reboot,
180    })),
181  ))
182}
183
184// ---------------------------------------------------------------------------
185// POST /api/v1/kernel-parameters/add
186// ---------------------------------------------------------------------------
187
188/// Request body for `POST /kernel-parameters/add`.
189#[derive(Deserialize, ToSchema)]
190pub struct AddKernelParametersRequest {
191  /// Space-separated kernel parameter key=value pairs to add.
192  pub params: String,
193  /// Hosts expression (xnames, nids, or hostlist notation); mutually exclusive with `hsm_group`.
194  pub xnames_expression: Option<String>,
195  /// Target HSM group; all members are resolved to xnames.
196  pub hsm_group: Option<String>,
197  /// When true, overwrite parameters that already exist.
198  #[serde(default)]
199  pub overwrite: bool,
200  /// Whether to project SBPS images (default true).
201  #[serde(default = "default_true")]
202  pub project_sbps: bool,
203  /// When true, returns the computed changeset without applying it.
204  #[serde(default)]
205  pub dry_run: bool,
206}
207
208/// `POST /api/v1/kernel-parameters/add` — merge new kernel parameters into existing node BSS entries.
209#[utoipa::path(post, path = "/kernel-parameters/add", tag = "kernel-parameters",
210  params(SiteHeader),
211  request_body = AddKernelParametersRequest,
212  security(("bearerAuth" = [])),
213  responses(
214    (status = 200, description = "Parameters added or preview", body = serde_json::Value),
215    (status = 400, description = "Bad request",                 body = ErrorResponse),
216    (status = 401, description = "Unauthorized",                body = ErrorResponse),
217    (status = 500, description = "Internal error",              body = ErrorResponse),
218  )
219)]
220#[tracing::instrument(skip_all)]
221pub async fn add_kernel_parameters(
222  ctx: RequestCtx,
223  Json(body): Json<AddKernelParametersRequest>,
224) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
225  let infra = ctx.infra();
226  let xnames = resolve_xnames_from_request(
227    infra.backend,
228    &ctx.token,
229    body.xnames_expression.as_deref(),
230    body.hsm_group.as_deref(),
231  )
232  .await?;
233
234  tracing::info!(
235    "add_kernel_parameters xnames={:?} dry_run={}",
236    xnames,
237    body.dry_run
238  );
239
240  let operation = service::kernel_parameters::KernelParamOperation::Add {
241    params: &body.params,
242    overwrite: body.overwrite,
243  };
244
245  let changeset = service::kernel_parameters::prepare_kernel_params_changes(
246    &infra, &ctx.token, &xnames, &operation,
247  )
248  .await
249  .map_err(to_handler_error)?;
250
251  if body.dry_run {
252    return Ok((StatusCode::OK, Json(serialize_or_500(&changeset)?)));
253  }
254
255  let images_to_project = service::kernel_parameters::build_images_to_project(
256    &changeset,
257    body.project_sbps,
258  );
259
260  service::kernel_parameters::apply_kernel_params_changes(
261    &infra,
262    &ctx.token,
263    &changeset,
264    &images_to_project,
265  )
266  .await
267  .map_err(to_handler_error)?;
268
269  Ok((
270    StatusCode::OK,
271    Json(serde_json::json!({
272      "applied": true,
273      "has_changes": changeset.has_changes,
274      "xnames_to_reboot": changeset.xnames_to_reboot,
275    })),
276  ))
277}
278
279// ---------------------------------------------------------------------------
280// DELETE /api/v1/kernel-parameters
281// ---------------------------------------------------------------------------
282
283/// Request body for `DELETE /kernel-parameters`.
284#[derive(Deserialize, ToSchema)]
285pub struct DeleteKernelParametersRequest {
286  /// Space-separated kernel parameter names (or key=value pairs) to remove.
287  pub params: String,
288  /// Hosts expression (xnames, nids, or hostlist notation); mutually exclusive with `hsm_group`.
289  pub xnames_expression: Option<String>,
290  /// Target HSM group; all members are resolved to xnames.
291  pub hsm_group: Option<String>,
292  /// When true, returns the computed changeset without applying it.
293  #[serde(default)]
294  pub dry_run: bool,
295}
296
297/// `DELETE /api/v1/kernel-parameters` — remove named kernel parameters from node BSS entries.
298#[utoipa::path(delete, path = "/kernel-parameters", tag = "kernel-parameters",
299  params(SiteHeader),
300  request_body = DeleteKernelParametersRequest,
301  security(("bearerAuth" = [])),
302  responses(
303    (status = 200, description = "Parameters removed or preview", body = serde_json::Value),
304    (status = 400, description = "Bad request",                   body = ErrorResponse),
305    (status = 401, description = "Unauthorized",                  body = ErrorResponse),
306    (status = 500, description = "Internal error",                body = ErrorResponse),
307  )
308)]
309#[tracing::instrument(skip_all)]
310pub async fn delete_kernel_parameters(
311  ctx: RequestCtx,
312  Json(body): Json<DeleteKernelParametersRequest>,
313) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
314  let infra = ctx.infra();
315  let xnames = resolve_xnames_from_request(
316    infra.backend,
317    &ctx.token,
318    body.xnames_expression.as_deref(),
319    body.hsm_group.as_deref(),
320  )
321  .await?;
322
323  tracing::info!(
324    "delete_kernel_parameters xnames={:?} dry_run={}",
325    xnames,
326    body.dry_run
327  );
328
329  let operation = service::kernel_parameters::KernelParamOperation::Delete {
330    params: &body.params,
331  };
332
333  let changeset = service::kernel_parameters::prepare_kernel_params_changes(
334    &infra, &ctx.token, &xnames, &operation,
335  )
336  .await
337  .map_err(to_handler_error)?;
338
339  if body.dry_run {
340    return Ok((StatusCode::OK, Json(serialize_or_500(&changeset)?)));
341  }
342
343  service::kernel_parameters::apply_kernel_params_changes(
344    &infra,
345    &ctx.token,
346    &changeset,
347    &std::collections::HashMap::new(),
348  )
349  .await
350  .map_err(to_handler_error)?;
351
352  Ok((
353    StatusCode::OK,
354    Json(serde_json::json!({
355      "applied": true,
356      "has_changes": changeset.has_changes,
357      "xnames_to_reboot": changeset.xnames_to_reboot,
358    })),
359  ))
360}