1use std::collections::HashMap;
6
7use manta_backend_dispatcher::{
8 error::Error, interfaces::hsm::group::GroupTrait, types::Group,
9};
10
11use super::{
12 AddHwResult, ApplyHwResult, DeleteHwResult, HwClusterMode,
13 MEMORY_CAPACITY_LCM, pin_unpin, scoring,
14};
15use crate::manta_backend_dispatcher::StaticBackendDispatcher;
16
17#[allow(clippy::too_many_arguments)]
19pub async fn apply_hw_configuration(
20 backend: &StaticBackendDispatcher,
21 mode: HwClusterMode,
22 shasta_token: &str,
23 target_hsm_group_name: &str,
24 parent_hsm_group_name: &str,
25 pattern: &str,
26 dryrun: bool,
27 create_target_hsm_group: bool,
28 delete_empty_parent_hsm_group: bool,
29) -> Result<ApplyHwResult, Error> {
30 let (user_defined_hw_component_vec, user_defined_hw_component_count_hashmap) =
31 pin_unpin::parse_hw_pattern_usize(target_hsm_group_name, pattern)?;
32
33 pin_unpin::ensure_target_group_exists(
34 backend,
35 shasta_token,
36 target_hsm_group_name,
37 dryrun,
38 create_target_hsm_group,
39 )
40 .await?;
41
42 let (
43 target_hsm_group_member_vec,
44 target_hsm_node_hw_component_count_vec,
45 target_hsm_hw_component_summary,
46 ) = scoring::fetch_hsm_hw_inventory(
47 backend,
48 shasta_token,
49 &user_defined_hw_component_vec,
50 target_hsm_group_name,
51 MEMORY_CAPACITY_LCM,
52 )
53 .await?;
54
55 tracing::info!(
56 "HSM group '{}' hw component summary: {:?}",
57 target_hsm_group_name,
58 target_hsm_hw_component_summary
59 );
60
61 let (
62 parent_hsm_group_member_vec,
63 parent_hsm_node_hw_component_count_vec,
64 _parent_summary,
65 ) = scoring::fetch_hsm_hw_inventory(
66 backend,
67 shasta_token,
68 &user_defined_hw_component_vec,
69 parent_hsm_group_name,
70 MEMORY_CAPACITY_LCM,
71 )
72 .await?;
73
74 pin_unpin::validate_resource_sufficiency(
75 &target_hsm_node_hw_component_count_vec,
76 &parent_hsm_node_hw_component_count_vec,
77 &user_defined_hw_component_count_hashmap,
78 )?;
79
80 let (
81 target_hsm_node_hw_component_count_vec,
82 parent_hsm_node_hw_component_count_vec,
83 ) = scoring::resolve_hw_description_to_xnames(
84 mode,
85 target_hsm_node_hw_component_count_vec,
86 parent_hsm_node_hw_component_count_vec,
87 user_defined_hw_component_count_hashmap,
88 )
89 .await?;
90
91 let target_hsm_node_vec: Vec<String> = target_hsm_node_hw_component_count_vec
92 .into_iter()
93 .map(|(xname, _)| xname)
94 .collect();
95
96 let parent_hsm_node_vec: Vec<String> = parent_hsm_node_hw_component_count_vec
97 .into_iter()
98 .map(|(xname, _)| xname)
99 .collect();
100
101 pin_unpin::apply_group_updates(
102 backend,
103 shasta_token,
104 target_hsm_group_name,
105 parent_hsm_group_name,
106 &target_hsm_group_member_vec,
107 &parent_hsm_group_member_vec,
108 &target_hsm_node_vec,
109 &parent_hsm_node_vec,
110 dryrun,
111 delete_empty_parent_hsm_group,
112 )
113 .await?;
114
115 Ok(ApplyHwResult {
116 target_nodes: target_hsm_node_vec,
117 parent_nodes: parent_hsm_node_vec,
118 })
119}
120
121async fn ensure_add_target_group_exists(
125 backend: &StaticBackendDispatcher,
126 shasta_token: &str,
127 target_hsm_group_name: &str,
128 dryrun: bool,
129 create_hsm_group: bool,
130) -> Result<(), Error> {
131 match backend.get_group(shasta_token, target_hsm_group_name).await {
132 Ok(_) => {
133 tracing::debug!("The group '{}' exists, good.", target_hsm_group_name);
134 Ok(())
135 }
136 Err(_) => {
137 if !create_hsm_group {
138 return Err(Error::NotFound(format!(
139 "Group '{target_hsm_group_name}' does not exist, but the \
140 option to create the group was NOT \
141 specified, cannot continue."
142 )));
143 }
144 tracing::info!(
145 "Group '{}' does not exist, but the option \
146 to create the group has been selected, \
147 creating it now.",
148 target_hsm_group_name
149 );
150 if dryrun {
151 return Err(Error::BadRequest(
152 "Dryrun selected, cannot create \
153 the new group and continue."
154 .to_string(),
155 ));
156 }
157 let group = Group {
158 label: target_hsm_group_name.to_string(),
159 description: None,
160 tags: None,
161 members: None,
162 exclusive_group: Some("false".to_string()),
163 };
164 backend.add_group(shasta_token, group).await?;
165 Ok(())
166 }
167 }
168}
169
170fn compute_final_parent_summary(
172 current_summary: &HashMap<String, usize>,
173 deltas: &HashMap<String, isize>,
174 parent_group_name: &str,
175) -> Result<HashMap<String, usize>, Error> {
176 let mut final_summary: HashMap<String, usize> = HashMap::new();
177
178 for (hw_component, counter) in deltas {
179 let current = *current_summary.get(hw_component).unwrap_or(&0);
180 if *counter > current as isize {
181 return Err(Error::InsufficientResources(format!(
182 "Cannot remove more hw component '{}' \
183 ({}) than available in parent group \
184 '{}' ({})",
185 hw_component, *counter, parent_group_name, current
186 )));
187 }
188 let new_counter = current - *counter as usize;
189 final_summary.insert(hw_component.to_string(), new_counter);
190 }
191
192 Ok(final_summary)
193}
194
195pub async fn add_hw_component(
198 backend: &StaticBackendDispatcher,
199 shasta_token: &str,
200 target_hsm_group_name: &str,
201 parent_hsm_group_name: &str,
202 pattern: &str,
203 dryrun: bool,
204 create_hsm_group: bool,
205) -> Result<AddHwResult, Error> {
206 ensure_add_target_group_exists(
207 backend,
208 shasta_token,
209 target_hsm_group_name,
210 dryrun,
211 create_hsm_group,
212 )
213 .await?;
214
215 let pattern_str = format!("{target_hsm_group_name}:{pattern}");
216 let pattern_lowercase = pattern_str.to_lowercase();
217 let mut pattern_element_vec: Vec<&str> =
218 pattern_lowercase.split(':').collect();
219 let target_name = pattern_element_vec.remove(0);
220
221 let (
222 user_defined_delta_hw_component_vec,
223 user_defined_delta_hw_component_count_hashmap,
224 ) = scoring::parse_hw_pattern(&pattern_element_vec)?;
225
226 let (
227 _parent_member_vec,
228 mut parent_hsm_node_hw_component_count_vec,
229 parent_hsm_hw_component_summary,
230 ) = scoring::fetch_hsm_hw_inventory(
231 backend,
232 shasta_token,
233 &user_defined_delta_hw_component_vec,
234 parent_hsm_group_name,
235 MEMORY_CAPACITY_LCM,
236 )
237 .await?;
238
239 let final_parent_hsm_hw_component_summary = compute_final_parent_summary(
240 &parent_hsm_hw_component_summary,
241 &user_defined_delta_hw_component_count_hashmap,
242 parent_hsm_group_name,
243 )?;
244
245 let scarcity_scores = scoring::calculate_hw_component_scarcity_scores(
246 &parent_hsm_node_hw_component_count_vec,
247 )
248 .await;
249
250 let hw_counters_to_move = pin_unpin::calculate_target_hsm_unpin(
251 &final_parent_hsm_hw_component_summary,
252 &final_parent_hsm_hw_component_summary
253 .keys()
254 .cloned()
255 .collect::<Vec<String>>(),
256 &mut parent_hsm_node_hw_component_count_vec,
257 &scarcity_scores,
258 )?;
259
260 let nodes_to_move: Vec<String> = hw_counters_to_move
261 .iter()
262 .map(|(xname, _)| xname.clone())
263 .collect();
264
265 let mut target_hsm_node_vec: Vec<String> = backend
266 .get_member_vec_from_group_name_vec(
267 shasta_token,
268 &[target_name.to_string()],
269 )
270 .await?;
271
272 target_hsm_node_vec.extend(nodes_to_move.clone());
273 target_hsm_node_vec.sort();
274
275 if !dryrun {
276 for xname in &nodes_to_move {
277 backend
278 .delete_member_from_group(shasta_token, parent_hsm_group_name, xname)
279 .await?;
280
281 let _ = backend
282 .add_members_to_group(shasta_token, target_name, &[xname.as_str()])
283 .await?;
284 }
285 }
286
287 let parent_nodes: Vec<String> = parent_hsm_node_hw_component_count_vec
288 .iter()
289 .map(|(xname, _)| xname.clone())
290 .collect();
291
292 Ok(AddHwResult {
293 nodes_moved: nodes_to_move,
294 target_nodes: target_hsm_node_vec,
295 parent_nodes,
296 })
297}
298
299async fn handle_empty_target(
303 backend: &StaticBackendDispatcher,
304 shasta_token: &str,
305 target_hsm_group_name: &str,
306 dryrun: bool,
307 delete_hsm_group: bool,
308) -> Result<(), Error> {
309 tracing::info!(
310 "The target HSM group {} is already empty, cannot \
311 remove hardware from it.",
312 target_hsm_group_name
313 );
314
315 if dryrun || !delete_hsm_group {
316 tracing::info!(
317 "The option to delete empty groups has NOT been \
318 selected, or the dryrun has been enabled. We \
319 are done with this action."
320 );
321 return Ok(());
322 }
323
324 tracing::info!(
325 "The option to delete empty groups has been \
326 selected, removing it."
327 );
328 match backend
329 .delete_group(shasta_token, target_hsm_group_name)
330 .await
331 {
332 Ok(_) => {
333 tracing::info!(
334 "HSM group removed successfully, we are \
335 done with this action."
336 );
337 }
338 Err(e) => tracing::debug!(
339 "Error removing the HSM group. This always \
340 fails, ignore please. Reported: {}",
341 e
342 ),
343 }
344 Ok(())
345}
346
347fn compute_delete_final_summary(
349 current_summary: &HashMap<String, usize>,
350 deltas: &HashMap<String, isize>,
351) -> Result<HashMap<String, usize>, Error> {
352 let mut final_summary: HashMap<String, usize> = HashMap::new();
353
354 for (hw_component, counter) in deltas {
355 let current = *current_summary.get(hw_component).ok_or_else(|| {
356 Error::NotFound(format!(
357 "hw component '{hw_component}' not found in target HSM \
358 hw component summary"
359 ))
360 })?;
361
362 final_summary.insert(hw_component.to_string(), current - *counter as usize);
363 }
364
365 Ok(final_summary)
366}
367
368async fn apply_node_moves(
370 backend: &StaticBackendDispatcher,
371 shasta_token: &str,
372 target_group: &str,
373 parent_group: &str,
374 nodes: &[String],
375 target_will_be_empty: bool,
376 delete_hsm_group: bool,
377) -> Result<(), Error> {
378 for xname in nodes {
379 backend
380 .delete_member_from_group(shasta_token, target_group, xname.as_str())
381 .await?;
382
383 backend
384 .add_members_to_group(shasta_token, parent_group, &[xname.as_str()])
385 .await?;
386 }
387
388 if target_will_be_empty {
389 if delete_hsm_group {
390 tracing::info!(
391 "HSM group {} is now empty and the option to \
392 delete empty groups has been selected, \
393 removing it.",
394 target_group
395 );
396 match backend.delete_group(shasta_token, target_group).await {
397 Ok(_) => tracing::info!("HSM group removed successfully."),
398 Err(e) => tracing::debug!(
399 "Error removing the HSM group. This always \
400 fails, ignore please. Reported: {}",
401 e
402 ),
403 }
404 } else {
405 tracing::debug!(
406 "HSM group {} is now empty and the option to \
407 delete empty groups has NOT been selected, \
408 will not remove it.",
409 target_group
410 )
411 }
412 }
413
414 Ok(())
415}
416
417pub async fn delete_hw_component(
420 backend: &StaticBackendDispatcher,
421 token: &str,
422 target_hsm_group_name: &str,
423 parent_hsm_group_name: &str,
424 pattern: &str,
425 dryrun: bool,
426 delete_hsm_group: bool,
427) -> Result<DeleteHwResult, Error> {
428 match backend.get_group(token, target_hsm_group_name).await {
429 Ok(_) => {}
430 Err(_) => {
431 return Err(Error::NotFound(format!(
432 "HSM group {target_hsm_group_name} does not exist, cannot remove hw from it."
433 )));
434 }
435 }
436
437 let pattern_str = format!("{target_hsm_group_name}:{pattern}");
438 let pattern_lowercase = pattern_str.to_lowercase();
439 let mut pattern_element_vec: Vec<&str> =
440 pattern_lowercase.split(':').collect();
441 let target_name = pattern_element_vec.remove(0);
442
443 let (
444 user_defined_delta_hw_component_vec,
445 user_defined_delta_hw_component_count_hashmap,
446 ) = scoring::parse_hw_pattern(&pattern_element_vec)?;
447
448 let (
449 target_hsm_group_member_vec,
450 mut target_hsm_node_hw_component_count_vec,
451 target_hsm_hw_component_summary,
452 ) = scoring::fetch_hsm_hw_inventory(
453 backend,
454 token,
455 &user_defined_delta_hw_component_vec,
456 target_name,
457 MEMORY_CAPACITY_LCM,
458 )
459 .await?;
460
461 if target_hsm_node_hw_component_count_vec.is_empty() {
462 handle_empty_target(backend, token, target_name, dryrun, delete_hsm_group)
463 .await?;
464 return Ok(DeleteHwResult {
465 nodes_moved: vec![],
466 target_nodes: vec![],
467 parent_nodes: vec![],
468 });
469 }
470
471 let (
472 parent_hsm_group_member_vec,
473 parent_hsm_node_hw_component_count_vec,
474 _parent_summary,
475 ) = scoring::fetch_hsm_hw_inventory(
476 backend,
477 token,
478 &user_defined_delta_hw_component_vec,
479 parent_hsm_group_name,
480 MEMORY_CAPACITY_LCM,
481 )
482 .await?;
483
484 let combined = [
485 target_hsm_node_hw_component_count_vec.clone(),
486 parent_hsm_node_hw_component_count_vec.clone(),
487 ]
488 .concat();
489 let scarcity_scores =
490 scoring::calculate_hw_component_scarcity_scores(&combined).await;
491
492 let final_target_summary = compute_delete_final_summary(
493 &target_hsm_hw_component_summary,
494 &user_defined_delta_hw_component_count_hashmap,
495 )?;
496
497 let hw_counters_to_move = pin_unpin::calculate_target_hsm_unpin(
498 &final_target_summary,
499 &final_target_summary
500 .keys()
501 .cloned()
502 .collect::<Vec<String>>(),
503 &mut target_hsm_node_hw_component_count_vec,
504 &scarcity_scores,
505 )?;
506
507 let nodes_to_move: Vec<String> = hw_counters_to_move
508 .iter()
509 .map(|(xname, _)| xname.clone())
510 .collect();
511
512 let mut parent_nodes: Vec<String> = parent_hsm_group_member_vec;
513 parent_nodes.extend(nodes_to_move.clone());
514 parent_nodes.sort();
515
516 let target_nodes: Vec<String> = target_hsm_node_hw_component_count_vec
517 .iter()
518 .map(|(xname, _)| xname.clone())
519 .collect();
520
521 if !dryrun {
522 apply_node_moves(
523 backend,
524 token,
525 target_name,
526 parent_hsm_group_name,
527 &nodes_to_move,
528 target_hsm_group_member_vec.len() == nodes_to_move.len(),
529 delete_hsm_group,
530 )
531 .await?;
532 }
533
534 Ok(DeleteHwResult {
535 nodes_moved: nodes_to_move,
536 target_nodes,
537 parent_nodes,
538 })
539}