#!/usr/bin/env python3 """Devis du MTU : l'invite porte-t-il le MTU de la zone a laquelle il est attache ? Le MTU de la ZONE est pose sur l'hyperviseur par `make sdn-appliquer`. Rien ne garantit que l'INVITE le porte : sa carte doit l'heriter du pont (`mtu=1` chez Proxmox), sans quoi il nait a 1500 quelle que soit la zone. Quand les deux divergent, la panne est sournoise : la connexion s'etablit, les petites requetes passent, les grosses reponses restent suspendues. Elle depend alors entierement de la decouverte de MTU de chemin — donc de messages ICMP que n'importe quel pare-feu peut avaler en silence. ET ELLE EST INVISIBLE TANT QUE LA FLOTTE TIENT SUR UN SEUL HYPERVISEUR : deux VM du meme noeud communiquent par le pont local, sans encapsulation. Mesure du 2026-08-10 : zones `t11`/`t17` a 1450, les 14 invites a 1500, et aucun symptome — les 14 etaient sur `asgard`. Le jour ou on repartit la flotte, tout casse d'un coup et rien ne dit pourquoi. Le MTU attendu n'est pas saisi : il est lu dans `devis_sdn.py`, la source qui configure les zones. Le rattachement hote -> zone se derive du pont porte par l'inventaire (`t17serv` est un VNet de la zone `t17`). Aucun acces reseau ici — c'est le playbook qui releve (D-23). make mtu-mesurer """ from __future__ import annotations import argparse import json import subprocess import sys from pathlib import Path RACINE = Path(__file__).resolve().parent.parent def zones_attendues() -> dict[str, int]: """MTU attendu par VNet, lu a la source qui configure les zones.""" r = subprocess.run([sys.executable, str(RACINE / "scripts" / "devis_sdn.py"), "--json"], cwd=RACINE, capture_output=True, text=True) if r.returncode != 0: raise SystemExit("Le devis SDN ne se genere pas :\n" + r.stderr.strip()[:300]) par_vnet: dict[str, int] = {} for z in (json.loads(r.stdout).get("zones") or []): for v in (z.get("vnets") or []): par_vnet[str(v.get("vnet"))] = int(z.get("mtu") or 0) return par_vnet def analyser(releve: dict, attendus: dict[str, int]) -> tuple[list[str], list[str]]: ecarts: list[str] = [] lignes: list[str] = [] invites = releve.get("invites") or {} ponts = releve.get("ponts") or {} for hote in sorted(invites): vu = int(invites[hote] or 0) pont = str(ponts.get(hote) or "") attendu = attendus.get(pont) if attendu is None: # Hors SDN (pont classique) : le devis n'a pas d'attendu derive, il se tait # plutot que d'inventer un seuil. lignes.append(f" -- {hote:<16} {vu:>5} pont {pont or '(inconnu)'} — hors zone SDN") continue ok = vu == attendu lignes.append(f" {'ok ' if ok else 'ECART'} {hote:<16} {vu:>5} attendu {attendu} " f"(zone du pont {pont})") if not ok: ecarts.append(f"{hote} : carte a {vu}, zone {pont} a {attendu}") return ecarts, lignes def main(argv: list[str] | None = None) -> int: ap = argparse.ArgumentParser(description=__doc__) ap.add_argument("--releve", default=None) a = ap.parse_args(argv) chemin = Path(a.releve) if a.releve else RACINE / "instance" / "devis-mtu.json" if not chemin.is_file(): raise SystemExit(f"Aucun releve : {chemin}\nLancer d'abord `make mtu-mesurer`.") ecarts, lignes = analyser(json.loads(chemin.read_text()), zones_attendues()) print("Devis du MTU — carte de l'invite contre MTU de sa zone\n") print("\n".join(lignes)) if ecarts: print(f"\nECART : {len(ecarts)} invite(s) ne portent pas le MTU de leur zone.") for e in ecarts: print(" -", e) print("\n Corriger : `mtu: 1` sur la carte (herite du pont) — deja pose par\n" " `cloner_vm_debian.yml`. Une VM existante doit redemarrer pour le prendre.") return 1 print("\nCONFORME : chaque invite porte le MTU de sa zone.") return 0 if __name__ == "__main__": sys.exit(main())