contexte : materialisation M3, la reconstruction nomme son locataire
reconstruire_locataire lance locataire-raser et locataire-creer TENANT= sur le runner du site, sans monter le depot du locataire comme instance. Trou de M2 corrige avant tout usage : le sous-make cloner-vm nommait le pool par l'instance montee (hors de tout pool sur le site, pool de l'autre locataire sur le poste). devis_proxmox_pools.py --pool-du-locataire le derive de la face. test_appels_locataire.py : un faux ansible-playbook consigne la ligne qui part ; 26 machines, ancien chemin, site et poste identiques a l'argument pres. Temoin : l'ancienne ligne de pool fait echouer le test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
8294e5dc71
commit
e7f1a74040
6 changed files with 155 additions and 5 deletions
34
CHANGELOG.md
34
CHANGELOG.md
|
|
@ -1,5 +1,39 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-10-05 (99) — Matérialisation, M3 : la reconstruction nomme son locataire, et le pool suit
|
||||
|
||||
**Un trou dans M2, trouvé avant tout usage.** `locataire-creer` comparait les paramètres de
|
||||
clonage, pas la ligne qui part. Le sous-make `cloner-vm` nommait encore le pool par l'instance
|
||||
**montée** (`devis_proxmox_pools.py --pool-actif`). Sur le runner du site, sans instance, la VM
|
||||
serait née hors de tout pool. Sur le poste, avec Chezlepro monté,
|
||||
`locataire-creer TENANT=OPS-Technolibre` aurait rangé les VM dans le pool de Chezlepro. Mon
|
||||
`make -n` ne descendait pas dans le sous-make : il ne pouvait pas le voir. Personne n'a lancé
|
||||
`locataire-creer` entre-temps.
|
||||
|
||||
**Fait** :
|
||||
- `devis_proxmox_pools.py --pool-du-locataire <dépôt>` : même dérivation (`pool_de`), à partir
|
||||
de l'index de la face. `cloner-vm` l'emploie quand `TENANT` est donné.
|
||||
- `reconstruire_locataire.py` : les étapes `raser` et `creer` lancent sur le runner du site
|
||||
`make locataire-raser TENANT=…` et `make locataire-creer TENANT=…` ; plus de
|
||||
`SETOPS_INSTANCE=/opt/setops/OPS-x`. Le dépôt du locataire y reste tiré : sa face y est publiée.
|
||||
- `docs/conception-contextes.md` : l'état de l'étape 3.
|
||||
|
||||
**Éprouvé** :
|
||||
- `scripts/tests/test_appels_locataire.py` (dans `make test`) : un faux `ansible-playbook`
|
||||
consigne la ligne qui part réellement. Pour les 26 machines, trois appels : l'ancien chemin
|
||||
(instance montée), le site (aucune instance, `TENANT=`), le poste (l'autre locataire monté,
|
||||
`TENANT=`). Identiques **à l'argument près**.
|
||||
- Témoin : l'ancienne ligne de pool remise en place, le test échoue (pool vide côté site, pool
|
||||
de l'autre locataire côté poste).
|
||||
- Le test désigne les dépôts par des liens sans espace : le Makefile prend `SETOPS_INSTANCE`
|
||||
dans des `$(wildcard …)` qu'une espace coupe en deux (`Espace Chezlepro` sur le poste ; le
|
||||
runner du site vit sous `/opt/setops`). Premier essai faussé par là, les 26 « écarts »
|
||||
venaient de l'instrument.
|
||||
|
||||
**Pas encore** : la conformité horaire du site (`conformite-fabric.sh`) et `eprouver_parefeu.py`
|
||||
montent encore un locataire comme instance pour leurs devis — reste de l'étape 3, à part de M3.
|
||||
M4, la preuve par reconstruction, reste à faire et relève de l'exploitant.
|
||||
|
||||
## 2026-10-05 (98) — Matérialisation, M2 : les commandes du site nomment leur locataire
|
||||
|
||||
**Le chemin** (`docs/conception-contextes.md`, matérialisation M2) : le runner du site crée,
|
||||
|
|
|
|||
3
Makefile
3
Makefile
|
|
@ -334,6 +334,7 @@ test: ## Lance les tests unitaires (derivation de nomenclature et d'inventaire)
|
|||
python3 scripts/tests/test_repertoires_partages.py
|
||||
python3 scripts/tests/test_frontiere_refus.py
|
||||
python3 scripts/tests/test_contexte.py
|
||||
python3 scripts/tests/test_appels_locataire.py
|
||||
|
||||
# LA SONDE QUI RAPPORTE CE QUI DISTINGUE (2026-08-28). Trois faux diagnostics en une
|
||||
# journee, tous dus a l'instrument : `curl` et `bash /dev/tcp` ecrasent « la machine
|
||||
|
|
@ -1633,7 +1634,7 @@ cloner-vm: ansible-runtime ## Clone une VM depuis le gabarit dore — HOTE=<nom>
|
|||
-e proxmox_clone_nom="$(HOTE)" \
|
||||
-e proxmox_clone_vmid="$(VMID)" \
|
||||
-e proxmox_clone_ipconfig0="$$ipconfig" \
|
||||
-e proxmox_clone_pool="$(if $(POOL),$(POOL),$$(python3 scripts/devis_proxmox_pools.py --pool-actif))" \
|
||||
-e proxmox_clone_pool="$(if $(POOL),$(POOL),$$(python3 scripts/devis_proxmox_pools.py $(if $(TENANT),--pool-du-locataire $(TENANT),--pool-actif)))" \
|
||||
); \
|
||||
[[ -n "$(VMID_MODELE)" ]] && extra_vars+=( -e proxmox_clone_vmid_modele="$(VMID_MODELE)" ); \
|
||||
[[ -n "$(NOEUD_PROXMOX)" ]] && extra_vars+=( -e proxmox_clone_noeud="$(NOEUD_PROXMOX)" ); \
|
||||
|
|
|
|||
|
|
@ -183,7 +183,11 @@ les publie désormais (`sources_declarees`). La frontière, en trois temps : les
|
|||
**L'étape 2 est terminée** (2026-10-05). Étape 3 : l'instancier lit la fiche déposée par le site, et
|
||||
l'inventaire d'un locataire se génère sans son site, à l'octet près (P91). Le locataire publie sa face
|
||||
réseau (P92) ; les comptes de sauvegarde, le DNS public, le pare-feu Proxmox, la frontière et la
|
||||
découverte des locataires du site la lisent.
|
||||
découverte des locataires du site la lisent. La matérialisation aussi : la face publie les
|
||||
paramètres de clonage (P93) ; `locataire-creer`, `locataire-raser` et `placement-plan TENANT=`
|
||||
nomment leur locataire au lieu de le monter, et visent les mêmes machines, à l'argument près de
|
||||
la ligne `ansible-playbook` (P94, `test_appels_locataire.py`) ; `reconstruire-locataire` les
|
||||
emploie. Reste : la preuve par reconstruction.
|
||||
|
||||
Méthodes du contrat : `site.fiche_pour(locataire)`, `locataire.face_reseau()`. Une classe
|
||||
n'ouvre jamais les fichiers de l'autre ; une preuve vérifiera la règle.
|
||||
|
|
|
|||
|
|
@ -89,6 +89,20 @@ def pool_actif() -> str:
|
|||
return pool_de(instance.resolve().name, int(n["index"]))
|
||||
|
||||
|
||||
def pool_du_locataire(nom: str) -> str:
|
||||
"""Pool d'un locataire NOMME, d'apres l'index de sa face publiee (materialisation M2).
|
||||
|
||||
`pool_actif` lit l'instance MONTEE : appele par `make locataire-creer`, il rangeait les VM
|
||||
hors de tout pool sur le runner du site (aucune instance), et dans le pool d'un AUTRE
|
||||
locataire sur le poste (l'instance liee). Meme derivation (`pool_de`), autre source.
|
||||
"""
|
||||
import contexte
|
||||
face = contexte.Locataire.charger(nom).face_publiee()
|
||||
if face is None or face.get("index") is None:
|
||||
raise SystemExit(f"{nom} n'a pas publie sa face reseau (ou elle ne porte pas d'index).")
|
||||
return pool_de(contexte.Locataire.charger(nom).depot.resolve().name, int(face["index"]))
|
||||
|
||||
|
||||
def construire(tenants: list[tuple[str, str, dict]]) -> dict:
|
||||
"""{pools: [{pool, tenant, index, membres: [{nom, vmid, etat}], sans_vmid: []}]}"""
|
||||
blocs = []
|
||||
|
|
@ -191,6 +205,8 @@ def main(argv: list[str]) -> int:
|
|||
ap.add_argument("--verifier", action="store_true")
|
||||
ap.add_argument("--pool-actif", action="store_true",
|
||||
help="nom du pool du tenant actif (consomme par make creer-vm)")
|
||||
ap.add_argument("--pool-du-locataire", metavar="DEPOT",
|
||||
help="nom du pool d'un locataire nomme, d'apres sa face (make locataire-creer)")
|
||||
ap.add_argument("--pool-site", action="store_true",
|
||||
help="nom invariable du pool du genome (consomme par make site-creer)")
|
||||
args = ap.parse_args(argv)
|
||||
|
|
@ -198,6 +214,9 @@ def main(argv: list[str]) -> int:
|
|||
if args.pool_actif:
|
||||
print(pool_actif())
|
||||
return 0
|
||||
if args.pool_du_locataire:
|
||||
print(pool_du_locataire(args.pool_du_locataire))
|
||||
return 0
|
||||
|
||||
# LE SITE NE DERIVE D'AUCUN INDEX, DONC SON POOL NE SE CALCULE PAS (2026-09-13).
|
||||
#
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ def sur_site(args, commande: str, filtre: tuple[str, ...] = ()) -> int:
|
|||
def site_a_jour(args, tenant: str) -> int:
|
||||
"""Le runner du SITE tire les trois depots qu'il va utiliser, AVANT sa premiere etape.
|
||||
|
||||
Le moteur (Set-OPS-public), le plan du locataire, et le depot du SITE — deduit du lien
|
||||
Le moteur (Set-OPS-public), le depot du locataire (sa face reseau publiee), et le depot du SITE — deduit du lien
|
||||
`underlay.yml`, pas nomme : un autre site s'appelle autrement. Jusqu'au 2026-09-30 seul
|
||||
`raser` tirait, et seulement les deux premiers : une reprise `DEPUIS=creer` partait de
|
||||
ce que le runner avait sous la main, et `flotte-creer` lisait un plan du site jamais tire.
|
||||
|
|
@ -200,12 +200,17 @@ def main() -> int:
|
|||
rc = lancer(["ansible-playbook", "-i", str(inv), "playbooks/backup/restauration.yml",
|
||||
"-e", "restauration_action=sauvegarder"], env=env,
|
||||
filtre=("fatal:", "PLAY RECAP", "failed="))
|
||||
# LE SITE NOMME SON LOCATAIRE, IL NE LE MONTE PLUS (2026-10-05, materialisation M3).
|
||||
# Raser et creer lisaient le depot du locataire monte comme instance sur le runner du
|
||||
# site (`SETOPS_INSTANCE=…/OPS-x`). Ils lisent desormais sa face reseau publiee : memes
|
||||
# VMID, memes parametres de clonage, meme pool, a l'argument pres (P93, P94,
|
||||
# `test_appels_locataire.py`). Le depot du locataire reste tire : sa face y est publiee.
|
||||
elif nom == "raser":
|
||||
rc = sur_site(args, f"SETOPS_INSTANCE={args.racine_runner}/{T} make raser CONFIRMER=true "
|
||||
rc = sur_site(args, f"make locataire-raser TENANT={shlex.quote(T)} CONFIRMER=true "
|
||||
f"INSTANCE={shlex.quote(ecosysteme)}",
|
||||
filtre=("detruite", "VM detruites", "Refus", "REFUS", "ERREUR"))
|
||||
elif nom == "creer":
|
||||
rc = sur_site(args, f"SETOPS_INSTANCE={args.racine_runner}/{T} make flotte-creer "
|
||||
rc = sur_site(args, f"make locataire-creer TENANT={shlex.quote(T)} "
|
||||
f"CONFIRMER=true PARALLELE=4",
|
||||
filtre=("=== Creation", "a ECHOUE", "Toutes les VM", "Refus"))
|
||||
elif nom == "inseminer":
|
||||
|
|
|
|||
87
scripts/tests/test_appels_locataire.py
Normal file
87
scripts/tests/test_appels_locataire.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
#!/usr/bin/env python3
|
||||
"""`make creer-vm TENANT=` lance-t-il le MEME clonage que l'ancien chemin, l'instance montee ?
|
||||
|
||||
Compare, machine par machine, la ligne `ansible-playbook` qui part reellement — un faux
|
||||
`ansible-playbook` en tete du PATH la consigne au lieu de cloner. Trois appels par machine :
|
||||
- l'ancien chemin : `SETOPS_INSTANCE=<locataire> make creer-vm HOTE=h` ;
|
||||
- le runner du SITE : aucune instance, `TENANT=<locataire>` ;
|
||||
- le POSTE : l'AUTRE locataire monte, `TENANT=<locataire>`.
|
||||
|
||||
POURQUOI (2026-10-05, materialisation M2). Comparer les parametres de clonage ne suffisait pas :
|
||||
le sous-make `cloner-vm` nommait encore le pool par l'instance MONTEE (`--pool-actif`). Sur le
|
||||
site, la VM serait nee hors de tout pool ; sur le poste, dans le pool de l'autre locataire. Seule
|
||||
la ligne finale montre tout ce qui part, y compris ce qu'aucune liste de parametres ne nomme.
|
||||
|
||||
Les dossiers sont designes par des liens SANS ESPACE : le Makefile prend `SETOPS_INSTANCE`
|
||||
dans des `$(wildcard …)`, qu'une espace coupe en deux (le runner du site vit sous `/opt/setops`).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
RACINE = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(RACINE / "scripts"))
|
||||
|
||||
import contexte as C # noqa: E402
|
||||
|
||||
FAUX = "#!/bin/bash\nfor a in \"$@\"; do printf '%s\\n' \"$a\"; done >> \"$CAPTURE\"\n"
|
||||
|
||||
|
||||
def appel(tmp: Path, instance: str, hote: str, tenant: str | None) -> tuple[int, str]:
|
||||
capture = tmp / "capture"
|
||||
capture.write_text("")
|
||||
env = dict(os.environ, CAPTURE=str(capture), SETOPS_INSTANCE=instance,
|
||||
PATH=f"{tmp / 'faux'}:{os.environ['PATH']}")
|
||||
env.pop("SETOPS_INVENTAIRE", None)
|
||||
cmd = ["make", "--no-print-directory", "creer-vm", "ATTENDRE=false", f"HOTE={hote}"]
|
||||
if tenant:
|
||||
cmd.append(f"TENANT={tenant}")
|
||||
r = subprocess.run(cmd, cwd=RACINE, env=env, capture_output=True, text=True,
|
||||
stdin=subprocess.DEVNULL)
|
||||
return r.returncode, capture.read_text()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
couples = C.couples_hebergeur()
|
||||
locataires = sorted({l for _s, l in couples}, key=lambda l: l.nom)
|
||||
if len(locataires) < 2:
|
||||
print("SAUTE il faut deux locataires freres (le cas du poste monte l'autre)")
|
||||
return 0
|
||||
echecs, n = [], 0
|
||||
with tempfile.TemporaryDirectory() as t:
|
||||
tmp = Path(t)
|
||||
(tmp / "faux").mkdir()
|
||||
faux = tmp / "faux" / "ansible-playbook"
|
||||
faux.write_text(FAUX)
|
||||
faux.chmod(0o755)
|
||||
liens = {}
|
||||
for l in locataires:
|
||||
liens[l.nom] = tmp / l.nom
|
||||
liens[l.nom].symlink_to(l.depot.resolve())
|
||||
for l in locataires:
|
||||
autre = next(x for x in locataires if x.nom != l.nom)
|
||||
for h in C.hotes_actifs(l.face_publiee() or {}):
|
||||
rc0, ancien = appel(tmp, str(liens[l.nom]), h, None)
|
||||
rc1, site = appel(tmp, "/nulle/part", h, l.nom)
|
||||
rc2, poste = appel(tmp, str(liens[autre.nom]), h, l.nom)
|
||||
n += 1
|
||||
if rc0 or not ancien:
|
||||
echecs.append(f"{l.nom} {h} : l'ancien chemin n'a rien lance (code {rc0})")
|
||||
elif (rc1, site) != (0, ancien):
|
||||
echecs.append(f"{l.nom} {h} : le site lancerait autre chose (code {rc1})")
|
||||
elif (rc2, poste) != (0, ancien):
|
||||
echecs.append(f"{l.nom} {h} : le poste ({autre.nom} monte) lancerait autre chose")
|
||||
for e in echecs:
|
||||
print("ECHEC " + e)
|
||||
if echecs:
|
||||
return 1
|
||||
print(f"OK {n} machine(s) : site et poste lancent le clonage de l'ancien chemin, a l'argument pres")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Loading…
Reference in a new issue