Compare commits
1 Commits
b118eb57e6
...
730b913bd1
| Author | SHA1 | Date | |
|---|---|---|---|
| 730b913bd1 |
@ -32,6 +32,7 @@ pub enum InterpretName {
|
|||||||
Lamp,
|
Lamp,
|
||||||
ApplicationMonitoring,
|
ApplicationMonitoring,
|
||||||
K8sPrometheusCrdAlerting,
|
K8sPrometheusCrdAlerting,
|
||||||
|
Custom(&'static str),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for InterpretName {
|
impl std::fmt::Display for InterpretName {
|
||||||
@ -58,6 +59,7 @@ impl std::fmt::Display for InterpretName {
|
|||||||
InterpretName::Lamp => f.write_str("LAMP"),
|
InterpretName::Lamp => f.write_str("LAMP"),
|
||||||
InterpretName::ApplicationMonitoring => f.write_str("ApplicationMonitoring"),
|
InterpretName::ApplicationMonitoring => f.write_str("ApplicationMonitoring"),
|
||||||
InterpretName::K8sPrometheusCrdAlerting => f.write_str("K8sPrometheusCrdAlerting"),
|
InterpretName::K8sPrometheusCrdAlerting => f.write_str("K8sPrometheusCrdAlerting"),
|
||||||
|
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(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,5 +2,6 @@ pub mod bootstrap_dhcp;
|
|||||||
pub mod bootstrap_load_balancer;
|
pub mod bootstrap_load_balancer;
|
||||||
pub mod dhcp;
|
pub mod dhcp;
|
||||||
pub mod dns;
|
pub mod dns;
|
||||||
|
pub mod installation;
|
||||||
pub mod load_balancer;
|
pub mod load_balancer;
|
||||||
pub mod upgrade;
|
pub mod upgrade;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user