use anyhow::Result;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use tracing::{debug, error, info, warn};
pub struct LifecycleManager {
current_mode: String,
pub(crate) running_nodes: HashSet<String>,
mode_config: HashMap<String, ModeConfig>,
supervisor: Arc<dyn SupervisorTrait>,
}
#[derive(Debug, Clone)]
pub struct ModeConfig {
pub nodes: Vec<String>,
pub description: Option<String>,
}
#[async_trait::async_trait]
pub trait SupervisorTrait: Send + Sync {
async fn start_node(&self, name: &str) -> Result<()>;
async fn stop_node(&self, name: &str) -> Result<()>;
fn get_running_nodes(&self) -> Vec<String>;
}
impl LifecycleManager {
pub fn new(
supervisor: Arc<dyn SupervisorTrait>,
mode_config: HashMap<String, ModeConfig>,
default_mode: String,
) -> Self {
Self {
current_mode: default_mode,
running_nodes: HashSet::new(),
mode_config,
supervisor,
}
}
pub fn current_mode(&self) -> &str {
&self.current_mode
}
pub fn running_nodes(&self) -> &HashSet<String> {
&self.running_nodes
}
pub async fn change_mode(&mut self, target_mode: &str) -> Result<ModeTransitionResult> {
info!("Mode change requested: {} -> {}", self.current_mode, target_mode);
if !self.mode_config.contains_key(target_mode) {
let available: Vec<_> = self.mode_config.keys().map(|k| k.as_str()).collect();
return Err(anyhow::anyhow!(
"Mode '{}' not found. Available modes: {}",
target_mode,
available.join(", ")
));
}
let diff = self.calculate_mode_diff(target_mode)?;
info!("Mode transition plan: stop {:?}, start {:?}", diff.stop, diff.start);
self.execute_mode_transition(target_mode, diff).await
}
pub fn calculate_mode_diff(&self, target_mode: &str) -> Result<ModeDiff> {
let target_config = self
.mode_config
.get(target_mode)
.ok_or_else(|| anyhow::anyhow!("Mode not found"))?;
let (start, stop) = diff_nodes(&target_config.nodes, &self.running_nodes);
Ok(ModeDiff { start, stop })
}
async fn execute_mode_transition(&mut self, target_mode: &str, diff: ModeDiff) -> Result<ModeTransitionResult> {
debug!(
"Executing mode transition: {} -> {} (stop: {:?}, start: {:?})",
self.current_mode, target_mode, diff.stop, diff.start
);
let mut stopped = Vec::new();
let mut started = Vec::new();
let mut errors = Vec::new();
for node in &diff.stop {
info!("Stopping node: {}", node);
match self.supervisor.stop_node(node).await {
Ok(()) => {
self.running_nodes.remove(node);
stopped.push(node.clone());
debug!("✓ Stopped: {}", node);
}
Err(e) => {
warn!("Failed to stop node {}: {}", node, e);
errors.push(format!("stop {}: {}", node, e));
}
}
}
for node in &diff.start {
info!("Starting node: {}", node);
match self.supervisor.start_node(node).await {
Ok(()) => {
self.running_nodes.insert(node.clone());
started.push(node.clone());
debug!("✓ Started: {}", node);
}
Err(e) => {
error!("Failed to start node {}: {}", node, e);
errors.push(format!("start {}: {}", node, e));
}
}
}
let previous_mode = self.current_mode.clone();
self.current_mode = target_mode.to_string();
info!(
"Mode transition complete: {} -> {} (started: {}, stopped: {})",
previous_mode,
target_mode,
started.len(),
stopped.len()
);
Ok(ModeTransitionResult {
previous_mode,
current_mode: target_mode.to_string(),
nodes_started: started,
nodes_stopped: stopped,
errors,
})
}
}
pub fn diff_nodes(target_nodes: &[String], running_nodes: &HashSet<String>) -> (Vec<String>, Vec<String>) {
let target_set: HashSet<_> = target_nodes.iter().map(|s| s.as_str()).collect();
let start: Vec<_> = target_set
.iter()
.filter(|n| !running_nodes.contains(**n))
.map(|s| s.to_string())
.collect();
let stop: Vec<_> = running_nodes
.iter()
.filter(|n| !target_set.contains(n.as_str()))
.cloned()
.collect();
(start, stop)
}
#[derive(Debug)]
pub struct ModeDiff {
pub start: Vec<String>,
pub stop: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct ModeTransitionResult {
pub previous_mode: String,
pub current_mode: String,
pub nodes_started: Vec<String>,
pub nodes_stopped: Vec<String>,
pub errors: Vec<String>,
}