diff --git a/harmony-rs/Cargo.lock b/harmony-rs/Cargo.lock index a0a529d..6e0abb7 100644 --- a/harmony-rs/Cargo.lock +++ b/harmony-rs/Cargo.lock @@ -58,17 +58,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "affilium" -version = "0.1.0" -dependencies = [ - "cidr", - "env_logger", - "harmony", - "log", - "tokio", -] - [[package]] name = "aho-corasick" version = "1.1.3" @@ -258,6 +247,15 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +[[package]] +name = "castaway" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] + [[package]] name = "cbc" version = "0.1.2" @@ -315,6 +313,19 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +[[package]] +name = "compact_str" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "ryu", + "static_assertions", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -817,6 +828,7 @@ dependencies = [ "env_logger", "libredfish", "log", + "minidom", "reqwest", "russh", "rust-ipmi", @@ -1069,6 +1081,15 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minidom" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e394a0e3c7ccc2daea3dffabe82f09857b6b510cb25af87d54bf3e910ac1642d" +dependencies = [ + "rxml", +] + [[package]] name = "miniz_oxide" version = "0.7.4" @@ -1732,6 +1753,31 @@ dependencies = [ "base64", ] +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "rxml" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc94b580d0f5a6b7a2d604e597513d3c673154b52ddeccd1d5c32360d945ee" +dependencies = [ + "bytes", + "rxml_validation", +] + +[[package]] +name = "rxml_validation" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826e80413b9a35e9d33217b3dcac04cf95f6559d15944b93887a08be5496c4a4" +dependencies = [ + "compact_str", +] + [[package]] name = "ryu" version = "1.0.18" @@ -1993,6 +2039,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "subtle" version = "2.6.1" diff --git a/harmony-rs/Cargo.toml b/harmony-rs/Cargo.toml index 9e5cc19..954f000 100644 --- a/harmony-rs/Cargo.toml +++ b/harmony-rs/Cargo.toml @@ -17,3 +17,4 @@ derive-new = "0.7.0" async-trait = "0.1.82" tokio = { version = "1.40.0", features = ["io-std"] } cidr = "0.2.3" +minidom = "0.16.0" diff --git a/harmony-rs/harmony/Cargo.toml b/harmony-rs/harmony/Cargo.toml index 17b07da..b017d54 100644 --- a/harmony-rs/harmony/Cargo.toml +++ b/harmony-rs/harmony/Cargo.toml @@ -17,3 +17,4 @@ log = { workspace = true } env_logger = { workspace = true } async-trait = { workspace = true } cidr = { workspace = true } +minidom = { workspace = true } diff --git a/harmony-rs/harmony/src/domain/hardware/mod.rs b/harmony-rs/harmony/src/domain/hardware/mod.rs index 985e16e..8ec4f81 100644 --- a/harmony-rs/harmony/src/domain/hardware/mod.rs +++ b/harmony-rs/harmony/src/domain/hardware/mod.rs @@ -7,6 +7,7 @@ use crate::topology::MacAddress; pub type HostGroup = Vec; pub type SwitchGroup = Vec; pub type FirewallGroup = Vec; + #[derive(Debug, Clone)] pub struct PhysicalHost { pub category: HostCategory, diff --git a/harmony-rs/harmony/src/domain/inventory/mod.rs b/harmony-rs/harmony/src/domain/inventory/mod.rs index af4a00c..930ee02 100644 --- a/harmony-rs/harmony/src/domain/inventory/mod.rs +++ b/harmony-rs/harmony/src/domain/inventory/mod.rs @@ -23,6 +23,9 @@ use super::{ pub struct Inventory { pub location: Location, pub switch: SwitchGroup, + // Firewall is really just a host but with somewhat specialized hardware + // I'm not entirely sure it belongs to its own category but it helps make things easier and + // clearer for now so let's try it this way. pub firewall: FirewallGroup, pub worker_host: HostGroup, pub storage_host: HostGroup, @@ -34,9 +37,11 @@ impl Inventory { pub fn empty_inventory() -> Self { Self { location: Location::test_building(), - host: HostGroup::new(), switch: SwitchGroup::new(), firewall: FirewallGroup::new(), + worker_host: HostGroup::new(), + storage_host: HostGroup::new(), + control_plane_host: HostGroup::new(), } } } diff --git a/harmony-rs/harmony/src/domain/topology/load_balancer.rs b/harmony-rs/harmony/src/domain/topology/load_balancer.rs index cf79ac3..9f5226a 100644 --- a/harmony-rs/harmony/src/domain/topology/load_balancer.rs +++ b/harmony-rs/harmony/src/domain/topology/load_balancer.rs @@ -1,10 +1,11 @@ +use crate::executors::ExecutorError; use super::{IpAddress, LogicalHost}; pub trait LoadBalancer: Send + Sync { - fn add_backend(&mut self, backend: Backend) -> Result<(), LoadBalancerError>; - fn remove_backend(&mut self, backend_id: &str) -> Result<(), LoadBalancerError>; - fn add_frontend(&mut self, frontend: Frontend) -> Result<(), LoadBalancerError>; - fn remove_frontend(&mut self, frontend_id: &str) -> Result<(), LoadBalancerError>; + fn add_backend(&mut self, backend: Backend) -> Result<(), ExecutorError>; + fn remove_backend(&mut self, backend_id: &str) -> Result<(), ExecutorError>; + fn add_frontend(&mut self, frontend: Frontend) -> Result<(), ExecutorError>; + fn remove_frontend(&mut self, frontend_id: &str) -> Result<(), ExecutorError>; fn list_backends(&self) -> Vec; fn list_frontends(&self) -> Vec; fn get_ip(&self) -> IpAddress; @@ -17,8 +18,6 @@ impl std::fmt::Debug for dyn LoadBalancer { } } -pub struct LoadBalancerError; - #[derive(Clone, Debug)] pub struct Backend { pub id: String, diff --git a/harmony-rs/harmony/src/domain/topology/network.rs b/harmony-rs/harmony/src/domain/topology/network.rs index 00189e4..8096af9 100644 --- a/harmony-rs/harmony/src/domain/topology/network.rs +++ b/harmony-rs/harmony/src/domain/topology/network.rs @@ -1,3 +1,7 @@ +use async_trait::async_trait; + +use crate::executors::ExecutorError; + use super::{IpAddress, LogicalHost}; #[derive(Debug)] @@ -14,8 +18,8 @@ impl std::fmt::Display for DHCPStaticEntry { } pub trait Firewall: Send + Sync { - fn add_rule(&mut self, rule: FirewallRule) -> Result<(), FirewallError>; - fn remove_rule(&mut self, rule_id: &str) -> Result<(), FirewallError>; + fn add_rule(&mut self, rule: FirewallRule) -> Result<(), ExecutorError>; + fn remove_rule(&mut self, rule_id: &str) -> Result<(), ExecutorError>; fn list_rules(&self) -> Vec; fn get_ip(&self) -> IpAddress; fn get_host(&self) -> LogicalHost; @@ -31,10 +35,11 @@ pub struct NetworkDomain { pub name: String, } +#[async_trait] pub trait DhcpServer: Send + Sync { - fn add_static_mapping(&self, entry: &DHCPStaticEntry) -> Result<(), DhcpError>; - fn remove_static_mapping(&self, mac: &MacAddress) -> Result<(), DhcpError>; - fn list_static_mappings(&self) -> Vec<(MacAddress, IpAddress)>; + async fn add_static_mapping(&self, entry: &DHCPStaticEntry) -> Result<(), ExecutorError>; + async fn remove_static_mapping(&self, mac: &MacAddress) -> Result<(), ExecutorError>; + async fn list_static_mappings(&self) -> Vec<(MacAddress, IpAddress)>; fn get_ip(&self) -> IpAddress; fn get_host(&self) -> LogicalHost; } @@ -51,8 +56,8 @@ pub trait DnsServer: Send + Sync { name: &str, record_type: DnsRecordType, value: &str, - ) -> Result<(), DnsError>; - fn remove_record(&mut self, name: &str, record_type: DnsRecordType) -> Result<(), DnsError>; + ) -> Result<(), ExecutorError>; + fn remove_record(&mut self, name: &str, record_type: DnsRecordType) -> Result<(), ExecutorError>; fn list_records(&self) -> Vec; fn get_ip(&self) -> IpAddress; fn get_host(&self) -> LogicalHost; @@ -89,6 +94,9 @@ pub enum Action { #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct MacAddress(pub [u8; 6]); +// TODO create a small macro to provide a nice API to initiate a MacAddress +// MacAddress::from!("00:90:7f:df:2c:23"), + impl MacAddress { pub fn dummy() -> Self { Self([0, 0, 0, 0, 0, 0]) @@ -119,8 +127,3 @@ pub struct DnsRecord { pub record_type: DnsRecordType, pub value: String, } - -// Error types -pub struct FirewallError; -pub struct DhcpError; -pub struct DnsError; diff --git a/harmony-rs/harmony/src/infra/opnsense/config.rs b/harmony-rs/harmony/src/infra/opnsense/config.rs new file mode 100644 index 0000000..5ed2f40 --- /dev/null +++ b/harmony-rs/harmony/src/infra/opnsense/config.rs @@ -0,0 +1,43 @@ +use crate::executors::ExecutorError; + +pub struct OPNSenseXmlConfigEditor; + +impl OPNSenseXmlConfigEditor { + pub(crate) async fn add(vec: Vec<&str>, xml_entry: &str) -> Result<(), ExecutorError>{ + todo!() + } +} + +#[cfg(test)] +mod test { + use minidom::Element; + use std::fs; + use std::io::{BufReader, Read}; + use std::process::Command; + + // #[test] + // fn should_not_alter_config() { + // let path = "./private_repos/affilium_mcd/private/config.xml"; + // // TODO + // // Load file to string + // // Parse with minidom (ex: `let root: Element = file_str.parse().unwrap()`) + // // save file with suffix name + // // Verify that file is still identical with md5sum after removing indentation + // } + + #[test] + fn should_not_alter_config() { + let path = "/home/jeangab/work/nationtech/harmony/harmony-rs/affilium_mcd/private/config.xml"; + let output_path = format!("{}.test_output", path); + + // Load file to string + let file_str = fs::read_to_string(path).expect("Failed to read file"); + + // Parse with minidom + let root: Element = file_str.parse().unwrap(); + + // Save file with suffix name + fs::write(&output_path, String::from(&root)).expect("Failed to write output file"); + } + +} diff --git a/harmony-rs/harmony/src/infra/opnsense/mod.rs b/harmony-rs/harmony/src/infra/opnsense/mod.rs index 0f81d40..fd7bd6c 100644 --- a/harmony-rs/harmony/src/infra/opnsense/mod.rs +++ b/harmony-rs/harmony/src/infra/opnsense/mod.rs @@ -1,15 +1,20 @@ mod management; +mod config; +use async_trait::async_trait; pub use management::*; -use crate::{executors::ExecutorError, topology::{ - Backend, DHCPStaticEntry, DhcpServer, DnsServer, Firewall, FirewallError, FirewallRule, - Frontend, IpAddress, LoadBalancer, LoadBalancerError, LogicalHost, -}}; +use crate::{ + executors::ExecutorError, infra::opnsense::config::OPNSenseXmlConfigEditor, topology::{ + Backend, DHCPStaticEntry, DhcpServer, DnsServer, Firewall, FirewallRule, Frontend, + IpAddress, LoadBalancer, LogicalHost, + } +}; use derive_new::new; #[derive(new, Clone)] pub struct OPNSenseFirewall { host: LogicalHost, + cluster_nic_name: String, } impl OPNSenseFirewall { @@ -17,17 +22,14 @@ impl OPNSenseFirewall { self.host.ip } - fn save_xml_to_config(&self, xml_entry: &str) -> Result<(), ExecutorError> { - todo!("Save XML Entry in opnsense /conf/config.xml {xml_entry}"); - } } impl Firewall for OPNSenseFirewall { - fn add_rule(&mut self, _rule: FirewallRule) -> Result<(), FirewallError> { + fn add_rule(&mut self, _rule: FirewallRule) -> Result<(), ExecutorError> { todo!() } - fn remove_rule(&mut self, _rule_id: &str) -> Result<(), FirewallError> { + fn remove_rule(&mut self, _rule_id: &str) -> Result<(), ExecutorError> { todo!() } @@ -44,19 +46,19 @@ impl Firewall for OPNSenseFirewall { } impl LoadBalancer for OPNSenseFirewall { - fn add_backend(&mut self, _backend: Backend) -> Result<(), LoadBalancerError> { + fn add_backend(&mut self, _backend: Backend) -> Result<(), ExecutorError> { todo!() } - fn remove_backend(&mut self, _backend_id: &str) -> Result<(), LoadBalancerError> { + fn remove_backend(&mut self, _backend_id: &str) -> Result<(), ExecutorError> { todo!() } - fn add_frontend(&mut self, _frontend: Frontend) -> Result<(), LoadBalancerError> { + fn add_frontend(&mut self, _frontend: Frontend) -> Result<(), ExecutorError> { todo!() } - fn remove_frontend(&mut self, _frontend_id: &str) -> Result<(), LoadBalancerError> { + fn remove_frontend(&mut self, _frontend_id: &str) -> Result<(), ExecutorError> { todo!() } @@ -76,36 +78,40 @@ impl LoadBalancer for OPNSenseFirewall { } } +#[async_trait] impl DhcpServer for OPNSenseFirewall { - fn add_static_mapping( - &self, - entry: &DHCPStaticEntry, - ) -> Result<(), crate::topology::DhcpError> { + async fn add_static_mapping(&self, entry: &DHCPStaticEntry) -> Result<(), ExecutorError> { let mac = &entry.mac; let name = &entry.name; let ip = &entry.ip; - let xml_entry = format!(" -> {mac} -> {ip} -> {name} -> {name} -> -> -> -> "); - self.save_xml_to_config(&xml_entry)?; + let xml_entry = format!( + " + {mac} + {ip} + {name} + {name} + + + + " + ); + // XML Path : + OPNSenseXmlConfigEditor::add( + vec!["opnsense", "dhcpd", &self.cluster_nic_name, "staticmap"], + &xml_entry, + ).await?; todo!("Register {:?}", entry) } - fn remove_static_mapping( + async fn remove_static_mapping( &self, _mac: &crate::topology::MacAddress, - ) -> Result<(), crate::topology::DhcpError> { + ) -> Result<(), ExecutorError> { todo!() } - fn list_static_mappings(&self) -> Vec<(crate::topology::MacAddress, IpAddress)> { + async fn list_static_mappings(&self) -> Vec<(crate::topology::MacAddress, IpAddress)> { todo!() } @@ -122,7 +128,7 @@ impl DnsServer for OPNSenseFirewall { _name: &str, _record_type: crate::topology::DnsRecordType, _value: &str, - ) -> Result<(), crate::topology::DnsError> { + ) -> Result<(), ExecutorError> { todo!() } @@ -130,7 +136,7 @@ impl DnsServer for OPNSenseFirewall { &mut self, _name: &str, _record_type: crate::topology::DnsRecordType, - ) -> Result<(), crate::topology::DnsError> { + ) -> Result<(), ExecutorError> { todo!() } diff --git a/harmony-rs/harmony/src/modules/dhcp.rs b/harmony-rs/harmony/src/modules/dhcp.rs index ec100ea..246576b 100644 --- a/harmony-rs/harmony/src/modules/dhcp.rs +++ b/harmony-rs/harmony/src/modules/dhcp.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use async_trait::async_trait; use derive_new::new; use log::info; @@ -7,7 +9,6 @@ use crate::{ data::{Id, Version}, interpret::InterpretStatus, }, - infra::executors::russh::RusshClient, interpret::{Interpret, InterpretError, InterpretName, Outcome}, inventory::Inventory, topology::{DHCPStaticEntry, HAClusterTopology, HostBinding}, @@ -114,9 +115,8 @@ impl Interpret for DhcpInterpret { topology: &HAClusterTopology, ) -> Result { info!("Executing {} on inventory {inventory:?}", self.get_name()); - let ssh_client = RusshClient {}; - let entries: Vec = self + let dhcp_entries: Vec = self .score .host_binding .iter() @@ -126,16 +126,16 @@ impl Interpret for DhcpInterpret { ip: binding.logical_host.ip, }) .collect(); - info!("DHCPStaticEntry : {:?}", entries); + info!("DHCPStaticEntry : {:?}", dhcp_entries); - let dhcp = topology.dhcp_server.clone(); + let dhcp = Arc::new(Box::new(topology.dhcp_server.clone())); info!("DHCP server : {:?}", dhcp); - entries.iter().for_each(|entry| { - match dhcp.add_static_mapping(&entry) { + for entry in dhcp_entries.into_iter() { + match dhcp.add_static_mapping(&entry).await { Ok(_) => info!("Successfully registered DHCPStaticEntry {}", entry), Err(_) => todo!(), } - }); + } todo!("Configure DHCPServer"); Ok(Outcome::new(