Skip to main content

blvm_sdk/composition/
lifecycle.rs

1//! Module Lifecycle Management
2//!
3//! Handles starting, stopping, restarting, and health checking of modules.
4
5use crate::composition::registry::ModuleRegistry;
6use crate::composition::types::*;
7use blvm_node::module::manager::ModuleManager;
8use blvm_node::module::traits::{
9    ModuleMetadata as RefModuleMetadata, ModuleState as RefModuleState,
10};
11use std::collections::HashMap;
12use std::sync::Arc;
13use tokio::sync::Mutex;
14
15fn module_state_to_status(s: RefModuleState) -> ModuleStatus {
16    match s {
17        RefModuleState::Running => ModuleStatus::Running,
18        RefModuleState::Stopped => ModuleStatus::Stopped,
19        RefModuleState::Initializing => ModuleStatus::Initializing,
20        RefModuleState::Stopping => ModuleStatus::Stopping,
21        RefModuleState::Error(msg) => ModuleStatus::Error(msg),
22    }
23}
24
25/// Module lifecycle manager
26pub struct ModuleLifecycle {
27    /// Module registry reference
28    pub(crate) registry: ModuleRegistry,
29    /// Reference to blvm-node ModuleManager (if available)
30    module_manager: Option<Arc<Mutex<ModuleManager>>>,
31    /// Module status cache
32    status_cache: HashMap<String, ModuleStatus>,
33}
34
35impl ModuleLifecycle {
36    /// Create a new module lifecycle manager
37    pub fn new(registry: ModuleRegistry) -> Self {
38        Self {
39            registry,
40            module_manager: None,
41            status_cache: HashMap::new(),
42        }
43    }
44
45    /// Set the ModuleManager for actual module operations
46    pub fn with_module_manager(mut self, manager: Arc<Mutex<ModuleManager>>) -> Self {
47        self.module_manager = Some(manager);
48        self
49    }
50
51    /// Start a module with optional config (from ModuleSpec.config)
52    pub async fn start_module(
53        &mut self,
54        name: &str,
55        config: Option<&HashMap<String, serde_json::Value>>,
56    ) -> Result<()> {
57        if self.module_manager.is_none() {
58            return Err(CompositionError::InstallationFailed(
59                "ModuleManager required to start modules; use ModuleLifecycle::with_module_manager"
60                    .to_string(),
61            ));
62        }
63
64        let info = self.registry.get_module(name, None)?;
65
66        let config_map: HashMap<String, String> = config
67            .map(|c| {
68                c.iter()
69                    .map(|(k, v)| {
70                        let s = match v {
71                            serde_json::Value::String(s) => s.clone(),
72                            _ => v.to_string(),
73                        };
74                        (k.clone(), s)
75                    })
76                    .collect()
77            })
78            .unwrap_or_default();
79
80        if let Some(ref manager) = self.module_manager {
81            let metadata: RefModuleMetadata = info.clone().into();
82
83            let binary_path = info.binary_path.as_ref().ok_or_else(|| {
84                CompositionError::ModuleNotFound(format!("Module {name} has no binary path"))
85            })?;
86
87            let mut mgr = manager.lock().await;
88            mgr.load_module(&info.name, binary_path, metadata, config_map)
89                .await
90                .map_err(CompositionError::from)?;
91
92            self.status_cache
93                .insert(name.to_string(), ModuleStatus::Running);
94        }
95
96        Ok(())
97    }
98
99    /// Stop a module
100    pub async fn stop_module(&mut self, name: &str) -> Result<()> {
101        let _info = self.registry.get_module(name, None)?;
102
103        if let Some(ref manager) = self.module_manager {
104            let mut mgr = manager.lock().await;
105            mgr.unload_module(name)
106                .await
107                .map_err(CompositionError::from)?;
108        }
109
110        self.status_cache
111            .insert(name.to_string(), ModuleStatus::Stopped);
112        Ok(())
113    }
114
115    /// Restart a module
116    pub async fn restart_module(
117        &mut self,
118        name: &str,
119        config: Option<&HashMap<String, serde_json::Value>>,
120    ) -> Result<()> {
121        self.stop_module(name).await?;
122        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
123        self.start_module(name, config).await
124    }
125
126    /// Get module status (queries ModuleManager when available, else cache)
127    pub async fn get_module_status(&self, name: &str) -> Result<ModuleStatus> {
128        let _ = self.registry.get_module(name, None)?;
129
130        if let Some(ref manager) = self.module_manager {
131            if let Some(state) = manager.lock().await.get_module_state(name).await {
132                return Ok(module_state_to_status(state));
133            }
134        }
135
136        Ok(self
137            .status_cache
138            .get(name)
139            .cloned()
140            .unwrap_or(ModuleStatus::NotInstalled))
141    }
142
143    /// Perform health check on module
144    pub async fn health_check(&self, name: &str) -> Result<ModuleHealth> {
145        let status = self.get_module_status(name).await?;
146        match status {
147            ModuleStatus::Running => Ok(ModuleHealth::Healthy),
148            ModuleStatus::Error(msg) => Ok(ModuleHealth::Unhealthy(msg)),
149            ModuleStatus::Stopped | ModuleStatus::NotInstalled => Ok(ModuleHealth::Unknown),
150            _ => Ok(ModuleHealth::Degraded),
151        }
152    }
153
154    /// Get the module registry
155    pub fn registry(&self) -> &ModuleRegistry {
156        &self.registry
157    }
158
159    /// Get mutable access to the module registry
160    pub fn registry_mut(&mut self) -> &mut ModuleRegistry {
161        &mut self.registry
162    }
163}