wip: OKD Installation automation layed out. Next step : review this after some sleep and fill in the (many) blanks with actual implementations.
This commit is contained in:
parent
55bfe306ad
commit
56c181fc3d
@ -32,6 +32,7 @@ pub enum InterpretName {
|
||||
K8sPrometheusCrdAlerting,
|
||||
DiscoverInventoryAgent,
|
||||
CephClusterHealth,
|
||||
Custom(&'static str),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for InterpretName {
|
||||
@ -60,6 +61,7 @@ impl std::fmt::Display for InterpretName {
|
||||
InterpretName::K8sPrometheusCrdAlerting => f.write_str("K8sPrometheusCrdAlerting"),
|
||||
InterpretName::DiscoverInventoryAgent => f.write_str("DiscoverInventoryAgent"),
|
||||
InterpretName::CephClusterHealth => f.write_str("CephClusterHealth"),
|
||||
InterpretName::Custom(name) => f.write_str(name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
868
harmony/src/modules/okd/installation.rs
Normal file
868
harmony/src/modules/okd/installation.rs
Normal file
@ -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(),
|
||||
))
|
||||
}
|
||||
}
|
@ -3,5 +3,6 @@ pub mod bootstrap_load_balancer;
|
||||
pub mod dhcp;
|
||||
pub mod dns;
|
||||
pub mod ipxe;
|
||||
pub mod installation;
|
||||
pub mod load_balancer;
|
||||
pub mod upgrade;
|
||||
|
Loading…
Reference in New Issue
Block a user