use dashmap::DashMap;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::RwLock;
#[derive(Debug, Clone)]
pub struct MigrationConfig {
pub enable_auto_migration: bool,
pub probe_interval: Duration,
pub migration_timeout: Duration,
pub max_retries: usize,
pub grace_period: Duration,
}
impl Default for MigrationConfig {
fn default() -> Self {
Self {
enable_auto_migration: true,
probe_interval: Duration::from_secs(5),
migration_timeout: Duration::from_secs(30),
max_retries: 3,
grace_period: Duration::from_secs(10),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MigrationState {
Stable,
Preparing,
Migrating,
Completed,
Failed,
}
#[derive(Debug, Clone)]
pub enum MigrationEvent {
NetworkChangeDetected {
old_addr: SocketAddr,
new_addr: SocketAddr,
},
MigrationStarted {
connection_id: String,
from_addr: SocketAddr,
to_addr: SocketAddr,
},
MigrationCompleted {
connection_id: String,
new_addr: SocketAddr,
duration: Duration,
},
MigrationFailed {
connection_id: String,
reason: String,
retry_count: usize,
},
}
#[derive(Debug, Clone, Default)]
pub struct MigrationStats {
pub total_migrations: u64,
pub successful_migrations: u64,
pub failed_migrations: u64,
pub avg_migration_duration: Duration,
pub max_migration_duration: Duration,
}
impl MigrationStats {
pub fn success_rate(&self) -> f64 {
if self.total_migrations == 0 {
return 0.0;
}
self.successful_migrations as f64 / self.total_migrations as f64
}
}
#[derive(Debug)]
struct MigrationRecord {
state: MigrationState,
#[allow(dead_code)]
old_addr: SocketAddr,
new_addr: SocketAddr,
started_at: Instant,
retry_count: usize,
}
type EventCallback = Box<dyn Fn(MigrationEvent) + Send + Sync>;
pub struct ConnectionMigration {
config: MigrationConfig,
migrations: Arc<DashMap<String, MigrationRecord>>,
stats: Arc<RwLock<MigrationStats>>,
event_callbacks: Arc<RwLock<Vec<EventCallback>>>,
}
impl ConnectionMigration {
pub fn new(config: MigrationConfig) -> Self {
Self {
config,
migrations: Arc::new(DashMap::new()),
stats: Arc::new(RwLock::new(MigrationStats::default())),
event_callbacks: Arc::new(RwLock::new(Vec::new())),
}
}
pub async fn on_event<F>(&self, callback: F)
where
F: Fn(MigrationEvent) + Send + Sync + 'static,
{
let mut callbacks = self.event_callbacks.write().await;
callbacks.push(Box::new(callback));
}
async fn emit_event(&self, event: MigrationEvent) {
let callbacks = self.event_callbacks.read().await;
for callback in callbacks.iter() {
callback(event.clone());
}
}
pub async fn start_migration(
&self,
connection_id: String,
old_addr: SocketAddr,
new_addr: SocketAddr,
) -> Result<(), String> {
if self.migrations.contains_key(&connection_id) {
return Err("Migration already in progress".to_string());
}
self.migrations.insert(
connection_id.clone(),
MigrationRecord {
state: MigrationState::Preparing,
old_addr,
new_addr,
started_at: Instant::now(),
retry_count: 0,
},
);
let mut stats = self.stats.write().await;
stats.total_migrations += 1;
drop(stats);
self.emit_event(MigrationEvent::MigrationStarted {
connection_id: connection_id.clone(),
from_addr: old_addr,
to_addr: new_addr,
})
.await;
if let Some(mut record) = self.migrations.get_mut(&connection_id) {
record.state = MigrationState::Migrating;
}
Ok(())
}
pub async fn complete_migration(&self, connection_id: &str) -> Result<(), String> {
let (new_addr, duration) = {
if let Some(mut record) = self.migrations.get_mut(connection_id) {
record.state = MigrationState::Completed;
let duration = record.started_at.elapsed();
(record.new_addr, duration)
} else {
return Err("Migration not found".to_string());
}
};
let mut stats = self.stats.write().await;
stats.successful_migrations += 1;
let total_duration = stats.avg_migration_duration.as_millis() as u64
* (stats.successful_migrations - 1)
+ duration.as_millis() as u64;
stats.avg_migration_duration =
Duration::from_millis(total_duration / stats.successful_migrations);
if duration > stats.max_migration_duration {
stats.max_migration_duration = duration;
}
drop(stats);
self.emit_event(MigrationEvent::MigrationCompleted {
connection_id: connection_id.to_string(),
new_addr,
duration,
})
.await;
let connection_id = connection_id.to_string();
let migrations = self.migrations.clone();
let grace_period = self.config.grace_period;
tokio::spawn(async move {
tokio::time::sleep(grace_period).await;
migrations.remove(&connection_id);
});
Ok(())
}
pub async fn fail_migration(&self, connection_id: &str, reason: String) -> Result<(), String> {
let should_retry = {
if let Some(mut record) = self.migrations.get_mut(connection_id) {
record.retry_count += 1;
record.retry_count < self.config.max_retries
} else {
return Err("Migration not found".to_string());
}
};
if should_retry {
if let Some(mut record) = self.migrations.get_mut(connection_id) {
record.state = MigrationState::Preparing;
}
Ok(())
} else {
let retry_count = {
if let Some(mut record) = self.migrations.get_mut(connection_id) {
record.state = MigrationState::Failed;
record.retry_count
} else {
0
}
};
let mut stats = self.stats.write().await;
stats.failed_migrations += 1;
drop(stats);
self.emit_event(MigrationEvent::MigrationFailed {
connection_id: connection_id.to_string(),
reason,
retry_count,
})
.await;
self.migrations.remove(connection_id);
Err("Migration failed after retries".to_string())
}
}
pub fn get_state(&self, connection_id: &str) -> Option<MigrationState> {
self.migrations
.get(connection_id)
.map(|record| record.state)
}
pub fn is_migrating(&self, connection_id: &str) -> bool {
matches!(
self.get_state(connection_id),
Some(MigrationState::Preparing) | Some(MigrationState::Migrating)
)
}
pub async fn stats(&self) -> MigrationStats {
self.stats.read().await.clone()
}
pub async fn reset_stats(&self) {
let mut stats = self.stats.write().await;
*stats = MigrationStats::default();
}
pub async fn cleanup_timeouts(&self) {
let timeout = self.config.migration_timeout;
let failed_ids: Vec<String> = self
.migrations
.iter()
.filter(|entry| entry.started_at.elapsed() > timeout)
.map(|entry| entry.key().clone())
.collect();
for id in failed_ids {
let _ = self
.fail_migration(&id, "Migration timeout".to_string())
.await;
}
}
pub fn active_migrations(&self) -> usize {
self.migrations.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_connection_migration_creation() {
let config = MigrationConfig::default();
let migration = ConnectionMigration::new(config);
assert_eq!(migration.active_migrations(), 0);
}
#[tokio::test]
async fn test_start_migration() {
let migration = ConnectionMigration::new(MigrationConfig::default());
let old_addr: SocketAddr = "127.0.0.1:8000".parse().expect("test: valid socket addr");
let new_addr: SocketAddr = "127.0.0.1:8001".parse().expect("test: valid socket addr");
let result = migration
.start_migration("conn1".to_string(), old_addr, new_addr)
.await;
assert!(result.is_ok());
assert_eq!(migration.active_migrations(), 1);
let state = migration.get_state("conn1");
assert_eq!(state, Some(MigrationState::Migrating));
}
#[tokio::test]
async fn test_complete_migration() {
let migration = ConnectionMigration::new(MigrationConfig::default());
let old_addr: SocketAddr = "127.0.0.1:8000".parse().expect("test: valid socket addr");
let new_addr: SocketAddr = "127.0.0.1:8001".parse().expect("test: valid socket addr");
migration
.start_migration("conn1".to_string(), old_addr, new_addr)
.await
.expect("test: start migration should succeed");
let result = migration.complete_migration("conn1").await;
assert!(result.is_ok());
let stats = migration.stats().await;
assert_eq!(stats.successful_migrations, 1);
assert_eq!(stats.success_rate(), 1.0);
}
#[tokio::test]
async fn test_fail_migration_with_retry() {
let config = MigrationConfig {
max_retries: 3,
..Default::default()
};
let migration = ConnectionMigration::new(config);
let old_addr: SocketAddr = "127.0.0.1:8000".parse().expect("test: valid socket addr");
let new_addr: SocketAddr = "127.0.0.1:8001".parse().expect("test: valid socket addr");
migration
.start_migration("conn1".to_string(), old_addr, new_addr)
.await
.expect("test: start migration should succeed");
let result = migration
.fail_migration("conn1", "Network error".to_string())
.await;
assert!(result.is_ok());
let state = migration.get_state("conn1");
assert_eq!(state, Some(MigrationState::Preparing));
}
#[tokio::test]
async fn test_fail_migration_max_retries() {
let config = MigrationConfig {
max_retries: 2,
..Default::default()
};
let migration = ConnectionMigration::new(config);
let old_addr: SocketAddr = "127.0.0.1:8000".parse().expect("test: valid socket addr");
let new_addr: SocketAddr = "127.0.0.1:8001".parse().expect("test: valid socket addr");
migration
.start_migration("conn1".to_string(), old_addr, new_addr)
.await
.expect("test: start migration should succeed");
migration
.fail_migration("conn1", "Error 1".to_string())
.await
.expect("test: fail migration should succeed");
let result = migration
.fail_migration("conn1", "Error 2".to_string())
.await;
assert!(result.is_err());
let stats = migration.stats().await;
assert_eq!(stats.failed_migrations, 1);
}
#[tokio::test]
async fn test_is_migrating() {
let migration = ConnectionMigration::new(MigrationConfig::default());
let old_addr: SocketAddr = "127.0.0.1:8000".parse().expect("test: valid socket addr");
let new_addr: SocketAddr = "127.0.0.1:8001".parse().expect("test: valid socket addr");
assert!(!migration.is_migrating("conn1"));
migration
.start_migration("conn1".to_string(), old_addr, new_addr)
.await
.expect("test: start migration should succeed");
assert!(migration.is_migrating("conn1"));
migration
.complete_migration("conn1")
.await
.expect("test: complete migration should succeed");
let state = migration.get_state("conn1");
if state.is_some() {
assert_eq!(state, Some(MigrationState::Completed));
}
}
#[tokio::test]
async fn test_duplicate_migration_rejected() {
let migration = ConnectionMigration::new(MigrationConfig::default());
let old_addr: SocketAddr = "127.0.0.1:8000".parse().expect("test: valid socket addr");
let new_addr: SocketAddr = "127.0.0.1:8001".parse().expect("test: valid socket addr");
migration
.start_migration("conn1".to_string(), old_addr, new_addr)
.await
.expect("test: start migration should succeed");
let result = migration
.start_migration("conn1".to_string(), old_addr, new_addr)
.await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_stats_calculation() {
let config = MigrationConfig {
max_retries: 2,
..Default::default()
};
let migration = ConnectionMigration::new(config);
let old_addr: SocketAddr = "127.0.0.1:8000".parse().expect("test: valid socket addr");
let new_addr: SocketAddr = "127.0.0.1:8001".parse().expect("test: valid socket addr");
migration
.start_migration("conn1".to_string(), old_addr, new_addr)
.await
.expect("test: start migration should succeed");
migration
.complete_migration("conn1")
.await
.expect("test: complete migration should succeed");
migration
.start_migration("conn2".to_string(), old_addr, new_addr)
.await
.expect("test: start migration should succeed");
migration
.fail_migration("conn2", "Error".to_string())
.await
.ok();
migration
.fail_migration("conn2", "Error".to_string())
.await
.ok();
let stats = migration.stats().await;
assert_eq!(stats.total_migrations, 2);
assert_eq!(stats.successful_migrations, 1);
assert_eq!(stats.failed_migrations, 1);
assert_eq!(stats.success_rate(), 0.5);
}
#[tokio::test]
async fn test_reset_stats() {
let migration = ConnectionMigration::new(MigrationConfig::default());
let old_addr: SocketAddr = "127.0.0.1:8000".parse().expect("test: valid socket addr");
let new_addr: SocketAddr = "127.0.0.1:8001".parse().expect("test: valid socket addr");
migration
.start_migration("conn1".to_string(), old_addr, new_addr)
.await
.expect("test: start migration should succeed");
let stats = migration.stats().await;
assert!(stats.total_migrations > 0);
migration.reset_stats().await;
let stats = migration.stats().await;
assert_eq!(stats.total_migrations, 0);
}
#[tokio::test]
async fn test_event_callbacks() {
use std::sync::atomic::{AtomicUsize, Ordering};
let migration = ConnectionMigration::new(MigrationConfig::default());
let event_count = Arc::new(AtomicUsize::new(0));
let count_clone = event_count.clone();
migration
.on_event(move |_event| {
count_clone.fetch_add(1, Ordering::SeqCst);
})
.await;
let old_addr: SocketAddr = "127.0.0.1:8000".parse().expect("test: valid socket addr");
let new_addr: SocketAddr = "127.0.0.1:8001".parse().expect("test: valid socket addr");
migration
.start_migration("conn1".to_string(), old_addr, new_addr)
.await
.expect("test: start migration should succeed");
migration
.complete_migration("conn1")
.await
.expect("test: complete migration should succeed");
assert_eq!(event_count.load(Ordering::SeqCst), 2);
}
}