From 5ab58f025330488e60f89d4a40976815059ce24a Mon Sep 17 00:00:00 2001 From: Willem Date: Wed, 22 Oct 2025 14:39:12 -0400 Subject: [PATCH] fix: added impl node exporter for hacluster topology and dummy infra --- examples/nanodc/src/main.rs | 4 +- examples/okd_installation/src/topology.rs | 1 + examples/okd_pxe/src/topology.rs | 1 + examples/opnsense/src/main.rs | 1 + examples/opnsense_node_exporter/Cargo.toml | 20 ++++ examples/opnsense_node_exporter/src/main.rs | 110 +++++++++++++++++++ harmony/src/domain/topology/ha_cluster.rs | 36 +++++- harmony/src/domain/topology/node_exporter.rs | 17 +++ 8 files changed, 187 insertions(+), 3 deletions(-) create mode 100644 examples/opnsense_node_exporter/Cargo.toml create mode 100644 examples/opnsense_node_exporter/src/main.rs create mode 100644 harmony/src/domain/topology/node_exporter.rs diff --git a/examples/nanodc/src/main.rs b/examples/nanodc/src/main.rs index 57574d2..95b16a6 100644 --- a/examples/nanodc/src/main.rs +++ b/examples/nanodc/src/main.rs @@ -39,8 +39,7 @@ async fn main() { let gateway_ipv4 = Ipv4Addr::new(192, 168, 33, 1); let gateway_ip = IpAddr::V4(gateway_ipv4); let topology = harmony::topology::HAClusterTopology { - domain_name: "ncd0.harmony.mcd".to_string(), // TODO this must be set manually correctly - // when setting up the opnsense firewall + domain_name: "ncd0.harmony.mcd".to_string(), router: Arc::new(UnmanagedRouter::new( gateway_ip, Ipv4Cidr::new(lan_subnet, 24).unwrap(), @@ -84,6 +83,7 @@ async fn main() { }, ], switch: vec![], + node_exporter: opnsense.clone(), }; let inventory = Inventory { diff --git a/examples/okd_installation/src/topology.rs b/examples/okd_installation/src/topology.rs index 31062f5..4df6ab5 100644 --- a/examples/okd_installation/src/topology.rs +++ b/examples/okd_installation/src/topology.rs @@ -59,6 +59,7 @@ pub async fn get_topology() -> HAClusterTopology { }, workers: vec![], switch: vec![], + node_exporter: opnsense.clone(), } } diff --git a/examples/okd_pxe/src/topology.rs b/examples/okd_pxe/src/topology.rs index 707969a..63e3613 100644 --- a/examples/okd_pxe/src/topology.rs +++ b/examples/okd_pxe/src/topology.rs @@ -53,6 +53,7 @@ pub async fn get_topology() -> HAClusterTopology { }, workers: vec![], switch: vec![], + node_exporter: opnsense.clone(), } } diff --git a/examples/opnsense/src/main.rs b/examples/opnsense/src/main.rs index fcfaf09..8f4039d 100644 --- a/examples/opnsense/src/main.rs +++ b/examples/opnsense/src/main.rs @@ -55,6 +55,7 @@ async fn main() { }, workers: vec![], switch: vec![], + node_exporter: opnsense.clone(), }; let inventory = Inventory { diff --git a/examples/opnsense_node_exporter/Cargo.toml b/examples/opnsense_node_exporter/Cargo.toml new file mode 100644 index 0000000..957bdd9 --- /dev/null +++ b/examples/opnsense_node_exporter/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "example-opnsense-node-exporter" +edition = "2024" +version.workspace = true +readme.workspace = true +license.workspace = true + +[dependencies] +harmony = { path = "../../harmony" } +harmony_cli = { path = "../../harmony_cli" } +harmony_types = { path = "../../harmony_types" } +harmony_secret = { path = "../../harmony_secret" } +harmony_secret_derive = { path = "../../harmony_secret_derive" } +cidr = { workspace = true } +tokio = { workspace = true } +harmony_macros = { path = "../../harmony_macros" } +log = { workspace = true } +env_logger = { workspace = true } +url = { workspace = true } +serde.workspace = true diff --git a/examples/opnsense_node_exporter/src/main.rs b/examples/opnsense_node_exporter/src/main.rs new file mode 100644 index 0000000..4f1219d --- /dev/null +++ b/examples/opnsense_node_exporter/src/main.rs @@ -0,0 +1,110 @@ +use std::{ + net::{IpAddr, Ipv4Addr}, + sync::Arc, +}; + +use cidr::Ipv4Cidr; +use harmony::{ + hardware::{HostCategory, Location, PhysicalHost, SwitchGroup}, + infra::opnsense::OPNSenseManagementInterface, + inventory::Inventory, + modules::opnsense::node_exporter::NodeExporterScore, + topology::{HAClusterTopology, LogicalHost, UnmanagedRouter}, +}; +use harmony_macros::{ip, ipv4, mac_address}; + +#[tokio::main] +async fn main() { + let firewall = harmony::topology::LogicalHost { + ip: ip!("192.168.33.1"), + name: String::from("fw0"), + }; + + let opnsense = Arc::new( + harmony::infra::opnsense::OPNSenseFirewall::new(firewall, None, "root", "opnsense").await, + ); + let lan_subnet = Ipv4Addr::new(192, 168, 33, 0); + let gateway_ipv4 = Ipv4Addr::new(192, 168, 33, 1); + let gateway_ip = IpAddr::V4(gateway_ipv4); + let topology = harmony::topology::HAClusterTopology { + domain_name: "ncd0.harmony.mcd".to_string(), + router: Arc::new(UnmanagedRouter::new( + gateway_ip, + Ipv4Cidr::new(lan_subnet, 24).unwrap(), + )), + load_balancer: opnsense.clone(), + firewall: opnsense.clone(), + tftp_server: opnsense.clone(), + http_server: opnsense.clone(), + dhcp_server: opnsense.clone(), + dns_server: opnsense.clone(), + control_plane: vec![ + LogicalHost { + ip: ip!("192.168.33.20"), + name: "cp0".to_string(), + }, + LogicalHost { + ip: ip!("192.168.33.21"), + name: "cp1".to_string(), + }, + LogicalHost { + ip: ip!("192.168.33.22"), + name: "cp2".to_string(), + }, + ], + bootstrap_host: LogicalHost { + ip: ip!("192.168.33.66"), + name: "bootstrap".to_string(), + }, + workers: vec![ + LogicalHost { + ip: ip!("192.168.33.30"), + name: "wk0".to_string(), + }, + LogicalHost { + ip: ip!("192.168.33.31"), + name: "wk1".to_string(), + }, + LogicalHost { + ip: ip!("192.168.33.32"), + name: "wk2".to_string(), + }, + ], + switch: vec![], + node_exporter: opnsense.clone(), + }; + + let inventory = Inventory { + location: Location::new("I am mobile".to_string(), "earth".to_string()), + switch: SwitchGroup::from([]), + firewall_mgmt: Box::new(OPNSenseManagementInterface::new()), + storage_host: vec![], + worker_host: vec![ + PhysicalHost::empty(HostCategory::Server) + .mac_address(mac_address!("C4:62:37:02:61:0F")), + PhysicalHost::empty(HostCategory::Server) + .mac_address(mac_address!("C4:62:37:02:61:26")), + PhysicalHost::empty(HostCategory::Server) + .mac_address(mac_address!("C4:62:37:02:61:70")), + ], + control_plane_host: vec![ + PhysicalHost::empty(HostCategory::Server) + .mac_address(mac_address!("C4:62:37:02:60:FA")), + PhysicalHost::empty(HostCategory::Server) + .mac_address(mac_address!("C4:62:37:02:61:1A")), + PhysicalHost::empty(HostCategory::Server) + .mac_address(mac_address!("C4:62:37:01:BC:68")), + ], + }; + + let node_exporter_score = NodeExporterScore {}; + + harmony_cli::run( + inventory, + topology, + vec![Box::new(node_exporter_score)], + None, + ) + .await + .unwrap(); +} diff --git a/harmony/src/domain/topology/ha_cluster.rs b/harmony/src/domain/topology/ha_cluster.rs index 7be2725..a3e650d 100644 --- a/harmony/src/domain/topology/ha_cluster.rs +++ b/harmony/src/domain/topology/ha_cluster.rs @@ -11,7 +11,6 @@ use kube::api::ObjectMeta; use log::debug; use log::info; -use crate::data::FileContent; use crate::executors::ExecutorError; use crate::hardware::PhysicalHost; use crate::infra::brocade::BrocadeSwitchAuth; @@ -21,6 +20,7 @@ use crate::modules::okd::crd::{ nmstate::{self, NMState, NodeNetworkConfigurationPolicy, NodeNetworkConfigurationPolicySpec}, }; use crate::topology::PxeOptions; +use crate::{data::FileContent, topology::node_exporter::NodeExporter}; use super::{ DHCPStaticEntry, DhcpServer, DnsRecord, DnsRecordType, DnsServer, Firewall, HostNetworkConfig, @@ -43,6 +43,7 @@ pub struct HAClusterTopology { pub tftp_server: Arc, pub http_server: Arc, pub dns_server: Arc, + pub node_exporter: Arc, pub bootstrap_host: LogicalHost, pub control_plane: Vec, pub workers: Vec, @@ -333,6 +334,7 @@ impl HAClusterTopology { tftp_server: dummy_infra.clone(), http_server: dummy_infra.clone(), dns_server: dummy_infra.clone(), + node_exporter: dummy_infra.clone(), bootstrap_host: dummy_host, control_plane: vec![], workers: vec![], @@ -516,6 +518,23 @@ impl Switch for HAClusterTopology { self.configure_bond(host, &config).await?; self.configure_port_channel(host, &config).await } + + //TODO add snmp here +} + +#[async_trait] +impl NodeExporter for HAClusterTopology { + async fn ensure_initialized(&self) -> Result<(), ExecutorError> { + self.node_exporter.ensure_initialized().await + } + + async fn commit_config(&self) -> Result<(), ExecutorError> { + self.node_exporter.commit_config().await + } + + async fn reload_restart(&self) -> Result<(), ExecutorError> { + self.node_exporter.reload_restart().await + } } #[derive(Debug)] @@ -704,3 +723,18 @@ impl DnsServer for DummyInfra { unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) } } + +#[async_trait] +impl NodeExporter for DummyInfra { + async fn ensure_initialized(&self) -> Result<(), ExecutorError> { + unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) + } + + async fn commit_config(&self) -> Result<(), ExecutorError> { + unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) + } + + async fn reload_restart(&self) -> Result<(), ExecutorError> { + unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) + } +} diff --git a/harmony/src/domain/topology/node_exporter.rs b/harmony/src/domain/topology/node_exporter.rs new file mode 100644 index 0000000..88e3cc9 --- /dev/null +++ b/harmony/src/domain/topology/node_exporter.rs @@ -0,0 +1,17 @@ +use async_trait::async_trait; + +use crate::executors::ExecutorError; + +#[async_trait] +pub trait NodeExporter: Send + Sync { + async fn ensure_initialized(&self) -> Result<(), ExecutorError>; + async fn commit_config(&self) -> Result<(), ExecutorError>; + async fn reload_restart(&self) -> Result<(), ExecutorError>; +} + +//TODO complete this impl +impl std::fmt::Debug for dyn NodeExporter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("NodeExporter ",)) + } +}