feat: Invetory inspection score that dumps the inventory in a readable way, also quite a few fixes on okd installation scores

This commit is contained in:
Jean-Gabriel Gill-Couture 2025-09-04 23:34:23 -04:00
parent f31d21f9da
commit 1640e6d7a3
10 changed files with 174 additions and 8 deletions

View File

@ -149,6 +149,98 @@ impl PhysicalHost {
parts.join(" | ") parts.join(" | ")
} }
pub fn parts_list(&self) -> String {
let PhysicalHost {
id,
category,
network,
storage,
labels,
memory_modules,
cpus,
} = self;
let mut parts_list = String::new();
parts_list.push_str("\n\n=====================");
parts_list.push_str(&format!("\nHost ID {id}"));
parts_list.push_str("\n=====================");
parts_list.push_str("\n\n=====================");
parts_list.push_str(&format!("\nCPU count {}", cpus.len()));
parts_list.push_str("\n=====================");
cpus.iter().for_each(|c| {
let CPU {
model,
vendor,
cores,
threads,
frequency_mhz,
} = c;
parts_list.push_str(&format!(
"\n{vendor} {model}, {cores}/{threads} {}Ghz",
*frequency_mhz as f64 / 1000.0
));
});
parts_list.push_str("\n\n=====================");
parts_list.push_str(&format!("\nNetwork Interfaces count {}", network.len()));
parts_list.push_str("\n=====================");
network.iter().for_each(|nic| {
parts_list.push_str(&format!(
"\nNic({} {}Gbps mac({}) ipv4({}), ipv6({})",
nic.name,
nic.speed_mbps.unwrap_or(0) / 1000,
nic.mac_address,
nic.ipv4_addresses.join(","),
nic.ipv6_addresses.join(",")
));
});
parts_list.push_str("\n\n=====================");
parts_list.push_str(&format!("\nStorage drives count {}", storage.len()));
parts_list.push_str("\n=====================");
storage.iter().for_each(|drive| {
let StorageDrive {
name,
model,
serial,
size_bytes,
logical_block_size: _,
physical_block_size: _,
rotational: _,
wwn: _,
interface_type,
smart_status,
} = drive;
parts_list.push_str(&format!(
"\n{name} {}Gb {model} {interface_type} smart({smart_status:?}) {serial}",
size_bytes / 1000 / 1000 / 1000
));
});
parts_list.push_str("\n\n=====================");
parts_list.push_str(&format!("\nMemory modules count {}", memory_modules.len()));
parts_list.push_str("\n=====================");
memory_modules.iter().for_each(|mem| {
let MemoryModule {
size_bytes,
speed_mhz,
manufacturer,
part_number,
serial_number,
rank,
} = mem;
parts_list.push_str(&format!(
"\n{}Gb, {}Mhz, Manufacturer ({}), Part Number ({})",
size_bytes / 1000 / 1000 / 1000,
speed_mhz.unwrap_or(0),
manufacturer.as_ref().unwrap_or(&String::new()),
part_number.as_ref().unwrap_or(&String::new()),
));
});
parts_list
}
pub fn cluster_mac(&self) -> MacAddress { pub fn cluster_mac(&self) -> MacAddress {
self.network self.network
.first() .first()

View File

@ -18,6 +18,7 @@ impl InventoryFilter {
use derive_new::new; use derive_new::new;
use log::info; use log::info;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use strum::EnumIter;
use crate::hardware::{ManagementInterface, ManualManagementInterface}; use crate::hardware::{ManagementInterface, ManualManagementInterface};
@ -63,7 +64,7 @@ impl Inventory {
} }
} }
#[derive(Debug, Serialize, Deserialize, sqlx::Type, Clone)] #[derive(Debug, Serialize, Deserialize, sqlx::Type, Clone, EnumIter)]
pub enum HostRole { pub enum HostRole {
Bootstrap, Bootstrap,
ControlPlane, ControlPlane,

View File

@ -29,7 +29,7 @@ pub trait InventoryRepository: Send + Sync + 'static {
async fn save(&self, host: &PhysicalHost) -> Result<(), RepoError>; async fn save(&self, host: &PhysicalHost) -> Result<(), RepoError>;
async fn get_latest_by_id(&self, host_id: &str) -> Result<Option<PhysicalHost>, RepoError>; async fn get_latest_by_id(&self, host_id: &str) -> Result<Option<PhysicalHost>, RepoError>;
async fn get_all_hosts(&self) -> Result<Vec<PhysicalHost>, RepoError>; async fn get_all_hosts(&self) -> Result<Vec<PhysicalHost>, RepoError>;
async fn get_host_for_role(&self, role: HostRole) -> Result<Vec<PhysicalHost>, RepoError>; async fn get_host_for_role(&self, role: &HostRole) -> Result<Vec<PhysicalHost>, RepoError>;
async fn save_role_mapping( async fn save_role_mapping(
&self, &self,
role: &HostRole, role: &HostRole,

View File

@ -109,7 +109,7 @@ impl InventoryRepository for SqliteInventoryRepository {
Ok(()) Ok(())
} }
async fn get_host_for_role(&self, role: HostRole) -> Result<Vec<PhysicalHost>, RepoError> { async fn get_host_for_role(&self, role: &HostRole) -> Result<Vec<PhysicalHost>, RepoError> {
struct HostIdRow { struct HostIdRow {
host_id: String, host_id: String,
} }

View File

@ -76,7 +76,7 @@ impl<T: Topology> Interpret<T> for DiscoverHostForRoleInterpret {
Ok(choice) => { Ok(choice) => {
info!("Selected {} as the bootstrap node.", choice.summary()); info!("Selected {} as the bootstrap node.", choice.summary());
host_repo host_repo
.save_role_mapping(&HostRole::Bootstrap, &choice) .save_role_mapping(&self.score.role, &choice)
.await?; .await?;
host = choice; host = choice;
break; break;

View File

@ -0,0 +1,72 @@
use async_trait::async_trait;
use harmony_types::id::Id;
use log::info;
use serde::{Deserialize, Serialize};
use strum::IntoEnumIterator;
use crate::{
data::Version,
infra::inventory::InventoryRepositoryFactory,
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
inventory::{HostRole, Inventory},
score::Score,
topology::Topology,
};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct InspectInventoryScore {}
impl<T: Topology> Score<T> for InspectInventoryScore {
fn name(&self) -> String {
"InspectInventoryScore".to_string()
}
#[doc(hidden)]
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
Box::new(InspectInventoryInterpret {})
}
}
#[derive(Debug)]
pub struct InspectInventoryInterpret;
#[async_trait]
impl<T: Topology> Interpret<T> for InspectInventoryInterpret {
async fn execute(
&self,
_inventory: &Inventory,
_topology: &T,
) -> Result<Outcome, InterpretError> {
let repo = InventoryRepositoryFactory::build().await?;
for role in HostRole::iter() {
info!("Inspecting hosts for role {role:?}");
let hosts = repo.get_host_for_role(&role).await?;
info!("Hosts with role {role:?} : {}", hosts.len());
hosts.iter().enumerate().for_each(|(idx, h)| {
info!(
"Found host index {idx} with role {role:?} => \n{}\n{}",
h.summary(),
h.parts_list()
)
});
}
Ok(Outcome::success(
"Inventory inspection complete".to_string(),
))
}
fn get_name(&self) -> InterpretName {
InterpretName::Custom("InspectInventoryInterpret")
}
fn get_version(&self) -> Version {
todo!()
}
fn get_status(&self) -> InterpretStatus {
todo!()
}
fn get_children(&self) -> Vec<Id> {
todo!()
}
}

View File

@ -1,4 +1,5 @@
mod discovery; mod discovery;
pub mod inspect;
pub use discovery::*; pub use discovery::*;
use async_trait::async_trait; use async_trait::async_trait;

View File

@ -100,7 +100,7 @@ When you can dig them, confirm to continue.
let repo = InventoryRepositoryFactory::build().await?; let repo = InventoryRepositoryFactory::build().await?;
while bootstrap_host.is_none() { while bootstrap_host.is_none() {
let hosts = repo.get_host_for_role(HostRole::Bootstrap).await?; let hosts = repo.get_host_for_role(&HostRole::Bootstrap).await?;
bootstrap_host = hosts.into_iter().next().to_owned(); bootstrap_host = hosts.into_iter().next().to_owned();
DiscoverHostForRoleScore { DiscoverHostForRoleScore {
role: HostRole::Bootstrap, role: HostRole::Bootstrap,

View File

@ -67,7 +67,7 @@ impl OKDSetup02BootstrapInterpret {
async fn get_bootstrap_node(&self) -> Result<PhysicalHost, InterpretError> { async fn get_bootstrap_node(&self) -> Result<PhysicalHost, InterpretError> {
let repo = InventoryRepositoryFactory::build().await?; let repo = InventoryRepositoryFactory::build().await?;
match repo match repo
.get_host_for_role(HostRole::Bootstrap) .get_host_for_role(&HostRole::Bootstrap)
.await? .await?
.into_iter() .into_iter()
.next() .next()

View File

@ -66,7 +66,7 @@ impl OKDSetup03ControlPlaneInterpret {
) -> Result<Vec<PhysicalHost>, InterpretError> { ) -> Result<Vec<PhysicalHost>, InterpretError> {
const REQUIRED_HOSTS: usize = 3; const REQUIRED_HOSTS: usize = 3;
let repo = InventoryRepositoryFactory::build().await?; let repo = InventoryRepositoryFactory::build().await?;
let mut control_plane_hosts = repo.get_host_for_role(HostRole::ControlPlane).await?; let mut control_plane_hosts = repo.get_host_for_role(&HostRole::ControlPlane).await?;
while control_plane_hosts.len() < REQUIRED_HOSTS { while control_plane_hosts.len() < REQUIRED_HOSTS {
info!( info!(
@ -80,7 +80,7 @@ impl OKDSetup03ControlPlaneInterpret {
} }
.interpret(inventory, topology) .interpret(inventory, topology)
.await?; .await?;
control_plane_hosts = repo.get_host_for_role(HostRole::ControlPlane).await?; control_plane_hosts = repo.get_host_for_role(&HostRole::ControlPlane).await?;
} }
if control_plane_hosts.len() < REQUIRED_HOSTS { if control_plane_hosts.len() < REQUIRED_HOSTS {