@@ -0,0 +1,868 @@
//! OKDInstallationScore
//!
//! Overview
//! --------
//! OKDInstallationScore orchestrates an end-to-end, bare-metal OKD (OpenShift/OKD 4.19)
//! installation using Harmony’ s strongly-typed Scores and Interprets. It encodes the
//! “discovery-first, then provision” strategy with strict ordering, observable progress,
//! and minimal assumptions about the underlying network.
//!
//! Design goals
//! - Deterministic, observable pipeline from unknown hardware to a healthy OKD cluster.
//! - Do NOT require LACP bonding during PXE/inventory. Bonding is configured only
//! after the host has a stable OS on disk (SCOS/RHCOS) and OKD MachineConfigs/NNCP
//! can enforce persistence safely.
//! - Support per-MAC iPXE rendering without requiring multiple DHCP reservations for
//! the same host. Discovery runs with generic DHCP (access/unbonded). Role-specific
//! per-MAC PXE entries are activated just-in-time before install.
//! - Emit HarmonyEvent instrumentation at each step via the Score::interpret path.
//!
//! High-level flow
//! 1) OKDSetup01Inventory
//! - Serve default iPXE + Kickstart (in-RAM CentOS Stream 9) for discovery only.
//! - Enable SSH with the cluster’ s ephemeral pubkey, start a Rust inventory agent.
//! - Harmony discovers nodes by scraping the agent endpoint and collects MACs/NICs.
//! - DNS: optionally register temporary hostnames and enable DHCP lease registration.
//!
//! 2) OKDSetup02Bootstrap
//! - User selects which discovered node becomes bootstrap.
//! - Render per-MAC iPXE for bootstrap with OKD 4.19 SCOS live assets + ignition.
//! - Reboot node via SSH; install bootstrap; wait for bootstrap-complete.
//!
//! 3) OKDSetup03ControlPlane
//! - Render per-MAC iPXE for cp0/cp1/cp2 with ignition (includes persistent bond via
//! MachineConfig or NNCP if required). Reboot via SSH, join masters.
//!
//! 4) OKDSetup04Workers
//! - Render per-MAC iPXE for worker set; join workers.
//!
//! 5) OKDSetup05SanityCheck
//! - Validate API/ingress/clusteroperators; ensure healthy control plane and SDN.
//!
//! 6) OKDSetup06InstallationReport
//! - Produce a concise, machine-readable report (JSON) and a human summary.
//!
//! Network notes
//! - During Inventory: ports must be simple access (no LACP). DHCP succeeds; iPXE
//! loads CentOS Stream live with Kickstart and starts the inventory endpoint.
//! - During Provisioning: only after SCOS is on disk and Ignition/MC can be applied
//! do we set the bond persistently. If early bonding is truly required on a host,
//! use kernel args selectively in the per-MAC PXE for that host, but never for the
//! generic discovery path.
//!
//! DNS and hostname
//! - Because a single host may present multiple MACs, but DHCP/ISC on OPNsense may not
//! easily support “one hostname across multiple MACs” in a single lease entry, we avoid
//! strict hostname binding during discovery. We rely on dynamic leases and record the
//! mapping (IP/MAC) at scrape time.
//! - Once a role is assigned, we render a per-MAC PXE entry and ensure the role-specific
//! DNS A/AAAA/CNAME entries are present (e.g., api, api-int, apps wildcard). This keeps
//! DHCP simple and DNS consistent for OKD.
//!
//! Instrumentation
//! - All child Scores are executed via Score::interpret, which emits HarmonyEvent
//! InterpretExecutionStarted/Finished. The orchestrator also emits HarmonyStarted/
//! HarmonyFinished around the full pipeline execution.
//!
//! Configuration knobs
//! - lan_cidr: CIDR to scan/allow for discovery endpoints.
//! - public_domain: External wildcard/apps domain (e.g., apps.example.com).
//! - internal_domain: Internal cluster domain (e.g., cluster.local or harmony.mcd).
//!
//! Notes
//! - This file co-locates step Scores for ease of review. In follow-up changes, refactor
//! step Scores (OKDSetupXX*) into separate modules.
use async_trait ::async_trait ;
use derive_new ::new ;
use harmony_macros ::{ ip , ipv4 } ;
use log ::info ;
use serde ::{ Deserialize , Serialize } ;
use crate ::{
data ::Version ,
instrumentation ::{ HarmonyEvent , instrument } ,
interpret ::{ Interpret , InterpretError , InterpretName , InterpretStatus , Outcome } ,
inventory ::Inventory ,
score ::Score ,
topology ::{ DnsRecord , DnsRecordType , DnsServer , Topology } ,
} ;
// -------------------------------------------------------------------------------------------------
// Public Orchestrator Score
// -------------------------------------------------------------------------------------------------
#[ derive(Debug, Clone, Serialize, Deserialize, new) ]
pub struct OKDInstallationScore {
/// The LAN CIDR where discovery endpoints live (e.g., 192.168.10.0/24)
pub lan_cidr : String ,
/// Public external domain (e.g., example.com). Used for api/apps wildcard, etc.
pub public_domain : String ,
/// Internal cluster domain (e.g., harmony.mcd). Used for internal svc/ingress and DNS.
pub internal_domain : String ,
}
impl < T : Topology + DnsServer + 'static > Score < T > for OKDInstallationScore {
fn create_interpret ( & self ) -> Box < dyn Interpret < T > > {
Box ::new ( OKDInstallationInterpret ::new ( self . clone ( ) ) )
}
fn name ( & self ) -> String {
" OKDInstallationScore " . to_string ( )
}
}
// -------------------------------------------------------------------------------------------------
// Orchestrator Interpret
// -------------------------------------------------------------------------------------------------
#[ derive(Debug, Clone) ]
pub struct OKDInstallationInterpret {
score : OKDInstallationScore ,
version : Version ,
status : InterpretStatus ,
}
impl OKDInstallationInterpret {
pub fn new ( score : OKDInstallationScore ) -> Self {
let version = Version ::from ( " 0.1.0 " ) . expect ( " valid version " ) ;
Self {
score ,
version ,
status : InterpretStatus ::QUEUED ,
}
}
async fn run_inventory_phase < T : Topology + DnsServer > (
& self ,
inventory : & Inventory ,
topology : & T ,
) -> Result < ( ) , InterpretError > {
// 1) Prepare DNS and DHCP lease registration (optional)
let dns_score = OKDSetup01InventoryDnsScore ::new (
self . score . internal_domain . clone ( ) ,
self . score . public_domain . clone ( ) ,
Some ( true ) , // register_dhcp_leases
) ;
dns_score . interpret ( inventory , topology ) . await ? ;
// 2) Serve default iPXE + Kickstart and poll discovery
let discovery_score = OKDSetup01InventoryScore ::new ( self . score . lan_cidr . clone ( ) ) ;
discovery_score . interpret ( inventory , topology ) . await ? ;
Ok ( ( ) )
}
async fn run_bootstrap_phase < T : Topology + DnsServer > (
& self ,
inventory : & Inventory ,
topology : & T ,
) -> Result < ( ) , InterpretError > {
// Select and provision bootstrap
let bootstrap_score = OKDSetup02BootstrapScore ::new (
self . score . public_domain . clone ( ) ,
self . score . internal_domain . clone ( ) ,
) ;
bootstrap_score . interpret ( inventory , topology ) . await ? ;
Ok ( ( ) )
}
async fn run_control_plane_phase < T : Topology + DnsServer > (
& self ,
inventory : & Inventory ,
topology : & T ,
) -> Result < ( ) , InterpretError > {
let control_plane_score = OKDSetup03ControlPlaneScore ::new ( ) ;
control_plane_score . interpret ( inventory , topology ) . await ? ;
Ok ( ( ) )
}
async fn run_workers_phase < T : Topology + DnsServer > (
& self ,
inventory : & Inventory ,
topology : & T ,
) -> Result < ( ) , InterpretError > {
let workers_score = OKDSetup04WorkersScore ::new ( ) ;
workers_score . interpret ( inventory , topology ) . await ? ;
Ok ( ( ) )
}
async fn run_sanity_phase < T : Topology + DnsServer > (
& self ,
inventory : & Inventory ,
topology : & T ,
) -> Result < ( ) , InterpretError > {
let sanity_score = OKDSetup05SanityCheckScore ::new ( ) ;
sanity_score . interpret ( inventory , topology ) . await ? ;
Ok ( ( ) )
}
async fn run_report_phase < T : Topology + DnsServer > (
& self ,
inventory : & Inventory ,
topology : & T ,
) -> Result < ( ) , InterpretError > {
let report_score = OKDSetup06InstallationReportScore ::new (
self . score . public_domain . clone ( ) ,
self . score . internal_domain . clone ( ) ,
) ;
report_score . interpret ( inventory , topology ) . await ? ;
Ok ( ( ) )
}
}
#[ async_trait ]
impl < T : Topology + DnsServer > Interpret < T > for OKDInstallationInterpret {
fn get_name ( & self ) -> InterpretName {
InterpretName ::Custom ( " OKDInstallationInterpret " )
}
fn get_version ( & self ) -> Version {
self . version . clone ( )
}
fn get_status ( & self ) -> InterpretStatus {
self . status . clone ( )
}
fn get_children ( & self ) -> Vec < crate ::domain ::data ::Id > {
vec! [ ]
}
async fn execute (
& self ,
inventory : & Inventory ,
topology : & T ,
) -> Result < Outcome , InterpretError > {
instrument ( HarmonyEvent ::HarmonyStarted ) . ok ( ) ;
info! (
" Starting OKD installation pipeline for public_domain={} internal_domain={} lan_cidr={} " ,
self . score . public_domain , self . score . internal_domain , self . score . lan_cidr
) ;
// 1) Inventory (default PXE, in-RAM kickstart, Rust inventory agent)
self . run_inventory_phase ( inventory , topology ) . await ? ;
// 2) Bootstrap (render per-MAC iPXE + ignition; reboot node; wait for bootstrap complete)
self . run_bootstrap_phase ( inventory , topology ) . await ? ;
// 3) Control plane
self . run_control_plane_phase ( inventory , topology ) . await ? ;
// 4) Workers
self . run_workers_phase ( inventory , topology ) . await ? ;
// 5) Sanity checks
self . run_sanity_phase ( inventory , topology ) . await ? ;
// 6) Installation report
self . run_report_phase ( inventory , topology ) . await ? ;
instrument ( HarmonyEvent ::HarmonyFinished ) . ok ( ) ;
Ok ( Outcome ::new (
InterpretStatus ::SUCCESS ,
" OKD installation pipeline completed " . into ( ) ,
) )
}
}
// -------------------------------------------------------------------------------------------------
// Step 01: Inventory DNS setup
// - Keep DHCP simple; optionally register dynamic leases into DNS.
// - Ensure base records for internal/public domains (api/api-int/apps wildcard).
// -------------------------------------------------------------------------------------------------
#[ derive(Debug, Clone, Serialize, new) ]
struct OKDSetup01InventoryDnsScore {
internal_domain : String ,
public_domain : String ,
register_dhcp_leases : Option < bool > ,
}
impl < T : Topology + DnsServer > Score < T > for OKDSetup01InventoryDnsScore {
fn create_interpret ( & self ) -> Box < dyn Interpret < T > > {
Box ::new ( OKDSetup01InventoryDnsInterpret ::new ( self . clone ( ) ) )
}
fn name ( & self ) -> String {
" OKDSetup01InventoryDnsScore " . to_string ( )
}
}
#[ derive(Debug, Clone) ]
struct OKDSetup01InventoryDnsInterpret {
score : OKDSetup01InventoryDnsScore ,
version : Version ,
status : InterpretStatus ,
}
impl OKDSetup01InventoryDnsInterpret {
pub fn new ( score : OKDSetup01InventoryDnsScore ) -> Self {
let version = Version ::from ( " 1.0.0 " ) . unwrap ( ) ;
Self {
version ,
score ,
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 ]
impl < T : Topology + DnsServer > Interpret < T > for OKDSetup01InventoryDnsInterpret {
fn get_name ( & self ) -> InterpretName {
InterpretName ::Custom ( " OKDSetup01InventoryDns " )
}
fn get_version ( & self ) -> Version {
self . version . clone ( )
}
fn get_status ( & self ) -> InterpretStatus {
self . status . clone ( )
}
fn get_children ( & self ) -> Vec < crate ::domain ::data ::Id > {
vec! [ ]
}
async fn execute (
& self ,
_inventory : & Inventory ,
topology : & T ,
) -> Result < Outcome , InterpretError > {
info! ( " Ensuring base DNS and DHCP lease registration for discovery phase " ) ;
self . ensure_dns ( topology ) . await ? ;
Ok ( Outcome ::new (
InterpretStatus ::SUCCESS ,
" Inventory DNS prepared " . into ( ) ,
) )
}
}
// -------------------------------------------------------------------------------------------------
// Step 01: Inventory (default PXE + Kickstart in RAM + Rust agent)
// - This score exposes/ensures the default inventory assets and waits for discoveries.
// - No early bonding. Simple access DHCP.
// -------------------------------------------------------------------------------------------------
#[ derive(Debug, Clone, Serialize, new) ]
struct OKDSetup01InventoryScore {
lan_cidr : String ,
}
impl < T : Topology > Score < T > for OKDSetup01InventoryScore {
fn create_interpret ( & self ) -> Box < dyn Interpret < T > > {
Box ::new ( OKDSetup01InventoryInterpret ::new ( self . clone ( ) ) )
}
fn name ( & self ) -> String {
" OKDSetup01InventoryScore " . to_string ( )
}
}
#[ derive(Debug, Clone) ]
struct OKDSetup01InventoryInterpret {
score : OKDSetup01InventoryScore ,
version : Version ,
status : InterpretStatus ,
}
impl OKDSetup01InventoryInterpret {
pub fn new ( score : OKDSetup01InventoryScore ) -> Self {
let version = Version ::from ( " 1.0.0 " ) . unwrap ( ) ;
Self {
version ,
score ,
status : InterpretStatus ::QUEUED ,
}
}
async fn ensure_inventory_assets < T : Topology > (
& self ,
topology : & T ,
) -> Result < ( ) , InterpretError > {
// Placeholder: push or verify iPXE default, Kickstart, and Rust inventory agent are hosted.
// Real implementation: publish to the PXE/HTTP server via the topology.
info! (
" [Inventory] Ensuring default iPXE, Kickstart, and inventory agent are available for LAN {} " ,
self . score . lan_cidr
) ;
// topology.publish_http_asset(…) ?
Ok ( ( ) )
}
async fn discover_nodes ( & self ) -> Result < usize , InterpretError > {
// Placeholder: implement Harmony discovery logic (scan/pull/push mode).
// Returns number of newly discovered nodes.
info! (
" [Inventory] Scanning for inventory agents in {} " ,
self . score . lan_cidr
) ;
// In practice, this would query harmony_composer or a local registry store.
Ok ( 3 )
}
}
#[ async_trait ]
impl < T : Topology > Interpret < T > for OKDSetup01InventoryInterpret {
fn get_name ( & self ) -> InterpretName {
InterpretName ::Custom ( " OKDSetup01Inventory " )
}
fn get_version ( & self ) -> Version {
self . version . clone ( )
}
fn get_status ( & self ) -> InterpretStatus {
self . status . clone ( )
}
fn get_children ( & self ) -> Vec < crate ::domain ::data ::Id > {
vec! [ ]
}
async fn execute (
& self ,
_inventory : & Inventory ,
topology : & T ,
) -> Result < Outcome , InterpretError > {
self . ensure_inventory_assets ( topology ) . await ? ;
let count = self . discover_nodes ( ) . await ? ;
info! ( " [Inventory] Discovered {count} nodes " ) ;
Ok ( Outcome ::new (
InterpretStatus ::SUCCESS ,
format! ( " Inventory phase complete. Nodes discovered: {count} " ) ,
) )
}
}
// -------------------------------------------------------------------------------------------------
// Step 02: Bootstrap
// - Select bootstrap node (from discovered set).
// - Render per-MAC iPXE pointing to OKD 4.19 SCOS live assets + bootstrap ignition.
// - Reboot the host via SSH and wait for bootstrap-complete.
// - No bonding at this stage unless absolutely required; prefer persistence via MC later.
// -------------------------------------------------------------------------------------------------
#[ derive(Debug, Clone, Serialize, new) ]
struct OKDSetup02BootstrapScore {
public_domain : String ,
internal_domain : String ,
}
impl < T : Topology > Score < T > for OKDSetup02BootstrapScore {
fn create_interpret ( & self ) -> Box < dyn Interpret < T > > {
Box ::new ( OKDSetup02BootstrapInterpret ::new ( self . clone ( ) ) )
}
fn name ( & self ) -> String {
" OKDSetup02BootstrapScore " . to_string ( )
}
}
#[ derive(Debug, Clone) ]
struct OKDSetup02BootstrapInterpret {
score : OKDSetup02BootstrapScore ,
version : Version ,
status : InterpretStatus ,
}
impl OKDSetup02BootstrapInterpret {
pub fn new ( score : OKDSetup02BootstrapScore ) -> Self {
let version = Version ::from ( " 1.0.0 " ) . unwrap ( ) ;
Self {
version ,
score ,
status : InterpretStatus ::QUEUED ,
}
}
async fn render_per_mac_pxe ( & self ) -> Result < ( ) , InterpretError > {
// Placeholder: use Harmony templates to emit {MAC}.ipxe selecting SCOS live + bootstrap ignition.
info! ( " [Bootstrap] Rendering per-MAC PXE for bootstrap node " ) ;
Ok ( ( ) )
}
async fn reboot_target ( & self ) -> Result < ( ) , InterpretError > {
// Placeholder: ssh reboot using the inventory ephemeral key
info! ( " [Bootstrap] Rebooting bootstrap node via SSH " ) ;
Ok ( ( ) )
}
async fn wait_for_bootstrap_complete ( & self ) -> Result < ( ) , InterpretError > {
// Placeholder: wait-for bootstrap-complete
info! ( " [Bootstrap] Waiting for bootstrap-complete … " ) ;
Ok ( ( ) )
}
}
#[ async_trait ]
impl < T : Topology > Interpret < T > for OKDSetup02BootstrapInterpret {
fn get_name ( & self ) -> InterpretName {
InterpretName ::Custom ( " OKDSetup02Bootstrap " )
}
fn get_version ( & self ) -> Version {
self . version . clone ( )
}
fn get_status ( & self ) -> InterpretStatus {
self . status . clone ( )
}
fn get_children ( & self ) -> Vec < crate ::domain ::data ::Id > {
vec! [ ]
}
async fn execute (
& self ,
_inventory : & Inventory ,
_topology : & T ,
) -> Result < Outcome , InterpretError > {
self . render_per_mac_pxe ( ) . await ? ;
self . reboot_target ( ) . await ? ;
self . wait_for_bootstrap_complete ( ) . await ? ;
Ok ( Outcome ::new (
InterpretStatus ::SUCCESS ,
" Bootstrap phase complete " . into ( ) ,
) )
}
}
// -------------------------------------------------------------------------------------------------
// Step 03: Control Plane
// - Render per-MAC PXE & ignition for cp0/cp1/cp2.
// - Persist bonding via MachineConfigs (or NNCP) once SCOS is active.
// -------------------------------------------------------------------------------------------------
#[ derive(Debug, Clone, Serialize, new) ]
struct OKDSetup03ControlPlaneScore { }
impl < T : Topology > Score < T > for OKDSetup03ControlPlaneScore {
fn create_interpret ( & self ) -> Box < dyn Interpret < T > > {
Box ::new ( OKDSetup03ControlPlaneInterpret ::new ( self . clone ( ) ) )
}
fn name ( & self ) -> String {
" OKDSetup03ControlPlaneScore " . to_string ( )
}
}
#[ derive(Debug, Clone) ]
struct OKDSetup03ControlPlaneInterpret {
score : OKDSetup03ControlPlaneScore ,
version : Version ,
status : InterpretStatus ,
}
impl OKDSetup03ControlPlaneInterpret {
pub fn new ( score : OKDSetup03ControlPlaneScore ) -> Self {
let version = Version ::from ( " 1.0.0 " ) . unwrap ( ) ;
Self {
version ,
score ,
status : InterpretStatus ::QUEUED ,
}
}
async fn render_and_reboot ( & self ) -> Result < ( ) , InterpretError > {
info! ( " [ControlPlane] Rendering per-MAC PXE for masters and rebooting " ) ;
Ok ( ( ) )
}
async fn persist_network_bond ( & self ) -> Result < ( ) , InterpretError > {
// Generate MC or NNCP from inventory NIC data; apply via ignition or post-join.
info! ( " [ControlPlane] Ensuring persistent bonding via MachineConfig/NNCP " ) ;
Ok ( ( ) )
}
}
#[ async_trait ]
impl < T : Topology > Interpret < T > for OKDSetup03ControlPlaneInterpret {
fn get_name ( & self ) -> InterpretName {
InterpretName ::Custom ( " OKDSetup03ControlPlane " )
}
fn get_version ( & self ) -> Version {
self . version . clone ( )
}
fn get_status ( & self ) -> InterpretStatus {
self . status . clone ( )
}
fn get_children ( & self ) -> Vec < crate ::domain ::data ::Id > {
vec! [ ]
}
async fn execute (
& self ,
_inventory : & Inventory ,
_topology : & T ,
) -> Result < Outcome , InterpretError > {
self . render_and_reboot ( ) . await ? ;
self . persist_network_bond ( ) . await ? ;
Ok ( Outcome ::new (
InterpretStatus ::SUCCESS ,
" Control plane provisioned " . into ( ) ,
) )
}
}
// -------------------------------------------------------------------------------------------------
// Step 04: Workers
// - Render per-MAC PXE & ignition for workers; join nodes.
// - Persist bonding via MC/NNCP as required (same approach as masters).
// -------------------------------------------------------------------------------------------------
#[ derive(Debug, Clone, Serialize, new) ]
struct OKDSetup04WorkersScore { }
impl < T : Topology > Score < T > for OKDSetup04WorkersScore {
fn create_interpret ( & self ) -> Box < dyn Interpret < T > > {
Box ::new ( OKDSetup04WorkersInterpret ::new ( self . clone ( ) ) )
}
fn name ( & self ) -> String {
" OKDSetup04WorkersScore " . to_string ( )
}
}
#[ derive(Debug, Clone) ]
struct OKDSetup04WorkersInterpret {
score : OKDSetup04WorkersScore ,
version : Version ,
status : InterpretStatus ,
}
impl OKDSetup04WorkersInterpret {
pub fn new ( score : OKDSetup04WorkersScore ) -> Self {
let version = Version ::from ( " 1.0.0 " ) . unwrap ( ) ;
Self {
version ,
score ,
status : InterpretStatus ::QUEUED ,
}
}
async fn render_and_reboot ( & self ) -> Result < ( ) , InterpretError > {
info! ( " [Workers] Rendering per-MAC PXE for workers and rebooting " ) ;
Ok ( ( ) )
}
}
#[ async_trait ]
impl < T : Topology > Interpret < T > for OKDSetup04WorkersInterpret {
fn get_name ( & self ) -> InterpretName {
InterpretName ::Custom ( " OKDSetup04Workers " )
}
fn get_version ( & self ) -> Version {
self . version . clone ( )
}
fn get_status ( & self ) -> InterpretStatus {
self . status . clone ( )
}
fn get_children ( & self ) -> Vec < crate ::domain ::data ::Id > {
vec! [ ]
}
async fn execute (
& self ,
_inventory : & Inventory ,
_topology : & T ,
) -> Result < Outcome , InterpretError > {
self . render_and_reboot ( ) . await ? ;
Ok ( Outcome ::new (
InterpretStatus ::SUCCESS ,
" Workers provisioned " . into ( ) ,
) )
}
}
// -------------------------------------------------------------------------------------------------
// Step 05: Sanity Check
// - Validate API reachability, ClusterOperators, ingress, and SDN status.
// -------------------------------------------------------------------------------------------------
#[ derive(Debug, Clone, Serialize, new) ]
struct OKDSetup05SanityCheckScore { }
impl < T : Topology > Score < T > for OKDSetup05SanityCheckScore {
fn create_interpret ( & self ) -> Box < dyn Interpret < T > > {
Box ::new ( OKDSetup05SanityCheckInterpret ::new ( self . clone ( ) ) )
}
fn name ( & self ) -> String {
" OKDSetup05SanityCheckScore " . to_string ( )
}
}
#[ derive(Debug, Clone) ]
struct OKDSetup05SanityCheckInterpret {
score : OKDSetup05SanityCheckScore ,
version : Version ,
status : InterpretStatus ,
}
impl OKDSetup05SanityCheckInterpret {
pub fn new ( score : OKDSetup05SanityCheckScore ) -> Self {
let version = Version ::from ( " 1.0.0 " ) . unwrap ( ) ;
Self {
version ,
score ,
status : InterpretStatus ::QUEUED ,
}
}
async fn run_checks ( & self ) -> Result < ( ) , InterpretError > {
info! ( " [Sanity] Checking API, COs, Ingress, and SDN health … " ) ;
Ok ( ( ) )
}
}
#[ async_trait ]
impl < T : Topology > Interpret < T > for OKDSetup05SanityCheckInterpret {
fn get_name ( & self ) -> InterpretName {
InterpretName ::Custom ( " OKDSetup05SanityCheck " )
}
fn get_version ( & self ) -> Version {
self . version . clone ( )
}
fn get_status ( & self ) -> InterpretStatus {
self . status . clone ( )
}
fn get_children ( & self ) -> Vec < crate ::domain ::data ::Id > {
vec! [ ]
}
async fn execute (
& self ,
_inventory : & Inventory ,
_topology : & T ,
) -> Result < Outcome , InterpretError > {
self . run_checks ( ) . await ? ;
Ok ( Outcome ::new (
InterpretStatus ::SUCCESS ,
" Sanity checks passed " . into ( ) ,
) )
}
}
// -------------------------------------------------------------------------------------------------
// Step 06: Installation Report
// - Emit JSON and concise human summary of nodes, roles, versions, and health.
// -------------------------------------------------------------------------------------------------
#[ derive(Debug, Clone, Serialize, new) ]
struct OKDSetup06InstallationReportScore {
public_domain : String ,
internal_domain : String ,
}
impl < T : Topology > Score < T > for OKDSetup06InstallationReportScore {
fn create_interpret ( & self ) -> Box < dyn Interpret < T > > {
Box ::new ( OKDSetup06InstallationReportInterpret ::new ( self . clone ( ) ) )
}
fn name ( & self ) -> String {
" OKDSetup06InstallationReportScore " . to_string ( )
}
}
#[ derive(Debug, Clone) ]
struct OKDSetup06InstallationReportInterpret {
score : OKDSetup06InstallationReportScore ,
version : Version ,
status : InterpretStatus ,
}
impl OKDSetup06InstallationReportInterpret {
pub fn new ( score : OKDSetup06InstallationReportScore ) -> Self {
let version = Version ::from ( " 1.0.0 " ) . unwrap ( ) ;
Self {
version ,
score ,
status : InterpretStatus ::QUEUED ,
}
}
async fn generate ( & self ) -> Result < ( ) , InterpretError > {
info! (
" [Report] Generating installation report for {} / {} " ,
self . score . public_domain , self . score . internal_domain
) ;
Ok ( ( ) )
}
}
#[ async_trait ]
impl < T : Topology > Interpret < T > for OKDSetup06InstallationReportInterpret {
fn get_name ( & self ) -> InterpretName {
InterpretName ::Custom ( " OKDSetup06InstallationReport " )
}
fn get_version ( & self ) -> Version {
self . version . clone ( )
}
fn get_status ( & self ) -> InterpretStatus {
self . status . clone ( )
}
fn get_children ( & self ) -> Vec < crate ::domain ::data ::Id > {
vec! [ ]
}
async fn execute (
& self ,
_inventory : & Inventory ,
_topology : & T ,
) -> Result < Outcome , InterpretError > {
self . generate ( ) . await ? ;
Ok ( Outcome ::new (
InterpretStatus ::SUCCESS ,
" Installation report generated " . into ( ) ,
) )
}
}