forked from NationTech/harmony
		
	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