mecha10_runtime/
lifecycle.rs1use anyhow::Result;
37use std::collections::{HashMap, HashSet};
38use std::sync::Arc;
39use tracing::{debug, error, info, warn};
40
41pub struct LifecycleManager {
42 current_mode: String,
43
44 pub(crate) running_nodes: HashSet<String>,
45
46 mode_config: HashMap<String, ModeConfig>,
47
48 supervisor: Arc<dyn SupervisorTrait>,
49}
50
51#[derive(Debug, Clone)]
52pub struct ModeConfig {
53 pub nodes: Vec<String>,
54
55 pub description: Option<String>,
56}
57
58#[async_trait::async_trait]
59pub trait SupervisorTrait: Send + Sync {
60 async fn start_node(&self, name: &str) -> Result<()>;
62
63 async fn stop_node(&self, name: &str) -> Result<()>;
65
66 fn get_running_nodes(&self) -> Vec<String>;
68}
69
70impl LifecycleManager {
71 pub fn new(
79 supervisor: Arc<dyn SupervisorTrait>,
80 mode_config: HashMap<String, ModeConfig>,
81 default_mode: String,
82 ) -> Self {
83 Self {
84 current_mode: default_mode,
85 running_nodes: HashSet::new(),
86 mode_config,
87 supervisor,
88 }
89 }
90
91 pub fn current_mode(&self) -> &str {
93 &self.current_mode
94 }
95
96 pub fn running_nodes(&self) -> &HashSet<String> {
98 &self.running_nodes
99 }
100
101 pub async fn change_mode(&mut self, target_mode: &str) -> Result<ModeTransitionResult> {
114 info!("Mode change requested: {} -> {}", self.current_mode, target_mode);
115
116 if !self.mode_config.contains_key(target_mode) {
117 let available: Vec<_> = self.mode_config.keys().map(|k| k.as_str()).collect();
118 return Err(anyhow::anyhow!(
119 "Mode '{}' not found. Available modes: {}",
120 target_mode,
121 available.join(", ")
122 ));
123 }
124
125 let diff = self.calculate_mode_diff(target_mode)?;
126
127 info!("Mode transition plan: stop {:?}, start {:?}", diff.stop, diff.start);
128
129 self.execute_mode_transition(target_mode, diff).await
130 }
131
132 pub fn calculate_mode_diff(&self, target_mode: &str) -> Result<ModeDiff> {
134 let target_config = self
135 .mode_config
136 .get(target_mode)
137 .ok_or_else(|| anyhow::anyhow!("Mode not found"))?;
138
139 let (start, stop) = diff_nodes(&target_config.nodes, &self.running_nodes);
140
141 Ok(ModeDiff { start, stop })
142 }
143
144 async fn execute_mode_transition(&mut self, target_mode: &str, diff: ModeDiff) -> Result<ModeTransitionResult> {
146 debug!(
147 "Executing mode transition: {} -> {} (stop: {:?}, start: {:?})",
148 self.current_mode, target_mode, diff.stop, diff.start
149 );
150
151 let mut stopped = Vec::new();
152 let mut started = Vec::new();
153 let mut errors = Vec::new();
154
155 for node in &diff.stop {
156 info!("Stopping node: {}", node);
157 match self.supervisor.stop_node(node).await {
158 Ok(()) => {
159 self.running_nodes.remove(node);
160 stopped.push(node.clone());
161 debug!("✓ Stopped: {}", node);
162 }
163 Err(e) => {
164 warn!("Failed to stop node {}: {}", node, e);
165 errors.push(format!("stop {}: {}", node, e));
166 }
167 }
168 }
169
170 for node in &diff.start {
171 info!("Starting node: {}", node);
172 match self.supervisor.start_node(node).await {
173 Ok(()) => {
174 self.running_nodes.insert(node.clone());
175 started.push(node.clone());
176 debug!("✓ Started: {}", node);
177 }
178 Err(e) => {
179 error!("Failed to start node {}: {}", node, e);
180 errors.push(format!("start {}: {}", node, e));
181 }
182 }
183 }
184
185 let previous_mode = self.current_mode.clone();
186 self.current_mode = target_mode.to_string();
187
188 info!(
189 "Mode transition complete: {} -> {} (started: {}, stopped: {})",
190 previous_mode,
191 target_mode,
192 started.len(),
193 stopped.len()
194 );
195
196 Ok(ModeTransitionResult {
197 previous_mode,
198 current_mode: target_mode.to_string(),
199 nodes_started: started,
200 nodes_stopped: stopped,
201 errors,
202 })
203 }
204}
205
206pub fn diff_nodes(target_nodes: &[String], running_nodes: &HashSet<String>) -> (Vec<String>, Vec<String>) {
222 let target_set: HashSet<_> = target_nodes.iter().map(|s| s.as_str()).collect();
223
224 let start: Vec<_> = target_set
225 .iter()
226 .filter(|n| !running_nodes.contains(**n))
227 .map(|s| s.to_string())
228 .collect();
229
230 let stop: Vec<_> = running_nodes
231 .iter()
232 .filter(|n| !target_set.contains(n.as_str()))
233 .cloned()
234 .collect();
235
236 (start, stop)
237}
238
239#[derive(Debug)]
240pub struct ModeDiff {
241 pub start: Vec<String>,
242
243 pub stop: Vec<String>,
244}
245
246#[derive(Debug, Clone)]
247pub struct ModeTransitionResult {
248 pub previous_mode: String,
249
250 pub current_mode: String,
251
252 pub nodes_started: Vec<String>,
253
254 pub nodes_stopped: Vec<String>,
255
256 pub errors: Vec<String>,
257}