From 62ea6ebb7ff5833d8beaac2e62a97584f2cc72d4 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Wed, 29 Jul 2026 09:30:16 +0000 Subject: [PATCH] =?UTF-8?q?[IMP]=20script=20todo:=20dashboard=20=E2=80=94?= =?UTF-8?q?=20=C3=A9tats=20Pause=20/=20Effac=C3=A9e=20(check=20virsh=20len?= =?UTF-8?q?t)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pendant le suivi, une VM pouvait « stagner » sans qu'on sache pourquoi (mise en pause, ou effacée à côté). Le dashboard interroge désormais l'état libvirt (« virsh list --all ») à INTERVALLE LENT (toutes les 10 s, un seul appel pour tout le parc ; le tableau applique le cache à chaque tick de 2 s) : - VM en pause (virsh suspend) -> État « ⏸ pause » (non terminal, peut reprendre). - VM absente de virsh (effacée pendant l'attente) -> État « ❌ effacée », terminal (on cesse de lire son log). Sinon on garde le statut basé sur le log (⏳ / ✅ / ❌). Validé headless : running -> ⏳, paused -> ⏸ pause, absente -> ❌ effacée. Co-Authored-By: Claude Opus 4.8 (1M context) --- script/todo/qemu_install_monitor.py | 54 +++++++++++++++++++++++++++++ script/todo/todo_i18n.py | 8 +++++ 2 files changed, 62 insertions(+) diff --git a/script/todo/qemu_install_monitor.py b/script/todo/qemu_install_monitor.py index 62de968..fecee28 100644 --- a/script/todo/qemu_install_monitor.py +++ b/script/todo/qemu_install_monitor.py @@ -265,6 +265,30 @@ def cli_browser() -> str | None: return None +def virsh_domstates() -> dict: + """{nom: état} de tous les domaines libvirt (« virsh list --all »). Sert à + détecter une VM EN PAUSE ou EFFACÉE pendant le suivi. Un seul appel virsh + pour tout le parc (à interroger à intervalle LENT).""" + try: + res = subprocess.run( + ["sudo", "virsh", "list", "--all"], + capture_output=True, + text=True, + timeout=15, + ) + except (OSError, subprocess.SubprocessError): + return {} + states = {} + for line in res.stdout.splitlines(): + parts = line.split() + # Ignore l'en-tête (« Id Name State ») et le séparateur (« ---- », + # un seul token). Une VM éteinte a « - » en Id : à NE PAS ignorer. + if len(parts) < 3 or parts[0] == "Id": + continue + states[parts[1]] = " ".join(parts[2:]) + return states + + # --------------------------------------------------------------------------- # # Dashboard Textual # --------------------------------------------------------------------------- # @@ -316,6 +340,8 @@ def run_monitor(manifest_path: str, run_app: bool = True): # Historique de durées (ETA) + dossier disque à surveiller. self._stats = load_stats() self._disk_dir = os.path.dirname(vm_disk_path(vms[0])) if vms else "/" + # État libvirt (running/paused/gone), rafraîchi à intervalle LENT. + self._domstate = {} @staticmethod def _fmt(secs): @@ -362,6 +388,10 @@ def run_monitor(manifest_path: str, run_app: bool = True): # sélectionné toutes les 1 s (une seule lecture incrémentale). self.set_interval(2.0, self._tick_table) self.set_interval(1.0, self._tick_log) + # État libvirt (pause / effacée) : check LENT (appel virsh) toutes + # les 10 s ; le tableau applique le cache à chaque tick (2 s). + self.set_interval(10.0, self._tick_domstate) + self._tick_domstate() # premier relevé immédiat # -- helpers -------------------------------------------------------- # def _vm_by_name(self, name): @@ -415,6 +445,14 @@ def run_monitor(manifest_path: str, run_app: bool = True): ) if name in self._final: continue # déjà terminé -> plus de lecture de statut + # VM EFFACÉE pendant le suivi -> état terminal « effacé ». + ds = self._domstate.get(name) + if ds == "gone": + self._final[name] = ("deleted", None, now - started) + self._set_cell( + table, name, "state", f"❌ {t('deleted')}" + ) + continue state, code = read_status(vm["log"]) if state in ("done", "failed"): # Fige la durée + MÉMORISE pour l'ETA des prochaines. @@ -428,6 +466,12 @@ def run_monitor(manifest_path: str, run_app: bool = True): self._set_cell( table, name, "elapsed", self._fmt(elapsed) ) + elif ds == "paused": + # VM EN PAUSE (virsh suspend) : l'install ne progresse + # pas -> on l'indique (non terminal, peut reprendre). + self._set_cell( + table, name, "state", f"⏸ {t('paused')}" + ) else: # running/pending : icône stable -> pas de churn. ETA # = référence historique (par arch) - temps écoulé. @@ -475,6 +519,16 @@ def run_monitor(manifest_path: str, run_app: bool = True): except Exception: pass + def _tick_domstate(self): + # Relevé LENT de l'état libvirt : une VM absente de « virsh list » + # est EFFACÉE (« gone ») ; sinon on garde son état (paused, …). + try: + states = virsh_domstates() + for vm in vms: + self._domstate[vm["name"]] = states.get(vm["name"], "gone") + except Exception: + pass + # -- events --------------------------------------------------------- # def on_data_table_row_highlighted(self, event) -> None: name = event.row_key.value diff --git a/script/todo/todo_i18n.py b/script/todo/todo_i18n.py index 6c8f809..3c9013e 100644 --- a/script/todo/todo_i18n.py +++ b/script/todo/todo_i18n.py @@ -1373,6 +1373,14 @@ TRANSLATIONS = { "fr": "terminées", "en": "completed", }, + "deleted": { + "fr": "effacée", + "en": "deleted", + }, + "paused": { + "fr": "pause", + "en": "paused", + }, "Waiting for the VM to start (boot + cloud-init)": { "fr": "En attente du démarrage de la VM (boot + cloud-init)", "en": "Waiting for the VM to start (boot + cloud-init)",