gmao/app_new/health/routes.py

107 lines
3.6 KiB
Python
Raw Normal View History

2026-08-14 18:02:25 +02:00
"""Healthcheck endpoint - GMAO Collège"""
from flask import Blueprint, jsonify
from app_new.extensions import db
from app_new.core.models import User
from app_new.version import get_version
import subprocess
import os
import time
from datetime import datetime
health_bp = Blueprint('health', __name__, url_prefix='/health')
# Constantes
WATCHDOG_STARTUP_GRACE_SECONDS = 60
APP_START_TIME = time.time()
def _check_watchdog(name, start_time_threshold=None):
"""Verifie si un processus watchdog tourne via /proc."""
try:
pids = []
for pid in os.listdir('/proc'):
if not pid.isdigit():
continue
try:
cmdline_path = f'/proc/{pid}/cmdline'
with open(cmdline_path, 'rb') as f:
cmdline = f.read().decode('utf-8', errors='replace').replace('\x00', ' ')
if name in cmdline and 'grep' not in cmdline:
pids.append(int(pid))
except (FileNotFoundError, PermissionError):
continue
if pids:
return {'running': True, 'pid': pids[0], 'instances': len(pids)}
# Grace period au demarrage
if start_time_threshold and time.time() - APP_START_TIME < start_time_threshold:
return {'running': False, 'starting': True}
return {'running': False}
except Exception as e:
return {'running': False, 'error': str(e)}
def _check_database():
"""Verifie la connexion a la base de donnees."""
try:
db.session.execute(db.text('SELECT 1'))
return {'connected': True, 'dialect': str(db.engine.url).split('://')[0]}
except Exception as e:
return {'connected': False, 'error': str(e)}
@health_bp.route('/')
def health():
"""Endpoint public de healthcheck."""
db_status = _check_database()
# Le watchdog pronote met parfois plus de temps a demarrer (connexion ENT)
watchdogs = {
'flask': _check_watchdog('gunicorn'),
'gmao_watchdog': _check_watchdog('gmao_watchdog.py'),
'ent_watchdog': _check_watchdog('ent_watchdog.py'),
'pronote_watchdog': _check_watchdog('pronote_watchdog.py', start_time_threshold=WATCHDOG_STARTUP_GRACE_SECONDS),
'watchdog_dnd': _check_watchdog('watchdog_dnd.py'),
}
all_watchdogs_ok = all(
w.get('running') or w.get('starting')
for w in watchdogs.values()
)
healthy = db_status.get('connected') and all_watchdogs_ok and not any(w.get('starting') for w in watchdogs.values())
git_commit = 'unknown'
try:
result = subprocess.run(
['git', '-C', '/app', 'rev-parse', '--short', 'HEAD'],
capture_output=True,
text=True,
timeout=5
)
if result.returncode == 0:
git_commit = result.stdout.strip()
except Exception:
pass
# Fallback si git n'est pas disponible dans l'image
if git_commit == 'unknown':
version_file = '/app/.git-version'
if os.path.exists(version_file):
try:
with open(version_file) as f:
git_commit = f.read().strip()
except Exception:
pass
response = {
'status': 'healthy' if healthy else 'degraded',
'version': get_version(),
'timestamp': datetime.utcnow().isoformat() + 'Z',
'database': db_status,
'watchdogs': watchdogs,
'git_commit': git_commit,
'environment': os.environ.get('FLASK_ENV', 'production'),
'uptime_seconds': int(time.time() - APP_START_TIME)
}
return jsonify(response), 200 if healthy else 503