pub mod audit;
pub mod bandwidth;
pub mod deletion;
pub mod eviction;
pub mod eviction_service;
pub mod garbage_collection;
pub mod lifecycle;
pub mod preemption;
pub mod retention;
pub mod storage;
pub mod sync_mode;
use serde::{Deserialize, Serialize};
use std::fmt;
pub use deletion::{
DeleteResult, DeletionPolicy, DeletionPolicyRegistry, PropagationDirection, Tombstone,
TombstoneBatch, TombstoneDecodeError, TombstoneSyncMessage,
};
pub use sync_mode::{SyncMode, SyncModeRegistry};
pub use audit::{AuditAction, AuditEntry, AuditSummary, EvictionAuditLog};
pub use bandwidth::{
BandwidthAllocation, BandwidthConfig, BandwidthPermit, BandwidthQuota, QuotaConfig,
};
pub use eviction::{EvictionConfig, EvictionController, EvictionResult};
pub use garbage_collection::{
start_periodic_gc, GarbageCollector, GcConfig, GcResult, GcStats, GcStore, ResurrectionPolicy,
};
pub use lifecycle::{
make_lifecycle_decision, LifecycleDecision, LifecyclePolicies, LifecyclePolicy,
};
pub use preemption::{ActiveTransfer, PreemptionController, PreemptionStats, TransferId};
pub use retention::{RetentionPolicies, RetentionPolicy};
pub use storage::{
ClassStorageMetrics, EvictionCandidate, QoSAwareStorage, StorageMetrics, StoredDocument,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
#[repr(u8)]
pub enum QoSClass {
Critical = 1,
High = 2,
#[default]
#[serde(alias = "default")]
Normal = 3,
Low = 4,
Bulk = 5,
}
impl QoSClass {
#[inline]
pub fn as_u8(&self) -> u8 {
*self as u8
}
#[inline]
pub fn can_preempt(&self, other: &QoSClass) -> bool {
self.as_u8() < other.as_u8()
}
#[inline]
pub fn is_critical(&self) -> bool {
matches!(self, Self::Critical)
}
pub fn bandwidth_allocation_percent(&self) -> u8 {
match self {
Self::Critical => 40,
Self::High => 30,
Self::Normal => 20,
Self::Low => 8,
Self::Bulk => 2,
}
}
pub fn for_collection(collection: &str) -> Self {
match collection {
"commands" | "contact_reports" | "alerts" => QoSClass::Critical,
"cells" | "nodes" | "audit_logs" => QoSClass::High,
"beacons" | "platforms" | "tracks" => QoSClass::Normal,
"node_positions" | "capabilities" | "node_states" => QoSClass::Low,
_ => QoSClass::Bulk,
}
}
pub fn all_by_priority() -> &'static [QoSClass] {
&[
QoSClass::Critical,
QoSClass::High,
QoSClass::Normal,
QoSClass::Low,
QoSClass::Bulk,
]
}
}
impl PartialOrd for QoSClass {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for QoSClass {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
other.as_u8().cmp(&self.as_u8())
}
}
impl fmt::Display for QoSClass {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Critical => write!(f, "P1:Critical"),
Self::High => write!(f, "P2:High"),
Self::Normal => write!(f, "P3:Normal"),
Self::Low => write!(f, "P4:Low"),
Self::Bulk => write!(f, "P5:Bulk"),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct QoSPolicy {
pub base_class: QoSClass,
pub max_latency_ms: Option<u64>,
pub max_size_bytes: Option<usize>,
pub ttl_seconds: Option<u64>,
pub retention_priority: u8,
pub preemptable: bool,
}
impl Default for QoSPolicy {
fn default() -> Self {
Self {
base_class: QoSClass::Normal,
max_latency_ms: Some(60_000), max_size_bytes: None,
ttl_seconds: None,
retention_priority: 3,
preemptable: true,
}
}
}
impl QoSPolicy {
pub fn critical() -> Self {
Self {
base_class: QoSClass::Critical,
max_latency_ms: Some(500),
max_size_bytes: Some(64 * 1024), ttl_seconds: None, retention_priority: 5, preemptable: false,
}
}
pub fn high() -> Self {
Self {
base_class: QoSClass::High,
max_latency_ms: Some(5_000), max_size_bytes: Some(10 * 1024 * 1024), ttl_seconds: Some(3600), retention_priority: 4,
preemptable: true,
}
}
pub fn low() -> Self {
Self {
base_class: QoSClass::Low,
max_latency_ms: Some(300_000), max_size_bytes: None,
ttl_seconds: Some(86400), retention_priority: 2,
preemptable: true,
}
}
pub fn bulk() -> Self {
Self {
base_class: QoSClass::Bulk,
max_latency_ms: None, max_size_bytes: None, ttl_seconds: Some(604800), retention_priority: 1,
preemptable: true,
}
}
pub fn exceeds_latency(&self, latency_ms: u64) -> bool {
self.max_latency_ms
.map(|max| latency_ms > max)
.unwrap_or(false)
}
pub fn exceeds_size(&self, size_bytes: usize) -> bool {
self.max_size_bytes
.map(|max| size_bytes > max)
.unwrap_or(false)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_qos_class_ordering() {
assert!(QoSClass::Critical > QoSClass::High);
assert!(QoSClass::High > QoSClass::Normal);
assert!(QoSClass::Normal > QoSClass::Low);
assert!(QoSClass::Low > QoSClass::Bulk);
let mut classes = [
QoSClass::Bulk,
QoSClass::Critical,
QoSClass::Low,
QoSClass::High,
QoSClass::Normal,
];
classes.sort();
classes.reverse(); assert_eq!(classes[0], QoSClass::Critical);
assert_eq!(classes[4], QoSClass::Bulk);
}
#[test]
fn test_qos_class_can_preempt() {
assert!(QoSClass::Critical.can_preempt(&QoSClass::High));
assert!(QoSClass::Critical.can_preempt(&QoSClass::Bulk));
assert!(QoSClass::High.can_preempt(&QoSClass::Normal));
assert!(!QoSClass::Low.can_preempt(&QoSClass::High));
assert!(!QoSClass::Normal.can_preempt(&QoSClass::Normal));
}
#[test]
fn test_qos_class_bandwidth_allocation() {
assert_eq!(QoSClass::Critical.bandwidth_allocation_percent(), 40);
assert_eq!(QoSClass::High.bandwidth_allocation_percent(), 30);
assert_eq!(QoSClass::Normal.bandwidth_allocation_percent(), 20);
assert_eq!(QoSClass::Low.bandwidth_allocation_percent(), 8);
assert_eq!(QoSClass::Bulk.bandwidth_allocation_percent(), 2);
let total: u8 = QoSClass::all_by_priority()
.iter()
.map(|c| c.bandwidth_allocation_percent())
.sum();
assert_eq!(total, 100);
}
#[test]
fn test_qos_class_display() {
assert_eq!(QoSClass::Critical.to_string(), "P1:Critical");
assert_eq!(QoSClass::High.to_string(), "P2:High");
assert_eq!(QoSClass::Normal.to_string(), "P3:Normal");
assert_eq!(QoSClass::Low.to_string(), "P4:Low");
assert_eq!(QoSClass::Bulk.to_string(), "P5:Bulk");
}
#[test]
fn test_qos_policy_defaults() {
let policy = QoSPolicy::default();
assert_eq!(policy.base_class, QoSClass::Normal);
assert_eq!(policy.max_latency_ms, Some(60_000));
assert!(policy.preemptable);
}
#[test]
fn test_qos_policy_critical() {
let policy = QoSPolicy::critical();
assert_eq!(policy.base_class, QoSClass::Critical);
assert_eq!(policy.max_latency_ms, Some(500));
assert!(!policy.preemptable);
assert_eq!(policy.retention_priority, 5);
}
#[test]
fn test_qos_policy_latency_check() {
let policy = QoSPolicy::critical();
assert!(!policy.exceeds_latency(400));
assert!(policy.exceeds_latency(600));
let bulk_policy = QoSPolicy::bulk();
assert!(!bulk_policy.exceeds_latency(1_000_000)); }
#[test]
fn test_qos_policy_size_check() {
let policy = QoSPolicy::critical();
assert!(!policy.exceeds_size(60 * 1024));
assert!(policy.exceeds_size(70 * 1024));
let bulk_policy = QoSPolicy::bulk();
assert!(!bulk_policy.exceeds_size(100_000_000)); }
#[test]
fn test_qos_class_serialization() {
let class = QoSClass::Critical;
let json = serde_json::to_string(&class).unwrap();
assert_eq!(json, "\"Critical\"");
let deserialized: QoSClass = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, QoSClass::Critical);
}
#[test]
fn test_qos_class_for_collection() {
assert_eq!(QoSClass::for_collection("commands"), QoSClass::Critical);
assert_eq!(
QoSClass::for_collection("contact_reports"),
QoSClass::Critical
);
assert_eq!(QoSClass::for_collection("alerts"), QoSClass::Critical);
assert_eq!(QoSClass::for_collection("cells"), QoSClass::High);
assert_eq!(QoSClass::for_collection("nodes"), QoSClass::High);
assert_eq!(QoSClass::for_collection("beacons"), QoSClass::Normal);
assert_eq!(QoSClass::for_collection("tracks"), QoSClass::Normal);
assert_eq!(QoSClass::for_collection("node_positions"), QoSClass::Low);
assert_eq!(
QoSClass::for_collection("unknown_collection"),
QoSClass::Bulk
);
}
#[test]
fn test_qos_policy_serialization() {
let policy = QoSPolicy::critical();
let json = serde_json::to_string(&policy).unwrap();
let deserialized: QoSPolicy = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, policy);
}
}