Skip to main content

zerodds_corba_dnc/
node.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! NodeManager / NodeApplicationManager — D&C §9.2.
5//!
6//! Spec §9.2: ein `NodeManager` lebt auf jedem Deployment-Node und
7//! wird vom `DomainApplicationManager` (per Plan) gerufen, um lokale
8//! Instances aufzubringen.
9
10use alloc::collections::BTreeMap;
11use alloc::string::String;
12use alloc::vec::Vec;
13
14use crate::plan::InstanceDeploymentDescription;
15
16/// `NodeApplication` — D&C §9.2.3.
17#[derive(Debug, Clone, PartialEq, Eq, Default)]
18pub struct NodeApplication {
19    /// Node-Name.
20    pub node: String,
21    /// Liste der lokalen Instances.
22    pub instances: Vec<InstanceDeploymentDescription>,
23    /// Aktive (post-launch) Instance-Namen.
24    pub active: Vec<String>,
25}
26
27/// `NodeApplicationManager` — D&C §9.2.2.
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct NodeApplicationManager {
30    node: String,
31    instances: Vec<InstanceDeploymentDescription>,
32}
33
34impl NodeApplicationManager {
35    /// Konstruktor.
36    #[must_use]
37    pub fn new(node: String, instances: Vec<InstanceDeploymentDescription>) -> Self {
38        Self { node, instances }
39    }
40
41    /// Spec §9.2.2 `startLaunch` — bringt die Instances online.
42    #[must_use]
43    pub fn start_launch(&self) -> NodeApplication {
44        NodeApplication {
45            node: self.node.clone(),
46            instances: self.instances.clone(),
47            active: self.instances.iter().map(|i| i.name.clone()).collect(),
48        }
49    }
50
51    /// Spec §9.2.2 `destroyApplication`.
52    pub fn destroy_application(app: &mut NodeApplication) {
53        app.active.clear();
54    }
55}
56
57/// `NodeManager` — D&C §9.2.1.
58#[derive(Debug, Default, Clone, PartialEq, Eq)]
59pub struct NodeManager {
60    /// Node-Name.
61    pub name: String,
62    /// Aktive NodeApplications.
63    apps: BTreeMap<String, NodeApplication>,
64}
65
66impl NodeManager {
67    /// Konstruktor.
68    #[must_use]
69    pub fn new(name: String) -> Self {
70        Self {
71            name,
72            apps: BTreeMap::new(),
73        }
74    }
75
76    /// Spec §9.2.1 `preparePlan` — fuer eine Liste von Instances
77    /// (die fuer diesen Node bestimmt sind) einen
78    /// `NodeApplicationManager` erzeugen.
79    #[must_use]
80    pub fn prepare_plan(
81        &self,
82        instances: Vec<InstanceDeploymentDescription>,
83    ) -> NodeApplicationManager {
84        NodeApplicationManager::new(self.name.clone(), instances)
85    }
86
87    /// Registriert eine `NodeApplication` (Caller pflegt das, nachdem
88    /// `start_launch` aufgerufen wurde).
89    pub fn register_application(&mut self, plan_label: String, app: NodeApplication) {
90        self.apps.insert(plan_label, app);
91    }
92
93    /// Liefert die aktiven Application-Plan-Labels auf diesem Node.
94    #[must_use]
95    pub fn active_plans(&self) -> Vec<String> {
96        self.apps.keys().cloned().collect()
97    }
98
99    /// Pop eine NodeApplication beim Tear-down.
100    pub fn unregister_application(&mut self, plan_label: &str) -> Option<NodeApplication> {
101        self.apps.remove(plan_label)
102    }
103}
104
105#[cfg(test)]
106#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
107mod tests {
108    use super::*;
109    use alloc::string::ToString;
110
111    fn make_inst(name: &str) -> InstanceDeploymentDescription {
112        InstanceDeploymentDescription {
113            name: name.into(),
114            implementation: "I".into(),
115            node: "N1".into(),
116            ..InstanceDeploymentDescription::default()
117        }
118    }
119
120    #[test]
121    fn prepare_plan_then_start_launch() {
122        let nm = NodeManager::new("N1".into());
123        let app_mgr = nm.prepare_plan(alloc::vec![make_inst("a"), make_inst("b")]);
124        let app = app_mgr.start_launch();
125        assert_eq!(app.node, "N1");
126        assert_eq!(app.active.len(), 2);
127        assert!(app.active.iter().any(|s| s == "a"));
128        assert!(app.active.iter().any(|s| s == "b"));
129    }
130
131    #[test]
132    fn destroy_application_clears_active() {
133        let nm = NodeManager::new("N1".into());
134        let mut app = nm.prepare_plan(alloc::vec![make_inst("a")]).start_launch();
135        NodeApplicationManager::destroy_application(&mut app);
136        assert!(app.active.is_empty());
137        assert_eq!(app.instances.len(), 1, "instance metadata bleibt erhalten");
138    }
139
140    #[test]
141    fn register_and_unregister_application() {
142        let mut nm = NodeManager::new("N1".into());
143        let app = nm.prepare_plan(alloc::vec![make_inst("a")]).start_launch();
144        nm.register_application("Plan1".into(), app);
145        assert_eq!(nm.active_plans(), alloc::vec!["Plan1".to_string()]);
146        let popped = nm.unregister_application("Plan1");
147        assert!(popped.is_some());
148        assert!(nm.active_plans().is_empty());
149    }
150
151    #[test]
152    fn unregister_unknown_returns_none() {
153        let mut nm = NodeManager::new("N".into());
154        assert!(nm.unregister_application("nope").is_none());
155    }
156}