feat(check satellite): cascade LAN+DMZ avec fallback automatique + migration servers.satellite_url + override BDD prioritaire

This commit is contained in:
Pierre & Lumière 2026-05-05 14:34:47 +02:00
parent c74ac5ec3e
commit 0ed564a8ed
3 changed files with 88 additions and 40 deletions

View File

@ -625,7 +625,8 @@ async def iexec_check(request: Request, row_id: int, db=Depends(get_db)):
row = db.execute(text("""
SELECT r.id, r.asset_name, r.intervenant, r.environnement, r.domaine,
r.os, r.os_version, r.is_eligible, r.server_id, s.hostname
r.os, r.os_version, r.is_eligible, r.server_id,
s.hostname, s.satellite_url
FROM patch_planning_import_rows r
LEFT JOIN servers s ON s.id = r.server_id
WHERE r.id = :id
@ -656,6 +657,7 @@ async def iexec_check(request: Request, row_id: int, db=Depends(get_db)):
"intervenant": row.intervenant,
"environnement": row.environnement,
"domaine": row.domaine,
"satellite_url": getattr(row, "satellite_url", None),
})
return JSONResponse({"ok": True, "row_id": row_id, **result})

View File

@ -40,13 +40,21 @@ SATELLITE_LAN = "vpdsiasat2.sanef.groupe"
SATELLITE_DMZ = "vpdsiasat1.sanef.groupe"
def _pick_satellite(row: Dict[str, Any]) -> str:
"""Renvoie le hostname du Satellite cible selon le domaine.
Si la colonne Domaine contient 'DMZ' vpdsiasat1, sinon vpdsiasat2 (LAN)."""
def _pick_satellites(row: Dict[str, Any]) -> List[str]:
"""Renvoie la liste ordonnée des Satellites à tester.
Priorité : 1) servers.satellite_url renseigné en BDD,
2) DMZ d'abord si la colonne Domaine contient 'DMZ',
3) LAN d'abord par défaut.
L'autre satellite est toujours ajouté en fallback."""
forced = (row.get("satellite_url") or "").strip()
domaine = str(row.get("domaine") or "").upper()
if forced:
# Override : on commence par celui en BDD, fallback sur l'autre
other = SATELLITE_DMZ if forced == SATELLITE_LAN else SATELLITE_LAN
return [forced] if forced not in (SATELLITE_LAN, SATELLITE_DMZ) else [forced, other]
if "DMZ" in domaine:
return SATELLITE_DMZ
return SATELLITE_LAN
return [SATELLITE_DMZ, SATELLITE_LAN]
return [SATELLITE_LAN, SATELLITE_DMZ]
def _exec(client, cmd: str) -> Dict[str, Any]:
@ -193,32 +201,38 @@ def check_disk(ctx: Dict[str, Any]) -> Dict[str, Any]:
def check_satellite(ctx: Dict[str, Any]) -> Dict[str, Any]:
"""Vérifie :
1. la joignabilité du Satellite cible (LAN ou DMZ selon Domaine)
1. la joignabilité d'un Satellite (LAN d'abord, fallback DMZ ou
inversement si serveur DMZ)
2. l'inscription du serveur (subscription-manager identity)
3. l'accès aux repos (yum repolist enabled --quiet)
Toutes les commandes utilisent sudo -n (non-interactif).
Toutes les commandes utilisent sudo -n.
"""
client = ctx.get("client")
sat = _pick_satellite(ctx.get("row") or {})
label = f"Satellite ({sat})"
satellites = _pick_satellites(ctx.get("row") or {})
preferred = satellites[0]
label = f"Satellite (préféré: {preferred})"
if client is None:
return {
"name": "satellite",
"label": label,
"status": "ko",
"message": "SSH KO en amont",
"details": "",
"name": "satellite", "label": label, "status": "ko",
"message": "SSH KO en amont", "details": "",
}
# 1) Joignabilité réseau du Satellite (HEAD https://<sat>/pub/)
# 1) Joignabilité : on tente chaque satellite jusqu'au premier qui répond
sat_reached = None
sat_attempts = []
for sat in satellites:
r0 = _exec(client,
f"sudo -n curl -k -s -o /dev/null -w '%{{http_code}}' "
f"--max-time 5 https://{sat}/pub/ 2>&1")
http_code = (r0["stdout"] or "").strip()
sat_reachable = http_code in ("200", "301", "302", "403")
sat_attempts.append(f"{sat} → HTTP {http_code or 'N/A'}")
if http_code in ("200", "301", "302", "403"):
sat_reached = sat
break
# 2) subscription-manager identity
# Locale-indépendant : on cherche un UUID dans la sortie (présent en EN comme en FR).
sat_reachable = sat_reached is not None
# 2) subscription-manager identity (locale-indépendant via UUID)
r1 = _exec(client, "sudo -n subscription-manager identity 2>&1")
sub_ok = (r1["rc"] == 0 and bool(re.search(
r"\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b",
@ -229,7 +243,7 @@ def check_satellite(ctx: Dict[str, Any]) -> Dict[str, Any]:
repolist_ok = (r2["rc"] == 0 and r2["stdout"].strip() != "")
details = (
f"$ curl https://{sat}/pub/ → http_code={http_code or 'N/A'}\n"
"$ curl tests :\n " + "\n ".join(sat_attempts) + "\n"
f"$ sudo subscription-manager identity →\n{r1['stdout']}\n{r1['stderr']}\n"
f"---\n"
f"$ sudo yum repolist enabled --quiet (head -50) →\n{r2['stdout']}\n{r2['stderr']}"
@ -240,29 +254,23 @@ def check_satellite(ctx: Dict[str, Any]) -> Dict[str, Any]:
if ln and not ln.lower().startswith(("repo id", "loaded plugins",
"updating subscription",
"this system")))
return {
"name": "satellite",
"label": label,
"status": "ok",
"message": f"{sat} joignable · système enregistré · ~{nb} repo(s) actifs",
"details": details,
}
# Construit message synthétique des KO
msg = f"{sat_reached} joignable · système enregistré · ~{nb} repo(s)"
if sat_reached != preferred:
msg += f" — fallback depuis {preferred}"
return {"name": "satellite", "label": label, "status": "ok",
"message": msg, "details": details}
issues = []
if not sat_reachable:
issues.append(f"Satellite {sat} injoignable (http={http_code or 'N/A'})")
tried = ", ".join(s for s in satellites)
issues.append(f"Aucun Satellite joignable (testés: {tried})")
if not sub_ok:
issues.append("subscription-manager identity KO")
if not repolist_ok:
issues.append("yum repolist vide / KO")
status = "ko" if (not sat_reachable or not repolist_ok) else "warn"
return {
"name": "satellite",
"label": label,
"status": status,
"message": " · ".join(issues),
"details": details,
}
return {"name": "satellite", "label": label, "status": status,
"message": " · ".join(issues), "details": details}
# ────────────────────────────────────────────────────────────────────────

View File

@ -0,0 +1,38 @@
-- Migration : ajoute servers.satellite_url avec backfill auto
-- - DMZ (zone "DMZ" ou domaine "DMZ") → vpdsiasat1.sanef.groupe
-- - Tous les autres → vpdsiasat2.sanef.groupe
-- Champ optionnel (override de la convention par défaut côté code).
-- Idempotent.
ALTER TABLE public.servers
ADD COLUMN IF NOT EXISTS satellite_url text;
-- Backfill DMZ (par zone = 'DMZ' ou nom de domaine contenant 'DMZ')
UPDATE public.servers s
SET satellite_url = 'vpdsiasat1.sanef.groupe',
updated_at = NOW()
FROM public.zones z, public.domain_environments de, public.domains d
WHERE s.zone_id = z.id
AND s.domain_env_id = de.id
AND de.domain_id = d.id
AND s.satellite_url IS NULL
AND (z.is_dmz = true
OR UPPER(z.name) = 'DMZ'
OR UPPER(d.name) LIKE '%DMZ%'
OR UPPER(d.code) = 'DMZ');
-- Backfill tout le reste (LAN par défaut)
UPDATE public.servers
SET satellite_url = 'vpdsiasat2.sanef.groupe',
updated_at = NOW()
WHERE satellite_url IS NULL;
-- Compteurs après backfill
SELECT 'satellite_lan (vpdsiasat2)' AS scope, COUNT(*) AS nb
FROM public.servers WHERE satellite_url = 'vpdsiasat2.sanef.groupe'
UNION ALL
SELECT 'satellite_dmz (vpdsiasat1)' AS scope, COUNT(*) AS nb
FROM public.servers WHERE satellite_url = 'vpdsiasat1.sanef.groupe'
UNION ALL
SELECT 'satellite_null' AS scope, COUNT(*) AS nb
FROM public.servers WHERE satellite_url IS NULL;