diff --git a/harmony-rs/harmony/src/domain/interpret/mod.rs b/harmony-rs/harmony/src/domain/interpret/mod.rs index ecf953e..7ec1362 100644 --- a/harmony-rs/harmony/src/domain/interpret/mod.rs +++ b/harmony-rs/harmony/src/domain/interpret/mod.rs @@ -7,7 +7,6 @@ use super::{ data::{Id, Version}, executors::ExecutorError, inventory::Inventory, - score::Score, topology::HAClusterTopology, }; @@ -41,9 +40,13 @@ pub struct Outcome { status: InterpretStatus, message: String, } -impl std::fmt::Display for Outcome { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("Outcome {}: {}", self.status, self.message)) + +impl Outcome { + pub fn noop() -> Self { + Self { + status: InterpretStatus::NOOP, + message: String::new(), + } } } @@ -54,6 +57,7 @@ pub enum InterpretStatus { RUNNING, QUEUED, BLOCKED, + NOOP, } impl std::fmt::Display for InterpretStatus { @@ -64,6 +68,7 @@ impl std::fmt::Display for InterpretStatus { InterpretStatus::RUNNING => "RUNNING", InterpretStatus::QUEUED => "QUEUED", InterpretStatus::BLOCKED => "BLOCKED", + InterpretStatus::NOOP => "NO_OP", }; f.write_str(msg) } diff --git a/harmony-rs/harmony/src/domain/topology/network.rs b/harmony-rs/harmony/src/domain/topology/network.rs index 536370c..ff19dc0 100644 --- a/harmony-rs/harmony/src/domain/topology/network.rs +++ b/harmony-rs/harmony/src/domain/topology/network.rs @@ -45,6 +45,7 @@ pub trait DhcpServer: Send + Sync { 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)>; + async fn set_next_server(&self, ip: IpAddress) -> Result<(), ExecutorError>; fn get_ip(&self) -> IpAddress; fn get_host(&self) -> LogicalHost; async fn commit_config(&self) -> Result<(), ExecutorError>; diff --git a/harmony-rs/harmony/src/infra/opnsense/mod.rs b/harmony-rs/harmony/src/infra/opnsense/mod.rs index 8bc27d6..7aa4407 100644 --- a/harmony-rs/harmony/src/infra/opnsense/mod.rs +++ b/harmony-rs/harmony/src/infra/opnsense/mod.rs @@ -136,9 +136,24 @@ impl DhcpServer for OPNSenseFirewall { fn get_ip(&self) -> IpAddress { OPNSenseFirewall::get_ip(self) } + fn get_host(&self) -> LogicalHost { self.host.clone() } + + async fn set_next_server(&self, ip: IpAddress) -> Result<(), ExecutorError> { + let ipv4 = match ip { + std::net::IpAddr::V4(ipv4_addr) => ipv4_addr, + std::net::IpAddr::V6(_) => todo!("ipv6 not supported yet"), + }; + { + let mut writable_opnsense = self.opnsense_config.write().await; + writable_opnsense.dhcp().set_next_server(ipv4); + debug!("OPNsense dhcp server set next server {ipv4}"); + } + + Ok(()) + } } impl DnsServer for OPNSenseFirewall { diff --git a/harmony-rs/harmony/src/modules/dhcp.rs b/harmony-rs/harmony/src/modules/dhcp.rs index 73e6d65..09dd87a 100644 --- a/harmony-rs/harmony/src/modules/dhcp.rs +++ b/harmony-rs/harmony/src/modules/dhcp.rs @@ -11,7 +11,7 @@ use crate::{ }, interpret::{Interpret, InterpretError, InterpretName, Outcome}, inventory::Inventory, - topology::{DHCPStaticEntry, HAClusterTopology, HostBinding}, + topology::{DHCPStaticEntry, HAClusterTopology, HostBinding, IpAddress}, }; use crate::domain::score::Score; @@ -19,6 +19,7 @@ use crate::domain::score::Score; #[derive(Debug, new, Clone)] pub struct DhcpScore { host_binding: Vec, + next_server: Option, } impl Score for DhcpScore { @@ -53,6 +54,67 @@ impl DhcpInterpret { status: InterpretStatus::QUEUED, } } + async fn add_static_entries( + &self, + _inventory: &Inventory, + topology: &HAClusterTopology, + ) -> Result { + let dhcp_entries: Vec = self + .score + .host_binding + .iter() + .map(|binding| { + let ip = match binding.logical_host.ip { + std::net::IpAddr::V4(ipv4) => ipv4, + std::net::IpAddr::V6(_) => { + unimplemented!("DHCPStaticEntry only supports ipv4 at the moment") + } + }; + + DHCPStaticEntry { + name: binding.logical_host.name.clone(), + mac: binding.physical_host.cluster_mac(), + ip, + } + }) + .collect(); + info!("DHCPStaticEntry : {:?}", dhcp_entries); + + let dhcp_server = Arc::new(topology.dhcp_server.clone()); + info!("DHCP server : {:?}", dhcp_server); + + let number_new_entries = dhcp_entries.len(); + + for entry in dhcp_entries.into_iter() { + match dhcp_server.add_static_mapping(&entry).await { + Ok(_) => info!("Successfully registered DHCPStaticEntry {}", entry), + Err(_) => todo!(), + } + } + + Ok(Outcome::new( + InterpretStatus::SUCCESS, + format!("Dhcp Interpret registered {} entries", number_new_entries), + )) + } + + async fn set_next_server( + &self, + _inventory: &Inventory, + topology: &HAClusterTopology, + ) -> Result { + match self.score.next_server { + Some(next_server) => { + let dhcp_server = Arc::new(topology.dhcp_server.clone()); + dhcp_server.set_next_server(next_server).await?; + Ok(Outcome::new( + InterpretStatus::SUCCESS, + format!("Dhcp Interpret Set next boot to {next_server}"), + )) + } + None => Ok(Outcome::noop()), + } + } } #[async_trait] @@ -80,68 +142,15 @@ impl Interpret for DhcpInterpret { ) -> Result { info!("Executing {} on inventory {inventory:?}", self.get_name()); - let dhcp_entries: Vec = self - .score - .host_binding - .iter() - .map(|binding| { - let ip = match binding.logical_host.ip { - std::net::IpAddr::V4(ipv4) => ipv4, - std::net::IpAddr::V6(_) => { - unimplemented!("DHCPStaticEntry only supports ipv4 at the moment") - } - }; + self.set_next_server(inventory, topology).await?; - DHCPStaticEntry { - name: binding.logical_host.name.clone(), - mac: binding.physical_host.cluster_mac(), - ip, - } - }) - .collect(); - info!("DHCPStaticEntry : {:?}", dhcp_entries); + // self.add_static_entries(inventory, topology).await?; - let dhcp_server = Arc::new(Box::new(topology.dhcp_server.clone())); - info!("DHCP server : {:?}", dhcp_server); - - let number_new_entries = dhcp_entries.len(); - - for entry in dhcp_entries.into_iter() { - match dhcp_server.add_static_mapping(&entry).await { - Ok(_) => info!("Successfully registered DHCPStaticEntry {}", entry), - Err(_) => todo!(), - } - } - - dhcp_server.commit_config().await; + topology.dhcp_server.commit_config().await?; Ok(Outcome::new( InterpretStatus::SUCCESS, - format!("Dhcp Interpret registered {} entries", number_new_entries), + format!("Dhcp Interpret execution successful"), )) } } - -#[cfg(test)] -mod test { - - #[test] - fn opnsense_dns_score_should_do_nothing_on_empty_inventory() { - todo!(); - } - - #[test] - fn opnsense_dns_score_should_set_entry_for_bootstrap_node() { - todo!(); - } - - #[test] - fn opnsense_dns_score_should_set_entry_for_control_plane_members() { - todo!(); - } - - #[test] - fn opnsense_dns_score_should_set_entry_for_workers() { - todo!(); - } -} diff --git a/harmony-rs/harmony/src/modules/okd/dhcp.rs b/harmony-rs/harmony/src/modules/okd/dhcp.rs index 3603696..b22efff 100644 --- a/harmony-rs/harmony/src/modules/okd/dhcp.rs +++ b/harmony-rs/harmony/src/modules/okd/dhcp.rs @@ -12,25 +12,25 @@ pub struct OKDBootstrapDhcpScore { impl OKDBootstrapDhcpScore { pub fn new(topology: &HAClusterTopology, inventory: &Inventory) -> Self { + let host_binding = topology + .control_plane + .iter() + .enumerate() + .map(|(index, topology_entry)| HostBinding { + logical_host: topology_entry.clone(), + physical_host: inventory + .control_plane_host + .get(index) + .expect("Iventory should contain at least as many physical hosts as topology") + .clone(), + }) + .collect(); Self { dhcp_score: DhcpScore::new( - topology - .control_plane - .iter() - .enumerate() - .map(|(index, topology_entry)| { - HostBinding { - logical_host: topology_entry.clone(), - physical_host: inventory - .control_plane_host - .get(index) - .expect( - "Iventory should contain at least as many physical hosts as topology", - ) - .clone(), - } - }) - .collect(), + host_binding, + // TODO : we should add a tftp server to the topology instead of relying on the + // router address, this is leaking implementation details + Some(topology.router.get_gateway()), ), } } diff --git a/harmony-rs/opnsense-config-xml/src/data/dhcpd.rs b/harmony-rs/opnsense-config-xml/src/data/dhcpd.rs index 9c06716..212758a 100644 --- a/harmony-rs/opnsense-config-xml/src/data/dhcpd.rs +++ b/harmony-rs/opnsense-config-xml/src/data/dhcpd.rs @@ -16,6 +16,8 @@ pub struct DhcpInterface { pub enable: Option, pub gateway: Option, pub domain: Option, + pub netboot: Option, + pub nextserver: Option, #[yaserde(rename = "ddnsdomainalgorithm")] pub ddns_domain_algorithm: Option, #[yaserde(rename = "numberoptions")] diff --git a/harmony-rs/opnsense-config/src/modules/dhcp.rs b/harmony-rs/opnsense-config/src/modules/dhcp.rs index 537c3c4..ae9ccbc 100644 --- a/harmony-rs/opnsense-config/src/modules/dhcp.rs +++ b/harmony-rs/opnsense-config/src/modules/dhcp.rs @@ -54,7 +54,9 @@ impl<'a> DhcpConfig<'a> { pub fn remove_static_mapping(&mut self, mac: &str) { let lan_dhcpd = self.get_lan_dhcpd(); - lan_dhcpd.staticmaps.retain(|static_entry| static_entry.mac != mac); + lan_dhcpd + .staticmaps + .retain(|static_entry| static_entry.mac != mac); } fn get_lan_dhcpd(&mut self) -> &mut opnsense_config_xml::DhcpInterface { @@ -77,7 +79,6 @@ impl<'a> DhcpConfig<'a> { let mac = mac.to_string(); let hostname = hostname.to_string(); let lan_dhcpd = self.get_lan_dhcpd(); - let range = &lan_dhcpd.range; let existing_mappings: &mut Vec = &mut lan_dhcpd.staticmaps; if !Self::is_valid_mac(&mac) { @@ -87,7 +88,7 @@ impl<'a> DhcpConfig<'a> { // TODO verify if address is in subnet range // This check here does not do what we want to do, as we want to assign static leases // outside of the dynamic DHCP pool - // + // let range = &lan_dhcpd.range; // if !Self::is_ip_in_range(&ipaddr, range) { // return Err(DhcpError::IpAddressOutOfRange(ipaddr.to_string())); // } @@ -177,6 +178,14 @@ impl<'a> DhcpConfig<'a> { Ok(static_maps) } + pub fn enable_netboot(&mut self) { + self.get_lan_dhcpd().netboot = Some(1); + } + + pub fn set_next_server(&mut self, ip: Ipv4Addr) { + self.enable_netboot(); + self.get_lan_dhcpd().nextserver = Some(ip.to_string()); + } } #[cfg(test)]