1#![allow(dead_code)]
2use std::collections::{HashMap, HashSet};
9use std::fmt;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub enum LocationTier {
14 Host,
16 Rack,
18 DataCenter,
20 Region,
22 Remote,
24}
25
26impl LocationTier {
27 #[must_use]
29 pub fn cost_weight(self) -> u32 {
30 match self {
31 Self::Host => 0,
32 Self::Rack => 1,
33 Self::DataCenter => 5,
34 Self::Region => 20,
35 Self::Remote => 100,
36 }
37 }
38}
39
40impl fmt::Display for LocationTier {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 match self {
43 Self::Host => write!(f, "Host"),
44 Self::Rack => write!(f, "Rack"),
45 Self::DataCenter => write!(f, "DataCenter"),
46 Self::Region => write!(f, "Region"),
47 Self::Remote => write!(f, "Remote"),
48 }
49 }
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, Hash)]
54pub struct NodeLocation {
55 pub region: String,
57 pub data_center: String,
59 pub rack: String,
61 pub host: String,
63}
64
65impl NodeLocation {
66 pub fn new(
68 region: impl Into<String>,
69 data_center: impl Into<String>,
70 rack: impl Into<String>,
71 host: impl Into<String>,
72 ) -> Self {
73 Self {
74 region: region.into(),
75 data_center: data_center.into(),
76 rack: rack.into(),
77 host: host.into(),
78 }
79 }
80
81 #[must_use]
83 pub fn tier_to(&self, other: &Self) -> LocationTier {
84 if self.host == other.host
85 && self.rack == other.rack
86 && self.data_center == other.data_center
87 && self.region == other.region
88 {
89 LocationTier::Host
90 } else if self.rack == other.rack
91 && self.data_center == other.data_center
92 && self.region == other.region
93 {
94 LocationTier::Rack
95 } else if self.data_center == other.data_center {
96 LocationTier::DataCenter
97 } else if self.region == other.region {
98 LocationTier::Region
99 } else {
100 LocationTier::Remote
101 }
102 }
103
104 #[must_use]
106 pub fn cost_to(&self, other: &Self) -> u32 {
107 self.tier_to(other).cost_weight()
108 }
109}
110
111#[derive(Debug, Clone)]
113pub struct TopologyNode {
114 pub node_id: String,
116 pub location: NodeLocation,
118 pub available: bool,
120 pub local_data: HashSet<String>,
122}
123
124impl TopologyNode {
125 pub fn new(node_id: impl Into<String>, location: NodeLocation) -> Self {
127 Self {
128 node_id: node_id.into(),
129 location,
130 available: true,
131 local_data: HashSet::new(),
132 }
133 }
134
135 pub fn add_local_data(&mut self, data_id: impl Into<String>) {
137 self.local_data.insert(data_id.into());
138 }
139
140 pub fn remove_local_data(&mut self, data_id: &str) {
142 self.local_data.remove(data_id);
143 }
144
145 #[must_use]
147 pub fn has_data(&self, data_id: &str) -> bool {
148 self.local_data.contains(data_id)
149 }
150}
151
152#[derive(Debug, Clone)]
154pub struct TopologyManager {
155 nodes: HashMap<String, TopologyNode>,
157}
158
159impl TopologyManager {
160 #[must_use]
162 pub fn new() -> Self {
163 Self {
164 nodes: HashMap::new(),
165 }
166 }
167
168 pub fn add_node(&mut self, node: TopologyNode) {
170 self.nodes.insert(node.node_id.clone(), node);
171 }
172
173 pub fn remove_node(&mut self, node_id: &str) -> Option<TopologyNode> {
175 self.nodes.remove(node_id)
176 }
177
178 #[must_use]
180 pub fn get_node(&self, node_id: &str) -> Option<&TopologyNode> {
181 self.nodes.get(node_id)
182 }
183
184 pub fn get_node_mut(&mut self, node_id: &str) -> Option<&mut TopologyNode> {
186 self.nodes.get_mut(node_id)
187 }
188
189 #[must_use]
191 pub fn node_count(&self) -> usize {
192 self.nodes.len()
193 }
194
195 #[must_use]
197 pub fn node_ids(&self) -> Vec<&str> {
198 self.nodes.keys().map(std::string::String::as_str).collect()
199 }
200
201 #[must_use]
203 pub fn nodes_with_data(&self, data_id: &str) -> Vec<&TopologyNode> {
204 let mut nodes: Vec<&TopologyNode> = self
205 .nodes
206 .values()
207 .filter(|n| n.has_data(data_id) && n.available)
208 .collect();
209 nodes.sort_by(|a, b| a.node_id.cmp(&b.node_id));
211 nodes
212 }
213
214 #[must_use]
219 pub fn rank_by_locality(
220 &self,
221 data_id: &str,
222 reference_location: &NodeLocation,
223 ) -> Vec<(String, u32)> {
224 let mut candidates: Vec<(String, u32)> = self
225 .nodes
226 .values()
227 .filter(|n| n.available)
228 .map(|n| {
229 let mut cost = reference_location.cost_to(&n.location);
230 if n.has_data(data_id) {
232 cost = cost.saturating_sub(1);
233 }
234 (n.node_id.clone(), cost)
235 })
236 .collect();
237 candidates.sort_by_key(|&(_, cost)| cost);
238 candidates
239 }
240
241 #[must_use]
243 pub fn nodes_in_region(&self, region: &str) -> Vec<&TopologyNode> {
244 self.nodes
245 .values()
246 .filter(|n| n.location.region == region && n.available)
247 .collect()
248 }
249
250 #[must_use]
252 pub fn nodes_in_data_center(&self, dc: &str) -> Vec<&TopologyNode> {
253 self.nodes
254 .values()
255 .filter(|n| n.location.data_center == dc && n.available)
256 .collect()
257 }
258
259 #[must_use]
261 pub fn nodes_in_rack(&self, rack: &str) -> Vec<&TopologyNode> {
262 self.nodes
263 .values()
264 .filter(|n| n.location.rack == rack && n.available)
265 .collect()
266 }
267
268 pub fn set_available(&mut self, node_id: &str, available: bool) {
270 if let Some(node) = self.nodes.get_mut(node_id) {
271 node.available = available;
272 }
273 }
274
275 #[must_use]
277 pub fn regions(&self) -> Vec<String> {
278 let mut set: HashSet<String> = HashSet::new();
279 for n in self.nodes.values() {
280 set.insert(n.location.region.clone());
281 }
282 let mut regions: Vec<String> = set.into_iter().collect();
283 regions.sort();
284 regions
285 }
286}
287
288impl Default for TopologyManager {
289 fn default() -> Self {
290 Self::new()
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297
298 fn loc(region: &str, dc: &str, rack: &str, host: &str) -> NodeLocation {
299 NodeLocation::new(region, dc, rack, host)
300 }
301
302 #[test]
303 fn test_tier_same_host() {
304 let a = loc("us", "dc1", "r1", "h1");
305 let b = loc("us", "dc1", "r1", "h1");
306 assert_eq!(a.tier_to(&b), LocationTier::Host);
307 }
308
309 #[test]
310 fn test_tier_same_rack() {
311 let a = loc("us", "dc1", "r1", "h1");
312 let b = loc("us", "dc1", "r1", "h2");
313 assert_eq!(a.tier_to(&b), LocationTier::Rack);
314 }
315
316 #[test]
317 fn test_tier_same_dc() {
318 let a = loc("us", "dc1", "r1", "h1");
319 let b = loc("us", "dc1", "r2", "h3");
320 assert_eq!(a.tier_to(&b), LocationTier::DataCenter);
321 }
322
323 #[test]
324 fn test_tier_same_region() {
325 let a = loc("us", "dc1", "r1", "h1");
326 let b = loc("us", "dc2", "r5", "h9");
327 assert_eq!(a.tier_to(&b), LocationTier::Region);
328 }
329
330 #[test]
331 fn test_tier_remote() {
332 let a = loc("us", "dc1", "r1", "h1");
333 let b = loc("eu", "dc3", "r1", "h1");
334 assert_eq!(a.tier_to(&b), LocationTier::Remote);
335 }
336
337 #[test]
338 fn test_cost_ordering() {
339 assert!(LocationTier::Host.cost_weight() < LocationTier::Rack.cost_weight());
340 assert!(LocationTier::Rack.cost_weight() < LocationTier::DataCenter.cost_weight());
341 assert!(LocationTier::DataCenter.cost_weight() < LocationTier::Region.cost_weight());
342 assert!(LocationTier::Region.cost_weight() < LocationTier::Remote.cost_weight());
343 }
344
345 #[test]
346 fn test_topology_manager_add_remove() {
347 let mut mgr = TopologyManager::new();
348 let node = TopologyNode::new("n1", loc("us", "dc1", "r1", "h1"));
349 mgr.add_node(node);
350 assert_eq!(mgr.node_count(), 1);
351 mgr.remove_node("n1");
352 assert_eq!(mgr.node_count(), 0);
353 }
354
355 #[test]
356 fn test_nodes_with_data() {
357 let mut mgr = TopologyManager::new();
358 let mut n1 = TopologyNode::new("n1", loc("us", "dc1", "r1", "h1"));
359 n1.add_local_data("block-42");
360 let n2 = TopologyNode::new("n2", loc("us", "dc1", "r1", "h2"));
361 mgr.add_node(n1);
362 mgr.add_node(n2);
363 let holders = mgr.nodes_with_data("block-42");
364 assert_eq!(holders.len(), 1);
365 assert_eq!(holders[0].node_id, "n1");
366 }
367
368 #[test]
369 fn test_rank_by_locality() {
370 let mut mgr = TopologyManager::new();
371 let mut n_local = TopologyNode::new("local", loc("us", "dc1", "r1", "h1"));
372 n_local.add_local_data("data-1");
373 let n_remote = TopologyNode::new("remote", loc("eu", "dc3", "r1", "h9"));
374 mgr.add_node(n_local);
375 mgr.add_node(n_remote);
376 let ref_loc = loc("us", "dc1", "r1", "h1");
377 let ranked = mgr.rank_by_locality("data-1", &ref_loc);
378 assert_eq!(ranked[0].0, "local");
379 assert!(ranked[0].1 < ranked[1].1);
380 }
381
382 #[test]
383 fn test_set_available() {
384 let mut mgr = TopologyManager::new();
385 mgr.add_node(TopologyNode::new("n1", loc("us", "dc1", "r1", "h1")));
386 mgr.set_available("n1", false);
387 assert!(!mgr.get_node("n1").expect("node should exist").available);
388 mgr.set_available("n1", true);
389 assert!(mgr.get_node("n1").expect("node should exist").available);
390 }
391
392 #[test]
393 fn test_nodes_in_region() {
394 let mut mgr = TopologyManager::new();
395 mgr.add_node(TopologyNode::new("n1", loc("us", "dc1", "r1", "h1")));
396 mgr.add_node(TopologyNode::new("n2", loc("eu", "dc2", "r1", "h1")));
397 assert_eq!(mgr.nodes_in_region("us").len(), 1);
398 assert_eq!(mgr.nodes_in_region("eu").len(), 1);
399 }
400
401 #[test]
402 fn test_nodes_in_data_center() {
403 let mut mgr = TopologyManager::new();
404 mgr.add_node(TopologyNode::new("n1", loc("us", "dc1", "r1", "h1")));
405 mgr.add_node(TopologyNode::new("n2", loc("us", "dc1", "r2", "h2")));
406 mgr.add_node(TopologyNode::new("n3", loc("us", "dc2", "r1", "h3")));
407 assert_eq!(mgr.nodes_in_data_center("dc1").len(), 2);
408 }
409
410 #[test]
411 fn test_regions_list() {
412 let mut mgr = TopologyManager::new();
413 mgr.add_node(TopologyNode::new("n1", loc("us", "dc1", "r1", "h1")));
414 mgr.add_node(TopologyNode::new("n2", loc("eu", "dc2", "r1", "h1")));
415 mgr.add_node(TopologyNode::new("n3", loc("us", "dc3", "r1", "h2")));
416 let regions = mgr.regions();
417 assert_eq!(regions, vec!["eu", "us"]);
418 }
419
420 #[test]
421 fn test_location_tier_display() {
422 assert_eq!(LocationTier::Host.to_string(), "Host");
423 assert_eq!(LocationTier::Remote.to_string(), "Remote");
424 }
425
426 #[test]
427 fn test_node_local_data_operations() {
428 let mut node = TopologyNode::new("n1", loc("us", "dc1", "r1", "h1"));
429 node.add_local_data("d1");
430 node.add_local_data("d2");
431 assert!(node.has_data("d1"));
432 assert!(node.has_data("d2"));
433 node.remove_local_data("d1");
434 assert!(!node.has_data("d1"));
435 assert!(node.has_data("d2"));
436 }
437}