leptos_sync_core/reliability/
health_checks.rs

1//! Health Checks System
2//!
3//! This module provides comprehensive health checking capabilities including:
4//! - System health monitoring
5//! - Component health verification
6//! - Health status reporting
7//! - Health check scheduling and execution
8
9use std::collections::HashMap;
10use std::sync::Arc;
11use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
12use tokio::sync::RwLock;
13use serde::{Deserialize, Serialize};
14
15/// Health checker for system and component health monitoring
16#[derive(Debug, Clone)]
17pub struct HealthChecker {
18    /// Health checks
19    health_checks: Arc<RwLock<HashMap<String, HealthCheck>>>,
20    /// Health check results
21    results: Arc<RwLock<HashMap<String, HealthCheckResult>>>,
22    /// Health check configuration
23    config: HealthConfig,
24    /// Whether the system is initialized
25    initialized: bool,
26}
27
28impl HealthChecker {
29    /// Create a new health checker
30    pub fn new() -> Self {
31        Self {
32            health_checks: Arc::new(RwLock::new(HashMap::new())),
33            results: Arc::new(RwLock::new(HashMap::new())),
34            config: HealthConfig::default(),
35            initialized: false,
36        }
37    }
38    
39    /// Create a new health checker with configuration
40    pub fn with_config(config: HealthConfig) -> Self {
41        Self {
42            health_checks: Arc::new(RwLock::new(HashMap::new())),
43            results: Arc::new(RwLock::new(HashMap::new())),
44            config,
45            initialized: false,
46        }
47    }
48    
49    /// Initialize the health checker
50    pub async fn initialize(&mut self) -> Result<(), HealthError> {
51        // Initialize default health checks
52        self.add_default_health_checks().await;
53        
54        self.initialized = true;
55        Ok(())
56    }
57    
58    /// Shutdown the health checker
59    pub async fn shutdown(&mut self) -> Result<(), HealthError> {
60        self.initialized = false;
61        Ok(())
62    }
63    
64    /// Check if the system is initialized
65    pub fn is_initialized(&self) -> bool {
66        self.initialized
67    }
68    
69    /// Add a health check
70    pub async fn add_health_check(&self, name: String, health_check: HealthCheck) -> Result<(), HealthError> {
71        if !self.initialized {
72            return Err(HealthError::NotInitialized);
73        }
74        
75        let mut health_checks = self.health_checks.write().await;
76        health_checks.insert(name, health_check);
77        
78        Ok(())
79    }
80    
81    /// Remove a health check
82    pub async fn remove_health_check(&self, name: &str) -> Result<(), HealthError> {
83        if !self.initialized {
84            return Err(HealthError::NotInitialized);
85        }
86        
87        let mut health_checks = self.health_checks.write().await;
88        health_checks.remove(name);
89        
90        let mut results = self.results.write().await;
91        results.remove(name);
92        
93        Ok(())
94    }
95    
96    /// Run a specific health check
97    pub async fn run_health_check(&self, name: &str) -> Result<HealthCheckResult, HealthError> {
98        if !self.initialized {
99            return Err(HealthError::NotInitialized);
100        }
101        
102        let health_checks = self.health_checks.read().await;
103        let health_check = health_checks.get(name)
104            .ok_or_else(|| HealthError::HealthCheckNotFound(name.to_string()))?;
105        
106        let result = self.execute_health_check(health_check).await;
107        
108        // Store the result
109        let mut results = self.results.write().await;
110        results.insert(name.to_string(), result.clone());
111        
112        Ok(result)
113    }
114    
115    /// Run all health checks
116    pub async fn check_all(&self) -> Result<Vec<HealthCheckResult>, HealthError> {
117        if !self.initialized {
118            return Err(HealthError::NotInitialized);
119        }
120        
121        let health_checks = self.health_checks.read().await;
122        let mut results = Vec::new();
123        
124        for (name, health_check) in health_checks.iter() {
125            let result = self.execute_health_check(health_check).await;
126            results.push(result);
127        }
128        
129        // Store all results
130        let mut stored_results = self.results.write().await;
131        for (name, health_check) in health_checks.iter() {
132            let result = self.execute_health_check(health_check).await;
133            stored_results.insert(name.clone(), result);
134        }
135        
136        Ok(results)
137    }
138    
139    /// Get health check result
140    pub async fn get_health_check_result(&self, name: &str) -> Result<Option<HealthCheckResult>, HealthError> {
141        if !self.initialized {
142            return Err(HealthError::NotInitialized);
143        }
144        
145        let results = self.results.read().await;
146        Ok(results.get(name).cloned())
147    }
148    
149    /// Get all health check results
150    pub async fn get_all_results(&self) -> Result<HashMap<String, HealthCheckResult>, HealthError> {
151        if !self.initialized {
152            return Err(HealthError::NotInitialized);
153        }
154        
155        let results = self.results.read().await;
156        Ok(results.clone())
157    }
158    
159    /// Get overall system health
160    pub async fn get_system_health(&self) -> Result<SystemHealth, HealthError> {
161        if !self.initialized {
162            return Err(HealthError::NotInitialized);
163        }
164        
165        let results = self.check_all().await?;
166        let overall_status = if results.iter().all(|r| r.is_healthy) {
167            HealthStatus::Healthy
168        } else {
169            HealthStatus::Unhealthy
170        };
171        
172        Ok(SystemHealth {
173            overall_status,
174            health_checks: results,
175            last_checked: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
176        })
177    }
178    
179    /// Execute a health check
180    async fn execute_health_check(&self, health_check: &HealthCheck) -> HealthCheckResult {
181        let start_time = Instant::now();
182        
183        match health_check.check_type {
184            HealthCheckType::Ping => self.ping_check(health_check).await,
185            HealthCheckType::Http => self.http_check(health_check).await,
186            HealthCheckType::Database => self.database_check(health_check).await,
187            HealthCheckType::Custom => self.custom_check(health_check).await,
188        }
189    }
190    
191    /// Ping health check
192    async fn ping_check(&self, health_check: &HealthCheck) -> HealthCheckResult {
193        let start_time = Instant::now();
194        
195        // Simulate ping check
196        tokio::time::sleep(Duration::from_millis(10)).await;
197        
198        let duration = start_time.elapsed();
199        let is_healthy = duration < Duration::from_millis(100);
200        
201        HealthCheckResult {
202            name: health_check.name.clone(),
203            is_healthy,
204            message: if is_healthy {
205                "Ping successful".to_string()
206            } else {
207                "Ping timeout".to_string()
208            },
209            duration,
210            last_checked: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
211            details: HashMap::new(),
212        }
213    }
214    
215    /// HTTP health check
216    async fn http_check(&self, health_check: &HealthCheck) -> HealthCheckResult {
217        let start_time = Instant::now();
218        
219        // Simulate HTTP check
220        tokio::time::sleep(Duration::from_millis(50)).await;
221        
222        let duration = start_time.elapsed();
223        let is_healthy = duration < Duration::from_millis(500);
224        
225        let mut details = HashMap::new();
226        details.insert("status_code".to_string(), "200".to_string());
227        details.insert("response_time_ms".to_string(), duration.as_millis().to_string());
228        
229        HealthCheckResult {
230            name: health_check.name.clone(),
231            is_healthy,
232            message: if is_healthy {
233                "HTTP check successful".to_string()
234            } else {
235                "HTTP check failed".to_string()
236            },
237            duration,
238            last_checked: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
239            details,
240        }
241    }
242    
243    /// Database health check
244    async fn database_check(&self, health_check: &HealthCheck) -> HealthCheckResult {
245        let start_time = Instant::now();
246        
247        // Simulate database check
248        tokio::time::sleep(Duration::from_millis(20)).await;
249        
250        let duration = start_time.elapsed();
251        let is_healthy = duration < Duration::from_millis(200);
252        
253        let mut details = HashMap::new();
254        details.insert("connection_pool_size".to_string(), "10".to_string());
255        details.insert("active_connections".to_string(), "5".to_string());
256        
257        HealthCheckResult {
258            name: health_check.name.clone(),
259            is_healthy,
260            message: if is_healthy {
261                "Database check successful".to_string()
262            } else {
263                "Database check failed".to_string()
264            },
265            duration,
266            last_checked: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
267            details,
268        }
269    }
270    
271    /// Custom health check
272    async fn custom_check(&self, health_check: &HealthCheck) -> HealthCheckResult {
273        let start_time = Instant::now();
274        
275        // Simulate custom check
276        tokio::time::sleep(Duration::from_millis(30)).await;
277        
278        let duration = start_time.elapsed();
279        let is_healthy = duration < Duration::from_millis(300);
280        
281        HealthCheckResult {
282            name: health_check.name.clone(),
283            is_healthy,
284            message: if is_healthy {
285                "Custom check successful".to_string()
286            } else {
287                "Custom check failed".to_string()
288            },
289            duration,
290            last_checked: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
291            details: HashMap::new(),
292        }
293    }
294    
295    /// Add default health checks
296    async fn add_default_health_checks(&self) {
297        let default_checks = vec![
298            ("system_memory".to_string(), HealthCheck {
299                name: "system_memory".to_string(),
300                check_type: HealthCheckType::Custom,
301                interval: Duration::from_secs(30),
302                timeout: Duration::from_secs(5),
303                enabled: true,
304            }),
305            ("system_cpu".to_string(), HealthCheck {
306                name: "system_cpu".to_string(),
307                check_type: HealthCheckType::Custom,
308                interval: Duration::from_secs(30),
309                timeout: Duration::from_secs(5),
310                enabled: true,
311            }),
312            ("system_disk".to_string(), HealthCheck {
313                name: "system_disk".to_string(),
314                check_type: HealthCheckType::Custom,
315                interval: Duration::from_secs(60),
316                timeout: Duration::from_secs(10),
317                enabled: true,
318            }),
319        ];
320        
321        let mut health_checks = self.health_checks.write().await;
322        for (name, health_check) in default_checks {
323            health_checks.insert(name, health_check);
324        }
325    }
326}
327
328/// Health check data structure
329#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
330pub struct HealthCheck {
331    /// Health check name
332    pub name: String,
333    /// Health check type
334    pub check_type: HealthCheckType,
335    /// Check interval
336    pub interval: Duration,
337    /// Check timeout
338    pub timeout: Duration,
339    /// Whether the check is enabled
340    pub enabled: bool,
341}
342
343/// Health check types
344#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
345pub enum HealthCheckType {
346    /// Ping check
347    Ping,
348    /// HTTP check
349    Http,
350    /// Database check
351    Database,
352    /// Custom check
353    Custom,
354}
355
356/// Health check result
357#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
358pub struct HealthCheckResult {
359    /// Health check name
360    pub name: String,
361    /// Whether the check is healthy
362    pub is_healthy: bool,
363    /// Health check message
364    pub message: String,
365    /// Check duration
366    pub duration: Duration,
367    /// Last checked timestamp
368    pub last_checked: u64,
369    /// Additional details
370    pub details: HashMap<String, String>,
371}
372
373/// System health status
374#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
375pub struct SystemHealth {
376    /// Overall health status
377    pub overall_status: HealthStatus,
378    /// Individual health check results
379    pub health_checks: Vec<HealthCheckResult>,
380    /// Last checked timestamp
381    pub last_checked: u64,
382}
383
384/// Health status
385#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
386pub enum HealthStatus {
387    /// System is healthy
388    Healthy,
389    /// System is unhealthy
390    Unhealthy,
391}
392
393/// Health configuration
394#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
395pub struct HealthConfig {
396    /// Health checks
397    pub health_checks: Vec<HealthCheck>,
398    /// Default check interval
399    pub default_interval: Duration,
400    /// Default check timeout
401    pub default_timeout: Duration,
402    /// Enable health checks
403    pub enable_health_checks: bool,
404}
405
406impl Default for HealthConfig {
407    fn default() -> Self {
408        Self {
409            health_checks: Vec::new(),
410            default_interval: Duration::from_secs(30),
411            default_timeout: Duration::from_secs(5),
412            enable_health_checks: true,
413        }
414    }
415}
416
417/// Health errors
418#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
419pub enum HealthError {
420    /// System not initialized
421    NotInitialized,
422    /// Health check not found
423    HealthCheckNotFound(String),
424    /// Health check execution failed
425    HealthCheckExecutionFailed(String),
426    /// Health check timeout
427    HealthCheckTimeout,
428    /// Configuration error
429    ConfigurationError(String),
430}
431
432impl std::fmt::Display for HealthError {
433    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
434        match self {
435            HealthError::NotInitialized => write!(f, "Health checker not initialized"),
436            HealthError::HealthCheckNotFound(name) => write!(f, "Health check not found: {}", name),
437            HealthError::HealthCheckExecutionFailed(msg) => write!(f, "Health check execution failed: {}", msg),
438            HealthError::HealthCheckTimeout => write!(f, "Health check timeout"),
439            HealthError::ConfigurationError(msg) => write!(f, "Configuration error: {}", msg),
440        }
441    }
442}
443
444impl std::error::Error for HealthError {}
445
446#[cfg(test)]
447mod tests {
448    use super::*;
449    
450    #[tokio::test]
451    async fn test_health_checker_creation() {
452        let checker = HealthChecker::new();
453        assert!(!checker.is_initialized());
454    }
455    
456    #[tokio::test]
457    async fn test_health_checker_initialization() {
458        let mut checker = HealthChecker::new();
459        let result = checker.initialize().await;
460        assert!(result.is_ok());
461        assert!(checker.is_initialized());
462    }
463    
464    #[tokio::test]
465    async fn test_health_checker_shutdown() {
466        let mut checker = HealthChecker::new();
467        checker.initialize().await.unwrap();
468        let result = checker.shutdown().await;
469        assert!(result.is_ok());
470        assert!(!checker.is_initialized());
471    }
472    
473    #[tokio::test]
474    async fn test_add_health_check() {
475        let mut checker = HealthChecker::new();
476        checker.initialize().await.unwrap();
477        
478        let health_check = HealthCheck {
479            name: "test_check".to_string(),
480            check_type: HealthCheckType::Ping,
481            interval: Duration::from_secs(30),
482            timeout: Duration::from_secs(5),
483            enabled: true,
484        };
485        
486        let result = checker.add_health_check("test_check".to_string(), health_check).await;
487        assert!(result.is_ok());
488    }
489    
490    #[tokio::test]
491    async fn test_remove_health_check() {
492        let mut checker = HealthChecker::new();
493        checker.initialize().await.unwrap();
494        
495        let health_check = HealthCheck {
496            name: "test_check".to_string(),
497            check_type: HealthCheckType::Ping,
498            interval: Duration::from_secs(30),
499            timeout: Duration::from_secs(5),
500            enabled: true,
501        };
502        
503        checker.add_health_check("test_check".to_string(), health_check).await.unwrap();
504        
505        let result = checker.remove_health_check("test_check").await;
506        assert!(result.is_ok());
507    }
508    
509    #[tokio::test]
510    async fn test_run_health_check() {
511        let mut checker = HealthChecker::new();
512        checker.initialize().await.unwrap();
513        
514        let health_check = HealthCheck {
515            name: "test_check".to_string(),
516            check_type: HealthCheckType::Ping,
517            interval: Duration::from_secs(30),
518            timeout: Duration::from_secs(5),
519            enabled: true,
520        };
521        
522        checker.add_health_check("test_check".to_string(), health_check).await.unwrap();
523        
524        let result = checker.run_health_check("test_check").await.unwrap();
525        assert_eq!(result.name, "test_check");
526        assert!(result.is_healthy);
527        assert!(!result.message.is_empty());
528    }
529    
530    #[tokio::test]
531    async fn test_run_health_check_not_found() {
532        let mut checker = HealthChecker::new();
533        checker.initialize().await.unwrap();
534        
535        let result = checker.run_health_check("nonexistent").await;
536        assert!(result.is_err());
537        assert!(matches!(result.unwrap_err(), HealthError::HealthCheckNotFound(_)));
538    }
539    
540    #[tokio::test]
541    async fn test_check_all() {
542        let mut checker = HealthChecker::new();
543        checker.initialize().await.unwrap();
544        
545        let results = checker.check_all().await.unwrap();
546        assert!(!results.is_empty());
547        
548        // Should have default health checks
549        assert!(results.iter().any(|r| r.name == "system_memory"));
550        assert!(results.iter().any(|r| r.name == "system_cpu"));
551        assert!(results.iter().any(|r| r.name == "system_disk"));
552    }
553    
554    #[tokio::test]
555    async fn test_get_health_check_result() {
556        let mut checker = HealthChecker::new();
557        checker.initialize().await.unwrap();
558        
559        let health_check = HealthCheck {
560            name: "test_check".to_string(),
561            check_type: HealthCheckType::Ping,
562            interval: Duration::from_secs(30),
563            timeout: Duration::from_secs(5),
564            enabled: true,
565        };
566        
567        checker.add_health_check("test_check".to_string(), health_check).await.unwrap();
568        checker.run_health_check("test_check").await.unwrap();
569        
570        let result = checker.get_health_check_result("test_check").await.unwrap();
571        assert!(result.is_some());
572        let result = result.unwrap();
573        assert_eq!(result.name, "test_check");
574        assert!(result.is_healthy);
575    }
576    
577    #[tokio::test]
578    async fn test_get_all_results() {
579        let mut checker = HealthChecker::new();
580        checker.initialize().await.unwrap();
581        
582        checker.check_all().await.unwrap();
583        
584        let results = checker.get_all_results().await.unwrap();
585        assert!(!results.is_empty());
586        
587        // Should have default health checks
588        assert!(results.contains_key("system_memory"));
589        assert!(results.contains_key("system_cpu"));
590        assert!(results.contains_key("system_disk"));
591    }
592    
593    #[tokio::test]
594    async fn test_get_system_health() {
595        let mut checker = HealthChecker::new();
596        checker.initialize().await.unwrap();
597        
598        let system_health = checker.get_system_health().await.unwrap();
599        assert_eq!(system_health.overall_status, HealthStatus::Healthy);
600        assert!(!system_health.health_checks.is_empty());
601        assert!(system_health.last_checked > 0);
602    }
603    
604    #[tokio::test]
605    async fn test_health_check_types() {
606        let mut checker = HealthChecker::new();
607        checker.initialize().await.unwrap();
608        
609        // Test ping check
610        let ping_check = HealthCheck {
611            name: "ping_check".to_string(),
612            check_type: HealthCheckType::Ping,
613            interval: Duration::from_secs(30),
614            timeout: Duration::from_secs(5),
615            enabled: true,
616        };
617        
618        checker.add_health_check("ping_check".to_string(), ping_check).await.unwrap();
619        let result = checker.run_health_check("ping_check").await.unwrap();
620        assert!(result.is_healthy);
621        assert!(result.message.contains("Ping"));
622        
623        // Test HTTP check
624        let http_check = HealthCheck {
625            name: "http_check".to_string(),
626            check_type: HealthCheckType::Http,
627            interval: Duration::from_secs(30),
628            timeout: Duration::from_secs(5),
629            enabled: true,
630        };
631        
632        checker.add_health_check("http_check".to_string(), http_check).await.unwrap();
633        let result = checker.run_health_check("http_check").await.unwrap();
634        assert!(result.is_healthy);
635        assert!(result.message.contains("HTTP"));
636        assert!(result.details.contains_key("status_code"));
637        
638        // Test database check
639        let db_check = HealthCheck {
640            name: "db_check".to_string(),
641            check_type: HealthCheckType::Database,
642            interval: Duration::from_secs(30),
643            timeout: Duration::from_secs(5),
644            enabled: true,
645        };
646        
647        checker.add_health_check("db_check".to_string(), db_check).await.unwrap();
648        let result = checker.run_health_check("db_check").await.unwrap();
649        assert!(result.is_healthy);
650        assert!(result.message.contains("Database"));
651        assert!(result.details.contains_key("connection_pool_size"));
652        
653        // Test custom check
654        let custom_check = HealthCheck {
655            name: "custom_check".to_string(),
656            check_type: HealthCheckType::Custom,
657            interval: Duration::from_secs(30),
658            timeout: Duration::from_secs(5),
659            enabled: true,
660        };
661        
662        checker.add_health_check("custom_check".to_string(), custom_check).await.unwrap();
663        let result = checker.run_health_check("custom_check").await.unwrap();
664        assert!(result.is_healthy);
665        assert!(result.message.contains("Custom"));
666    }
667    
668    #[test]
669    fn test_health_config_default() {
670        let config = HealthConfig::default();
671        assert_eq!(config.default_interval, Duration::from_secs(30));
672        assert_eq!(config.default_timeout, Duration::from_secs(5));
673        assert!(config.enable_health_checks);
674        assert!(config.health_checks.is_empty());
675    }
676    
677    #[test]
678    fn test_health_check_creation() {
679        let health_check = HealthCheck {
680            name: "test_check".to_string(),
681            check_type: HealthCheckType::Ping,
682            interval: Duration::from_secs(30),
683            timeout: Duration::from_secs(5),
684            enabled: true,
685        };
686        
687        assert_eq!(health_check.name, "test_check");
688        assert_eq!(health_check.check_type, HealthCheckType::Ping);
689        assert_eq!(health_check.interval, Duration::from_secs(30));
690        assert_eq!(health_check.timeout, Duration::from_secs(5));
691        assert!(health_check.enabled);
692    }
693    
694    #[test]
695    fn test_health_check_result_creation() {
696        let result = HealthCheckResult {
697            name: "test_check".to_string(),
698            is_healthy: true,
699            message: "Test check passed".to_string(),
700            duration: Duration::from_millis(100),
701            last_checked: 1234567890,
702            details: HashMap::new(),
703        };
704        
705        assert_eq!(result.name, "test_check");
706        assert!(result.is_healthy);
707        assert_eq!(result.message, "Test check passed");
708        assert_eq!(result.duration, Duration::from_millis(100));
709        assert_eq!(result.last_checked, 1234567890);
710    }
711    
712    #[test]
713    fn test_health_error_display() {
714        let error = HealthError::HealthCheckNotFound("test_check".to_string());
715        let error_string = format!("{}", error);
716        assert!(error_string.contains("Health check not found"));
717        assert!(error_string.contains("test_check"));
718    }
719}