manta_server/service/
kernel_parameters.rs1use manta_backend_dispatcher::error::Error;
4use manta_backend_dispatcher::interfaces::bss::BootParametersTrait;
5use manta_backend_dispatcher::interfaces::ims::ImsTrait;
6use manta_backend_dispatcher::types::bss::BootParameters;
7use manta_backend_dispatcher::types::ims::Image;
8use std::collections::HashMap;
9
10use crate::server::common::app_context::InfraContext;
11use crate::service::authorization::validate_user_group_members_access;
12use crate::service::node_ops;
13pub use manta_shared::types::api::kernel_parameters::GetKernelParametersParams;
14
15pub async fn get_kernel_parameters(
22 infra: &InfraContext<'_>,
23 token: &str,
24 params: &GetKernelParametersParams,
25) -> Result<Vec<BootParameters>, Error> {
26 let xname_vec = node_ops::resolve_target_nodes(
27 infra,
28 token,
29 params.nodes.as_deref(),
30 params.group_name.as_deref(),
31 params.settings_group_name.as_deref(),
32 )
33 .await?;
34
35 validate_user_group_members_access(infra, token, &xname_vec).await?;
36
37 let boot_parameter_vec =
38 infra.backend.get_bootparameters(token, &xname_vec).await?;
39
40 Ok(boot_parameter_vec)
41}
42
43pub(crate) enum KernelParamOperation<'a> {
45 Add {
47 params: &'a str,
49 overwrite: bool,
52 },
53 Apply {
55 params: &'a str,
58 },
59 Delete {
61 params: &'a str,
64 },
65}
66
67impl<'a> KernelParamOperation<'a> {
68 fn mutate(&self, boot_parameter: &mut BootParameters) -> bool {
71 match self {
72 Self::Add { params, overwrite } => {
73 boot_parameter.add_kernel_params(params, *overwrite)
74 }
75 Self::Apply { params } => boot_parameter.apply_kernel_params(params),
76 Self::Delete { params } => boot_parameter.delete_kernel_params(params),
77 }
78 }
79
80 fn handles_sbps_images(&self) -> bool {
82 match self {
83 Self::Add { .. } | Self::Apply { .. } => true,
84 Self::Delete { .. } => false,
85 }
86 }
87}
88
89#[derive(serde::Serialize)]
91pub struct KernelParamsChangeset {
92 pub boot_params: Vec<BootParameters>,
94 pub xnames_to_reboot: Vec<String>,
96 pub has_changes: bool,
98 pub sbps_candidates: Vec<(String, Image)>,
101}
102
103pub(crate) async fn prepare_kernel_params_changes(
114 infra: &InfraContext<'_>,
115 token: &str,
116 xname_vec: &[String],
117 operation: &KernelParamOperation<'_>,
118) -> Result<KernelParamsChangeset, Error> {
119 let mut boot_params: Vec<BootParameters> =
120 infra.backend.get_bootparameters(token, xname_vec).await?;
121
122 let mut has_changes = false;
123 let mut xnames_to_reboot: Vec<String> = Vec::new();
124
125 let handles_sbps = operation.handles_sbps_images();
132 let mut sbps_image_ids: Vec<String> = Vec::new();
133 let mut seen_image_ids: std::collections::HashSet<String> =
134 std::collections::HashSet::new();
135
136 for bp in &mut boot_params {
137 let changed = operation.mutate(bp);
138 if changed {
139 has_changes = true;
140 xnames_to_reboot.extend(bp.hosts.iter().cloned());
141 }
142
143 if handles_sbps
144 && bp.is_root_kernel_param_iscsi_ready()
145 && let Some(image_id) = bp.try_get_boot_image_id()
146 && seen_image_ids.insert(image_id.clone())
147 {
148 sbps_image_ids.push(image_id);
149 }
150 }
151
152 let sbps_candidates: Vec<(String, Image)> = if sbps_image_ids.is_empty() {
154 Vec::new()
155 } else {
156 futures::future::try_join_all(sbps_image_ids.into_iter().map(|id| async {
157 let image = infra
158 .backend
159 .get_images(token, Some(id.as_str()))
160 .await?
161 .first()
162 .ok_or_else(|| {
163 Error::NotFound(format!("No image found for image id '{id}'"))
164 })?
165 .clone();
166 Ok::<_, Error>((id, image))
167 }))
168 .await?
169 };
170
171 Ok(KernelParamsChangeset {
172 boot_params,
173 xnames_to_reboot,
174 has_changes,
175 sbps_candidates,
176 })
177}
178
179pub async fn apply_kernel_params_changes(
187 infra: &InfraContext<'_>,
188 token: &str,
189 changeset: &KernelParamsChangeset,
190 images_to_project: &HashMap<String, Image>,
191) -> Result<(), Error> {
192 validate_user_group_members_access(infra, token, &changeset.xnames_to_reboot)
193 .await?;
194
195 for bp in &changeset.boot_params {
197 infra.backend.update_bootparameters(token, bp).await?;
198 }
199
200 for image in images_to_project.values() {
202 infra
203 .backend
204 .update_image(
205 token,
206 image
207 .id
208 .clone()
209 .ok_or_else(|| Error::MissingField("Image has no id".to_string()))?
210 .as_str(),
211 &image.clone().into(),
212 )
213 .await?;
214 }
215
216 Ok(())
217}
218
219pub fn build_images_to_project(
224 changeset: &KernelParamsChangeset,
225 project_sbps: bool,
226) -> HashMap<String, Image> {
227 if !project_sbps {
228 return HashMap::new();
229 }
230 changeset
231 .sbps_candidates
232 .iter()
233 .map(|(id, img)| {
234 let mut img = img.clone();
235 img.set_boot_image_iscsi_ready();
236 (id.clone(), img)
237 })
238 .collect()
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244
245 fn add(params: &str) -> KernelParamOperation<'_> {
246 KernelParamOperation::Add {
247 params,
248 overwrite: false,
249 }
250 }
251 fn add_overwrite(params: &str) -> KernelParamOperation<'_> {
252 KernelParamOperation::Add {
253 params,
254 overwrite: true,
255 }
256 }
257 fn apply(params: &str) -> KernelParamOperation<'_> {
258 KernelParamOperation::Apply { params }
259 }
260 fn delete(params: &str) -> KernelParamOperation<'_> {
261 KernelParamOperation::Delete { params }
262 }
263
264 #[test]
265 fn overwrite_flag_preserved() {
266 match add_overwrite("x") {
267 KernelParamOperation::Add { overwrite, .. } => assert!(overwrite),
268 _ => panic!("wrong variant"),
269 }
270 match add("x") {
271 KernelParamOperation::Add { overwrite, .. } => assert!(!overwrite),
272 _ => panic!("wrong variant"),
273 }
274 }
275
276 #[test]
277 fn handles_sbps_images_only_for_add_and_apply() {
278 assert!(add("quiet").handles_sbps_images());
279 assert!(apply("quiet").handles_sbps_images());
280 assert!(!delete("quiet").handles_sbps_images());
281 }
282}