[ADD] qemu deploy : case 3D à la création, même sans écran virtuel
Une VM sans console peut vouloir un virtio-gpu accéléré — rendu hors écran, ou émulateur qui tourne dedans — et « auto » ne l'accorde jamais : il s'abstient sans écran, pour ne pas poser un périphérique vidéo que personne n'a demandé. La case envoie donc « --gpu on », qui l'accorde désormais. « --graphics none » est alors écarté : il dit « aucun affichage », et egl-headless EST un affichage. Le repli le rend quand la 3D tombe, sinon la VM repartirait sur le défaut de virt-install. Vérifié : 8 tests neufs et 2 sur le repli, rougis par trois mutations. Le rendu de la case dans le terminal plein écran n'est pas testé. --- EN --- A VM without a console may want an accelerated virtio-gpu — offscreen rendering, or an emulator running inside — and « auto » never grants it: it abstains without a screen, so as not to add a video device nobody asked for. The box therefore sends « --gpu on », which now grants it. « --graphics none » is then dropped: it means « no display », and egl-headless IS a display. The fallback gives it back when 3D fails, otherwise the VM would restart on virt-install's default. Checked: 8 new tests and 2 on the fallback, turned red by three mutations. The box's rendering in the full-screen terminal is not tested. Assisted-by: Claude Opus 5
This commit is contained in:
parent
c8d76f1c33
commit
f417a9ea86
7 changed files with 248 additions and 28 deletions
|
|
@ -327,14 +327,10 @@ def gpu_decision(mode: str, node: str, screen: bool) -> tuple[bool, str]:
|
||||||
mode = (mode or "auto").lower()
|
mode = (mode or "auto").lower()
|
||||||
if mode == "off":
|
if mode == "off":
|
||||||
return False, ""
|
return False, ""
|
||||||
if not screen:
|
if not screen and mode != "on":
|
||||||
# Sans écran virtuel, la 3D n'a rien à accélérer. Le dire seulement
|
# Sans écran virtuel, « auto » s'abstient : une VM serveur n'a pas
|
||||||
# quand elle a été demandée explicitement.
|
# demandé de périphérique vidéo, et lui en poser un d'office change
|
||||||
if mode == "on":
|
# son matériel sans qu'on l'ait voulu.
|
||||||
return (
|
|
||||||
False,
|
|
||||||
" GPU : pas d'écran virtuel sur cette VM, 3D ignorée.",
|
|
||||||
)
|
|
||||||
return False, ""
|
return False, ""
|
||||||
if not node:
|
if not node:
|
||||||
if mode == "on":
|
if mode == "on":
|
||||||
|
|
@ -348,6 +344,16 @@ def gpu_decision(mode: str, node: str, screen: bool) -> tuple[bool, str]:
|
||||||
False,
|
False,
|
||||||
" GPU : aucun sur l'hôte, rendu logiciel (virgl absent).",
|
" GPU : aucun sur l'hôte, rendu logiciel (virgl absent).",
|
||||||
)
|
)
|
||||||
|
if not screen:
|
||||||
|
# 3D demandée sur une VM sans console : le virtio-gpu est POSÉ quand
|
||||||
|
# même, et « egl-headless » n'ouvre aucun port — l'invité reçoit un
|
||||||
|
# périphérique DRM accéléré sans écran à regarder. C'est ce qui sert
|
||||||
|
# au rendu hors écran et à un émulateur qui tourne dans la VM.
|
||||||
|
return (
|
||||||
|
True,
|
||||||
|
f" GPU : 3D activée par {node} sans écran virtuel"
|
||||||
|
" (virtio-gpu accéléré, aucun port ouvert).",
|
||||||
|
)
|
||||||
return True, f" GPU : 3D activée par {node} (virtio-gpu + egl-headless)."
|
return True, f" GPU : 3D activée par {node} (virtio-gpu + egl-headless)."
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -3086,8 +3092,6 @@ def virt_install(
|
||||||
osinfo,
|
osinfo,
|
||||||
"--network",
|
"--network",
|
||||||
args.network,
|
args.network,
|
||||||
"--graphics",
|
|
||||||
graphics,
|
|
||||||
"--console",
|
"--console",
|
||||||
# Journal de console pour la voie installateur. Une console « pty »
|
# Journal de console pour la voie installateur. Une console « pty »
|
||||||
# seule ne gardE rien : quand d-i échoue, il l'écrit à l'écran d'une
|
# seule ne gardE rien : quand d-i échoue, il l'écrit à l'écran d'une
|
||||||
|
|
@ -3105,6 +3109,12 @@ def virt_install(
|
||||||
"--channel",
|
"--channel",
|
||||||
"unix,target.type=virtio,target.name=org.qemu.guest_agent.0",
|
"unix,target.type=virtio,target.name=org.qemu.guest_agent.0",
|
||||||
]
|
]
|
||||||
|
# « --graphics none » dit « aucun affichage » : le poser à côté d'un
|
||||||
|
# « egl-headless », qui EST un affichage, se contredit. Sur une VM sans
|
||||||
|
# console dont la 3D est demandée, egl-headless reste donc le seul.
|
||||||
|
graphics_omis = bool(gpu_args) and graphics == "none"
|
||||||
|
if not graphics_omis:
|
||||||
|
cmd += ["--graphics", graphics]
|
||||||
i_gpu = len(cmd)
|
i_gpu = len(cmd)
|
||||||
cmd += video + gpu_args
|
cmd += video + gpu_args
|
||||||
if args.arch == "s390x":
|
if args.arch == "s390x":
|
||||||
|
|
@ -3185,20 +3195,25 @@ def virt_install(
|
||||||
f"\nÉchec de la commande (code {code}) :\n"
|
f"\nÉchec de la commande (code {code}) :\n"
|
||||||
f" {' '.join(log_env + cmd)}"
|
f" {' '.join(log_env + cmd)}"
|
||||||
)
|
)
|
||||||
if (args.gpu or "auto").lower() == "on":
|
# Le repli vaut AUSSI pour « --gpu on ». Une VM qu'on n'a pas est pire
|
||||||
# « --gpu on » est une exigence, pas une préférence : la trahir en
|
# qu'une VM sans 3D, et le repli n'est pas silencieux : il le dit, en
|
||||||
# silence donnerait une VM qui n'est pas celle qu'on a demandée.
|
# nommant ce qui a été demandé et ce qui a été obtenu.
|
||||||
sys.exit(
|
demande = (args.gpu or "auto").lower() == "on"
|
||||||
"\nErreur : --gpu on demandé, mais EGL ne démarre pas sur"
|
|
||||||
f" {gpu_node}.\n Relancer avec --gpu off pour une VM en rendu"
|
|
||||||
" logiciel."
|
|
||||||
)
|
|
||||||
print(
|
print(
|
||||||
f"\n ⚠ EGL ne démarre pas sur {gpu_node} : la 3D est retirée et la"
|
f"\n ⚠ EGL ne démarre pas sur {gpu_node} :"
|
||||||
"\n VM recréée en rendu logiciel. « --gpu off » évite cet essai."
|
f" {'la 3D DEMANDÉE est retirée' if demande else 'la 3D est retirée'}"
|
||||||
|
"\n et la VM recréée en rendu logiciel."
|
||||||
|
" « --gpu off » évite cet essai."
|
||||||
)
|
)
|
||||||
|
# Rendre l'affichage écarté plus haut : sans lui, la VM repartirait sans
|
||||||
|
# « --graphics none », donc avec le défaut de virt-install, qui n'est pas
|
||||||
|
# ce qu'on avait demandé.
|
||||||
|
rendu = [] if not graphics_omis else ["--graphics", graphics]
|
||||||
cmd_sans_3d = (
|
cmd_sans_3d = (
|
||||||
cmd[:i_gpu] + video_sans_3d + cmd[i_gpu + len(video) + len(gpu_args) :]
|
cmd[:i_gpu]
|
||||||
|
+ rendu
|
||||||
|
+ video_sans_3d
|
||||||
|
+ cmd[i_gpu + len(video) + len(gpu_args) :]
|
||||||
)
|
)
|
||||||
# Le domaine défini par l'essai raté doit partir : sans quoi virt-install
|
# Le domaine défini par l'essai raté doit partir : sans quoi virt-install
|
||||||
# refuse le nom, et la VM resterait celle qui ne démarre pas.
|
# refuse le nom, et la VM resterait celle qui ne démarre pas.
|
||||||
|
|
|
||||||
|
|
@ -393,6 +393,9 @@ def build_spec(vms, domains, form):
|
||||||
# ERPLibre se suit aussi (cloud-init, puis relevé système). Absent de
|
# ERPLibre se suit aussi (cloud-init, puis relevé système). Absent de
|
||||||
# cette assemblée, le choix du formulaire n'atteignait jamais la spec.
|
# cette assemblée, le choix du formulaire n'atteignait jamais la spec.
|
||||||
"monitor": form.get("monitor", True),
|
"monitor": form.get("monitor", True),
|
||||||
|
# Au niveau du déploiement : la 3D est une propriété du matériel de la
|
||||||
|
# VM, pas de ce qu'on installe dedans.
|
||||||
|
"gpu3d": form.get("gpu3d", False),
|
||||||
"add_ssh_config": form["add_ssh_config"],
|
"add_ssh_config": form["add_ssh_config"],
|
||||||
"parallelism": form["parallelism"],
|
"parallelism": form["parallelism"],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -975,6 +975,7 @@ class QemuDeployMixin:
|
||||||
prod=False,
|
prod=False,
|
||||||
install_cmd="",
|
install_cmd="",
|
||||||
vm_tools=(),
|
vm_tools=(),
|
||||||
|
gpu3d=False,
|
||||||
):
|
):
|
||||||
"""Construit la commande deploy_qemu.py d'UNE VM (utilisée pour l'aperçu
|
"""Construit la commande deploy_qemu.py d'UNE VM (utilisée pour l'aperçu
|
||||||
dry-run ET le déploiement réel)."""
|
dry-run ET le déploiement réel)."""
|
||||||
|
|
@ -1010,6 +1011,10 @@ class QemuDeployMixin:
|
||||||
parts += ["--locale", locale]
|
parts += ["--locale", locale]
|
||||||
if desktop:
|
if desktop:
|
||||||
parts.append("--desktop")
|
parts.append("--desktop")
|
||||||
|
if gpu3d:
|
||||||
|
# « on » et non « auto » : auto s'abstient sur une VM sans écran,
|
||||||
|
# or c'est précisément ce que la case permet de demander.
|
||||||
|
parts += ["--gpu", "on"]
|
||||||
# Guide affiché à la connexion SSH de la VM : dans la langue du menu, et
|
# Guide affiché à la connexion SSH de la VM : dans la langue du menu, et
|
||||||
# avec la section ERPLibre seulement là où ERPLibre sera installé — une
|
# avec la section ERPLibre seulement là où ERPLibre sera installé — une
|
||||||
# VM déployée nue n'annonce pas un dépôt qui n'existe pas.
|
# VM déployée nue n'annonce pas un dépôt qui n'existe pas.
|
||||||
|
|
@ -1081,6 +1086,7 @@ class QemuDeployMixin:
|
||||||
vm.get("install_cmd") or (install or {}).get("cmd") or ""
|
vm.get("install_cmd") or (install or {}).get("cmd") or ""
|
||||||
),
|
),
|
||||||
vm_tools=spec.get("vm_tools") or (),
|
vm_tools=spec.get("vm_tools") or (),
|
||||||
|
gpu3d=bool(spec.get("gpu3d")),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _qemu_arches_for(self, distro, arch):
|
def _qemu_arches_for(self, distro, arch):
|
||||||
|
|
@ -1845,6 +1851,14 @@ class QemuDeployMixin:
|
||||||
input(f"{t('Watch the VMs start (no install)')} ? (O/n) : ")
|
input(f"{t('Watch the VMs start (no install)')} ? (O/n) : ")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Posée même sans bureau : une VM sans console peut vouloir un
|
||||||
|
# virtio-gpu accéléré, et c'est ce que « auto » n'accorde jamais.
|
||||||
|
gpu3d = self._is_yes(
|
||||||
|
input(
|
||||||
|
t("3D acceleration (host GPU), even without a screen? (y/N): ")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
add_ssh_config = self._is_yes_default_yes(
|
add_ssh_config = self._is_yes_default_yes(
|
||||||
input(t("Add each VM to ~/.ssh/config? (Y/n): "))
|
input(t("Add each VM to ~/.ssh/config? (Y/n): "))
|
||||||
)
|
)
|
||||||
|
|
@ -1907,6 +1921,7 @@ class QemuDeployMixin:
|
||||||
# Au niveau du déploiement : le suivi survit à une installation
|
# Au niveau du déploiement : le suivi survit à une installation
|
||||||
# décochée (voir _qemu_run_spec).
|
# décochée (voir _qemu_run_spec).
|
||||||
"monitor": monitor,
|
"monitor": monitor,
|
||||||
|
"gpu3d": gpu3d,
|
||||||
"add_ssh_config": add_ssh_config,
|
"add_ssh_config": add_ssh_config,
|
||||||
"parallelism": parallelism,
|
"parallelism": parallelism,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -339,6 +339,15 @@ def run_deploy_form(ctx, run_app: bool = True):
|
||||||
value=defaults.get("monitor", True),
|
value=defaults.get("monitor", True),
|
||||||
id="f_monitor",
|
id="f_monitor",
|
||||||
)
|
)
|
||||||
|
# Indépendante du type de VM : une machine sans console
|
||||||
|
# peut vouloir un virtio-gpu accéléré — rendu hors écran,
|
||||||
|
# ou émulateur qui tourne dedans. Sans écran,
|
||||||
|
# « egl-headless » n'ouvre aucun port.
|
||||||
|
yield Checkbox(
|
||||||
|
t("3D acceleration (host GPU), even without a screen"),
|
||||||
|
value=defaults.get("gpu3d", False),
|
||||||
|
id="f_gpu3d",
|
||||||
|
)
|
||||||
# Le parallélisme reste dans « Déploiement » : c'est le
|
# Le parallélisme reste dans « Déploiement » : c'est le
|
||||||
# nombre de VM menées de front, pas une option
|
# nombre de VM menées de front, pas une option
|
||||||
# d'installation.
|
# d'installation.
|
||||||
|
|
@ -911,6 +920,7 @@ def run_deploy_form(ctx, run_app: bool = True):
|
||||||
# l'installation : décocher ERPLibre emportait la case avec
|
# l'installation : décocher ERPLibre emportait la case avec
|
||||||
# elle, et le tableau de bord ne s'ouvrait plus du tout.
|
# elle, et le tableau de bord ne s'ouvrait plus du tout.
|
||||||
"monitor": self.query_one("#f_monitor", Checkbox).value,
|
"monitor": self.query_one("#f_monitor", Checkbox).value,
|
||||||
|
"gpu3d": self.query_one("#f_gpu3d", Checkbox).value,
|
||||||
"res_label": (
|
"res_label": (
|
||||||
t("custom")
|
t("custom")
|
||||||
if self.profile == "custom"
|
if self.profile == "custom"
|
||||||
|
|
|
||||||
|
|
@ -4993,6 +4993,17 @@ TRANSLATIONS = {
|
||||||
"fr": "Conservés — le déploiement de ces VM ÉCHOUERA :",
|
"fr": "Conservés — le déploiement de ces VM ÉCHOUERA :",
|
||||||
"en": "Kept - the deployment of these VMs will FAIL:",
|
"en": "Kept - the deployment of these VMs will FAIL:",
|
||||||
},
|
},
|
||||||
|
"3D acceleration (host GPU), even without a screen": {
|
||||||
|
"fr": "Accélération 3D (GPU de l'hôte), même sans écran",
|
||||||
|
"en": "3D acceleration (host GPU), even without a screen",
|
||||||
|
},
|
||||||
|
"3D acceleration (host GPU), even without a screen? (y/N): ": {
|
||||||
|
"fr": "Accélération 3D (GPU de l'hôte), même sans écran ? (o/N) : ",
|
||||||
|
"en": (
|
||||||
|
"3D acceleration (host GPU), even without a screen?"
|
||||||
|
" (y/N, default: no): "
|
||||||
|
),
|
||||||
|
},
|
||||||
"Full output:": {
|
"Full output:": {
|
||||||
"fr": "Sortie complète :",
|
"fr": "Sortie complète :",
|
||||||
"en": "Full output:",
|
"en": "Full output:",
|
||||||
|
|
|
||||||
|
|
@ -137,19 +137,69 @@ class Repli(unittest.TestCase):
|
||||||
self.assertIn("vm-a", milieu)
|
self.assertIn("vm-a", milieu)
|
||||||
self.assertLess(1, len(lances) - 1)
|
self.assertLess(1, len(lances) - 1)
|
||||||
|
|
||||||
def test_gpu_on_is_not_silently_downgraded(self):
|
def test_gpu_on_also_falls_back_but_says_so(self):
|
||||||
"""« --gpu on » est une exigence : la trahir donnerait une VM qui
|
"""Une VM qu'on n'a pas est pire qu'une VM sans 3D. Le repli vaut donc
|
||||||
n'est pas celle qu'on a demandée."""
|
aussi pour une 3D demandée — à condition de nommer ce qui manque, sans
|
||||||
lances, exc, _ = self._lancer(gpu="on")
|
quoi l'utilisateur croirait avoir obtenu ce qu'il a coché."""
|
||||||
self.assertIsInstance(exc, SystemExit)
|
lances, exc, rendu = self._lancer(gpu="on")
|
||||||
self.assertIn("--gpu off", str(exc))
|
self.assertIsNone(exc, rendu)
|
||||||
self.assertEqual(1, len(lances))
|
self.assertIn("DEMANDÉE", rendu)
|
||||||
|
self.assertNotIn("accel3d", " ".join(lances[-1]))
|
||||||
|
|
||||||
def test_another_failure_is_not_retried(self):
|
def test_another_failure_is_not_retried(self):
|
||||||
lances, exc, _ = self._lancer(sortie=SORTIE_AUTRE)
|
lances, exc, _ = self._lancer(sortie=SORTIE_AUTRE)
|
||||||
self.assertIsInstance(exc, SystemExit)
|
self.assertIsInstance(exc, SystemExit)
|
||||||
self.assertEqual(1, len(lances))
|
self.assertEqual(1, len(lances))
|
||||||
|
|
||||||
|
def _sans_ecran(self, resultat):
|
||||||
|
"""virt_install sur une VM sans console dont la 3D est demandée.
|
||||||
|
|
||||||
|
`resultat` est ce que rend le runner en mode capture. Rend les
|
||||||
|
commandes lancées."""
|
||||||
|
args = self._args(gpu="on")
|
||||||
|
args.desktop = False
|
||||||
|
lances = []
|
||||||
|
|
||||||
|
class FauxRunner:
|
||||||
|
dry_run = False
|
||||||
|
use_sudo = False
|
||||||
|
|
||||||
|
def run(self, cmd, *, privileged=False, check=True, capture=False):
|
||||||
|
lances.append(list(cmd))
|
||||||
|
return resultat if capture else None
|
||||||
|
|
||||||
|
with mock.patch.object(
|
||||||
|
DQ, "host_arch", return_value="amd64"
|
||||||
|
), mock.patch.object(
|
||||||
|
DQ, "kvm_available", return_value=True
|
||||||
|
), mock.patch.object(
|
||||||
|
DQ, "os"
|
||||||
|
) as faux_os:
|
||||||
|
faux_os.getuid.return_value = 1000
|
||||||
|
with redirect_stdout(io.StringIO()):
|
||||||
|
DQ.virt_install(
|
||||||
|
args,
|
||||||
|
Path("/tmp/d.qcow2"),
|
||||||
|
Path("/tmp/s.iso"),
|
||||||
|
"ubuntu26.04",
|
||||||
|
FauxRunner(),
|
||||||
|
)
|
||||||
|
return lances
|
||||||
|
|
||||||
|
def test_a_screenless_3d_vm_keeps_egl_as_its_only_display(self):
|
||||||
|
"""« --graphics none » dit « aucun affichage » : le poser à côté d'un
|
||||||
|
egl-headless, qui EST un affichage, se contredit."""
|
||||||
|
rendu = " ".join(self._sans_ecran((0, ""))[0])
|
||||||
|
self.assertIn("egl-headless", rendu)
|
||||||
|
self.assertNotIn("--graphics none", rendu)
|
||||||
|
|
||||||
|
def test_dropping_the_3d_gives_the_display_back(self):
|
||||||
|
"""Le repli doit rendre le « --graphics none » écarté pour la 3D,
|
||||||
|
sinon la VM repart sur le défaut de virt-install."""
|
||||||
|
dernier = " ".join(self._sans_ecran((1, SORTIE_EGL))[-1])
|
||||||
|
self.assertIn("--graphics none", dernier)
|
||||||
|
self.assertNotIn("egl-headless", dernier)
|
||||||
|
|
||||||
def test_a_success_never_retries(self):
|
def test_a_success_never_retries(self):
|
||||||
lances, exc, _ = self._lancer(sortie="", code=0)
|
lances, exc, _ = self._lancer(sortie="", code=0)
|
||||||
self.assertIsNone(exc)
|
self.assertIsNone(exc)
|
||||||
|
|
|
||||||
116
test/test_qemu_gpu_option.py
Normal file
116
test/test_qemu_gpu_option.py
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# © 2026 TechnoLibre (http://www.technolibre.ca)
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||||
|
"""La 3D se coche à la création, même sur une VM sans écran.
|
||||||
|
|
||||||
|
« auto » s'abstient sans écran virtuel : une VM serveur n'a pas demandé de
|
||||||
|
périphérique vidéo, et lui en poser un d'office changerait son matériel. Mais
|
||||||
|
une VM sans console peut vouloir un virtio-gpu accéléré — rendu hors écran,
|
||||||
|
émulateur qui tourne dedans. La case le demande explicitement.
|
||||||
|
|
||||||
|
Ce que ces tests gardent :
|
||||||
|
|
||||||
|
- la case atteint vraiment la commande, à travers la spec ;
|
||||||
|
- sans elle, rien ne change pour une VM serveur ;
|
||||||
|
- « --graphics none » et « egl-headless » ne coexistent jamais : le premier
|
||||||
|
dit « aucun affichage », le second EST un affichage.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
sys.argv = ["todo.py"]
|
||||||
|
from script.todo.deploy_form_lib import build_spec # noqa: E402
|
||||||
|
from script.todo.todo import TODO # noqa: E402
|
||||||
|
|
||||||
|
RACINE = Path(__file__).resolve().parents[1]
|
||||||
|
NODE = "/dev/dri/renderD128"
|
||||||
|
|
||||||
|
|
||||||
|
def _deploy_qemu():
|
||||||
|
path = RACINE / "script/qemu/deploy_qemu.py"
|
||||||
|
spec = importlib.util.spec_from_file_location("deploy_qemu", path)
|
||||||
|
mod = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(mod)
|
||||||
|
return mod
|
||||||
|
|
||||||
|
|
||||||
|
DQ = _deploy_qemu()
|
||||||
|
|
||||||
|
|
||||||
|
class LaDecision(unittest.TestCase):
|
||||||
|
def test_auto_still_abstains_without_a_screen(self):
|
||||||
|
"""Le défaut ne doit pas changer : une VM serveur reste sans vidéo."""
|
||||||
|
on, msg = DQ.gpu_decision("auto", NODE, False)
|
||||||
|
self.assertFalse(on)
|
||||||
|
self.assertEqual("", msg)
|
||||||
|
|
||||||
|
def test_on_now_enables_3d_without_a_screen(self):
|
||||||
|
on, msg = DQ.gpu_decision("on", NODE, False)
|
||||||
|
self.assertTrue(on)
|
||||||
|
self.assertIn("sans écran", msg)
|
||||||
|
|
||||||
|
def test_off_wins_over_everything(self):
|
||||||
|
self.assertEqual((False, ""), DQ.gpu_decision("off", NODE, False))
|
||||||
|
|
||||||
|
def test_without_a_host_node_it_still_refuses(self):
|
||||||
|
on, msg = DQ.gpu_decision("on", "", False)
|
||||||
|
self.assertFalse(on)
|
||||||
|
self.assertTrue(msg)
|
||||||
|
|
||||||
|
|
||||||
|
class LaCaseAtteintLaCommande(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.todo = TODO.__new__(TODO)
|
||||||
|
|
||||||
|
def _parts(self, gpu3d):
|
||||||
|
vm = {
|
||||||
|
"name": "vm-a",
|
||||||
|
"distro": "ubuntu",
|
||||||
|
"version": "24.04",
|
||||||
|
"arch": "amd64",
|
||||||
|
"ram": 4096,
|
||||||
|
"vcpus": 2,
|
||||||
|
"disk": "20G",
|
||||||
|
}
|
||||||
|
spec = {
|
||||||
|
"vms": [vm],
|
||||||
|
"gpu3d": gpu3d,
|
||||||
|
"install": None,
|
||||||
|
"ssh_key": "",
|
||||||
|
"vm_tools": (),
|
||||||
|
}
|
||||||
|
return self.todo._qemu_deploy_parts_for(vm, spec, dry_run=True)
|
||||||
|
|
||||||
|
def test_the_box_adds_the_flag(self):
|
||||||
|
self.assertIn("--gpu", self._parts(True))
|
||||||
|
parts = self._parts(True)
|
||||||
|
self.assertEqual("on", parts[parts.index("--gpu") + 1])
|
||||||
|
|
||||||
|
def test_without_the_box_nothing_is_added(self):
|
||||||
|
self.assertNotIn("--gpu", self._parts(False))
|
||||||
|
|
||||||
|
|
||||||
|
class LaSpecLaTransporte(unittest.TestCase):
|
||||||
|
def _spec(self, form_extra):
|
||||||
|
form = {
|
||||||
|
"res_label": "x1",
|
||||||
|
"ssh_key": "",
|
||||||
|
"install": None,
|
||||||
|
"add_ssh_config": True,
|
||||||
|
"parallelism": 1,
|
||||||
|
}
|
||||||
|
form.update(form_extra)
|
||||||
|
return build_spec([{"name": "vm-a"}], [], form)
|
||||||
|
|
||||||
|
def test_the_checkbox_reaches_the_spec(self):
|
||||||
|
self.assertTrue(self._spec({"gpu3d": True})["gpu3d"])
|
||||||
|
|
||||||
|
def test_its_default_is_off(self):
|
||||||
|
self.assertFalse(self._spec({})["gpu3d"])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Loading…
Reference in a new issue