devis de placement : le gabarit et le stockage que le clonage utilise (ceux du site)

Le devis lisait le gabarit du tenant : il validait chez Technolibre un 99998 que
plus rien ne clone, et refusait Chezlepro, qui suit la regle et n'en declare plus.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-10-07 13:24:39 -04:00
parent 543791e859
commit 12caff1cff
3 changed files with 64 additions and 0 deletions

View file

@ -1,5 +1,31 @@
# CHANGELOG — Set-OPS # CHANGELOG — Set-OPS
## 2026-10-07 (102) — Le devis de placement vérifiait un gabarit que personne ne clone
**Trouvé au pré-vol de la reconstruction de Chezlepro** (`make placement-plan
TENANT=OPS-Chezlepro`, runner du site, lecture seule) : rasage conforme (13 VM, chacune
sous son nom, aucun conflit), mais placement en écart, avec un gabarit « None » introuvable.
**La cause.** Depuis le 2026-09-01, le gabarit et son stockage viennent du **site**
(`underlay.py --gabarit` : 9006 sur `vishnu`, stockage `CephNVMe`) : `cloner-vm` les passe en
`-e`, par-dessus le tenant. Chezlepro suit la règle et ne déclare plus de gabarit ;
Technolibre déclare encore `99998` et `TrueNAS`, valeurs que plus rien n'utilise (les 13
clones de M4 sont nés du 9006). Le devis lisait le tenant : il **validait chez Technolibre
ce que personne ne clone**, et refusait Chezlepro. Défaut antérieur à la face (l'ancien
chemin lisait les mêmes valeurs) ; il ne touche pas le clonage.
**Fait** : `devis_placement.ce_que_le_clonage_utilise()` remplace le gabarit et le stockage
du tenant par ceux du site, même règle que le Makefile : le site décide ; s'il ne déclare
rien, le tenant reprend la main.
**Éprouvé** : `test_devis_placement.py`, un sixième test (valeurs périmées remplacées, tenant
sans gabarit servi, site muet = tenant tel quel). Témoin : le correctif neutralisé, le test
échoue. `make verifier` conforme, **94/94**.
**Reste** : les lignes `proxmox_clone_vmid_modele` et `proxmox_clone_source_nom` de
Technolibre sont mortes ; les mettre en commentaire comme chez Chezlepro changera sa face
(à republier). Sans urgence : le site a la priorité partout.
## 2026-10-07 (101) — M4 : Technolibre reconstruite par le site qui la nomme ; l'instantané d'avant rasage ne survivait pas ## 2026-10-07 (101) — M4 : Technolibre reconstruite par le site qui la nomme ; l'instantané d'avant rasage ne survivait pas
**M4, la preuve par reconstruction** (`make reconstruire-locataire TENANT=OPS-Technolibre`, **M4, la preuve par reconstruction** (`make reconstruire-locataire TENANT=OPS-Technolibre`,

View file

@ -207,6 +207,27 @@ def nom_du_tenant(source: Path | None, locataire: str | None = None) -> str:
return source.parents[3].resolve().name if source else "?" return source.parents[3].resolve().name if source else "?"
def ce_que_le_clonage_utilise(place: dict, gabarit: dict) -> dict:
"""Le placement tel que `cloner-vm` le passe REELLEMENT a Proxmox.
LE DEVIS VERIFIAIT CE QUE PERSONNE NE CLONE (2026-10-07). Depuis le 2026-09-01, le gabarit
et son stockage viennent du SITE (`underlay.py --gabarit`) : `cloner-vm` les passe en
`-e`, par-dessus ce que dit le tenant. Le devis, lui, lisait encore le tenant. Chez
Technolibre, il validait le 99998, que plus rien ne clone (M4 est nee du 9006, sur
CephNVMe) ; chez Chezlepro, qui ne declare plus de gabarit (c'est la regle), il refusait
« None » — trouve au pre-vol de sa reconstruction.
Meme regle que le Makefile : le site decide ; s'il ne declare rien, le tenant reprend
la main. Degrader, jamais deviner.
"""
vu = dict(place)
if gabarit.get("vmid") not in (None, ""):
vu["proxmox_clone_vmid_modele"] = int(gabarit["vmid"])
if gabarit.get("stockage"):
vu["proxmox_clone_stockage"] = gabarit["stockage"]
return vu
def afficher(lignes: list[dict], tenant: str, depot: Path) -> bool: def afficher(lignes: list[dict], tenant: str, depot: Path) -> bool:
print(f"Devis du placement — tenant « {tenant} » sur l'hebergeur « {depot.name} »\n") print(f"Devis du placement — tenant « {tenant} » sur l'hebergeur « {depot.name} »\n")
print(f" {'OBJET':<12} {'DEMANDE':<34} {'VERDICT'}") print(f" {'OBJET':<12} {'DEMANDE':<34} {'VERDICT'}")
@ -247,6 +268,7 @@ def main() -> int:
else: else:
place, source = placement_du_tenant() place, source = placement_du_tenant()
tenant = nom_du_tenant(source, a.locataire) tenant = nom_du_tenant(source, a.locataire)
place = ce_que_le_clonage_utilise(place, underlay_mod.gabarit())
if not place: if not place:
print(f"Aucun placement declare par « {tenant} » — rien a confronter.") print(f"Aucun placement declare par « {tenant} » — rien a confronter.")
return 0 return 0

View file

@ -117,6 +117,22 @@ def test_le_devis_nomme_le_locataire_designe():
assert "« OPS-Exemple »" in entete and "?" not in entete, f"en-tete : {entete!r}" assert "« OPS-Exemple »" in entete and "?" not in entete, f"en-tete : {entete!r}"
def test_le_devis_verifie_le_gabarit_et_le_stockage_que_le_clonage_utilise():
"""Le site decide du gabarit et de son stockage ; `cloner-vm` les passe en `-e`. Le devis
lisait le tenant : il validait chez Technolibre un 99998 que plus rien ne clone, et
refusait chez Chezlepro un gabarit « None » (pre-vol de sa reconstruction, 2026-10-07)."""
site = {"vmid": 9006, "nom": "modeleSetOPS-minimal", "noeud": "vishnu", "stockage": "CephNVMe"}
perime = {**PLACEMENT, "proxmox_clone_vmid_modele": 99998, "proxmox_clone_stockage": "TrueNAS"}
vu = dp.ce_que_le_clonage_utilise(perime, site)
assert vu["proxmox_clone_vmid_modele"] == 9006 and vu["proxmox_clone_stockage"] == "CephNVMe", vu
sans = {k: v for k, v in perime.items() if k != "proxmox_clone_vmid_modele"}
assert dp.ce_que_le_clonage_utilise(sans, site)["proxmox_clone_vmid_modele"] == 9006, \
"un tenant qui suit la regle (aucun gabarit declare) recoit celui du site"
assert dp.ce_que_le_clonage_utilise(perime, {}) == perime, \
"sans gabarit declare par le site, le tenant reprend la main, tel quel"
assert perime["proxmox_clone_vmid_modele"] == 99998, "le placement du tenant n'est pas modifie en place"
if __name__ == "__main__": if __name__ == "__main__":
tests = [v for k, v in sorted(globals().items()) if k.startswith("test_")] tests = [v for k, v in sorted(globals().items()) if k.startswith("test_")]
for t in tests: for t in tests: