2026-08-12 15:59:56 -04:00
|
|
|
#!/usr/bin/env python3
|
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>
2026-08-12 16:21:33 -04:00
|
|
|
"""Adressage derive : la bande basse de l'underlay (D-77) et les bornes de l'index.
|
2026-08-12 15:59:56 -04:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
propriete de `inventory_rules`, pas une place qu'on aurait constatee libre un jour : si
|
|
|
|
|
`OCTET_ZONE` bougeait sans que la borne suive, la validation continuerait a dire « oui »
|
|
|
|
|
en laissant l'underlay recouvrir les zones — et personne ne le verrait avant que deux
|
|
|
|
|
machines ne se disputent une adresse.
|
|
|
|
|
|
|
|
|
|
Le test derive donc sa frontiere de la MEME constante que le code, et verifie les trois
|
|
|
|
|
cas qui comptent, y compris ceux ou un prefixe FRANCHIT la frontiere (un /19 couvre les
|
|
|
|
|
octets 0 a 31 : il deborde, meme s'il commence dans la bande basse).
|
|
|
|
|
"""
|
|
|
|
|
import sys
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
RACINE = Path(__file__).resolve().parent.parent.parent
|
|
|
|
|
sys.path.insert(0, str(RACINE / "scripts"))
|
|
|
|
|
|
|
|
|
|
import underlay as u # noqa: E402
|
|
|
|
|
from inventory_rules import OCTET_ZONE # noqa: E402
|
|
|
|
|
|
insemination : declarer le lien, l'emettre d'un seul cote, et un test rouge
L'insemination avait un nom depuis ce matin ; elle n'avait pas de flux. Deux
declarations, aux deux bouts, et rien d'autre :
serveur_ops_site egress 22/tcp -> serveur_ops_tenant
serveur_ops_tenant ingress 22/tcp <- runner_site partage: true
ETROIT PAR CONSTRUCTION : il vise le GROUPE `serveur_ops_tenant`, qu'un ecosysteme
ne pose que sur une machine. Au socle, il aurait ouvert le SSH du site vers toute
la flotte du tenant.
L'en-tete disait « ce role n'entre JAMAIS chez un tenant ». Frontiere intenable :
`creer-vm` exige `_instance-requise`, et le runner du site avait deja du basculer
son symlink `instance` sur OPS-Chezlepro pour materialiser ses VM. Declarer ne cree
pas ce pouvoir — ca rend limitable un pouvoir qui s'exercait sans borne. Ce qui
reste interdit n'est pas une regle mais un FAIT : il n'a pas la voute du tenant.
LA REGLE EST EMISE D'UN SEUL COTE, et pas celui qu'on croit. Le paquet penetre le
pare-feu par la patte du SITE, pas par le transit : la regle appartient au cote
site du devis. L'emettre aussi depuis l'`ingress` du tenant aurait produit une
seconde regle sur la mauvaise interface — jamais evaluee, indiscernable d'une regle
utile. La declaration du tenant pose sa regle nftables, et elle seule :
ip saddr { 10.0.31.11 } tcp dport 22 accept
L'adresse DERIVE du plan du site. Ecrite a la main, elle aurait survecu au prochain
deplacement du runner sans bruit — le site a deja deplace ses machines le 08-25.
Plan de la frontiere : 2 objets a creer, 0 a retirer, 126 inchanges. RIEN D'APPLIQUE.
P41 APPLIQUEE AU PLAN DU SITE : `resoudre_flux` en avait besoin a son tour ; les
trois lecteurs demenagent dans `underlay` et `devis_opnsense` delegue.
DEUX GARDES ONT TRAVAILLE : P33 a refuse `ingress 22` sur un hote portant deja le
sshd du socle (reponse : `partage: true`, comme `serveur_backup`), et le devis a
refuse d'emettre vers un alias vide.
UN TEST ROUGE DEPUIS TROIS JOURS. `test_adressage_derive` construisait un site avec
un `index` — or un SITE n'en a pas depuis add94f2 (08-25), remplace par
`bande_basse_de`. Invisible parce que le geste quotidien est `make prouver`, qui ne
joue pas les tests. Remis sur le contrat actuel, avec sa contrepartie : sans
`bande_basse_de`, aucun chevauchement n'est tolere.
make verifier : vert. make prouver : CONFORME, 52 OK, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-28 13:09:28 -04:00
|
|
|
SUPERNETS = ["10.11.0.0/16", "10.17.0.0/16"] # deux tenants : index 11 et 17
|
|
|
|
|
MIEN = "OPS-Mien" # le tenant dont ce site occupe la bande basse
|
|
|
|
|
MIEN_INDEX = 11
|
|
|
|
|
|
|
|
|
|
# UN SITE N'A PAS D'INDEX (add94f2, 2026-08-25). Il ne derive pas : il DECLARE ses
|
|
|
|
|
# adresses, parce qu'il EST le terrain. Ce qui a remplace l'index, c'est `bande_basse_de`
|
|
|
|
|
# — porte par CHAQUE reseau, et non par le site : deux reseaux d'un meme site peuvent
|
|
|
|
|
# occuper la bande basse de deux tenants differents.
|
|
|
|
|
#
|
|
|
|
|
# CE TEST A SURVECU TROIS JOURS A CE CHANGEMENT EN ETANT ROUGE (2026-08-25 -> 08-28), et
|
|
|
|
|
# personne ne l'a vu : le geste quotidien est `make prouver`, qui ne joue pas les tests.
|
|
|
|
|
# Un test rouge que rien ne lit ne garde plus rien.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _erreurs(sous: str, mien: str | None = MIEN) -> list[str]:
|
|
|
|
|
reseau = {"nom": "g", "vlan": 10, "sous_reseau": sous}
|
|
|
|
|
if mien is not None:
|
|
|
|
|
reseau["bande_basse_de"] = mien
|
|
|
|
|
site = {"reseaux": [reseau], "tenants": {MIEN: MIEN_INDEX}}
|
2026-08-12 15:59:56 -04:00
|
|
|
return [e for e in u.valider(site, tenants_vlans=set(), tenants_supernets=SUPERNETS)
|
|
|
|
|
if "deborde" in e or "chevauche le supernet" in e]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_la_borne_derive_de_la_regle_des_zones():
|
|
|
|
|
"""La frontiere n'est pas 16 en dur : elle suit `OCTET_ZONE`."""
|
|
|
|
|
assert u.PREMIER_OCTET_ZONE == OCTET_ZONE + 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_bande_basse_de_son_propre_supernet_acceptee():
|
adressage : le decalage de +10 est retire, l'index se lit dans l'adresse
supernet_de rendait 10.(10+index).0.0/16. Personne ne savait plus pourquoi : ni le
commentaire de la constante, ni le wiki, ni le commit fondateur 36a882b ne le
justifiaient. Trois endroits consultes, zero raison ecrite.
Ses deux effets constates :
- il reservait 10.0-10.9 sous la plage tenant. Utile tant que l'underlay vivait
la — mais D-77 l'a fait entrer dans la bande basse de son propre /16, ce qui a
vide cette reserve de son role la veille ;
- il eloignait le premier tenant de 10.0.0.0/16, la plage la plus repandue en
reseau domestique. Ce risque revient donc aux index bas, et c'est ASSUME.
En echange l'index se lit directement dans l'adresse (17 -> 10.17.x.x) et le
plafond passe de 245 a 255 ecosystemes federes.
Chezlepro (17) 10.27.0.0/16 -> 10.17.0.0/16
Technolibre (11) 10.21.0.0/16 -> 10.11.0.0/16
lab (1) 10.11.0.0/16 -> 10.1.0.0/16
Doc alignee : les trois pages du wiki, multi-instances.md (plafond et exemple
devenus faux arithmetiquement), sdn-evpn.md, le libelle de la GUI, la docstring
d'underlay.py, D-77, et le document de preparation d'un site hebergeur. Les
CONSTATS DE TERRAIN dates sont laisses tels quels : ce sont des mesures.
CE COMMIT NE RENUMEROTE RIEN. Il change ce que le plan DERIVE ; l'inventaire
applique porte toujours 10.27.x.x et les quatorze VM tournent dessus. Appliquer
sans reconstruire rendrait la flotte injoignable — le renumerotage est une
operation a part, a mener a froid.
Au passage, retire un debris : une copie de conflit Nextcloud de
serveur_powerdns/defaults/main.yml, IDENTIQUE a l'original, commitee par accident
dans 1295eea et jamais chargee par Ansible.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 16:16:08 -04:00
|
|
|
for sous in ("10.11.0.0/24", "10.11.4.0/24", "10.11.5.0/24", "10.11.15.0/24"):
|
2026-08-12 15:59:56 -04:00
|
|
|
assert _erreurs(sous) == [], f"{sous} devrait etre conforme (bande basse)"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_bande_des_zones_refusee():
|
adressage : le decalage de +10 est retire, l'index se lit dans l'adresse
supernet_de rendait 10.(10+index).0.0/16. Personne ne savait plus pourquoi : ni le
commentaire de la constante, ni le wiki, ni le commit fondateur 36a882b ne le
justifiaient. Trois endroits consultes, zero raison ecrite.
Ses deux effets constates :
- il reservait 10.0-10.9 sous la plage tenant. Utile tant que l'underlay vivait
la — mais D-77 l'a fait entrer dans la bande basse de son propre /16, ce qui a
vide cette reserve de son role la veille ;
- il eloignait le premier tenant de 10.0.0.0/16, la plage la plus repandue en
reseau domestique. Ce risque revient donc aux index bas, et c'est ASSUME.
En echange l'index se lit directement dans l'adresse (17 -> 10.17.x.x) et le
plafond passe de 245 a 255 ecosystemes federes.
Chezlepro (17) 10.27.0.0/16 -> 10.17.0.0/16
Technolibre (11) 10.21.0.0/16 -> 10.11.0.0/16
lab (1) 10.11.0.0/16 -> 10.1.0.0/16
Doc alignee : les trois pages du wiki, multi-instances.md (plafond et exemple
devenus faux arithmetiquement), sdn-evpn.md, le libelle de la GUI, la docstring
d'underlay.py, D-77, et le document de preparation d'un site hebergeur. Les
CONSTATS DE TERRAIN dates sont laisses tels quels : ce sont des mesures.
CE COMMIT NE RENUMEROTE RIEN. Il change ce que le plan DERIVE ; l'inventaire
applique porte toujours 10.27.x.x et les quatorze VM tournent dessus. Appliquer
sans reconstruire rendrait la flotte injoignable — le renumerotage est une
operation a part, a mener a froid.
Au passage, retire un debris : une copie de conflit Nextcloud de
serveur_powerdns/defaults/main.yml, IDENTIQUE a l'original, commitee par accident
dans 1295eea et jamais chargee par Ansible.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 16:16:08 -04:00
|
|
|
for sous in (f"10.11.{OCTET_ZONE + 1}.0/24", "10.11.18.0/24", "10.11.16.0/20"):
|
2026-08-12 15:59:56 -04:00
|
|
|
assert _erreurs(sous), f"{sous} devrait etre REFUSE (bande des zones)"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_un_prefixe_qui_franchit_la_frontiere_est_refuse():
|
|
|
|
|
"""Le piege : il COMMENCE dans la bande basse et deborde. Un test qui ne
|
|
|
|
|
regarderait que le premier octet le laisserait passer."""
|
adressage : le decalage de +10 est retire, l'index se lit dans l'adresse
supernet_de rendait 10.(10+index).0.0/16. Personne ne savait plus pourquoi : ni le
commentaire de la constante, ni le wiki, ni le commit fondateur 36a882b ne le
justifiaient. Trois endroits consultes, zero raison ecrite.
Ses deux effets constates :
- il reservait 10.0-10.9 sous la plage tenant. Utile tant que l'underlay vivait
la — mais D-77 l'a fait entrer dans la bande basse de son propre /16, ce qui a
vide cette reserve de son role la veille ;
- il eloignait le premier tenant de 10.0.0.0/16, la plage la plus repandue en
reseau domestique. Ce risque revient donc aux index bas, et c'est ASSUME.
En echange l'index se lit directement dans l'adresse (17 -> 10.17.x.x) et le
plafond passe de 245 a 255 ecosystemes federes.
Chezlepro (17) 10.27.0.0/16 -> 10.17.0.0/16
Technolibre (11) 10.21.0.0/16 -> 10.11.0.0/16
lab (1) 10.11.0.0/16 -> 10.1.0.0/16
Doc alignee : les trois pages du wiki, multi-instances.md (plafond et exemple
devenus faux arithmetiquement), sdn-evpn.md, le libelle de la GUI, la docstring
d'underlay.py, D-77, et le document de preparation d'un site hebergeur. Les
CONSTATS DE TERRAIN dates sont laisses tels quels : ce sont des mesures.
CE COMMIT NE RENUMEROTE RIEN. Il change ce que le plan DERIVE ; l'inventaire
applique porte toujours 10.27.x.x et les quatorze VM tournent dessus. Appliquer
sans reconstruire rendrait la flotte injoignable — le renumerotage est une
operation a part, a mener a froid.
Au passage, retire un debris : une copie de conflit Nextcloud de
serveur_powerdns/defaults/main.yml, IDENTIQUE a l'original, commitee par accident
dans 1295eea et jamais chargee par Ansible.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 16:16:08 -04:00
|
|
|
for sous in ("10.11.0.0/19", "10.11.0.0/16"):
|
2026-08-12 15:59:56 -04:00
|
|
|
assert _erreurs(sous), f"{sous} couvre au-dela de {OCTET_ZONE} : a refuser"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_supernet_d_un_autre_site_refuse():
|
|
|
|
|
"""C'est ce qui empeche deux sites d'etre relies — l'erreur la plus couteuse."""
|
adressage : le decalage de +10 est retire, l'index se lit dans l'adresse
supernet_de rendait 10.(10+index).0.0/16. Personne ne savait plus pourquoi : ni le
commentaire de la constante, ni le wiki, ni le commit fondateur 36a882b ne le
justifiaient. Trois endroits consultes, zero raison ecrite.
Ses deux effets constates :
- il reservait 10.0-10.9 sous la plage tenant. Utile tant que l'underlay vivait
la — mais D-77 l'a fait entrer dans la bande basse de son propre /16, ce qui a
vide cette reserve de son role la veille ;
- il eloignait le premier tenant de 10.0.0.0/16, la plage la plus repandue en
reseau domestique. Ce risque revient donc aux index bas, et c'est ASSUME.
En echange l'index se lit directement dans l'adresse (17 -> 10.17.x.x) et le
plafond passe de 245 a 255 ecosystemes federes.
Chezlepro (17) 10.27.0.0/16 -> 10.17.0.0/16
Technolibre (11) 10.21.0.0/16 -> 10.11.0.0/16
lab (1) 10.11.0.0/16 -> 10.1.0.0/16
Doc alignee : les trois pages du wiki, multi-instances.md (plafond et exemple
devenus faux arithmetiquement), sdn-evpn.md, le libelle de la GUI, la docstring
d'underlay.py, D-77, et le document de preparation d'un site hebergeur. Les
CONSTATS DE TERRAIN dates sont laisses tels quels : ce sont des mesures.
CE COMMIT NE RENUMEROTE RIEN. Il change ce que le plan DERIVE ; l'inventaire
applique porte toujours 10.27.x.x et les quatorze VM tournent dessus. Appliquer
sans reconstruire rendrait la flotte injoignable — le renumerotage est une
operation a part, a mener a froid.
Au passage, retire un debris : une copie de conflit Nextcloud de
serveur_powerdns/defaults/main.yml, IDENTIQUE a l'original, commitee par accident
dans 1295eea et jamais chargee par Ansible.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 16:16:08 -04:00
|
|
|
assert _erreurs("10.17.0.0/24"), "le supernet d'un autre site doit etre refuse"
|
2026-08-12 15:59:56 -04:00
|
|
|
|
|
|
|
|
|
insemination : declarer le lien, l'emettre d'un seul cote, et un test rouge
L'insemination avait un nom depuis ce matin ; elle n'avait pas de flux. Deux
declarations, aux deux bouts, et rien d'autre :
serveur_ops_site egress 22/tcp -> serveur_ops_tenant
serveur_ops_tenant ingress 22/tcp <- runner_site partage: true
ETROIT PAR CONSTRUCTION : il vise le GROUPE `serveur_ops_tenant`, qu'un ecosysteme
ne pose que sur une machine. Au socle, il aurait ouvert le SSH du site vers toute
la flotte du tenant.
L'en-tete disait « ce role n'entre JAMAIS chez un tenant ». Frontiere intenable :
`creer-vm` exige `_instance-requise`, et le runner du site avait deja du basculer
son symlink `instance` sur OPS-Chezlepro pour materialiser ses VM. Declarer ne cree
pas ce pouvoir — ca rend limitable un pouvoir qui s'exercait sans borne. Ce qui
reste interdit n'est pas une regle mais un FAIT : il n'a pas la voute du tenant.
LA REGLE EST EMISE D'UN SEUL COTE, et pas celui qu'on croit. Le paquet penetre le
pare-feu par la patte du SITE, pas par le transit : la regle appartient au cote
site du devis. L'emettre aussi depuis l'`ingress` du tenant aurait produit une
seconde regle sur la mauvaise interface — jamais evaluee, indiscernable d'une regle
utile. La declaration du tenant pose sa regle nftables, et elle seule :
ip saddr { 10.0.31.11 } tcp dport 22 accept
L'adresse DERIVE du plan du site. Ecrite a la main, elle aurait survecu au prochain
deplacement du runner sans bruit — le site a deja deplace ses machines le 08-25.
Plan de la frontiere : 2 objets a creer, 0 a retirer, 126 inchanges. RIEN D'APPLIQUE.
P41 APPLIQUEE AU PLAN DU SITE : `resoudre_flux` en avait besoin a son tour ; les
trois lecteurs demenagent dans `underlay` et `devis_opnsense` delegue.
DEUX GARDES ONT TRAVAILLE : P33 a refuse `ingress 22` sur un hote portant deja le
sshd du socle (reponse : `partage: true`, comme `serveur_backup`), et le devis a
refuse d'emettre vers un alias vide.
UN TEST ROUGE DEPUIS TROIS JOURS. `test_adressage_derive` construisait un site avec
un `index` — or un SITE n'en a pas depuis add94f2 (08-25), remplace par
`bande_basse_de`. Invisible parce que le geste quotidien est `make prouver`, qui ne
joue pas les tests. Remis sur le contrat actuel, avec sa contrepartie : sans
`bande_basse_de`, aucun chevauchement n'est tolere.
make verifier : vert. make prouver : CONFORME, 52 OK, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-28 13:09:28 -04:00
|
|
|
def test_sans_bande_basse_declaree_tout_chevauchement_est_refuse():
|
|
|
|
|
"""Le defaut est le refus : un reseau qui ne DIT PAS de quel tenant il occupe la
|
|
|
|
|
bande basse n'a droit a aucun chevauchement, pas meme sur 10.11.x. Ne pas declarer
|
|
|
|
|
ne peut pas valoir permission."""
|
|
|
|
|
assert _erreurs("10.11.0.0/24", mien=None), \
|
|
|
|
|
"sans `bande_basse_de`, un chevauchement doit etre refuse"
|
|
|
|
|
|
|
|
|
|
|
2026-08-12 15:59:56 -04:00
|
|
|
def test_hors_de_tout_supernet_accepte():
|
|
|
|
|
"""Le stockage (192.168.x, identique partout) et l'heritage 10.0.x avant migration."""
|
|
|
|
|
for sous in ("192.168.20.0/24", "192.168.31.0/24", "10.0.0.0/24"):
|
|
|
|
|
assert _erreurs(sous) == [], f"{sous} est hors des supernets : conforme"
|
|
|
|
|
|
|
|
|
|
|
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>
2026-08-12 16:21:33 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- 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
|
|
|
|
|
|
|
|
|
|
|
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>
2026-08-18 16:55:49 -04:00
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
2026-08-12 15:59:56 -04:00
|
|
|
if __name__ == "__main__":
|
|
|
|
|
n = 0
|
|
|
|
|
for nom, fn in sorted(globals().items()):
|
|
|
|
|
if nom.startswith("test_") and callable(fn):
|
|
|
|
|
fn()
|
|
|
|
|
n += 1
|
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>
2026-08-12 16:21:33 -04:00
|
|
|
print(f"OK — {n} test(s) adressage derive : bande basse (OCTET_ZONE={OCTET_ZONE}) + bornes de l'index.")
|