adressage : l'index est borne [0-255] — 10.300.0.0/16 n'est pas un reseau
Rien ne bornait `index`. supernet_de(300) rendait « 10.300.0.0/16 » : une CHAINE
qui ressemble a un reseau. Elle traverse tout le moteur sans bruit et n'echoue
qu'au premier ip_network() qui la lit, tres loin de l'index fautif.
La borne est posee A LA SOURCE (valider_index), pas dans un validateur de plan :
supernet_de, base3_de et vlan_de y passent toutes, donc aucune ne peut fabriquer
une adresse invalide — d'ou qu'on l'appelle : plan, GUI, devis ou test.
Elle protege un SECOND plafond, moins visible : a l'index 255 le VLAN vaut
3550+zone, sous les 4094 du 802.1Q. Un index a trois chiffres debordait aussi la.
Garde statique ajoutee au controle de federation (P21) : elle nomme le depot
fautif au lieu de laisser l'erreur remonter d'une bibliotheque.
LE TEST A TROUVE CE QUE LA RELECTURE N'AVAIT PAS VU : valider_index n'attrapait
que TypeError et ValueError, or int(float('inf')) leve OverflowError — un infini
faisait PLANTER la garde au lieu d'etre refuse.
test_underlay_bande_basse.py -> test_adressage_derive.py (il ne parlait plus
seulement de la bande basse). 12 cas, dont les refus.
Piege de structure au passage : les nouveaux cas, ajoutes apres le bloc
`if __name__ == "__main__"`, ne s'executaient pas — le bloc tourne avant que les
fonctions suivantes ne soient definies, et le compte affichait « 7 tests » au lieu
de 12. Un harnais qui compte ses propres tests doit etre lu.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
5ace6bdb95
commit
8b4ad276ca
5 changed files with 130 additions and 7 deletions
32
CHANGELOG.md
32
CHANGELOG.md
|
|
@ -1,5 +1,37 @@
|
||||||
# CHANGELOG — Set-OPS
|
# CHANGELOG — Set-OPS
|
||||||
|
|
||||||
|
## 2026-08-12 — L'index est borné : `10.300.0.0/16` n'est pas un réseau
|
||||||
|
|
||||||
|
Rien ne bornait `index`. `supernet_de(300)` rendait `"10.300.0.0/16"` — **une chaîne qui
|
||||||
|
ressemble à un réseau**. Elle traverse tout le moteur sans bruit et n'échoue qu'au premier
|
||||||
|
`ip_network()` qui la lit, très loin de l'index fautif.
|
||||||
|
|
||||||
|
La borne est posée **à la source** (`valider_index`), pas dans un validateur de plan :
|
||||||
|
toutes les fonctions dérivées y passent — `supernet_de`, `base3_de`, `vlan_de` — donc
|
||||||
|
aucune ne peut fabriquer une adresse invalide, d'où qu'on l'appelle : plan, GUI, devis ou
|
||||||
|
test.
|
||||||
|
|
||||||
|
Elle protège **un second plafond, moins visible** : à l'index 255 le VLAN vaut `3550+zone`,
|
||||||
|
sous les 4094 du 802.1Q. Un index à trois chiffres débordait aussi là.
|
||||||
|
|
||||||
|
Et une garde statique dans le contrôle de fédération (**P21**), qui nomme le dépôt fautif
|
||||||
|
au lieu de laisser l'erreur remonter d'une bibliothèque.
|
||||||
|
|
||||||
|
### Le test a trouvé ce que la relecture n'avait pas vu
|
||||||
|
|
||||||
|
`valider_index` n'attrapait que `TypeError` et `ValueError`. Or `int(float('inf'))` lève
|
||||||
|
**`OverflowError`** : un infini flottant passait la garde en la faisant planter au lieu de
|
||||||
|
la faire refuser. Corrigé — et c'est le cas de test qui l'a levé, pas ma relecture.
|
||||||
|
|
||||||
|
`test_underlay_bande_basse.py` devient `test_adressage_derive.py` : il ne parlait plus
|
||||||
|
seulement de la bande basse. **12 cas**, dont les refus.
|
||||||
|
|
||||||
|
> **Un piège de structure, au passage.** Les nouveaux cas, ajoutés après le bloc
|
||||||
|
> `if __name__ == "__main__":`, ne s'exécutaient pas — le bloc tourne avant que les
|
||||||
|
> fonctions suivantes ne soient définies, et le compte affichait tranquillement « 7 tests »
|
||||||
|
> au lieu de 12. Un harnais qui compte ses propres tests doit être lu : *sept* était la
|
||||||
|
> bonne réponse à la mauvaise question.
|
||||||
|
|
||||||
## 2026-08-12 — Le décalage de `+10` est retiré : l'index se lit dans l'adresse
|
## 2026-08-12 — Le décalage de `+10` est retiré : l'index se lit dans l'adresse
|
||||||
|
|
||||||
`supernet_de(index)` rendait `10.(10+index).0.0/16`. **Personne ne savait plus pourquoi** —
|
`supernet_de(index)` rendait `10.(10+index).0.0/16`. **Personne ne savait plus pourquoi** —
|
||||||
|
|
|
||||||
2
Makefile
2
Makefile
|
|
@ -211,7 +211,7 @@ test: ## Lance les tests unitaires (derivation de nomenclature et d'inventaire)
|
||||||
python3 scripts/tests/test_inventory_host.py
|
python3 scripts/tests/test_inventory_host.py
|
||||||
python3 scripts/tests/test_raser.py
|
python3 scripts/tests/test_raser.py
|
||||||
python3 scripts/tests/test_raser_resultat.py
|
python3 scripts/tests/test_raser_resultat.py
|
||||||
python3 scripts/tests/test_underlay_bande_basse.py
|
python3 scripts/tests/test_adressage_derive.py
|
||||||
|
|
||||||
.PHONY: verifier
|
.PHONY: verifier
|
||||||
verifier: lint test inventaire-verifier site-verifier flux-verifier syntaxe ## Rejoue les preuves SANS reecrire le rapport (verification rapide)
|
verifier: lint test inventaire-verifier site-verifier flux-verifier syntaxe ## Rejoue les preuves SANS reecrire le rapport (verification rapide)
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,9 @@ RACINE = Path(__file__).resolve().parents[1]
|
||||||
FRERES = RACINE.parent
|
FRERES = RACINE.parent
|
||||||
LIEN_INSTANCE = RACINE / "instance"
|
LIEN_INSTANCE = RACINE / "instance"
|
||||||
|
|
||||||
|
sys.path.insert(0, str(RACINE / "scripts"))
|
||||||
|
from inventory_rules import INDEX_MIN, INDEX_MAX # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
def _actif() -> Path | None:
|
def _actif() -> Path | None:
|
||||||
try:
|
try:
|
||||||
|
|
@ -98,8 +101,19 @@ def main() -> int:
|
||||||
|
|
||||||
instances = decouvrir()
|
instances = decouvrir()
|
||||||
coll = collisions(instances)
|
coll = collisions(instances)
|
||||||
|
# Un index hors bornes ne produit pas une adresse mais une chaine qui y ressemble.
|
||||||
|
# Le lever ICI, avec le nom du depot fautif, plutot que de laisser `ip_network()`
|
||||||
|
# echouer plus tard sur une valeur dont on ne saura plus d'ou elle vient.
|
||||||
|
hors_bornes = [(i["nom"], i["index"]) for i in instances
|
||||||
|
if not (INDEX_MIN <= int(i["index"]) <= INDEX_MAX)]
|
||||||
|
|
||||||
if args.verifier:
|
if args.verifier:
|
||||||
|
if hors_bornes:
|
||||||
|
print(f"erreur: index hors bornes [{INDEX_MIN}-{INDEX_MAX}] :", file=sys.stderr)
|
||||||
|
for nom, idx in sorted(hors_bornes):
|
||||||
|
print(f" - {nom} : index {idx} — le 2e octet d'une IPv4 ne peut pas le porter",
|
||||||
|
file=sys.stderr)
|
||||||
|
return 2
|
||||||
if coll:
|
if coll:
|
||||||
print("erreur: collision d'index entre instances FEDEREES "
|
print("erreur: collision d'index entre instances FEDEREES "
|
||||||
"(memes VLAN/VMID) :", file=sys.stderr)
|
"(memes VLAN/VMID) :", file=sys.stderr)
|
||||||
|
|
|
||||||
|
|
@ -472,17 +472,45 @@ def fonction_seq(nom: str) -> tuple[str, int | None]:
|
||||||
# plage utile passe de 0-245 a 0-255.
|
# plage utile passe de 0-245 a 0-255.
|
||||||
OCTET_ZONE = 15
|
OCTET_ZONE = 15
|
||||||
SOCLE_VLAN = 1000
|
SOCLE_VLAN = 1000
|
||||||
|
|
||||||
|
# BORNES DE L'INDEX. Le 2e octet d'une IPv4 vaut 0-255 : au-dela, la derivation ne
|
||||||
|
# produit plus une adresse mais une CHAINE qui y ressemble — `10.300.0.0/16` passe
|
||||||
|
# silencieusement tant que personne ne la donne a `ip_network()`, et l'erreur remonte
|
||||||
|
# alors tres loin de sa cause. Depuis le retrait du decalage de +10 (2026-08-12), la
|
||||||
|
# plage utile est exactement celle de l'octet.
|
||||||
|
INDEX_MIN, INDEX_MAX = 0, 255
|
||||||
INDEX_BAC_A_SABLE = 9 # index par defaut d'une nomenclature sans seed (jetable)
|
INDEX_BAC_A_SABLE = 9 # index par defaut d'une nomenclature sans seed (jetable)
|
||||||
|
|
||||||
|
|
||||||
|
def valider_index(index) -> int:
|
||||||
|
"""L'index, verifie et rendu en entier. Leve `ValueError` hors bornes.
|
||||||
|
|
||||||
|
Pose a la SOURCE de la derivation plutot que dans un validateur de plan : toutes
|
||||||
|
les fonctions derivees passent par ici, donc aucune ne peut fabriquer une adresse
|
||||||
|
invalide, d'ou qu'on l'appelle — plan, GUI, devis ou test.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
i = int(index)
|
||||||
|
except (TypeError, ValueError, OverflowError):
|
||||||
|
# OverflowError : `int(float('inf'))`. Trouve par le test, pas par relecture —
|
||||||
|
# un infini flottant n'est ni un entier valide ni une TypeError.
|
||||||
|
raise ValueError(f"index invalide : {index!r} n'est pas un entier") from None
|
||||||
|
if not (INDEX_MIN <= i <= INDEX_MAX):
|
||||||
|
raise ValueError(
|
||||||
|
f"index {i} hors bornes [{INDEX_MIN}-{INDEX_MAX}] : le 2e octet d'une IPv4 "
|
||||||
|
f"ne peut pas le porter. La derivation produirait « 10.{i}.0.0/16 », qui "
|
||||||
|
f"ressemble a un reseau sans en etre un.")
|
||||||
|
return i
|
||||||
|
|
||||||
|
|
||||||
def supernet_de(index: int) -> str:
|
def supernet_de(index: int) -> str:
|
||||||
"""/16 du tenant : 10.<index>.0.0/16 — l'index se lit dans l'adresse."""
|
"""/16 du tenant : 10.<index>.0.0/16 — l'index se lit dans l'adresse."""
|
||||||
return f"10.{int(index)}.0.0/16"
|
return f"10.{valider_index(index)}.0.0/16"
|
||||||
|
|
||||||
|
|
||||||
def base3_de(index: int, categorie: int) -> str:
|
def base3_de(index: int, categorie: int) -> str:
|
||||||
"""Trois premiers octets d'une zone : 10.<index>.(15+categorie)."""
|
"""Trois premiers octets d'une zone : 10.<index>.(15+categorie)."""
|
||||||
return f"10.{int(index)}.{OCTET_ZONE + int(categorie)}"
|
return f"10.{valider_index(index)}.{OCTET_ZONE + int(categorie)}"
|
||||||
|
|
||||||
|
|
||||||
def sous_reseau_de(index: int, categorie: int, cidr: int = 24) -> str:
|
def sous_reseau_de(index: int, categorie: int, cidr: int = 24) -> str:
|
||||||
|
|
@ -496,8 +524,12 @@ def passerelle_de(index: int, categorie: int) -> str:
|
||||||
|
|
||||||
|
|
||||||
def vlan_de(index: int, zone: int) -> int:
|
def vlan_de(index: int, zone: int) -> int:
|
||||||
"""VLAN 802.1Q sur le trunk convergE : 1000 + index x 10 + zone (unique globalement)."""
|
"""VLAN 802.1Q sur le trunk convergE : 1000 + index x 10 + zone (unique globalement).
|
||||||
return SOCLE_VLAN + int(index) * 10 + int(zone)
|
|
||||||
|
La meme borne y protege un second plafond : a l'index 255 le VLAN vaut 3550+zone,
|
||||||
|
sous les 4094 du 802.1Q. Sans elle, un index de trois chiffres deborderait aussi la.
|
||||||
|
"""
|
||||||
|
return SOCLE_VLAN + valider_index(index) * 10 + int(zone)
|
||||||
|
|
||||||
|
|
||||||
def deriver_nomenclature(fonction: str, seq: int, nomenclature: dict) -> dict | None:
|
def deriver_nomenclature(fonction: str, seq: int, nomenclature: dict) -> dict | None:
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""D-77 — l'underlay tient dans la BANDE BASSE du supernet de son propre site.
|
"""Adressage derive : la bande basse de l'underlay (D-77) et les bornes de l'index.
|
||||||
|
|
||||||
Ce que ce test garde, et pourquoi il existe. La regle « underlay dans la bande basse »
|
Ce que ce test garde, et pourquoi il existe. La regle « underlay dans la bande basse »
|
||||||
n'est sure que parce que les zones d'un tenant commencent a `OCTET_ZONE + 1`. C'est une
|
n'est sure que parce que les zones d'un tenant commencent a `OCTET_ZONE + 1`. C'est une
|
||||||
|
|
@ -73,10 +73,55 @@ def test_sans_index_on_retombe_sur_la_regle_stricte():
|
||||||
"sans index, tout chevauchement doit etre refuse"
|
"sans index, tout chevauchement doit etre refuse"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# --- Bornes de l'index (2026-08-12) -----------------------------------------------
|
||||||
|
# Sans elles, `supernet_de(300)` rendait « 10.300.0.0/16 » : une chaine qui RESSEMBLE
|
||||||
|
# a un reseau. Elle traverse tout le moteur sans bruit et n'echoue qu'au premier
|
||||||
|
# `ip_network()` qui la lit — tres loin de l'index fautif.
|
||||||
|
|
||||||
|
def test_les_bornes_couvrent_exactement_l_octet():
|
||||||
|
from inventory_rules import INDEX_MIN, INDEX_MAX
|
||||||
|
assert (INDEX_MIN, INDEX_MAX) == (0, 255)
|
||||||
|
|
||||||
|
|
||||||
|
def test_index_aux_bornes_accepte():
|
||||||
|
from inventory_rules import supernet_de
|
||||||
|
assert supernet_de(0) == "10.0.0.0/16"
|
||||||
|
assert supernet_de(255) == "10.255.0.0/16"
|
||||||
|
|
||||||
|
|
||||||
|
def test_index_hors_bornes_refuse():
|
||||||
|
from inventory_rules import supernet_de, base3_de, vlan_de
|
||||||
|
for fn, args in ((supernet_de, (256,)), (supernet_de, (-1,)),
|
||||||
|
(base3_de, (300, 1)), (vlan_de, (256, 1))):
|
||||||
|
try:
|
||||||
|
fn(*args)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
raise AssertionError(f"{fn.__name__}{args} aurait du etre refuse")
|
||||||
|
|
||||||
|
|
||||||
|
def test_index_non_entier_refuse():
|
||||||
|
from inventory_rules import supernet_de
|
||||||
|
for mauvais in ("abc", None, 1.5e400):
|
||||||
|
try:
|
||||||
|
supernet_de(mauvais)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
raise AssertionError(f"index {mauvais!r} aurait du etre refuse")
|
||||||
|
|
||||||
|
|
||||||
|
def test_le_vlan_reste_sous_le_plafond_802_1q():
|
||||||
|
"""La meme borne protege un SECOND plafond, moins visible."""
|
||||||
|
from inventory_rules import vlan_de, INDEX_MAX
|
||||||
|
assert vlan_de(INDEX_MAX, 9) < 4094
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
n = 0
|
n = 0
|
||||||
for nom, fn in sorted(globals().items()):
|
for nom, fn in sorted(globals().items()):
|
||||||
if nom.startswith("test_") and callable(fn):
|
if nom.startswith("test_") and callable(fn):
|
||||||
fn()
|
fn()
|
||||||
n += 1
|
n += 1
|
||||||
print(f"OK — {n} test(s) D-77 (bande basse, frontiere derivee d'OCTET_ZONE={OCTET_ZONE}).")
|
print(f"OK — {n} test(s) adressage derive : bande basse (OCTET_ZONE={OCTET_ZONE}) + bornes de l'index.")
|
||||||
Loading…
Reference in a new issue