diff --git a/app/routers/planning_import.py b/app/routers/planning_import.py index 1a9a8ad..7d0e737 100644 --- a/app/routers/planning_import.py +++ b/app/routers/planning_import.py @@ -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}) diff --git a/app/services/prepatch_check_service.py b/app/services/prepatch_check_service.py index a5ef72c..45b2bc0 100644 --- a/app/services/prepatch_check_service.py +++ b/app/services/prepatch_check_service.py @@ -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:///pub/) - 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") + # 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_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} # ──────────────────────────────────────────────────────────────────────── diff --git a/migrate_servers_satellite.sql b/migrate_servers_satellite.sql new file mode 100644 index 0000000..0a2beb5 --- /dev/null +++ b/migrate_servers_satellite.sql @@ -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;