1use manta_backend_dispatcher::{
4 error::Error,
5 interfaces::{bss::BootParametersTrait, cfs::CfsTrait, ims::ImsTrait},
6 types::{
7 bss::BootParameters,
8 ims::{Image, PatchImage},
9 },
10};
11use std::collections::HashMap;
12
13use crate::manta_backend_dispatcher::StaticBackendDispatcher;
14use crate::server::common;
15use crate::server::common::app_context::InfraContext;
16use crate::server::common::authorization::validate_target_hsm_members;
17use crate::server::common::ims_ops::get_image_vec_related_cfs_configuration_name;
18pub use manta_shared::shared::params::boot_parameters::{
19 GetBootParametersParams, UpdateBootParametersParams,
20};
21
22pub async fn get_boot_parameters(
24 infra: &InfraContext<'_>,
25 token: &str,
26 params: &GetBootParametersParams,
27) -> Result<Vec<BootParameters>, Error> {
28 let xname_vec = common::node_ops::resolve_target_nodes(
29 infra.backend,
30 token,
31 params.nodes.as_deref(),
32 params.hsm_group.as_deref(),
33 params.settings_hsm_group_name.as_deref(),
34 )
35 .await?;
36
37 tracing::info!("Get boot parameters");
38
39 infra.backend.get_bootparameters(token, &xname_vec).await
40}
41
42pub async fn delete_boot_parameters(
44 infra: &InfraContext<'_>,
45 token: &str,
46 hosts: Vec<String>,
47) -> Result<(), Error> {
48 let boot_parameters = BootParameters {
49 hosts,
50 macs: None,
51 nids: None,
52 params: String::new(),
53 kernel: String::new(),
54 initrd: String::new(),
55 cloud_init: None,
56 };
57
58 infra
59 .backend
60 .delete_bootparameters(token, &boot_parameters)
61 .await
62 .map(|_| ())
63}
64
65pub async fn add_boot_parameters(
67 infra: &InfraContext<'_>,
68 token: &str,
69 boot_parameters: &BootParameters,
70) -> Result<(), Error> {
71 infra
72 .backend
73 .add_bootparameters(token, boot_parameters)
74 .await
75}
76
77pub async fn update_boot_parameters(
79 infra: &InfraContext<'_>,
80 token: &str,
81 params: UpdateBootParametersParams,
82) -> Result<(), Error> {
83 validate_target_hsm_members(infra.backend, token, ¶ms.hosts).await?;
84
85 let boot_parameters = BootParameters {
86 hosts: params.hosts,
87 macs: params.macs,
88 nids: params.nids,
89 params: params.params,
90 kernel: params.kernel,
91 initrd: params.initrd,
92 cloud_init: None,
93 };
94
95 tracing::debug!("new boot params: {:#?}", boot_parameters);
96
97 infra
98 .backend
99 .update_bootparameters(token, &boot_parameters)
100 .await
101}
102
103#[derive(serde::Serialize)]
105pub struct BootConfigChangeset {
106 pub xname_vec: Vec<String>,
108 pub boot_param_vec: Vec<BootParameters>,
110 pub image_vec: HashMap<String, Image>,
112 pub need_restart: bool,
114}
115
116pub async fn prepare_boot_config(
118 infra: &InfraContext<'_>,
119 token: &str,
120 hosts_expression: &str,
121 new_boot_image_id_opt: Option<&str>,
122 new_boot_image_configuration_opt: Option<&str>,
123 new_kernel_parameters_opt: Option<&str>,
124) -> Result<BootConfigChangeset, Error> {
125 let backend = infra.backend;
126
127 let mut need_restart = false;
128
129 let xname_vec = common::node_ops::resolve_hosts_expression(
130 backend,
131 token,
132 hosts_expression,
133 false,
134 )
135 .await?;
136
137 let mut current_node_boot_param_vec: Vec<BootParameters> =
138 backend.get_bootparameters(token, &xname_vec).await?;
139
140 let new_boot_image_opt = get_new_boot_image(
141 backend,
142 token,
143 infra.shasta_base_url,
144 infra.shasta_root_cert,
145 new_boot_image_configuration_opt,
146 new_boot_image_id_opt,
147 )
148 .await?;
149
150 if let Some(new_kernel_parameters) = new_kernel_parameters_opt {
152 need_restart |= apply_kernel_params(
153 &mut current_node_boot_param_vec,
154 new_kernel_parameters,
155 )?;
156 }
157
158 let mut image_vec = collect_boot_images(
159 backend,
160 token,
161 &mut current_node_boot_param_vec,
162 new_boot_image_opt,
163 &mut need_restart,
164 )
165 .await?;
166
167 if current_node_boot_param_vec
168 .first()
169 .ok_or_else(|| Error::NotFound("No boot parameters found".to_string()))?
170 .is_root_kernel_param_iscsi_ready()
171 {
172 for image in image_vec.values_mut() {
173 image.set_boot_image_iscsi_ready();
174 }
175 }
176
177 Ok(BootConfigChangeset {
178 xname_vec,
179 boot_param_vec: current_node_boot_param_vec,
180 image_vec,
181 need_restart,
182 })
183}
184
185pub async fn persist_boot_config(
187 infra: &InfraContext<'_>,
188 token: &str,
189 changeset: &BootConfigChangeset,
190 new_runtime_configuration_opt: Option<&str>,
191) -> Result<(), Error> {
192 tracing::info!("Persist changes");
193
194 for boot_parameter in &changeset.boot_param_vec {
195 tracing::debug!("Updating boot parameter:\n{:#?}", boot_parameter);
196 let component_patch_rep = infra
197 .backend
198 .update_bootparameters(token, boot_parameter)
199 .await;
200 tracing::debug!(
201 "Component boot parameters resp:\n{:#?}",
202 component_patch_rep
203 );
204 }
205
206 if let Some(new_runtime_configuration_name) = new_runtime_configuration_opt {
207 println!(
208 "Updating runtime configuration to '{new_runtime_configuration_name}'"
209 );
210
211 infra
212 .backend
213 .update_runtime_configuration(
214 token,
215 infra.shasta_base_url,
216 infra.shasta_root_cert,
217 &changeset.xname_vec,
218 new_runtime_configuration_name,
219 true,
220 )
221 .await?;
222
223 for image in changeset.image_vec.values() {
224 let image_id = image.id.clone().ok_or_else(|| {
225 Error::MissingField("Image id is missing".to_string())
226 })?;
227 let patch_image: PatchImage = image.clone().into();
228 infra
229 .backend
230 .update_image(token, &image_id, &patch_image)
231 .await?;
232 }
233 } else {
234 tracing::info!("Runtime configuration does not change.");
235 }
236
237 Ok(())
238}
239
240async fn get_new_boot_image(
241 backend: &StaticBackendDispatcher,
242 shasta_token: &str,
243 shasta_base_url: &str,
244 shasta_root_cert: &[u8],
245 new_boot_image_configuration_opt: Option<&str>,
246 new_boot_image_id_opt: Option<&str>,
247) -> Result<Option<Image>, Error> {
248 let new_boot_image = if let Some(new_boot_image_configuration) =
249 new_boot_image_configuration_opt
250 {
251 tracing::info!(
252 "Boot configuration '{}' provided",
253 new_boot_image_configuration
254 );
255 let mut image_vec = get_image_vec_related_cfs_configuration_name(
256 backend,
257 shasta_token,
258 shasta_base_url,
259 shasta_root_cert,
260 new_boot_image_configuration.to_string(),
261 )
262 .await?;
263
264 if image_vec.is_empty() {
265 return Err(Error::NotFound(format!(
266 "No boot image found for configuration '{new_boot_image_configuration}'"
267 )));
268 }
269
270 backend.filter_images(&mut image_vec)?;
271
272 let most_recent_image = image_vec.iter().last().ok_or_else(|| {
273 Error::NotFound("No image found for configuration".to_string())
274 })?;
275
276 tracing::debug!(
277 "Boot image id related to configuration '{}' found:\n{:#?}",
278 new_boot_image_configuration,
279 most_recent_image
280 );
281
282 Some(most_recent_image.clone())
283 } else if let Some(boot_image_id) = new_boot_image_id_opt {
284 tracing::info!("Boot image id '{}' provided", boot_image_id);
285 let image_in_csm_vec = backend
286 .get_images(shasta_token, new_boot_image_id_opt)
287 .await?;
288
289 if image_in_csm_vec.is_empty() {
290 return Err(Error::NotFound(format!(
291 "Boot image id '{boot_image_id}' not found"
292 )));
293 }
294
295 image_in_csm_vec.first().cloned()
296 } else {
297 None
298 };
299
300 Ok(new_boot_image)
301}
302
303fn apply_kernel_params(
304 boot_param_vec: &mut [BootParameters],
305 new_kernel_parameters: &str,
306) -> Result<bool, Error> {
307 let mut any_changed = false;
308
309 for boot_parameter in boot_param_vec.iter_mut() {
310 tracing::info!(
311 "Updating '{:?}' kernel parameters to '{}'",
312 boot_parameter.hosts,
313 new_kernel_parameters
314 );
315
316 let changed = boot_parameter.apply_kernel_params(new_kernel_parameters);
317 any_changed = changed || any_changed;
318
319 tracing::info!("need restart? {}", any_changed);
320
321 let image_id = boot_parameter.try_get_boot_image_id().ok_or_else(|| {
322 Error::MissingField(format!(
323 "Could not get boot image id from boot parameters for hosts: {:?}",
324 boot_parameter.hosts
325 ))
326 })?;
327
328 let _ = boot_parameter
329 .update_boot_image(&image_id, &boot_parameter.get_boot_image_etag())?;
330 }
331
332 Ok(any_changed)
333}
334
335async fn collect_boot_images(
336 backend: &StaticBackendDispatcher,
337 shasta_token: &str,
338 boot_param_vec: &mut [BootParameters],
339 new_boot_image_opt: Option<Image>,
340 need_restart: &mut bool,
341) -> Result<HashMap<String, Image>, Error> {
342 let mut image_vec = HashMap::<String, Image>::new();
343
344 if let Some(new_boot_image) = new_boot_image_opt {
345 let new_boot_image_id = new_boot_image
346 .id
347 .as_ref()
348 .ok_or_else(|| {
349 Error::MissingField("New boot image id is missing".to_string())
350 })?
351 .clone();
352
353 let new_boot_image_etag = new_boot_image
354 .link
355 .as_ref()
356 .and_then(|link| link.etag.as_ref())
357 .ok_or_else(|| {
358 Error::MissingField("New boot image etag is missing".to_string())
359 })?;
360
361 image_vec.insert(new_boot_image_id.clone(), new_boot_image.clone());
362
363 let any_differ = boot_param_vec.iter().any(|bp| {
364 bp.try_get_boot_image_id().as_deref() != Some(new_boot_image_id.as_str())
365 });
366
367 if any_differ {
368 for boot_parameter in boot_param_vec.iter_mut() {
369 tracing::info!(
370 "Updating '{:?}' boot image to '{}'",
371 boot_parameter.hosts,
372 new_boot_image_id
373 );
374 boot_parameter
375 .update_boot_image(&new_boot_image_id, new_boot_image_etag)?;
376 }
377 *need_restart = true;
378 }
379 } else {
380 for boot_parameter in boot_param_vec.iter() {
381 let boot_image_id =
382 boot_parameter.try_get_boot_image_id().ok_or_else(|| {
383 Error::MissingField(format!(
384 "Could not get boot image id from boot parameters for hosts: {:?}",
385 boot_parameter.hosts
386 ))
387 })?;
388
389 let boot_image = backend
390 .get_images(shasta_token, Some(boot_image_id.as_str()))
391 .await?
392 .first()
393 .ok_or_else(|| {
394 Error::NotFound("No image found for boot image id".to_string())
395 })?
396 .clone();
397
398 image_vec.insert(boot_image_id, boot_image);
399 }
400 }
401
402 Ok(image_vec)
403}