diff --git a/harmony/src/domain/hardware/mod.rs b/harmony/src/domain/hardware/mod.rs index 20038d2..52ab1f8 100644 --- a/harmony/src/domain/hardware/mod.rs +++ b/harmony/src/domain/hardware/mod.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use derive_new::new; use harmony_inventory_agent::hwinfo::{CPU, MemoryModule, NetworkInterface, StorageDrive}; use harmony_types::net::MacAddress; -use serde::{Deserialize, Serialize, Serializer, ser::SerializeStruct}; +use serde::{Deserialize, Serialize}; use serde_value::Value; pub type HostGroup = Vec; @@ -35,7 +35,120 @@ impl PhysicalHost { } pub fn summary(&self) -> String { - todo!(); + let mut parts = Vec::new(); + + // Part 1: System Model (from labels) or Category as a fallback + let model = self + .labels + .iter() + .find(|l| l.name == "system-product-name" || l.name == "model") + .map(|l| l.value.clone()) + .unwrap_or_else(|| self.category.to_string()); + parts.push(model); + + // Part 2: CPU Information + if !self.cpus.is_empty() { + let cpu_count = self.cpus.len(); + let total_cores = self.cpus.iter().map(|c| c.cores).sum::(); + let total_threads = self.cpus.iter().map(|c| c.threads).sum::(); + let model_name = &self.cpus[0].model; + + let cpu_summary = if cpu_count > 1 { + format!( + "{}x {} ({}c/{}t)", + cpu_count, model_name, total_cores, total_threads + ) + } else { + format!("{} ({}c/{}t)", model_name, total_cores, total_threads) + }; + parts.push(cpu_summary); + } + + // Part 3: Memory Information + if !self.memory_modules.is_empty() { + let total_mem_bytes = self + .memory_modules + .iter() + .map(|m| m.size_bytes) + .sum::(); + let total_mem_gb = (total_mem_bytes as f64 / (1024.0 * 1024.0 * 1024.0)).round() as u64; + + // Find the most common speed among modules + let mut speeds = std::collections::HashMap::new(); + for module in &self.memory_modules { + if let Some(speed) = module.speed_mhz { + *speeds.entry(speed).or_insert(0) += 1; + } + } + let common_speed = speeds + .into_iter() + .max_by_key(|&(_, count)| count) + .map(|(speed, _)| speed); + + if let Some(speed) = common_speed { + parts.push(format!("{} GB RAM @ {}MHz", total_mem_gb, speed)); + } else { + parts.push(format!("{} GB RAM", total_mem_gb)); + } + } + + // Part 4: Storage Information + if !self.storage.is_empty() { + let total_storage_bytes = self.storage.iter().map(|d| d.size_bytes).sum::(); + let drive_count = self.storage.len(); + let first_drive_model = &self.storage[0].model; + + // Helper to format bytes into TB or GB + let format_storage = |bytes: u64| { + let tb = bytes as f64 / (1024.0 * 1024.0 * 1024.0 * 1024.0); + if tb >= 1.0 { + format!("{:.2} TB", tb) + } else { + let gb = bytes as f64 / (1024.0 * 1024.0 * 1024.0); + format!("{:.0} GB", gb) + } + }; + + let storage_summary = if drive_count > 1 { + format!( + "{} Storage ({}x {})", + format_storage(total_storage_bytes), + drive_count, + first_drive_model + ) + } else { + format!( + "{} Storage ({})", + format_storage(total_storage_bytes), + first_drive_model + ) + }; + parts.push(storage_summary); + } + + // Part 5: Network Information + // Prioritize an "up" interface with an IPv4 address + let best_nic = self + .network + .iter() + .find(|n| n.is_up && !n.ipv4_addresses.is_empty()) + .or_else(|| self.network.first()); + + if let Some(nic) = best_nic { + let speed = nic + .speed_mbps + .map(|s| format!("{}Gbps", s / 1000)) + .unwrap_or_else(|| "N/A".to_string()); + let mac = nic.mac_address.to_string(); + let nic_summary = if let Some(ip) = nic.ipv4_addresses.first() { + format!("NIC: {} ({}, {})", speed, ip, mac) + } else { + format!("NIC: {} ({})", speed, mac) + }; + parts.push(nic_summary); + } + + parts.join(" | ") } pub fn cluster_mac(&self) -> MacAddress { diff --git a/harmony/src/modules/inventory/mod.rs b/harmony/src/modules/inventory/mod.rs index b6952c6..335e82f 100644 --- a/harmony/src/modules/inventory/mod.rs +++ b/harmony/src/modules/inventory/mod.rs @@ -71,7 +71,6 @@ impl Interpret for DiscoverInventoryAgentInterpret { info!("Getting host inventory on service at {address} port {port}"); tokio::task::spawn(async move { - todo!("are we here"); info!("Getting inventory for host {address} {port}"); let host = harmony_inventory_agent::client::get_host_inventory(&address, port) @@ -126,20 +125,9 @@ impl Interpret for DiscoverInventoryAgentInterpret { }, ) .await; - info!("Launched inventory host information gathering"); - info!( - "tokio current {:?}", - tokio::runtime::Handle::try_current().unwrap() - ); - tokio::spawn(async { - info!("Spawned a sleeper"); - tokio::time::sleep(Duration::from_millis(100)).await; - info!("done a sleeper"); - }); - tokio::time::sleep(Duration::from_millis(1000)).await; Ok(Outcome { - status: InterpretStatus::RUNNING, - message: "Launched discovery process".to_string(), + status: InterpretStatus::SUCCESS, + message: "Discovery process completed successfully".to_string(), }) } diff --git a/harmony_inventory_agent/src/local_presence/discover.rs b/harmony_inventory_agent/src/local_presence/discover.rs index 04a65e7..bda0049 100644 --- a/harmony_inventory_agent/src/local_presence/discover.rs +++ b/harmony_inventory_agent/src/local_presence/discover.rs @@ -16,7 +16,7 @@ where // The receiver will be a stream of events. let receiver = mdns.browse(SERVICE_NAME).expect("Failed to browse"); - tokio::spawn(async move { + tokio::task::spawn_blocking(move || { while let Ok(event) = receiver.recv() { if let Err(e) = on_event(event.clone()) { error!("Event callback failed : {e}"); diff --git a/migrations/20250830163356_Physical_hosts.sql b/migrations/20250830163356_Physical_hosts.sql new file mode 100644 index 0000000..a07bdce --- /dev/null +++ b/migrations/20250830163356_Physical_hosts.sql @@ -0,0 +1,8 @@ +-- Add migration script here +CREATE TABLE IF NOT EXISTS physical_hosts ( + version_id TEXT PRIMARY KEY NOT NULL, + id TEXT NOT NULL, + data JSON NOT NULL +); +CREATE INDEX IF NOT EXISTS idx_host_id_time +ON physical_hosts (id, version_id DESC);