zerodds_corba_dnc/
execution.rs1use alloc::collections::BTreeMap;
12use alloc::string::String;
13use alloc::vec::Vec;
14
15use crate::plan::{DeploymentPlan, PlanError};
16
17#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct DomainApplication {
20 pub plan_label: String,
22 pub plan_uuid: String,
24 pub running_instances: BTreeMap<String, String>,
26 pub state: AppState,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum AppState {
33 Prepared,
35 Launched,
37 Running,
39 Finished,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct DomainApplicationManager {
46 plan: DeploymentPlan,
47 state: AppState,
48}
49
50impl DomainApplicationManager {
51 pub fn prepare_plan(plan: DeploymentPlan) -> Result<Self, PlanError> {
57 plan.validate()?;
58 Ok(Self {
59 plan,
60 state: AppState::Prepared,
61 })
62 }
63
64 pub fn start_launch(&mut self) -> Result<DomainApplication, &'static str> {
70 if self.state != AppState::Prepared {
71 return Err("startLaunch requires Prepared state");
72 }
73 self.state = AppState::Launched;
74 let mut running = BTreeMap::new();
75 for inst in &self.plan.instances {
76 running.insert(inst.name.clone(), inst.node.clone());
77 }
78 Ok(DomainApplication {
79 plan_label: self.plan.label.clone(),
80 plan_uuid: self.plan.uuid.clone(),
81 running_instances: running,
82 state: AppState::Launched,
83 })
84 }
85
86 pub fn finish_launch(&mut self, app: &mut DomainApplication) -> Result<(), &'static str> {
92 if self.state != AppState::Launched {
93 return Err("finishLaunch requires Launched state");
94 }
95 self.state = AppState::Running;
96 app.state = AppState::Running;
97 Ok(())
98 }
99
100 pub fn destroy_application(&mut self, app: &mut DomainApplication) -> Result<(), &'static str> {
105 if self.state != AppState::Running {
106 return Err("destroyApplication requires Running state");
107 }
108 self.state = AppState::Finished;
109 app.state = AppState::Finished;
110 app.running_instances.clear();
111 Ok(())
112 }
113
114 #[must_use]
116 pub fn state(&self) -> AppState {
117 self.state
118 }
119
120 #[must_use]
122 pub fn plan(&self) -> &DeploymentPlan {
123 &self.plan
124 }
125}
126
127#[derive(Debug, Default, Clone, PartialEq, Eq)]
129pub struct ExecutionManager {
130 plans: Vec<String>,
131}
132
133impl ExecutionManager {
134 #[must_use]
136 pub fn new() -> Self {
137 Self::default()
138 }
139
140 pub fn prepare_plan(
146 &mut self,
147 plan: DeploymentPlan,
148 ) -> Result<DomainApplicationManager, PlanError> {
149 let label = plan.label.clone();
150 let mgr = DomainApplicationManager::prepare_plan(plan)?;
151 self.plans.push(label);
152 Ok(mgr)
153 }
154
155 #[must_use]
158 pub fn managed_plans(&self) -> &[String] {
159 &self.plans
160 }
161}
162
163#[cfg(test)]
164#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
165mod tests {
166 use super::*;
167 use crate::plan::{ImplementationDescription, InstanceDeploymentDescription};
168 use alloc::string::ToString;
169
170 fn sample_plan() -> DeploymentPlan {
171 DeploymentPlan {
172 label: "P".into(),
173 uuid: "u".into(),
174 realizes: "P".into(),
175 implementations: alloc::vec![ImplementationDescription {
176 label: "I".into(),
177 uuid: "iu".into(),
178 artifacts: alloc::vec!["a.so".into()],
179 realizes: "IDL:X:1.0".into(),
180 ..ImplementationDescription::default()
181 }],
182 instances: alloc::vec![InstanceDeploymentDescription {
183 name: "x".into(),
184 implementation: "I".into(),
185 node: "N".into(),
186 ..InstanceDeploymentDescription::default()
187 }],
188 connections: alloc::vec![],
189 }
190 }
191
192 #[test]
193 fn full_lifecycle_round_trip() {
194 let mut em = ExecutionManager::new();
195 let mut mgr = em.prepare_plan(sample_plan()).unwrap();
196 assert_eq!(mgr.state(), AppState::Prepared);
197 let mut app = mgr.start_launch().unwrap();
198 assert_eq!(app.state, AppState::Launched);
199 mgr.finish_launch(&mut app).unwrap();
200 assert_eq!(app.state, AppState::Running);
201 assert_eq!(
202 app.running_instances.get("x").map(String::as_str),
203 Some("N")
204 );
205 mgr.destroy_application(&mut app).unwrap();
206 assert_eq!(app.state, AppState::Finished);
207 assert!(app.running_instances.is_empty());
208 }
209
210 #[test]
211 fn invalid_plan_rejected_at_prepare() {
212 let mut em = ExecutionManager::new();
213 let mut bad = sample_plan();
214 bad.instances[0].implementation = "MissingImpl".into();
215 assert!(em.prepare_plan(bad).is_err());
216 }
217
218 #[test]
219 fn double_start_fails() {
220 let mut mgr = DomainApplicationManager::prepare_plan(sample_plan()).unwrap();
221 let _ = mgr.start_launch().unwrap();
222 let mut second = DomainApplication {
223 plan_label: "P".into(),
224 plan_uuid: "u".into(),
225 running_instances: BTreeMap::new(),
226 state: AppState::Prepared,
227 };
228 assert!(mgr.start_launch().is_err());
229 assert!(mgr.finish_launch(&mut second).is_err() || mgr.state() != AppState::Launched);
230 }
231
232 #[test]
233 fn destroy_before_running_fails() {
234 let mut mgr = DomainApplicationManager::prepare_plan(sample_plan()).unwrap();
235 let mut app = mgr.start_launch().unwrap();
236 assert!(mgr.destroy_application(&mut app).is_err());
237 }
238
239 #[test]
240 fn execution_manager_lists_prepared_plans() {
241 let mut em = ExecutionManager::new();
242 em.prepare_plan(sample_plan()).unwrap();
243 assert_eq!(em.managed_plans(), &["P".to_string()]);
244 }
245}