use manta_backend_dispatcher::error::Error;
use manta_backend_dispatcher::interfaces::hsm::{
component::ComponentTrait, group::GroupTrait,
hardware_inventory::HardwareInventory,
};
use manta_backend_dispatcher::types::{
ComponentArrayPostArray, ComponentCreate, HWInventoryByLocationList,
};
use manta_shared::types::dto::NodeDetails;
use std::path::PathBuf;
use crate::server::common::app_context::InfraContext;
use crate::service::authorization::validate_user_group_members_access;
use crate::service::node_details;
use crate::service::node_ops::from_user_hosts_expression_to_xname_vec;
pub use manta_shared::types::api::node::GetNodesParams;
pub async fn get_nodes(
infra: &InfraContext<'_>,
token: &str,
params: &GetNodesParams,
) -> Result<Vec<NodeDetails>, Error> {
let node_list = from_user_hosts_expression_to_xname_vec(
infra,
token,
¶ms.host_expression,
params.include_siblings,
)
.await?;
if node_list.is_empty() {
return Err(Error::BadRequest(
"The list of nodes to operate is empty. Nothing to do".to_string(),
));
}
validate_user_group_members_access(infra, token, &node_list).await?;
let mut node_details_list =
node_details::get_node_details(infra, token, &node_list).await?;
if let Some(ref status) = params.status_filter {
node_details_list.retain(|nd| {
nd.power_status.eq_ignore_ascii_case(status)
|| nd.configuration_status.eq_ignore_ascii_case(status)
});
}
node_details_list.sort_by(|a, b| a.xname.cmp(&b.xname));
Ok(node_details_list)
}
pub async fn delete_node(
infra: &InfraContext<'_>,
token: &str,
id: &str,
) -> Result<(), Error> {
validate_user_group_members_access(infra, token, &[id.to_string()]).await?;
infra.backend.delete_node(token, id).await.map(|_| ())
}
pub async fn add_node(
infra: &InfraContext<'_>,
token: &str,
id: &str,
group: &str,
enabled: bool,
arch_opt: Option<String>,
hardware_file_path: Option<&PathBuf>,
) -> Result<(), Error> {
validate_user_group_members_access(infra, token, &[id.to_string()]).await?;
let component = ComponentCreate {
id: id.to_string(),
state: "Unknown".to_string(),
flag: None,
enabled: Some(enabled),
software_status: None,
role: None,
sub_role: None,
nid: None,
subtype: None,
net_type: None,
arch: arch_opt,
class: None,
};
let components = ComponentArrayPostArray {
components: vec![component],
force: Some(true),
};
infra.backend.post_nodes(token, components).await?;
tracing::info!("Node saved '{}'", id);
let hw_inventory_opt: Option<HWInventoryByLocationList> =
if let Some(hardware_file) = hardware_file_path {
match read_hw_inventory(hardware_file).await {
Ok(inv) => Some(inv),
Err(e) => {
rollback_node(infra, token, id).await;
return Err(e);
}
}
} else {
None
};
if let Some(hw_inventory) = hw_inventory_opt {
tracing::info!("Adding hardware inventory for '{}'", id);
if let Err(error) = infra
.backend
.post_inventory_hardware(token, hw_inventory)
.await
.map(|_| ())
{
rollback_node(infra, token, id).await;
return Err(error);
}
}
if let Err(error) = infra
.backend
.post_member(token, group, id)
.await
.map(|_| ())
{
rollback_node(infra, token, id).await;
return Err(error);
}
Ok(())
}
async fn read_hw_inventory(
path: &PathBuf,
) -> Result<HWInventoryByLocationList, Error> {
let bytes = tokio::fs::read(path).await?;
let value: serde_json::Value = serde_json::from_slice(&bytes)?;
let inv = serde_json::from_value::<HWInventoryByLocationList>(value)?;
Ok(inv)
}
async fn rollback_node(infra: &InfraContext<'_>, token: &str, id: &str) {
tracing::warn!("Rolling back: attempting to delete node '{}'", id);
let delete_node_rslt = infra.backend.delete_node(token, id).await;
if delete_node_rslt.is_ok() {
tracing::info!("Rollback: node '{}' deleted", id);
}
}