instancier : accepter un adressage declare, pour les plans sans index

Un SITE decrit les machines de l'hebergeur : pas d'index, pas de cohabitation
avec les tenants, il vit dans le reseau d'administration. Rien ne peut donc
deriver d'un seed inexistant.

L'explicite (`ip`, `vmid`, `vlan`) gagne sur le derive, et les deux chemins se
rejoignent sur un seul jeu de hostvars. La garde reste entiere : une machine
sans adresse -- ni declaree ni derivable -- est toujours refusee.

Revele en preparant le plan du site : l'underlay ne dit PAS quel pont Proxmox
porte quel reseau. Les tenants ne s'en apercevaient pas, leur pont etant un VNet
derive de leur index. Un SITE n'en a pas.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-08-24 20:35:52 -04:00
parent 85ef6e107e
commit 9960cfd68e
2 changed files with 55 additions and 4 deletions

View file

@ -1,5 +1,34 @@
# CHANGELOG — Set-OPS
## 2026-08-24 — Un plan peut désormais déclarer son adressage, faute d'index à en dériver
Un **SITE** décrit les machines de l'hébergeur. Il n'a pas d'index — il ne cohabite pas
avec les tenants, il vit dans le réseau d'administration — et rien ne peut donc se dériver
d'un seed qui n'existe pas.
`instancier` accepte maintenant un adressage **déclaré** (`ip`, `vmid`, `vlan`, `cidr`,
`passerelle` au plan), et l'explicite gagne sur le dérivé. Les deux chemins se rejoignent
sur un seul jeu de `hostvars` : un tenant ne déclare rien et tout descend de son index ;
un site déclare, parce qu'il n'y a rien d'où descendre.
**La garde reste entière** : une machine sans adresse — ni déclarée ni dérivable — est
toujours refusée. C'est l'absence d'adresse qui est fautive, pas l'absence de dérivation.
### Ce que la préparation a révélé
**Le réseau d'administration `10.17.0.0/24` est à l'intérieur du supernet que l'index 17
dérive pour Chezlepro** (`10.17.0.0/16`). Pas de collision réelle — les zones d'un tenant
commencent à `10.17.16.0` — mais le nom trompe, et un jour ça mordra.
**L'underlay ne dit pas quel pont Proxmox porte quel réseau.** Il déclare les VLAN, les
sous-réseaux, les passerelles ; il documente `vmbr0` pour la grappe et `vmbr3` pour le
transport VXLAN — en commentaire. Rien ne dit par où une VM rejoint le plan
d'administration. Les tenants ne s'en apercevaient pas : leur pont est un VNet SDN, dérivé
de leur index. Un SITE, lui, n'en a pas.
C'est un intrant manquant, pas un détail : sans lui, un plan de site ne peut pas dire à
quoi rattacher ses machines.
## 2026-08-24 — Quinze invites pour un seul mot de passe
`flotte-creer` appelait `make creer-vm` **une fois par hôte**, et chaque appel ajoutait

View file

@ -186,16 +186,27 @@ def generer() -> dict:
#
# On refuse en BLOC plutot qu'a la premiere fautive : l'exploitant corrige son plan
# une fois, pas une machine a la fois.
# UN SITE N'A PAS D'INDEX, DONC RIEN NE SE DERIVE (2026-08-24).
#
# Un SITE decrit les machines de l'HEBERGEUR : elles vivent dans le reseau
# d'administration, pas dans un supernet de tenant, et il n'y en a qu'un jeu par site.
# Leur adressage est donc DECLARE, pas derive — il n'y a pas de seed dont le faire
# descendre, et inventer un index pour un site le ferait cohabiter avec les tenants.
#
# La garde reste entiere pour les deux mondes : une machine sans adresse — ni derivee
# ni declaree — est refusee. C'est l'absence d'adresse qui est fautive, pas l'absence
# de derivation.
sans_fonction = [
nom for nom, srv in serveurs.items()
if not (deriver_nomenclature(str(srv.get("fonction", "")),
fonction_seq(nom)[1], nomenclature) or {}).get("adresse_ip")
if not str(srv.get("ip", "")).strip()
and not (deriver_nomenclature(str(srv.get("fonction", "")),
fonction_seq(nom)[1], nomenclature) or {}).get("adresse_ip")
]
if sans_fonction:
connues = ", ".join(sorted((nomenclature.get("fonctions") or {}))) or "(aucune)"
raise SystemExit(
"Machines sans adresse derivable — leur `fonction` n'est pas declaree dans "
"`nomenclature.fonctions` :\n"
"Machines sans adresse : ni declaree (`ip:` au plan, cas d'un SITE), ni "
"derivable — leur `fonction` n'est pas dans `nomenclature.fonctions` :\n"
+ "".join(f" - {n} : fonction « {serveurs[n].get('fonction', '')} »\n" for n in sans_fonction)
+ f"Fonctions connues de ce plan : {connues}")
@ -207,6 +218,17 @@ def generer() -> dict:
for nom, srv in serveurs.items():
_, seq = fonction_seq(nom)
d = deriver_nomenclature(str(srv.get("fonction", "")), seq, nomenclature) or {}
# L'EXPLICITE GAGNE SUR LE DERIVE. Un SITE declare `ip`, `vmid` et `vlan` ; un
# tenant ne declare rien et tout descend de son index. Les deux chemins se
# rejoignent ici, et un seul jeu de hostvars en sort.
if str(srv.get("ip", "")).strip():
d = dict(d)
d["adresse_ip"] = srv["ip"]
d["cidr"] = srv.get("cidr", 24)
d["passerelle"] = srv.get("passerelle") or d.get("passerelle")
d["vlan"] = srv.get("vlan", d.get("vlan"))
d["vmid"] = srv.get("vmid", d.get("vmid"))
d["zone"] = None # pas de zone : pas de VNet SDN a deriver
hostvars = {
# Supernet du tenant, DERIVE du seed comme tout le reste. Il repond a la
# question « quels clients mon service doit-il accepter ? », posee par