use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use std::time::Duration;
use super::peer::{PeerInfo, PeerStatus};
#[derive(Debug, Clone)]
pub struct PeerConfig {
pub static_peers: Vec<String>,
pub server_id: String,
pub listen_address: String,
pub heartbeat_interval: Duration,
pub heartbeat_timeout: Duration,
}
impl Default for PeerConfig {
fn default() -> Self {
Self {
static_peers: vec![],
server_id: "server_1".to_string(),
listen_address: "127.0.0.1:9000".to_string(),
heartbeat_interval: Duration::from_secs(30),
heartbeat_timeout: Duration::from_secs(60),
}
}
}
pub struct PeerRegistry {
config: PeerConfig,
peers: Arc<RwLock<HashMap<String, PeerInfo>>>,
cluster_size: usize,
}
impl PeerRegistry {
pub fn new(config: PeerConfig) -> Self {
Self {
config,
peers: Arc::new(RwLock::new(HashMap::new())),
cluster_size: 0,
}
}
pub async fn initialize(&mut self) -> Result<(), String> {
let mut peers = self.peers.write().await;
for (idx, address) in self.config.static_peers.iter().enumerate() {
let server_id = format!("peer_{}", idx + 1);
let peer_info = PeerInfo::new(server_id.clone(), address.clone());
peers.insert(server_id, peer_info);
}
self.cluster_size = self.config.static_peers.len() + 1;
Ok(())
}
pub async fn get_healthy_peers(&self) -> Vec<PeerInfo> {
let peers = self.peers.read().await;
peers
.values()
.filter(|p| p.is_healthy())
.cloned()
.collect()
}
pub async fn get_all_peers(&self) -> Vec<PeerInfo> {
let peers = self.peers.read().await;
peers.values().cloned().collect()
}
pub async fn get_peer(&self, server_id: &str) -> Option<PeerInfo> {
let peers = self.peers.read().await;
peers.get(server_id).cloned()
}
pub async fn mark_heartbeat_received(&self, server_id: &str) -> Result<(), String> {
let mut peers = self.peers.write().await;
if let Some(peer) = peers.get_mut(server_id) {
peer.heartbeat_received();
Ok(())
} else {
Err(format!("Peer {} not found", server_id))
}
}
pub async fn mark_heartbeat_missed(&self, server_id: &str) -> Result<(), String> {
let mut peers = self.peers.write().await;
if let Some(peer) = peers.get_mut(server_id) {
peer.heartbeat_missed();
Ok(())
} else {
Err(format!("Peer {} not found", server_id))
}
}
pub async fn get_cluster_status(&self) -> ClusterStatus {
let peers = self.peers.read().await;
let healthy_count = peers.values().filter(|p| p.is_healthy()).count();
let suspected_count = peers.values().filter(|p| p.status == PeerStatus::Suspected).count();
let down_count = peers.values().filter(|p| p.status == PeerStatus::Down).count();
ClusterStatus {
total_peers: self.cluster_size,
healthy_peers: healthy_count + 1, suspected_peers: suspected_count,
down_peers: down_count,
is_degraded: suspected_count > 0 || down_count > 0,
}
}
pub async fn add_bytes_received(&self, server_id: &str, bytes: u64) -> Result<(), String> {
let mut peers = self.peers.write().await;
if let Some(peer) = peers.get_mut(server_id) {
peer.add_bytes_received(bytes);
Ok(())
} else {
Err(format!("Peer {} not found", server_id))
}
}
pub async fn add_bytes_sent(&self, server_id: &str, bytes: u64) -> Result<(), String> {
let mut peers = self.peers.write().await;
if let Some(peer) = peers.get_mut(server_id) {
peer.add_bytes_sent(bytes);
Ok(())
} else {
Err(format!("Peer {} not found", server_id))
}
}
pub fn config(&self) -> &PeerConfig {
&self.config
}
pub fn cluster_size(&self) -> usize {
self.cluster_size
}
pub async fn add_peer(&mut self, peer: PeerInfo) {
let mut peers = self.peers.write().await;
peers.insert(peer.server_id.clone(), peer);
}
}
#[derive(Debug, Clone)]
pub struct ClusterStatus {
pub total_peers: usize,
pub healthy_peers: usize,
pub suspected_peers: usize,
pub down_peers: usize,
pub is_degraded: bool,
}
impl ClusterStatus {
pub fn is_quorum_available(&self) -> bool {
self.healthy_peers > self.total_peers / 2
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_peer_registry_creation() {
let config = PeerConfig {
server_id: "server_1".to_string(),
listen_address: "127.0.0.1:9000".to_string(),
static_peers: vec!["127.0.0.1:9001".to_string(), "127.0.0.1:9002".to_string()],
..Default::default()
};
let mut registry = PeerRegistry::new(config);
registry.initialize().await.unwrap();
assert_eq!(registry.cluster_size(), 3); }
#[tokio::test]
async fn test_get_all_peers() {
let config = PeerConfig {
server_id: "server_1".to_string(),
listen_address: "127.0.0.1:9000".to_string(),
static_peers: vec!["127.0.0.1:9001".to_string(), "127.0.0.1:9002".to_string()],
..Default::default()
};
let mut registry = PeerRegistry::new(config);
registry.initialize().await.unwrap();
let peers = registry.get_all_peers().await;
assert_eq!(peers.len(), 2);
}
#[tokio::test]
async fn test_get_healthy_peers() {
let config = PeerConfig {
server_id: "server_1".to_string(),
listen_address: "127.0.0.1:9000".to_string(),
static_peers: vec!["127.0.0.1:9001".to_string(), "127.0.0.1:9002".to_string()],
..Default::default()
};
let mut registry = PeerRegistry::new(config);
registry.initialize().await.unwrap();
let healthy = registry.get_healthy_peers().await;
assert_eq!(healthy.len(), 2); }
#[tokio::test]
async fn test_mark_heartbeat_received() {
let config = PeerConfig {
server_id: "server_1".to_string(),
listen_address: "127.0.0.1:9000".to_string(),
static_peers: vec!["127.0.0.1:9001".to_string()],
..Default::default()
};
let mut registry = PeerRegistry::new(config);
registry.initialize().await.unwrap();
let peer = registry.get_peer("peer_1").await.unwrap();
assert!(peer.is_healthy());
registry.mark_heartbeat_received("peer_1").await.unwrap();
let peer = registry.get_peer("peer_1").await.unwrap();
assert!(peer.is_healthy());
}
#[tokio::test]
async fn test_mark_heartbeat_missed() {
let config = PeerConfig {
server_id: "server_1".to_string(),
listen_address: "127.0.0.1:9000".to_string(),
static_peers: vec!["127.0.0.1:9001".to_string()],
..Default::default()
};
let mut registry = PeerRegistry::new(config);
registry.initialize().await.unwrap();
registry.mark_heartbeat_missed("peer_1").await.unwrap();
registry.mark_heartbeat_missed("peer_1").await.unwrap();
let peer = registry.get_peer("peer_1").await.unwrap();
assert_eq!(peer.status, PeerStatus::Suspected);
}
#[tokio::test]
async fn test_cluster_status() {
let config = PeerConfig {
server_id: "server_1".to_string(),
listen_address: "127.0.0.1:9000".to_string(),
static_peers: vec!["127.0.0.1:9001".to_string(), "127.0.0.1:9002".to_string()],
..Default::default()
};
let mut registry = PeerRegistry::new(config);
registry.initialize().await.unwrap();
let status = registry.get_cluster_status().await;
assert_eq!(status.total_peers, 3);
assert_eq!(status.healthy_peers, 3); assert!(!status.is_degraded);
assert!(status.is_quorum_available());
}
#[tokio::test]
async fn test_bytes_tracking() {
let config = PeerConfig {
server_id: "server_1".to_string(),
listen_address: "127.0.0.1:9000".to_string(),
static_peers: vec!["127.0.0.1:9001".to_string()],
..Default::default()
};
let mut registry = PeerRegistry::new(config);
registry.initialize().await.unwrap();
registry.add_bytes_received("peer_1", 1024).await.unwrap();
registry.add_bytes_sent("peer_1", 512).await.unwrap();
let peer = registry.get_peer("peer_1").await.unwrap();
assert_eq!(peer.bytes_received, 1024);
assert_eq!(peer.bytes_sent, 512);
}
#[tokio::test]
async fn test_add_peer_increases_count() {
let config = PeerConfig::default();
let mut registry = PeerRegistry::new(config);
registry.initialize().await.unwrap();
let peer = PeerInfo::new("new_peer".to_string(), "127.0.0.1:9099".to_string());
registry.add_peer(peer).await;
let all = registry.get_all_peers().await;
assert!(all.iter().any(|p| p.server_id == "new_peer"));
}
#[tokio::test]
async fn test_get_peer_unknown_returns_none() {
let config = PeerConfig::default();
let mut registry = PeerRegistry::new(config);
registry.initialize().await.unwrap();
let result = registry.get_peer("ghost").await;
assert!(result.is_none());
}
#[test]
fn test_config_getter() {
let config = PeerConfig {
server_id: "server_x".to_string(),
..Default::default()
};
let registry = PeerRegistry::new(config);
assert_eq!(registry.config().server_id, "server_x");
}
#[test]
fn test_cluster_size_before_init() {
let config = PeerConfig::default();
let registry = PeerRegistry::new(config);
assert_eq!(registry.cluster_size(), 0);
}
#[test]
fn test_is_quorum_available_true() {
let status = ClusterStatus {
total_peers: 3,
healthy_peers: 2,
suspected_peers: 0,
down_peers: 1,
is_degraded: true,
};
assert!(status.is_quorum_available());
}
#[test]
fn test_is_quorum_available_false() {
let status = ClusterStatus {
total_peers: 3,
healthy_peers: 1,
suspected_peers: 0,
down_peers: 2,
is_degraded: true,
};
assert!(!status.is_quorum_available());
}
}