feat: Harmony now sets dhcp next boot server for PXE on okd setup
This commit is contained in:
		
							parent
							
								
									b15df3c93f
								
							
						
					
					
						commit
						18c67adfad
					
				| @ -7,7 +7,6 @@ use super::{ | |||||||
|     data::{Id, Version}, |     data::{Id, Version}, | ||||||
|     executors::ExecutorError, |     executors::ExecutorError, | ||||||
|     inventory::Inventory, |     inventory::Inventory, | ||||||
|     score::Score, |  | ||||||
|     topology::HAClusterTopology, |     topology::HAClusterTopology, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -41,9 +40,13 @@ pub struct Outcome { | |||||||
|     status: InterpretStatus, |     status: InterpretStatus, | ||||||
|     message: String, |     message: String, | ||||||
| } | } | ||||||
| impl std::fmt::Display for Outcome { | 
 | ||||||
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | impl Outcome { | ||||||
|         f.write_fmt(format_args!("Outcome {}: {}", self.status, self.message)) |     pub fn noop() -> Self { | ||||||
|  |         Self { | ||||||
|  |             status: InterpretStatus::NOOP, | ||||||
|  |             message: String::new(), | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -54,6 +57,7 @@ pub enum InterpretStatus { | |||||||
|     RUNNING, |     RUNNING, | ||||||
|     QUEUED, |     QUEUED, | ||||||
|     BLOCKED, |     BLOCKED, | ||||||
|  |     NOOP, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl std::fmt::Display for InterpretStatus { | impl std::fmt::Display for InterpretStatus { | ||||||
| @ -64,6 +68,7 @@ impl std::fmt::Display for InterpretStatus { | |||||||
|             InterpretStatus::RUNNING => "RUNNING", |             InterpretStatus::RUNNING => "RUNNING", | ||||||
|             InterpretStatus::QUEUED => "QUEUED", |             InterpretStatus::QUEUED => "QUEUED", | ||||||
|             InterpretStatus::BLOCKED => "BLOCKED", |             InterpretStatus::BLOCKED => "BLOCKED", | ||||||
|  |             InterpretStatus::NOOP => "NO_OP", | ||||||
|         }; |         }; | ||||||
|         f.write_str(msg) |         f.write_str(msg) | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -45,6 +45,7 @@ pub trait DhcpServer: Send + Sync { | |||||||
|     async fn add_static_mapping(&self, entry: &DHCPStaticEntry) -> Result<(), ExecutorError>; |     async fn add_static_mapping(&self, entry: &DHCPStaticEntry) -> Result<(), ExecutorError>; | ||||||
|     async fn remove_static_mapping(&self, mac: &MacAddress) -> Result<(), ExecutorError>; |     async fn remove_static_mapping(&self, mac: &MacAddress) -> Result<(), ExecutorError>; | ||||||
|     async fn list_static_mappings(&self) -> Vec<(MacAddress, IpAddress)>; |     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_ip(&self) -> IpAddress; | ||||||
|     fn get_host(&self) -> LogicalHost; |     fn get_host(&self) -> LogicalHost; | ||||||
|     async fn commit_config(&self) -> Result<(), ExecutorError>; |     async fn commit_config(&self) -> Result<(), ExecutorError>; | ||||||
|  | |||||||
| @ -136,9 +136,24 @@ impl DhcpServer for OPNSenseFirewall { | |||||||
|     fn get_ip(&self) -> IpAddress { |     fn get_ip(&self) -> IpAddress { | ||||||
|         OPNSenseFirewall::get_ip(self) |         OPNSenseFirewall::get_ip(self) | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     fn get_host(&self) -> LogicalHost { |     fn get_host(&self) -> LogicalHost { | ||||||
|         self.host.clone() |         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 { | impl DnsServer for OPNSenseFirewall { | ||||||
|  | |||||||
| @ -11,7 +11,7 @@ use crate::{ | |||||||
|     }, |     }, | ||||||
|     interpret::{Interpret, InterpretError, InterpretName, Outcome}, |     interpret::{Interpret, InterpretError, InterpretName, Outcome}, | ||||||
|     inventory::Inventory, |     inventory::Inventory, | ||||||
|     topology::{DHCPStaticEntry, HAClusterTopology, HostBinding}, |     topology::{DHCPStaticEntry, HAClusterTopology, HostBinding, IpAddress}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| use crate::domain::score::Score; | use crate::domain::score::Score; | ||||||
| @ -19,6 +19,7 @@ use crate::domain::score::Score; | |||||||
| #[derive(Debug, new, Clone)] | #[derive(Debug, new, Clone)] | ||||||
| pub struct DhcpScore { | pub struct DhcpScore { | ||||||
|     host_binding: Vec<HostBinding>, |     host_binding: Vec<HostBinding>, | ||||||
|  |     next_server: Option<IpAddress>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl Score for DhcpScore { | impl Score for DhcpScore { | ||||||
| @ -53,6 +54,67 @@ impl DhcpInterpret { | |||||||
|             status: InterpretStatus::QUEUED, |             status: InterpretStatus::QUEUED, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |     async fn add_static_entries( | ||||||
|  |         &self, | ||||||
|  |         _inventory: &Inventory, | ||||||
|  |         topology: &HAClusterTopology, | ||||||
|  |     ) -> Result<Outcome, InterpretError> { | ||||||
|  |         let dhcp_entries: Vec<DHCPStaticEntry> = 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<Outcome, InterpretError> { | ||||||
|  |         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] | #[async_trait] | ||||||
| @ -80,68 +142,15 @@ impl Interpret for DhcpInterpret { | |||||||
|     ) -> Result<Outcome, InterpretError> { |     ) -> Result<Outcome, InterpretError> { | ||||||
|         info!("Executing {} on inventory {inventory:?}", self.get_name()); |         info!("Executing {} on inventory {inventory:?}", self.get_name()); | ||||||
| 
 | 
 | ||||||
|         let dhcp_entries: Vec<DHCPStaticEntry> = self |         self.set_next_server(inventory, topology).await?; | ||||||
|             .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 { |         // self.add_static_entries(inventory, topology).await?;
 | ||||||
|                     name: binding.logical_host.name.clone(), |  | ||||||
|                     mac: binding.physical_host.cluster_mac(), |  | ||||||
|                     ip, |  | ||||||
|                 } |  | ||||||
|             }) |  | ||||||
|             .collect(); |  | ||||||
|         info!("DHCPStaticEntry : {:?}", dhcp_entries); |  | ||||||
| 
 | 
 | ||||||
|         let dhcp_server = Arc::new(Box::new(topology.dhcp_server.clone())); |         topology.dhcp_server.commit_config().await?; | ||||||
|         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; |  | ||||||
| 
 | 
 | ||||||
|         Ok(Outcome::new( |         Ok(Outcome::new( | ||||||
|             InterpretStatus::SUCCESS, |             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!(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -12,25 +12,25 @@ pub struct OKDBootstrapDhcpScore { | |||||||
| 
 | 
 | ||||||
| impl OKDBootstrapDhcpScore { | impl OKDBootstrapDhcpScore { | ||||||
|     pub fn new(topology: &HAClusterTopology, inventory: &Inventory) -> Self { |     pub fn new(topology: &HAClusterTopology, inventory: &Inventory) -> Self { | ||||||
|         Self { |         let host_binding = topology | ||||||
|             dhcp_score: DhcpScore::new( |  | ||||||
|                 topology |  | ||||||
|             .control_plane |             .control_plane | ||||||
|             .iter() |             .iter() | ||||||
|             .enumerate() |             .enumerate() | ||||||
|                     .map(|(index, topology_entry)| { |             .map(|(index, topology_entry)| HostBinding { | ||||||
|                         HostBinding { |  | ||||||
|                 logical_host: topology_entry.clone(), |                 logical_host: topology_entry.clone(), | ||||||
|                 physical_host: inventory |                 physical_host: inventory | ||||||
|                     .control_plane_host |                     .control_plane_host | ||||||
|                     .get(index) |                     .get(index) | ||||||
|                         .expect( |                     .expect("Iventory should contain at least as many physical hosts as topology") | ||||||
|                             "Iventory should contain at least as many physical hosts as topology", |  | ||||||
|                         ) |  | ||||||
|                     .clone(), |                     .clone(), | ||||||
|                 } |  | ||||||
|             }) |             }) | ||||||
|                     .collect(), |             .collect(); | ||||||
|  |         Self { | ||||||
|  |             dhcp_score: DhcpScore::new( | ||||||
|  |                 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()), | ||||||
|             ), |             ), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -16,6 +16,8 @@ pub struct DhcpInterface { | |||||||
|     pub enable: Option<MaybeString>, |     pub enable: Option<MaybeString>, | ||||||
|     pub gateway: Option<MaybeString>, |     pub gateway: Option<MaybeString>, | ||||||
|     pub domain: Option<MaybeString>, |     pub domain: Option<MaybeString>, | ||||||
|  |     pub netboot: Option<u32>, | ||||||
|  |     pub nextserver: Option<String>, | ||||||
|     #[yaserde(rename = "ddnsdomainalgorithm")] |     #[yaserde(rename = "ddnsdomainalgorithm")] | ||||||
|     pub ddns_domain_algorithm: Option<MaybeString>, |     pub ddns_domain_algorithm: Option<MaybeString>, | ||||||
|     #[yaserde(rename = "numberoptions")] |     #[yaserde(rename = "numberoptions")] | ||||||
|  | |||||||
| @ -54,7 +54,9 @@ impl<'a> DhcpConfig<'a> { | |||||||
| 
 | 
 | ||||||
|     pub fn remove_static_mapping(&mut self, mac: &str) { |     pub fn remove_static_mapping(&mut self, mac: &str) { | ||||||
|         let lan_dhcpd = self.get_lan_dhcpd(); |         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 { |     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 mac = mac.to_string(); | ||||||
|         let hostname = hostname.to_string(); |         let hostname = hostname.to_string(); | ||||||
|         let lan_dhcpd = self.get_lan_dhcpd(); |         let lan_dhcpd = self.get_lan_dhcpd(); | ||||||
|         let range = &lan_dhcpd.range; |  | ||||||
|         let existing_mappings: &mut Vec<StaticMap> = &mut lan_dhcpd.staticmaps; |         let existing_mappings: &mut Vec<StaticMap> = &mut lan_dhcpd.staticmaps; | ||||||
| 
 | 
 | ||||||
|         if !Self::is_valid_mac(&mac) { |         if !Self::is_valid_mac(&mac) { | ||||||
| @ -87,7 +88,7 @@ impl<'a> DhcpConfig<'a> { | |||||||
|         // TODO verify if address is in subnet range
 |         // 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
 |         // This check here does not do what we want to do, as we want to assign static leases
 | ||||||
|         // outside of the dynamic DHCP pool
 |         // outside of the dynamic DHCP pool
 | ||||||
|         //
 |         // let range = &lan_dhcpd.range;
 | ||||||
|         // if !Self::is_ip_in_range(&ipaddr, range) {
 |         // if !Self::is_ip_in_range(&ipaddr, range) {
 | ||||||
|         //     return Err(DhcpError::IpAddressOutOfRange(ipaddr.to_string()));
 |         //     return Err(DhcpError::IpAddressOutOfRange(ipaddr.to_string()));
 | ||||||
|         // }
 |         // }
 | ||||||
| @ -177,6 +178,14 @@ impl<'a> DhcpConfig<'a> { | |||||||
| 
 | 
 | ||||||
|         Ok(static_maps) |         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)] | #[cfg(test)] | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user