feat: introduce topology readiness and initialization
Adds a `ensure_ready` method to the `Topology` trait to ensure the infrastructure is prepared before score execution. - Introduces a new `Outcome` status to indicate the result of the readiness check. - Implements a `topology_preparation_result` field in `Maestro` to track initialization status. - Adds a check in `interpret` to warn if the topology isn't fully initialized. - Provides detailed documentation for the `Topology` trait and `ensure_ready` method, including recommended patterns for complex setups. - Adds `async_trait` dependency.
This commit is contained in:
parent
eeafa086f3
commit
027114c48c
@ -1,9 +1,9 @@
|
|||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
|
|
||||||
use log::info;
|
use log::{info, warn};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
interpret::{InterpretError, Outcome},
|
interpret::{InterpretError, InterpretStatus, Outcome},
|
||||||
inventory::Inventory,
|
inventory::Inventory,
|
||||||
score::Score,
|
score::Score,
|
||||||
topology::Topology,
|
topology::Topology,
|
||||||
@ -15,6 +15,7 @@ pub struct Maestro<T: Topology> {
|
|||||||
inventory: Inventory,
|
inventory: Inventory,
|
||||||
topology: T,
|
topology: T,
|
||||||
scores: Arc<RwLock<ScoreVec<T>>>,
|
scores: Arc<RwLock<ScoreVec<T>>>,
|
||||||
|
topology_preparation_result: Mutex<Option<Outcome>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Topology> Maestro<T> {
|
impl<T: Topology> Maestro<T> {
|
||||||
@ -23,9 +24,28 @@ impl<T: Topology> Maestro<T> {
|
|||||||
inventory,
|
inventory,
|
||||||
topology,
|
topology,
|
||||||
scores: Arc::new(RwLock::new(Vec::new())),
|
scores: Arc::new(RwLock::new(Vec::new())),
|
||||||
|
topology_preparation_result: None.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ensures the associated Topology is ready for operations.
|
||||||
|
/// Delegates the readiness check and potential setup actions to the Topology.
|
||||||
|
pub async fn prepare_topology(&self) -> Result<Outcome, InterpretError> {
|
||||||
|
info!("Ensuring topology '{}' is ready...", self.topology.name());
|
||||||
|
let outcome = self.topology.ensure_ready().await?;
|
||||||
|
info!(
|
||||||
|
"Topology '{}' readiness check complete: {}",
|
||||||
|
self.topology.name(),
|
||||||
|
outcome.status
|
||||||
|
);
|
||||||
|
|
||||||
|
self.topology_preparation_result
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.replace(outcome.clone());
|
||||||
|
Ok(outcome)
|
||||||
|
}
|
||||||
|
|
||||||
// Load the inventory and inventory from environment.
|
// Load the inventory and inventory from environment.
|
||||||
// This function is able to discover the context that it is running in, such as k8s clusters, aws cloud, linux host, etc.
|
// This function is able to discover the context that it is running in, such as k8s clusters, aws cloud, linux host, etc.
|
||||||
// When the HARMONY_TOPOLOGY environment variable is not set, it will default to install k3s
|
// When the HARMONY_TOPOLOGY environment variable is not set, it will default to install k3s
|
||||||
@ -47,16 +67,31 @@ impl<T: Topology> Maestro<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(&mut self) {
|
|
||||||
info!("Starting Maestro");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn register_all(&mut self, mut scores: ScoreVec<T>) {
|
pub fn register_all(&mut self, mut scores: ScoreVec<T>) {
|
||||||
let mut score_mut = self.scores.write().expect("Should acquire lock");
|
let mut score_mut = self.scores.write().expect("Should acquire lock");
|
||||||
score_mut.append(&mut scores);
|
score_mut.append(&mut scores);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_topology_initialized(&self) -> bool {
|
||||||
|
let result = self.topology_preparation_result.lock().unwrap();
|
||||||
|
if let Some(outcome) = result.as_ref() {
|
||||||
|
match outcome.status {
|
||||||
|
InterpretStatus::SUCCESS => return true,
|
||||||
|
_ => return false,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn interpret(&self, score: Box<dyn Score<T>>) -> Result<Outcome, InterpretError> {
|
pub async fn interpret(&self, score: Box<dyn Score<T>>) -> Result<Outcome, InterpretError> {
|
||||||
|
if !self.is_topology_initialized() {
|
||||||
|
warn!(
|
||||||
|
"Launching interpret for score {} but Topology {} is not fully initialized!",
|
||||||
|
score.name(),
|
||||||
|
self.topology.name(),
|
||||||
|
);
|
||||||
|
}
|
||||||
info!("Running score {score:?}");
|
info!("Running score {score:?}");
|
||||||
let interpret = score.create_interpret();
|
let interpret = score.create_interpret();
|
||||||
info!("Launching interpret {interpret:?}");
|
info!("Launching interpret {interpret:?}");
|
||||||
|
|||||||
@ -5,6 +5,7 @@ mod load_balancer;
|
|||||||
pub mod openshift;
|
pub mod openshift;
|
||||||
mod router;
|
mod router;
|
||||||
mod tftp;
|
mod tftp;
|
||||||
|
use async_trait::async_trait;
|
||||||
pub use ha_cluster::*;
|
pub use ha_cluster::*;
|
||||||
pub use load_balancer::*;
|
pub use load_balancer::*;
|
||||||
pub use router::*;
|
pub use router::*;
|
||||||
@ -17,8 +18,38 @@ pub use tftp::*;
|
|||||||
|
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
|
||||||
pub trait Topology {
|
use super::interpret::{InterpretError, Outcome};
|
||||||
|
|
||||||
|
/// Represents a logical view of an infrastructure environment providing specific capabilities.
|
||||||
|
///
|
||||||
|
/// A Topology acts as a self-contained "package" responsible for managing access
|
||||||
|
/// to its underlying resources and ensuring they are in a ready state before use.
|
||||||
|
/// It defines the contract for the capabilities it provides through implemented
|
||||||
|
/// capability traits (e.g., `HasK8sCapability`, `HasDnsServer`).
|
||||||
|
#[async_trait]
|
||||||
|
pub trait Topology: Send + Sync {
|
||||||
|
/// Returns a unique identifier or name for this specific topology instance.
|
||||||
|
/// This helps differentiate between multiple instances of potentially the same type.
|
||||||
fn name(&self) -> &str;
|
fn name(&self) -> &str;
|
||||||
|
|
||||||
|
/// Ensures that the topology and its required underlying components or services
|
||||||
|
/// are ready to provide their declared capabilities.
|
||||||
|
///
|
||||||
|
/// Implementations of this method MUST be idempotent. Subsequent calls after a
|
||||||
|
/// successful readiness check should ideally be cheap NO-OPs.
|
||||||
|
///
|
||||||
|
/// This method encapsulates the logic for:
|
||||||
|
/// 1. **Checking Current State:** Assessing if the required resources/services are already running and configured.
|
||||||
|
/// 2. **Discovery:** Identifying the runtime environment (e.g., local Docker, AWS, existing cluster).
|
||||||
|
/// 3. **Initialization/Bootstrapping:** Performing necessary setup actions if not already ready. This might involve:
|
||||||
|
/// * Making API calls.
|
||||||
|
/// * Running external commands (e.g., `k3d`, `docker`).
|
||||||
|
/// * **Internal Orchestration:** For complex topologies, this method might manage dependencies on other sub-topologies, ensuring *their* `ensure_ready` is called first. Using nested `Maestros` to run setup `Scores` against these sub-topologies is the recommended pattern for non-trivial bootstrapping, allowing reuse of Harmony's core orchestration logic.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// - `Ok(Outcome)`: Indicates the topology is now ready. The `Outcome` status might be `SUCCESS` if actions were taken, or `NOOP` if it was already ready. The message should provide context.
|
||||||
|
/// - `Err(TopologyError)`: Indicates the topology could not reach a ready state due to configuration issues, discovery failures, bootstrap errors, or unsupported environments.
|
||||||
|
async fn ensure_ready(&self) -> Result<Outcome, InterpretError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type IpAddress = IpAddr;
|
pub type IpAddress = IpAddr;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user