From 1b26c4661be94c7f55419e26303ee852e91b2944 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Thu, 3 Sep 2026 03:30:54 -0400 Subject: [PATCH] =?UTF-8?q?[FIX]=20qemu=203D=20:=20voir=20l'ABI=20fig?= =?UTF-8?q?=C3=A9e=20qui=20annule=20l'acc=C3=A9l=C3=A9ration=20demand?= =?UTF-8?q?=C3=A9e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Depuis libvirt 12.5.0, le vidéo porte un attribut « device » qui grave le device QEMU retenu, pour tenir l'ABI de l'invité stable d'un démarrage à l'autre. Il l'emporte sur « accel3d ». Une VM démarrée une première fois sans 3D — module GL absent, ou option non cochée — garde donc le device sans GL, et l'activer ensuite n'écrit qu'une intention : QEMU reçoit toujours « virtio-vga » nu. L'état lisait « accel3d » sans lire cet attribut, si bien que le résumé annonçait « 3D » sur une VM qui tourne sans. Il porte désormais le device figé quand celui-ci contredit la demande. --- EN --- Since libvirt 12.5.0 the video carries a « device » attribute recording the chosen QEMU device, to keep the guest ABI stable across restarts. It outranks « accel3d ». A VM first started without 3D — GL module absent, or the box unticked — therefore keeps the non-GL device, and enabling it afterwards writes an intent only: QEMU still gets plain « virtio-vga ». State read « accel3d » without that attribute, so the summary announced « 3D » on a VM running without it. It now names the pinned device whenever it contradicts the request. Assisted-by: Claude Opus 5 --- script/todo/qemu_hardware.py | 33 ++++++++++++++++- script/todo/todo_i18n.py | 6 ++++ test/test_qemu_gpu_option.py | 70 ++++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 1 deletion(-) diff --git a/script/todo/qemu_hardware.py b/script/todo/qemu_hardware.py index 700cfc4..3d5ca13 100644 --- a/script/todo/qemu_hardware.py +++ b/script/todo/qemu_hardware.py @@ -144,6 +144,9 @@ def hw_state(xml: str, autostart=None) -> dict: "max_mem_mib": 0, "video": "", "accel3d": False, + # Device QEMU figé par libvirt (attribut « device » du , + # depuis libvirt 12.5.0). Vide sur les versions antérieures. + "video_device": "", "egl": False, "render": "", "screen": False, @@ -182,6 +185,7 @@ def hw_state(xml: str, autostart=None) -> dict: state["heads"] = int(model.get("heads") or 1) except ValueError: state["heads"] = 1 + state["video_device"] = model.get("device") or "" accel = model.find("acceleration") state["accel3d"] = accel is not None and accel.get("accel3d") == "yes" for graphics in root.findall("./devices/graphics"): @@ -197,6 +201,27 @@ def hw_state(xml: str, autostart=None) -> dict: return state +def pin_defeats_3d(state) -> bool: + """L'ABI figée annule-t-elle la 3D pourtant demandée ? + + Depuis libvirt 12.5.0, le vidéo porte un attribut « device » + qui GRAVE le device QEMU retenu — « virtio-vga », « virtio-vga-gl », + et leurs équivalents sans VGA. Il existe pour tenir l'ABI de l'invité + stable d'un démarrage à l'autre, et il l'emporte sur « accel3d ». + + Le mode de défaillance qu'il produit : une VM démarrée une première + fois sans 3D — parce que le module GL manquait, ou parce que l'option + n'était pas cochée — garde le device non accéléré. Cocher la 3D + ensuite écrit « accel3d=yes » et ne change RIEN à ce que QEMU reçoit. + La définition et la ligne de commande se contredisent alors sans que + rien ne le signale : seul le suffixe « -gl » du device dit la vérité. + """ + fige = state.get("video_device") or "" + return ( + bool(state.get("accel3d")) and bool(fige) and not fige.endswith("-gl") + ) + + def net_token(kind: str, source) -> str: """Identité du réseau d'une interface : « network:default », « bridge:br0 ». @@ -486,7 +511,13 @@ def hw_summary(state: dict) -> str: bits = [f"{state.get('vcpus') or '?'} vCPU", fmt_mib(state.get("mem_mib"))] if state.get("accel3d") or state.get("render"): node = state.get("render") or "?" - bits.append(f"3D {node.rsplit('/', 1)[-1]}") + marque = f"3D {node.rsplit('/', 1)[-1]}" + # Une VM dont l'ABI est figée sur un device sans GL tourne SANS 3D, + # quoi que dise « accel3d ». Afficher « 3D » tout court y ferait + # croire l'inverse, et c'est la ligne sur laquelle on décide. + if pin_defeats_3d(state): + marque += f" ⚠ {t('frozen on')} {state['video_device']}" + bits.append(marque) elif state.get("screen"): bits.append(t("software rendering")) if (state.get("heads") or 1) > 1: diff --git a/script/todo/todo_i18n.py b/script/todo/todo_i18n.py index ca1d72b..193b987 100644 --- a/script/todo/todo_i18n.py +++ b/script/todo/todo_i18n.py @@ -5008,6 +5008,12 @@ TRANSLATIONS = { ), }, # Récupération de fichiers dans le disque d'une VM (libguestfs) + # Une VM dont libvirt a figé le device vidéo sur une variante sans GL + # tourne sans 3D, même avec « accel3d=yes » dans sa définition. + "frozen on": { + "fr": "figée sur", + "en": "frozen on", + }, "Recover files from a VM disk (libguestfs)": { "fr": "💾 Récupérer des fichiers dans le disque d'une VM (libguestfs)", "en": "💾 Recover files from a VM disk (libguestfs)", diff --git a/test/test_qemu_gpu_option.py b/test/test_qemu_gpu_option.py index f749e01..1dca6f6 100644 --- a/test/test_qemu_gpu_option.py +++ b/test/test_qemu_gpu_option.py @@ -112,5 +112,75 @@ class LaSpecLaTransporte(unittest.TestCase): self.assertFalse(self._spec({})["gpu3d"]) +class LAbiFigee(unittest.TestCase): + """Une 3D demandée que l'ABI figée annule doit se voir. + + Depuis libvirt 12.5.0, le vidéo porte un attribut « device » + qui grave le device QEMU retenu, pour tenir l'ABI de l'invité stable + d'un démarrage à l'autre. Il l'emporte sur « accel3d » : une VM + démarrée une première fois sans 3D garde le device sans GL, et cocher + la 3D ensuite ne change rien à ce que QEMU reçoit. La définition et + la ligne de commande se contredisent alors en silence. + """ + + XML = ( + "vm8" + "33554432" + "" + "" + "" + "" + ) + + def _etat(self, device="", accel="yes"): + from script.todo.qemu_hardware import hw_state + + attr = f"device='{device}'" if device else "" + return hw_state(self.XML.format(attr=attr, accel=accel), False) + + def test_the_pinned_device_is_read(self): + self.assertEqual( + self._etat("virtio-vga")["video_device"], "virtio-vga" + ) + self.assertEqual(self._etat()["video_device"], "") + + def test_a_non_gl_pin_defeats_the_requested_3d(self): + from script.todo.qemu_hardware import pin_defeats_3d + + self.assertTrue(pin_defeats_3d(self._etat("virtio-vga"))) + + def test_a_gl_pin_does_not(self): + """Le suffixe « -gl » est ce qui distingue les deux devices. + + Sans cette lecture, tout épinglage passerait pour une panne et la + VM correctement accélérée porterait un avertissement à tort. + """ + from script.todo.qemu_hardware import pin_defeats_3d + + self.assertFalse(pin_defeats_3d(self._etat("virtio-vga-gl"))) + self.assertFalse(pin_defeats_3d(self._etat("virtio-gpu-gl"))) + + def test_no_pin_and_no_3d_are_not_flagged(self): + """Deux cas voisins qu'un test trop large confondrait : libvirt + antérieur à 12.5.0 n'écrit pas l'attribut, et une VM sans 3D + demandée n'a rien à signaler même si son device est figé.""" + from script.todo.qemu_hardware import pin_defeats_3d + + self.assertFalse(pin_defeats_3d(self._etat())) + self.assertFalse(pin_defeats_3d(self._etat("virtio-vga", accel="no"))) + + def test_the_summary_says_so(self): + """La ligne de résumé est celle sur laquelle on décide : elle ne + doit pas afficher « 3D » tout court quand la 3D ne tourne pas.""" + from script.todo.qemu_hardware import hw_summary + + figee = hw_summary(self._etat("virtio-vga")) + vivante = hw_summary(self._etat("virtio-vga-gl")) + self.assertIn("virtio-vga", figee) + self.assertIn("⚠", figee) + self.assertNotIn("⚠", vivante) + + if __name__ == "__main__": unittest.main()