homelab_automation/app/services/bootstrap_status_service.py

121 lines
4.8 KiB
Python

"""
Service de gestion du statut de bootstrap des hôtes.
"""
import asyncio
from datetime import datetime, timezone
from typing import Dict, Optional
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.database import async_session_maker
class BootstrapStatusService:
"""Service pour gérer le statut de bootstrap des hôtes.
Cette version utilise la base de données SQLite via SQLAlchemy async.
Note: Le modèle BD utilise host_id (FK), mais ce service utilise host_name
pour la compatibilité avec le code existant. Il fait la correspondance via HostRepository.
"""
def __init__(self):
# Cache en mémoire pour éviter les requêtes BD répétées
self._cache: Dict[str, Dict] = {}
async def _get_host_id_by_name(self, session: AsyncSession, host_name: str) -> Optional[str]:
"""Récupère l'ID d'un hôte par son nom."""
from app.crud.host import HostRepository
repo = HostRepository(session)
host = await repo.get_by_name(host_name)
return host.id if host else None
def set_bootstrap_status(self, host_name: str, success: bool, details: str = None) -> Dict:
"""Enregistre le statut de bootstrap d'un hôte (version synchrone avec cache)."""
status_data = {
"bootstrap_ok": success,
"bootstrap_date": datetime.now(timezone.utc).isoformat(),
"details": details
}
self._cache[host_name] = status_data
# Planifier la sauvegarde en BD de manière asynchrone
asyncio.create_task(self._save_to_db(host_name, success, details))
return status_data
async def _save_to_db(self, host_name: str, success: bool, details: str = None):
"""Sauvegarde le statut dans la BD."""
try:
async with async_session_maker() as session:
host_id = await self._get_host_id_by_name(session, host_name)
if not host_id:
print(f"Host '{host_name}' non trouvé en BD pour bootstrap status")
return
from app.crud.bootstrap_status import BootstrapStatusRepository
repo = BootstrapStatusRepository(session)
await repo.create(
host_id=host_id,
status="success" if success else "failed",
last_attempt=datetime.now(timezone.utc),
error_message=None if success else details,
)
await session.commit()
except Exception as e:
print(f"Erreur sauvegarde bootstrap status en BD: {e}")
def get_bootstrap_status(self, host_name: str) -> Dict:
"""Récupère le statut de bootstrap d'un hôte depuis le cache."""
return self._cache.get(host_name, {
"bootstrap_ok": False,
"bootstrap_date": None,
"details": None
})
def get_all_status(self) -> Dict[str, Dict]:
"""Récupère le statut de tous les hôtes depuis le cache."""
return self._cache.copy()
def remove_host(self, host_name: str) -> bool:
"""Supprime le statut d'un hôte du cache."""
if host_name in self._cache:
del self._cache[host_name]
return True
return False
async def load_from_db(self):
"""Charge tous les statuts depuis la BD dans le cache (appelé au démarrage)."""
try:
async with async_session_maker() as session:
from sqlalchemy import select
from app.models.bootstrap_status import BootstrapStatus
from app.models.host import Host
# Récupérer tous les derniers statuts avec les noms d'hôtes
stmt = (
select(BootstrapStatus, Host.name)
.join(Host, BootstrapStatus.host_id == Host.id)
.order_by(BootstrapStatus.created_at.desc())
)
result = await session.execute(stmt)
# Garder seulement le dernier statut par hôte
seen_hosts = set()
for bs, host_name in result:
if host_name not in seen_hosts:
self._cache[host_name] = {
"bootstrap_ok": bs.status == "success",
"bootstrap_date": bs.last_attempt.isoformat() if bs.last_attempt else bs.created_at.isoformat(),
"details": bs.error_message
}
seen_hosts.add(host_name)
print(f"📋 {len(self._cache)} statut(s) bootstrap chargé(s) depuis la BD")
except Exception as e:
print(f"Erreur chargement bootstrap status depuis BD: {e}")
# Instance singleton du service
bootstrap_status_service = BootstrapStatusService()