Skip to main content

noxu_rep/
rep_node.rs

1//! Replication node information.
2//!
3
4use std::time::Duration;
5
6use crate::node_type::NodeType;
7
8/// Information about a node in a replication group.
9///
10/// Each node has a unique name within its group, a type that determines
11/// its role, and a network address (host and port) for replication
12/// communication.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct RepNode {
15    /// The unique name of this node within the replication group.
16    pub name: String,
17    /// The type of this node (electable, monitor, secondary, arbiter).
18    pub node_type: NodeType,
19    /// The hostname or IP address for replication communication.
20    pub host: String,
21    /// The port number for replication communication.
22    pub port: u16,
23    /// The unique numeric identifier assigned to this node by the group.
24    pub node_id: u32,
25    /// Relative read throughput capacity × 100 (default 100 = 1.0).
26    ///
27    /// Used by quoracle strategy when computing load-optimal quorums.
28    pub read_capacity_pct: u32,
29    /// Relative write throughput capacity × 100 (default 100 = 1.0).
30    pub write_capacity_pct: u32,
31    /// Expected one-way latency hint in milliseconds (default 1).
32    pub latency_hint_ms: u64,
33}
34
35impl RepNode {
36    /// Creates a new `RepNode` with the given parameters.
37    ///
38    /// Capacity fields default to 1.0 (100 pct) and latency to 1 ms.
39    pub fn new(
40        name: String,
41        node_type: NodeType,
42        host: String,
43        port: u16,
44        node_id: u32,
45    ) -> Self {
46        Self {
47            name,
48            node_type,
49            host,
50            port,
51            node_id,
52            read_capacity_pct: 100,
53            write_capacity_pct: 100,
54            latency_hint_ms: 1,
55        }
56    }
57
58    /// Set relative read capacity (e.g. `0.5` for half-speed node).
59    ///
60    /// The value is stored as `(cap * 100) as u32`.
61    pub fn with_read_capacity(mut self, cap: f64) -> Self {
62        self.read_capacity_pct = (cap * 100.0).round() as u32;
63        self
64    }
65
66    /// Set relative write capacity.
67    pub fn with_write_capacity(mut self, cap: f64) -> Self {
68        self.write_capacity_pct = (cap * 100.0).round() as u32;
69        self
70    }
71
72    /// Set expected one-way latency hint.
73    pub fn with_latency_hint(mut self, d: Duration) -> Self {
74        self.latency_hint_ms = d.as_millis() as u64;
75        self
76    }
77
78    /// Build a `quoracle::Node<String>` from this `RepNode`, embedding
79    /// the capacity and latency hints so that quoracle's LP strategy
80    /// can factor them into load-optimal quorum selection.
81    pub fn to_quoracle_node(&self) -> quoracle::Node<String> {
82        let read_cap = self.read_capacity_pct as f64 / 100.0;
83        let write_cap = self.write_capacity_pct as f64 / 100.0;
84        let latency = Duration::from_millis(self.latency_hint_ms);
85        quoracle::Node::new(self.name.clone())
86            .with_read_write_capacity(read_cap, write_cap)
87            .with_latency(latency)
88    }
89
90    /// Returns the name of this node.
91    pub fn name(&self) -> &str {
92        &self.name
93    }
94
95    /// Returns the type of this node.
96    pub fn node_type(&self) -> NodeType {
97        self.node_type
98    }
99
100    /// Returns the hostname of this node.
101    pub fn host(&self) -> &str {
102        &self.host
103    }
104
105    /// Returns the port number of this node.
106    pub fn port(&self) -> u16 {
107        self.port
108    }
109
110    /// Returns the numeric node identifier.
111    pub fn node_id(&self) -> u32 {
112        self.node_id
113    }
114
115    /// Returns the socket address string in "host:port" format.
116    pub fn socket_address(&self) -> String {
117        format!("{}:{}", self.host, self.port)
118    }
119
120    /// Returns `true` if this node can participate in elections.
121    pub fn is_electable(&self) -> bool {
122        self.node_type.is_electable()
123    }
124
125    /// Returns `true` if this node stores data.
126    pub fn is_data_node(&self) -> bool {
127        self.node_type.is_data_node()
128    }
129
130    /// Returns `true` if this node can become master.
131    pub fn can_be_master(&self) -> bool {
132        self.node_type.can_be_master()
133    }
134}
135
136impl std::fmt::Display for RepNode {
137    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138        write!(
139            f,
140            "RepNode(name={}, type={}, addr={}, id={})",
141            self.name,
142            self.node_type,
143            self.socket_address(),
144            self.node_id
145        )
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152
153    fn make_node() -> RepNode {
154        RepNode::new(
155            "node1".to_string(),
156            NodeType::Electable,
157            "localhost".to_string(),
158            5001,
159            1,
160        )
161    }
162
163    #[test]
164    fn test_new_and_getters() {
165        let node = make_node();
166        assert_eq!(node.name(), "node1");
167        assert_eq!(node.node_type(), NodeType::Electable);
168        assert_eq!(node.host(), "localhost");
169        assert_eq!(node.port(), 5001);
170        assert_eq!(node.node_id(), 1);
171    }
172
173    #[test]
174    fn test_socket_address() {
175        let node = make_node();
176        assert_eq!(node.socket_address(), "localhost:5001");
177    }
178
179    #[test]
180    fn test_socket_address_with_ip() {
181        let node = RepNode::new(
182            "node2".to_string(),
183            NodeType::Secondary,
184            "192.168.1.100".to_string(),
185            6000,
186            2,
187        );
188        assert_eq!(node.socket_address(), "192.168.1.100:6000");
189    }
190
191    #[test]
192    fn test_delegation_methods() {
193        let electable = make_node();
194        assert!(electable.is_electable());
195        assert!(electable.is_data_node());
196        assert!(electable.can_be_master());
197
198        let monitor = RepNode::new(
199            "mon".to_string(),
200            NodeType::Monitor,
201            "localhost".to_string(),
202            5002,
203            2,
204        );
205        assert!(!monitor.is_electable());
206        assert!(!monitor.is_data_node());
207        assert!(!monitor.can_be_master());
208    }
209
210    #[test]
211    fn test_display() {
212        let node = make_node();
213        let s = node.to_string();
214        assert!(s.contains("node1"));
215        assert!(s.contains("ELECTABLE"));
216        assert!(s.contains("localhost:5001"));
217        assert!(s.contains("id=1"));
218    }
219
220    #[test]
221    fn test_clone_and_eq() {
222        let node = make_node();
223        let cloned = node.clone();
224        assert_eq!(node, cloned);
225    }
226
227    #[test]
228    fn test_not_equal() {
229        let node1 = make_node();
230        let node2 = RepNode::new(
231            "node2".to_string(),
232            NodeType::Electable,
233            "localhost".to_string(),
234            5001,
235            2,
236        );
237        assert_ne!(node1, node2);
238    }
239
240    #[test]
241    fn test_debug() {
242        let node = make_node();
243        let s = format!("{:?}", node);
244        assert!(s.contains("RepNode"));
245        assert!(s.contains("node1"));
246    }
247}