From 0070373714057c5965540854293a16f7c08ef268 Mon Sep 17 00:00:00 2001 From: Jean-Gabriel Gill-Couture Date: Tue, 2 Sep 2025 00:39:52 -0400 Subject: [PATCH] feat(okd installation): Process works nicely all the way up to setting the bootstrap host binding in opnsense automatically! Next step : generate the mac address boot file for bootstrap host, install ignition files and the cluster will booooooooot --- ...a3fd4878dc2e217dc83f9bf45a402dfd62a91.json | 20 +++++++++++ ...090c94a222115c543231f2140cba27bd0f067.json | 2 +- examples/okd_installation/src/topology.rs | 6 ++-- harmony/src/domain/hardware/mod.rs | 1 - harmony/src/domain/inventory/repository.rs | 1 + harmony/src/infra/inventory/sqlite.rs | 36 +++++++++++++++++-- harmony/src/modules/dhcp.rs | 4 +-- harmony/src/modules/okd/installation.rs | 25 +++++++++---- 8 files changed, 78 insertions(+), 17 deletions(-) create mode 100644 .sqlx/query-2ea29df2326f7c84bd4100ad510a3fd4878dc2e217dc83f9bf45a402dfd62a91.json diff --git a/.sqlx/query-2ea29df2326f7c84bd4100ad510a3fd4878dc2e217dc83f9bf45a402dfd62a91.json b/.sqlx/query-2ea29df2326f7c84bd4100ad510a3fd4878dc2e217dc83f9bf45a402dfd62a91.json new file mode 100644 index 0000000..4245c23 --- /dev/null +++ b/.sqlx/query-2ea29df2326f7c84bd4100ad510a3fd4878dc2e217dc83f9bf45a402dfd62a91.json @@ -0,0 +1,20 @@ +{ + "db_name": "SQLite", + "query": "SELECT host_id FROM host_role_mapping WHERE role = ?", + "describe": { + "columns": [ + { + "name": "host_id", + "ordinal": 0, + "type_info": "Text" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + }, + "hash": "2ea29df2326f7c84bd4100ad510a3fd4878dc2e217dc83f9bf45a402dfd62a91" +} diff --git a/.sqlx/query-8d247918eca10a88b784ee353db090c94a222115c543231f2140cba27bd0f067.json b/.sqlx/query-8d247918eca10a88b784ee353db090c94a222115c543231f2140cba27bd0f067.json index ba998bc..0b92e37 100644 --- a/.sqlx/query-8d247918eca10a88b784ee353db090c94a222115c543231f2140cba27bd0f067.json +++ b/.sqlx/query-8d247918eca10a88b784ee353db090c94a222115c543231f2140cba27bd0f067.json @@ -16,7 +16,7 @@ { "name": "data: Json", "ordinal": 2, - "type_info": "Null" + "type_info": "Blob" } ], "parameters": { diff --git a/examples/okd_installation/src/topology.rs b/examples/okd_installation/src/topology.rs index 27eb8c0..3c7de8e 100644 --- a/examples/okd_installation/src/topology.rs +++ b/examples/okd_installation/src/topology.rs @@ -50,12 +50,12 @@ pub async fn get_topology() -> HAClusterTopology { dhcp_server: opnsense.clone(), dns_server: opnsense.clone(), control_plane: vec![LogicalHost { - ip: ip!("10.100.8.20"), + ip: ip!("192.168.1.20"), name: "cp0".to_string(), }], bootstrap_host: LogicalHost { - ip: ip!("10.100.8.20"), - name: "cp0".to_string(), + ip: ip!("192.168.1.20"), + name: "bootstrap".to_string(), }, workers: vec![], switch: vec![], diff --git a/harmony/src/domain/hardware/mod.rs b/harmony/src/domain/hardware/mod.rs index e605380..5d1e846 100644 --- a/harmony/src/domain/hardware/mod.rs +++ b/harmony/src/domain/hardware/mod.rs @@ -225,7 +225,6 @@ impl PhysicalHost { // } // } - #[derive(new, Serialize)] pub struct ManualManagementInterface; diff --git a/harmony/src/domain/inventory/repository.rs b/harmony/src/domain/inventory/repository.rs index f638b18..0728cc7 100644 --- a/harmony/src/domain/inventory/repository.rs +++ b/harmony/src/domain/inventory/repository.rs @@ -29,6 +29,7 @@ pub trait InventoryRepository: Send + Sync + 'static { async fn save(&self, host: &PhysicalHost) -> Result<(), RepoError>; async fn get_latest_by_id(&self, host_id: &str) -> Result, RepoError>; async fn get_all_hosts(&self) -> Result, RepoError>; + async fn get_host_for_role(&self, role: HostRole) -> Result, RepoError>; async fn save_role_mapping( &self, role: &HostRole, diff --git a/harmony/src/infra/inventory/sqlite.rs b/harmony/src/infra/inventory/sqlite.rs index b678b0e..d626640 100644 --- a/harmony/src/infra/inventory/sqlite.rs +++ b/harmony/src/infra/inventory/sqlite.rs @@ -46,14 +46,15 @@ impl InventoryRepository for SqliteInventoryRepository { } async fn get_latest_by_id(&self, host_id: &str) -> Result, RepoError> { - let _row = sqlx::query_as!( + let row = sqlx::query_as!( DbHost, r#"SELECT id, version_id, data as "data: Json" FROM physical_hosts WHERE id = ? ORDER BY version_id DESC LIMIT 1"#, host_id ) .fetch_optional(&self.pool) .await?; - todo!() + + Ok(row.map(|r| r.data.0)) } async fn get_all_hosts(&self) -> Result, RepoError> { @@ -107,6 +108,36 @@ impl InventoryRepository for SqliteInventoryRepository { Ok(()) } + async fn get_host_for_role(&self, role: HostRole) -> Result, RepoError> { + struct HostIdRow { + host_id: String, + } + + let role_str = format!("{:?}", role); + + let host_id_rows = sqlx::query_as!( + HostIdRow, + "SELECT host_id FROM host_role_mapping WHERE role = ?", + role_str + ) + .fetch_all(&self.pool) + .await?; + + let mut hosts = Vec::with_capacity(host_id_rows.len()); + for row in host_id_rows { + match self.get_latest_by_id(&row.host_id).await? { + Some(host) => hosts.push(host), + None => { + log::warn!( + "Found a role mapping for host_id '{}', but the host does not exist in the physical_hosts table. This may indicate a data integrity issue.", + row.host_id + ); + } + } + } + + Ok(hosts) + } } use sqlx::types::Json; @@ -115,4 +146,3 @@ struct DbHost { id: String, version_id: String, } - diff --git a/harmony/src/modules/dhcp.rs b/harmony/src/modules/dhcp.rs index b5c540c..1270fa6 100644 --- a/harmony/src/modules/dhcp.rs +++ b/harmony/src/modules/dhcp.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; use derive_new::new; use harmony_types::id::Id; -use log::info; +use log::{info, trace}; use serde::Serialize; use crate::{ @@ -177,7 +177,7 @@ impl DhcpHostBindingInterpret { .collect(); info!("DHCPStaticEntry : {:?}", dhcp_entries); - info!("DHCP server : {:?}", dhcp_server); + trace!("DHCP server : {:?}", dhcp_server); let number_new_entries = dhcp_entries.len(); diff --git a/harmony/src/modules/okd/installation.rs b/harmony/src/modules/okd/installation.rs index e61de25..1087a24 100644 --- a/harmony/src/modules/okd/installation.rs +++ b/harmony/src/modules/okd/installation.rs @@ -411,11 +411,22 @@ impl OKDSetup02BootstrapInterpret { } } - 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 get_bootstrap_node( + &self, + _inventory: &Inventory, + ) -> Result { + let repo = InventoryRepositoryFactory::build().await?; + match repo + .get_host_for_role(HostRole::Bootstrap) + .await? + .into_iter() + .next() + { + Some(host) => Ok(host), + None => Err(InterpretError::new( + "No bootstrap node available".to_string(), + )), + } } async fn configure_host_binding( @@ -425,7 +436,7 @@ impl OKDSetup02BootstrapInterpret { ) -> Result<(), InterpretError> { let binding = HostBinding { logical_host: topology.bootstrap_host.clone(), - physical_host: self.get_bootstrap_node(inventory).clone(), + physical_host: self.get_bootstrap_node(inventory).await?, }; info!("Configuring host binding for bootstrap node {binding:?}"); @@ -444,7 +455,7 @@ impl OKDSetup02BootstrapInterpret { ) -> Result<(), InterpretError> { // Placeholder: use Harmony templates to emit {MAC}.ipxe selecting SCOS live + bootstrap ignition. info!("[Bootstrap] Rendering per-MAC PXE for bootstrap node"); - let bootstrap_node = self.get_bootstrap_node(inventory); + let bootstrap_node = self.get_bootstrap_node(inventory).await?; IPxeMacBootFileScore { mac_address: bootstrap_node.get_mac_address(), content: todo!("templace for bootstrap node"),