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
All checks were successful
Run Check Script / check (pull_request) Successful in 1m16s

This commit is contained in:
Jean-Gabriel Gill-Couture 2025-09-02 00:39:52 -04:00
parent f6e665f990
commit 0070373714
8 changed files with 78 additions and 17 deletions

View File

@ -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"
}

View File

@ -16,7 +16,7 @@
{ {
"name": "data: Json<PhysicalHost>", "name": "data: Json<PhysicalHost>",
"ordinal": 2, "ordinal": 2,
"type_info": "Null" "type_info": "Blob"
} }
], ],
"parameters": { "parameters": {

View File

@ -50,12 +50,12 @@ pub async fn get_topology() -> HAClusterTopology {
dhcp_server: opnsense.clone(), dhcp_server: opnsense.clone(),
dns_server: opnsense.clone(), dns_server: opnsense.clone(),
control_plane: vec![LogicalHost { control_plane: vec![LogicalHost {
ip: ip!("10.100.8.20"), ip: ip!("192.168.1.20"),
name: "cp0".to_string(), name: "cp0".to_string(),
}], }],
bootstrap_host: LogicalHost { bootstrap_host: LogicalHost {
ip: ip!("10.100.8.20"), ip: ip!("192.168.1.20"),
name: "cp0".to_string(), name: "bootstrap".to_string(),
}, },
workers: vec![], workers: vec![],
switch: vec![], switch: vec![],

View File

@ -225,7 +225,6 @@ impl PhysicalHost {
// } // }
// } // }
#[derive(new, Serialize)] #[derive(new, Serialize)]
pub struct ManualManagementInterface; pub struct ManualManagementInterface;

View File

@ -29,6 +29,7 @@ pub trait InventoryRepository: Send + Sync + 'static {
async fn save(&self, host: &PhysicalHost) -> Result<(), RepoError>; async fn save(&self, host: &PhysicalHost) -> Result<(), RepoError>;
async fn get_latest_by_id(&self, host_id: &str) -> Result<Option<PhysicalHost>, RepoError>; async fn get_latest_by_id(&self, host_id: &str) -> Result<Option<PhysicalHost>, RepoError>;
async fn get_all_hosts(&self) -> Result<Vec<PhysicalHost>, RepoError>; async fn get_all_hosts(&self) -> Result<Vec<PhysicalHost>, RepoError>;
async fn get_host_for_role(&self, role: HostRole) -> Result<Vec<PhysicalHost>, RepoError>;
async fn save_role_mapping( async fn save_role_mapping(
&self, &self,
role: &HostRole, role: &HostRole,

View File

@ -46,14 +46,15 @@ impl InventoryRepository for SqliteInventoryRepository {
} }
async fn get_latest_by_id(&self, host_id: &str) -> Result<Option<PhysicalHost>, RepoError> { async fn get_latest_by_id(&self, host_id: &str) -> Result<Option<PhysicalHost>, RepoError> {
let _row = sqlx::query_as!( let row = sqlx::query_as!(
DbHost, DbHost,
r#"SELECT id, version_id, data as "data: Json<PhysicalHost>" FROM physical_hosts WHERE id = ? ORDER BY version_id DESC LIMIT 1"#, r#"SELECT id, version_id, data as "data: Json<PhysicalHost>" FROM physical_hosts WHERE id = ? ORDER BY version_id DESC LIMIT 1"#,
host_id host_id
) )
.fetch_optional(&self.pool) .fetch_optional(&self.pool)
.await?; .await?;
todo!()
Ok(row.map(|r| r.data.0))
} }
async fn get_all_hosts(&self) -> Result<Vec<PhysicalHost>, RepoError> { async fn get_all_hosts(&self) -> Result<Vec<PhysicalHost>, RepoError> {
@ -107,6 +108,36 @@ impl InventoryRepository for SqliteInventoryRepository {
Ok(()) Ok(())
} }
async fn get_host_for_role(&self, role: HostRole) -> Result<Vec<PhysicalHost>, 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; use sqlx::types::Json;
@ -115,4 +146,3 @@ struct DbHost {
id: String, id: String,
version_id: String, version_id: String,
} }

View File

@ -1,7 +1,7 @@
use async_trait::async_trait; use async_trait::async_trait;
use derive_new::new; use derive_new::new;
use harmony_types::id::Id; use harmony_types::id::Id;
use log::info; use log::{info, trace};
use serde::Serialize; use serde::Serialize;
use crate::{ use crate::{
@ -177,7 +177,7 @@ impl DhcpHostBindingInterpret {
.collect(); .collect();
info!("DHCPStaticEntry : {:?}", dhcp_entries); info!("DHCPStaticEntry : {:?}", dhcp_entries);
info!("DHCP server : {:?}", dhcp_server); trace!("DHCP server : {:?}", dhcp_server);
let number_new_entries = dhcp_entries.len(); let number_new_entries = dhcp_entries.len();

View File

@ -411,11 +411,22 @@ impl OKDSetup02BootstrapInterpret {
} }
} }
fn get_bootstrap_node<'a>(&self, inventory: &'a Inventory) -> &'a PhysicalHost { async fn get_bootstrap_node(
inventory &self,
.worker_host _inventory: &Inventory,
.first() ) -> Result<PhysicalHost, InterpretError> {
.expect("At least one worker host is required to be used as bootstrap node") 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( async fn configure_host_binding(
@ -425,7 +436,7 @@ impl OKDSetup02BootstrapInterpret {
) -> Result<(), InterpretError> { ) -> Result<(), InterpretError> {
let binding = HostBinding { let binding = HostBinding {
logical_host: topology.bootstrap_host.clone(), 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:?}"); info!("Configuring host binding for bootstrap node {binding:?}");
@ -444,7 +455,7 @@ impl OKDSetup02BootstrapInterpret {
) -> Result<(), InterpretError> { ) -> 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); let bootstrap_node = self.get_bootstrap_node(inventory).await?;
IPxeMacBootFileScore { IPxeMacBootFileScore {
mac_address: bootstrap_node.get_mac_address(), mac_address: bootstrap_node.get_mac_address(),
content: todo!("templace for bootstrap node"), content: todo!("templace for bootstrap node"),