[FIX] qemu 3D : voir l'ABI figée qui annule l'accélération demandée
Depuis libvirt 12.5.0, le <model> 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 <model> 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
This commit is contained in:
parent
94090d5280
commit
1b26c4661b
3 changed files with 108 additions and 1 deletions
|
|
@ -144,6 +144,9 @@ def hw_state(xml: str, autostart=None) -> dict:
|
||||||
"max_mem_mib": 0,
|
"max_mem_mib": 0,
|
||||||
"video": "",
|
"video": "",
|
||||||
"accel3d": False,
|
"accel3d": False,
|
||||||
|
# Device QEMU figé par libvirt (attribut « device » du <model>,
|
||||||
|
# depuis libvirt 12.5.0). Vide sur les versions antérieures.
|
||||||
|
"video_device": "",
|
||||||
"egl": False,
|
"egl": False,
|
||||||
"render": "",
|
"render": "",
|
||||||
"screen": False,
|
"screen": False,
|
||||||
|
|
@ -182,6 +185,7 @@ def hw_state(xml: str, autostart=None) -> dict:
|
||||||
state["heads"] = int(model.get("heads") or 1)
|
state["heads"] = int(model.get("heads") or 1)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
state["heads"] = 1
|
state["heads"] = 1
|
||||||
|
state["video_device"] = model.get("device") or ""
|
||||||
accel = model.find("acceleration")
|
accel = model.find("acceleration")
|
||||||
state["accel3d"] = accel is not None and accel.get("accel3d") == "yes"
|
state["accel3d"] = accel is not None and accel.get("accel3d") == "yes"
|
||||||
for graphics in root.findall("./devices/graphics"):
|
for graphics in root.findall("./devices/graphics"):
|
||||||
|
|
@ -197,6 +201,27 @@ def hw_state(xml: str, autostart=None) -> dict:
|
||||||
return state
|
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 <model> 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:
|
def net_token(kind: str, source) -> str:
|
||||||
"""Identité du réseau d'une interface : « network:default », « bridge:br0 ».
|
"""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"))]
|
bits = [f"{state.get('vcpus') or '?'} vCPU", fmt_mib(state.get("mem_mib"))]
|
||||||
if state.get("accel3d") or state.get("render"):
|
if state.get("accel3d") or state.get("render"):
|
||||||
node = state.get("render") or "?"
|
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"):
|
elif state.get("screen"):
|
||||||
bits.append(t("software rendering"))
|
bits.append(t("software rendering"))
|
||||||
if (state.get("heads") or 1) > 1:
|
if (state.get("heads") or 1) > 1:
|
||||||
|
|
|
||||||
|
|
@ -5008,6 +5008,12 @@ TRANSLATIONS = {
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
# Récupération de fichiers dans le disque d'une VM (libguestfs)
|
# 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)": {
|
"Recover files from a VM disk (libguestfs)": {
|
||||||
"fr": "💾 Récupérer des fichiers dans le disque d'une VM (libguestfs)",
|
"fr": "💾 Récupérer des fichiers dans le disque d'une VM (libguestfs)",
|
||||||
"en": "💾 Recover files from a VM disk (libguestfs)",
|
"en": "💾 Recover files from a VM disk (libguestfs)",
|
||||||
|
|
|
||||||
|
|
@ -112,5 +112,75 @@ class LaSpecLaTransporte(unittest.TestCase):
|
||||||
self.assertFalse(self._spec({})["gpu3d"])
|
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 <model> 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 = (
|
||||||
|
"<domain><name>vm</name><vcpu>8</vcpu>"
|
||||||
|
"<memory unit='KiB'>33554432</memory><devices>"
|
||||||
|
"<video><model type='virtio' heads='1' {attr}>"
|
||||||
|
"<acceleration accel3d='{accel}'/></model></video>"
|
||||||
|
"<graphics type='egl-headless'>"
|
||||||
|
"<gl rendernode='/dev/dri/renderD128'/></graphics>"
|
||||||
|
"</devices></domain>"
|
||||||
|
)
|
||||||
|
|
||||||
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue