anya_core/infrastructure/high_availability/
failover.rs1use serde::{Deserialize, Serialize};
2use std::sync::Arc;
3use std::time::Duration;
4use tokio::sync::RwLock;
5use tokio::time::Instant;
6use tracing::{debug, info, instrument, warn};
7
8use crate::infrastructure::high_availability::{
9 config::HighAvailabilityConfig, FailoverPhase, HaError,
10};
11
12#[derive(Debug)]
16pub struct FailoverManager {
17 config: Arc<HighAvailabilityConfig>,
18 current_phase: Arc<RwLock<FailoverPhase>>,
19 failover_history: Arc<RwLock<Vec<FailoverEvent>>>,
20 enabled: Arc<RwLock<bool>>,
21 last_failover_attempt: Arc<RwLock<Option<Instant>>>,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct FailoverEvent {
27 pub id: String,
28 pub timestamp: chrono::DateTime<chrono::Utc>,
29 pub trigger_reason: String,
30 pub source_node: Option<String>,
31 pub target_node: Option<String>,
32 pub phase: FailoverPhase,
33 pub duration_ms: Option<u64>,
34 pub success: bool,
35 pub error: Option<String>,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
40pub enum FailoverTrigger {
41 NodeFailure(String),
43 HealthCheckFailure(String),
45 Manual,
47 PerformanceDegradation(String),
49 NetworkPartition,
51}
52
53impl FailoverManager {
54 pub fn new(config: &HighAvailabilityConfig) -> Self {
56 Self {
57 config: Arc::new(config.clone()),
58 current_phase: Arc::new(RwLock::new(FailoverPhase::Completed)),
59 failover_history: Arc::new(RwLock::new(Vec::new())),
60 enabled: Arc::new(RwLock::new(config.failover.enabled)),
61 last_failover_attempt: Arc::new(RwLock::new(None)),
62 }
63 }
64
65 #[instrument(skip(self))]
67 pub async fn initialize(&mut self) -> Result<(), HaError> {
68 info!("Initializing failover manager");
69
70 if !self.config.failover.enabled {
71 warn!("Failover is disabled in configuration");
72 *self.enabled.write().await = false;
73 return Ok(());
74 }
75
76 *self.enabled.write().await = true;
77 *self.current_phase.write().await = FailoverPhase::Completed;
78
79 info!("Failover manager initialized");
80 Ok(())
81 }
82
83 #[instrument(skip(self))]
85 pub async fn enable(&mut self) -> Result<(), HaError> {
86 info!("Enabling failover manager");
87 *self.enabled.write().await = true;
88 Ok(())
89 }
90
91 #[instrument(skip(self))]
93 pub async fn disable(&mut self) -> Result<(), HaError> {
94 info!("Disabling failover manager");
95 *self.enabled.write().await = false;
96 Ok(())
97 }
98
99 #[instrument(skip(self))]
101 pub async fn trigger_manual_failover(&mut self) -> Result<(), HaError> {
102 self.trigger_failover(FailoverTrigger::Manual, None, None)
103 .await
104 }
105
106 #[instrument(skip(self))]
108 pub async fn trigger_failover(
109 &mut self,
110 trigger: FailoverTrigger,
111 source_node: Option<String>,
112 target_node: Option<String>,
113 ) -> Result<(), HaError> {
114 if !*self.enabled.read().await {
115 warn!("Failover is disabled, ignoring trigger: {:?}", trigger);
116 return Ok(());
117 }
118
119 let current_phase = *self.current_phase.read().await;
120 if current_phase != FailoverPhase::Completed {
121 warn!(
122 "Failover already in progress (phase: {:?}), ignoring new trigger: {:?}",
123 current_phase, trigger
124 );
125 return Err(HaError::FailoverError(
126 "Failover already in progress".to_string(),
127 ));
128 }
129
130 if let Some(last_attempt) = *self.last_failover_attempt.read().await {
132 let elapsed = last_attempt.elapsed();
133 let min_interval = Duration::from_secs(30); if elapsed < min_interval {
136 warn!("Failover rate limited, last attempt was {:?} ago", elapsed);
137 return Err(HaError::FailoverError("Failover rate limited".to_string()));
138 }
139 }
140
141 info!("Triggering failover: {:?}", trigger);
142 *self.last_failover_attempt.write().await = Some(Instant::now());
143
144 let event_id = uuid::Uuid::new_v4().to_string();
145 let start_time = Instant::now();
146
147 let result = self
149 .execute_failover_sequence(
150 event_id.clone(),
151 trigger.clone(),
152 source_node.clone(),
153 target_node.clone(),
154 start_time,
155 )
156 .await;
157
158 let duration = start_time.elapsed().as_millis() as u64;
160 let (success, error) = match &result {
161 Ok(_) => (true, None),
162 Err(e) => (false, Some(e.to_string())),
163 };
164
165 let event = FailoverEvent {
166 id: event_id,
167 timestamp: chrono::Utc::now(),
168 trigger_reason: format!("{trigger:?}"),
169 source_node,
170 target_node,
171 phase: *self.current_phase.read().await,
172 duration_ms: Some(duration),
173 success,
174 error,
175 };
176
177 self.failover_history.write().await.push(event);
178
179 *self.current_phase.write().await = FailoverPhase::Completed;
181
182 result
183 }
184
185 async fn execute_failover_sequence(
187 &mut self,
188 event_id: String,
189 trigger: FailoverTrigger,
190 source_node: Option<String>,
191 target_node: Option<String>,
192 start_time: Instant,
193 ) -> Result<(), HaError> {
194 *self.current_phase.write().await = FailoverPhase::Detection;
196 info!("Failover {}: Detection phase", event_id);
197 self.detect_failure(&trigger).await?;
198
199 *self.current_phase.write().await = FailoverPhase::Election;
201 info!("Failover {}: Election phase", event_id);
202 let new_leader = self
203 .elect_new_leader(source_node.as_deref(), target_node.as_deref())
204 .await?;
205
206 *self.current_phase.write().await = FailoverPhase::Promotion;
208 info!(
209 "Failover {}: Promotion phase - promoting {}",
210 event_id, new_leader
211 );
212 self.promote_new_leader(&new_leader).await?;
213
214 *self.current_phase.write().await = FailoverPhase::Redirection;
216 info!("Failover {}: Redirection phase", event_id);
217 self.redirect_traffic(&new_leader).await?;
218
219 if let Some(failed_node) = &source_node {
221 *self.current_phase.write().await = FailoverPhase::Recovery;
222 info!("Failover {}: Recovery phase for {}", event_id, failed_node);
223 self.initiate_recovery(failed_node).await?;
224 }
225
226 info!(
227 "Failover {} completed successfully in {}ms",
228 event_id,
229 start_time.elapsed().as_millis()
230 );
231
232 Ok(())
233 }
234
235 async fn detect_failure(&self, trigger: &FailoverTrigger) -> Result<(), HaError> {
237 debug!("Detecting failure: {:?}", trigger);
238
239 match trigger {
240 FailoverTrigger::NodeFailure(node) => {
241 if !self.verify_node_failure(node).await? {
243 return Err(HaError::FailoverError(format!(
244 "Node {node} appears to be healthy"
245 )));
246 }
247 }
248 FailoverTrigger::HealthCheckFailure(component) => {
249 if !self.verify_health_failure(component).await? {
251 return Err(HaError::FailoverError(format!(
252 "Component {component} health check not critical"
253 )));
254 }
255 }
256 FailoverTrigger::Manual => {
257 info!("Manual failover requested");
259 }
260 FailoverTrigger::PerformanceDegradation(reason) => {
261 debug!("Performance degradation detected: {}", reason);
262 }
263 FailoverTrigger::NetworkPartition => {
264 debug!("Network partition detected");
265 }
266 }
267
268 Ok(())
269 }
270
271 async fn elect_new_leader(
273 &self,
274 failed_node: Option<&str>,
275 preferred_node: Option<&str>,
276 ) -> Result<String, HaError> {
277 debug!("Electing new leader");
278
279 if let Some(node) = preferred_node {
281 info!("Using preferred node as new leader: {}", node);
282 return Ok(node.to_string());
283 }
284
285 let available_nodes = self.get_available_nodes(failed_node).await?;
293
294 if available_nodes.is_empty() {
295 return Err(HaError::FailoverError(
296 "No available nodes for promotion".to_string(),
297 ));
298 }
299
300 let new_leader = available_nodes[0].clone();
302 info!("Elected new leader: {}", new_leader);
303
304 Ok(new_leader)
305 }
306
307 async fn promote_new_leader(&self, node: &str) -> Result<(), HaError> {
309 debug!("Promoting {} to leader", node);
310
311 tokio::time::sleep(Duration::from_millis(100)).await; info!("Successfully promoted {} to leader", node);
320 Ok(())
321 }
322
323 async fn redirect_traffic(&self, new_leader: &str) -> Result<(), HaError> {
325 debug!("Redirecting traffic to {}", new_leader);
326
327 tokio::time::sleep(Duration::from_millis(50)).await; info!("Successfully redirected traffic to {}", new_leader);
336 Ok(())
337 }
338
339 async fn initiate_recovery(&self, failed_node: &str) -> Result<(), HaError> {
341 debug!("Initiating recovery for {}", failed_node);
342
343 info!("Recovery initiated for {}", failed_node);
350 Ok(())
351 }
352
353 async fn verify_node_failure(&self, node: &str) -> Result<bool, HaError> {
355 debug!("Verifying failure of node: {}", node);
356
357 Ok(true)
365 }
366
367 async fn verify_health_failure(&self, component: &str) -> Result<bool, HaError> {
369 debug!("Verifying health failure of component: {}", component);
370
371 Ok(true)
378 }
379
380 async fn get_available_nodes(
382 &self,
383 exclude_node: Option<&str>,
384 ) -> Result<Vec<String>, HaError> {
385 let mut nodes = vec![
387 "node-1".to_string(),
388 "node-2".to_string(),
389 "node-3".to_string(),
390 ];
391
392 if let Some(failed) = exclude_node {
394 nodes.retain(|n| n != failed);
395 }
396
397 Ok(nodes)
398 }
399
400 #[instrument(skip(self, config))]
402 pub async fn update_config(&mut self, config: &HighAvailabilityConfig) -> Result<(), HaError> {
403 info!("Updating failover manager configuration");
404 self.config = Arc::new(config.clone());
405 *self.enabled.write().await = config.failover.enabled;
406 Ok(())
407 }
408
409 pub async fn get_current_phase(&self) -> FailoverPhase {
411 *self.current_phase.read().await
412 }
413
414 pub async fn get_failover_history(&self) -> Vec<FailoverEvent> {
416 self.failover_history.read().await.clone()
417 }
418
419 pub async fn is_failover_active(&self) -> bool {
421 *self.current_phase.read().await != FailoverPhase::Completed
422 }
423}
424
425#[cfg(test)]
426mod tests {
427 use super::*;
428 use crate::infrastructure::high_availability::config::FailoverConfig;
429
430 fn create_test_config() -> HighAvailabilityConfig {
431 HighAvailabilityConfig {
432 failover: FailoverConfig {
433 enabled: true,
434 auto_failover: true,
435 failover_timeout: Duration::from_secs(30),
436 min_nodes_for_failover: 2,
437 max_auto_failovers: Some(3),
438 auto_failover_period: Duration::from_secs(3600),
439 fencing_enabled: true,
440 },
441 ..Default::default()
442 }
443 }
444
445 #[tokio::test]
446 async fn test_failover_manager_creation() {
447 let config = create_test_config();
448 let manager = FailoverManager::new(&config);
449
450 assert!(!manager.is_failover_active().await);
451 assert_eq!(manager.get_current_phase().await, FailoverPhase::Completed);
452 }
453
454 #[tokio::test]
455 async fn test_enable_disable() {
456 let config = create_test_config();
457 let mut manager = FailoverManager::new(&config);
458
459 manager.initialize().await.unwrap();
460 assert!(*manager.enabled.read().await);
461
462 manager.disable().await.unwrap();
463 assert!(!*manager.enabled.read().await);
464
465 manager.enable().await.unwrap();
466 assert!(*manager.enabled.read().await);
467 }
468
469 #[tokio::test]
470 async fn test_manual_failover() {
471 let config = create_test_config();
472 let mut manager = FailoverManager::new(&config);
473
474 manager.initialize().await.unwrap();
475
476 let result = manager.trigger_manual_failover().await;
477 assert!(result.is_ok());
478
479 let history = manager.get_failover_history().await;
480 assert_eq!(history.len(), 1);
481 assert!(history[0].success);
482 }
483}