underlay : les tenants declares sont confrontes aux dossiers reels
Suite du filtre de portee : `underlay.tenants` nomme des DOSSIERS FRERES, et une faute de frappe y etait invisible — le tenant disparaissait des trois devis du site, qui restaient « conformes » sur ce qu'il en restait. Sur un site a UN SEUL tenant — le cas de la prochaine implantation — la faute rend un devis VIDE : une frontiere sans regle, un commutateur sans VLAN. Rien dans le mot « conforme » ne dirait qu'on vient de dessiner le vide. L'ecart est lisible sans toucher au materiel : d'un cote une liste de noms, de l'autre les dossiers presents. Il se dit donc a `make underlay` (D-75). Quatre situations, quatre messages distincts : dossier absent ; dossier sans plan/nomenclature.yml ; nomenclature non federee (index absent, categories vide, federe: false) ; plus aucun nom qui corresponde. CE QU'UN GABARIT NE DOIT PAS SUBIR. Un modele decrit du materiel, pas un site deploye : sans garde, tout modele portant un exemple de `tenants` echouerait chez quiconque n'a pas ce dossier, et P17 deviendrait rouge sur la machine du voisin. La distinction existait deja : modeles.py passe des reperes de tenants EXPLICITES (gabarit), le site les laisse deriver. La verification ne s'applique qu'au second cas. Trois tests dans test_adressage_derive.py — nom introuvable, clef absente, gabarit epargne — avec un nom absurde pour qu'aucun test ne depende des dossiers de la machine. make test 15 + 9 ; prouver 37 OK, 0 echec, 0 saute. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
3445ccb836
commit
98ab74d047
4 changed files with 113 additions and 2 deletions
35
CHANGELOG.md
35
CHANGELOG.md
|
|
@ -1,5 +1,40 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-08-18 — `make underlay` confronte les tenants déclarés aux dossiers réels
|
||||
|
||||
Suite immédiate du filtre de portée : `underlay.tenants` nomme des **dossiers frères**.
|
||||
Une faute de frappe y était invisible — le tenant disparaissait simplement des trois
|
||||
devis du site, qui restaient « conformes » sur ce qu'il en restait.
|
||||
|
||||
Sur un site à **un seul tenant** — le cas de la prochaine implantation — la faute de
|
||||
frappe rend un devis **vide** : une frontière sans règle, un commutateur sans VLAN. Et
|
||||
rien dans le mot « conforme » ne dirait qu'on vient de dessiner le vide.
|
||||
|
||||
L'écart est entièrement lisible sans toucher au matériel : d'un côté une liste de noms,
|
||||
de l'autre les dossiers présents. Il se dit donc à `make underlay` (D-75), pas au moment
|
||||
où l'on pousse dans un boîtier. Quatre situations, quatre messages distincts :
|
||||
|
||||
```
|
||||
dossier absent → aucun dossier frere de ce nom (attendu : …/OPS-Fantome)
|
||||
dossier sans nomenclature → dossier present, mais sans plan/nomenclature.yml
|
||||
nomenclature non fédérée → `index` absent, `categories` vide ou `federe: false`
|
||||
plus rien ne correspond → les devis de ce site n'auraient rien a poser
|
||||
```
|
||||
|
||||
### Ce qu'un gabarit ne doit surtout pas subir
|
||||
|
||||
Un **modèle** décrit du matériel, pas un site déployé : il ne peut nommer aucun tenant
|
||||
réel. Sans garde, tout modèle portant un exemple de `tenants` échouerait chez quiconque
|
||||
n'a pas ce dossier — et P17 (« tous les modèles valident ») deviendrait rouge sur la
|
||||
machine du voisin. La distinction existait déjà dans le code : `modeles.py` passe des
|
||||
repères de tenants **explicites**, ce qui dit « gabarit » ; le site, lui, les laisse
|
||||
dériver. La vérification ne s'applique qu'au second cas.
|
||||
|
||||
Trois tests ajoutés à `test_adressage_derive.py` — le nom introuvable, la clé absente, et
|
||||
le gabarit épargné — avec un nom volontairement absurde pour qu'aucun test ne dépende
|
||||
des dossiers de la machine qui l'exécute. `make test` 15 + 9 ; `prouver` 37/37.
|
||||
|
||||
|
||||
## 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
|
||||
|
|
|
|||
|
|
@ -118,6 +118,37 @@ def test_le_vlan_reste_sous_le_plafond_802_1q():
|
|||
assert vlan_de(INDEX_MAX, 9) < 4094
|
||||
|
||||
|
||||
def test_un_tenant_declare_introuvable_est_refuse_statiquement():
|
||||
"""`tenants` nomme des dossiers freres : une faute de frappe doit se voir ICI.
|
||||
|
||||
Sinon elle ne se voit nulle part — le tenant disparait des trois devis du site, qui
|
||||
restent « conformes » sur ce qu'il en reste. Le nom est volontairement absurde : le
|
||||
test ne doit dependre d'aucun dossier reel de la machine qui l'execute.
|
||||
"""
|
||||
erreurs = u._erreurs_tenants_declares({"tenants": ["OPS-NExistePas-9f2c"]})
|
||||
assert any("aucun dossier frere" in e for e in erreurs), erreurs
|
||||
assert any("aucun nom declare ne correspond" in e for e in erreurs), erreurs
|
||||
|
||||
|
||||
def test_sans_la_clef_tenants_rien_n_est_verifie():
|
||||
"""Clef absente = toute la federation : un site unique n'a rien a declarer."""
|
||||
assert u._erreurs_tenants_declares({"reseaux": []}) == []
|
||||
assert u._erreurs_tenants_declares(None) == []
|
||||
|
||||
|
||||
def test_un_gabarit_n_est_pas_confronte_aux_dossiers_freres():
|
||||
"""Un modele decrit du MATERIEL : il ne peut pas nommer de tenants reels.
|
||||
|
||||
La distinction tient a l'appelant — `modeles.py` passe des reperes de tenants
|
||||
explicites, ce qui dit « gabarit ». Sans cette garde, tout modele portant un exemple
|
||||
de `tenants` echouerait chez quiconque n'a pas ce dossier.
|
||||
"""
|
||||
gabarit = {"tenants": ["OPS-NExistePas-9f2c"],
|
||||
"reseaux": [{"nom": "g", "vlan": 10, "sous_reseau": "10.11.0.0/24"}]}
|
||||
erreurs = u.valider(gabarit, tenants_vlans=set(), tenants_supernets=SUPERNETS)
|
||||
assert not any("tenants:" in e for e in erreurs), erreurs
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
n = 0
|
||||
for nom, fn in sorted(globals().items()):
|
||||
|
|
|
|||
|
|
@ -338,6 +338,44 @@ def _tenants_reperes() -> tuple[set[int], list[str]]:
|
|||
return vlans, supernets
|
||||
|
||||
|
||||
def _erreurs_tenants_declares(underlay: dict | None) -> list[str]:
|
||||
"""`tenants` ne nomme que des tenants que la decouverte trouve REELLEMENT.
|
||||
|
||||
POURQUOI STATIQUEMENT (D-75). Un nom mal orthographie dans `tenants` ne se voit pas :
|
||||
le tenant disparait simplement des trois devis du site, qui restent « conformes » sur
|
||||
ce qu'il en reste. Sur un site a un seul tenant, la faute de frappe rend un devis VIDE
|
||||
— une frontiere sans regle, un commutateur sans VLAN — et rien dans le mot
|
||||
« conforme » ne dit qu'on vient de dessiner le vide.
|
||||
|
||||
L'ecart est entierement lisible sans toucher au materiel : d'un cote une liste de
|
||||
noms, de l'autre les dossiers freres. On le dit donc a `make underlay`, pas au
|
||||
moment ou l'on pousse dans un boitier.
|
||||
"""
|
||||
portes = tenants_du_site(underlay)
|
||||
if portes is None:
|
||||
return [] # clef absente = toute la federation : rien a verifier
|
||||
import devis_reseau # import paresseux : meme cycle que _tenants_reperes
|
||||
connus = {nom for nom, _pfx, _n in devis_reseau.decouvrir()}
|
||||
erreurs: list[str] = []
|
||||
for nom in portes:
|
||||
if nom in connus:
|
||||
continue
|
||||
dossier = RACINE.parent / nom
|
||||
if not dossier.is_dir():
|
||||
erreurs.append(f"tenants: « {nom} » — aucun dossier frere de ce nom "
|
||||
f"(attendu : {dossier})")
|
||||
elif not (dossier / "plan" / "nomenclature.yml").is_file():
|
||||
erreurs.append(f"tenants: « {nom} » — dossier present, mais sans "
|
||||
f"plan/nomenclature.yml : ce n'est pas une instance")
|
||||
else:
|
||||
erreurs.append(f"tenants: « {nom} » — nomenclature presente mais non federee "
|
||||
f"(`index` absent, `categories` vide ou `federe: false`)")
|
||||
if not (set(portes) & connus):
|
||||
erreurs.append("tenants: aucun nom declare ne correspond a un tenant federe — "
|
||||
"les devis de ce site n'auraient rien a poser")
|
||||
return erreurs
|
||||
|
||||
|
||||
def valider(underlay: dict | None,
|
||||
tenants_vlans: set[int] | None = None,
|
||||
tenants_supernets: list[str] | None = None,
|
||||
|
|
@ -346,6 +384,10 @@ def valider(underlay: dict | None,
|
|||
surtout l'absence de collision avec la plage tenant (VLAN et sous-reseaux)."""
|
||||
if underlay is None:
|
||||
return [] # absent = rien a valider (le devis omettra la section)
|
||||
# Les tenants reperes fournis par l'appelant = validation d'un GABARIT (modeles.py) :
|
||||
# un modele decrit du materiel, pas un site deploye, et ne peut pas nommer de tenants
|
||||
# reels. Seule la validation d'un SITE confronte `tenants` aux dossiers freres.
|
||||
mode_site = tenants_vlans is None or tenants_supernets is None
|
||||
if tenants_vlans is None or tenants_supernets is None:
|
||||
tenants_vlans, tenants_supernets = _tenants_reperes()
|
||||
t_supernets = [ipaddress.ip_network(s, strict=False) for s in tenants_supernets]
|
||||
|
|
@ -354,6 +396,8 @@ def valider(underlay: dict | None,
|
|||
idx_site = index_site(underlay)
|
||||
|
||||
erreurs: list[str] = []
|
||||
if mode_site:
|
||||
erreurs += _erreurs_tenants_declares(underlay)
|
||||
vus_vlan: dict[int, str] = {}
|
||||
vus_sous: list[tuple[ipaddress.IPv4Network, str]] = []
|
||||
noms_reseaux: set[str] = set()
|
||||
|
|
|
|||
|
|
@ -27,8 +27,9 @@ underlay:
|
|||
# 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.
|
||||
# qui doit se nommer. Un nom qu'aucun dossier frere ne fournit est REFUSE par
|
||||
# `make underlay` (ecart statique, D-75) ; 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).
|
||||
|
|
|
|||
Loading…
Reference in a new issue