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