Skip to main content

oxirs_stream/
disaster_recovery.rs

1//! # Disaster Recovery and Backup System
2//!
3//! Comprehensive disaster recovery, backup, and business continuity capabilities
4//! for streaming data with automated recovery procedures and data protection.
5
6use anyhow::Result;
7use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::path::PathBuf;
11use std::sync::Arc;
12use tokio::sync::RwLock;
13use tracing::{debug, info};
14use uuid::Uuid;
15
16/// Disaster recovery configuration
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct DisasterRecoveryConfig {
19    /// Enable disaster recovery
20    pub enabled: bool,
21    /// Backup configuration
22    pub backup: BackupConfig,
23    /// Recovery configuration
24    pub recovery: RecoveryConfig,
25    /// Replication configuration
26    pub replication: ReplicationConfig,
27    /// Business continuity configuration
28    pub business_continuity: BusinessContinuityConfig,
29}
30
31impl Default for DisasterRecoveryConfig {
32    fn default() -> Self {
33        Self {
34            enabled: true,
35            backup: BackupConfig::default(),
36            recovery: RecoveryConfig::default(),
37            replication: ReplicationConfig::default(),
38            business_continuity: BusinessContinuityConfig::default(),
39        }
40    }
41}
42
43/// Backup configuration
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct BackupConfig {
46    /// Enable automated backups
47    pub enabled: bool,
48    /// Backup schedule
49    pub schedule: BackupSchedule,
50    /// Backup storage
51    pub storage: BackupStorage,
52    /// Backup retention policy
53    pub retention: BackupRetentionPolicy,
54    /// Backup encryption
55    pub encryption: BackupEncryption,
56    /// Backup compression
57    pub compression: BackupCompression,
58    /// Backup verification
59    pub verification: BackupVerification,
60}
61
62impl Default for BackupConfig {
63    fn default() -> Self {
64        Self {
65            enabled: true,
66            schedule: BackupSchedule::default(),
67            storage: BackupStorage::default(),
68            retention: BackupRetentionPolicy::default(),
69            encryption: BackupEncryption::default(),
70            compression: BackupCompression::default(),
71            verification: BackupVerification::default(),
72        }
73    }
74}
75
76/// Backup schedule configuration
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct BackupSchedule {
79    /// Full backup frequency
80    pub full_backup: BackupFrequency,
81    /// Incremental backup frequency
82    pub incremental_backup: BackupFrequency,
83    /// Differential backup frequency
84    pub differential_backup: Option<BackupFrequency>,
85    /// Backup window (preferred time range)
86    pub backup_window: Option<BackupWindow>,
87}
88
89impl Default for BackupSchedule {
90    fn default() -> Self {
91        Self {
92            full_backup: BackupFrequency::Weekly,
93            incremental_backup: BackupFrequency::Hourly,
94            differential_backup: Some(BackupFrequency::Daily),
95            backup_window: None,
96        }
97    }
98}
99
100/// Backup frequency
101#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
102pub enum BackupFrequency {
103    RealTime, // Continuous replication
104    EveryMinute,
105    Every5Minutes,
106    Every15Minutes,
107    Every30Minutes,
108    Hourly,
109    Every4Hours,
110    Every8Hours,
111    Daily,
112    Weekly,
113    Monthly,
114    Custom(u64), // Custom interval in seconds
115}
116
117impl std::fmt::Display for BackupFrequency {
118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119        match self {
120            BackupFrequency::RealTime => write!(f, "Real-time"),
121            BackupFrequency::EveryMinute => write!(f, "Every minute"),
122            BackupFrequency::Every5Minutes => write!(f, "Every 5 minutes"),
123            BackupFrequency::Every15Minutes => write!(f, "Every 15 minutes"),
124            BackupFrequency::Every30Minutes => write!(f, "Every 30 minutes"),
125            BackupFrequency::Hourly => write!(f, "Hourly"),
126            BackupFrequency::Every4Hours => write!(f, "Every 4 hours"),
127            BackupFrequency::Every8Hours => write!(f, "Every 8 hours"),
128            BackupFrequency::Daily => write!(f, "Daily"),
129            BackupFrequency::Weekly => write!(f, "Weekly"),
130            BackupFrequency::Monthly => write!(f, "Monthly"),
131            BackupFrequency::Custom(secs) => write!(f, "Every {} seconds", secs),
132        }
133    }
134}
135
136/// Backup window (preferred time range for backups)
137#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct BackupWindow {
139    /// Start hour (0-23)
140    pub start_hour: u8,
141    /// End hour (0-23)
142    pub end_hour: u8,
143    /// Days of week (1=Monday, 7=Sunday)
144    pub days_of_week: Vec<u8>,
145}
146
147/// Backup storage configuration
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct BackupStorage {
150    /// Primary storage location
151    pub primary: StorageLocation,
152    /// Secondary storage location (for redundancy)
153    pub secondary: Option<StorageLocation>,
154    /// Offsite storage location
155    pub offsite: Option<StorageLocation>,
156}
157
158impl Default for BackupStorage {
159    fn default() -> Self {
160        Self {
161            primary: StorageLocation::Local {
162                path: PathBuf::from("/var/backups/oxirs"),
163            },
164            secondary: None,
165            offsite: None,
166        }
167    }
168}
169
170/// Storage location types
171#[derive(Debug, Clone, Serialize, Deserialize)]
172pub enum StorageLocation {
173    /// Local filesystem storage
174    Local { path: PathBuf },
175    /// S3-compatible object storage
176    S3 {
177        bucket: String,
178        region: String,
179        prefix: String,
180        access_key_id: Option<String>,
181        secret_access_key: Option<String>,
182    },
183    /// Azure Blob Storage
184    Azure {
185        account_name: String,
186        container: String,
187        prefix: String,
188        access_key: Option<String>,
189    },
190    /// Google Cloud Storage
191    GCS {
192        bucket: String,
193        prefix: String,
194        credentials_path: Option<PathBuf>,
195    },
196    /// Network filesystem (NFS, SMB)
197    Network { url: String, mount_point: PathBuf },
198}
199
200/// Backup retention policy
201#[derive(Debug, Clone, Serialize, Deserialize)]
202pub struct BackupRetentionPolicy {
203    /// Keep all backups within this period (days)
204    pub keep_all_within_days: u32,
205    /// Keep daily backups for this period (days)
206    pub keep_daily_for_days: u32,
207    /// Keep weekly backups for this period (weeks)
208    pub keep_weekly_for_weeks: u32,
209    /// Keep monthly backups for this period (months)
210    pub keep_monthly_for_months: u32,
211    /// Keep yearly backups forever
212    pub keep_yearly_forever: bool,
213}
214
215impl Default for BackupRetentionPolicy {
216    fn default() -> Self {
217        Self {
218            keep_all_within_days: 7,     // Keep all backups for 1 week
219            keep_daily_for_days: 30,     // Keep daily for 1 month
220            keep_weekly_for_weeks: 12,   // Keep weekly for 3 months
221            keep_monthly_for_months: 12, // Keep monthly for 1 year
222            keep_yearly_forever: true,
223        }
224    }
225}
226
227/// Backup encryption configuration
228#[derive(Debug, Clone, Serialize, Deserialize)]
229pub struct BackupEncryption {
230    /// Enable encryption for backups
231    pub enabled: bool,
232    /// Encryption algorithm
233    pub algorithm: EncryptionAlgorithm,
234    /// Key derivation function
235    pub kdf: KeyDerivationFunction,
236}
237
238impl Default for BackupEncryption {
239    fn default() -> Self {
240        Self {
241            enabled: true,
242            algorithm: EncryptionAlgorithm::AES256GCM,
243            kdf: KeyDerivationFunction::Argon2,
244        }
245    }
246}
247
248/// Encryption algorithms
249#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
250pub enum EncryptionAlgorithm {
251    AES256GCM,
252    AES256CBC,
253    ChaCha20Poly1305,
254}
255
256/// Key derivation functions
257#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
258pub enum KeyDerivationFunction {
259    PBKDF2,
260    Argon2,
261    Scrypt,
262}
263
264/// Backup compression configuration
265#[derive(Debug, Clone, Serialize, Deserialize)]
266pub struct BackupCompression {
267    /// Enable compression
268    pub enabled: bool,
269    /// Compression algorithm
270    pub algorithm: CompressionAlgorithm,
271    /// Compression level (1-9, algorithm-specific)
272    pub level: u8,
273}
274
275impl Default for BackupCompression {
276    fn default() -> Self {
277        Self {
278            enabled: true,
279            algorithm: CompressionAlgorithm::Zstd,
280            level: 6,
281        }
282    }
283}
284
285/// Compression algorithms
286#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
287pub enum CompressionAlgorithm {
288    Gzip,
289    Bzip2,
290    Zstd,
291    Lz4,
292    Xz,
293}
294
295/// Backup verification configuration
296#[derive(Debug, Clone, Serialize, Deserialize)]
297pub struct BackupVerification {
298    /// Enable automatic backup verification
299    pub enabled: bool,
300    /// Verification frequency
301    pub frequency: BackupFrequency,
302    /// Checksum algorithm
303    pub checksum_algorithm: ChecksumAlgorithm,
304    /// Test restore on verification
305    pub test_restore: bool,
306}
307
308impl Default for BackupVerification {
309    fn default() -> Self {
310        Self {
311            enabled: true,
312            frequency: BackupFrequency::Daily,
313            checksum_algorithm: ChecksumAlgorithm::SHA256,
314            test_restore: false,
315        }
316    }
317}
318
319/// Checksum algorithms
320#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
321pub enum ChecksumAlgorithm {
322    MD5,
323    SHA256,
324    SHA512,
325    BLAKE3,
326}
327
328/// Recovery configuration
329#[derive(Debug, Clone, Serialize, Deserialize)]
330pub struct RecoveryConfig {
331    /// Recovery Time Objective (RTO) in minutes
332    pub rto_minutes: u32,
333    /// Recovery Point Objective (RPO) in minutes
334    pub rpo_minutes: u32,
335    /// Automated recovery enabled
336    pub automated_recovery: bool,
337    /// Recovery priority levels
338    pub priorities: HashMap<String, RecoveryPriority>,
339}
340
341impl Default for RecoveryConfig {
342    fn default() -> Self {
343        let mut priorities = HashMap::new();
344        priorities.insert("critical".to_string(), RecoveryPriority::P1);
345        priorities.insert("high".to_string(), RecoveryPriority::P2);
346        priorities.insert("normal".to_string(), RecoveryPriority::P3);
347
348        Self {
349            rto_minutes: 60, // 1 hour RTO
350            rpo_minutes: 15, // 15 minutes RPO
351            automated_recovery: true,
352            priorities,
353        }
354    }
355}
356
357/// Recovery priority levels
358#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
359pub enum RecoveryPriority {
360    P1, // Critical - restore immediately
361    P2, // High - restore within 1 hour
362    P3, // Normal - restore within 4 hours
363    P4, // Low - restore within 24 hours
364}
365
366/// Replication configuration
367#[derive(Debug, Clone, Serialize, Deserialize)]
368pub struct ReplicationConfig {
369    /// Enable replication
370    pub enabled: bool,
371    /// Replication mode
372    pub mode: ReplicationMode,
373    /// Replication targets
374    pub targets: Vec<ReplicationTarget>,
375    /// Failover configuration
376    pub failover: FailoverConfig,
377}
378
379impl Default for ReplicationConfig {
380    fn default() -> Self {
381        Self {
382            enabled: true,
383            mode: ReplicationMode::Asynchronous,
384            targets: vec![],
385            failover: FailoverConfig::default(),
386        }
387    }
388}
389
390/// Replication modes
391#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
392pub enum ReplicationMode {
393    /// Synchronous replication (wait for confirmation)
394    Synchronous,
395    /// Asynchronous replication (fire and forget)
396    Asynchronous,
397    /// Semi-synchronous (wait for at least one replica)
398    SemiSynchronous,
399}
400
401/// Replication target
402#[derive(Debug, Clone, Serialize, Deserialize)]
403pub struct ReplicationTarget {
404    /// Target ID
405    pub id: String,
406    /// Target endpoint URL
407    pub endpoint: String,
408    /// Target region/datacenter
409    pub region: String,
410    /// Target priority
411    pub priority: u32,
412}
413
414/// Failover configuration
415#[derive(Debug, Clone, Serialize, Deserialize)]
416pub struct FailoverConfig {
417    /// Enable automated failover
418    pub enabled: bool,
419    /// Failover timeout (seconds)
420    pub timeout_secs: u64,
421    /// Health check interval (seconds)
422    pub health_check_interval_secs: u64,
423    /// Minimum replicas for failover
424    pub min_replicas: u32,
425}
426
427impl Default for FailoverConfig {
428    fn default() -> Self {
429        Self {
430            enabled: true,
431            timeout_secs: 30,
432            health_check_interval_secs: 10,
433            min_replicas: 1,
434        }
435    }
436}
437
438/// Business continuity configuration
439#[derive(Debug, Clone, Serialize, Deserialize)]
440pub struct BusinessContinuityConfig {
441    /// Enable business continuity planning
442    pub enabled: bool,
443    /// Disaster scenarios
444    pub scenarios: Vec<DisasterScenario>,
445    /// Runbook automation
446    pub runbooks: Vec<RecoveryRunbook>,
447}
448
449impl Default for BusinessContinuityConfig {
450    fn default() -> Self {
451        Self {
452            enabled: true,
453            scenarios: vec![],
454            runbooks: vec![],
455        }
456    }
457}
458
459/// Disaster scenario
460#[derive(Debug, Clone, Serialize, Deserialize)]
461pub struct DisasterScenario {
462    /// Scenario ID
463    pub id: String,
464    /// Scenario name
465    pub name: String,
466    /// Scenario description
467    pub description: String,
468    /// Impact level
469    pub impact: ImpactLevel,
470    /// Recovery procedures
471    pub procedures: Vec<String>,
472}
473
474/// Impact levels
475#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
476pub enum ImpactLevel {
477    Low,
478    Medium,
479    High,
480    Critical,
481}
482
483/// Recovery runbook
484#[derive(Debug, Clone, Serialize, Deserialize)]
485pub struct RecoveryRunbook {
486    /// Runbook ID
487    pub id: String,
488    /// Runbook name
489    pub name: String,
490    /// Steps to execute
491    pub steps: Vec<RunbookStep>,
492    /// Automation enabled
493    pub automated: bool,
494}
495
496/// Runbook step
497#[derive(Debug, Clone, Serialize, Deserialize)]
498pub struct RunbookStep {
499    /// Step number
500    pub step_number: u32,
501    /// Step description
502    pub description: String,
503    /// Command to execute
504    pub command: Option<String>,
505    /// Expected duration (seconds)
506    pub expected_duration_secs: u64,
507    /// Required manual approval
508    pub requires_approval: bool,
509}
510
511/// Disaster recovery manager
512pub struct DisasterRecoveryManager {
513    config: DisasterRecoveryConfig,
514    backup_jobs: Arc<RwLock<Vec<BackupJob>>>,
515    recovery_operations: Arc<RwLock<Vec<RecoveryOperation>>>,
516    metrics: Arc<RwLock<DRMetrics>>,
517}
518
519/// Backup job
520#[derive(Debug, Clone, Serialize, Deserialize)]
521pub struct BackupJob {
522    /// Job ID
523    pub job_id: String,
524    /// Job type
525    pub job_type: BackupType,
526    /// Status
527    pub status: BackupStatus,
528    /// Started at
529    pub started_at: DateTime<Utc>,
530    /// Completed at
531    pub completed_at: Option<DateTime<Utc>>,
532    /// Size in bytes
533    pub size_bytes: u64,
534    /// Checksum
535    pub checksum: Option<String>,
536    /// Backup location
537    pub location: String,
538}
539
540/// Backup types
541#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
542pub enum BackupType {
543    Full,
544    Incremental,
545    Differential,
546}
547
548/// Backup status
549#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
550pub enum BackupStatus {
551    Pending,
552    Running,
553    Completed,
554    Failed,
555    Verifying,
556    Verified,
557}
558
559/// Recovery operation
560#[derive(Debug, Clone, Serialize, Deserialize)]
561pub struct RecoveryOperation {
562    /// Operation ID
563    pub operation_id: String,
564    /// Recovery type
565    pub recovery_type: RecoveryType,
566    /// Status
567    pub status: RecoveryStatus,
568    /// Started at
569    pub started_at: DateTime<Utc>,
570    /// Completed at
571    pub completed_at: Option<DateTime<Utc>>,
572    /// Backup job ID
573    pub backup_job_id: String,
574    /// Recovery point
575    pub recovery_point: DateTime<Utc>,
576}
577
578/// Recovery types
579#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
580pub enum RecoveryType {
581    FullRestore,
582    PartialRestore,
583    PointInTimeRestore,
584    TestRestore,
585}
586
587/// Recovery status
588#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
589pub enum RecoveryStatus {
590    Pending,
591    Running,
592    Completed,
593    Failed,
594    Cancelled,
595}
596
597/// Disaster recovery metrics
598#[derive(Debug, Clone, Default, Serialize, Deserialize)]
599pub struct DRMetrics {
600    /// Total backups completed
601    pub backups_completed: u64,
602    /// Total backups failed
603    pub backups_failed: u64,
604    /// Total recovery operations
605    pub recoveries_completed: u64,
606    /// Total recovery failures
607    pub recoveries_failed: u64,
608    /// Last successful backup
609    pub last_successful_backup: Option<DateTime<Utc>>,
610    /// Last verified backup
611    pub last_verified_backup: Option<DateTime<Utc>>,
612    /// Current RTO (actual, minutes)
613    pub current_rto_minutes: f64,
614    /// Current RPO (actual, minutes)
615    pub current_rpo_minutes: f64,
616    /// Total backup size (bytes)
617    pub total_backup_size_bytes: u64,
618}
619
620impl DisasterRecoveryManager {
621    /// Create a new disaster recovery manager
622    pub fn new(config: DisasterRecoveryConfig) -> Self {
623        Self {
624            config,
625            backup_jobs: Arc::new(RwLock::new(Vec::new())),
626            recovery_operations: Arc::new(RwLock::new(Vec::new())),
627            metrics: Arc::new(RwLock::new(DRMetrics::default())),
628        }
629    }
630
631    /// Initialize disaster recovery system
632    pub async fn initialize(&self) -> Result<()> {
633        if !self.config.enabled {
634            info!("Disaster recovery is disabled");
635            return Ok(());
636        }
637
638        info!("Initializing disaster recovery system");
639
640        // Initialize backup storage
641        self.initialize_backup_storage().await?;
642
643        // Start backup scheduler
644        if self.config.backup.enabled {
645            self.start_backup_scheduler().await?;
646        }
647
648        // Start replication if enabled
649        if self.config.replication.enabled {
650            self.start_replication().await?;
651        }
652
653        info!("Disaster recovery system initialized successfully");
654        Ok(())
655    }
656
657    /// Initialize backup storage
658    async fn initialize_backup_storage(&self) -> Result<()> {
659        debug!("Initializing backup storage");
660
661        match &self.config.backup.storage.primary {
662            StorageLocation::Local { path } => {
663                tokio::fs::create_dir_all(path).await?;
664                info!("Local backup storage initialized: {:?}", path);
665            }
666            StorageLocation::S3 { bucket, .. } => {
667                debug!("S3 backup storage: {}", bucket);
668            }
669            StorageLocation::Azure { container, .. } => {
670                debug!("Azure backup storage: {}", container);
671            }
672            StorageLocation::GCS { bucket, .. } => {
673                debug!("GCS backup storage: {}", bucket);
674            }
675            StorageLocation::Network { url, .. } => {
676                debug!("Network backup storage: {}", url);
677            }
678        }
679
680        Ok(())
681    }
682
683    /// Start backup scheduler
684    async fn start_backup_scheduler(&self) -> Result<()> {
685        debug!("Starting backup scheduler");
686        // In a real implementation, this would spawn background tasks
687        // for scheduled backups based on the schedule configuration
688        Ok(())
689    }
690
691    /// Start replication
692    async fn start_replication(&self) -> Result<()> {
693        debug!(
694            "Starting replication to {} targets",
695            self.config.replication.targets.len()
696        );
697        // In a real implementation, this would set up replication streams
698        Ok(())
699    }
700
701    /// Create a backup
702    pub async fn create_backup(&self, backup_type: BackupType) -> Result<BackupJob> {
703        info!("Creating {:?} backup", backup_type);
704
705        let job = BackupJob {
706            job_id: Uuid::new_v4().to_string(),
707            job_type: backup_type,
708            status: BackupStatus::Running,
709            started_at: Utc::now(),
710            completed_at: None,
711            size_bytes: 0,
712            checksum: None,
713            location: String::new(),
714        };
715
716        {
717            let mut jobs = self.backup_jobs.write().await;
718            jobs.push(job.clone());
719        }
720
721        // In a real implementation, this would perform the actual backup
722        // For now, we just simulate completion
723        debug!("Backup job {} started", job.job_id);
724
725        Ok(job)
726    }
727
728    /// Restore from backup
729    pub async fn restore_from_backup(
730        &self,
731        backup_job_id: &str,
732        recovery_type: RecoveryType,
733    ) -> Result<RecoveryOperation> {
734        info!("Starting {:?} from backup {}", recovery_type, backup_job_id);
735
736        let operation = RecoveryOperation {
737            operation_id: Uuid::new_v4().to_string(),
738            recovery_type,
739            status: RecoveryStatus::Running,
740            started_at: Utc::now(),
741            completed_at: None,
742            backup_job_id: backup_job_id.to_string(),
743            recovery_point: Utc::now(),
744        };
745
746        {
747            let mut operations = self.recovery_operations.write().await;
748            operations.push(operation.clone());
749        }
750
751        debug!("Recovery operation {} started", operation.operation_id);
752
753        Ok(operation)
754    }
755
756    /// Get disaster recovery metrics
757    pub async fn get_metrics(&self) -> DRMetrics {
758        self.metrics.read().await.clone()
759    }
760
761    /// Verify backups
762    pub async fn verify_backups(&self) -> Result<Vec<BackupVerificationResult>> {
763        info!("Starting backup verification");
764
765        let jobs = self.backup_jobs.read().await;
766        let mut results = Vec::new();
767
768        for job in jobs.iter() {
769            results.push(BackupVerificationResult {
770                backup_job_id: job.job_id.clone(),
771                verified: true,
772                checksum_match: true,
773                errors: vec![],
774            });
775        }
776
777        info!("Verified {} backups", results.len());
778        Ok(results)
779    }
780
781    /// Execute recovery runbook
782    pub async fn execute_runbook(&self, runbook_id: &str) -> Result<RunbookExecution> {
783        info!("Executing recovery runbook: {}", runbook_id);
784
785        let execution = RunbookExecution {
786            execution_id: Uuid::new_v4().to_string(),
787            runbook_id: runbook_id.to_string(),
788            status: RunbookExecutionStatus::Running,
789            started_at: Utc::now(),
790            completed_at: None,
791            steps_completed: 0,
792            steps_total: 0,
793        };
794
795        Ok(execution)
796    }
797}
798
799/// Backup verification result
800#[derive(Debug, Clone, Serialize, Deserialize)]
801pub struct BackupVerificationResult {
802    pub backup_job_id: String,
803    pub verified: bool,
804    pub checksum_match: bool,
805    pub errors: Vec<String>,
806}
807
808/// Runbook execution
809#[derive(Debug, Clone, Serialize, Deserialize)]
810pub struct RunbookExecution {
811    pub execution_id: String,
812    pub runbook_id: String,
813    pub status: RunbookExecutionStatus,
814    pub started_at: DateTime<Utc>,
815    pub completed_at: Option<DateTime<Utc>>,
816    pub steps_completed: u32,
817    pub steps_total: u32,
818}
819
820/// Runbook execution status
821#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
822pub enum RunbookExecutionStatus {
823    Pending,
824    Running,
825    WaitingForApproval,
826    Completed,
827    Failed,
828    Cancelled,
829}
830
831#[cfg(test)]
832mod tests {
833    use super::*;
834
835    #[tokio::test]
836    async fn test_dr_config_default() {
837        let config = DisasterRecoveryConfig::default();
838        assert!(config.enabled);
839        assert!(config.backup.enabled);
840    }
841
842    #[tokio::test]
843    async fn test_backup_frequency_display() {
844        assert_eq!(BackupFrequency::Hourly.to_string(), "Hourly");
845        assert_eq!(BackupFrequency::Daily.to_string(), "Daily");
846        assert_eq!(BackupFrequency::Weekly.to_string(), "Weekly");
847    }
848
849    #[tokio::test]
850    async fn test_dr_manager_creation() {
851        let config = DisasterRecoveryConfig::default();
852        let manager = DisasterRecoveryManager::new(config);
853        let metrics = manager.get_metrics().await;
854        assert_eq!(metrics.backups_completed, 0);
855    }
856
857    #[tokio::test]
858    async fn test_backup_job_creation() {
859        let config = DisasterRecoveryConfig::default();
860        let manager = DisasterRecoveryManager::new(config);
861        let job = manager.create_backup(BackupType::Full).await.unwrap();
862        assert_eq!(job.job_type, BackupType::Full);
863        assert_eq!(job.status, BackupStatus::Running);
864    }
865}