diff --git a/CHANGELOG.md b/CHANGELOG.md index b254276..01762c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,36 @@ # CHANGELOG — Set-OPS +## 2026-10-05 (94) — Étape 3 : le site découvre ses locataires par leur face réseau + +**Le chemin** (`docs/conception-contextes.md` §6), étape 3. La découverte des locataires +(`devis_reseau.decouvrir_du_site`) alimente tous les devis du site : frontière, Proxmox, SDN, +pools, commutateur, et l'inventaire du site. Elle lisait la nomenclature de chaque locataire. + +**Fait** : +- relevé de ce que le site lit d'une nomenclature : **cinq champs** (`index`, `categories`, + `reservations`, `cidr_hote`, `federe`) ; la face réseau les publie (`nomenclature`) ; +- `decouvrir_du_site` part des noms que le site déclare (`underlay.tenants`) et prend index + et zones dans la face publiée de chacun. Ordre, filtre `federe`, avertissement pour un nom + sans dépôt et refus d'un site sans locataire inchangés. Sans face, la nomenclature, et elle + le dit ; +- faces republiées chez les deux locataires. + +**Le critère** : les sept sorties des consommateurs du site sont **identiques octet pour +octet** à celles de la version de HEAD, lancée à part : commutateur (8,8 Ko), frontière +(167 Ko), Proxmox (63 Ko), SDN (ses deux formats), pools, inventaire du site (81 Ko). +`make frontiere-plan` contre la frontière réelle : rien à faire. + +**Ce qui lit encore la nomenclature des locataires**, nommé : la découverte de la +**fédération** (`decouvrir`), qui sert sur le poste à garder les collisions d'index (P21) et à +lister les instances — légitime, ce n'est pas le site — et que `decouvrir_du_site` appelle +encore pour son repli ; `vpn_admin.py` (index pour le tunnel) ; `devis_proxmox_pools` +(nommer les VM, avec le plan). Ils partiront avec la matérialisation et la fin du repli. + +**Éprouvé** : +- `test_contexte.py`, 103 contrôles : un index altéré dans la face publiée est celui que le + site découvre (17, puis 99) — la découverte lit bien la face. +- `make verifier` conforme, 92/92. + ## 2026-10-05 (93) — Étape 3 : la frontière lit la face réseau **Le chemin** (`docs/conception-contextes.md` §6), étape 3. Quatrième consommateur du site, le diff --git a/docs/conception-contextes.md b/docs/conception-contextes.md index 1ed2326..b093e2d 100644 --- a/docs/conception-contextes.md +++ b/docs/conception-contextes.md @@ -182,8 +182,8 @@ les publie désormais (`sources_declarees`). La frontière, en trois temps : les (P87), les entrées publiques (P88), l'administration (P89) et les sorties (P90) sont faites. **L'étape 2 est terminée** (2026-10-05). Étape 3 : l'instancier lit la fiche déposée par le site, et l'inventaire d'un locataire se génère sans son site, à l'octet près (P91). Le locataire publie sa face -réseau (P92) ; les comptes de sauvegarde, le DNS public, le pare-feu Proxmox et la frontière du site -la lisent. +réseau (P92) ; les comptes de sauvegarde, le DNS public, le pare-feu Proxmox, la frontière et la +découverte des locataires du site la lisent. Méthodes du contrat : `site.fiche_pour(locataire)`, `locataire.face_reseau()`. Une classe n'ouvre jamais les fichiers de l'autre ; une preuve vérifiera la règle. diff --git a/scripts/contexte.py b/scripts/contexte.py index 20d0c65..36fb674 100644 --- a/scripts/contexte.py +++ b/scripts/contexte.py @@ -540,6 +540,11 @@ class Locataire(Ecosysteme): "sources": sources, "index": self.index, "zones": zones, + # Ce que le site lit de sa nomenclature (decouverte, frontiere, SDN, commutateur) : + # ces cinq champs, et rien d'autre (releve du 2026-10-05). + "nomenclature": {k: self.nomenclature()[k] for k in + ("index", "categories", "reservations", "cidr_hote", "federe") + if k in self.nomenclature()}, "machines": machines, "groupes": groupes, "administration": {"intrant": intrant, "tunnel": tunnel_admin_de(self.depot), diff --git a/scripts/devis_reseau.py b/scripts/devis_reseau.py index e39d843..01a86c9 100644 --- a/scripts/devis_reseau.py +++ b/scripts/devis_reseau.py @@ -619,6 +619,20 @@ def decouvrir() -> list[tuple[str, str, dict]]: return tenants +def _nomenclature_publiee(nom: str) -> dict | None: + """Les champs de nomenclature que ce locataire publie dans sa face reseau, ou None.""" + try: + import contexte + face = contexte.Locataire(DOSSIER_INSTANCES / nom).face_publiee() + except Exception: + face = None + if face is None or not face.get("nomenclature"): + print(f"note : {nom} n'a pas publie sa nomenclature dans sa face reseau — lecture de " + f"son plan (transition).", file=sys.stderr) + return None + return dict(face["nomenclature"]) + + def decouvrir_du_site() -> list[tuple[str, str, dict]]: """Les tenants que CE SITE heberge — `decouvrir()` restreint par `underlay.tenants`. @@ -645,12 +659,23 @@ def decouvrir_du_site() -> list[tuple[str, str, dict]]: portes = underlay_mod.tenants_du_site(underlay_mod.charger()) if portes is None: return tenants - connus = {nom for nom, _pfx, _n in tenants} + # LA FACE RESEAU D'ABORD (2026-10-05, docs/conception-contextes.md §6, etape 3). Les noms + # viennent du site (`underlay.tenants`) ; l'index et les zones, de ce que chaque locataire + # PUBLIE (`face-reseau.yml`, champ `nomenclature`) — plus de sa nomenclature. Sans face + # publiee (transition), sa nomenclature, et on le dit. + par_nom = {nom: (pfx, n) for nom, pfx, n in tenants} + retenus = [] for nom in portes: - if nom not in connus: - print(f"ATTENTION : `tenants` de l'underlay nomme « {nom} », " - f"qu'aucun dossier frere ne fournit.", file=sys.stderr) - retenus = [t for t in tenants if t[0] in set(portes)] + n = _nomenclature_publiee(nom) + if n is None: + if nom not in par_nom: + print(f"ATTENTION : `tenants` de l'underlay nomme « {nom} », " + f"qu'aucun dossier frere ne fournit.", file=sys.stderr) + continue + n = par_nom[nom][1] + if n.get("index") is not None and n.get("categories") and n.get("federe", True): + retenus.append((nom, prefixe(nom), n)) + retenus.sort(key=lambda t: t[2]["index"]) if not retenus: raise SystemExit("Aucun tenant heberge sur ce site : `underlay.tenants` n'en " "retient aucun de connu.") diff --git a/scripts/tests/test_contexte.py b/scripts/tests/test_contexte.py index 827eda8..3111aa5 100644 --- a/scripts/tests/test_contexte.py +++ b/scripts/tests/test_contexte.py @@ -551,6 +551,34 @@ def frontiere_lit_la_face() -> None: f"la frontiere lit la face publiee : {g} {port}/tcp sur le WAN, {avant} regle(s), 0 une fois le verdict bascule") +def decouverte_lit_la_face() -> None: + """La decouverte du site lit l'index que le locataire publie, pas sa nomenclature.""" + import devis_reseau + import yaml as _yaml + couples = C.couples_hebergeur() + if not couples: + print("SAUTE aucun site frere : aucune decouverte a confronter") + return + site, loc = couples[0] + chemin = loc.depot / C.NOM_FICHIER_FACE_RESEAU + original = chemin.read_text(encoding="utf-8") + + def index_decouvert(): + with C._monter(site): + return next((n["index"] for nom, _p, n in devis_reseau.decouvrir_du_site() if nom == loc.nom), None) + + avant = index_decouvert() + try: + f = _yaml.safe_load(original) + f["nomenclature"]["index"] = 99 + chemin.write_text(_yaml.safe_dump(f, allow_unicode=True, sort_keys=False), encoding="utf-8") + apres = index_decouvert() + finally: + chemin.write_text(original, encoding="utf-8") + verifier(avant == loc.index and apres == 99, + f"la decouverte du site lit l'index publie : {avant}, puis 99 une fois la face alteree") + + def main() -> int: with tempfile.TemporaryDirectory() as tmp: d = Path(tmp) @@ -570,6 +598,7 @@ def main() -> int: face_publiee() proxmox_lit_la_face() frontiere_lit_la_face() + decouverte_lit_la_face() if ECHECS: print(f"\n{len(ECHECS)} echec(s).") return 1