177 lines
6.4 KiB
Python
177 lines
6.4 KiB
Python
from fastapi import APIRouter, HTTPException
|
|
import logging
|
|
from datetime import datetime, timedelta
|
|
from database.connection import db_connection
|
|
from models.sync_models import HealthCheck, EstadoSincronizacion
|
|
import time
|
|
|
|
router = APIRouter(prefix="/api", tags=["health"])
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Variable global para tracking del tiempo de inicio
|
|
start_time = time.time()
|
|
|
|
|
|
def get_uptime() -> str:
|
|
"""Calcula el tiempo de actividad del servicio"""
|
|
uptime_seconds = time.time() - start_time
|
|
|
|
if uptime_seconds < 60:
|
|
return f"{int(uptime_seconds)} segundos"
|
|
elif uptime_seconds < 3600:
|
|
minutes = int(uptime_seconds / 60)
|
|
seconds = int(uptime_seconds % 60)
|
|
return f"{minutes}m {seconds}s"
|
|
elif uptime_seconds < 86400:
|
|
hours = int(uptime_seconds / 3600)
|
|
minutes = int((uptime_seconds % 3600) / 60)
|
|
return f"{hours}h {minutes}m"
|
|
else:
|
|
days = int(uptime_seconds / 86400)
|
|
hours = int((uptime_seconds % 86400) / 3600)
|
|
return f"{days}d {hours}h"
|
|
|
|
|
|
@router.get("/health",
|
|
response_model=HealthCheck,
|
|
summary="Verificar salud del sistema",
|
|
description="Retorna el estado general de salud del sistema de monitoreo")
|
|
async def health_check():
|
|
"""
|
|
Endpoint de health check que proporciona información sobre:
|
|
- Estado general del sistema
|
|
- Conectividad con la base de datos
|
|
- Estadísticas generales de nodos
|
|
- Tiempo de actividad del servicio
|
|
- Versión de la API
|
|
"""
|
|
try:
|
|
logger.info("Ejecutando health check del sistema")
|
|
|
|
# Probar conexión a ambas bases de datos
|
|
main_db_status = "healthy"
|
|
backup_db_status = "healthy"
|
|
|
|
try:
|
|
main_db_test = db_connection.test_connection("main")
|
|
if not main_db_test.get("connection_successful", False):
|
|
main_db_status = "unhealthy"
|
|
except Exception as e:
|
|
logger.error(f"Error en conexión a BD principal durante health check: {str(e)}")
|
|
main_db_status = "unhealthy"
|
|
|
|
try:
|
|
backup_db_test = db_connection.test_connection("backup")
|
|
if not backup_db_test.get("connection_successful", False):
|
|
backup_db_status = "unhealthy"
|
|
except Exception as e:
|
|
logger.error(f"Error en conexión a BD backup durante health check: {str(e)}")
|
|
backup_db_status = "unhealthy"
|
|
|
|
# Estado general de BD (ambas deben estar healthy)
|
|
db_status = "healthy" if main_db_status == "healthy" and backup_db_status == "healthy" else "unhealthy"
|
|
|
|
# Obtener estadísticas de nodos si la BD está disponible
|
|
total_nodes = 0
|
|
nodes_healthy = 0
|
|
nodes_attention = 0
|
|
nodes_error = 0
|
|
|
|
if db_status == "healthy":
|
|
try:
|
|
# Consultar estadísticas de nodos por estado
|
|
stats_query = """
|
|
SELECT
|
|
CASE
|
|
WHEN Activo = 1 THEN 'Actualizada'
|
|
ELSE 'Atención'
|
|
END as EstadoSincronizacion,
|
|
COUNT(DISTINCT NodoSubNodo) as NodeCount
|
|
FROM BasesdeDatos
|
|
GROUP BY Activo
|
|
"""
|
|
|
|
results = db_connection.execute_query(stats_query, server_type="main")
|
|
|
|
status_mapping = {
|
|
EstadoSincronizacion.ACTUALIZADA.value: 'healthy',
|
|
EstadoSincronizacion.ATENCION.value: 'attention',
|
|
EstadoSincronizacion.ERROR.value: 'error'
|
|
}
|
|
|
|
for row in results:
|
|
estado = row.get('EstadoSincronizacion', '')
|
|
count = row.get('NodeCount', 0)
|
|
total_nodes += count
|
|
|
|
if estado == EstadoSincronizacion.ACTUALIZADA.value:
|
|
nodes_healthy += count
|
|
elif estado == EstadoSincronizacion.ATENCION.value:
|
|
nodes_attention += count
|
|
elif estado == EstadoSincronizacion.ERROR.value:
|
|
nodes_error += count
|
|
|
|
logger.info(f"Estadísticas de nodos - Total: {total_nodes}, "
|
|
f"Healthy: {nodes_healthy}, Atención: {nodes_attention}, Error: {nodes_error}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error obteniendo estadísticas de nodos: {str(e)}")
|
|
# Mantener valores por defecto si hay error
|
|
|
|
# Determinar estado general del sistema
|
|
overall_status = "healthy"
|
|
|
|
if db_status != "healthy":
|
|
overall_status = "unhealthy"
|
|
elif nodes_error > 0 or (total_nodes > 0 and nodes_healthy / total_nodes < 0.5):
|
|
overall_status = "degraded"
|
|
elif nodes_attention > 0:
|
|
overall_status = "degraded"
|
|
|
|
# Crear respuesta del health check
|
|
health_response = HealthCheck(
|
|
status=overall_status,
|
|
database_status=db_status,
|
|
total_nodes=total_nodes,
|
|
nodes_healthy=nodes_healthy,
|
|
nodes_attention=nodes_attention,
|
|
nodes_error=nodes_error,
|
|
last_check=datetime.now(),
|
|
uptime=get_uptime(),
|
|
version="1.0.0"
|
|
)
|
|
|
|
logger.info(f"Health check completado - Estado: {overall_status}")
|
|
return health_response
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error crítico durante health check: {str(e)}")
|
|
|
|
# Retornar estado de error crítico
|
|
return HealthCheck(
|
|
status="unhealthy",
|
|
database_status="error",
|
|
total_nodes=0,
|
|
nodes_healthy=0,
|
|
nodes_attention=0,
|
|
nodes_error=0,
|
|
last_check=datetime.now(),
|
|
uptime=get_uptime(),
|
|
version="1.0.0"
|
|
)
|
|
|
|
|
|
@router.get("/ping",
|
|
summary="Ping simple",
|
|
description="Endpoint simple para verificar que la API está respondiendo")
|
|
async def ping():
|
|
"""
|
|
Endpoint simple de ping para verificación básica de disponibilidad.
|
|
Útil para load balancers y monitoreo básico.
|
|
"""
|
|
return {
|
|
"message": "pong",
|
|
"timestamp": datetime.now().isoformat(),
|
|
"status": "ok"
|
|
}
|