import asyncio import logging import time from enum import Enum from typing import Dict, List, Any, Optional from pydantic import BaseModel _logger = logging.getLogger("api.health_manager") class HealthStatus(str, Enum): HEALTHY = "healthy" DEGRADED = "degraded" DOWN = "down" class ComponentHealth(BaseModel): id: str type: str # "worker" | "provider" | "service" status: HealthStatus = HealthStatus.HEALTHY failure_count: int = 0 last_check: float = 0.0 latency: float = 0.0 error_message: Optional[str] = None class HealthManager: """ ARCH-P5.1: Health Manager Gestisce il monitoraggio, il Circuit Breaker e il Traffic Management. """ def __init__(self): self.components: Dict[str, ComponentHealth] = {} self._lock = asyncio.Lock() self.failure_threshold = 5 # Numero di errori prima di aprire il circuit self.recovery_timeout = 60 # Secondi prima di riprovare un componente DOWN async def record_success(self, component_id: str, latency: float = 0.0, component_type: str = "worker"): """Registra un'operazione riuscita per un componente.""" async with self._lock: if component_id not in self.components: self.components[component_id] = ComponentHealth(id=component_id, type=component_type) c = self.components[component_id] c.status = HealthStatus.HEALTHY c.failure_count = 0 c.last_check = time.time() c.latency = latency c.error_message = None async def record_failure(self, component_id: str, error: str, component_type: str = "worker"): """Registra un fallimento e attiva il circuit breaker se necessario.""" async with self._lock: if component_id not in self.components: self.components[component_id] = ComponentHealth(id=component_id, type=component_type) c = self.components[component_id] c.failure_count += 1 c.last_check = time.time() c.error_message = error if c.failure_count >= self.failure_threshold: if c.status != HealthStatus.DOWN: _logger.warning(f"Circuit Breaker APERTO per {component_id}: {error}") c.status = HealthStatus.DOWN elif c.failure_count >= 2: c.status = HealthStatus.DEGRADED async def is_healthy(self, component_id: str) -> bool: """Verifica se un componente è sano (o se è tempo di riprovare).""" async with self._lock: if component_id not in self.components: return True c = self.components[component_id] if c.status == HealthStatus.DOWN: # Half-open state: riprova dopo il timeout if time.time() - c.last_check > self.recovery_timeout: _logger.info(f"Circuit Breaker HALF-OPEN per {component_id} (tentativo di recovery)") return True return False return True async def get_status(self) -> Dict[str, Any]: """Ritorna lo stato aggregato di salute del sistema.""" async with self._lock: return { "ts": time.time(), "components": {k: v.dict() for k, v in self.components.items()}, "summary": { "healthy": sum(1 for c in self.components.values() if c.status == HealthStatus.HEALTHY), "degraded": sum(1 for c in self.components.values() if c.status == HealthStatus.DEGRADED), "down": sum(1 for c in self.components.values() if c.status == HealthStatus.DOWN), } } # Singleton instance health_manager = HealthManager()