use std::time::SystemTime;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecoveryStatus {
pub store_name: String,
pub step: u32,
pub step_name: String,
pub progress_percent: u32,
pub estimated_time_remaining_secs: u64,
pub errors: Vec<String>,
pub started_at: i64,
}
impl RecoveryStatus {
pub fn new(store_name: String) -> Self {
Self {
store_name,
step: 1,
step_name: "Assessing damage".to_string(),
progress_percent: 0,
estimated_time_remaining_secs: 3600, errors: Vec::new(),
started_at: SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0),
}
}
pub fn next_step(&mut self) -> bool {
if self.step >= 6 {
return false;
}
self.step += 1;
self.step_name = match self.step {
1 => "Assessing damage",
2 => "Restoring PostgreSQL",
3 => "Restoring Redis",
4 => "Restoring ClickHouse",
5 => "Restoring Elasticsearch",
6 => "Verification and restart",
_ => "Unknown",
}
.to_string();
self.progress_percent = (self.step * 16) + 1;
true
}
pub fn add_error(&mut self, error: String) {
self.errors.push(error);
}
pub fn is_complete(&self) -> bool {
self.step >= 6 && self.errors.is_empty()
}
pub fn is_failed(&self) -> bool {
!self.errors.is_empty()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecoveryChecklistItem {
pub id: String,
pub description: String,
pub completed: bool,
pub notes: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct RecoveryChecklist {
pub items: Vec<RecoveryChecklistItem>,
}
impl RecoveryChecklist {
pub fn new() -> Self {
Self {
items: vec![
RecoveryChecklistItem {
id: "assess".to_string(),
description: "Assess data loss and impact".to_string(),
completed: false,
notes: None,
},
RecoveryChecklistItem {
id: "notify".to_string(),
description: "Notify stakeholders of RTO/RPO".to_string(),
completed: false,
notes: None,
},
RecoveryChecklistItem {
id: "postgres".to_string(),
description: "Restore PostgreSQL from backup".to_string(),
completed: false,
notes: None,
},
RecoveryChecklistItem {
id: "redis".to_string(),
description: "Restore Redis from dump".to_string(),
completed: false,
notes: None,
},
RecoveryChecklistItem {
id: "clickhouse".to_string(),
description: "Restore ClickHouse from snapshot".to_string(),
completed: false,
notes: None,
},
RecoveryChecklistItem {
id: "elasticsearch".to_string(),
description: "Restore Elasticsearch indices".to_string(),
completed: false,
notes: None,
},
RecoveryChecklistItem {
id: "verify".to_string(),
description: "Verify data integrity and consistency".to_string(),
completed: false,
notes: None,
},
RecoveryChecklistItem {
id: "test".to_string(),
description: "Run acceptance tests".to_string(),
completed: false,
notes: None,
},
RecoveryChecklistItem {
id: "restart".to_string(),
description: "Restart all services".to_string(),
completed: false,
notes: None,
},
RecoveryChecklistItem {
id: "monitor".to_string(),
description: "Monitor applications for issues".to_string(),
completed: false,
notes: None,
},
],
}
}
pub fn complete_item(&mut self, id: &str, notes: Option<String>) -> bool {
for item in &mut self.items {
if item.id == id {
item.completed = true;
item.notes = notes;
return true;
}
}
false
}
pub fn completion_percent(&self) -> u32 {
let completed = self.items.iter().filter(|i| i.completed).count();
((completed as u32 * 100) / self.items.len() as u32).min(100)
}
pub fn is_complete(&self) -> bool {
self.items.iter().all(|i| i.completed)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_recovery_status_steps() {
let mut status = RecoveryStatus::new("postgres".to_string());
assert_eq!(status.step, 1);
status.next_step();
assert_eq!(status.step, 2);
assert_eq!(status.step_name, "Restoring PostgreSQL");
for _ in 0..4 {
status.next_step();
}
assert_eq!(status.step, 6);
assert!(!status.next_step()); }
#[test]
fn test_recovery_status_complete() {
let mut status = RecoveryStatus::new("postgres".to_string());
for _ in 0..5 {
status.next_step();
}
assert!(status.is_complete());
}
#[test]
fn test_recovery_checklist() {
let mut checklist = RecoveryChecklist::new();
assert_eq!(checklist.completion_percent(), 0);
checklist.complete_item("assess", Some("Data loss confirmed".to_string()));
assert!(checklist.completion_percent() > 0);
for item in &mut checklist.items {
item.completed = true;
}
assert!(checklist.is_complete());
assert_eq!(checklist.completion_percent(), 100);
}
}