manta_server/service/
node.rs1use manta_backend_dispatcher::error::Error;
11use manta_backend_dispatcher::interfaces::hsm::{
12 component::ComponentTrait, group::GroupTrait,
13 hardware_inventory::HardwareInventory,
14};
15use manta_backend_dispatcher::types::{
16 ComponentArrayPostArray, ComponentCreate, HWInventoryByLocationList,
17};
18use manta_shared::types::dto::NodeDetails;
19use std::path::PathBuf;
20
21use crate::server::common::app_context::InfraContext;
22use crate::service::authorization::validate_user_group_members_access;
23use crate::service::node_details;
24use crate::service::node_ops::from_user_hosts_expression_to_xname_vec;
25pub use manta_shared::types::api::node::GetNodesParams;
26
27pub async fn get_nodes(
39 infra: &InfraContext<'_>,
40 token: &str,
41 params: &GetNodesParams,
42) -> Result<Vec<NodeDetails>, Error> {
43 let node_list = from_user_hosts_expression_to_xname_vec(
44 infra,
45 token,
46 ¶ms.host_expression,
47 params.include_siblings,
48 )
49 .await?;
50
51 if node_list.is_empty() {
52 return Err(Error::BadRequest(
53 "The list of nodes to operate is empty. Nothing to do".to_string(),
54 ));
55 }
56
57 validate_user_group_members_access(infra, token, &node_list).await?;
59
60 let mut node_details_list =
61 node_details::get_node_details(infra, token, &node_list).await?;
62
63 if let Some(ref status) = params.status_filter {
65 node_details_list.retain(|nd| {
66 nd.power_status.eq_ignore_ascii_case(status)
67 || nd.configuration_status.eq_ignore_ascii_case(status)
68 });
69 }
70
71 node_details_list.sort_by(|a, b| a.xname.cmp(&b.xname));
72
73 Ok(node_details_list)
74}
75
76pub async fn delete_node(
84 infra: &InfraContext<'_>,
85 token: &str,
86 id: &str,
87) -> Result<(), Error> {
88 validate_user_group_members_access(infra, token, &[id.to_string()]).await?;
89
90 infra.backend.delete_node(token, id).await.map(|_| ())
91}
92
93pub async fn add_node(
104 infra: &InfraContext<'_>,
105 token: &str,
106 id: &str,
107 group: &str,
108 enabled: bool,
109 arch_opt: Option<String>,
110 hardware_file_path: Option<&PathBuf>,
111) -> Result<(), Error> {
112 validate_user_group_members_access(infra, token, &[id.to_string()]).await?;
113
114 let component = ComponentCreate {
116 id: id.to_string(),
117 state: "Unknown".to_string(),
118 flag: None,
119 enabled: Some(enabled),
120 software_status: None,
121 role: None,
122 sub_role: None,
123 nid: None,
124 subtype: None,
125 net_type: None,
126 arch: arch_opt,
127 class: None,
128 };
129
130 let components = ComponentArrayPostArray {
131 components: vec![component],
132 force: Some(true),
133 };
134
135 infra.backend.post_nodes(token, components).await?;
136
137 tracing::info!("Node saved '{}'", id);
138
139 let hw_inventory_opt: Option<HWInventoryByLocationList> =
149 if let Some(hardware_file) = hardware_file_path {
150 match read_hw_inventory(hardware_file).await {
151 Ok(inv) => Some(inv),
152 Err(e) => {
153 rollback_node(infra, token, id).await;
154 return Err(e);
155 }
156 }
157 } else {
158 None
159 };
160
161 if let Some(hw_inventory) = hw_inventory_opt {
162 tracing::info!("Adding hardware inventory for '{}'", id);
163 if let Err(error) = infra
164 .backend
165 .post_inventory_hardware(token, hw_inventory)
166 .await
167 .map(|_| ())
168 {
169 rollback_node(infra, token, id).await;
170 return Err(error);
171 }
172 }
173
174 if let Err(error) = infra
176 .backend
177 .post_member(token, group, id)
178 .await
179 .map(|_| ())
180 {
181 rollback_node(infra, token, id).await;
182 return Err(error);
183 }
184
185 Ok(())
186}
187
188async fn read_hw_inventory(
194 path: &PathBuf,
195) -> Result<HWInventoryByLocationList, Error> {
196 let bytes = tokio::fs::read(path).await?;
197 let value: serde_json::Value = serde_json::from_slice(&bytes)?;
198 let inv = serde_json::from_value::<HWInventoryByLocationList>(value)?;
199 Ok(inv)
200}
201
202async fn rollback_node(infra: &InfraContext<'_>, token: &str, id: &str) {
204 tracing::warn!("Rolling back: attempting to delete node '{}'", id);
205 let delete_node_rslt = infra.backend.delete_node(token, id).await;
206 if delete_node_rslt.is_ok() {
207 tracing::info!("Rollback: node '{}' deleted", id);
208 }
209}