portee : les trois devis d'un site partagent enfin la meme regle
Le commit du 2026-08-14 nommait lui-meme ce qui restait : « meme hypothese ailleurs, non corrigee — devis_sdn et devis_reseau partent du meme decouvrir(). A traiter quand ils serviront sur un second site. » C'est fait AVANT, pas pendant la visite. Les trois devis equipent le MATERIEL d'un site : la frontiere (regles, routes), le commutateur (VLAN, SVI, routes) et le SDN de l'hyperviseur (zones, VNets). Un tenant d'ailleurs y ajoutait des objets que le materiel accepte, qui ne correspondent jamais a rien, et que rien ne signale. UNE SEULE FONCTION AU LIEU D'UN FILTRE RECOPIE TROIS FOIS : `devis_reseau.decouvrir_du_site()` = decouvrir() restreint par `underlay.tenants`, la doctrine ecrite une fois. Le filtre inline de devis_opnsense est retire au profit d'elle. `admin_tous_tenants()` la suit : le routeur d'un site n'a pas a savoir revenir vers le plan de gestion d'un tenant qu'il ne porte pas. EPROUVE dans les trois situations : underlay sans la cle -> les deux tenants, comme avant ; underlay du second site -> OPS-Technolibre seul ; nom declare qu'aucun dossier ne fournit -> ATTENTION et le reste est retenu ; filtre qui ne retient rien -> refus, code 1. SANS EFFET SUR LE SITE ACTUEL : l'underlay de Chezlepro ne declare pas `tenants`, et cle absente = toute la federation (verifie : decouvrir() et decouvrir_du_site() rendent la meme liste ici). ET LA CLE EST ENFIN DOCUMENTEE — c'etait le vrai trou. `underlay.tenants` existait depuis le 14 sans figurer ni dans underlay.yml.example ni dans l'annexe du runbook d'implantation : indecouvrable pour qui monte un second site. prouver 37 OK, 0 echec, 0 saute ; make test inchange. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
577c78e659
commit
3445ccb836
6 changed files with 124 additions and 24 deletions
52
CHANGELOG.md
52
CHANGELOG.md
|
|
@ -1,5 +1,57 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-08-18 — Les trois devis d'un site partagent enfin la même portée
|
||||
|
||||
Le 14 août, la frontière a appris qu'elle ne police que les tenants de **son** site. Le
|
||||
commit le disait lui-même : *« même hypothèse ailleurs, non corrigée — `devis_sdn` et
|
||||
`devis_reseau` partent du même `decouvrir()`. À traiter quand ils serviront sur un second
|
||||
site. »* C'est fait avant, pas pendant.
|
||||
|
||||
Les trois devis équipent le **matériel d'un site** :
|
||||
|
||||
| devis | ce qu'il pose | ce qu'un tenant d'ailleurs y ajoutait |
|
||||
|---|---|---|
|
||||
| `devis_opnsense` | règles et routes de la frontière | des routes vers des sous-réseaux inexistants |
|
||||
| `devis_reseau` | VLAN, SVI, routes du commutateur | des VLAN qu'aucune VM ne peuplera |
|
||||
| `devis_sdn` | zones et VNets EVPN de l'hyperviseur | des zones sans machine |
|
||||
|
||||
Aucun de ces objets ne fait de mal visible : le matériel les accepte, ils ne
|
||||
correspondent jamais à rien, et rien ne les signale. C'est la définition même du chèque
|
||||
vert sur un périmètre vide — sauf qu'ici, il faut le lire à l'envers : une politique qui
|
||||
a l'air complète et ne protège rien.
|
||||
|
||||
### Une seule fonction, au lieu d'un filtre recopié trois fois
|
||||
|
||||
`devis_reseau.decouvrir_du_site()` — `decouvrir()` restreint par `underlay.tenants`, avec
|
||||
la doctrine écrite une fois pour les trois. Le filtre inline de `devis_opnsense` est
|
||||
retiré au profit d'elle. `admin_tous_tenants()` la suit : le routeur d'un site n'a aucune
|
||||
raison de savoir revenir vers le plan de gestion d'un tenant qu'il ne porte pas.
|
||||
|
||||
Éprouvé dans les trois situations qui comptent :
|
||||
|
||||
```
|
||||
underlay sans la clé → ['OPS-Chezlepro', 'OPS-Technolibre'] (identique à avant)
|
||||
underlay du second site → ['OPS-Technolibre']
|
||||
un nom qu'aucun dossier ne fournit → ATTENTION, et le reste est retenu
|
||||
le filtre ne retient rien → refus, code 1 (jamais un devis vide)
|
||||
```
|
||||
|
||||
**Sans effet sur le site actuel** : l'underlay de Chezlepro ne déclare pas `tenants`, et
|
||||
clé absente = toute la fédération. `prouver` 37/37, `make test` inchangé.
|
||||
|
||||
### Et la clé est enfin documentée
|
||||
|
||||
C'était le vrai trou : `underlay.tenants` existait depuis le 14 et n'apparaissait **ni**
|
||||
dans `underlay.yml.example` **ni** dans l'annexe du runbook d'implantation. Un exploitant
|
||||
montant un second site ne pouvait pas la découvrir — il aurait posé la politique du
|
||||
premier tenant chez le second, et le seul symptôme aurait été un silence.
|
||||
|
||||
> **Traiter la deuxième occurrence quand on nomme la première.** Le défaut de portée
|
||||
> était écrit noir sur blanc dans le commit du 14, avec la liste des endroits où il
|
||||
> restait. Quatre jours plus tard, le coût de le finir est d'une heure ; sur place, il
|
||||
> aurait coûté une visite.
|
||||
|
||||
|
||||
## 2026-08-18 — Le panneau d'intrants effaçait la mémoire écrite du dépôt
|
||||
|
||||
Un enregistrement du panneau « Intrants de base », à 13:48, a emporté **94 lignes de
|
||||
|
|
|
|||
|
|
@ -208,6 +208,12 @@ underlay:
|
|||
# la bande basse lui appartient. Sans elle, `make underlay` refuse le réseau de
|
||||
# gestion en le prenant pour celui d'un AUTRE site — message déroutant, cause triviale.
|
||||
index: <index>
|
||||
# LES TENANTS QUE CE SITE PORTE — noms de dossier, pas de fantaisie. Les trois devis
|
||||
# d'équipement (frontière, commutateur, SDN) découvrent TOUTE la fédération : sans
|
||||
# cette clé, le second site se voit proposer les règles, les VLAN et les zones du
|
||||
# premier. Le matériel les accepte, aucune ne correspond jamais à un paquet, et rien
|
||||
# ne le signale. Un site UNIQUE n'a rien à déclarer ; c'est le second qui se nomme.
|
||||
tenants: [OPS-<tenant>]
|
||||
routeur: <nom-du-commutateur> # racine du spanning-tree, pas un routeur
|
||||
# `stp` exige `routeur` : ne pas le déclarer tant qu'aucun commutateur ne l'est.
|
||||
routage_tenants: sdn # le routage inter-zone vit sur l'hyperviseur
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ from resoudre_flux import ( # noqa: E402
|
|||
# `inventaire_de` / `admin_de` viennent de devis_reseau : les routes de retour du
|
||||
# switch et les regles de la frontiere doivent lire les MEMES tenants, sinon l'un
|
||||
# certifie des routes que l'autre n'emet pas.
|
||||
from devis_reseau import admin_de, decouvrir, inventaire_de, prefixe # noqa: E402
|
||||
from devis_reseau import admin_de, decouvrir, decouvrir_du_site, inventaire_de, prefixe # noqa: E402
|
||||
# Le lien de transit (et donc le prochain saut) vit dans l'underlay : il est PARTAGE
|
||||
# par tous les tenants, il ne peut deriver d'aucun index. Source unique, pas un intrant.
|
||||
import underlay as underlay_mod # noqa: E402
|
||||
|
|
@ -792,28 +792,14 @@ def main(argv: list[str]) -> int:
|
|||
ap.add_argument("--verifier", action="store_true", help="Valide le devis sans l'afficher.")
|
||||
args = ap.parse_args(argv)
|
||||
|
||||
tenants = decouvrir()
|
||||
# UNE FRONTIERE NE POLICE QUE LES TENANTS DE SON SITE (2026-08-14). Le filtre est
|
||||
# devenu la source unique des trois devis qui equipent un site — commutateur, SDN et
|
||||
# frontiere : cf. `devis_reseau.decouvrir_du_site`.
|
||||
tenants = decouvrir_du_site()
|
||||
if not tenants:
|
||||
print("Aucune instance federee trouvee (../*/plan/nomenclature.yml).", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# UNE FRONTIERE NE POLICE QUE LES TENANTS DE SON SITE. `decouvrir()` rend toute la
|
||||
# federation ; l'hebergeur declare dans SON underlay ceux qu'il porte reellement.
|
||||
# Sans ce filtre, la frontiere du second site recevait les regles et les routes du
|
||||
# premier — acceptees par le boitier, jamais correspondantes, jamais signalees.
|
||||
portes = underlay_mod.tenants_du_site(underlay_mod.charger())
|
||||
if portes is not None:
|
||||
connus = {nom for nom, _, _ in tenants}
|
||||
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)
|
||||
tenants = [t for t in tenants if t[0] in set(portes)]
|
||||
if not tenants:
|
||||
print("Aucun tenant heberge sur ce site : `underlay.tenants` n'en retient "
|
||||
"aucun de connu.", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
devis = construire(tenants)
|
||||
|
||||
if args.verifier:
|
||||
|
|
|
|||
|
|
@ -451,14 +451,17 @@ def admin_de(nom_instance: str) -> list[str]:
|
|||
|
||||
|
||||
def admin_tous_tenants() -> list[str]:
|
||||
"""Union des reseaux d'administration de TOUS les tenants federes.
|
||||
"""Union des reseaux d'administration des tenants heberges sur CE SITE.
|
||||
|
||||
Le switch routeur est partage : il doit savoir revenir vers chaque plan de gestion,
|
||||
pas seulement celui de l'instance active. Router n'est pas autoriser — le
|
||||
cloisonnement se fait a la frontiere, par un alias distinct par tenant.
|
||||
SOURCE UNIQUE : `devis_opnsense` importe cette fonction plutot que d'en refaire une.
|
||||
|
||||
Bornee au site depuis le 2026-08-18 : le routeur d'un site n'a aucune raison de
|
||||
savoir revenir vers le plan de gestion d'un tenant qu'il ne porte pas.
|
||||
"""
|
||||
return sorted({c for nom, _pfx, _n in decouvrir() for c in admin_de(nom)})
|
||||
return sorted({c for nom, _pfx, _n in decouvrir_du_site() for c in admin_de(nom)})
|
||||
|
||||
|
||||
def route_statique(reseau: str, saut: str, dialecte: str) -> str:
|
||||
|
|
@ -569,6 +572,44 @@ def decouvrir() -> list[tuple[str, str, dict]]:
|
|||
return tenants
|
||||
|
||||
|
||||
def decouvrir_du_site() -> list[tuple[str, str, dict]]:
|
||||
"""Les tenants que CE SITE heberge — `decouvrir()` restreint par `underlay.tenants`.
|
||||
|
||||
POURQUOI CE FILTRE (mesure du 2026-08-14, sur le second site). `decouvrir()` rend
|
||||
TOUTE la federation : tout dossier frere portant une nomenclature avec un index.
|
||||
C'etait juste tant qu'il n'y avait qu'un site — l'hebergeur unique portait bien tous
|
||||
les tenants. Des le second, c'est faux : `make frontiere-plan` voulait poser sur la
|
||||
frontiere de Technolibre les regles ET les routes de Chezlepro, dont six routes vers
|
||||
des sous-reseaux qui n'existent pas la-bas. Le boitier les aurait acceptees, aucune
|
||||
n'aurait jamais correspondu a un paquet, et rien ne l'aurait signale.
|
||||
|
||||
Le meme raisonnement vaut pour les DEUX autres devis qui equipent un site : le
|
||||
commutateur ne porte que les VLAN et les SVI des tenants poses dessus, et le SDN de
|
||||
l'hyperviseur ne cree que leurs zones. Un objet provisionne pour un tenant absent est
|
||||
au mieux inutile, au pire un chemin ouvert que personne ne relit.
|
||||
|
||||
CLE ABSENTE = toute la federation : un site unique n'a rien a declarer, c'est le
|
||||
second qui doit se nommer. Un nom declare qu'aucun dossier frere ne fournit est
|
||||
signale, pas ignore. Un filtre qui ne retient RIEN est un refus, pas un devis vide.
|
||||
"""
|
||||
tenants = decouvrir()
|
||||
if not tenants:
|
||||
return tenants # « aucune instance federee » : au caller de le dire
|
||||
portes = underlay_mod.tenants_du_site(underlay_mod.charger())
|
||||
if portes is None:
|
||||
return tenants
|
||||
connus = {nom for nom, _pfx, _n in tenants}
|
||||
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)]
|
||||
if not retenus:
|
||||
raise SystemExit("Aucun tenant heberge sur ce site : `underlay.tenants` n'en "
|
||||
"retient aucun de connu.")
|
||||
return retenus
|
||||
|
||||
|
||||
def generer(tenants: list[tuple[str, str, dict]], dialecte: str | None = None) -> str:
|
||||
dialecte = dialecte_effectif(dialecte)
|
||||
underlay = underlay_mod.charger()
|
||||
|
|
@ -756,7 +797,7 @@ def main() -> None:
|
|||
ap.add_argument("--dialecte", choices=DIALECTES, default=None,
|
||||
help="CLI du commutateur (defaut : SETOPS_DIALECTE, sinon underlay.dialecte)")
|
||||
args = ap.parse_args()
|
||||
tenants = decouvrir()
|
||||
tenants = decouvrir_du_site() # le commutateur ne porte que les tenants de son site
|
||||
if not tenants:
|
||||
print("Aucune instance 'ip-miroir' federee trouvee (../*/plan/nomenclature.yml).",
|
||||
file=sys.stderr)
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ RACINE = Path(__file__).resolve().parents[1]
|
|||
sys.path.insert(0, str(RACINE / "scripts"))
|
||||
|
||||
from inventory_rules import passerelle_de, sous_reseau_de, supernet_de, vlan_de # noqa: E402
|
||||
from devis_reseau import decouvrir # noqa: E402
|
||||
from devis_reseau import decouvrir_du_site # noqa: E402
|
||||
import underlay as underlay_mod # noqa: E402
|
||||
|
||||
|
||||
|
|
@ -334,7 +334,8 @@ def main(argv: list[str]) -> int:
|
|||
ap.add_argument("--verifier", action="store_true")
|
||||
args = ap.parse_args(argv)
|
||||
|
||||
devis = construire(decouvrir())
|
||||
# Le SDN d'un hyperviseur ne cree que les zones des tenants qu'il porte.
|
||||
devis = construire(decouvrir_du_site())
|
||||
if args.verifier:
|
||||
zones = devis["zones"]
|
||||
if not zones:
|
||||
|
|
|
|||
|
|
@ -17,6 +17,20 @@
|
|||
# Storage/Ceph restent en general L2 pur (pas de passerelle).
|
||||
---
|
||||
underlay:
|
||||
# LES TENANTS QUE CE SITE PORTE — noms de DOSSIER des instances (../OPS-<nom>).
|
||||
#
|
||||
# Les trois devis qui equipent un site (frontiere, commutateur, SDN) decouvrent TOUTE
|
||||
# la federation : tout dossier frere portant une nomenclature avec un index. C'est
|
||||
# juste tant qu'il n'y a qu'un site ; des le second, le devis du site B veut y poser
|
||||
# les regles, les VLAN et les zones du tenant du site A. Le materiel les accepte,
|
||||
# aucune ne correspond jamais a un paquet, et rien ne le signale (mesure du
|
||||
# 2026-08-14 : trente objets de trop, dont six routes vers des reseaux inexistants).
|
||||
#
|
||||
# ABSENTE = toute la federation : un site UNIQUE n'a rien a declarer, c'est le second
|
||||
# qui doit se nommer. Un nom qu'aucun dossier frere ne fournit est signale ; un filtre
|
||||
# qui ne retient aucun tenant connu est un refus, pas un devis vide.
|
||||
#tenants: [OPS-monorganisation]
|
||||
|
||||
# FABRICS PHYSIQUES. Chaque reseau appartient a une fabric (`principal` par defaut).
|
||||
# Deux fabrics ne partagent aucun cable : le devis d'une fabric ne declare ni ne
|
||||
# transporte les VLAN d'une autre, et leurs spanning-tree sont independants.
|
||||
|
|
|
|||
Loading…
Reference in a new issue