From 03f6205b9e67eef8acf138b881cd57c0c1bbcea9 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Wed, 29 Jul 2026 11:05:24 +0000 Subject: [PATCH] =?UTF-8?q?[IMP]=20script=20todo:=20r=C3=A9duction=20disqu?= =?UTF-8?q?e=20=E2=80=94=20proposer=20d'=C3=A9teindre=20la=20VM=20et=20r?= =?UTF-8?q?=C3=A9essayer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- script/todo/todo.py | 60 ++++++++++++++++++++++++++++++++++++++-- script/todo/todo_i18n.py | 20 ++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/script/todo/todo.py b/script/todo/todo.py index 5588217..f79a06b 100755 --- a/script/todo/todo.py +++ b/script/todo/todo.py @@ -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): ")) ): diff --git a/script/todo/todo_i18n.py b/script/todo/todo_i18n.py index 94cb6e6..dbf5d5d 100644 --- a/script/todo/todo_i18n.py +++ b/script/todo/todo_i18n.py @@ -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): ",