1use std::{collections::HashMap, sync::Arc};
4
5use tokio::sync::RwLock;
6
7use super::{
8 backup_config::{BackupConfig, BackupStatus},
9 backup_provider::{BackupError, BackupProvider, BackupResult},
10};
11
12pub struct BackupManager {
14 providers: Arc<RwLock<HashMap<String, Arc<dyn BackupProvider>>>>,
16
17 status_cache: Arc<RwLock<HashMap<String, BackupStatus>>>,
19
20 configs: HashMap<String, BackupConfig>,
22}
23
24impl BackupManager {
25 pub fn new(configs: HashMap<String, BackupConfig>) -> Self {
27 Self {
28 providers: Arc::new(RwLock::new(HashMap::new())),
29 status_cache: Arc::new(RwLock::new(HashMap::new())),
30 configs,
31 }
32 }
33
34 pub async fn register_provider(
36 &self,
37 name: String,
38 provider: Arc<dyn BackupProvider>,
39 ) -> Result<(), String> {
40 let mut providers = self.providers.write().await;
41
42 if providers.contains_key(&name) {
43 return Err(format!("Provider '{}' already registered", name));
44 }
45
46 providers.insert(name, provider);
47 Ok(())
48 }
49
50 pub async fn start(&self) -> Result<(), String> {
52 let providers = self.providers.read().await;
57 for (name, provider) in providers.iter() {
58 match provider.health_check().await {
59 Ok(_) => {
60 eprintln!("✓ Backup provider '{}' healthy", name);
61 },
62 Err(e) => {
63 eprintln!("✗ Backup provider '{}' failed health check: {:?}", name, e);
64 },
65 }
66 }
67
68 Ok(())
69 }
70
71 pub async fn backup(&self, provider_name: &str) -> BackupResult<()> {
73 let providers = self.providers.read().await;
74
75 let provider = providers.get(provider_name).ok_or_else(|| BackupError::BackupFailed {
76 store: provider_name.to_string(),
77 message: "Provider not registered".to_string(),
78 })?;
79
80 let config = self.configs.get(provider_name).ok_or_else(|| BackupError::BackupFailed {
81 store: provider_name.to_string(),
82 message: "No configuration found".to_string(),
83 })?;
84
85 if !config.enabled {
86 return Err(BackupError::BackupFailed {
87 store: provider_name.to_string(),
88 message: "Backups disabled".to_string(),
89 });
90 }
91
92 let backup_future = provider.backup();
94 let timeout_duration = config.timeout();
95
96 let result = tokio::time::timeout(timeout_duration, backup_future).await.map_err(|_| {
97 BackupError::Timeout {
98 store: provider_name.to_string(),
99 }
100 })?;
101
102 match result {
103 Ok(backup_info) => {
104 let mut cache = self.status_cache.write().await;
106 cache.insert(
107 provider_name.to_string(),
108 BackupStatus {
109 store_name: provider_name.to_string(),
110 enabled: config.enabled,
111 last_successful_backup: Some(backup_info.timestamp),
112 last_backup_size: Some(backup_info.size_bytes),
113 available_backups: 1, last_error: None,
115 status: "healthy".to_string(),
116 },
117 );
118 Ok(())
119 },
120 Err(e) => {
121 let mut cache = self.status_cache.write().await;
123 cache.insert(
124 provider_name.to_string(),
125 BackupStatus {
126 store_name: provider_name.to_string(),
127 enabled: config.enabled,
128 last_successful_backup: None,
129 last_backup_size: None,
130 available_backups: 0,
131 last_error: Some(e.to_string()),
132 status: "error".to_string(),
133 },
134 );
135 Err(e)
136 },
137 }
138 }
139
140 pub async fn get_status(&self) -> HashMap<String, BackupStatus> {
142 self.status_cache.read().await.clone()
143 }
144
145 pub async fn get_provider_status(&self, provider_name: &str) -> Option<BackupStatus> {
147 self.status_cache.read().await.get(provider_name).cloned()
148 }
149
150 pub async fn restore(&self, provider_name: &str, backup_id: &str) -> BackupResult<()> {
152 let providers = self.providers.read().await;
153
154 let provider = providers.get(provider_name).ok_or_else(|| BackupError::RestoreFailed {
155 store: provider_name.to_string(),
156 message: "Provider not registered".to_string(),
157 })?;
158
159 let config = self.configs.get(provider_name).ok_or_else(|| BackupError::RestoreFailed {
160 store: provider_name.to_string(),
161 message: "No configuration found".to_string(),
162 })?;
163
164 provider.restore(backup_id, config.verify_after_backup).await
165 }
166
167 pub async fn list_backups(&self, provider_name: &str) -> BackupResult<Vec<String>> {
169 let providers = self.providers.read().await;
170
171 let provider = providers.get(provider_name).ok_or_else(|| BackupError::BackupFailed {
172 store: provider_name.to_string(),
173 message: "Provider not registered".to_string(),
174 })?;
175
176 let backups = provider.list_backups().await?;
177 Ok(backups.iter().map(|b| b.backup_id.clone()).collect())
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184 use crate::backup::backup_provider::{BackupInfo, StorageUsage};
185
186 struct MockBackupProvider {
188 name: String,
189 }
190
191 #[async_trait::async_trait]
192 impl BackupProvider for MockBackupProvider {
193 fn name(&self) -> &str {
194 &self.name
195 }
196
197 async fn health_check(&self) -> BackupResult<()> {
198 Ok(())
199 }
200
201 async fn backup(&self) -> BackupResult<BackupInfo> {
202 Ok(BackupInfo {
203 backup_id: format!("{}-backup-1", self.name),
204 store_name: self.name.clone(),
205 timestamp: 1_000_000,
206 size_bytes: 1024 * 1024,
207 verified: true,
208 compression: Some("gzip".to_string()),
209 metadata: Default::default(),
210 })
211 }
212
213 async fn restore(&self, _backup_id: &str, _verify: bool) -> BackupResult<()> {
214 Ok(())
215 }
216
217 async fn list_backups(&self) -> BackupResult<Vec<BackupInfo>> {
218 Ok(vec![BackupInfo {
219 backup_id: format!("{}-backup-1", self.name),
220 store_name: self.name.clone(),
221 timestamp: 1_000_000,
222 size_bytes: 1024 * 1024,
223 verified: true,
224 compression: Some("gzip".to_string()),
225 metadata: Default::default(),
226 }])
227 }
228
229 async fn get_backup(&self, backup_id: &str) -> BackupResult<BackupInfo> {
230 Ok(BackupInfo {
231 backup_id: backup_id.to_string(),
232 store_name: self.name.clone(),
233 timestamp: 1_000_000,
234 size_bytes: 1024 * 1024,
235 verified: true,
236 compression: Some("gzip".to_string()),
237 metadata: Default::default(),
238 })
239 }
240
241 async fn delete_backup(&self, _backup_id: &str) -> BackupResult<()> {
242 Ok(())
243 }
244
245 async fn verify_backup(&self, _backup_id: &str) -> BackupResult<()> {
246 Ok(())
247 }
248
249 async fn get_storage_usage(&self) -> BackupResult<StorageUsage> {
250 Ok(StorageUsage {
251 total_bytes: 1024 * 1024 * 100,
252 backup_count: 7,
253 oldest_backup_timestamp: Some(999_999),
254 newest_backup_timestamp: Some(1_000_000),
255 })
256 }
257 }
258
259 #[tokio::test]
260 async fn test_register_provider() {
261 let configs = HashMap::new();
262 let manager = BackupManager::new(configs);
263
264 let provider = Arc::new(MockBackupProvider {
265 name: "postgres".to_string(),
266 });
267
268 assert!(manager.register_provider("postgres".to_string(), provider).await.is_ok());
269 }
270
271 #[tokio::test]
272 async fn test_duplicate_provider() {
273 let configs = HashMap::new();
274 let manager = BackupManager::new(configs);
275
276 let provider = Arc::new(MockBackupProvider {
277 name: "postgres".to_string(),
278 });
279
280 manager.register_provider("postgres".to_string(), provider.clone()).await.ok();
281 let result = manager.register_provider("postgres".to_string(), provider).await;
282
283 assert!(result.is_err());
284 }
285
286 #[tokio::test]
287 async fn test_backup_updates_status() {
288 let mut configs = HashMap::new();
289 configs.insert("postgres".to_string(), BackupConfig::postgres_default());
290
291 let manager = BackupManager::new(configs);
292
293 let provider = Arc::new(MockBackupProvider {
294 name: "postgres".to_string(),
295 });
296
297 manager.register_provider("postgres".to_string(), provider).await.unwrap();
298
299 manager.backup("postgres").await.unwrap();
300
301 let status = manager.get_provider_status("postgres").await;
302 assert!(status.is_some());
303 assert_eq!(status.unwrap().status, "healthy");
304 }
305
306 #[tokio::test]
307 async fn test_list_backups() {
308 let configs = HashMap::new();
309 let manager = BackupManager::new(configs);
310
311 let provider = Arc::new(MockBackupProvider {
312 name: "postgres".to_string(),
313 });
314
315 manager.register_provider("postgres".to_string(), provider).await.unwrap();
316
317 let backups = manager.list_backups("postgres").await.unwrap();
318 assert_eq!(backups.len(), 1);
319 assert!(backups[0].contains("backup-1"));
320 }
321}