1#![allow(dead_code)]
7
8use std::collections::HashMap;
9use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
10use std::sync::Mutex;
11use std::time::{Duration, Instant};
12
13#[derive(Debug)]
19pub struct RaftState {
20 pub current_term: u64,
22 pub voted_for: Option<u64>,
24 pub log: Vec<LogEntry>,
26}
27
28#[derive(Debug, Clone)]
30pub struct LogEntry {
31 pub term: u64,
33 pub index: u64,
35 pub command: String,
37}
38
39impl RaftState {
40 #[must_use]
42 pub fn new() -> Self {
43 Self {
44 current_term: 0,
45 voted_for: None,
46 log: Vec::new(),
47 }
48 }
49}
50
51impl Default for RaftState {
52 fn default() -> Self {
53 Self::new()
54 }
55}
56
57#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct VoteResponse {
60 pub term: u64,
62 pub vote_granted: bool,
64}
65
66#[derive(Debug)]
68pub struct RaftNode {
69 pub node_id: u64,
71 state: Mutex<RaftState>,
73}
74
75impl RaftNode {
76 #[must_use]
78 pub fn new(node_id: u64) -> Self {
79 Self {
80 node_id,
81 state: Mutex::new(RaftState::new()),
82 }
83 }
84
85 pub fn request_vote(
98 &self,
99 term: u64,
100 candidate_id: u64,
101 last_log_index: u64,
102 last_log_term: u64,
103 ) -> VoteResponse {
104 let mut state = self.state.lock().unwrap_or_else(|e| e.into_inner());
105
106 if term > state.current_term {
108 state.current_term = term;
109 state.voted_for = None;
110 }
111
112 if term < state.current_term {
114 return VoteResponse {
115 term: state.current_term,
116 vote_granted: false,
117 };
118 }
119
120 let can_vote = state.voted_for.is_none() || state.voted_for == Some(candidate_id);
122 if !can_vote {
123 return VoteResponse {
124 term: state.current_term,
125 vote_granted: false,
126 };
127 }
128
129 let our_last_term = state.log.last().map_or(0, |e| e.term);
131 let our_last_index = state.log.len() as u64;
132
133 let candidate_log_ok = if last_log_term != our_last_term {
134 last_log_term > our_last_term
135 } else {
136 last_log_index >= our_last_index
137 };
138
139 if candidate_log_ok {
140 state.voted_for = Some(candidate_id);
141 VoteResponse {
142 term: state.current_term,
143 vote_granted: true,
144 }
145 } else {
146 VoteResponse {
147 term: state.current_term,
148 vote_granted: false,
149 }
150 }
151 }
152
153 pub fn current_term(&self) -> u64 {
155 self.state
156 .lock()
157 .unwrap_or_else(|e| e.into_inner())
158 .current_term
159 }
160
161 pub fn voted_for(&self) -> Option<u64> {
163 self.state
164 .lock()
165 .unwrap_or_else(|e| e.into_inner())
166 .voted_for
167 }
168}
169
170#[derive(Debug)]
178pub struct WorkStealingQueue<T> {
179 local: Vec<T>,
181 stolen: Vec<T>,
183}
184
185impl<T> WorkStealingQueue<T> {
186 #[must_use]
188 pub fn new() -> Self {
189 Self {
190 local: Vec::new(),
191 stolen: Vec::new(),
192 }
193 }
194
195 pub fn push(&mut self, item: T) {
197 self.local.push(item);
198 }
199
200 pub fn pop(&mut self) -> Option<T> {
205 if let Some(item) = self.stolen.pop() {
206 return Some(item);
207 }
208 self.local.pop()
209 }
210
211 pub fn steal(&mut self) -> Option<T> {
215 if self.local.is_empty() {
216 None
217 } else {
218 Some(self.local.remove(0))
219 }
220 }
221
222 #[must_use]
224 pub fn len(&self) -> usize {
225 self.local.len() + self.stolen.len()
226 }
227
228 #[must_use]
230 pub fn is_empty(&self) -> bool {
231 self.local.is_empty() && self.stolen.is_empty()
232 }
233}
234
235impl<T> Default for WorkStealingQueue<T> {
236 fn default() -> Self {
237 Self::new()
238 }
239}
240
241#[derive(Debug)]
250pub struct BackpressureController {
251 max_pending: usize,
253 pending: AtomicUsize,
255}
256
257impl BackpressureController {
258 #[must_use]
260 pub fn new(max_pending: usize) -> Self {
261 Self {
262 max_pending,
263 pending: AtomicUsize::new(0),
264 }
265 }
266
267 pub fn try_submit(&self) -> bool {
272 loop {
274 let current = self.pending.load(Ordering::Acquire);
275 if current >= self.max_pending {
276 return false;
277 }
278 match self.pending.compare_exchange(
279 current,
280 current + 1,
281 Ordering::AcqRel,
282 Ordering::Acquire,
283 ) {
284 Ok(_) => return true,
285 Err(_) => continue, }
287 }
288 }
289
290 pub fn complete_one(&self) {
294 let _ = self
295 .pending
296 .fetch_update(Ordering::AcqRel, Ordering::Acquire, |v| {
297 if v > 0 {
298 Some(v - 1)
299 } else {
300 None
301 }
302 });
303 }
304
305 #[must_use]
307 pub fn pending_count(&self) -> usize {
308 self.pending.load(Ordering::Acquire)
309 }
310
311 #[must_use]
313 pub fn max_pending(&self) -> usize {
314 self.max_pending
315 }
316}
317
318#[derive(Debug, Clone)]
324pub struct DistributedCheckpoint {
325 pub node_id: u64,
327 pub sequence: u64,
329 pub state: Vec<u8>,
331}
332
333#[derive(Debug, Default)]
335pub struct CheckpointCoordinator {
336 sequences: HashMap<u64, u64>,
338 checkpoints: Vec<DistributedCheckpoint>,
340}
341
342impl CheckpointCoordinator {
343 #[must_use]
345 pub fn new() -> Self {
346 Self::default()
347 }
348
349 pub fn take_checkpoint(&mut self, node_id: u64, state: &[u8]) -> u64 {
354 let seq = self.sequences.entry(node_id).or_insert(0);
355 *seq += 1;
356 let sequence = *seq;
357
358 self.checkpoints.push(DistributedCheckpoint {
359 node_id,
360 sequence,
361 state: state.to_vec(),
362 });
363
364 sequence
365 }
366
367 #[must_use]
369 pub fn latest_checkpoint(&self, node_id: u64) -> Option<&DistributedCheckpoint> {
370 self.checkpoints
371 .iter()
372 .filter(|c| c.node_id == node_id)
373 .max_by_key(|c| c.sequence)
374 }
375
376 #[must_use]
378 pub fn checkpoint_count(&self) -> usize {
379 self.checkpoints.len()
380 }
381}
382
383#[derive(Debug)]
391pub struct ConsistentHashRing {
392 virtual_nodes: u32,
394 ring: Vec<(u64, u64)>,
396 nodes: Vec<u64>,
398}
399
400impl ConsistentHashRing {
401 #[must_use]
403 pub fn new(virtual_nodes: u32) -> Self {
404 Self {
405 virtual_nodes,
406 ring: Vec::new(),
407 nodes: Vec::new(),
408 }
409 }
410
411 pub fn add_node(&mut self, id: u64) {
413 if self.nodes.contains(&id) {
414 return;
415 }
416 self.nodes.push(id);
417 for i in 0..self.virtual_nodes {
418 let key = format!("{id}:vn:{i}");
419 let h = Self::fnv1a(key.as_bytes());
420 self.ring.push((h, id));
421 }
422 self.ring.sort_unstable_by_key(|(h, _)| *h);
423 }
424
425 pub fn remove_node(&mut self, id: u64) {
427 self.nodes.retain(|&n| n != id);
428 for i in 0..self.virtual_nodes {
429 let key = format!("{id}:vn:{i}");
430 let h = Self::fnv1a(key.as_bytes());
431 self.ring.retain(|(rh, _)| *rh != h);
432 }
433 }
434
435 #[must_use]
439 pub fn get_node(&self, key: &[u8]) -> Option<u64> {
440 if self.ring.is_empty() {
441 return None;
442 }
443 let h = Self::fnv1a(key);
444 match self.ring.binary_search_by_key(&h, |(rh, _)| *rh) {
446 Ok(idx) => Some(self.ring[idx].1),
447 Err(idx) => {
448 let idx = idx % self.ring.len();
450 Some(self.ring[idx].1)
451 }
452 }
453 }
454
455 #[must_use]
457 pub fn node_count(&self) -> usize {
458 self.nodes.len()
459 }
460
461 fn fnv1a(data: &[u8]) -> u64 {
463 let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
464 for &byte in data {
465 hash ^= u64::from(byte);
466 hash = hash.wrapping_mul(0x0100_0000_01b3);
467 }
468 hash
469 }
470}
471
472#[derive(Debug)]
478pub struct DistributedCircuitBreaker {
479 threshold: u32,
481 timeout_ms: u64,
483 failures: AtomicU64,
485 opened_at_ms: AtomicU64,
487 open: AtomicBool,
489}
490
491impl DistributedCircuitBreaker {
492 #[must_use]
494 pub fn new(threshold: u32, timeout_ms: u64) -> Self {
495 Self {
496 threshold,
497 timeout_ms,
498 failures: AtomicU64::new(0),
499 opened_at_ms: AtomicU64::new(0),
500 open: AtomicBool::new(false),
501 }
502 }
503
504 pub fn call_succeeded(&self) {
507 if !self.open.load(Ordering::Acquire) {
508 self.failures.store(0, Ordering::Release);
509 }
510 }
511
512 pub fn call_failed(&self) {
515 let prev = self.failures.fetch_add(1, Ordering::AcqRel);
516 if prev + 1 >= u64::from(self.threshold) {
517 let now_ms = Self::now_ms();
518 self.opened_at_ms.store(now_ms, Ordering::Release);
519 self.open.store(true, Ordering::Release);
520 }
521 }
522
523 pub fn is_open(&self) -> bool {
529 if !self.open.load(Ordering::Acquire) {
530 return false;
531 }
532 let opened_at = self.opened_at_ms.load(Ordering::Acquire);
534 let elapsed = Self::now_ms().saturating_sub(opened_at);
535 if elapsed >= self.timeout_ms {
536 self.open.store(false, Ordering::Release);
538 self.failures.store(0, Ordering::Release);
539 return false;
540 }
541 true
542 }
543
544 #[must_use]
546 pub fn failure_count(&self) -> u64 {
547 self.failures.load(Ordering::Acquire)
548 }
549
550 pub fn reset(&self) {
552 self.failures.store(0, Ordering::Release);
553 self.open.store(false, Ordering::Release);
554 self.opened_at_ms.store(0, Ordering::Release);
555 }
556
557 fn now_ms() -> u64 {
560 std::time::SystemTime::now()
561 .duration_since(std::time::UNIX_EPOCH)
562 .map(|d| d.as_millis() as u64)
563 .unwrap_or(0)
564 }
565}
566
567#[must_use]
573pub fn shard_assign(key_hash: u64, num_shards: u32) -> u32 {
574 if num_shards == 0 {
575 return 0;
576 }
577 (key_hash % u64::from(num_shards)) as u32
578}
579
580#[must_use]
582pub fn fnv1a_hash(data: &[u8]) -> u64 {
583 let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
584 for &byte in data {
585 hash ^= u64::from(byte);
586 hash = hash.wrapping_mul(0x0100_0000_01b3);
587 }
588 hash
589}
590
591#[derive(Debug)]
596pub struct SimpleShardMap {
597 shards: Vec<HashMap<Vec<u8>, Vec<u8>>>,
598 num_shards: u32,
599}
600
601impl SimpleShardMap {
602 #[must_use]
604 pub fn new(num_shards: u32) -> Self {
605 let count = num_shards.max(1) as usize;
606 Self {
607 shards: vec![HashMap::new(); count],
608 num_shards: num_shards.max(1),
609 }
610 }
611
612 pub fn insert(&mut self, key: &[u8], value: Vec<u8>) {
614 let h = fnv1a_hash(key);
615 let shard = shard_assign(h, self.num_shards) as usize;
616 self.shards[shard].insert(key.to_vec(), value);
617 }
618
619 #[must_use]
621 pub fn get(&self, key: &[u8]) -> Option<&[u8]> {
622 let h = fnv1a_hash(key);
623 let shard = shard_assign(h, self.num_shards) as usize;
624 self.shards[shard].get(key).map(|v| v.as_slice())
625 }
626
627 #[must_use]
629 pub fn len(&self) -> usize {
630 self.shards.iter().map(|s| s.len()).sum()
631 }
632
633 #[must_use]
635 pub fn is_empty(&self) -> bool {
636 self.len() == 0
637 }
638
639 #[must_use]
641 pub fn shard_counts(&self) -> Vec<usize> {
642 self.shards.iter().map(|s| s.len()).collect()
643 }
644}
645
646#[derive(Debug, Clone)]
652struct ServiceEntry {
653 addr: String,
655 expires_at: Instant,
657}
658
659#[derive(Debug)]
661pub struct ServiceRegistry {
662 entries: Mutex<HashMap<u64, ServiceEntry>>,
663 default_ttl: Duration,
665}
666
667impl ServiceRegistry {
668 #[must_use]
670 pub fn new(default_ttl: Duration) -> Self {
671 Self {
672 entries: Mutex::new(HashMap::new()),
673 default_ttl,
674 }
675 }
676
677 #[must_use]
679 pub fn with_default_ttl() -> Self {
680 Self::new(Duration::from_secs(60))
681 }
682
683 pub fn register(&self, service_id: u64, addr: &str) {
687 let entry = ServiceEntry {
688 addr: addr.to_string(),
689 expires_at: Instant::now() + self.default_ttl,
690 };
691 self.entries
692 .lock()
693 .unwrap_or_else(|e| e.into_inner())
694 .insert(service_id, entry);
695 }
696
697 pub fn discover(&self, service_id: u64) -> Option<String> {
701 let mut guard = self.entries.lock().unwrap_or_else(|e| e.into_inner());
702 match guard.get(&service_id) {
703 Some(entry) if entry.expires_at > Instant::now() => Some(entry.addr.clone()),
704 Some(_) => {
705 guard.remove(&service_id);
707 None
708 }
709 None => None,
710 }
711 }
712
713 #[must_use]
715 pub fn registered_count(&self) -> usize {
716 self.entries.lock().unwrap_or_else(|e| e.into_inner()).len()
717 }
718
719 pub fn evict_expired(&self) {
721 let now = Instant::now();
722 self.entries
723 .lock()
724 .unwrap_or_else(|e| e.into_inner())
725 .retain(|_, e| e.expires_at > now);
726 }
727}
728
729#[derive(Debug, Default)]
735pub struct ReplicationManager {
736 replicas: HashMap<String, Vec<u64>>,
738}
739
740impl ReplicationManager {
741 #[must_use]
743 pub fn new() -> Self {
744 Self::default()
745 }
746
747 pub fn replicate(&mut self, data: &[u8], factor: u32, nodes: &[u64]) -> Vec<u64> {
755 let count = (factor as usize).min(nodes.len());
756 let selected: Vec<u64> = nodes[..count].to_vec();
757
758 let key = format!("{:x}", fnv1a_hash(data));
760 self.replicas.insert(key, selected.clone());
761
762 selected
763 }
764
765 #[must_use]
767 pub fn replica_nodes(&self, data: &[u8]) -> Option<&[u64]> {
768 let key = format!("{:x}", fnv1a_hash(data));
769 self.replicas.get(&key).map(|v| v.as_slice())
770 }
771
772 #[must_use]
774 pub fn record_count(&self) -> usize {
775 self.replicas.len()
776 }
777}
778
779#[cfg(test)]
784mod tests {
785 use super::*;
786
787 #[test]
790 fn test_raft_vote_granted_when_term_greater() {
791 let node = RaftNode::new(1);
792 let resp = node.request_vote(5, 2, 0, 0);
793 assert!(resp.vote_granted, "should grant vote for higher term");
794 assert_eq!(resp.term, 5);
795 }
796
797 #[test]
798 fn test_raft_vote_denied_stale_term() {
799 let node = RaftNode::new(1);
800 node.request_vote(5, 2, 0, 0);
802 let resp = node.request_vote(3, 3, 0, 0);
804 assert!(!resp.vote_granted, "should deny vote for stale term");
805 }
806
807 #[test]
808 fn test_raft_vote_denied_already_voted() {
809 let node = RaftNode::new(1);
810 node.request_vote(1, 2, 0, 0); let resp = node.request_vote(1, 3, 0, 0); assert!(!resp.vote_granted, "should deny double-vote in same term");
813 }
814
815 #[test]
816 fn test_raft_vote_same_candidate_ok() {
817 let node = RaftNode::new(1);
818 node.request_vote(1, 2, 0, 0); let resp = node.request_vote(1, 2, 0, 0); assert!(
821 resp.vote_granted,
822 "idempotent vote for same candidate should succeed"
823 );
824 }
825
826 #[test]
827 fn test_raft_vote_new_term_clears_old_vote() {
828 let node = RaftNode::new(1);
829 node.request_vote(1, 2, 0, 0); let resp = node.request_vote(2, 3, 0, 0); assert!(resp.vote_granted);
832 assert_eq!(node.voted_for(), Some(3));
833 }
834
835 #[test]
838 fn test_wsq_push_pop_lifo() {
839 let mut q: WorkStealingQueue<u32> = WorkStealingQueue::new();
840 q.push(1);
841 q.push(2);
842 q.push(3);
843 assert_eq!(q.pop(), Some(3)); assert_eq!(q.pop(), Some(2));
845 assert_eq!(q.pop(), Some(1));
846 assert_eq!(q.pop(), None);
847 }
848
849 #[test]
850 fn test_wsq_steal_fifo() {
851 let mut q: WorkStealingQueue<u32> = WorkStealingQueue::new();
852 q.push(1);
853 q.push(2);
854 q.push(3);
855 assert_eq!(q.steal(), Some(1)); assert_eq!(q.steal(), Some(2));
857 assert_eq!(q.steal(), Some(3));
858 assert_eq!(q.steal(), None);
859 }
860
861 #[test]
862 fn test_wsq_len_and_empty() {
863 let mut q: WorkStealingQueue<&str> = WorkStealingQueue::new();
864 assert!(q.is_empty());
865 q.push("a");
866 q.push("b");
867 assert_eq!(q.len(), 2);
868 }
869
870 #[test]
873 fn test_backpressure_allows_up_to_max() {
874 let bp = BackpressureController::new(3);
875 assert!(bp.try_submit());
876 assert!(bp.try_submit());
877 assert!(bp.try_submit());
878 assert!(!bp.try_submit(), "should be rejected when at max");
879 }
880
881 #[test]
882 fn test_backpressure_complete_frees_slot() {
883 let bp = BackpressureController::new(1);
884 assert!(bp.try_submit());
885 assert!(!bp.try_submit()); bp.complete_one();
887 assert!(bp.try_submit()); }
889
890 #[test]
891 fn test_backpressure_pending_count() {
892 let bp = BackpressureController::new(10);
893 bp.try_submit();
894 bp.try_submit();
895 assert_eq!(bp.pending_count(), 2);
896 bp.complete_one();
897 assert_eq!(bp.pending_count(), 1);
898 }
899
900 #[test]
903 fn test_checkpoint_sequence_increments() {
904 let mut coord = CheckpointCoordinator::new();
905 let s1 = coord.take_checkpoint(1, b"state_a");
906 let s2 = coord.take_checkpoint(1, b"state_b");
907 assert_eq!(s1, 1);
908 assert_eq!(s2, 2);
909 }
910
911 #[test]
912 fn test_checkpoint_latest() {
913 let mut coord = CheckpointCoordinator::new();
914 coord.take_checkpoint(1, b"old");
915 coord.take_checkpoint(1, b"new");
916 let latest = coord.latest_checkpoint(1).expect("should have checkpoint");
917 assert_eq!(latest.state, b"new");
918 assert_eq!(latest.sequence, 2);
919 }
920
921 #[test]
922 fn test_checkpoint_independent_per_node() {
923 let mut coord = CheckpointCoordinator::new();
924 let s1 = coord.take_checkpoint(1, b"n1");
925 let s2 = coord.take_checkpoint(2, b"n2");
926 assert_eq!(s1, 1);
927 assert_eq!(s2, 1); assert_eq!(coord.checkpoint_count(), 2);
929 }
930
931 #[test]
934 fn test_hash_ring_get_node_returns_same_for_same_key() {
935 let mut ring = ConsistentHashRing::new(100);
936 ring.add_node(1);
937 ring.add_node(2);
938 ring.add_node(3);
939 let n1 = ring.get_node(b"my-key");
940 let n2 = ring.get_node(b"my-key");
941 assert_eq!(n1, n2, "same key should always map to same node");
942 }
943
944 #[test]
945 fn test_hash_ring_empty_returns_none() {
946 let ring = ConsistentHashRing::new(50);
947 assert!(ring.get_node(b"anything").is_none());
948 }
949
950 #[test]
951 fn test_hash_ring_single_node_owns_all() {
952 let mut ring = ConsistentHashRing::new(10);
953 ring.add_node(42);
954 assert_eq!(ring.get_node(b"k1"), Some(42));
955 assert_eq!(ring.get_node(b"k2"), Some(42));
956 }
957
958 #[test]
959 fn test_hash_ring_remove_node() {
960 let mut ring = ConsistentHashRing::new(10);
961 ring.add_node(1);
962 ring.add_node(2);
963 ring.remove_node(1);
964 assert_eq!(ring.node_count(), 1);
965 assert_eq!(ring.get_node(b"any"), Some(2));
966 }
967
968 #[test]
971 fn test_circuit_breaker_opens_after_threshold() {
972 let cb = DistributedCircuitBreaker::new(3, 60_000);
973 cb.call_failed();
974 assert!(!cb.is_open());
975 cb.call_failed();
976 assert!(!cb.is_open());
977 cb.call_failed(); assert!(cb.is_open());
979 }
980
981 #[test]
982 fn test_circuit_breaker_reset() {
983 let cb = DistributedCircuitBreaker::new(1, 60_000);
984 cb.call_failed();
985 assert!(cb.is_open());
986 cb.reset();
987 assert!(!cb.is_open());
988 }
989
990 #[test]
991 fn test_circuit_breaker_success_resets_count() {
992 let cb = DistributedCircuitBreaker::new(3, 60_000);
993 cb.call_failed();
994 cb.call_succeeded(); cb.call_failed();
996 assert!(!cb.is_open()); }
998
999 #[test]
1002 fn test_shard_map_insert_get() {
1003 let mut sm = SimpleShardMap::new(4);
1004 sm.insert(b"key1", b"value1".to_vec());
1005 assert_eq!(sm.get(b"key1"), Some(b"value1".as_slice()));
1006 assert_eq!(sm.get(b"missing"), None);
1007 }
1008
1009 #[test]
1010 fn test_shard_map_uniform_distribution() {
1011 let num_shards = 8u32;
1012 let mut sm = SimpleShardMap::new(num_shards);
1013
1014 for i in 0u32..1000 {
1016 let key = i.to_le_bytes();
1017 sm.insert(&key, key.to_vec());
1018 }
1019
1020 let counts = sm.shard_counts();
1021 let max = *counts.iter().max().expect("should have max");
1022 let min = *counts.iter().min().expect("should have min");
1023 assert!(
1025 max <= min * 2 + 1,
1026 "distribution too uneven: max={max} min={min}"
1027 );
1028 }
1029
1030 #[test]
1031 fn test_shard_assign_basic() {
1032 assert_eq!(shard_assign(0, 4), 0);
1033 assert_eq!(shard_assign(4, 4), 0);
1034 assert_eq!(shard_assign(5, 4), 1);
1035 assert_eq!(shard_assign(7, 4), 3);
1036 }
1037
1038 #[test]
1041 fn test_service_registry_register_and_discover() {
1042 let reg = ServiceRegistry::with_default_ttl();
1043 reg.register(1, "10.0.0.1:50052");
1044 assert_eq!(reg.discover(1), Some("10.0.0.1:50052".to_string()));
1045 }
1046
1047 #[test]
1048 fn test_service_registry_missing_returns_none() {
1049 let reg = ServiceRegistry::with_default_ttl();
1050 assert!(reg.discover(99).is_none());
1051 }
1052
1053 #[test]
1054 fn test_service_registry_expired() {
1055 let reg = ServiceRegistry::new(Duration::from_nanos(1));
1057 reg.register(1, "10.0.0.1:50052");
1058 std::thread::sleep(Duration::from_millis(2));
1060 assert!(
1061 reg.discover(1).is_none(),
1062 "expired entry should return None"
1063 );
1064 }
1065
1066 #[test]
1069 fn test_replication_manager_selects_factor_nodes() {
1070 let mut rm = ReplicationManager::new();
1071 let nodes = [1u64, 2, 3, 4, 5];
1072 let selected = rm.replicate(b"my-data", 3, &nodes);
1073 assert_eq!(selected.len(), 3);
1074 assert_eq!(selected, vec![1, 2, 3]);
1075 }
1076
1077 #[test]
1078 fn test_replication_manager_fewer_nodes_than_factor() {
1079 let mut rm = ReplicationManager::new();
1080 let nodes = [1u64, 2];
1081 let selected = rm.replicate(b"data", 5, &nodes);
1082 assert_eq!(selected.len(), 2, "should use all available nodes");
1083 }
1084
1085 #[test]
1086 fn test_replication_manager_lookup() {
1087 let mut rm = ReplicationManager::new();
1088 let nodes = [10u64, 20, 30];
1089 rm.replicate(b"key-data", 2, &nodes);
1090 let replicas = rm.replica_nodes(b"key-data").expect("should have replicas");
1091 assert_eq!(replicas, [10, 20]);
1092 }
1093}