Une VM prend son adresse dans le /24 de son réseau et en sort par le .1, porté par le pont : déplacer ce /24 sous une VM allumée lui laisse un bail qui ne mène nulle part, et abattre le réseau détache son tap, que libvirt n'y remet pas. network_qemu.py tient les trois gestes dans l'ordre — arrêter les VM attachées, redéfinir le réseau sur le préfixe voulu, rallumer celles qu'il a arrêtées, elles seules. Une VM sourde au shutdown annule la redéfinition plutôt que d'y perdre son pont ; --force-off coupe le courant. Un préfixe qui recouvre ce que l'hôte route déjà est refusé. Vérifié : 13 tests neufs, la numérotation du menu, une passe à blanc. --- EN --- A VM takes its address in its network's /24 and leaves through the .1, carried by the bridge: moving that /24 under a live VM leaves it a lease leading nowhere, and tearing the network down detaches its tap, which libvirt does not put back. network_qemu.py holds the three steps in order — shut down the attached VMs, redefine the network on the wanted prefix, start again the ones it stopped, those only. A VM deaf to the shutdown cancels the redefinition rather than losing its bridge; --force-off cuts the power. A prefix overlapping what the host already routes is refused. Checked: 13 new tests, the menu numbering, one dry run. Assisted-by: Claude Opus 5
89 lines
3.7 KiB
Python
89 lines
3.7 KiB
Python
#!/usr/bin/env python3
|
|
# © 2026 TechnoLibre (http://www.technolibre.ca)
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
"""Menu QEMU/KVM : le sous-réseau où vivent les VM.
|
|
|
|
Deux entrées pour une seule question — quel /24 le réseau libvirt sert-il, et
|
|
que faire quand ce n'est plus celui que les VM attendent. Voir l'état ne
|
|
modifie rien ; le recréer arrête les VM attachées, redéfinit le réseau, puis
|
|
redémarre ces VM.
|
|
|
|
Le travail est fait par script/qemu/network_qemu.py, lancé comme un programme
|
|
et non importé : c'est lui qui porte l'ordre des trois gestes, et il reste
|
|
utilisable seul sur un hôte où le menu ne tourne pas — celui, justement, qui
|
|
vient de perdre son réseau.
|
|
|
|
Mixin de la classe TODO : ses méthodes vivent sur la même instance que celles
|
|
des autres fichiers, elles s'appellent donc par « self. » sans rien importer.
|
|
"""
|
|
|
|
import os
|
|
|
|
from script.todo.qemu_privilege import needs_sudo
|
|
from script.todo.todo_i18n import t
|
|
|
|
# Le préfixe d'origine du « default » de libvirt, proposé par défaut. Il fait
|
|
# autorité dans network_qemu.py ; répété ici pour que l'invite ait une valeur à
|
|
# afficher sans charger le script.
|
|
PREFIXE_LIBVIRT = "192.168.122"
|
|
|
|
|
|
class QemuNetworkMixin:
|
|
"""Menu QEMU/KVM : le sous-réseau où vivent les VM."""
|
|
|
|
def _qemu_network_script_path(self):
|
|
"""Chemin absolu vers script/qemu/network_qemu.py."""
|
|
path = os.path.join(
|
|
os.path.dirname(os.path.abspath(__file__)),
|
|
"..",
|
|
"qemu",
|
|
"network_qemu.py",
|
|
)
|
|
return os.path.realpath(path)
|
|
|
|
def _qemu_network_cmd(self, *args):
|
|
"""La commande du script, sudo laissé à qui en a besoin.
|
|
|
|
« --no-sudo » quand le groupe libvirt suffit : préfixer sudo ne donne
|
|
alors aucun droit de plus et réclame un mot de passe pour rien.
|
|
"""
|
|
parts = [self._qemu_network_script_path(), *args]
|
|
if not needs_sudo():
|
|
parts.append("--no-sudo")
|
|
return " ".join(parts)
|
|
|
|
def _qemu_network_status(self):
|
|
"""Affiche le sous-réseau servi, les VM attachées et leurs baux."""
|
|
cmd = self._qemu_network_cmd("--status")
|
|
print(f"{t('Will execute:')} {cmd}")
|
|
self.execute.exec_command_live(cmd, source_erplibre=False)
|
|
|
|
def _qemu_network_recreate(self):
|
|
"""Recrée le sous-réseau : arrête les VM, redéfinit, redémarre.
|
|
|
|
L'état est montré AVANT la question : le préfixe à viser se lit sur
|
|
celui que le réseau sert aujourd'hui, et personne ne devrait avoir à
|
|
deviner ce qu'il va changer.
|
|
"""
|
|
self._qemu_network_status()
|
|
print(f"\n{t('Recreating the subnet does three things:')}")
|
|
print(f" 0. {t('shut down the VMs attached to the network')}")
|
|
print(f" 1. {t('redefine the network on the wanted prefix')}")
|
|
print(f" 2. {t('start those same VMs again')}")
|
|
prefixe = input(
|
|
f"\n{t('Target prefix')} [{PREFIXE_LIBVIRT}] : "
|
|
).strip()
|
|
prefixe = prefixe or PREFIXE_LIBVIRT
|
|
reseau = input(f"{t('libvirt network')} [default] : ").strip()
|
|
reseau = reseau or "default"
|
|
forcer = self._is_yes(
|
|
input(t("Power off VMs that ignore the shutdown? (y/N): "))
|
|
)
|
|
args = ["--recreate", "--network", reseau, "--prefix", prefixe]
|
|
if forcer:
|
|
args.append("--force-off")
|
|
cmd = self._qemu_network_cmd(*args)
|
|
print(f"\n{t('Will execute:')} {cmd}")
|
|
# La confirmation est posée par le script lui-même : il la pose sur
|
|
# /dev/tty, donc elle fonctionne aussi quand il est lancé hors du menu.
|
|
self.execute.exec_command_live(cmd, source_erplibre=False)
|