use crate::domain::{
CpuTopology, HardwareInfo, HardwareReport, InterfaceIPs, PublishConfig, PublishError,
ReportConfig, ReportError, SystemSummary,
};
use crate::ports::{
ConfigurationProvider, DataPublisher, HardwareReportingService, SystemInfoProvider,
};
use async_trait::async_trait;
use std::collections::HashMap;
use std::sync::Arc;
struct SystemSummaryParams<'a> {
system_info: crate::domain::SystemInfo,
memory: &'a crate::domain::MemoryInfo,
storage: &'a crate::domain::StorageInfo,
gpus: &'a crate::domain::GpuInfo,
network: &'a crate::domain::NetworkInfo,
bios: crate::domain::BiosInfo,
chassis: crate::domain::ChassisInfo,
motherboard: crate::domain::MotherboardInfo,
numa_topology: HashMap<String, crate::domain::NumaNode>,
filesystems: Vec<String>,
cpu: &'a crate::domain::CpuInfo,
}
pub struct HardwareCollectionService {
system_provider: Arc<dyn SystemInfoProvider>,
data_publisher: Arc<dyn DataPublisher>,
#[allow(dead_code)]
config_provider: Arc<dyn ConfigurationProvider>,
}
impl HardwareCollectionService {
pub fn new(
system_provider: Arc<dyn SystemInfoProvider>,
data_publisher: Arc<dyn DataPublisher>,
config_provider: Arc<dyn ConfigurationProvider>,
) -> Self {
Self {
system_provider,
data_publisher,
config_provider,
}
}
async fn collect_hardware_info(&self) -> Result<(HardwareInfo, SystemSummary), ReportError> {
let (cpu_result, memory_result, storage_result, gpu_result, network_result) = tokio::join!(
self.system_provider.get_cpu_info(),
self.system_provider.get_memory_info(),
self.system_provider.get_storage_info(),
self.system_provider.get_gpu_info(),
self.system_provider.get_network_info(),
);
let cpu = cpu_result
.map_err(|e| ReportError::GenerationFailed(format!("CPU collection failed: {e}")))?;
let memory = memory_result
.map_err(|e| ReportError::GenerationFailed(format!("Memory collection failed: {e}")))?;
let storage = storage_result.map_err(|e| {
ReportError::GenerationFailed(format!("Storage collection failed: {e}"))
})?;
let gpus = gpu_result
.map_err(|e| ReportError::GenerationFailed(format!("GPU collection failed: {e}")))?;
let network = network_result.map_err(|e| {
ReportError::GenerationFailed(format!("Network collection failed: {e}"))
})?;
let hardware = HardwareInfo {
cpu: cpu.clone(),
memory: memory.clone(),
storage: storage.clone(),
gpus: gpus.clone(),
};
let (
system_info_result,
bios_result,
chassis_result,
motherboard_result,
numa_result,
filesystems_result,
) = tokio::join!(
self.system_provider.get_system_info(),
self.system_provider.get_bios_info(),
self.system_provider.get_chassis_info(),
self.system_provider.get_motherboard_info(),
self.system_provider.get_numa_topology(),
self.system_provider.get_filesystems(),
);
let system_info = system_info_result.map_err(|e| {
ReportError::GenerationFailed(format!("System info collection failed: {e}"))
})?;
let bios = bios_result
.map_err(|e| ReportError::GenerationFailed(format!("BIOS collection failed: {e}")))?;
let chassis = chassis_result.map_err(|e| {
ReportError::GenerationFailed(format!("Chassis collection failed: {e}"))
})?;
let motherboard = motherboard_result.map_err(|e| {
ReportError::GenerationFailed(format!("Motherboard collection failed: {e}"))
})?;
let numa_topology = numa_result
.map_err(|e| ReportError::GenerationFailed(format!("NUMA collection failed: {e}")))?;
let filesystems = filesystems_result.map_err(|e| {
ReportError::GenerationFailed(format!("Filesystem collection failed: {e}"))
})?;
let summary = self
.create_system_summary(SystemSummaryParams {
system_info,
memory: &memory,
storage: &storage,
gpus: &gpus,
network: &network,
bios,
chassis,
motherboard,
numa_topology,
filesystems,
cpu: &cpu,
})
.await?;
Ok((hardware, summary))
}
async fn create_system_summary(
&self,
params: SystemSummaryParams<'_>,
) -> Result<SystemSummary, ReportError> {
let cpu_topology = CpuTopology {
total_cores: params.cpu.cores * params.cpu.sockets,
total_threads: params.cpu.cores * params.cpu.sockets * params.cpu.threads,
sockets: params.cpu.sockets,
cores_per_socket: params.cpu.cores,
threads_per_core: params.cpu.threads,
numa_nodes: params.numa_topology.len() as u32,
cpu_model: params.cpu.model.clone(),
};
let total_storage_tb = self.calculate_total_storage_tb(¶ms.storage.devices);
let cpu_summary = format!(
"{} ({} Socket{}, {} Core{}/Socket, {} Thread{}/Core, {} NUMA Node{})",
params.cpu.model,
params.cpu.sockets,
if params.cpu.sockets == 1 { "" } else { "s" },
params.cpu.cores,
if params.cpu.cores == 1 { "" } else { "s" },
params.cpu.threads,
if params.cpu.threads == 1 { "" } else { "s" },
params.numa_topology.len(),
if params.numa_topology.len() == 1 {
""
} else {
"s"
}
);
let memory_config = format!("{} @ {}", params.memory.type_, params.memory.speed);
Ok(SystemSummary {
system_info: params.system_info,
total_memory: params.memory.total.clone(),
memory_config,
total_storage: self.format_total_storage(¶ms.storage.devices),
total_storage_tb,
filesystems: params.filesystems,
bios: params.bios,
chassis: params.chassis,
motherboard: params.motherboard,
total_gpus: params.gpus.devices.len(),
total_nics: params.network.interfaces.len(),
numa_topology: params.numa_topology,
cpu_topology,
cpu_summary,
})
}
fn calculate_total_storage_tb(&self, devices: &[crate::domain::StorageDevice]) -> f64 {
devices
.iter()
.map(|device| self.parse_storage_size_to_bytes(&device.size))
.sum::<u64>() as f64
/ (1024.0 * 1024.0 * 1024.0 * 1024.0) }
fn format_total_storage(&self, devices: &[crate::domain::StorageDevice]) -> String {
if devices.is_empty() {
return "No storage devices found".to_string();
}
let total_tb = self.calculate_total_storage_tb(devices);
if total_tb >= 1.0 {
format!("{total_tb:.1} TB")
} else {
let total_gb = total_tb * 1024.0;
format!("{total_gb:.0} GB")
}
}
fn parse_storage_size_to_bytes(&self, size: &str) -> u64 {
if size.contains("TB") {
if let Some(num_str) = size.split_whitespace().next() {
if let Ok(num) = num_str.parse::<f64>() {
return (num * 1024.0 * 1024.0 * 1024.0 * 1024.0) as u64;
}
}
} else if size.contains("GB") {
if let Some(num_str) = size.split_whitespace().next() {
if let Ok(num) = num_str.parse::<f64>() {
return (num * 1024.0 * 1024.0 * 1024.0) as u64;
}
}
}
0
}
async fn get_network_identity(
&self,
) -> Result<(String, String, Vec<InterfaceIPs>), ReportError> {
let hostname = self.system_provider.get_hostname().await.map_err(|e| {
ReportError::GenerationFailed(format!("Hostname collection failed: {e}"))
})?;
let fqdn =
self.system_provider.get_fqdn().await.map_err(|e| {
ReportError::GenerationFailed(format!("FQDN collection failed: {e}"))
})?;
let os_ip = Vec::new();
Ok((hostname, fqdn, os_ip))
}
}
#[async_trait]
impl HardwareReportingService for HardwareCollectionService {
async fn generate_report(&self, _config: ReportConfig) -> Result<HardwareReport, ReportError> {
let (network_result, hardware_result) =
tokio::join!(self.get_network_identity(), self.collect_hardware_info());
let (hostname, fqdn, os_ip) = network_result?;
let (hardware, summary) = hardware_result?;
let network = self.system_provider.get_network_info().await.map_err(|e| {
ReportError::GenerationFailed(format!("Network collection failed: {e}"))
})?;
let report = HardwareReport {
summary,
hostname,
fqdn,
os_ip,
bmc_ip: None, bmc_mac: None, hardware,
network,
};
Ok(report)
}
async fn publish_report(
&self,
report: &HardwareReport,
config: &PublishConfig,
) -> Result<(), PublishError> {
self.data_publisher.publish(report, config).await
}
async fn validate_dependencies(&self) -> Result<Vec<String>, ReportError> {
self.system_provider
.get_missing_dependencies()
.await
.map_err(|e| {
ReportError::GenerationFailed(format!("Dependency validation failed: {e}"))
})
}
async fn check_privileges(&self) -> Result<bool, ReportError> {
self.system_provider
.has_required_privileges()
.await
.map_err(|e| ReportError::GenerationFailed(format!("Privilege check failed: {e}")))
}
}