[IMP] script todo: réduction disque — proposer d'éteindre la VM et réessayer

Avant, réduire le disque d'une VM allumée affichait seulement « Éteignez
la VM » puis abandonnait. Désormais on DEMANDE (o/N) si on veut l'éteindre
et réessayer :

- _qemu_shutdown_wait : arrêt ACPI gracieux (virsh shutdown), attente
  jusqu'à « shut off » (timeout 120s), puis propose un arrêt forcé
  (virsh destroy) si l'arrêt traîne.
- _qemu_domname : résout un ID numérique en nom canonique — l'ID
  disparaît une fois la VM éteinte, le polling doit utiliser le nom.
  Validé : domname(14) -> erplibre-ubuntu-2004.
- Tous les nouveaux prompts affichent la valeur par défaut (o/N).

Une fois éteinte, la réduction (qemu-img resize --shrink) se poursuit
automatiquement après confirmation du risque.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mathieu Benoit 2026-07-29 11:05:24 +00:00
parent 4d2b7cea00
commit 03f6205b9e
2 changed files with 78 additions and 2 deletions

View file

@ -1280,6 +1280,54 @@ class TODO:
return ""
return res.stdout.strip() if res.returncode == 0 else ""
@staticmethod
def _qemu_domname(name):
"""Nom canonique de la VM (si on a fourni un ID numérique, le
résout ; sinon renvoie tel quel). Utile car un ID disparaît une
fois la VM éteinte."""
if not str(name).isdigit():
return name
try:
res = subprocess.run(
["sudo", "virsh", "domname", str(name)],
capture_output=True,
text=True,
timeout=15,
)
except (OSError, subprocess.SubprocessError):
return name
out = res.stdout.strip()
return out if res.returncode == 0 and out else name
def _qemu_shutdown_wait(self, name, timeout=120):
"""Arrête la VM proprement (ACPI) et attend qu'elle soit « shut off ».
Si l'arrêt gracieux traîne, propose un arrêt forcé (destroy).
Renvoie True si la VM est bien éteinte."""
name = self._qemu_domname(name)
if self._qemu_domstate(name) == "shut off":
return True
cmd = f"sudo virsh shutdown {shlex.quote(name)}"
print(f"{t('Will execute:')} {cmd}")
self.execute.exec_command_live(cmd, source_erplibre=False)
print(t("Waiting for the VM to shut down..."))
deadline = time.time() + timeout
while time.time() < deadline:
if self._qemu_domstate(name) == "shut off":
print(f"{name}: {t('VM is off.')}")
return True
time.sleep(3)
# Arrêt gracieux trop long : proposer un arrêt forcé.
if self._is_yes(
input(t("Graceful shutdown timed out. Force off (destroy)? "
"(y/N): "))
):
cmd = f"sudo virsh destroy {shlex.quote(name)}"
print(f"{t('Will execute:')} {cmd}")
self.execute.exec_command_live(cmd, source_erplibre=False)
time.sleep(2)
return self._qemu_domstate(name) == "shut off"
return False
@staticmethod
def _qemu_main_disk(name):
"""Chemin du disque PRINCIPAL (qcow2) de la VM via domblklist. On
@ -1396,8 +1444,16 @@ class TODO:
)
print(f"{danger}")
if state != "shut off":
print(t("Shut the VM off before shrinking (virsh shutdown)."))
return
if not self._is_yes(
input(t("The VM must be off. Shut it down and retry? "
"(y/N): "))
):
print(t("Cancelled."))
return
if not self._qemu_shutdown_wait(name):
print(t("VM is still not off; aborting."))
return
state = "shut off"
if not self._is_yes(
input(t("Type y to confirm you understand the risk (y/N): "))
):

View file

@ -1420,6 +1420,26 @@ TRANSLATIONS = {
"fr": "Impossible de lire la taille actuelle du disque ; abandon.",
"en": "Could not read current disk size; aborting.",
},
"The VM must be off. Shut it down and retry? (y/N): ": {
"fr": "La VM doit être éteinte. L'éteindre et réessayer ? (o/N) : ",
"en": "The VM must be off. Shut it down and retry? (y/N): ",
},
"VM is still not off; aborting.": {
"fr": "La VM n'est toujours pas éteinte ; abandon.",
"en": "VM is still not off; aborting.",
},
"Waiting for the VM to shut down...": {
"fr": "Attente de l'arrêt de la VM…",
"en": "Waiting for the VM to shut down...",
},
"VM is off.": {
"fr": "VM éteinte.",
"en": "VM is off.",
},
"Graceful shutdown timed out. Force off (destroy)? (y/N): ": {
"fr": "Arrêt gracieux trop long. Forcer l'arrêt (destroy) ? (o/N) : ",
"en": "Graceful shutdown timed out. Force off (destroy)? (y/N): ",
},
"Show advanced info (vCPU, RAM, disk)? (y/N): ": {
"fr": "Afficher les infos avancées (vCPU, RAM, disque) ? (o/N) : ",
"en": "Show advanced info (vCPU, RAM, disk)? (y/N): ",