1use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use tracing::{debug, instrument};
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct RoutingTable {
45 node_to_cell: HashMap<String, (String, u64)>,
47 cell_to_zone: HashMap<String, (String, u64)>,
49 cell_leaders: HashMap<String, (String, u64)>,
51}
52
53impl RoutingTable {
54 pub fn new() -> Self {
56 Self {
57 node_to_cell: HashMap::new(),
58 cell_to_zone: HashMap::new(),
59 cell_leaders: HashMap::new(),
60 }
61 }
62
63 #[instrument(skip(self))]
73 pub fn assign_node(&mut self, node_id: &str, cell_id: &str, timestamp: u64) -> bool {
74 if let Some((_, existing_ts)) = self.node_to_cell.get(node_id) {
75 if timestamp <= *existing_ts {
76 debug!(
77 "Rejected node assignment (old timestamp): {} → {} @ {}",
78 node_id, cell_id, timestamp
79 );
80 return false;
81 }
82 }
83
84 debug!(
85 "Assigning node to cell: {} → {} @ {}",
86 node_id, cell_id, timestamp
87 );
88 self.node_to_cell
89 .insert(node_id.to_string(), (cell_id.to_string(), timestamp));
90 true
91 }
92
93 #[instrument(skip(self))]
102 pub fn remove_node(&mut self, node_id: &str, timestamp: u64) -> bool {
103 if let Some((_, existing_ts)) = self.node_to_cell.get(node_id) {
104 if timestamp < *existing_ts {
105 debug!(
106 "Rejected node removal (old timestamp): {} @ {}",
107 node_id, timestamp
108 );
109 return false;
110 }
111 }
112
113 debug!("Removing node from cell: {} @ {}", node_id, timestamp);
114 self.node_to_cell.remove(node_id);
115 true
116 }
117
118 #[instrument(skip(self))]
128 pub fn assign_cell(&mut self, cell_id: &str, zone_id: &str, timestamp: u64) -> bool {
129 if let Some((_, existing_ts)) = self.cell_to_zone.get(cell_id) {
130 if timestamp <= *existing_ts {
131 debug!(
132 "Rejected cell assignment (old timestamp): {} → {} @ {}",
133 cell_id, zone_id, timestamp
134 );
135 return false;
136 }
137 }
138
139 debug!(
140 "Assigning cell to zone: {} → {} @ {}",
141 cell_id, zone_id, timestamp
142 );
143 self.cell_to_zone
144 .insert(cell_id.to_string(), (zone_id.to_string(), timestamp));
145 true
146 }
147
148 #[instrument(skip(self))]
157 pub fn remove_cell(&mut self, cell_id: &str, timestamp: u64) -> bool {
158 if let Some((_, existing_ts)) = self.cell_to_zone.get(cell_id) {
159 if timestamp < *existing_ts {
160 debug!(
161 "Rejected cell removal (old timestamp): {} @ {}",
162 cell_id, timestamp
163 );
164 return false;
165 }
166 }
167
168 debug!("Removing cell from zone: {} @ {}", cell_id, timestamp);
169 self.cell_to_zone.remove(cell_id);
170 true
171 }
172
173 #[instrument(skip(self))]
183 pub fn set_cell_leader(&mut self, cell_id: &str, leader_node_id: &str, timestamp: u64) -> bool {
184 if let Some((_, existing_ts)) = self.cell_leaders.get(cell_id) {
185 if timestamp <= *existing_ts {
186 debug!(
187 "Rejected leader assignment (old timestamp): {} → {} @ {}",
188 cell_id, leader_node_id, timestamp
189 );
190 return false;
191 }
192 }
193
194 debug!(
195 "Setting cell leader: {} → {} @ {}",
196 cell_id, leader_node_id, timestamp
197 );
198 self.cell_leaders
199 .insert(cell_id.to_string(), (leader_node_id.to_string(), timestamp));
200 true
201 }
202
203 #[instrument(skip(self))]
212 pub fn remove_cell_leader(&mut self, cell_id: &str, timestamp: u64) -> bool {
213 if let Some((_, existing_ts)) = self.cell_leaders.get(cell_id) {
214 if timestamp < *existing_ts {
215 debug!(
216 "Rejected leader removal (old timestamp): {} @ {}",
217 cell_id, timestamp
218 );
219 return false;
220 }
221 }
222
223 debug!("Removing cell leader: {} @ {}", cell_id, timestamp);
224 self.cell_leaders.remove(cell_id);
225 true
226 }
227
228 pub fn get_node_cell(&self, node_id: &str) -> Option<&str> {
232 self.node_to_cell
233 .get(node_id)
234 .map(|(cell_id, _)| cell_id.as_str())
235 }
236
237 pub fn get_cell_zone(&self, cell_id: &str) -> Option<&str> {
241 self.cell_to_zone
242 .get(cell_id)
243 .map(|(zone_id, _)| zone_id.as_str())
244 }
245
246 pub fn get_node_zone(&self, node_id: &str) -> Option<&str> {
250 let cell_id = self.get_node_cell(node_id)?;
251 self.get_cell_zone(cell_id)
252 }
253
254 pub fn is_cell_leader(&self, node_id: &str, cell_id: &str) -> bool {
258 self.cell_leaders
259 .get(cell_id)
260 .map(|(leader_id, _)| leader_id == node_id)
261 .unwrap_or(false)
262 }
263
264 pub fn get_cell_leader(&self, cell_id: &str) -> Option<&str> {
268 self.cell_leaders
269 .get(cell_id)
270 .map(|(leader_id, _)| leader_id.as_str())
271 }
272
273 #[instrument(skip(self, other))]
277 pub fn merge(&mut self, other: &RoutingTable) {
278 debug!("Merging routing tables");
279
280 for (node_id, (cell_id, timestamp)) in &other.node_to_cell {
282 self.assign_node(node_id, cell_id, *timestamp);
283 }
284
285 for (cell_id, (zone_id, timestamp)) in &other.cell_to_zone {
287 self.assign_cell(cell_id, zone_id, *timestamp);
288 }
289
290 for (cell_id, (leader_id, timestamp)) in &other.cell_leaders {
292 self.set_cell_leader(cell_id, leader_id, *timestamp);
293 }
294 }
295
296 pub fn stats(&self) -> RoutingTableStats {
298 RoutingTableStats {
299 node_count: self.node_to_cell.len(),
300 cell_count: self.cell_to_zone.len(),
301 leader_count: self.cell_leaders.len(),
302 }
303 }
304
305 pub fn get_cell_nodes(&self, cell_id: &str) -> Vec<&str> {
307 self.node_to_cell
308 .iter()
309 .filter(|(_, (cid, _))| cid == cell_id)
310 .map(|(node_id, _)| node_id.as_str())
311 .collect()
312 }
313
314 pub fn get_zone_cells(&self, zone_id: &str) -> Vec<&str> {
316 self.cell_to_zone
317 .iter()
318 .filter(|(_, (zid, _))| zid == zone_id)
319 .map(|(cell_id, _)| cell_id.as_str())
320 .collect()
321 }
322
323 #[instrument(skip(self))]
338 pub fn merge_cells(
339 &mut self,
340 source_cell_ids: &[&str],
341 merged_cell_id: &str,
342 zone_id: Option<&str>,
343 timestamp: u64,
344 ) -> usize {
345 debug!(
346 "Merging cells {:?} into {} @ {}",
347 source_cell_ids, merged_cell_id, timestamp
348 );
349
350 let mut reassigned_count = 0;
351
352 let nodes_to_reassign: Vec<String> = source_cell_ids
354 .iter()
355 .flat_map(|cell_id| {
356 self.node_to_cell
357 .iter()
358 .filter(|(_, (cid, _))| cid == *cell_id)
359 .map(|(node_id, _)| node_id.clone())
360 .collect::<Vec<_>>()
361 })
362 .collect();
363
364 for node_id in nodes_to_reassign {
366 if self.assign_node(&node_id, merged_cell_id, timestamp) {
367 reassigned_count += 1;
368 }
369 }
370
371 if let Some(zid) = zone_id {
373 self.assign_cell(merged_cell_id, zid, timestamp);
374 }
375
376 for cell_id in source_cell_ids {
378 self.remove_cell(cell_id, timestamp);
379 self.remove_cell_leader(cell_id, timestamp);
380 }
381
382 debug!(
383 "Merge complete: {} nodes reassigned to {}",
384 reassigned_count, merged_cell_id
385 );
386
387 reassigned_count
388 }
389
390 #[instrument(skip(self, nodes_for_a, nodes_for_b))]
408 #[allow(clippy::too_many_arguments)]
409 pub fn split_cell(
410 &mut self,
411 source_cell_id: &str,
412 cell_a_id: &str,
413 cell_b_id: &str,
414 nodes_for_a: &[&str],
415 nodes_for_b: &[&str],
416 zone_id: Option<&str>,
417 timestamp: u64,
418 ) -> (usize, usize) {
419 debug!(
420 "Splitting cell {} into {} and {} @ {}",
421 source_cell_id, cell_a_id, cell_b_id, timestamp
422 );
423
424 let mut count_a = 0;
425 let mut count_b = 0;
426
427 for node_id in nodes_for_a {
429 if self.assign_node(node_id, cell_a_id, timestamp) {
430 count_a += 1;
431 }
432 }
433
434 for node_id in nodes_for_b {
436 if self.assign_node(node_id, cell_b_id, timestamp) {
437 count_b += 1;
438 }
439 }
440
441 if let Some(zid) = zone_id {
443 self.assign_cell(cell_a_id, zid, timestamp);
444 self.assign_cell(cell_b_id, zid, timestamp);
445 }
446
447 self.remove_cell(source_cell_id, timestamp);
449 self.remove_cell_leader(source_cell_id, timestamp);
450
451 debug!(
452 "Split complete: {} → {} nodes in {}, {} nodes in {}",
453 source_cell_id, count_a, cell_a_id, count_b, cell_b_id
454 );
455
456 (count_a, count_b)
457 }
458}
459
460impl Default for RoutingTable {
461 fn default() -> Self {
462 Self::new()
463 }
464}
465
466#[derive(Debug, Clone)]
468pub struct RoutingTableStats {
469 pub node_count: usize,
471 pub cell_count: usize,
473 pub leader_count: usize,
475}
476
477#[cfg(test)]
478mod tests {
479 use super::*;
480
481 #[test]
482 fn test_routing_table_creation() {
483 let table = RoutingTable::new();
484 let stats = table.stats();
485 assert_eq!(stats.node_count, 0);
486 assert_eq!(stats.cell_count, 0);
487 assert_eq!(stats.leader_count, 0);
488 }
489
490 #[test]
491 fn test_node_assignment() {
492 let mut table = RoutingTable::new();
493
494 assert!(table.assign_node("node1", "cell_alpha", 100));
496 assert_eq!(table.get_node_cell("node1"), Some("cell_alpha"));
497
498 assert!(table.assign_node("node2", "cell_alpha", 101));
500 assert_eq!(table.get_node_cell("node2"), Some("cell_alpha"));
501
502 let stats = table.stats();
504 assert_eq!(stats.node_count, 2);
505 }
506
507 #[test]
508 fn test_lww_semantics_node_assignment() {
509 let mut table = RoutingTable::new();
510
511 assert!(table.assign_node("node1", "cell_alpha", 100));
513 assert_eq!(table.get_node_cell("node1"), Some("cell_alpha"));
514
515 assert!(table.assign_node("node1", "cell_beta", 200));
517 assert_eq!(table.get_node_cell("node1"), Some("cell_beta"));
518
519 assert!(!table.assign_node("node1", "cell_gamma", 150));
521 assert_eq!(table.get_node_cell("node1"), Some("cell_beta"));
522
523 assert!(!table.assign_node("node1", "cell_delta", 200));
525 assert_eq!(table.get_node_cell("node1"), Some("cell_beta"));
526 }
527
528 #[test]
529 fn test_cell_assignment() {
530 let mut table = RoutingTable::new();
531
532 assert!(table.assign_cell("cell_alpha", "zone_north", 100));
534 assert_eq!(table.get_cell_zone("cell_alpha"), Some("zone_north"));
535
536 let stats = table.stats();
537 assert_eq!(stats.cell_count, 1);
538 }
539
540 #[test]
541 fn test_lww_semantics_cell_assignment() {
542 let mut table = RoutingTable::new();
543
544 assert!(table.assign_cell("cell_alpha", "zone_north", 100));
546 assert_eq!(table.get_cell_zone("cell_alpha"), Some("zone_north"));
547
548 assert!(table.assign_cell("cell_alpha", "zone_south", 200));
550 assert_eq!(table.get_cell_zone("cell_alpha"), Some("zone_south"));
551
552 assert!(!table.assign_cell("cell_alpha", "zone_east", 150));
554 assert_eq!(table.get_cell_zone("cell_alpha"), Some("zone_south"));
555 }
556
557 #[test]
558 fn test_cell_leader() {
559 let mut table = RoutingTable::new();
560
561 table.assign_node("node1", "cell_alpha", 100);
563 table.assign_node("node2", "cell_alpha", 101);
564
565 assert!(table.set_cell_leader("cell_alpha", "node1", 102));
567 assert!(table.is_cell_leader("node1", "cell_alpha"));
568 assert!(!table.is_cell_leader("node2", "cell_alpha"));
569 assert_eq!(table.get_cell_leader("cell_alpha"), Some("node1"));
570
571 let stats = table.stats();
572 assert_eq!(stats.leader_count, 1);
573 }
574
575 #[test]
576 fn test_lww_semantics_leader() {
577 let mut table = RoutingTable::new();
578
579 assert!(table.set_cell_leader("cell_alpha", "node1", 100));
581 assert!(table.is_cell_leader("node1", "cell_alpha"));
582
583 assert!(table.set_cell_leader("cell_alpha", "node2", 200));
585 assert!(!table.is_cell_leader("node1", "cell_alpha"));
586 assert!(table.is_cell_leader("node2", "cell_alpha"));
587
588 assert!(!table.set_cell_leader("cell_alpha", "node3", 150));
590 assert!(table.is_cell_leader("node2", "cell_alpha"));
591 }
592
593 #[test]
594 fn test_node_zone_lookup() {
595 let mut table = RoutingTable::new();
596
597 table.assign_node("node1", "cell_alpha", 100);
599 table.assign_cell("cell_alpha", "zone_north", 101);
600
601 assert_eq!(table.get_node_zone("node1"), Some("zone_north"));
603
604 assert_eq!(table.get_node_zone("node2"), None);
606
607 table.assign_node("node3", "cell_beta", 102);
609 assert_eq!(table.get_node_zone("node3"), None);
610 }
611
612 #[test]
613 fn test_get_cell_nodes() {
614 let mut table = RoutingTable::new();
615
616 table.assign_node("node1", "cell_alpha", 100);
617 table.assign_node("node2", "cell_alpha", 101);
618 table.assign_node("node3", "cell_beta", 102);
619
620 let mut alpha_nodes = table.get_cell_nodes("cell_alpha");
621 alpha_nodes.sort();
622 assert_eq!(alpha_nodes, vec!["node1", "node2"]);
623
624 let beta_nodes = table.get_cell_nodes("cell_beta");
625 assert_eq!(beta_nodes, vec!["node3"]);
626
627 let empty_nodes = table.get_cell_nodes("cell_gamma");
628 assert_eq!(empty_nodes.len(), 0);
629 }
630
631 #[test]
632 fn test_get_zone_cells() {
633 let mut table = RoutingTable::new();
634
635 table.assign_cell("cell_alpha", "zone_north", 100);
636 table.assign_cell("cell_beta", "zone_north", 101);
637 table.assign_cell("cell_gamma", "zone_south", 102);
638
639 let mut north_cells = table.get_zone_cells("zone_north");
640 north_cells.sort();
641 assert_eq!(north_cells, vec!["cell_alpha", "cell_beta"]);
642
643 let south_cells = table.get_zone_cells("zone_south");
644 assert_eq!(south_cells, vec!["cell_gamma"]);
645
646 let empty_cells = table.get_zone_cells("zone_east");
647 assert_eq!(empty_cells.len(), 0);
648 }
649
650 #[test]
651 fn test_node_removal() {
652 let mut table = RoutingTable::new();
653
654 table.assign_node("node1", "cell_alpha", 100);
655 assert_eq!(table.get_node_cell("node1"), Some("cell_alpha"));
656
657 assert!(!table.remove_node("node1", 50));
659 assert_eq!(table.get_node_cell("node1"), Some("cell_alpha"));
660
661 assert!(table.remove_node("node1", 200));
663 assert_eq!(table.get_node_cell("node1"), None);
664 }
665
666 #[test]
667 fn test_cell_removal() {
668 let mut table = RoutingTable::new();
669
670 table.assign_cell("cell_alpha", "zone_north", 100);
671 assert_eq!(table.get_cell_zone("cell_alpha"), Some("zone_north"));
672
673 assert!(!table.remove_cell("cell_alpha", 50));
675 assert_eq!(table.get_cell_zone("cell_alpha"), Some("zone_north"));
676
677 assert!(table.remove_cell("cell_alpha", 200));
679 assert_eq!(table.get_cell_zone("cell_alpha"), None);
680 }
681
682 #[test]
683 fn test_leader_removal() {
684 let mut table = RoutingTable::new();
685
686 table.set_cell_leader("cell_alpha", "node1", 100);
687 assert_eq!(table.get_cell_leader("cell_alpha"), Some("node1"));
688
689 assert!(!table.remove_cell_leader("cell_alpha", 50));
691 assert_eq!(table.get_cell_leader("cell_alpha"), Some("node1"));
692
693 assert!(table.remove_cell_leader("cell_alpha", 200));
695 assert_eq!(table.get_cell_leader("cell_alpha"), None);
696 }
697
698 #[test]
699 fn test_merge() {
700 let mut table1 = RoutingTable::new();
701 let mut table2 = RoutingTable::new();
702
703 table1.assign_node("node1", "cell_alpha", 100);
705 table1.assign_cell("cell_alpha", "zone_north", 100);
706
707 table2.assign_node("node1", "cell_beta", 200);
709 table2.assign_node("node2", "cell_gamma", 150);
710 table2.assign_cell("cell_beta", "zone_south", 200);
711
712 table1.merge(&table2);
714
715 assert_eq!(table1.get_node_cell("node1"), Some("cell_beta"));
717
718 assert_eq!(table1.get_node_cell("node2"), Some("cell_gamma"));
720
721 assert_eq!(table1.get_cell_zone("cell_beta"), Some("zone_south"));
723 assert_eq!(table1.get_cell_zone("cell_alpha"), Some("zone_north"));
724 }
725
726 #[test]
727 fn test_merge_leaders() {
728 let mut table1 = RoutingTable::new();
729 let mut table2 = RoutingTable::new();
730
731 table1.set_cell_leader("cell_alpha", "node1", 100);
732 table2.set_cell_leader("cell_alpha", "node2", 200);
733
734 table1.merge(&table2);
735
736 assert!(table1.is_cell_leader("node2", "cell_alpha"));
738 assert!(!table1.is_cell_leader("node1", "cell_alpha"));
739 }
740
741 #[test]
742 fn test_merge_cells() {
743 let mut table = RoutingTable::new();
744
745 table.assign_node("node1", "cell_alpha", 100);
747 table.assign_node("node2", "cell_alpha", 101);
748 table.assign_node("node3", "cell_beta", 102);
749 table.assign_node("node4", "cell_beta", 103);
750
751 table.assign_cell("cell_alpha", "zone_north", 100);
752 table.assign_cell("cell_beta", "zone_north", 101);
753
754 table.set_cell_leader("cell_alpha", "node1", 100);
755 table.set_cell_leader("cell_beta", "node3", 101);
756
757 let count = table.merge_cells(
759 &["cell_alpha", "cell_beta"],
760 "cell_merged",
761 Some("zone_north"),
762 200,
763 );
764
765 assert_eq!(count, 4);
767
768 assert_eq!(table.get_node_cell("node1"), Some("cell_merged"));
770 assert_eq!(table.get_node_cell("node2"), Some("cell_merged"));
771 assert_eq!(table.get_node_cell("node3"), Some("cell_merged"));
772 assert_eq!(table.get_node_cell("node4"), Some("cell_merged"));
773
774 assert_eq!(table.get_cell_zone("cell_merged"), Some("zone_north"));
776
777 assert_eq!(table.get_cell_zone("cell_alpha"), None);
779 assert_eq!(table.get_cell_zone("cell_beta"), None);
780
781 assert_eq!(table.get_cell_leader("cell_alpha"), None);
783 assert_eq!(table.get_cell_leader("cell_beta"), None);
784
785 assert_eq!(table.get_cell_leader("cell_merged"), None);
787 }
788
789 #[test]
790 fn test_split_cell() {
791 let mut table = RoutingTable::new();
792
793 table.assign_node("node1", "cell_alpha", 100);
795 table.assign_node("node2", "cell_alpha", 101);
796 table.assign_node("node3", "cell_alpha", 102);
797 table.assign_node("node4", "cell_alpha", 103);
798
799 table.assign_cell("cell_alpha", "zone_north", 100);
800 table.set_cell_leader("cell_alpha", "node1", 100);
801
802 let (count_a, count_b) = table.split_cell(
804 "cell_alpha",
805 "cell_a",
806 "cell_b",
807 &["node1", "node2"],
808 &["node3", "node4"],
809 Some("zone_north"),
810 200,
811 );
812
813 assert_eq!(count_a, 2);
815 assert_eq!(count_b, 2);
816
817 assert_eq!(table.get_node_cell("node1"), Some("cell_a"));
819 assert_eq!(table.get_node_cell("node2"), Some("cell_a"));
820 assert_eq!(table.get_node_cell("node3"), Some("cell_b"));
821 assert_eq!(table.get_node_cell("node4"), Some("cell_b"));
822
823 assert_eq!(table.get_cell_zone("cell_a"), Some("zone_north"));
825 assert_eq!(table.get_cell_zone("cell_b"), Some("zone_north"));
826
827 assert_eq!(table.get_cell_zone("cell_alpha"), None);
829
830 assert_eq!(table.get_cell_leader("cell_alpha"), None);
832
833 assert_eq!(table.get_cell_leader("cell_a"), None);
835 assert_eq!(table.get_cell_leader("cell_b"), None);
836 }
837
838 #[test]
839 fn test_merge_cells_without_zone() {
840 let mut table = RoutingTable::new();
841
842 table.assign_node("node1", "cell_alpha", 100);
843 table.assign_node("node2", "cell_beta", 101);
844
845 let count = table.merge_cells(&["cell_alpha", "cell_beta"], "cell_merged", None, 200);
847
848 assert_eq!(count, 2);
849 assert_eq!(table.get_node_cell("node1"), Some("cell_merged"));
850 assert_eq!(table.get_node_cell("node2"), Some("cell_merged"));
851
852 assert_eq!(table.get_cell_zone("cell_merged"), None);
854 }
855
856 #[test]
857 fn test_split_cell_without_zone() {
858 let mut table = RoutingTable::new();
859
860 table.assign_node("node1", "cell_alpha", 100);
861 table.assign_node("node2", "cell_alpha", 101);
862
863 let (count_a, count_b) = table.split_cell(
865 "cell_alpha",
866 "cell_a",
867 "cell_b",
868 &["node1"],
869 &["node2"],
870 None,
871 200,
872 );
873
874 assert_eq!(count_a, 1);
875 assert_eq!(count_b, 1);
876
877 assert_eq!(table.get_cell_zone("cell_a"), None);
879 assert_eq!(table.get_cell_zone("cell_b"), None);
880 }
881}