200 lines
6.5 KiB
Python
200 lines
6.5 KiB
Python
"""
|
|
Base de données hybride combinant données Ansible et données runtime.
|
|
"""
|
|
|
|
from datetime import datetime, timezone
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from app.schemas.host_api import Host
|
|
from app.schemas.task_api import Task
|
|
from app.schemas.common import LogEntry, SystemMetrics
|
|
|
|
|
|
class HybridDB:
|
|
"""Base de données hybride combinant l'inventaire Ansible avec les données runtime.
|
|
|
|
Cette classe agit comme un agrégateur de données provenant de plusieurs sources:
|
|
- Inventaire Ansible (via AnsibleService)
|
|
- Statuts bootstrap (via BootstrapStatusService)
|
|
- Statuts runtime (via HostStatusService)
|
|
- Tâches et logs en mémoire
|
|
"""
|
|
|
|
def __init__(self):
|
|
# Cache des hôtes
|
|
self._hosts_cache: Optional[List[Host]] = None
|
|
self._hosts_cache_time: float = 0
|
|
self._cache_ttl = 60 # secondes
|
|
|
|
# Données en mémoire
|
|
self.tasks: List[Task] = []
|
|
self.logs: List[LogEntry] = []
|
|
|
|
# Compteurs pour les IDs
|
|
self._id_counters: Dict[str, int] = {
|
|
"tasks": 0,
|
|
"logs": 0,
|
|
}
|
|
|
|
def get_next_id(self, entity: str) -> int:
|
|
"""Génère un nouvel ID pour une entité."""
|
|
self._id_counters[entity] = self._id_counters.get(entity, 0) + 1
|
|
return self._id_counters[entity]
|
|
|
|
@property
|
|
def hosts(self) -> List[Host]:
|
|
"""Retourne la liste des hôtes, en la mettant à jour si nécessaire."""
|
|
import time
|
|
current_time = time.time()
|
|
|
|
if self._hosts_cache and (current_time - self._hosts_cache_time) < self._cache_ttl:
|
|
return self._hosts_cache
|
|
|
|
return self.refresh_hosts()
|
|
|
|
def refresh_hosts(self) -> List[Host]:
|
|
"""Rafraîchit la liste des hôtes depuis l'inventaire Ansible."""
|
|
import time
|
|
from app.services.ansible_service import ansible_service
|
|
from app.services.bootstrap_status_service import bootstrap_status_service
|
|
from app.services.host_status_service import host_status_service
|
|
|
|
hosts = []
|
|
inventory_hosts = ansible_service.get_hosts_from_inventory()
|
|
|
|
for inv_host in inventory_hosts:
|
|
# Récupérer le statut bootstrap
|
|
bs_status = bootstrap_status_service.get_bootstrap_status(inv_host.name)
|
|
|
|
# Récupérer le statut runtime
|
|
rt_status = host_status_service.get_status(inv_host.name)
|
|
|
|
host = Host(
|
|
id=inv_host.name, # Utiliser le nom comme ID
|
|
name=inv_host.name,
|
|
ip=inv_host.ansible_host or inv_host.name,
|
|
status=rt_status.get("status") or "unknown",
|
|
os=rt_status.get("os") or "Linux",
|
|
last_seen=rt_status.get("last_seen"),
|
|
groups=inv_host.groups or [inv_host.group] if inv_host.group else [],
|
|
bootstrap_ok=bs_status.get("bootstrap_ok", False),
|
|
bootstrap_date=bs_status.get("bootstrap_date")
|
|
)
|
|
hosts.append(host)
|
|
|
|
self._hosts_cache = hosts
|
|
self._hosts_cache_time = time.time()
|
|
|
|
return hosts
|
|
|
|
def invalidate_hosts_cache(self):
|
|
"""Invalide le cache des hôtes."""
|
|
self._hosts_cache = None
|
|
|
|
def get_host(self, host_id: str) -> Optional[Host]:
|
|
"""Récupère un hôte par son ID ou nom."""
|
|
for host in self.hosts:
|
|
if host.id == host_id or host.name == host_id or host.ip == host_id:
|
|
return host
|
|
return None
|
|
|
|
def update_host_status(
|
|
self,
|
|
host_name: str,
|
|
status: str,
|
|
os_info: str = None
|
|
):
|
|
"""Met à jour le statut d'un hôte."""
|
|
from app.services.host_status_service import host_status_service
|
|
|
|
host_status_service.set_status(
|
|
host_name=host_name,
|
|
status=status,
|
|
last_seen=datetime.now(timezone.utc),
|
|
os_info=os_info
|
|
)
|
|
|
|
# Invalider le cache pour forcer le rechargement
|
|
self.invalidate_hosts_cache()
|
|
|
|
@property
|
|
def metrics(self) -> SystemMetrics:
|
|
"""Calcule et retourne les métriques système."""
|
|
hosts = self.hosts
|
|
|
|
online_count = sum(1 for h in hosts if h.status == "online")
|
|
total_tasks = len(self.tasks)
|
|
|
|
# Calculer le taux de succès
|
|
completed = sum(1 for t in self.tasks if t.status == "completed")
|
|
failed = sum(1 for t in self.tasks if t.status == "failed")
|
|
total_finished = completed + failed
|
|
success_rate = (completed / total_finished * 100) if total_finished > 0 else 100.0
|
|
|
|
return SystemMetrics(
|
|
online_hosts=online_count,
|
|
total_tasks=total_tasks,
|
|
success_rate=round(success_rate, 1),
|
|
uptime=99.9, # TODO: calculer depuis le démarrage
|
|
cpu_usage=0.0,
|
|
memory_usage=0.0,
|
|
disk_usage=0.0
|
|
)
|
|
|
|
def add_task(self, task: Task):
|
|
"""Ajoute une tâche à la liste."""
|
|
self.tasks.insert(0, task)
|
|
|
|
# Limiter la taille de la liste
|
|
if len(self.tasks) > 1000:
|
|
self.tasks = self.tasks[:1000]
|
|
|
|
def get_task(self, task_id: str) -> Optional[Task]:
|
|
"""Récupère une tâche par son ID."""
|
|
for task in self.tasks:
|
|
if str(task.id) == str(task_id):
|
|
return task
|
|
return None
|
|
|
|
def update_task(self, task_id: str, **kwargs):
|
|
"""Met à jour une tâche existante."""
|
|
task = self.get_task(task_id)
|
|
if task:
|
|
for key, value in kwargs.items():
|
|
if hasattr(task, key):
|
|
setattr(task, key, value)
|
|
|
|
def add_log(self, log: LogEntry):
|
|
"""Ajoute une entrée de log."""
|
|
if log.id == 0:
|
|
log.id = self.get_next_id("logs")
|
|
self.logs.insert(0, log)
|
|
|
|
# Limiter la taille de la liste
|
|
if len(self.logs) > 5000:
|
|
self.logs = self.logs[:5000]
|
|
|
|
def get_recent_logs(self, limit: int = 50, level: str = None, source: str = None) -> List[LogEntry]:
|
|
"""Récupère les logs récents avec filtrage optionnel."""
|
|
logs = self.logs
|
|
|
|
if level:
|
|
logs = [l for l in logs if l.level == level]
|
|
|
|
if source:
|
|
logs = [l for l in logs if l.source == source]
|
|
|
|
return logs[:limit]
|
|
|
|
def clear_logs(self):
|
|
"""Efface tous les logs."""
|
|
self.logs.clear()
|
|
|
|
def clear_tasks(self):
|
|
"""Efface toutes les tâches."""
|
|
self.tasks.clear()
|
|
|
|
|
|
# Instance singleton de la base de données hybride
|
|
db = HybridDB()
|