use noxu_sync::Mutex;
use std::time::{Duration, Instant};
use crate::error::{RepError, Result};
#[derive(Debug, Clone)]
pub struct MasterTransferConfig {
pub target_node: String,
pub timeout: Duration,
pub force: bool,
}
impl MasterTransferConfig {
pub fn new(target_node: String, timeout: Duration) -> Self {
Self { target_node, timeout, force: false }
}
pub fn with_force(mut self, force: bool) -> Self {
self.force = force;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TransferState {
NotStarted,
InProgress,
Completed,
Failed,
TimedOut,
}
pub struct MasterTransfer {
config: MasterTransferConfig,
state: Mutex<TransferState>,
start_time: Mutex<Option<Instant>>,
}
impl MasterTransfer {
pub fn new(config: MasterTransferConfig) -> Self {
Self {
config,
state: Mutex::new(TransferState::NotStarted),
start_time: Mutex::new(None),
}
}
pub fn get_state(&self) -> TransferState {
*self.state.lock()
}
pub fn get_target(&self) -> &str {
&self.config.target_node
}
pub fn get_config(&self) -> &MasterTransferConfig {
&self.config
}
pub fn start(&self) -> Result<()> {
let mut state = self.state.lock();
if *state != TransferState::NotStarted {
return Err(RepError::StateError(
"Transfer already started".to_string(),
));
}
*state = TransferState::InProgress;
*self.start_time.lock() = Some(Instant::now());
Ok(())
}
pub fn complete(&self) -> Result<()> {
let mut state = self.state.lock();
if *state != TransferState::InProgress {
return Err(RepError::StateError(
"Transfer not in progress".to_string(),
));
}
*state = TransferState::Completed;
Ok(())
}
pub fn fail(&self, _reason: &str) -> Result<()> {
let mut state = self.state.lock();
if *state != TransferState::InProgress {
return Err(RepError::StateError(
"Transfer not in progress".to_string(),
));
}
*state = TransferState::Failed;
Ok(())
}
pub fn is_timed_out(&self) -> bool {
if let Some(start) = *self.start_time.lock() {
start.elapsed() > self.config.timeout
} else {
false
}
}
pub fn elapsed(&self) -> Option<Duration> {
self.start_time.lock().map(|t| t.elapsed())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_config() -> MasterTransferConfig {
MasterTransferConfig::new("node2".to_string(), Duration::from_secs(30))
}
#[test]
fn test_config_builder() {
let config = MasterTransferConfig::new(
"node2".to_string(),
Duration::from_secs(30),
)
.with_force(true);
assert_eq!(config.target_node, "node2");
assert_eq!(config.timeout, Duration::from_secs(30));
assert!(config.force);
}
#[test]
fn test_initial_state() {
let transfer = MasterTransfer::new(test_config());
assert_eq!(transfer.get_state(), TransferState::NotStarted);
assert_eq!(transfer.get_target(), "node2");
}
#[test]
fn test_start() {
let transfer = MasterTransfer::new(test_config());
assert!(transfer.start().is_ok());
assert_eq!(transfer.get_state(), TransferState::InProgress);
assert!(transfer.elapsed().is_some());
}
#[test]
fn test_start_twice_fails() {
let transfer = MasterTransfer::new(test_config());
transfer.start().unwrap();
assert!(transfer.start().is_err());
}
#[test]
fn test_complete() {
let transfer = MasterTransfer::new(test_config());
transfer.start().unwrap();
assert!(transfer.complete().is_ok());
assert_eq!(transfer.get_state(), TransferState::Completed);
}
#[test]
fn test_complete_without_start_fails() {
let transfer = MasterTransfer::new(test_config());
assert!(transfer.complete().is_err());
}
#[test]
fn test_fail() {
let transfer = MasterTransfer::new(test_config());
transfer.start().unwrap();
assert!(transfer.fail("test reason").is_ok());
assert_eq!(transfer.get_state(), TransferState::Failed);
}
#[test]
fn test_timeout_not_started() {
let transfer = MasterTransfer::new(test_config());
assert!(!transfer.is_timed_out());
}
#[test]
fn test_timeout_short() {
let config = MasterTransferConfig::new(
"node2".to_string(),
Duration::from_millis(1),
);
let transfer = MasterTransfer::new(config);
transfer.start().unwrap();
std::thread::sleep(Duration::from_millis(5));
assert!(transfer.is_timed_out());
}
#[test]
fn test_timeout_long() {
let config = MasterTransferConfig::new(
"node2".to_string(),
Duration::from_secs(60),
);
let transfer = MasterTransfer::new(config);
transfer.start().unwrap();
assert!(!transfer.is_timed_out());
}
}