1use std::collections::{HashMap, VecDeque};
47
48pub const MAX_PENDING_OPS: usize = 10_000;
52pub const MAX_LOG_ENTRIES: usize = 500;
54
55#[inline]
59pub fn xorshift64(state: &mut u64) -> u64 {
60 let mut x = *state;
61 x ^= x << 13;
62 x ^= x >> 7;
63 x ^= x << 17;
64 *state = x;
65 x
66}
67
68#[inline]
70pub fn fnv1a_64(data: &[u8]) -> u64 {
71 let mut h: u64 = 14_695_981_039_346_656_037;
72 for &b in data {
73 h ^= b as u64;
74 h = h.wrapping_mul(1_099_511_628_211);
75 }
76 h
77}
78
79#[inline]
81fn op_seed(cid: &[u8; 32], replica_id: &ReplicaId) -> u64 {
82 let mut combined = [0u8; 48];
83 combined[..32].copy_from_slice(cid);
84 combined[32..].copy_from_slice(replica_id);
85 fnv1a_64(&combined)
86}
87
88pub type ReplicaId = [u8; 16];
92
93#[derive(Debug, Clone, PartialEq, Eq)]
97pub struct ReplicaTarget {
98 pub id: ReplicaId,
100 pub endpoint: String,
102 pub priority: u32,
104 pub is_healthy: bool,
106 pub last_sync_ts: u64,
108 pub bytes_replicated: u64,
110}
111
112pub type SrmReplicaTarget = ReplicaTarget;
114
115#[derive(Debug, Clone, PartialEq, Eq)]
119pub enum ReplicationOp {
120 Put {
122 cid: [u8; 32],
124 data_len: usize,
126 ts: u64,
128 },
129 Delete {
131 cid: [u8; 32],
133 ts: u64,
135 },
136 Sync {
138 since_ts: u64,
140 },
141}
142
143pub type SrmReplicationOp = ReplicationOp;
145
146#[derive(Debug, Clone, PartialEq, Eq)]
150pub enum ReplicationPolicy {
151 Synchronous,
153 Asynchronous,
155 BestEffort,
157 QuorumWrite(usize),
159 PriorityFirst,
161}
162
163pub type SrmReplicationPolicy = ReplicationPolicy;
165
166#[derive(Debug, Clone)]
170pub struct SrmReplicationConfig {
171 pub policy: ReplicationPolicy,
173 pub min_replicas: usize,
175 pub max_replicas: usize,
177 pub retry_limit: usize,
179 pub batch_size: usize,
181}
182
183impl Default for SrmReplicationConfig {
184 fn default() -> Self {
185 Self {
186 policy: ReplicationPolicy::Asynchronous,
187 min_replicas: 2,
188 max_replicas: 10,
189 retry_limit: 3,
190 batch_size: 64,
191 }
192 }
193}
194
195#[derive(Debug, Clone)]
199pub struct ReplicationLogEntry {
200 pub ts: u64,
202 pub op_kind: String,
204 pub replica_id: ReplicaId,
206 pub success: bool,
208 pub bytes: usize,
210 pub error: Option<String>,
212}
213
214#[derive(Debug, Clone, Default)]
218pub struct SrmBatchResult {
219 pub ops_processed: usize,
221 pub success_count: usize,
223 pub failure_count: usize,
225 pub bytes_dispatched: usize,
227}
228
229#[derive(Debug, Clone, Default)]
233pub struct SrmReplicationStats {
234 pub total_ops_enqueued: u64,
236 pub total_ops_processed: u64,
238 pub total_successes: u64,
240 pub total_failures: u64,
242 pub total_bytes_replicated: u64,
244 pub pending_ops: usize,
246 pub healthy_replicas: usize,
248 pub unhealthy_replicas: usize,
250 pub log_entries: usize,
252 pub queue_overflow_count: u64,
254}
255
256#[derive(Debug, Clone, PartialEq, Eq)]
260pub enum SrmError {
261 DuplicateReplica(ReplicaId),
263 ReplicaNotFound(ReplicaId),
265 RegistryFull,
267 QueueFull,
269}
270
271impl std::fmt::Display for SrmError {
272 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
273 match self {
274 Self::DuplicateReplica(id) => write!(f, "replica already registered: {:?}", id),
275 Self::ReplicaNotFound(id) => write!(f, "replica not found: {:?}", id),
276 Self::RegistryFull => write!(f, "replica registry is full"),
277 Self::QueueFull => write!(f, "pending-operation queue is full"),
278 }
279 }
280}
281
282impl std::error::Error for SrmError {}
283
284#[derive(Debug)]
297pub struct StorageReplicationManager {
298 pub targets: HashMap<ReplicaId, ReplicaTarget>,
300 pub pending_ops: VecDeque<ReplicationOp>,
302 pub replication_log: Vec<ReplicationLogEntry>,
304 pub config: SrmReplicationConfig,
306 stats: SrmReplicationStats,
308 prng_state: u64,
310 clock_ms: u64,
312}
313
314impl StorageReplicationManager {
315 pub fn new(config: SrmReplicationConfig) -> Self {
322 let seed = fnv1a_64(&[
323 config.min_replicas as u8,
324 config.max_replicas as u8,
325 config.retry_limit as u8,
326 config.batch_size as u8,
327 ]);
328 Self {
329 targets: HashMap::new(),
330 pending_ops: VecDeque::new(),
331 replication_log: Vec::new(),
332 config,
333 stats: SrmReplicationStats::default(),
334 prng_state: seed | 1, clock_ms: 1_000, }
337 }
338
339 pub fn with_defaults() -> Self {
341 Self::new(SrmReplicationConfig::default())
342 }
343
344 pub fn add_replica(&mut self, target: ReplicaTarget) -> Result<(), SrmError> {
352 if self.targets.len() >= self.config.max_replicas {
353 return Err(SrmError::RegistryFull);
354 }
355 if self.targets.contains_key(&target.id) {
356 return Err(SrmError::DuplicateReplica(target.id));
357 }
358 self.targets.insert(target.id, target);
359 Ok(())
360 }
361
362 pub fn remove_replica(&mut self, id: ReplicaId) -> Result<ReplicaTarget, SrmError> {
367 self.targets
368 .remove(&id)
369 .ok_or(SrmError::ReplicaNotFound(id))
370 }
371
372 pub fn mark_unhealthy(&mut self, id: ReplicaId) -> Result<(), SrmError> {
377 let target = self
378 .targets
379 .get_mut(&id)
380 .ok_or(SrmError::ReplicaNotFound(id))?;
381 target.is_healthy = false;
382 Ok(())
383 }
384
385 pub fn mark_healthy(&mut self, id: ReplicaId) -> Result<(), SrmError> {
390 let target = self
391 .targets
392 .get_mut(&id)
393 .ok_or(SrmError::ReplicaNotFound(id))?;
394 target.is_healthy = true;
395 Ok(())
396 }
397
398 pub fn set_priority(&mut self, id: ReplicaId, priority: u32) -> Result<(), SrmError> {
403 let target = self
404 .targets
405 .get_mut(&id)
406 .ok_or(SrmError::ReplicaNotFound(id))?;
407 target.priority = priority;
408 Ok(())
409 }
410
411 pub fn enqueue_put(&mut self, cid: [u8; 32], data_len: usize) -> bool {
417 self.try_enqueue(ReplicationOp::Put {
418 cid,
419 data_len,
420 ts: self.clock_ms,
421 })
422 }
423
424 pub fn enqueue_delete(&mut self, cid: [u8; 32]) -> bool {
428 self.try_enqueue(ReplicationOp::Delete {
429 cid,
430 ts: self.clock_ms,
431 })
432 }
433
434 pub fn enqueue_sync(&mut self, since_ts: u64) -> bool {
438 self.try_enqueue(ReplicationOp::Sync { since_ts })
439 }
440
441 fn try_enqueue(&mut self, op: ReplicationOp) -> bool {
443 if self.pending_ops.len() >= MAX_PENDING_OPS {
444 self.stats.queue_overflow_count += 1;
445 return false;
446 }
447 self.pending_ops.push_back(op);
448 self.stats.total_ops_enqueued += 1;
449 self.clock_ms = self.clock_ms.wrapping_add(1);
450 true
451 }
452
453 pub fn process_batch(&mut self) -> SrmBatchResult {
463 let limit = self.config.batch_size.min(self.pending_ops.len());
464 let mut result = SrmBatchResult::default();
465
466 let mut ordered_ids: Vec<(u32, ReplicaId)> = self
468 .targets
469 .values()
470 .filter(|t| t.is_healthy)
471 .map(|t| (t.priority, t.id))
472 .collect();
473
474 match &self.config.policy {
475 ReplicationPolicy::PriorityFirst => {
476 ordered_ids.sort_by_key(|(p, _)| *p);
478 }
479 _ => {
480 ordered_ids.sort_by_key(|(_, id)| *id);
482 }
483 }
484
485 let replica_ids: Vec<ReplicaId> = ordered_ids.into_iter().map(|(_, id)| id).collect();
486
487 for _ in 0..limit {
488 let op = match self.pending_ops.pop_front() {
489 Some(o) => o,
490 None => break,
491 };
492 result.ops_processed += 1;
493 self.stats.total_ops_processed += 1;
494
495 let (op_kind, cid_ref, data_len_val) = match &op {
496 ReplicationOp::Put { cid, data_len, .. } => ("Put", *cid, *data_len),
497 ReplicationOp::Delete { cid, .. } => ("Delete", *cid, 0usize),
498 ReplicationOp::Sync { .. } => {
499 let ts = if let ReplicationOp::Sync { since_ts } = &op {
501 *since_ts
502 } else {
503 0
504 };
505 let mut cid = [0u8; 32];
506 let ts_bytes = ts.to_le_bytes();
507 cid[..8].copy_from_slice(&ts_bytes);
508 ("Sync", cid, 0usize)
509 }
510 };
511
512 let mut quorum_satisfied = false;
513 let quorum_needed = if let ReplicationPolicy::QuorumWrite(n) = self.config.policy {
514 n
515 } else {
516 0
517 };
518 let mut quorum_count = 0usize;
519
520 for &replica_id in &replica_ids {
521 let mut seed = op_seed(&cid_ref, &replica_id);
523 seed ^= self.prng_state;
524 self.prng_state = xorshift64(&mut self.prng_state);
526
527 let rnd = xorshift64(&mut seed);
528
529 let success = match op_kind {
531 "Sync" => (rnd & 0x3) != 0,
532 _ => (rnd & 0x7) != 0,
533 };
534
535 let bytes_for_entry = if success && op_kind == "Put" {
536 data_len_val
537 } else {
538 0
539 };
540
541 let log_entry = ReplicationLogEntry {
542 ts: self.clock_ms,
543 op_kind: op_kind.to_string(),
544 replica_id,
545 success,
546 bytes: bytes_for_entry,
547 error: if success {
548 None
549 } else {
550 Some(format!(
551 "simulated network failure for replica {:?}",
552 replica_id
553 ))
554 },
555 };
556
557 if success {
558 result.success_count += 1;
559 result.bytes_dispatched += bytes_for_entry;
560 self.stats.total_successes += 1;
561 self.stats.total_bytes_replicated += bytes_for_entry as u64;
562 quorum_count += 1;
563
564 if let Some(target) = self.targets.get_mut(&replica_id) {
566 target.last_sync_ts = self.clock_ms;
567 target.bytes_replicated += bytes_for_entry as u64;
568 }
569 } else {
570 result.failure_count += 1;
571 self.stats.total_failures += 1;
572
573 if matches!(self.config.policy, ReplicationPolicy::Synchronous) {
576 if let Some(target) = self.targets.get_mut(&replica_id) {
577 target.is_healthy = false;
578 }
579 }
580 }
581
582 self.append_log(log_entry);
584
585 if quorum_count >= quorum_needed && quorum_needed > 0 {
587 quorum_satisfied = true;
588 }
589
590 if matches!(self.config.policy, ReplicationPolicy::PriorityFirst) && !success {
592 break;
593 }
594 }
595
596 let _ = quorum_satisfied;
598
599 self.clock_ms = self.clock_ms.wrapping_add(1);
600 }
601
602 result
603 }
604
605 fn append_log(&mut self, entry: ReplicationLogEntry) {
607 if self.replication_log.len() >= MAX_LOG_ENTRIES {
608 self.replication_log.remove(0);
609 }
610 self.replication_log.push(entry);
611 }
612
613 pub fn check_quorum(&self, required: usize) -> bool {
617 self.healthy_replica_count() >= required
618 }
619
620 pub fn recovery_plan(&self) -> Vec<ReplicaId> {
622 self.targets
623 .values()
624 .filter(|t| !t.is_healthy)
625 .map(|t| t.id)
626 .collect()
627 }
628
629 pub fn replication_stats(&self) -> SrmReplicationStats {
633 let (healthy, unhealthy) = self.replica_health_counts();
634 SrmReplicationStats {
635 total_ops_enqueued: self.stats.total_ops_enqueued,
636 total_ops_processed: self.stats.total_ops_processed,
637 total_successes: self.stats.total_successes,
638 total_failures: self.stats.total_failures,
639 total_bytes_replicated: self.stats.total_bytes_replicated,
640 pending_ops: self.pending_ops.len(),
641 healthy_replicas: healthy,
642 unhealthy_replicas: unhealthy,
643 log_entries: self.replication_log.len(),
644 queue_overflow_count: self.stats.queue_overflow_count,
645 }
646 }
647
648 pub fn healthy_replica_count(&self) -> usize {
652 self.targets.values().filter(|t| t.is_healthy).count()
653 }
654
655 pub fn replica_count(&self) -> usize {
657 self.targets.len()
658 }
659
660 pub fn replica_health_counts(&self) -> (usize, usize) {
662 let healthy = self.targets.values().filter(|t| t.is_healthy).count();
663 let unhealthy = self.targets.len() - healthy;
664 (healthy, unhealthy)
665 }
666
667 pub fn get_replica(&self, id: &ReplicaId) -> Option<&ReplicaTarget> {
669 self.targets.get(id)
670 }
671
672 pub fn get_replica_mut(&mut self, id: &ReplicaId) -> Option<&mut ReplicaTarget> {
674 self.targets.get_mut(id)
675 }
676
677 pub fn pending_count(&self) -> usize {
679 self.pending_ops.len()
680 }
681
682 pub fn log_entries_for_kind(&self, kind: &str) -> Vec<&ReplicationLogEntry> {
684 self.replication_log
685 .iter()
686 .filter(|e| e.op_kind == kind)
687 .collect()
688 }
689
690 pub fn failed_log_entries(&self) -> Vec<&ReplicationLogEntry> {
692 self.replication_log.iter().filter(|e| !e.success).collect()
693 }
694
695 pub fn drain_pending(&mut self) -> usize {
697 let n = self.pending_ops.len();
698 self.pending_ops.clear();
699 n
700 }
701
702 pub fn clear_log(&mut self) {
704 self.replication_log.clear();
705 }
706
707 pub fn reset_stats(&mut self) {
709 self.stats = SrmReplicationStats::default();
710 }
711
712 pub fn clock(&self) -> u64 {
714 self.clock_ms
715 }
716
717 pub fn total_bytes_across_targets(&self) -> u64 {
719 self.targets.values().map(|t| t.bytes_replicated).sum()
720 }
721
722 pub fn most_active_replica(&self) -> Option<&ReplicaTarget> {
724 self.targets.values().max_by_key(|t| t.bytes_replicated)
725 }
726
727 pub fn least_active_replica(&self) -> Option<&ReplicaTarget> {
729 self.targets.values().min_by_key(|t| t.bytes_replicated)
730 }
731
732 pub fn has_minimum_quorum(&self) -> bool {
734 self.check_quorum(self.config.min_replicas)
735 }
736
737 pub fn replicas_by_priority(&self) -> Vec<ReplicaId> {
739 let mut pairs: Vec<(u32, ReplicaId)> =
740 self.targets.values().map(|t| (t.priority, t.id)).collect();
741 pairs.sort_by_key(|(p, _)| *p);
742 pairs.into_iter().map(|(_, id)| id).collect()
743 }
744
745 pub fn healthy_replica_ids(&self) -> Vec<ReplicaId> {
747 self.targets
748 .values()
749 .filter(|t| t.is_healthy)
750 .map(|t| t.id)
751 .collect()
752 }
753
754 pub fn unhealthy_replica_ids(&self) -> Vec<ReplicaId> {
756 self.targets
757 .values()
758 .filter(|t| !t.is_healthy)
759 .map(|t| t.id)
760 .collect()
761 }
762
763 pub fn last_log_entry(&self) -> Option<&ReplicationLogEntry> {
765 self.replication_log.last()
766 }
767
768 pub fn log_success_count(&self) -> usize {
770 self.replication_log.iter().filter(|e| e.success).count()
771 }
772
773 pub fn log_failure_count(&self) -> usize {
775 self.replication_log.iter().filter(|e| !e.success).count()
776 }
777
778 pub fn log_success_rate(&self) -> f64 {
780 let total = self.replication_log.len();
781 if total == 0 {
782 return 0.0;
783 }
784 self.log_success_count() as f64 / total as f64
785 }
786
787 pub fn log_total_bytes(&self) -> usize {
789 self.replication_log.iter().map(|e| e.bytes).sum()
790 }
791
792 pub fn log_entries_for_replica(&self, id: &ReplicaId) -> Vec<&ReplicationLogEntry> {
794 self.replication_log
795 .iter()
796 .filter(|e| &e.replica_id == id)
797 .collect()
798 }
799}
800
801pub type SrmStorageReplicationManager = StorageReplicationManager;
805
806#[cfg(test)]
813mod tests {
814 use super::*;
815
816 fn make_config(policy: ReplicationPolicy) -> SrmReplicationConfig {
819 SrmReplicationConfig {
820 policy,
821 min_replicas: 2,
822 max_replicas: 8,
823 retry_limit: 2,
824 batch_size: 16,
825 }
826 }
827
828 fn make_target(id_byte: u8) -> ReplicaTarget {
829 ReplicaTarget {
830 id: [id_byte; 16],
831 endpoint: format!("10.0.0.{}:4001", id_byte),
832 priority: id_byte as u32 * 10,
833 is_healthy: true,
834 last_sync_ts: 0,
835 bytes_replicated: 0,
836 }
837 }
838
839 fn make_mgr_with_replicas(n: u8) -> StorageReplicationManager {
840 let mut mgr = StorageReplicationManager::new(make_config(ReplicationPolicy::Asynchronous));
841 for i in 1..=n {
842 mgr.add_replica(make_target(i)).unwrap();
843 }
844 mgr
845 }
846
847 #[test]
850 fn test_xorshift64_non_zero() {
851 let mut s = 12345u64;
852 let v = xorshift64(&mut s);
853 assert_ne!(v, 0);
854 }
855
856 #[test]
857 fn test_xorshift64_state_mutated() {
858 let mut s = 99u64;
859 let initial = s;
860 xorshift64(&mut s);
861 assert_ne!(s, initial);
862 }
863
864 #[test]
865 fn test_xorshift64_deterministic() {
866 let mut s1 = 42u64;
867 let mut s2 = 42u64;
868 assert_eq!(xorshift64(&mut s1), xorshift64(&mut s2));
869 }
870
871 #[test]
872 fn test_xorshift64_sequence_no_repeat() {
873 let mut s = 7u64;
874 let a = xorshift64(&mut s);
875 let b = xorshift64(&mut s);
876 assert_ne!(a, b);
877 }
878
879 #[test]
882 fn test_fnv1a_empty() {
883 let h = fnv1a_64(&[]);
884 assert_eq!(h, 14_695_981_039_346_656_037u64);
885 }
886
887 #[test]
888 fn test_fnv1a_known() {
889 let h = fnv1a_64(b"hello");
891 assert_ne!(h, 0);
892 assert_ne!(h, 14_695_981_039_346_656_037u64);
893 }
894
895 #[test]
896 fn test_fnv1a_different_inputs_differ() {
897 assert_ne!(fnv1a_64(b"abc"), fnv1a_64(b"xyz"));
898 }
899
900 #[test]
903 fn test_new_empty_manager() {
904 let mgr = StorageReplicationManager::with_defaults();
905 assert_eq!(mgr.replica_count(), 0);
906 assert_eq!(mgr.pending_count(), 0);
907 }
908
909 #[test]
910 fn test_default_config_fields() {
911 let cfg = SrmReplicationConfig::default();
912 assert_eq!(cfg.min_replicas, 2);
913 assert_eq!(cfg.max_replicas, 10);
914 assert_eq!(cfg.batch_size, 64);
915 }
916
917 #[test]
918 fn test_new_seeds_prng_nonzero() {
919 let mgr = StorageReplicationManager::with_defaults();
920 assert_ne!(mgr.prng_state, 0);
921 }
922
923 #[test]
926 fn test_add_replica_success() {
927 let mut mgr = StorageReplicationManager::with_defaults();
928 assert!(mgr.add_replica(make_target(1)).is_ok());
929 assert_eq!(mgr.replica_count(), 1);
930 }
931
932 #[test]
933 fn test_add_replica_duplicate_error() {
934 let mut mgr = StorageReplicationManager::with_defaults();
935 mgr.add_replica(make_target(1)).unwrap();
936 let err = mgr.add_replica(make_target(1));
937 assert!(matches!(err, Err(SrmError::DuplicateReplica(_))));
938 }
939
940 #[test]
941 fn test_add_replica_registry_full() {
942 let mut mgr = StorageReplicationManager::new(SrmReplicationConfig {
943 max_replicas: 2,
944 ..Default::default()
945 });
946 mgr.add_replica(make_target(1)).unwrap();
947 mgr.add_replica(make_target(2)).unwrap();
948 let err = mgr.add_replica(make_target(3));
949 assert!(matches!(err, Err(SrmError::RegistryFull)));
950 }
951
952 #[test]
953 fn test_add_multiple_replicas() {
954 let mgr = make_mgr_with_replicas(5);
955 assert_eq!(mgr.replica_count(), 5);
956 }
957
958 #[test]
961 fn test_remove_replica_success() {
962 let mut mgr = make_mgr_with_replicas(3);
963 let id = [1u8; 16];
964 let removed = mgr.remove_replica(id).unwrap();
965 assert_eq!(removed.id, id);
966 assert_eq!(mgr.replica_count(), 2);
967 }
968
969 #[test]
970 fn test_remove_replica_not_found() {
971 let mut mgr = StorageReplicationManager::with_defaults();
972 let err = mgr.remove_replica([99u8; 16]);
973 assert!(matches!(err, Err(SrmError::ReplicaNotFound(_))));
974 }
975
976 #[test]
979 fn test_mark_unhealthy() {
980 let mut mgr = make_mgr_with_replicas(2);
981 let id = [1u8; 16];
982 mgr.mark_unhealthy(id).unwrap();
983 assert!(!mgr.get_replica(&id).unwrap().is_healthy);
984 }
985
986 #[test]
987 fn test_mark_healthy_restores() {
988 let mut mgr = make_mgr_with_replicas(2);
989 let id = [1u8; 16];
990 mgr.mark_unhealthy(id).unwrap();
991 mgr.mark_healthy(id).unwrap();
992 assert!(mgr.get_replica(&id).unwrap().is_healthy);
993 }
994
995 #[test]
996 fn test_mark_unhealthy_not_found() {
997 let mut mgr = StorageReplicationManager::with_defaults();
998 assert!(matches!(
999 mgr.mark_unhealthy([0u8; 16]),
1000 Err(SrmError::ReplicaNotFound(_))
1001 ));
1002 }
1003
1004 #[test]
1007 fn test_set_priority() {
1008 let mut mgr = make_mgr_with_replicas(1);
1009 let id = [1u8; 16];
1010 mgr.set_priority(id, 999).unwrap();
1011 assert_eq!(mgr.get_replica(&id).unwrap().priority, 999);
1012 }
1013
1014 #[test]
1015 fn test_set_priority_not_found() {
1016 let mut mgr = StorageReplicationManager::with_defaults();
1017 assert!(mgr.set_priority([0u8; 16], 5).is_err());
1018 }
1019
1020 #[test]
1023 fn test_enqueue_put_increments_pending() {
1024 let mut mgr = make_mgr_with_replicas(1);
1025 let pushed = mgr.enqueue_put([0u8; 32], 512);
1026 assert!(pushed);
1027 assert_eq!(mgr.pending_count(), 1);
1028 }
1029
1030 #[test]
1031 fn test_enqueue_delete() {
1032 let mut mgr = make_mgr_with_replicas(1);
1033 assert!(mgr.enqueue_delete([1u8; 32]));
1034 assert_eq!(mgr.pending_count(), 1);
1035 }
1036
1037 #[test]
1038 fn test_enqueue_sync() {
1039 let mut mgr = make_mgr_with_replicas(1);
1040 assert!(mgr.enqueue_sync(0));
1041 assert_eq!(mgr.pending_count(), 1);
1042 }
1043
1044 #[test]
1045 fn test_enqueue_increments_stats() {
1046 let mut mgr = make_mgr_with_replicas(1);
1047 mgr.enqueue_put([0u8; 32], 100);
1048 mgr.enqueue_delete([1u8; 32]);
1049 let stats = mgr.replication_stats();
1050 assert_eq!(stats.total_ops_enqueued, 2);
1051 }
1052
1053 #[test]
1054 fn test_enqueue_overflow_returns_false() {
1055 let mut mgr = StorageReplicationManager::new(SrmReplicationConfig {
1056 batch_size: 1,
1057 max_replicas: 10,
1058 ..Default::default()
1059 });
1060 for _ in 0..MAX_PENDING_OPS {
1062 mgr.enqueue_put([0u8; 32], 1);
1063 }
1064 let result = mgr.enqueue_put([1u8; 32], 1);
1065 assert!(!result);
1066 let stats = mgr.replication_stats();
1067 assert_eq!(stats.queue_overflow_count, 1);
1068 }
1069
1070 #[test]
1073 fn test_process_batch_drains_ops() {
1074 let mut mgr = make_mgr_with_replicas(3);
1075 for i in 0..10u8 {
1076 mgr.enqueue_put([i; 32], 128);
1077 }
1078 let result = mgr.process_batch();
1079 assert!(result.ops_processed <= 16);
1080 assert!(mgr.pending_count() <= 10);
1081 }
1082
1083 #[test]
1084 fn test_process_batch_empty_queue() {
1085 let mut mgr = make_mgr_with_replicas(2);
1086 let result = mgr.process_batch();
1087 assert_eq!(result.ops_processed, 0);
1088 assert_eq!(result.success_count, 0);
1089 }
1090
1091 #[test]
1092 fn test_process_batch_no_replicas() {
1093 let mut mgr = StorageReplicationManager::with_defaults();
1094 mgr.enqueue_put([0u8; 32], 512);
1095 let result = mgr.process_batch();
1096 assert_eq!(result.ops_processed, 1);
1098 assert_eq!(result.success_count, 0);
1099 assert_eq!(result.failure_count, 0);
1100 }
1101
1102 #[test]
1103 fn test_process_batch_updates_log() {
1104 let mut mgr = make_mgr_with_replicas(2);
1105 mgr.enqueue_put([0u8; 32], 256);
1106 mgr.process_batch();
1107 assert!(!mgr.replication_log.is_empty());
1108 }
1109
1110 #[test]
1111 fn test_process_batch_bytes_dispatched() {
1112 let mut mgr = make_mgr_with_replicas(3);
1113 mgr.enqueue_put([0xAAu8; 32], 1024);
1114 let result = mgr.process_batch();
1115 let _ = result.bytes_dispatched;
1118 }
1119
1120 #[test]
1121 fn test_process_batch_stats_updated() {
1122 let mut mgr = make_mgr_with_replicas(2);
1123 mgr.enqueue_put([0u8; 32], 100);
1124 mgr.process_batch();
1125 let stats = mgr.replication_stats();
1126 assert_eq!(stats.total_ops_processed, 1);
1127 }
1128
1129 #[test]
1132 fn test_process_delete_op() {
1133 let mut mgr = make_mgr_with_replicas(2);
1134 mgr.enqueue_delete([0xBBu8; 32]);
1135 let result = mgr.process_batch();
1136 assert_eq!(result.ops_processed, 1);
1137 }
1138
1139 #[test]
1140 fn test_process_sync_op() {
1141 let mut mgr = make_mgr_with_replicas(2);
1142 mgr.enqueue_sync(500);
1143 let result = mgr.process_batch();
1144 assert_eq!(result.ops_processed, 1);
1145 }
1146
1147 #[test]
1150 fn test_log_capped_at_max() {
1151 let mut mgr = StorageReplicationManager::new(SrmReplicationConfig {
1153 batch_size: 1000,
1154 max_replicas: 10,
1155 ..Default::default()
1156 });
1157 mgr.add_replica(make_target(1)).unwrap();
1158
1159 for i in 0..600u16 {
1160 mgr.enqueue_put([(i & 0xFF) as u8; 32], 10);
1161 }
1162 mgr.process_batch();
1163
1164 assert!(mgr.replication_log.len() <= MAX_LOG_ENTRIES);
1165 }
1166
1167 #[test]
1168 fn test_log_entry_fields() {
1169 let mut mgr = make_mgr_with_replicas(1);
1170 mgr.enqueue_put([7u8; 32], 2048);
1171 mgr.process_batch();
1172 let entry = mgr.last_log_entry().unwrap();
1173 assert_eq!(entry.op_kind, "Put");
1174 assert_eq!(entry.replica_id, [1u8; 16]);
1175 }
1176
1177 #[test]
1180 fn test_quorum_met() {
1181 let mgr = make_mgr_with_replicas(3);
1182 assert!(mgr.check_quorum(2));
1183 assert!(mgr.check_quorum(3));
1184 }
1185
1186 #[test]
1187 fn test_quorum_not_met() {
1188 let mgr = make_mgr_with_replicas(1);
1189 assert!(!mgr.check_quorum(2));
1190 }
1191
1192 #[test]
1193 fn test_quorum_zero_always_met() {
1194 let mgr = StorageReplicationManager::with_defaults();
1195 assert!(mgr.check_quorum(0));
1196 }
1197
1198 #[test]
1199 fn test_has_minimum_quorum() {
1200 let mgr = make_mgr_with_replicas(3);
1201 assert!(mgr.has_minimum_quorum()); }
1203
1204 #[test]
1207 fn test_recovery_plan_empty_when_all_healthy() {
1208 let mgr = make_mgr_with_replicas(3);
1209 assert!(mgr.recovery_plan().is_empty());
1210 }
1211
1212 #[test]
1213 fn test_recovery_plan_lists_unhealthy() {
1214 let mut mgr = make_mgr_with_replicas(3);
1215 mgr.mark_unhealthy([1u8; 16]).unwrap();
1216 mgr.mark_unhealthy([2u8; 16]).unwrap();
1217 let plan = mgr.recovery_plan();
1218 assert_eq!(plan.len(), 2);
1219 }
1220
1221 #[test]
1222 fn test_recovery_plan_excludes_healthy() {
1223 let mut mgr = make_mgr_with_replicas(3);
1224 mgr.mark_unhealthy([1u8; 16]).unwrap();
1225 let plan = mgr.recovery_plan();
1226 assert!(!plan.contains(&[2u8; 16]));
1227 assert!(!plan.contains(&[3u8; 16]));
1228 }
1229
1230 #[test]
1233 fn test_stats_initial_zeros() {
1234 let mgr = StorageReplicationManager::with_defaults();
1235 let s = mgr.replication_stats();
1236 assert_eq!(s.total_ops_enqueued, 0);
1237 assert_eq!(s.total_ops_processed, 0);
1238 assert_eq!(s.total_bytes_replicated, 0);
1239 }
1240
1241 #[test]
1242 fn test_stats_healthy_unhealthy_counts() {
1243 let mut mgr = make_mgr_with_replicas(4);
1244 mgr.mark_unhealthy([1u8; 16]).unwrap();
1245 let s = mgr.replication_stats();
1246 assert_eq!(s.healthy_replicas, 3);
1247 assert_eq!(s.unhealthy_replicas, 1);
1248 }
1249
1250 #[test]
1251 fn test_stats_log_entries_count() {
1252 let mut mgr = make_mgr_with_replicas(1);
1253 mgr.enqueue_put([0u8; 32], 100);
1254 mgr.process_batch();
1255 let s = mgr.replication_stats();
1256 assert!(s.log_entries > 0);
1257 }
1258
1259 #[test]
1262 fn test_replicas_by_priority_sorted() {
1263 let mut mgr = StorageReplicationManager::new(make_config(ReplicationPolicy::PriorityFirst));
1264 mgr.add_replica(ReplicaTarget {
1265 priority: 30,
1266 ..make_target(1)
1267 })
1268 .unwrap();
1269 mgr.add_replica(ReplicaTarget {
1270 priority: 10,
1271 ..make_target(2)
1272 })
1273 .unwrap();
1274 mgr.add_replica(ReplicaTarget {
1275 priority: 20,
1276 ..make_target(3)
1277 })
1278 .unwrap();
1279 let ids = mgr.replicas_by_priority();
1280 assert_eq!(ids[0], [2u8; 16]);
1282 assert_eq!(ids[2], [1u8; 16]);
1283 }
1284
1285 #[test]
1288 fn test_healthy_replica_ids() {
1289 let mut mgr = make_mgr_with_replicas(3);
1290 mgr.mark_unhealthy([1u8; 16]).unwrap();
1291 let ids = mgr.healthy_replica_ids();
1292 assert_eq!(ids.len(), 2);
1293 assert!(!ids.contains(&[1u8; 16]));
1294 }
1295
1296 #[test]
1297 fn test_unhealthy_replica_ids() {
1298 let mut mgr = make_mgr_with_replicas(3);
1299 mgr.mark_unhealthy([2u8; 16]).unwrap();
1300 let ids = mgr.unhealthy_replica_ids();
1301 assert_eq!(ids.len(), 1);
1302 assert!(ids.contains(&[2u8; 16]));
1303 }
1304
1305 #[test]
1308 fn test_most_active_replica_none_when_empty() {
1309 let mgr = StorageReplicationManager::with_defaults();
1310 assert!(mgr.most_active_replica().is_none());
1311 }
1312
1313 #[test]
1314 fn test_least_active_replica_none_when_empty() {
1315 let mgr = StorageReplicationManager::with_defaults();
1316 assert!(mgr.least_active_replica().is_none());
1317 }
1318
1319 #[test]
1320 fn test_total_bytes_across_targets_zero_initially() {
1321 let mgr = make_mgr_with_replicas(3);
1322 assert_eq!(mgr.total_bytes_across_targets(), 0);
1323 }
1324
1325 #[test]
1328 fn test_drain_pending() {
1329 let mut mgr = make_mgr_with_replicas(1);
1330 mgr.enqueue_put([0u8; 32], 100);
1331 mgr.enqueue_delete([1u8; 32]);
1332 let n = mgr.drain_pending();
1333 assert_eq!(n, 2);
1334 assert_eq!(mgr.pending_count(), 0);
1335 }
1336
1337 #[test]
1338 fn test_clear_log() {
1339 let mut mgr = make_mgr_with_replicas(1);
1340 mgr.enqueue_put([0u8; 32], 100);
1341 mgr.process_batch();
1342 assert!(!mgr.replication_log.is_empty());
1343 mgr.clear_log();
1344 assert!(mgr.replication_log.is_empty());
1345 }
1346
1347 #[test]
1348 fn test_reset_stats() {
1349 let mut mgr = make_mgr_with_replicas(1);
1350 mgr.enqueue_put([0u8; 32], 100);
1351 mgr.process_batch();
1352 mgr.reset_stats();
1353 let s = mgr.replication_stats();
1354 assert_eq!(s.total_ops_enqueued, 0);
1355 assert_eq!(s.total_ops_processed, 0);
1356 }
1357
1358 #[test]
1361 fn test_log_entries_for_kind() {
1362 let mut mgr = make_mgr_with_replicas(2);
1363 mgr.enqueue_put([0u8; 32], 100);
1364 mgr.enqueue_delete([1u8; 32]);
1365 mgr.process_batch();
1366 let puts = mgr.log_entries_for_kind("Put");
1367 let deletes = mgr.log_entries_for_kind("Delete");
1368 assert!(!puts.is_empty());
1369 assert!(!deletes.is_empty());
1370 }
1371
1372 #[test]
1373 fn test_failed_log_entries_type() {
1374 let mgr = make_mgr_with_replicas(1);
1375 let failed = mgr.failed_log_entries();
1376 for e in &failed {
1377 assert!(!e.success);
1378 }
1379 }
1380
1381 #[test]
1382 fn test_log_success_rate_empty() {
1383 let mgr = StorageReplicationManager::with_defaults();
1384 assert_eq!(mgr.log_success_rate(), 0.0);
1385 }
1386
1387 #[test]
1388 fn test_log_success_rate_after_batch() {
1389 let mut mgr = make_mgr_with_replicas(4);
1390 for i in 0..8u8 {
1391 mgr.enqueue_put([i; 32], 256);
1392 }
1393 mgr.process_batch();
1394 let rate = mgr.log_success_rate();
1395 assert!((0.0..=1.0).contains(&rate));
1396 }
1397
1398 #[test]
1399 fn test_log_total_bytes_nonnegative() {
1400 let mut mgr = make_mgr_with_replicas(3);
1401 mgr.enqueue_put([0u8; 32], 4096);
1402 mgr.process_batch();
1403 let _ = mgr.log_total_bytes();
1405 }
1406
1407 #[test]
1408 fn test_log_entries_for_replica() {
1409 let mut mgr = make_mgr_with_replicas(2);
1410 mgr.enqueue_put([0u8; 32], 512);
1411 mgr.process_batch();
1412 let id = [1u8; 16];
1413 let entries = mgr.log_entries_for_replica(&id);
1414 for e in &entries {
1415 assert_eq!(e.replica_id, id);
1416 }
1417 }
1418
1419 #[test]
1422 fn test_policy_best_effort() {
1423 let mut mgr = StorageReplicationManager::new(make_config(ReplicationPolicy::BestEffort));
1424 mgr.add_replica(make_target(1)).unwrap();
1425 mgr.add_replica(make_target(2)).unwrap();
1426 mgr.enqueue_put([0u8; 32], 100);
1427 let result = mgr.process_batch();
1428 assert_eq!(result.ops_processed, 1);
1429 }
1430
1431 #[test]
1432 fn test_policy_quorum_write() {
1433 let mut mgr =
1434 StorageReplicationManager::new(make_config(ReplicationPolicy::QuorumWrite(2)));
1435 for i in 1..=4 {
1436 mgr.add_replica(make_target(i)).unwrap();
1437 }
1438 mgr.enqueue_put([0u8; 32], 512);
1439 let result = mgr.process_batch();
1440 assert_eq!(result.ops_processed, 1);
1441 }
1442
1443 #[test]
1444 fn test_policy_synchronous() {
1445 let mut mgr = StorageReplicationManager::new(make_config(ReplicationPolicy::Synchronous));
1446 mgr.add_replica(make_target(1)).unwrap();
1447 mgr.enqueue_put([0u8; 32], 256);
1448 let result = mgr.process_batch();
1449 assert_eq!(result.ops_processed, 1);
1450 }
1451
1452 #[test]
1453 fn test_policy_priority_first() {
1454 let mut mgr = StorageReplicationManager::new(make_config(ReplicationPolicy::PriorityFirst));
1455 mgr.add_replica(ReplicaTarget {
1456 priority: 5,
1457 ..make_target(1)
1458 })
1459 .unwrap();
1460 mgr.add_replica(ReplicaTarget {
1461 priority: 1,
1462 ..make_target(2)
1463 })
1464 .unwrap();
1465 mgr.enqueue_put([0u8; 32], 256);
1466 let result = mgr.process_batch();
1467 assert_eq!(result.ops_processed, 1);
1468 }
1469
1470 #[test]
1473 fn test_clock_advances_on_enqueue() {
1474 let mut mgr = StorageReplicationManager::with_defaults();
1475 let t0 = mgr.clock();
1476 mgr.enqueue_put([0u8; 32], 1);
1477 assert!(mgr.clock() > t0);
1478 }
1479
1480 #[test]
1481 fn test_clock_advances_on_process() {
1482 let mut mgr = make_mgr_with_replicas(1);
1483 mgr.enqueue_put([0u8; 32], 1);
1484 let t0 = mgr.clock();
1485 mgr.process_batch();
1486 assert!(mgr.clock() > t0);
1487 }
1488
1489 #[test]
1492 fn test_replica_health_counts() {
1493 let mut mgr = make_mgr_with_replicas(4);
1494 mgr.mark_unhealthy([1u8; 16]).unwrap();
1495 mgr.mark_unhealthy([2u8; 16]).unwrap();
1496 let (h, u) = mgr.replica_health_counts();
1497 assert_eq!(h, 2);
1498 assert_eq!(u, 2);
1499 }
1500
1501 #[test]
1504 fn test_stats_pending_ops_matches_queue_len() {
1505 let mut mgr = make_mgr_with_replicas(2);
1506 mgr.enqueue_put([0u8; 32], 50);
1507 mgr.enqueue_put([1u8; 32], 60);
1508 let s = mgr.replication_stats();
1509 assert_eq!(s.pending_ops, mgr.pending_count());
1510 }
1511
1512 #[test]
1515 fn test_error_display_duplicate() {
1516 let e = SrmError::DuplicateReplica([0u8; 16]);
1517 let s = e.to_string();
1518 assert!(s.contains("already registered"));
1519 }
1520
1521 #[test]
1522 fn test_error_display_not_found() {
1523 let e = SrmError::ReplicaNotFound([0u8; 16]);
1524 let s = e.to_string();
1525 assert!(s.contains("not found"));
1526 }
1527
1528 #[test]
1529 fn test_error_display_registry_full() {
1530 let s = SrmError::RegistryFull.to_string();
1531 assert!(s.contains("full"));
1532 }
1533
1534 #[test]
1535 fn test_error_display_queue_full() {
1536 let s = SrmError::QueueFull.to_string();
1537 assert!(s.contains("full"));
1538 }
1539
1540 #[test]
1543 fn test_srm_aliases_accessible() {
1544 let _: SrmReplicaTarget = make_target(1);
1545 let _: SrmReplicationPolicy = ReplicationPolicy::Asynchronous;
1546 let _: SrmReplicationConfig = SrmReplicationConfig::default();
1547 let _: SrmStorageReplicationManager = StorageReplicationManager::with_defaults();
1548 }
1549
1550 #[test]
1553 fn test_multiple_batches_accumulate_stats() {
1554 let mut mgr = make_mgr_with_replicas(2);
1555 for i in 0..32u8 {
1556 mgr.enqueue_put([i; 32], 128);
1557 }
1558 mgr.process_batch(); mgr.process_batch(); let s = mgr.replication_stats();
1561 assert_eq!(s.total_ops_processed, 32);
1562 assert_eq!(s.pending_ops, 0);
1563 }
1564
1565 #[test]
1568 fn test_op_seed_deterministic() {
1569 let cid = [0xCCu8; 32];
1570 let rid = [0xDDu8; 16];
1571 let s1 = op_seed(&cid, &rid);
1572 let s2 = op_seed(&cid, &rid);
1573 assert_eq!(s1, s2);
1574 }
1575
1576 #[test]
1577 fn test_op_seed_differs_by_cid() {
1578 let rid = [0u8; 16];
1579 let s1 = op_seed(&[1u8; 32], &rid);
1580 let s2 = op_seed(&[2u8; 32], &rid);
1581 assert_ne!(s1, s2);
1582 }
1583}