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()