1use std::{collections::HashMap, sync::Arc, time::Instant};
2
3use regex::Regex;
4use serde_json::Value;
5use tokio::sync::Semaphore;
6
7use crate::{bss, cfs, error::Error, hsm};
8
9use super::types::NodeDetails;
10
11pub async fn validate_target_hsm_members(
16 shasta_token: &str,
17 shasta_base_url: &str,
18 shasta_root_cert: &[u8],
19 socks5_proxy: Option<&str>,
20 hsm_group_members_opt: &[&str],
21) -> Result<Vec<String>, Error> {
22 let hsm_groups_user_has_access = hsm::group::utils::get_group_name_available(
23 shasta_token,
24 shasta_base_url,
25 shasta_root_cert,
26 socks5_proxy,
27 )
28 .await?;
29 let xnames_user_has_access =
37 hsm::group::utils::get_member_vec_from_hsm_name_vec(
38 shasta_token,
39 shasta_base_url,
40 shasta_root_cert,
41 socks5_proxy,
42 &hsm_groups_user_has_access,
43 )
44 .await?;
45 if hsm_group_members_opt
55 .iter()
56 .all(|hsm_member| xnames_user_has_access.contains(&hsm_member.to_string()))
57 {
58 Ok(
59 hsm_group_members_opt
60 .as_ref()
61 .iter()
62 .cloned()
63 .map(str::to_string)
64 .collect(),
65 )
66 } else {
67 return Err(Error::Message(format!(
68 "Can't access all or any of the HSM members '{}'.\nPlease choose members form the list of HSM groups below:\n{}\nExit",
69 hsm_group_members_opt.join(", "),
70 hsm_groups_user_has_access.join(", ")
71 )));
72 }
73}
74
75pub fn validate_nid_format_regex(node_vec: Vec<String>, regex: Regex) -> bool {
77 node_vec.iter().all(|nid| regex.is_match(nid))
78}
79
80pub fn validate_nid_format_vec(node_vec: Vec<String>) -> bool {
82 node_vec.iter().all(|nid| validate_nid_format(nid))
83}
84
85pub fn validate_nid_format(nid: &str) -> bool {
87 nid.to_lowercase().starts_with("nid")
88 && nid.len() == 9
89 && nid
90 .strip_prefix("nid")
91 .is_some_and(|nid_number| nid_number.chars().all(char::is_numeric))
92}
93
94pub fn validate_xname_format_regex(
96 node_vec: Vec<String>,
97 regex: Regex,
98) -> bool {
99 node_vec.iter().all(|nid| regex.is_match(nid))
100}
101
102pub fn validate_xname_format_vec(node_vec: Vec<String>) -> bool {
104 node_vec.iter().all(|nid| validate_xname_format(nid))
105}
106
107pub fn validate_xname_format(xname: &str) -> bool {
109 let xname_re =
110 Regex::new(r"^x\d{4}c[0-7]s([0-9]|[1-5][0-9]|6[0-4])b[0-1]n[0-7]$")
111 .unwrap();
112
113 xname_re.is_match(xname)
114}
115
116pub async fn validate_xnames_format_and_membership_agaisnt_single_hsm(
122 shasta_token: &str,
123 shasta_base_url: &str,
124 shasta_root_cert: &[u8],
125 socks5_proxy: Option<&str>,
126 xnames: &[&str],
127 hsm_group_name_opt: Option<&str>,
128) -> Result<bool, Error> {
129 let hsm_group_members: Vec<String> =
130 if let Some(hsm_group_name) = hsm_group_name_opt {
131 hsm::group::utils::get_member_vec_from_hsm_group_name(
132 shasta_token,
133 shasta_base_url,
134 shasta_root_cert,
135 socks5_proxy,
136 hsm_group_name,
137 )
138 .await?
139 } else {
140 Vec::new()
141 };
142
143 if xnames.iter().any(|&xname| {
144 !validate_xname_format(xname)
145 || (!hsm_group_members.is_empty()
146 && !hsm_group_members.contains(&xname.to_string()))
147 }) {
148 return Ok(false);
149 }
150
151 Ok(true)
152}
153
154pub async fn get_node_details(
158 shasta_token: &str,
159 shasta_base_url: &str,
160 shasta_root_cert: &[u8],
161 socks5_proxy: Option<&str>,
162 xname_list: Vec<String>,
163) -> Result<Vec<NodeDetails>, Error> {
164 let start = Instant::now();
165
166 let (
167 components_status_rslt,
168 node_boot_params_vec_rslt,
169 node_hsm_info_rslt,
170 cfs_session_vec_rslt,
171 ) = tokio::join!(
172 cfs::component::http_client::v2::get_multiple(
174 shasta_token,
175 shasta_base_url,
176 shasta_root_cert,
177 socks5_proxy,
178 &xname_list,
179 ),
180 bss::http_client::get_multiple(
182 shasta_token,
183 shasta_base_url,
184 shasta_root_cert,
185 socks5_proxy,
186 &xname_list,
187 ),
188 hsm::component::http_client::get_and_filter(
190 shasta_token,
191 shasta_base_url,
192 shasta_root_cert,
193 socks5_proxy,
194 &xname_list,
195 ),
196 cfs::session::get_and_sort(
198 shasta_token,
199 shasta_base_url,
200 shasta_root_cert,
201 socks5_proxy,
202 None,
203 None,
204 None,
205 None,
206 Some(true),
207 )
208 );
209
210 let node_hsm_info = node_hsm_info_rslt?;
211 let node_boot_params_vec = node_boot_params_vec_rslt?;
212 let cfs_session_vec = cfs_session_vec_rslt?;
213
214 let mut node_details_map = HashMap::new();
217 let mut tasks = tokio::task::JoinSet::new();
218
219 let sem = Arc::new(Semaphore::new(10)); for xname in xname_list {
222 let shasta_token_string = shasta_token.to_string();
223 let shasta_base_url_string = shasta_base_url.to_string();
224 let shasta_root_cert_vec = shasta_root_cert.to_vec();
225 let socks5_proxy_opt = socks5_proxy.map(str::to_owned);
226
227 let components_status = components_status_rslt.as_ref().unwrap();
228
229 let component_details_opt = components_status
231 .iter()
232 .find(|component_status| component_status.id.as_ref().eq(&Some(&xname)));
233
234 let component_details =
237 if let Some(component_details) = component_details_opt {
238 component_details
239 } else {
240 return Err(Error::Message(format!(
241 "ERROR - CFS component details for node {}.\nReason:\n{:#?}",
242 xname, component_details_opt
243 )));
244 };
245
246 let desired_configuration = &component_details.desired_config;
247 let configuration_status = &component_details.configuration_status;
248 let enabled = component_details.enabled;
249 let error_count = component_details.error_count.clone();
250
251 let node_hsm_info = node_hsm_info
253 .iter()
254 .find(|component| component.id.eq(&Some(xname.clone())))
255 .ok_or_else(|| Error::HsmComponentNotFound(xname.clone()))?;
256
257 let node_hsm_id = node_hsm_info
258 .id
259 .as_ref()
260 .ok_or_else(|| Error::HsmComponentIdNotDefined(xname.clone()))?;
261
262 let node_power_status = node_hsm_info
264 .state
265 .as_ref()
266 .ok_or_else(|| Error::HsmComponentPowerStateNotDefined(xname.clone()))?
267 .to_uppercase();
268
269 let nid = node_hsm_info
271 .nid
272 .ok_or_else(|| Error::HsmComponentNidNotDefined(node_hsm_id.clone()))?;
273
274 let node_nid = format!("nid{:0>6}", nid.to_string());
276
277 let (image_id_in_kernel_params, kernel_params): (String, String) =
281 if let Some(node_boot_params) =
282 bss::utils::find_boot_params_related_to_node(
283 &node_boot_params_vec,
284 &xname,
285 )
286 {
287 (node_boot_params.get_boot_image(), node_boot_params.params)
288 } else {
289 eprintln!("BSS boot parameters for node '{}' - NOT FOUND", xname);
290 ("Not found".to_string(), "Not found".to_string())
291 };
292
293 let cfs_session_related_to_image_id_opt =
295 cfs::session::utils::find_cfs_session_related_to_image_id(
296 &cfs_session_vec,
297 &image_id_in_kernel_params,
298 );
299
300 let cfs_configuration_boot = if let Some(cfs_session_related_to_image_id) =
301 cfs_session_related_to_image_id_opt
302 {
303 let session_name = cfs_session_related_to_image_id.name;
304
305 cfs_session_related_to_image_id
306 .configuration
307 .ok_or_else(|| {
308 Error::SessionConfigurationNotDefined(session_name.clone())
309 })?
310 .name
311 .ok_or_else(|| {
312 Error::SessionConfigurationNotDefined(session_name.clone())
313 })?
314 } else {
315 "Not found".to_string()
316 };
317
318 node_details_map
319 .entry(xname.clone())
320 .and_modify(|node_details: &mut NodeDetails| {
321 node_details.xname = xname.clone();
322 node_details.nid = node_nid.clone();
323 node_details.hsm = "".to_string();
324 node_details.power_status = node_power_status.clone();
325 node_details.desired_configuration =
326 desired_configuration.clone().unwrap();
327 node_details.configuration_status =
328 configuration_status.clone().unwrap();
329 node_details.enabled = enabled.as_ref().map(bool::to_string).unwrap();
330 node_details.error_count =
331 error_count.as_ref().map(u64::to_string).unwrap();
332 node_details.boot_image_id = image_id_in_kernel_params.clone();
333 node_details.boot_configuration = cfs_configuration_boot.clone();
334 node_details.kernel_params = kernel_params.clone();
335 })
336 .or_insert(NodeDetails {
337 xname: xname.clone(),
338 nid: node_nid,
339 hsm: "".to_string(),
340 power_status: node_power_status,
341 desired_configuration: desired_configuration.clone().unwrap(),
342 configuration_status: configuration_status.clone().unwrap(),
343 enabled: enabled.as_ref().map(bool::to_string).unwrap(),
344 error_count: error_count.as_ref().map(u64::to_string).unwrap(),
345 boot_image_id: image_id_in_kernel_params,
346 boot_configuration: cfs_configuration_boot,
347 kernel_params,
348 });
349
350 let permit = Arc::clone(&sem).acquire_owned().await;
351
352 tasks.spawn(async move {
353 let _permit = permit; hsm::memberships::http_client::get_xname(
356 &shasta_token_string,
357 &shasta_base_url_string,
358 &shasta_root_cert_vec,
359 socks5_proxy_opt.as_deref(),
360 &xname,
361 )
362 .await
363 });
364 }
365
366 while let Some(message) = tasks.join_next().await {
367 let node_membership = message??;
368
369 let node_details = NodeDetails {
370 xname: "".to_string(),
371 nid: "".to_string(),
372 hsm: node_membership.group_labels.join(", "),
373 power_status: "".to_string(),
374 desired_configuration: "".to_string(),
375 configuration_status: "".to_string(),
376 enabled: "".to_string(),
377 error_count: "".to_string(),
378 boot_image_id: "".to_string(),
379 boot_configuration: "".to_string(),
380 kernel_params: "".to_string(),
381 };
382
383 node_details_map
384 .entry(node_membership.id.clone())
385 .and_modify(|node_details: &mut NodeDetails| {
386 node_details.hsm = node_membership.group_labels.join(", ")
387 })
388 .or_insert(node_details);
389 }
390
391 let duration = start.elapsed();
392 log::info!("Time elapsed to get node details is: {:?}", duration);
393 Ok(node_details_map.into_values().collect())
396}
397
398pub fn nodes_to_string_format_one_line(nodes: Option<&Vec<Value>>) -> String {
399 if let Some(nodes_content) = nodes {
400 nodes_to_string_format_discrete_columns(nodes, nodes_content.len() + 1)
401 } else {
402 "".to_string()
403 }
404}
405
406pub fn nodes_to_string_format_discrete_columns(
407 nodes: Option<&Vec<Value>>,
408 num_columns: usize,
409) -> String {
410 let mut members: String;
411
412 match nodes {
413 Some(nodes) if !nodes.is_empty() => {
414 members = nodes
415 .first()
416 .and_then(Value::as_str)
417 .map(str::to_string)
418 .unwrap(); for (i, _) in nodes.iter().enumerate().skip(1) {
421 if i % num_columns == 0 {
423 members.push_str(",\n");
426 } else {
427 members.push(',');
428 }
429
430 members.push_str(nodes.get(i).and_then(Value::as_str).unwrap());
431 }
432 }
433 _ => members = "".to_string(),
434 }
435
436 members
437}