anya-core 1.2.0

Enterprise-grade Bitcoin Infrastructure Platform
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::RwLock;
use tokio::time::Instant;
use tracing::{debug, info, instrument, warn};

use crate::infrastructure::high_availability::{
    config::HighAvailabilityConfig, FailoverPhase, HaError,
};

/// Failover manager implementing automatic failover patterns
/// Follows the Leader and Followers pattern from distributed systems
/// [AIR-3][AIS-3][RES-3]
#[derive(Debug)]
pub struct FailoverManager {
    config: Arc<HighAvailabilityConfig>,
    current_phase: Arc<RwLock<FailoverPhase>>,
    failover_history: Arc<RwLock<Vec<FailoverEvent>>>,
    enabled: Arc<RwLock<bool>>,
    last_failover_attempt: Arc<RwLock<Option<Instant>>>,
}

/// Represents a failover event in the system
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FailoverEvent {
    pub id: String,
    pub timestamp: chrono::DateTime<chrono::Utc>,
    pub trigger_reason: String,
    pub source_node: Option<String>,
    pub target_node: Option<String>,
    pub phase: FailoverPhase,
    pub duration_ms: Option<u64>,
    pub success: bool,
    pub error: Option<String>,
}

/// Failover triggers
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FailoverTrigger {
    /// Node failure detected
    NodeFailure(String),
    /// Health check failure
    HealthCheckFailure(String),
    /// Manual failover requested
    Manual,
    /// Performance degradation
    PerformanceDegradation(String),
    /// Network partition
    NetworkPartition,
}

impl FailoverManager {
    /// Creates a new failover manager
    pub fn new(config: &HighAvailabilityConfig) -> Self {
        Self {
            config: Arc::new(config.clone()),
            current_phase: Arc::new(RwLock::new(FailoverPhase::Completed)),
            failover_history: Arc::new(RwLock::new(Vec::new())),
            enabled: Arc::new(RwLock::new(config.failover.enabled)),
            last_failover_attempt: Arc::new(RwLock::new(None)),
        }
    }

    /// Initializes the failover manager
    #[instrument(skip(self))]
    pub async fn initialize(&mut self) -> Result<(), HaError> {
        info!("Initializing failover manager");

        if !self.config.failover.enabled {
            warn!("Failover is disabled in configuration");
            *self.enabled.write().await = false;
            return Ok(());
        }

        *self.enabled.write().await = true;
        *self.current_phase.write().await = FailoverPhase::Completed;

        info!("Failover manager initialized");
        Ok(())
    }

    /// Enables the failover manager
    #[instrument(skip(self))]
    pub async fn enable(&mut self) -> Result<(), HaError> {
        info!("Enabling failover manager");
        *self.enabled.write().await = true;
        Ok(())
    }

    /// Disables the failover manager
    #[instrument(skip(self))]
    pub async fn disable(&mut self) -> Result<(), HaError> {
        info!("Disabling failover manager");
        *self.enabled.write().await = false;
        Ok(())
    }

    /// Triggers a manual failover
    #[instrument(skip(self))]
    pub async fn trigger_manual_failover(&mut self) -> Result<(), HaError> {
        self.trigger_failover(FailoverTrigger::Manual, None, None)
            .await
    }

    /// Triggers failover for a specific reason
    #[instrument(skip(self))]
    pub async fn trigger_failover(
        &mut self,
        trigger: FailoverTrigger,
        source_node: Option<String>,
        target_node: Option<String>,
    ) -> Result<(), HaError> {
        if !*self.enabled.read().await {
            warn!("Failover is disabled, ignoring trigger: {:?}", trigger);
            return Ok(());
        }

        let current_phase = *self.current_phase.read().await;
        if current_phase != FailoverPhase::Completed {
            warn!(
                "Failover already in progress (phase: {:?}), ignoring new trigger: {:?}",
                current_phase, trigger
            );
            return Err(HaError::FailoverError(
                "Failover already in progress".to_string(),
            ));
        }

        // Check rate limiting
        if let Some(last_attempt) = *self.last_failover_attempt.read().await {
            let elapsed = last_attempt.elapsed();
            let min_interval = Duration::from_secs(30); // Minimum 30 seconds between failovers

            if elapsed < min_interval {
                warn!("Failover rate limited, last attempt was {:?} ago", elapsed);
                return Err(HaError::FailoverError("Failover rate limited".to_string()));
            }
        }

        info!("Triggering failover: {:?}", trigger);
        *self.last_failover_attempt.write().await = Some(Instant::now());

        let event_id = uuid::Uuid::new_v4().to_string();
        let start_time = Instant::now();

        // Execute failover phases
        let result = self
            .execute_failover_sequence(
                event_id.clone(),
                trigger.clone(),
                source_node.clone(),
                target_node.clone(),
                start_time,
            )
            .await;

        // Record the event
        let duration = start_time.elapsed().as_millis() as u64;
        let (success, error) = match &result {
            Ok(_) => (true, None),
            Err(e) => (false, Some(e.to_string())),
        };

        let event = FailoverEvent {
            id: event_id,
            timestamp: chrono::Utc::now(),
            trigger_reason: format!("{trigger:?}"),
            source_node,
            target_node,
            phase: *self.current_phase.read().await,
            duration_ms: Some(duration),
            success,
            error,
        };

        self.failover_history.write().await.push(event);

        // Ensure we're back to completed state
        *self.current_phase.write().await = FailoverPhase::Completed;

        result
    }

    /// Executes the complete failover sequence
    async fn execute_failover_sequence(
        &mut self,
        event_id: String,
        trigger: FailoverTrigger,
        source_node: Option<String>,
        target_node: Option<String>,
        start_time: Instant,
    ) -> Result<(), HaError> {
        // Phase 1: Detection
        *self.current_phase.write().await = FailoverPhase::Detection;
        info!("Failover {}: Detection phase", event_id);
        self.detect_failure(&trigger).await?;

        // Phase 2: Election
        *self.current_phase.write().await = FailoverPhase::Election;
        info!("Failover {}: Election phase", event_id);
        let new_leader = self
            .elect_new_leader(source_node.as_deref(), target_node.as_deref())
            .await?;

        // Phase 3: Promotion
        *self.current_phase.write().await = FailoverPhase::Promotion;
        info!(
            "Failover {}: Promotion phase - promoting {}",
            event_id, new_leader
        );
        self.promote_new_leader(&new_leader).await?;

        // Phase 4: Redirection
        *self.current_phase.write().await = FailoverPhase::Redirection;
        info!("Failover {}: Redirection phase", event_id);
        self.redirect_traffic(&new_leader).await?;

        // Phase 5: Recovery (optional, for failed node)
        if let Some(failed_node) = &source_node {
            *self.current_phase.write().await = FailoverPhase::Recovery;
            info!("Failover {}: Recovery phase for {}", event_id, failed_node);
            self.initiate_recovery(failed_node).await?;
        }

        info!(
            "Failover {} completed successfully in {}ms",
            event_id,
            start_time.elapsed().as_millis()
        );

        Ok(())
    }

    /// Detects and validates the failure
    async fn detect_failure(&self, trigger: &FailoverTrigger) -> Result<(), HaError> {
        debug!("Detecting failure: {:?}", trigger);

        match trigger {
            FailoverTrigger::NodeFailure(node) => {
                // Verify the node is actually down
                if !self.verify_node_failure(node).await? {
                    return Err(HaError::FailoverError(format!(
                        "Node {node} appears to be healthy"
                    )));
                }
            }
            FailoverTrigger::HealthCheckFailure(component) => {
                // Verify health check failure is critical
                if !self.verify_health_failure(component).await? {
                    return Err(HaError::FailoverError(format!(
                        "Component {component} health check not critical"
                    )));
                }
            }
            FailoverTrigger::Manual => {
                // Manual failover always proceeds
                info!("Manual failover requested");
            }
            FailoverTrigger::PerformanceDegradation(reason) => {
                debug!("Performance degradation detected: {}", reason);
            }
            FailoverTrigger::NetworkPartition => {
                debug!("Network partition detected");
            }
        }

        Ok(())
    }

    /// Elects a new leader node
    async fn elect_new_leader(
        &self,
        failed_node: Option<&str>,
        preferred_node: Option<&str>,
    ) -> Result<String, HaError> {
        debug!("Electing new leader");

        // If a preferred node is specified, use it
        if let Some(node) = preferred_node {
            info!("Using preferred node as new leader: {}", node);
            return Ok(node.to_string());
        }

        // In a real implementation, this would:
        // 1. Query available nodes
        // 2. Check their health and eligibility
        // 3. Apply election algorithm (e.g., highest priority, least loaded)
        // 4. Ensure consensus among nodes

        // For now, simulate election
        let available_nodes = self.get_available_nodes(failed_node).await?;

        if available_nodes.is_empty() {
            return Err(HaError::FailoverError(
                "No available nodes for promotion".to_string(),
            ));
        }

        // Select the first available node (in real implementation, use proper election logic)
        let new_leader = available_nodes[0].clone();
        info!("Elected new leader: {}", new_leader);

        Ok(new_leader)
    }

    /// Promotes a node to leader
    async fn promote_new_leader(&self, node: &str) -> Result<(), HaError> {
        debug!("Promoting {} to leader", node);

        // In a real implementation, this would:
        // 1. Update the node's role to leader
        // 2. Initialize leader-specific services
        // 3. Update cluster metadata
        // 4. Notify other nodes

        tokio::time::sleep(Duration::from_millis(100)).await; // Simulate promotion time

        info!("Successfully promoted {} to leader", node);
        Ok(())
    }

    /// Redirects traffic to the new leader
    async fn redirect_traffic(&self, new_leader: &str) -> Result<(), HaError> {
        debug!("Redirecting traffic to {}", new_leader);

        // In a real implementation, this would:
        // 1. Update load balancer configuration
        // 2. Update DNS records
        // 3. Notify clients
        // 4. Update service discovery

        tokio::time::sleep(Duration::from_millis(50)).await; // Simulate redirection time

        info!("Successfully redirected traffic to {}", new_leader);
        Ok(())
    }

    /// Initiates recovery for a failed node
    async fn initiate_recovery(&self, failed_node: &str) -> Result<(), HaError> {
        debug!("Initiating recovery for {}", failed_node);

        // In a real implementation, this would:
        // 1. Try to restart the node
        // 2. Check if it can rejoin the cluster
        // 3. Sync any missed data
        // 4. Update its role appropriately

        info!("Recovery initiated for {}", failed_node);
        Ok(())
    }

    /// Verifies that a node has actually failed
    async fn verify_node_failure(&self, node: &str) -> Result<bool, HaError> {
        debug!("Verifying failure of node: {}", node);

        // In a real implementation, this would:
        // 1. Try to ping the node
        // 2. Check heartbeat timestamps
        // 3. Verify with other nodes
        // 4. Check network connectivity

        // For simulation, assume the node is indeed failed
        Ok(true)
    }

    /// Verifies that a health check failure is critical
    async fn verify_health_failure(&self, component: &str) -> Result<bool, HaError> {
        debug!("Verifying health failure of component: {}", component);

        // In a real implementation, this would:
        // 1. Check the severity of the health failure
        // 2. Verify with multiple health checks
        // 3. Check if it affects critical functionality

        // For simulation, assume it's critical
        Ok(true)
    }

    /// Gets list of available nodes for promotion
    async fn get_available_nodes(
        &self,
        exclude_node: Option<&str>,
    ) -> Result<Vec<String>, HaError> {
        // In a real implementation, this would query the cluster manager
        let mut nodes = vec![
            "node-1".to_string(),
            "node-2".to_string(),
            "node-3".to_string(),
        ];

        // Remove the failed node
        if let Some(failed) = exclude_node {
            nodes.retain(|n| n != failed);
        }

        Ok(nodes)
    }

    /// Updates the failover manager configuration
    #[instrument(skip(self, config))]
    pub async fn update_config(&mut self, config: &HighAvailabilityConfig) -> Result<(), HaError> {
        info!("Updating failover manager configuration");
        self.config = Arc::new(config.clone());
        *self.enabled.write().await = config.failover.enabled;
        Ok(())
    }

    /// Gets the current failover phase
    pub async fn get_current_phase(&self) -> FailoverPhase {
        *self.current_phase.read().await
    }

    /// Gets the failover history
    pub async fn get_failover_history(&self) -> Vec<FailoverEvent> {
        self.failover_history.read().await.clone()
    }

    /// Checks if failover is currently active
    pub async fn is_failover_active(&self) -> bool {
        *self.current_phase.read().await != FailoverPhase::Completed
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::infrastructure::high_availability::config::FailoverConfig;

    fn create_test_config() -> HighAvailabilityConfig {
        HighAvailabilityConfig {
            failover: FailoverConfig {
                enabled: true,
                auto_failover: true,
                failover_timeout: Duration::from_secs(30),
                min_nodes_for_failover: 2,
                max_auto_failovers: Some(3),
                auto_failover_period: Duration::from_secs(3600),
                fencing_enabled: true,
            },
            ..Default::default()
        }
    }

    #[tokio::test]
    async fn test_failover_manager_creation() {
        let config = create_test_config();
        let manager = FailoverManager::new(&config);

        assert!(!manager.is_failover_active().await);
        assert_eq!(manager.get_current_phase().await, FailoverPhase::Completed);
    }

    #[tokio::test]
    async fn test_enable_disable() {
        let config = create_test_config();
        let mut manager = FailoverManager::new(&config);

        manager.initialize().await.unwrap();
        assert!(*manager.enabled.read().await);

        manager.disable().await.unwrap();
        assert!(!*manager.enabled.read().await);

        manager.enable().await.unwrap();
        assert!(*manager.enabled.read().await);
    }

    #[tokio::test]
    async fn test_manual_failover() {
        let config = create_test_config();
        let mut manager = FailoverManager::new(&config);

        manager.initialize().await.unwrap();

        let result = manager.trigger_manual_failover().await;
        assert!(result.is_ok());

        let history = manager.get_failover_history().await;
        assert_eq!(history.len(), 1);
        assert!(history[0].success);
    }
}