Initial commit: SYNC_API project

This commit is contained in:
2025-11-06 09:50:10 -07:00
commit 733ef04eeb
42 changed files with 2380 additions and 0 deletions

176
sync_api/routers/health.py Normal file
View File

@@ -0,0 +1,176 @@
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"
}