use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::net::SocketAddr;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClusteringConfig {
pub server_id: String,
pub nodes: Vec<ServerNode>,
pub replication: ReplicationConfig,
pub consistency: ConsistencyConfig,
pub network: NetworkConfig,
pub storage: StorageConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerNode {
pub id: String,
pub address: SocketAddr,
#[serde(default)]
pub tls: Option<TlsConfig>,
#[serde(default = "default_max_connections")]
pub max_connections: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TlsConfig {
pub cert_path: String,
pub key_path: String,
pub ca_path: Option<String>,
#[serde(default = "default_true")]
pub verify_peer: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReplicationConfig {
#[serde(default = "default_sync_interval_ms")]
pub sync_interval_ms: u64,
#[serde(default = "default_batch_size")]
pub batch_size: usize,
#[serde(default = "default_max_replication_tasks")]
pub max_concurrent_tasks: usize,
#[serde(default = "default_true")]
pub gossip_enabled: bool,
#[serde(default = "default_replication_timeout_ms")]
pub replication_timeout_ms: u64,
#[serde(default = "default_true")]
pub enable_checksums: bool,
#[serde(default = "default_true")]
pub enable_resume: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConsistencyConfig {
pub strategy: ConflictResolutionStrategy,
#[serde(default = "default_true")]
pub enable_vector_clocks: bool,
#[serde(default)]
pub quorum_size: Option<usize>,
#[serde(default = "default_true")]
pub detect_conflicts: bool,
#[serde(default = "default_conflict_log")]
pub conflict_log_enabled: bool,
#[serde(default)]
pub conflict_log_path: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ConflictResolutionStrategy {
#[serde(rename = "last_write_wins")]
LastWriteWins {
#[serde(default)]
tie_breaker: Option<String>,
},
#[serde(rename = "server_priority")]
ServerPriority {
priority_order: Vec<String>,
},
#[serde(rename = "application_defined")]
ApplicationDefined {
#[serde(default)]
hints: HashMap<String, String>,
},
#[serde(rename = "merge")]
Merge {
#[serde(default = "default_merge_strategy")]
merge_type: String,
},
#[serde(rename = "custom")]
Custom {
handler_name: String,
#[serde(default)]
config: HashMap<String, String>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetworkConfig {
#[serde(default = "default_heartbeat_interval_ms")]
pub heartbeat_interval_ms: u64,
#[serde(default = "default_heartbeat_timeout_ms")]
pub heartbeat_timeout_ms: u64,
#[serde(default = "default_max_heartbeat_failures")]
pub max_heartbeat_failures: u32,
#[serde(default = "default_true")]
pub connection_pooling: bool,
#[serde(default = "default_max_queue_size")]
pub max_queue_size: usize,
#[serde(default = "default_true")]
pub tcp_keepalive: bool,
#[serde(default = "default_tcp_keepalive_ms")]
pub tcp_keepalive_ms: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageConfig {
pub backend: StorageBackend,
#[serde(default = "default_true")]
pub persist_cluster_state: bool,
#[serde(default = "default_metadata_dir")]
pub metadata_dir: String,
#[serde(default = "default_true")]
pub enable_snapshots: bool,
#[serde(default = "default_snapshot_interval_ms")]
pub snapshot_interval_ms: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum StorageBackend {
#[serde(rename = "memory")]
Memory,
#[serde(rename = "sqlite")]
Sqlite {
db_path: String,
},
#[serde(rename = "postgresql")]
PostgreSQL {
url: String,
#[serde(default = "default_db_max_connections")]
max_connections: u32,
},
#[serde(rename = "mysql")]
MySQL {
url: String,
#[serde(default = "default_db_max_connections")]
max_connections: u32,
},
#[serde(rename = "redis")]
Redis {
url: String,
},
}
fn default_max_connections() -> usize {
10
}
fn default_true() -> bool {
true
}
fn default_sync_interval_ms() -> u64 {
100
}
fn default_batch_size() -> usize {
1000
}
fn default_max_replication_tasks() -> usize {
4
}
fn default_replication_timeout_ms() -> u64 {
5000
}
fn default_conflict_log() -> bool {
false
}
fn default_merge_strategy() -> String {
"union".to_string()
}
fn default_heartbeat_interval_ms() -> u64 {
1000
}
fn default_heartbeat_timeout_ms() -> u64 {
3000
}
fn default_max_heartbeat_failures() -> u32 {
3
}
fn default_max_queue_size() -> usize {
10000
}
fn default_true_val() -> bool {
true
}
fn default_tcp_keepalive_ms() -> u64 {
30000
}
fn default_metadata_dir() -> String {
".commy/cluster".to_string()
}
fn default_snapshot_interval_ms() -> u64 {
60000
}
fn default_db_max_connections() -> u32 {
10
}
impl ClusteringConfig {
pub fn new(server_id: String) -> Self {
Self {
server_id,
nodes: Vec::new(),
replication: ReplicationConfig::default(),
consistency: ConsistencyConfig::default(),
network: NetworkConfig::default(),
storage: StorageConfig::default(),
}
}
pub fn add_node(mut self, node: ServerNode) -> Self {
self.nodes.push(node);
self
}
pub fn with_replication(mut self, replication: ReplicationConfig) -> Self {
self.replication = replication;
self
}
pub fn with_consistency(mut self, consistency: ConsistencyConfig) -> Self {
self.consistency = consistency;
self
}
pub fn with_network(mut self, network: NetworkConfig) -> Self {
self.network = network;
self
}
pub fn with_storage(mut self, storage: StorageConfig) -> Self {
self.storage = storage;
self
}
pub fn validate(&self) -> Result<(), String> {
if !self.nodes.iter().any(|n| n.id == self.server_id) {
return Err(format!(
"Server ID '{}' not found in nodes list",
self.server_id
));
}
if self.nodes.is_empty() {
return Err("Cluster must have at least 1 node".to_string());
}
let mut seen = std::collections::HashSet::new();
for node in &self.nodes {
if !seen.insert(&node.id) {
return Err(format!("Duplicate node ID: '{}'", node.id));
}
}
if let ConflictResolutionStrategy::ServerPriority { priority_order } =
&self.consistency.strategy
{
for server_id in priority_order {
if !self.nodes.iter().any(|n| &n.id == server_id) {
return Err(format!(
"Server '{}' in priority order not found in nodes",
server_id
));
}
}
}
Ok(())
}
pub fn three_node_cluster(server_id: String) -> Self {
let mut config = Self::new(server_id);
config.nodes = vec![
ServerNode {
id: "server-1".to_string(),
address: "127.0.0.1:9001".parse().unwrap(),
tls: None,
max_connections: 10,
},
ServerNode {
id: "server-2".to_string(),
address: "127.0.0.1:9002".parse().unwrap(),
tls: None,
max_connections: 10,
},
ServerNode {
id: "server-3".to_string(),
address: "127.0.0.1:9003".parse().unwrap(),
tls: None,
max_connections: 10,
},
];
config
}
pub fn five_node_cluster(server_id: String) -> Self {
let mut config = Self::new(server_id);
config.nodes = vec![
ServerNode {
id: "server-1".to_string(),
address: "127.0.0.1:9001".parse().unwrap(),
tls: None,
max_connections: 10,
},
ServerNode {
id: "server-2".to_string(),
address: "127.0.0.1:9002".parse().unwrap(),
tls: None,
max_connections: 10,
},
ServerNode {
id: "server-3".to_string(),
address: "127.0.0.1:9003".parse().unwrap(),
tls: None,
max_connections: 10,
},
ServerNode {
id: "server-4".to_string(),
address: "127.0.0.1:9004".parse().unwrap(),
tls: None,
max_connections: 10,
},
ServerNode {
id: "server-5".to_string(),
address: "127.0.0.1:9005".parse().unwrap(),
tls: None,
max_connections: 10,
},
];
config
}
}
impl Default for ReplicationConfig {
fn default() -> Self {
Self {
sync_interval_ms: 100,
batch_size: 1000,
max_concurrent_tasks: 4,
gossip_enabled: true,
replication_timeout_ms: 5000,
enable_checksums: true,
enable_resume: true,
}
}
}
impl Default for ConsistencyConfig {
fn default() -> Self {
Self {
strategy: ConflictResolutionStrategy::LastWriteWins { tie_breaker: None },
enable_vector_clocks: true,
quorum_size: None,
detect_conflicts: true,
conflict_log_enabled: false,
conflict_log_path: String::new(),
}
}
}
impl Default for NetworkConfig {
fn default() -> Self {
Self {
heartbeat_interval_ms: 1000,
heartbeat_timeout_ms: 3000,
max_heartbeat_failures: 3,
connection_pooling: true,
max_queue_size: 10000,
tcp_keepalive: true,
tcp_keepalive_ms: 30000,
}
}
}
impl Default for StorageConfig {
fn default() -> Self {
Self {
backend: StorageBackend::Memory,
persist_cluster_state: true,
metadata_dir: ".commy/cluster".to_string(),
enable_snapshots: true,
snapshot_interval_ms: 60000,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_creation() {
let config = ClusteringConfig::new("server-1".to_string());
assert_eq!(config.server_id, "server-1");
}
#[test]
fn test_three_node_cluster() {
let config = ClusteringConfig::three_node_cluster("server-1".to_string());
assert_eq!(config.nodes.len(), 3);
assert!(config.validate().is_ok());
}
#[test]
fn test_five_node_cluster() {
let config = ClusteringConfig::five_node_cluster("server-2".to_string());
assert_eq!(config.nodes.len(), 5);
assert!(config.validate().is_ok());
}
#[test]
fn test_validation_missing_server_id() {
let config = ClusteringConfig::new("server-99".to_string()).add_node(ServerNode {
id: "server-1".to_string(),
address: "127.0.0.1:9001".parse().unwrap(),
tls: None,
max_connections: 10,
});
assert!(config.validate().is_err());
}
#[test]
fn test_validation_duplicate_ids() {
let config = ClusteringConfig::new("server-1".to_string())
.add_node(ServerNode {
id: "server-1".to_string(),
address: "127.0.0.1:9001".parse().unwrap(),
tls: None,
max_connections: 10,
})
.add_node(ServerNode {
id: "server-1".to_string(),
address: "127.0.0.1:9002".parse().unwrap(),
tls: None,
max_connections: 10,
});
assert!(config.validate().is_err());
}
#[test]
fn test_builder_pattern() {
let config = ClusteringConfig::new("server-1".to_string())
.add_node(ServerNode {
id: "server-1".to_string(),
address: "127.0.0.1:9001".parse().unwrap(),
tls: None,
max_connections: 10,
})
.with_consistency(ConsistencyConfig {
strategy: ConflictResolutionStrategy::LastWriteWins { tie_breaker: None },
enable_vector_clocks: true,
quorum_size: None,
detect_conflicts: true,
conflict_log_enabled: false,
conflict_log_path: String::new(),
});
assert_eq!(config.nodes.len(), 1);
assert!(config.consistency.enable_vector_clocks);
}
#[test]
fn test_server_priority_validation() {
let config = ClusteringConfig::new("server-1".to_string())
.add_node(ServerNode {
id: "server-1".to_string(),
address: "127.0.0.1:9001".parse().unwrap(),
tls: None,
max_connections: 10,
})
.with_consistency(ConsistencyConfig {
strategy: ConflictResolutionStrategy::ServerPriority {
priority_order: vec!["server-99".to_string()],
},
enable_vector_clocks: true,
quorum_size: None,
detect_conflicts: true,
conflict_log_enabled: false,
conflict_log_path: String::new(),
});
assert!(config.validate().is_err());
}
#[test]
fn test_serialization() {
let config = ClusteringConfig::three_node_cluster("server-1".to_string());
let json = serde_json::to_string(&config).unwrap();
let deserialized: ClusteringConfig = serde_json::from_str(&json).unwrap();
assert_eq!(config.server_id, deserialized.server_id);
assert_eq!(config.nodes.len(), deserialized.nodes.len());
}
#[test]
fn test_with_replication() {
let replication = ReplicationConfig {
sync_interval_ms: 500,
gossip_enabled: false,
..ReplicationConfig::default()
};
let config = ClusteringConfig::new("server-1".to_string())
.with_replication(replication);
assert_eq!(config.replication.sync_interval_ms, 500);
assert!(!config.replication.gossip_enabled);
}
#[test]
fn test_with_network() {
let network = NetworkConfig {
heartbeat_interval_ms: 2000,
max_heartbeat_failures: 5,
..NetworkConfig::default()
};
let config = ClusteringConfig::new("server-1".to_string())
.with_network(network);
assert_eq!(config.network.heartbeat_interval_ms, 2000);
assert_eq!(config.network.max_heartbeat_failures, 5);
}
#[test]
fn test_with_storage() {
let storage = StorageConfig {
backend: StorageBackend::Memory,
persist_cluster_state: false,
enable_snapshots: false,
..StorageConfig::default()
};
let config = ClusteringConfig::new("server-1".to_string())
.with_storage(storage);
assert!(!config.storage.persist_cluster_state);
assert!(!config.storage.enable_snapshots);
assert!(matches!(config.storage.backend, StorageBackend::Memory));
}
}