devis switch : les rayons de l'étoile ne sont pas des ports de bord
L'ajout du spanning-tree venait de rendre dangereuse une imprécision de titre. La section 4 s'appelait « Trunk vers Proxmox + inter-switch » et n'émettait qu'un port, que la section 6 déclarait en bord de réseau. Réutiliser ce placeholder pour les rayons revenait à mettre portfast sur les liens qui portent les BPDU — désactiver la protection anti-boucle là où elle sert. Les rayons sont dérivés et émis à part (4c côté routeur, B3a côté accès), un par switch d'accès, marqués comme non-bord. La section 4 ne désigne plus que les hyperviseurs ; la partie B distingue sa montante de son trunk terminal. `underlay.switches_acces()` devient la source unique du « qui est un switch d'accès » — pour leur devis ET pour les rayons côté routeur. Vérifié : 3 commandes portfast avec `stp` déclaré, 0 sans lui, 0 sur un rayon. Preuves : 24 OK, 0 échec. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
8f61721cb3
commit
92d31c0406
3 changed files with 80 additions and 4 deletions
21
CHANGELOG.md
21
CHANGELOG.md
|
|
@ -1,5 +1,26 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-08-01 (suite 9) — rayons de l'étoile séparés des ports terminaux
|
||||
|
||||
L'ajout du spanning-tree venait de rendre dangereuse une imprécision qui, jusque-là, n'était
|
||||
qu'un titre approximatif. La section 4 s'appelait « Trunk vers Proxmox **+ inter-switch** »
|
||||
et n'émettait qu'un port, que la section 6 déclarait en bord de réseau. Réutiliser ce
|
||||
placeholder pour les rayons vers les switches d'accès revenait à mettre `portfast` sur les
|
||||
liens qui portent les BPDU — c'est-à-dire à désactiver la protection anti-boucle exactement
|
||||
là où elle sert.
|
||||
|
||||
Les rayons sont désormais **dérivés** et émis à part (section 4c côté routeur, B3a côté
|
||||
accès), un par switch d'accès, avec l'avertissement qu'ils ne sont pas des ports de bord.
|
||||
La section 4 ne désigne plus que les hyperviseurs, et la partie B distingue sa montante
|
||||
(`B3a`) de son trunk terminal (`B3b`).
|
||||
|
||||
`underlay.switches_acces()` devient la source unique du « qui est un switch d'accès » —
|
||||
utilisée pour leur devis **et** pour les rayons côté routeur : les deux ne peuvent pas
|
||||
diverger.
|
||||
|
||||
Vérifié : trois commandes `portfast` émises avec `stp` déclaré, **aucune** sans lui, et
|
||||
**aucune** sur un rayon.
|
||||
|
||||
## 2026-08-01 (suite 8) — spanning-tree dérivé de la topologie déclarée
|
||||
|
||||
### Ajouté — `underlay.stp` et les sections 6 / B4
|
||||
|
|
|
|||
|
|
@ -102,6 +102,33 @@ def section_underlay(underlay: dict | None) -> list[str]:
|
|||
return out
|
||||
|
||||
|
||||
def port_vers(nom_hote: str) -> str:
|
||||
"""Marqueur de port physique vers un equipement nomme."""
|
||||
return f"<PORT-VERS-{nom_hote.upper()}>"
|
||||
|
||||
|
||||
def section_rayons(underlay: dict | None, vlans: str) -> list[str]:
|
||||
"""Les rayons de l'etoile : un trunk du routeur vers chaque switch d'acces.
|
||||
|
||||
Ils sont emis A PART du trunk vers les hyperviseurs parce qu'ils n'ont pas le meme
|
||||
statut vis-a-vis du spanning-tree : un rayon porte les BPDU de la fabric et ne doit
|
||||
JAMAIS etre declare en bord de reseau, sinon la protection tombe la ou elle sert.
|
||||
"""
|
||||
acces = underlay_mod.switches_acces(underlay)
|
||||
if not acces:
|
||||
return []
|
||||
out = ["! ----- 4c. Rayons de l'etoile (vers les switches d'acces) -----",
|
||||
"! /!\\ CES PORTS NE SONT PAS DES PORTS DE BORD. Ils portent les BPDU de la",
|
||||
"! fabric : y appliquer `portfast` desactiverait la protection anti-boucle",
|
||||
"! exactement la ou elle sert. La section 6 ne les declare volontairement pas."]
|
||||
for h in acces:
|
||||
out += [f"interface {port_vers(h['nom'])}",
|
||||
f" description rayon vers {h['nom']}",
|
||||
" switchport mode trunk",
|
||||
f" switchport trunk allowed vlan add {vlans}"]
|
||||
return out
|
||||
|
||||
|
||||
def _stp_lignes(dialecte: str, mode: str, priorite: int) -> list[str]:
|
||||
"""Mode + priorite de pont, dans la forme du dialecte.
|
||||
|
||||
|
|
@ -210,11 +237,19 @@ def partie_acces(underlay: dict | None, tenants: list, vlans: str,
|
|||
f"ip default-gateway {mgmt['passerelle']} ! le routeur {r_nom}"]
|
||||
else:
|
||||
out.append("! Aucun reseau underlay avec passerelle : gestion a definir a la main.")
|
||||
out += ["!", "! ----- B3. Trunks -----",
|
||||
"! Vers les hyperviseurs ET vers le routeur : memes VLANs etiquetes.",
|
||||
"interface <PORT-TRUNK>",
|
||||
out += ["!", "! ----- B3a. Montante vers le routeur (rayon de l'etoile) -----",
|
||||
"! /!\\ PAS un port de bord : il porte les BPDU. Ne jamais y mettre portfast.",
|
||||
f"interface {port_vers(r_nom)}",
|
||||
f" description montante vers {r_nom}",
|
||||
" switchport mode trunk",
|
||||
f" switchport trunk allowed vlan add {vlans}",
|
||||
"!",
|
||||
"! ----- B3b. Trunk vers les hyperviseurs -----",
|
||||
"! Port terminal : rien derriere lui ne participe au spanning-tree.",
|
||||
"interface <PORT-VERS-PROXMOX>",
|
||||
" switchport mode trunk",
|
||||
f" switchport trunk allowed vlan add {vlans}",
|
||||
" spanning-tree portfast trunk" if underlay_mod.stp(underlay) else "!",
|
||||
"!"]
|
||||
s = underlay_mod.stp(underlay)
|
||||
if s:
|
||||
|
|
@ -391,7 +426,9 @@ def generer(tenants: list[tuple[str, str, dict]], dialecte: str | None = None) -
|
|||
out.append(f" deny ip {reseau} {m} {u_reseau} {u_m}")
|
||||
out += remarque(dialecte, "Reste (Internet / inter-tenant controle) -> passerelle OPNsense")
|
||||
out.append(f" permit ip {reseau} {m} any")
|
||||
out += ["!", "! ----- 4. Trunk vers Proxmox + inter-switch (a adapter) -----"]
|
||||
out += ["!", "! ----- 4. Trunk vers les hyperviseurs -----",
|
||||
"! Ports TERMINAUX : rien derriere eux ne participe au spanning-tree.",
|
||||
"! Les liens vers les autres switches sont en section 4c, pas ici."]
|
||||
transit = underlay_mod.reseau_transit(underlay)
|
||||
# Le VLAN de transit ne va PAS vers Proxmox : aucun hyperviseur n'a d'interface
|
||||
# dessus. Il sort par le port de la frontiere (ci-dessous), et lui seul.
|
||||
|
|
@ -409,6 +446,7 @@ def generer(tenants: list[tuple[str, str, dict]], dialecte: str | None = None) -
|
|||
out.append(" switchport mode trunk")
|
||||
out.append(f" switchport trunk allowed vlan add {vlans}")
|
||||
out += ["!"] + section_frontiere(transit)
|
||||
out += ["!"] + section_rayons(underlay, ",".join(vlans_underlay + vlans_tenants))
|
||||
out += ["!"] + section_routes(underlay)
|
||||
out += section_stp(underlay, dialecte, ["<PORT-VERS-PROXMOX>", "<PORT-VERS-FRONTIERE>"])
|
||||
out += ["end", "write memory"]
|
||||
|
|
|
|||
|
|
@ -107,6 +107,23 @@ def routeur(underlay: dict | None) -> str | None:
|
|||
return (underlay or {}).get("routeur") or None
|
||||
|
||||
|
||||
def switches_acces(underlay: dict | None) -> list[dict]:
|
||||
"""Les switches d'acces : hotes du reseau de management, hors routeur.
|
||||
|
||||
Source unique du « qui est un switch d'acces » — utilisee pour leur devis (partie B)
|
||||
ET pour les rayons de l'etoile emis cote routeur. Les deux ne peuvent pas diverger.
|
||||
"""
|
||||
r_nom = routeur(underlay)
|
||||
if not r_nom:
|
||||
return []
|
||||
fabric = fabric_du_routeur(underlay)
|
||||
mgmt = next((r for r in reseaux_de_fabric(underlay, fabric) if r.get("passerelle")), None)
|
||||
if not mgmt:
|
||||
return []
|
||||
return [h for h in hotes(underlay)
|
||||
if h.get("nom") != r_nom and h.get("reseau") == mgmt["nom"]]
|
||||
|
||||
|
||||
def reseau_transit(underlay: dict | None) -> dict | None:
|
||||
"""Le reseau de TRANSIT vers la frontiere nord/sud, s'il est declare.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue