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: a `NodeManager` lives on each deployment node and is
7//! invoked by the `DomainApplicationManager` (per plan) to bring up
8//! local instances.
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    /// List of local instances.
22    pub instances: Vec<InstanceDeploymentDescription>,
23    /// Active (post-launch) instance names.
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    /// Constructor.
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` — brings the 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    /// Active NodeApplications.
63    apps: BTreeMap<String, NodeApplication>,
64}
65
66impl NodeManager {
67    /// Constructor.
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` — creates a `NodeApplicationManager`
77    /// for a list of instances (those assigned to this node).
78    #[must_use]
79    pub fn prepare_plan(
80        &self,
81        instances: Vec<InstanceDeploymentDescription>,
82    ) -> NodeApplicationManager {
83        NodeApplicationManager::new(self.name.clone(), instances)
84    }
85
86    /// Registers a `NodeApplication` (the caller maintains this after
87    /// `start_launch` has been called).
88    pub fn register_application(&mut self, plan_label: String, app: NodeApplication) {
89        self.apps.insert(plan_label, app);
90    }
91
92    /// Returns the active application plan labels on this node.
93    #[must_use]
94    pub fn active_plans(&self) -> Vec<String> {
95        self.apps.keys().cloned().collect()
96    }
97
98    /// Pops a NodeApplication at tear-down.
99    pub fn unregister_application(&mut self, plan_label: &str) -> Option<NodeApplication> {
100        self.apps.remove(plan_label)
101    }
102}
103
104#[cfg(test)]
105#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
106mod tests {
107    use super::*;
108    use alloc::string::ToString;
109
110    fn make_inst(name: &str) -> InstanceDeploymentDescription {
111        InstanceDeploymentDescription {
112            name: name.into(),
113            implementation: "I".into(),
114            node: "N1".into(),
115            ..InstanceDeploymentDescription::default()
116        }
117    }
118
119    #[test]
120    fn prepare_plan_then_start_launch() {
121        let nm = NodeManager::new("N1".into());
122        let app_mgr = nm.prepare_plan(alloc::vec![make_inst("a"), make_inst("b")]);
123        let app = app_mgr.start_launch();
124        assert_eq!(app.node, "N1");
125        assert_eq!(app.active.len(), 2);
126        assert!(app.active.iter().any(|s| s == "a"));
127        assert!(app.active.iter().any(|s| s == "b"));
128    }
129
130    #[test]
131    fn destroy_application_clears_active() {
132        let nm = NodeManager::new("N1".into());
133        let mut app = nm.prepare_plan(alloc::vec![make_inst("a")]).start_launch();
134        NodeApplicationManager::destroy_application(&mut app);
135        assert!(app.active.is_empty());
136        assert_eq!(app.instances.len(), 1, "instance metadata is preserved");
137    }
138
139    #[test]
140    fn register_and_unregister_application() {
141        let mut nm = NodeManager::new("N1".into());
142        let app = nm.prepare_plan(alloc::vec![make_inst("a")]).start_launch();
143        nm.register_application("Plan1".into(), app);
144        assert_eq!(nm.active_plans(), alloc::vec!["Plan1".to_string()]);
145        let popped = nm.unregister_application("Plan1");
146        assert!(popped.is_some());
147        assert!(nm.active_plans().is_empty());
148    }
149
150    #[test]
151    fn unregister_unknown_returns_none() {
152        let mut nm = NodeManager::new("N".into());
153        assert!(nm.unregister_application("nope").is_none());
154    }
155}