[FIX] qemu deploy : l'unité de l'agent invité ne finit plus en échec

Dans le script du service qui pose qemu-guest-agent, « && » et « || » se
lisent à égalité de gauche à droite : une pose apt réussie enchaînait sur
« dnf install », puis sur le « command -v » d'un gestionnaire absent, qui
rend 127 sous dash. Le service finissait en échec après avoir posé
l'agent. Chaque branche est désormais entre accolades. Vérifié : le script
extrait de la vraie configuration cloud-init rend 0 avec apt-get, dnf ou
pacman factice.

--- EN ---

In the script of the service installing qemu-guest-agent, "&&" and "||"
have equal precedence, left to right: a successful apt install went on to
"dnf install", then to the "command -v" of a missing manager, which
returns 127 under dash. The service ended in failure after installing the
agent. Each branch is now braced. Checked: the script extracted from the
real cloud-init configuration returns 0 with a fake apt-get, dnf or
pacman.

Assisted-by: Claude Opus 5
This commit is contained in:
Mathieu Benoit 2026-09-14 14:41:19 -04:00
parent f6cc0bfc53
commit ddde76f0cf
2 changed files with 76 additions and 9 deletions

View file

@ -2537,10 +2537,9 @@ def build_cloud_config(
" 2>/dev/null && break; done || true", " 2>/dev/null && break; done || true",
# qemu-guest-agent : installé APRÈS sshd, et surtout HORS de cloud-init. # qemu-guest-agent : installé APRÈS sshd, et surtout HORS de cloud-init.
# #
# Mesuré sur Ubuntu 26.04 s390x : « apt-get install qemu-guest-agent » # Son installation tire plusieurs paquets de virtualisation, et sur une
# tire liburing2, ubuntu-helper-virt-hwe et ubuntu-virt depuis # architecture émulée elle tient cloud-final une dizaine de minutes. Or
# ports.ubuntu.com, et cloud-final tourne 9 min 47. Or le suivi # le suivi d'installation attend « cloud-init status: done » : autant
# d'installation attend « cloud-init status: done » : dix minutes
# d'attente pour un paquet accessoire, avant même de commencer le # d'attente pour un paquet accessoire, avant même de commencer le
# travail utile. # travail utile.
# #
@ -2548,13 +2547,20 @@ def build_cloud_config(
# en quelques secondes et l'agent apparaît quand il apparaît. On saute # en quelques secondes et l'agent apparaît quand il apparaît. On saute
# aussi l'installation quand qemu-ga est déjà là, ce qui est le cas de # aussi l'installation quand qemu-ga est déjà là, ce qui est le cas de
# beaucoup d'images. Repli en ligne si systemd-run manque. # beaucoup d'images. Repli en ligne si systemd-run manque.
#
# Chaque branche de gestionnaire est entre accolades. Sans elles, « && »
# et « || » se lisent à égalité de gauche à droite : une pose apt
# réussie enchaîne sur « dnf install », puis sur le « command -v »
# d'un gestionnaire absent, qui rend 127 sous dash — et le service
# finit en échec après avoir posé l'agent. Le nom du service est celui
# qu'attend _qemu_cloud_init_wait, côté hôte.
" - command -v qemu-ga >/dev/null 2>&1 ||" " - command -v qemu-ga >/dev/null 2>&1 ||"
" systemd-run --no-block --unit=erplibre-qga --collect" " systemd-run --no-block --unit=erplibre-qga --collect"
" /bin/sh -c 'command -v apt-get >/dev/null && { apt-get update -qq" " /bin/sh -c '{ command -v apt-get >/dev/null && { apt-get update -qq"
" || true; apt-get install -y qemu-guest-agent; }" " || true; apt-get install -y qemu-guest-agent; }; }"
" || command -v dnf >/dev/null && dnf install -y qemu-guest-agent" " || { command -v dnf >/dev/null && dnf install -y qemu-guest-agent; }"
" || command -v pacman >/dev/null && pacman -Sy --noconfirm" " || { command -v pacman >/dev/null && pacman -Sy --noconfirm"
" qemu-guest-agent' 2>/dev/null" " qemu-guest-agent; }' 2>/dev/null"
" || (command -v apt-get >/dev/null && (timeout 120 apt-get update -qq" " || (command -v apt-get >/dev/null && (timeout 120 apt-get update -qq"
" || true; timeout 300 apt-get install -y qemu-guest-agent)) ||" " || true; timeout 300 apt-get install -y qemu-guest-agent)) ||"
" (command -v dnf >/dev/null && timeout 300 dnf install -y" " (command -v dnf >/dev/null && timeout 300 dnf install -y"

View file

@ -1330,6 +1330,67 @@ class TestGnomeSiteExtensions(unittest.TestCase):
self.assertIn("dbus-run-session", self.block) self.assertIn("dbus-run-session", self.block)
class TestLeServiceDeLAgentInvite(unittest.TestCase):
"""Le service détaché qui pose qemu-guest-agent, lancé par cloud-init.
Son script essaie un gestionnaire de paquets après l'autre. Sans
accolades autour de chaque branche, une pose réussie enchaîne sur le
gestionnaire suivant, absent, et le service finit en échec — 127 sous
dash — alors que l'agent est posé. Le script est extrait de la vraie
configuration cloud-init, lue en YAML, et tourne dans un PATH où seul le
gestionnaire choisi existe, faux : aucun paquet n'est posé, aucun
service lancé.
"""
def _script(self):
import shlex
import yaml
from script.qemu import deploy_qemu
args = deploy_qemu.build_parser().parse_args(
["--distro", "ubuntu", "--hostname", "vm"]
)
doc = yaml.safe_load(deploy_qemu.build_cloud_config(args, None, []))
ligne = next(
c
for c in doc["runcmd"]
if isinstance(c, str) and "--unit=erplibre-qga" in c
)
mots = shlex.split(ligne)
# « systemd-run … /bin/sh -c '<script>' » : le script suit « -c ».
return mots[mots.index("/bin/sh") + 2]
def test_a_successful_install_exits_zero(self):
import os
import shutil
import tempfile
script = self._script()
for gestionnaire in ("apt-get", "dnf", "pacman"):
with self.subTest(gestionnaire=gestionnaire):
with tempfile.TemporaryDirectory() as tmp:
faux = pathlib.Path(tmp)
os.symlink(shutil.which("sh"), faux / "sh")
trace = faux / "trace"
chemin = faux / gestionnaire
chemin.write_text(
f'#!/bin/sh\necho "$0 $*" >> "{trace}"\nexit 0\n',
encoding="utf-8",
)
chemin.chmod(0o755)
fini = subprocess.run(
[str(faux / "sh"), "-c", script],
env={"PATH": str(faux)},
capture_output=True,
text=True,
timeout=30,
)
self.assertEqual(0, fini.returncode, fini.stderr)
self.assertIn("qemu-guest-agent", trace.read_text())
class TestLeVerrouAptNeCoutePasDesMinutes(unittest.TestCase): class TestLeVerrouAptNeCoutePasDesMinutes(unittest.TestCase):
"""« Impossible d'obtenir le verrou /var/lib/apt/lists/lock. Il est """« Impossible d'obtenir le verrou /var/lib/apt/lists/lock. Il est
occupé par le processus N (apt-get) », répété pendant des minutes au occupé par le processus N (apt-get) », répété pendant des minutes au