wip: bootstrap step of okd installation required some refactoring, its getting there
Some checks failed
Run Check Script / check (pull_request) Failing after 30s

This commit is contained in:
Jean-Gabriel Gill-Couture 2025-09-01 19:14:31 -04:00
parent 138e414727
commit f076d36297
10 changed files with 284 additions and 196 deletions

4
Cargo.lock generated
View File

@ -7049,7 +7049,7 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]] [[package]]
name = "yaserde" name = "yaserde"
version = "0.12.0" version = "0.12.0"
source = "git+https://github.com/jggc/yaserde.git#c94ca32b6505f9c9a668702a1b1f1f88c6374301" source = "git+https://github.com/jggc/yaserde.git#adfdb1c5f4d054f114e5bd0ea7bda9c07a369def"
dependencies = [ dependencies = [
"log", "log",
"xml-rs", "xml-rs",
@ -7058,7 +7058,7 @@ dependencies = [
[[package]] [[package]]
name = "yaserde_derive" name = "yaserde_derive"
version = "0.12.0" version = "0.12.0"
source = "git+https://github.com/jggc/yaserde.git#c94ca32b6505f9c9a668702a1b1f1f88c6374301" source = "git+https://github.com/jggc/yaserde.git#adfdb1c5f4d054f114e5bd0ea7bda9c07a369def"
dependencies = [ dependencies = [
"heck", "heck",
"log", "log",

View File

@ -173,6 +173,10 @@ impl PhysicalHost {
self self
} }
pub fn get_mac_address(&self) -> Vec<MacAddress> {
self.network.iter().map(|nic| nic.mac_address).collect()
}
pub fn label(mut self, name: String, value: String) -> Self { pub fn label(mut self, name: String, value: String) -> Self {
self.labels.push(Label { name, value }); self.labels.push(Label { name, value });
self self

View File

@ -52,6 +52,104 @@ impl DhcpInterpret {
status: InterpretStatus::QUEUED, status: InterpretStatus::QUEUED,
} }
} }
async fn set_pxe_options<D: DhcpServer>(
&self,
_inventory: &Inventory,
dhcp_server: &D,
) -> Result<Outcome, InterpretError> {
todo!(
"I don't think this set_pxe_options function still works since the major dnsmasq refactoring. It certainly is not far off, but we use the dedicated okd ipxe score now. They should work together, this needs refactoring."
);
let pxe_options = PxeOptions {
ipxe_filename: self.score.filenameipxe.clone().unwrap_or_default(),
bios_filename: self.score.filename.clone().unwrap_or_default(),
efi_filename: self.score.filename64.clone().unwrap_or_default(),
tftp_ip: self.score.next_server,
};
dhcp_server.set_pxe_options(pxe_options).await?;
Ok(Outcome::new(
InterpretStatus::SUCCESS,
format!(
"Dhcp Interpret Set next boot to [{:?}], boot_filename to [{:?}], filename to [{:?}], filename64 to [{:?}], filenameipxe to [:{:?}]",
self.score.boot_filename,
self.score.boot_filename,
self.score.filename,
self.score.filename64,
self.score.filenameipxe
),
))
}
}
#[async_trait]
impl<T: Topology + DhcpServer> Interpret<T> for DhcpInterpret {
fn get_name(&self) -> InterpretName {
InterpretName::OPNSenseDHCP
}
fn get_version(&self) -> crate::domain::data::Version {
self.version.clone()
}
fn get_status(&self) -> InterpretStatus {
self.status.clone()
}
fn get_children(&self) -> Vec<Id> {
todo!()
}
async fn execute(
&self,
inventory: &Inventory,
topology: &T,
) -> Result<Outcome, InterpretError> {
info!("Executing DhcpInterpret on inventory {inventory:?}");
self.set_pxe_options(inventory, topology).await?;
DhcpHostBindingScore {
host_binding: self.score.host_binding.clone(),
}
.interpret(inventory, topology)
.await?;
topology.commit_config().await?;
Ok(Outcome::new(
InterpretStatus::SUCCESS,
"Dhcp Interpret execution successful".to_string(),
))
}
}
#[derive(Debug, new, Clone, Serialize)]
pub struct DhcpHostBindingScore {
pub host_binding: Vec<HostBinding>,
}
impl<T: Topology + DhcpServer> Score<T> for DhcpHostBindingScore {
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
Box::new(DhcpHostBindingInterpret {
score: self.clone(),
})
}
fn name(&self) -> String {
"DhcpHostBindingScore".to_string()
}
}
// https://docs.opnsense.org/manual/dhcp.html#advanced-settings
#[derive(Debug, Clone)]
pub struct DhcpHostBindingInterpret {
score: DhcpScore,
}
impl DhcpHostBindingInterpret {
async fn add_static_entries<D: DhcpServer>( async fn add_static_entries<D: DhcpServer>(
&self, &self,
_inventory: &Inventory, _inventory: &Inventory,
@ -94,47 +192,20 @@ impl DhcpInterpret {
format!("Dhcp Interpret registered {} entries", number_new_entries), format!("Dhcp Interpret registered {} entries", number_new_entries),
)) ))
} }
async fn set_pxe_options<D: DhcpServer>(
&self,
_inventory: &Inventory,
dhcp_server: &D,
) -> Result<Outcome, InterpretError> {
let pxe_options = PxeOptions {
ipxe_filename: self.score.filenameipxe.clone().unwrap_or_default(),
bios_filename: self.score.filename.clone().unwrap_or_default(),
efi_filename: self.score.filename64.clone().unwrap_or_default(),
tftp_ip: self.score.next_server,
};
dhcp_server.set_pxe_options(pxe_options).await?;
Ok(Outcome::new(
InterpretStatus::SUCCESS,
format!(
"Dhcp Interpret Set next boot to [{:?}], boot_filename to [{:?}], filename to [{:?}], filename64 to [{:?}], filenameipxe to [:{:?}]",
self.score.boot_filename,
self.score.boot_filename,
self.score.filename,
self.score.filename64,
self.score.filenameipxe
),
))
}
} }
#[async_trait] #[async_trait]
impl<T: DhcpServer> Interpret<T> for DhcpInterpret { impl<T: DhcpServer> Interpret<T> for DhcpHostBindingInterpret {
fn get_name(&self) -> InterpretName { fn get_name(&self) -> InterpretName {
InterpretName::OPNSenseDHCP InterpretName::Custom("DhcpHostBindingInterpret")
} }
fn get_version(&self) -> crate::domain::data::Version { fn get_version(&self) -> crate::domain::data::Version {
self.version.clone() Version::from("1.0.0").unwrap()
} }
fn get_status(&self) -> InterpretStatus { fn get_status(&self) -> InterpretStatus {
self.status.clone() todo!()
} }
fn get_children(&self) -> Vec<Id> { fn get_children(&self) -> Vec<Id> {
@ -146,9 +217,10 @@ impl<T: DhcpServer> Interpret<T> for DhcpInterpret {
inventory: &Inventory, inventory: &Inventory,
topology: &T, topology: &T,
) -> Result<Outcome, InterpretError> { ) -> Result<Outcome, InterpretError> {
info!("Executing DhcpInterpret on inventory {inventory:?}"); info!(
"Executing DhcpHostBindingInterpret on {} bindings",
self.set_pxe_options(inventory, topology).await?; self.score.host_binding.len()
);
self.add_static_entries(inventory, topology).await?; self.add_static_entries(inventory, topology).await?;
@ -156,7 +228,10 @@ impl<T: DhcpServer> Interpret<T> for DhcpInterpret {
Ok(Outcome::new( Ok(Outcome::new(
InterpretStatus::SUCCESS, InterpretStatus::SUCCESS,
"Dhcp Interpret execution successful".to_string(), format!(
"Dhcp Host Binding Interpret execution successful on {} hosts",
self.score.host_binding.len()
),
)) ))
} }
} }

View File

@ -3,14 +3,14 @@ use derive_new::new;
use serde::Serialize; use serde::Serialize;
use crate::{ use crate::{
data::{FileContent, Version}, data::{FileContent, FilePath, Version},
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
inventory::Inventory, inventory::Inventory,
score::Score, score::Score,
topology::{HttpServer, Topology}, topology::{HttpServer, Topology},
}; };
use harmony_types::id::Id;
use harmony_types::net::Url; use harmony_types::net::Url;
use harmony_types::{id::Id, net::MacAddress};
/// Configure an HTTP server that is provided by the Topology /// Configure an HTTP server that is provided by the Topology
/// ///
@ -91,3 +91,33 @@ impl<T: Topology + HttpServer> Interpret<T> for StaticFilesHttpInterpret {
todo!() todo!()
} }
} }
#[derive(Debug, new, Clone, Serialize)]
pub struct IPxeMacBootFileScore {
pub content: String,
pub mac_address: Vec<MacAddress>,
}
impl<T: Topology + HttpServer> Score<T> for IPxeMacBootFileScore {
fn name(&self) -> String {
"IPxeMacBootFileScore".to_string()
}
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
StaticFilesHttpScore {
folder_to_serve: None,
files: self
.mac_address
.iter()
.map(|mac| FileContent {
path: FilePath::Relative(format!(
"byMAC/01-{}.ipxe",
mac.to_string().replace(":", "-")
)),
content: self.content.clone(),
})
.collect(),
}
.create_interpret()
}
}

View File

@ -1,67 +0,0 @@
use async_trait::async_trait;
use derive_new::new;
use serde::Serialize;
use crate::{
data::Version,
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
inventory::Inventory,
score::Score,
topology::Topology,
};
use harmony_types::id::Id;
#[derive(Debug, new, Clone, Serialize)]
pub struct IpxeScore {
//files_to_serve: Url,
}
impl<T: Topology> Score<T> for IpxeScore {
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
Box::new(IpxeInterpret::new(self.clone()))
}
fn name(&self) -> String {
"IpxeScore".to_string()
}
}
#[derive(Debug, new, Clone)]
pub struct IpxeInterpret {
_score: IpxeScore,
}
#[async_trait]
impl<T: Topology> Interpret<T> for IpxeInterpret {
async fn execute(
&self,
_inventory: &Inventory,
_topology: &T,
) -> Result<Outcome, InterpretError> {
/*
let http_server = &topology.http_server;
http_server.ensure_initialized().await?;
Ok(Outcome::success(format!(
"Http Server running and serving files from {}",
self.score.files_to_serve
)))
*/
todo!();
}
fn get_name(&self) -> InterpretName {
InterpretName::Ipxe
}
fn get_version(&self) -> Version {
todo!()
}
fn get_status(&self) -> InterpretStatus {
todo!()
}
fn get_children(&self) -> Vec<Id> {
todo!()
}
}

View File

@ -6,7 +6,6 @@ pub mod dummy;
pub mod helm; pub mod helm;
pub mod http; pub mod http;
pub mod inventory; pub mod inventory;
pub mod ipxe;
pub mod k3d; pub mod k3d;
pub mod k8s; pub mod k8s;
pub mod lamp; pub mod lamp;

View File

@ -50,18 +50,19 @@
use async_trait::async_trait; use async_trait::async_trait;
use derive_new::new; use derive_new::new;
use harmony_macros::ip;
use harmony_types::id::Id; use harmony_types::id::Id;
use log::info; use log::{error, info};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
data::Version, data::Version,
hardware::PhysicalHost,
instrumentation::{HarmonyEvent, instrument}, instrumentation::{HarmonyEvent, instrument},
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
inventory::Inventory, inventory::Inventory,
modules::{dhcp::DhcpHostBindingScore, http::IPxeMacBootFileScore},
score::Score, score::Score,
topology::{DnsRecord, DnsRecordType, DnsServer, Topology}, topology::{HAClusterTopology, HostBinding},
}; };
// ------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------
@ -78,8 +79,8 @@ pub struct OKDInstallationScore {
pub internal_domain: String, pub internal_domain: String,
} }
impl<T: Topology + DnsServer + 'static> Score<T> for OKDInstallationScore { impl Score<HAClusterTopology> for OKDInstallationScore {
fn create_interpret(&self) -> Box<dyn Interpret<T>> { fn create_interpret(&self) -> Box<dyn Interpret<HAClusterTopology>> {
Box::new(OKDInstallationInterpret::new(self.clone())) Box::new(OKDInstallationInterpret::new(self.clone()))
} }
@ -109,10 +110,10 @@ impl OKDInstallationInterpret {
} }
} }
async fn run_inventory_phase<T: Topology + DnsServer>( async fn run_inventory_phase(
&self, &self,
inventory: &Inventory, inventory: &Inventory,
topology: &T, topology: &HAClusterTopology,
) -> Result<(), InterpretError> { ) -> Result<(), InterpretError> {
// 1) Prepare DNS and DHCP lease registration (optional) // 1) Prepare DNS and DHCP lease registration (optional)
let dns_score = OKDSetup01InventoryDnsScore::new( let dns_score = OKDSetup01InventoryDnsScore::new(
@ -129,10 +130,10 @@ impl OKDInstallationInterpret {
Ok(()) Ok(())
} }
async fn run_bootstrap_phase<T: Topology + DnsServer>( async fn run_bootstrap_phase(
&self, &self,
inventory: &Inventory, inventory: &Inventory,
topology: &T, topology: &HAClusterTopology,
) -> Result<(), InterpretError> { ) -> Result<(), InterpretError> {
// Select and provision bootstrap // Select and provision bootstrap
let bootstrap_score = OKDSetup02BootstrapScore::new( let bootstrap_score = OKDSetup02BootstrapScore::new(
@ -143,40 +144,40 @@ impl OKDInstallationInterpret {
Ok(()) Ok(())
} }
async fn run_control_plane_phase<T: Topology + DnsServer>( async fn run_control_plane_phase(
&self, &self,
inventory: &Inventory, inventory: &Inventory,
topology: &T, topology: &HAClusterTopology,
) -> Result<(), InterpretError> { ) -> Result<(), InterpretError> {
let control_plane_score = OKDSetup03ControlPlaneScore::new(); let control_plane_score = OKDSetup03ControlPlaneScore::new();
control_plane_score.interpret(inventory, topology).await?; control_plane_score.interpret(inventory, topology).await?;
Ok(()) Ok(())
} }
async fn run_workers_phase<T: Topology + DnsServer>( async fn run_workers_phase(
&self, &self,
inventory: &Inventory, inventory: &Inventory,
topology: &T, topology: &HAClusterTopology,
) -> Result<(), InterpretError> { ) -> Result<(), InterpretError> {
let workers_score = OKDSetup04WorkersScore::new(); let workers_score = OKDSetup04WorkersScore::new();
workers_score.interpret(inventory, topology).await?; workers_score.interpret(inventory, topology).await?;
Ok(()) Ok(())
} }
async fn run_sanity_phase<T: Topology + DnsServer>( async fn run_sanity_phase(
&self, &self,
inventory: &Inventory, inventory: &Inventory,
topology: &T, topology: &HAClusterTopology,
) -> Result<(), InterpretError> { ) -> Result<(), InterpretError> {
let sanity_score = OKDSetup05SanityCheckScore::new(); let sanity_score = OKDSetup05SanityCheckScore::new();
sanity_score.interpret(inventory, topology).await?; sanity_score.interpret(inventory, topology).await?;
Ok(()) Ok(())
} }
async fn run_report_phase<T: Topology + DnsServer>( async fn run_report_phase(
&self, &self,
inventory: &Inventory, inventory: &Inventory,
topology: &T, topology: &HAClusterTopology,
) -> Result<(), InterpretError> { ) -> Result<(), InterpretError> {
let report_score = OKDSetup06InstallationReportScore::new( let report_score = OKDSetup06InstallationReportScore::new(
self.score.public_domain.clone(), self.score.public_domain.clone(),
@ -188,7 +189,7 @@ impl OKDInstallationInterpret {
} }
#[async_trait] #[async_trait]
impl<T: Topology + DnsServer> Interpret<T> for OKDInstallationInterpret { impl Interpret<HAClusterTopology> for OKDInstallationInterpret {
fn get_name(&self) -> InterpretName { fn get_name(&self) -> InterpretName {
InterpretName::Custom("OKDInstallationInterpret") InterpretName::Custom("OKDInstallationInterpret")
} }
@ -208,7 +209,7 @@ impl<T: Topology + DnsServer> Interpret<T> for OKDInstallationInterpret {
async fn execute( async fn execute(
&self, &self,
inventory: &Inventory, inventory: &Inventory,
topology: &T, topology: &HAClusterTopology,
) -> Result<Outcome, InterpretError> { ) -> Result<Outcome, InterpretError> {
instrument(HarmonyEvent::HarmonyStarted).ok(); instrument(HarmonyEvent::HarmonyStarted).ok();
@ -251,8 +252,8 @@ struct OKDSetup01InventoryDnsScore {
register_dhcp_leases: Option<bool>, register_dhcp_leases: Option<bool>,
} }
impl<T: Topology + DnsServer> Score<T> for OKDSetup01InventoryDnsScore { impl Score<HAClusterTopology> for OKDSetup01InventoryDnsScore {
fn create_interpret(&self) -> Box<dyn Interpret<T>> { fn create_interpret(&self) -> Box<dyn Interpret<HAClusterTopology>> {
Box::new(OKDSetup01InventoryDnsInterpret::new(self.clone())) Box::new(OKDSetup01InventoryDnsInterpret::new(self.clone()))
} }
@ -277,42 +278,10 @@ impl OKDSetup01InventoryDnsInterpret {
status: InterpretStatus::QUEUED, status: InterpretStatus::QUEUED,
} }
} }
async fn ensure_dns<T: DnsServer>(&self, dns: &T) -> Result<(), InterpretError> {
// Minimal records placeholders; real IPs are set elsewhere in the flow.
// We register the names early to ensure resolvability for clients relying on DNS.
let mut records: Vec<DnsRecord> = vec![
DnsRecord {
value: ip!("0.0.0.0"),
host: "api".to_string(),
domain: self.score.internal_domain.clone(),
record_type: DnsRecordType::A,
},
DnsRecord {
value: ip!("0.0.0.0"),
host: "api-int".to_string(),
domain: self.score.internal_domain.clone(),
record_type: DnsRecordType::A,
},
DnsRecord {
value: ip!("0.0.0.0"),
host: "*.apps.".to_string(),
domain: self.score.internal_domain.clone(),
record_type: DnsRecordType::A,
},
];
dns.ensure_hosts_registered(records.drain(..).collect())
.await?;
if let Some(register) = self.score.register_dhcp_leases {
dns.register_dhcp_leases(register).await?;
}
dns.commit_config().await?;
Ok(())
}
} }
#[async_trait] #[async_trait]
impl<T: Topology + DnsServer> Interpret<T> for OKDSetup01InventoryDnsInterpret { impl Interpret<HAClusterTopology> for OKDSetup01InventoryDnsInterpret {
fn get_name(&self) -> InterpretName { fn get_name(&self) -> InterpretName {
InterpretName::Custom("OKDSetup01InventoryDns") InterpretName::Custom("OKDSetup01InventoryDns")
} }
@ -332,10 +301,10 @@ impl<T: Topology + DnsServer> Interpret<T> for OKDSetup01InventoryDnsInterpret {
async fn execute( async fn execute(
&self, &self,
_inventory: &Inventory, _inventory: &Inventory,
topology: &T, topology: &HAClusterTopology,
) -> Result<Outcome, InterpretError> { ) -> Result<Outcome, InterpretError> {
info!("Ensuring base DNS and DHCP lease registration for discovery phase"); info!("Ensuring base DNS and DHCP lease registration for discovery phase");
self.ensure_dns(topology).await?; error!("TODO setup ipxe score here and launch inventory agent");
Ok(Outcome::new( Ok(Outcome::new(
InterpretStatus::SUCCESS, InterpretStatus::SUCCESS,
"Inventory DNS prepared".into(), "Inventory DNS prepared".into(),
@ -354,8 +323,8 @@ struct OKDSetup01InventoryScore {
lan_cidr: String, lan_cidr: String,
} }
impl<T: Topology> Score<T> for OKDSetup01InventoryScore { impl Score<HAClusterTopology> for OKDSetup01InventoryScore {
fn create_interpret(&self) -> Box<dyn Interpret<T>> { fn create_interpret(&self) -> Box<dyn Interpret<HAClusterTopology>> {
Box::new(OKDSetup01InventoryInterpret::new(self.clone())) Box::new(OKDSetup01InventoryInterpret::new(self.clone()))
} }
@ -381,9 +350,9 @@ impl OKDSetup01InventoryInterpret {
} }
} }
async fn ensure_inventory_assets<T: Topology>( async fn ensure_inventory_assets(
&self, &self,
topology: &T, topology: &HAClusterTopology,
) -> Result<(), InterpretError> { ) -> Result<(), InterpretError> {
// Placeholder: push or verify iPXE default, Kickstart, and Rust inventory agent are hosted. // Placeholder: push or verify iPXE default, Kickstart, and Rust inventory agent are hosted.
// Real implementation: publish to the PXE/HTTP server via the topology. // Real implementation: publish to the PXE/HTTP server via the topology.
@ -408,7 +377,7 @@ impl OKDSetup01InventoryInterpret {
} }
#[async_trait] #[async_trait]
impl<T: Topology> Interpret<T> for OKDSetup01InventoryInterpret { impl Interpret<HAClusterTopology> for OKDSetup01InventoryInterpret {
fn get_name(&self) -> InterpretName { fn get_name(&self) -> InterpretName {
InterpretName::Custom("OKDSetup01Inventory") InterpretName::Custom("OKDSetup01Inventory")
} }
@ -428,7 +397,7 @@ impl<T: Topology> Interpret<T> for OKDSetup01InventoryInterpret {
async fn execute( async fn execute(
&self, &self,
_inventory: &Inventory, _inventory: &Inventory,
topology: &T, topology: &HAClusterTopology,
) -> Result<Outcome, InterpretError> { ) -> Result<Outcome, InterpretError> {
self.ensure_inventory_assets(topology).await?; self.ensure_inventory_assets(topology).await?;
let count = self.discover_nodes().await?; let count = self.discover_nodes().await?;
@ -454,8 +423,8 @@ struct OKDSetup02BootstrapScore {
internal_domain: String, internal_domain: String,
} }
impl<T: Topology> Score<T> for OKDSetup02BootstrapScore { impl Score<HAClusterTopology> for OKDSetup02BootstrapScore {
fn create_interpret(&self) -> Box<dyn Interpret<T>> { fn create_interpret(&self) -> Box<dyn Interpret<HAClusterTopology>> {
Box::new(OKDSetup02BootstrapInterpret::new(self.clone())) Box::new(OKDSetup02BootstrapInterpret::new(self.clone()))
} }
@ -481,9 +450,46 @@ impl OKDSetup02BootstrapInterpret {
} }
} }
async fn render_per_mac_pxe(&self) -> Result<(), InterpretError> { fn get_bootstrap_node<'a>(&self, inventory: &'a Inventory) -> &'a PhysicalHost {
inventory
.worker_host
.first()
.expect("At least one worker host is required to be used as bootstrap node")
}
async fn configure_host_binding(
&self,
inventory: &Inventory,
topology: &HAClusterTopology,
) -> Result<(), InterpretError> {
let binding = HostBinding {
logical_host: topology.bootstrap_host.clone(),
physical_host: self.get_bootstrap_node(inventory).clone(),
};
info!("Configuring host binding for bootstrap node {binding:?}");
DhcpHostBindingScore {
host_binding: vec![binding],
}
.interpret(inventory, topology)
.await?;
Ok(())
}
async fn render_per_mac_pxe(
&self,
inventory: &Inventory,
topology: &HAClusterTopology,
) -> Result<(), InterpretError> {
// Placeholder: use Harmony templates to emit {MAC}.ipxe selecting SCOS live + bootstrap ignition. // Placeholder: use Harmony templates to emit {MAC}.ipxe selecting SCOS live + bootstrap ignition.
info!("[Bootstrap] Rendering per-MAC PXE for bootstrap node"); info!("[Bootstrap] Rendering per-MAC PXE for bootstrap node");
let bootstrap_node = self.get_bootstrap_node(inventory);
IPxeMacBootFileScore {
mac_address: bootstrap_node.get_mac_address(),
content: todo!("templace for bootstrap node"),
}
.interpret(inventory, topology)
.await?;
Ok(()) Ok(())
} }
@ -501,7 +507,7 @@ impl OKDSetup02BootstrapInterpret {
} }
#[async_trait] #[async_trait]
impl<T: Topology> Interpret<T> for OKDSetup02BootstrapInterpret { impl Interpret<HAClusterTopology> for OKDSetup02BootstrapInterpret {
fn get_name(&self) -> InterpretName { fn get_name(&self) -> InterpretName {
InterpretName::Custom("OKDSetup02Bootstrap") InterpretName::Custom("OKDSetup02Bootstrap")
} }
@ -520,10 +526,11 @@ impl<T: Topology> Interpret<T> for OKDSetup02BootstrapInterpret {
async fn execute( async fn execute(
&self, &self,
_inventory: &Inventory, inventory: &Inventory,
_topology: &T, topology: &HAClusterTopology,
) -> Result<Outcome, InterpretError> { ) -> Result<Outcome, InterpretError> {
self.render_per_mac_pxe().await?; self.configure_host_binding(inventory, topology).await?;
self.render_per_mac_pxe(inventory, topology).await?;
self.reboot_target().await?; self.reboot_target().await?;
self.wait_for_bootstrap_complete().await?; self.wait_for_bootstrap_complete().await?;
@ -543,8 +550,8 @@ impl<T: Topology> Interpret<T> for OKDSetup02BootstrapInterpret {
#[derive(Debug, Clone, Serialize, new)] #[derive(Debug, Clone, Serialize, new)]
struct OKDSetup03ControlPlaneScore {} struct OKDSetup03ControlPlaneScore {}
impl<T: Topology> Score<T> for OKDSetup03ControlPlaneScore { impl Score<HAClusterTopology> for OKDSetup03ControlPlaneScore {
fn create_interpret(&self) -> Box<dyn Interpret<T>> { fn create_interpret(&self) -> Box<dyn Interpret<HAClusterTopology>> {
Box::new(OKDSetup03ControlPlaneInterpret::new(self.clone())) Box::new(OKDSetup03ControlPlaneInterpret::new(self.clone()))
} }
@ -583,7 +590,7 @@ impl OKDSetup03ControlPlaneInterpret {
} }
#[async_trait] #[async_trait]
impl<T: Topology> Interpret<T> for OKDSetup03ControlPlaneInterpret { impl Interpret<HAClusterTopology> for OKDSetup03ControlPlaneInterpret {
fn get_name(&self) -> InterpretName { fn get_name(&self) -> InterpretName {
InterpretName::Custom("OKDSetup03ControlPlane") InterpretName::Custom("OKDSetup03ControlPlane")
} }
@ -603,7 +610,7 @@ impl<T: Topology> Interpret<T> for OKDSetup03ControlPlaneInterpret {
async fn execute( async fn execute(
&self, &self,
_inventory: &Inventory, _inventory: &Inventory,
_topology: &T, _topology: &HAClusterTopology,
) -> Result<Outcome, InterpretError> { ) -> Result<Outcome, InterpretError> {
self.render_and_reboot().await?; self.render_and_reboot().await?;
self.persist_network_bond().await?; self.persist_network_bond().await?;
@ -623,8 +630,8 @@ impl<T: Topology> Interpret<T> for OKDSetup03ControlPlaneInterpret {
#[derive(Debug, Clone, Serialize, new)] #[derive(Debug, Clone, Serialize, new)]
struct OKDSetup04WorkersScore {} struct OKDSetup04WorkersScore {}
impl<T: Topology> Score<T> for OKDSetup04WorkersScore { impl Score<HAClusterTopology> for OKDSetup04WorkersScore {
fn create_interpret(&self) -> Box<dyn Interpret<T>> { fn create_interpret(&self) -> Box<dyn Interpret<HAClusterTopology>> {
Box::new(OKDSetup04WorkersInterpret::new(self.clone())) Box::new(OKDSetup04WorkersInterpret::new(self.clone()))
} }
@ -657,7 +664,7 @@ impl OKDSetup04WorkersInterpret {
} }
#[async_trait] #[async_trait]
impl<T: Topology> Interpret<T> for OKDSetup04WorkersInterpret { impl Interpret<HAClusterTopology> for OKDSetup04WorkersInterpret {
fn get_name(&self) -> InterpretName { fn get_name(&self) -> InterpretName {
InterpretName::Custom("OKDSetup04Workers") InterpretName::Custom("OKDSetup04Workers")
} }
@ -677,7 +684,7 @@ impl<T: Topology> Interpret<T> for OKDSetup04WorkersInterpret {
async fn execute( async fn execute(
&self, &self,
_inventory: &Inventory, _inventory: &Inventory,
_topology: &T, _topology: &HAClusterTopology,
) -> Result<Outcome, InterpretError> { ) -> Result<Outcome, InterpretError> {
self.render_and_reboot().await?; self.render_and_reboot().await?;
Ok(Outcome::new( Ok(Outcome::new(
@ -695,8 +702,8 @@ impl<T: Topology> Interpret<T> for OKDSetup04WorkersInterpret {
#[derive(Debug, Clone, Serialize, new)] #[derive(Debug, Clone, Serialize, new)]
struct OKDSetup05SanityCheckScore {} struct OKDSetup05SanityCheckScore {}
impl<T: Topology> Score<T> for OKDSetup05SanityCheckScore { impl Score<HAClusterTopology> for OKDSetup05SanityCheckScore {
fn create_interpret(&self) -> Box<dyn Interpret<T>> { fn create_interpret(&self) -> Box<dyn Interpret<HAClusterTopology>> {
Box::new(OKDSetup05SanityCheckInterpret::new(self.clone())) Box::new(OKDSetup05SanityCheckInterpret::new(self.clone()))
} }
@ -729,7 +736,7 @@ impl OKDSetup05SanityCheckInterpret {
} }
#[async_trait] #[async_trait]
impl<T: Topology> Interpret<T> for OKDSetup05SanityCheckInterpret { impl Interpret<HAClusterTopology> for OKDSetup05SanityCheckInterpret {
fn get_name(&self) -> InterpretName { fn get_name(&self) -> InterpretName {
InterpretName::Custom("OKDSetup05SanityCheck") InterpretName::Custom("OKDSetup05SanityCheck")
} }
@ -749,7 +756,7 @@ impl<T: Topology> Interpret<T> for OKDSetup05SanityCheckInterpret {
async fn execute( async fn execute(
&self, &self,
_inventory: &Inventory, _inventory: &Inventory,
_topology: &T, _topology: &HAClusterTopology,
) -> Result<Outcome, InterpretError> { ) -> Result<Outcome, InterpretError> {
self.run_checks().await?; self.run_checks().await?;
Ok(Outcome::new( Ok(Outcome::new(
@ -770,8 +777,8 @@ struct OKDSetup06InstallationReportScore {
internal_domain: String, internal_domain: String,
} }
impl<T: Topology> Score<T> for OKDSetup06InstallationReportScore { impl Score<HAClusterTopology> for OKDSetup06InstallationReportScore {
fn create_interpret(&self) -> Box<dyn Interpret<T>> { fn create_interpret(&self) -> Box<dyn Interpret<HAClusterTopology>> {
Box::new(OKDSetup06InstallationReportInterpret::new(self.clone())) Box::new(OKDSetup06InstallationReportInterpret::new(self.clone()))
} }
@ -807,7 +814,7 @@ impl OKDSetup06InstallationReportInterpret {
} }
#[async_trait] #[async_trait]
impl<T: Topology> Interpret<T> for OKDSetup06InstallationReportInterpret { impl Interpret<HAClusterTopology> for OKDSetup06InstallationReportInterpret {
fn get_name(&self) -> InterpretName { fn get_name(&self) -> InterpretName {
InterpretName::Custom("OKDSetup06InstallationReport") InterpretName::Custom("OKDSetup06InstallationReport")
} }
@ -827,7 +834,7 @@ impl<T: Topology> Interpret<T> for OKDSetup06InstallationReportInterpret {
async fn execute( async fn execute(
&self, &self,
_inventory: &Inventory, _inventory: &Inventory,
_topology: &T, _topology: &HAClusterTopology,
) -> Result<Outcome, InterpretError> { ) -> Result<Outcome, InterpretError> {
self.generate().await?; self.generate().await?;
Ok(Outcome::new( Ok(Outcome::new(

View File

@ -36,6 +36,27 @@ pub struct DnsMasq {
pub dhcp_options: Vec<DhcpOptions>, pub dhcp_options: Vec<DhcpOptions>,
pub dhcp_boot: Vec<DhcpBoot>, pub dhcp_boot: Vec<DhcpBoot>,
pub dhcp_tags: Vec<RawXml>, pub dhcp_tags: Vec<RawXml>,
pub hosts: Vec<DnsmasqHost>,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize, Clone)]
#[yaserde(rename = "hosts")]
pub struct DnsmasqHost {
#[yaserde(attribute = true)]
pub uuid: String,
pub host: String,
pub domain: MaybeString,
pub local: MaybeString,
pub ip: MaybeString,
pub cnames: MaybeString,
pub client_id: MaybeString,
pub hwaddr: MaybeString,
pub lease_time: MaybeString,
pub ignore: Option<u8>,
pub set_tag: MaybeString,
pub descr: MaybeString,
pub comments: MaybeString,
pub aliases: MaybeString,
} }
// Represents the <dhcp> element and its nested fields. // Represents the <dhcp> element and its nested fields.

View File

@ -226,6 +226,7 @@ mod tests {
"src/tests/data/config-full-ncd0.xml", "src/tests/data/config-full-ncd0.xml",
"src/tests/data/config-full-25.7.xml", "src/tests/data/config-full-25.7.xml",
"src/tests/data/config-full-25.7-dummy-dnsmasq-options.xml", "src/tests/data/config-full-25.7-dummy-dnsmasq-options.xml",
"src/tests/data/config-25.7-dnsmasq-static-host.xml",
] { ] {
let mut test_file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let mut test_file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
test_file_path.push(path); test_file_path.push(path);

View File

@ -442,7 +442,11 @@ mod test {
// This IP belongs to host-a, but the hostname belongs to host-b. // This IP belongs to host-a, but the hostname belongs to host-b.
dhcp_config dhcp_config
.add_static_mapping("CC:CC:CC:CC:CC:CC", Ipv4Addr::new(192, 168, 1, 10), "host-b") .add_static_mapping(
"CC:CC:CC:CC:CC:CC",
Ipv4Addr::new(192, 168, 1, 10),
"host-b",
)
.unwrap(); .unwrap();
} }
@ -457,7 +461,11 @@ mod test {
// This IP is ambiguous. // This IP is ambiguous.
dhcp_config dhcp_config
.add_static_mapping("CC:CC:CC:CC:CC:CC", Ipv4Addr::new(192, 168, 1, 30), "new-host") .add_static_mapping(
"CC:CC:CC:CC:CC:CC",
Ipv4Addr::new(192, 168, 1, 30),
"new-host",
)
.unwrap(); .unwrap();
} }
@ -509,8 +517,18 @@ mod test {
#[test] #[test]
fn test_remove_mac_from_correct_host_only() { fn test_remove_mac_from_correct_host_only() {
let host1 = create_host("uuid-1", "host-1", "192.168.1.50", "AA:AA:AA:AA:AA:AA,BB:BB:BB:BB:BB:BB"); let host1 = create_host(
let host2 = create_host("uuid-2", "host-2", "192.168.1.51", "CC:CC:CC:CC:CC:CC,DD:DD:DD:DD:DD:DD"); "uuid-1",
"host-1",
"192.168.1.50",
"AA:AA:AA:AA:AA:AA,BB:BB:BB:BB:BB:BB",
);
let host2 = create_host(
"uuid-2",
"host-2",
"192.168.1.51",
"CC:CC:CC:CC:CC:CC,DD:DD:DD:DD:DD:DD",
);
let mut dhcp_config = setup_test_env(vec![host1.clone(), host2.clone()]); let mut dhcp_config = setup_test_env(vec![host1.clone(), host2.clone()]);
dhcp_config.remove_static_mapping("AA:AA:AA:AA:AA:AA"); dhcp_config.remove_static_mapping("AA:AA:AA:AA:AA:AA");