Set-OPS-Public/scripts/tests/test_raser.py
Daniel Allaire 3b16b8845d portabilite : monter un SECOND tenant revele trois defauts invisibles
Les deux reconstructions de la semaine rebatissaient Chezlepro sur son propre
materiel : une preuve de reproductibilite, pas de portabilite. La vraie
epreuve est un second tenant — Technolibre, index 11, plan distinct, voute
separee, meme cluster.

1. LE CLONAGE RESOLVAIT PAR NOM. proxmox_kvm cherche une VM portant le `name`
demande ; s'il en trouve une il rend `ok` et ne clone RIEN — aucune tache
cote cluster. Or les noms courts sont VOLONTAIREMENT identiques d'un tenant a
l'autre. `backup-01` de Technolibre tombait sur celui de Chezlepro. Mesure :
id-ldap-01 et sup-01 (noms absents chez Chezlepro) passaient du premier coup,
backup-01 echouait toujours. Le clonage cible desormais par VMID, via l'API.

Deux defauts de ce correctif, trouves en le mesurant : le corps assemble en
Jinja avec `>-` rendait une CHAINE et perdait le `pool` (VM nee hors de son
pool, sans un mot) ; et l'application du gabarit de calcul expirait a 5 s,
laissant la VM aux valeurs du gabarit — 2 coeurs/2 Go au lieu du plan, en
silence. Corps en mapping YAML avec omit ; six tentatives espacees.

Et `no_log` a masque la cause au moment ou elle servait. Les deux attentes
disent maintenant ce qu'elles ont constate.

2. LE GUI DETRUISAIT DES INTRANTS. Dans ecrire_intrants, `identite` etait la
seule branche sur quatre a ecrire par-dessus le disque au lieu de fusionner.
Un enregistrement a supprime dns_amorcage et amorcage_acces_courriel. Le meme
geste sur Chezlepro aurait mange les memes cles.

3. LE VERROU DE `raser` N'ETAIT PROUVE QUE POUR UN TENANT. Le faux cluster
codait en dur les VMID de Chezlepro ; monte ailleurs, le test rendait 0 au
lieu de 2. Il fabrique desormais la collision sur le plan courant.

Verifie : prouver.py 34 OK sur Technolibre, test_raser vert sur les deux
tenants, ansible-lint production sur le playbook de clonage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 11:18:14 -04:00

57 lines
2.4 KiB
Python

"""`raser` : un VMID du plan portant un AUTRE nom doit faire REFUSER l'operation entiere.
C'est le verrou le plus important de la seule commande destructive du moteur, et le seul
qu'on ne peut pas eprouver sur le vrai cluster sans y fabriquer une collision. On isole
donc la logique derriere un faux cluster.
Le danger n'est pas theorique : le 2026-08-07, une VM heritee portait un VMID du plan sous
le nom `web-frontal-01`, et `proxmox_kvm` avait rapporte `ok` sans rien faire. Rase sans ce
controle, on detruisait la machine de quelqu'un d'autre.
LE FAUX CLUSTER SE DERIVE DE L'INSTANCE MONTEE, il ne code aucun VMID en dur. La premiere
version en portait deux, ceux de Chezlepro (index 17) : le test passait pour ce tenant et
rendait 0 — « aucune VM du plan n'est presente, rien a faire » — des qu'on montait un tenant
dont le plan derive d'autres VMID. Trouve le 2026-08-10 en basculant sur Technolibre, au
premier pas de l'epreuve de portabilite. Un verrou prouve pour un seul tenant n'est pas
prouve : c'est exactement ce que le multi-instance est cense empecher.
"""
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
import raser # noqa: E402
BASE, NOM = raser.instance_active()
COUPLES = raser.plan_derive(BASE)
if not COUPLES:
raise SystemExit("Plan sans serveur : rien a eprouver.")
# La collision est FABRIQUEE sur le premier hote du plan courant, quel qu'il soit ; un
# second hote, correctement nomme, garantit que le refus vient bien de la collision et
# non d'un cluster vide.
(NOM_1, VMID_1), *reste = COUPLES
NOM_2, VMID_2 = reste[0] if reste else (NOM_1 + "-bis", VMID_1 + 1)
class FauxCluster:
"""Le cluster repond que `VMID_1` porte le nom d'une VM etrangere au plan."""
@classmethod
def depuis_hebergeur(cls):
return cls(), None
def __call__(self, chemin, methode="GET", corps=None):
return [{"type": "qemu", "vmid": VMID_1, "name": "vm-heritee-de-2019",
"node": "asgard"},
{"type": "qemu", "vmid": VMID_2, "name": NOM_2, "node": "asgard"}]
def rate(self, rep):
return None
raser.Cluster = FauxCluster
code = raser.main(["--instance", NOM, "--confirmer"])
print(f"\n>>> instance montee : {NOM} | collision fabriquee sur {VMID_1} ({NOM_1})")
print(f">>> code de sortie : {code} (2 = refus attendu)")
assert code == 2, "LE VERROU N'A PAS TENU"
print(">>> le verrou tient : aucune VM n'aurait ete touchee")