From 4d2b7cea0055281f36b17aef3237082be6f84538 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Wed, 29 Jul 2026 11:01:00 +0000 Subject: [PATCH] =?UTF-8?q?[IMP]=20script=20todo:=20QEMU=20=C2=AB=20Lister?= =?UTF-8?q?=20les=20VM=20=C2=BB=20=E2=80=94=20option=20infos=20avanc=C3=A9?= =?UTF-8?q?es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Après « sudo virsh list --all », le menu propose désormais (o/N) un tableau détaillé par VM : état, vCPU, RAM allouée (Max memory), taille disque virtuelle et réelle (qemu-img info -U, lit même VM allumée), plus l'espace total/libre/utilisé du stockage des images (shutil.disk_usage). - _qemu_list_vms(ask_advanced=False) : seul le menu [4] prompte ; les appels internes (IP, console, resize, delete) restent inchangés. - Helpers _qemu_dominfo (vcpu + Max memory) et _qemu_disk_sizes (virtuel + réel, -U). Validé en réel : 8 vCPU / 8.0G / 30.0G / 2.5G. Co-Authored-By: Claude Opus 4.8 (1M context) --- script/todo/todo.py | 94 +++++++++++++++++++++++++++++++++++++++- script/todo/todo_i18n.py | 16 +++++++ 2 files changed, 108 insertions(+), 2 deletions(-) diff --git a/script/todo/todo.py b/script/todo/todo.py index be3a2ce..5588217 100755 --- a/script/todo/todo.py +++ b/script/todo/todo.py @@ -1082,7 +1082,7 @@ class TODO: elif status == "3": self._qemu_download_image() elif status == "4": - self._qemu_list_vms() + self._qemu_list_vms(ask_advanced=True) elif status == "5": self._qemu_show_ip() elif status == "6": @@ -1130,10 +1130,100 @@ class TODO: print(f"{t('Will execute:')} {cmd}") self.execute.exec_command_live(cmd, source_erplibre=False) - def _qemu_list_vms(self): + def _qemu_list_vms(self, ask_advanced=False): cmd = "sudo virsh list --all" print(f"{t('Will execute:')} {cmd}") self.execute.exec_command_live(cmd, source_erplibre=False) + if ask_advanced and self._is_yes( + input(t("Show advanced info (vCPU, RAM, disk)? (y/N): ")) + ): + self._qemu_list_vms_advanced() + + @staticmethod + def _qemu_dominfo(name): + """(vcpus, max_mem_kib) via « virsh dominfo », ou (0, 0).""" + try: + res = subprocess.run( + ["sudo", "virsh", "dominfo", name], + capture_output=True, + text=True, + timeout=15, + ) + except (OSError, subprocess.SubprocessError): + return 0, 0 + vcpus, mem = 0, 0 + for line in res.stdout.splitlines(): + if line.startswith("CPU(s):"): + try: + vcpus = int(line.split(":", 1)[1].strip()) + except ValueError: + pass + elif line.startswith("Max memory:"): + # « 4194304 KiB » + try: + mem = int(line.split(":", 1)[1].split()[0]) + except (ValueError, IndexError): + pass + return vcpus, mem + + @staticmethod + def _qemu_disk_sizes(disk): + """(taille virtuelle, taille réelle sur disque) en octets, via + qemu-img info -U (lit même VM allumée). (0, 0) si échec.""" + try: + res = subprocess.run( + ["sudo", "qemu-img", "info", "-U", "--output=json", disk], + capture_output=True, + text=True, + timeout=20, + ) + data = json.loads(res.stdout) + return ( + int(data.get("virtual-size", 0)), + int(data.get("actual-size", 0)), + ) + except (OSError, subprocess.SubprocessError, ValueError): + return 0, 0 + + def _qemu_list_vms_advanced(self): + """Tableau détaillé par VM : état, vCPU, RAM allouée, disque (virtuel + + réel), plus l'espace total disponible du stockage des images.""" + names = self._qemu_list_domains() + if not names: + print(f"\n{t('No VM found.')}") + return + g = 1 << 30 + header = ( + f"\n{'VM':<28} {'État':<10} {'vCPU':>4} {'RAM':>8} " + f"{'Disque':>9} {'Réel':>9}" + ) + print(header) + print("─" * len(header.strip())) + disk_dirs = set() + for name in names: + state = self._qemu_domstate(name) or "?" + vcpus, mem_kib = self._qemu_dominfo(name) + disk = self._qemu_main_disk(name) + virt, actual = self._qemu_disk_sizes(disk) if disk else (0, 0) + if disk: + disk_dirs.add(os.path.dirname(disk)) + ram_g = (mem_kib * 1024) / g if mem_kib else 0 + print( + f"{name:<28.28} {state:<10.10} {vcpus:>4} " + f"{ram_g:>7.1f}G {virt / g:>8.1f}G {actual / g:>8.1f}G" + ) + # Espace total disponible sur le(s) stockage(s) des disques. + for d in sorted(disk_dirs) or ["/var/lib/libvirt/images"]: + try: + usage = shutil.disk_usage(d) + except OSError: + continue + print( + f"\n{t('Storage')} {d} : " + f"{usage.free / g:.1f}G {t('free')} / " + f"{usage.total / g:.1f}G {t('total')} " + f"({usage.used / g:.1f}G {t('used')})" + ) def _qemu_show_ip(self): # Affiche d'abord les VM (avec leur ID) pour que l'utilisateur sache diff --git a/script/todo/todo_i18n.py b/script/todo/todo_i18n.py index eacee4c..94cb6e6 100644 --- a/script/todo/todo_i18n.py +++ b/script/todo/todo_i18n.py @@ -1420,6 +1420,22 @@ TRANSLATIONS = { "fr": "Impossible de lire la taille actuelle du disque ; abandon.", "en": "Could not read current disk size; aborting.", }, + "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): ", + }, + "Storage": { + "fr": "Stockage", + "en": "Storage", + }, + "total": { + "fr": "au total", + "en": "total", + }, + "used": { + "fr": "utilisés", + "en": "used", + }, "Enter +NG to grow, -NG to shrink, or NG for a target size " "(e.g. +20G, -10G, 60G).": { "fr": "Entrez +NG pour agrandir, -NG pour réduire, ou NG pour une "