SITE-Chezlepro passe de 10.0.31-36 (tapes a la main, derives de rien) et 10.17.0.0/24 (emprunte au supernet du locataire) a son PROPRE index : gestion en 10.37.0.0/24, zones en 10.37.31-36. OPS-Chezlepro garde 17. CE QUE L OPERATION A REVELE. Il n existait aucun moyen de raser le site : raser.py ne vise que l instance active, un locataire. Ce n est donc pas que personne n avait essaye de le reconstruire depuis zero — l outil n en offrait pas le moyen, et la limite se lisait partout sans que sa cause soit nommee. make site-raser reprend les quatre verrous de raser.py. TROIS FOIS LE MEME DEFAUT, attrape par l exploitant. La declaration decrit la CIBLE pendant que l outillage s en sert pour joindre l EXISTANT : renumeroter le rebond avant de bouger la patte a rendu le site injoignable. Regle posee : le renumerotage declaratif vient APRES la derniere operation qui a besoin de l ancienne infrastructure. nftables_admin_ssh etait une liste a la main qui devait suivre le plan d administration. Elle a pris du retard le jour meme. Le plan de frontiere n a propose AUCUNE creation ni suppression de regle — seulement douze contenus d alias. Les regles visent des alias par leur nom : un renumerotage complet se reduit a changer ce que les noms designent. Routes des trois hyperviseurs refaites, declarees ET vives, avec sauvegarde de /etc/network/interfaces. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
165 lines
6.9 KiB
Python
165 lines
6.9 KiB
Python
#!/usr/bin/env python3
|
|
"""DESTRUCTIF : detruit les VM du SITE derivees de son underlay.
|
|
|
|
POURQUOI CETTE COMMANDE N'EXISTAIT PAS, ET CE QUE CA A COUTE (2026-09-12).
|
|
|
|
`raser.py` ne vise que l'instance ACTIVE — un locataire. Rien, nulle part, ne detruisait
|
|
les machines du site. Ce n'est donc pas que personne n'avait essaye de reconstruire le
|
|
site depuis zero : **l'outil n'en offrait pas le moyen**.
|
|
|
|
Cette absence se lisait partout ailleurs comme une limite honnete — « l'infrastructure
|
|
d'accueil n'a jamais ete reconstruite depuis zero » — sans que sa CAUSE soit nommee. Une
|
|
limite dont on ignore la cause finit par ressembler a une fatalite.
|
|
|
|
Et elle rendait la reconstruction du site non reproductible : detruire sept VM a la main
|
|
dans l'interface de Proxmox n'est pas une preuve, c'est un souvenir.
|
|
|
|
LES QUATRE VERROUS DE `raser.py`, REPRIS TELS QUELS :
|
|
|
|
1. la liste vient de l'UNDERLAY, jamais d'un argument libre ;
|
|
2. un VMID qui porte un AUTRE nom sur le cluster fait REFUSER l'ensemble — ce sont des
|
|
machines qui ne nous appartiennent pas ;
|
|
3. `--confirmer` est exige ;
|
|
4. il faut NOMMER le site, et que ce nom corresponde a celui qui est monte.
|
|
|
|
Usage :
|
|
python3 scripts/site_raser.py # affiche, ne detruit rien
|
|
python3 scripts/site_raser.py --site SITE-Chezlepro --confirmer
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
RACINE = Path(__file__).resolve().parent.parent
|
|
sys.path.insert(0, str(RACINE / "scripts"))
|
|
|
|
from proxmox_api import Cluster # noqa: E402
|
|
from raser import _attendre_tache # noqa: E402
|
|
import underlay as underlay_mod # noqa: E402
|
|
|
|
|
|
def _machines_du_site() -> list[tuple[str, int, str]]:
|
|
"""[(nom, vmid, noeud)] tels que l'UNDERLAY les derive — la meme source que
|
|
`site-creer`. Interroger une autre source reviendrait a fabriquer une seconde liste,
|
|
et c'est exactement ce que ce depot passe son temps a supprimer."""
|
|
noms = subprocess.run([sys.executable, str(RACINE / "scripts" / "site_machines.py"), "lister"],
|
|
capture_output=True, text=True).stdout.split()
|
|
couples = []
|
|
for nom in noms:
|
|
p = subprocess.run([sys.executable, str(RACINE / "scripts" / "site_machines.py"),
|
|
"parametres-proxmox", "--machine", nom],
|
|
capture_output=True, text=True).stdout
|
|
vals = dict(l.split("=", 1) for l in p.splitlines() if "=" in l)
|
|
vmid = vals.get("SETOPS_VMID", "").strip()
|
|
noeud = vals.get("SETOPS_NOEUD", "").strip()
|
|
if vmid.isdigit():
|
|
couples.append((nom, int(vmid), noeud))
|
|
return couples
|
|
|
|
|
|
def main() -> int:
|
|
ap = argparse.ArgumentParser(description=__doc__,
|
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
ap.add_argument("--site", help="nom du depot de site a raser (doit correspondre au monte)")
|
|
ap.add_argument("--machine", help="ne raser qu'une machine (les quatre verrous tiennent)")
|
|
ap.add_argument("--confirmer", action="store_true")
|
|
a = ap.parse_args()
|
|
|
|
chemin = underlay_mod.chemin()
|
|
if not chemin:
|
|
print("Aucun underlay monte : aucun site a raser.")
|
|
return 2
|
|
nom_monte = Path(chemin).resolve().parent.name
|
|
|
|
couples = _machines_du_site()
|
|
if a.machine:
|
|
couples = [c for c in couples if c[0] == a.machine]
|
|
if not couples:
|
|
print(f"La machine « {a.machine} » n'est pas a l'underlay : rien a raser.")
|
|
return 2
|
|
if not couples:
|
|
print("L'underlay ne derive aucune machine de site : rien a raser.")
|
|
return 0
|
|
|
|
cl, _ = Cluster.depuis_hebergeur()
|
|
rep = cl("/cluster/resources?type=vm")
|
|
if err := cl.rate(rep):
|
|
raise SystemExit(f"Cluster injoignable : {err}")
|
|
reelles = {int(v["vmid"]): v for v in rep if v.get("type") == "qemu"}
|
|
|
|
presentes, absentes, usurpees = [], [], []
|
|
for nom, vmid, _noeud in sorted(couples, key=lambda c: c[1]):
|
|
v = reelles.get(vmid)
|
|
if v is None:
|
|
absentes.append((nom, vmid))
|
|
elif v.get("name") != nom:
|
|
usurpees.append((nom, vmid, v.get("name")))
|
|
else:
|
|
presentes.append((nom, vmid, v.get("node")))
|
|
|
|
print(f"Site monte : « {nom_monte} » ({chemin})\n")
|
|
for nom, vmid, noeud in presentes:
|
|
print(f" À DÉTRUIRE {vmid} {nom:16} sur {noeud}")
|
|
for nom, vmid in absentes:
|
|
print(f" absente {vmid} {nom:16} (rien à faire)")
|
|
for nom, vmid, autre in usurpees:
|
|
print(f" CONFLIT {vmid} l'underlay dit « {nom} », le cluster dit « {autre} »")
|
|
|
|
if usurpees:
|
|
print(f"\nREFUS : {len(usurpees)} VMID de l'underlay portent un AUTRE nom sur le "
|
|
f"cluster.\nCe sont des machines qui ne nous appartiennent pas. Les renommer "
|
|
f"ou corriger\nl'underlay avant de raser quoi que ce soit — aucune VM n'a "
|
|
f"ete touchee.")
|
|
return 2
|
|
|
|
if not presentes:
|
|
print("\nAucune machine de l'underlay n'est presente : rien a faire.")
|
|
return 0
|
|
|
|
if not a.confirmer:
|
|
print(f"\n{len(presentes)} VM du SITE seraient DETRUITES, disques compris.\n"
|
|
f"Le site porte le depot de sauvegarde de CHAQUE locataire et la racine de\n"
|
|
f"son AC : sortir le depot hors site AVANT (`make depot-hors-site VERS=…`).\n"
|
|
f"Relancer avec : make site-raser CONFIRMER=true SITE={nom_monte}"
|
|
+ (f" MACHINE={a.machine}" if a.machine else ""))
|
|
return 0
|
|
|
|
if (a.site or "") != nom_monte:
|
|
print(f"\nREFUS : il faut NOMMER le site a raser.\n"
|
|
f"Monte : « {nom_monte} » ; recu : « {a.site or '(rien)'} ».\n"
|
|
f"Taper le nom est ce qui distingue l'essai du batiment reel.")
|
|
return 2
|
|
|
|
echecs = 0
|
|
for nom, vmid, noeud in presentes:
|
|
print(f"\n {vmid} {nom} — arret…", flush=True)
|
|
cl(f"/nodes/{noeud}/qemu/{vmid}/status/stop", "POST")
|
|
for _ in range(30):
|
|
time.sleep(2)
|
|
st = cl(f"/nodes/{noeud}/qemu/{vmid}/status/current")
|
|
if isinstance(st, dict) and st.get("status") == "stopped":
|
|
break
|
|
r = cl(f"/nodes/{noeud}/qemu/{vmid}?purge=1&destroy-unreferenced-disks=1", "DELETE")
|
|
if err := cl.rate(r):
|
|
print(f" {vmid} {nom} — ECHEC : {err}")
|
|
echecs += 1
|
|
continue
|
|
# ATTENDRE LE RESULTAT, PAS L'ACCUSE DE RECEPTION — meme lecon que `raser.py` :
|
|
# l'API rend un UPID et la main immediatement, et la tache peut echouer APRES.
|
|
etat = _attendre_tache(cl, noeud, r)
|
|
if etat == "OK":
|
|
print(f" {vmid} {nom} — detruite")
|
|
else:
|
|
print(f" {vmid} {nom} — ECHEC : {etat}")
|
|
echecs += 1
|
|
|
|
print(f"\n{len(presentes) - echecs}/{len(presentes)} VM du site detruites.")
|
|
return 1 if echecs else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|