[ADD] long_test : deep_qemu, et la preuve que KVM est bien là
Le pendant de deep_proxmox : des QEMU dans des QEMU. Le ralentissement du
quatrième étage vient du PROCESSEUR, mais le coût par étage vient de ce qu'on
installe — un nœud Proxmox pose un noyau, corosync, ceph et une interface web
là où un hôte libvirt pose libvirtd. Les deux mesures ensemble séparent ce qui
tient au matériel de ce qui tient à la pile.
Ce test ne peut pas se contenter de descendre. deploy_qemu.py ne passe jamais
« --cpu host-passthrough » et, quand /dev/kvm manque, il n'échoue PAS : il pose
« --virt-type qemu », avertit sur une ligne et crée une VM entièrement ÉMULÉE —
sept minutes et demie de démarrage, aucun code de retour pour le dire. Sans
garde, la descente mesurerait de la TCG empilée en croyant mesurer de
l'imbrication, et rendrait un chiffre plus flatteur et faux.
Chaque étage doit donc PROUVER : /dev/kvm lisible, « nested » à Y, et le
domaine de l'enfant en type='kvm'. Ce qui n'a pas été lu vaut NON — un
/sys/module absent, c'est un module non chargé, pas une permission.
nesting_plan reçoit ses coûts : les constantes vCPU décrivent la physique de
l'imbrication et valent pour les deux piles, les six nombres qui chiffrent un
Proxmox non. Un étage QEMU demande 2 Go et 20 Go, contre 4 et 25.
26 tests, six garde-fous morts sous mutation.
--- EN ---
The counterpart to deep_proxmox: QEMU inside QEMU. The fourth level's slowdown
comes from the PROCESSOR, but the per-level cost comes from what you install —
a Proxmox node lays down a kernel, corosync, ceph and a web UI where a libvirt
host lays down libvirtd. Together the two measurements separate what is due to
the hardware from what is due to the stack.
This test cannot merely descend. deploy_qemu.py never passes "--cpu
host-passthrough" and, when /dev/kvm is missing, it does NOT fail: it sets
"--virt-type qemu", warns on one line and creates a fully EMULATED VM — seven
and a half minutes to boot, no exit code to say so. Unguarded, the descent
would measure stacked TCG while believing it measured nesting, and return a
more flattering, false number.
Every level must therefore PROVE: /dev/kvm readable, "nested" at Y, and the
child's domain type='kvm'. What was not read counts as NO — an absent
/sys/module means an unloaded module, not a permission problem.
nesting_plan takes its costs: the vCPU constants describe the physics of
nesting and hold for both stacks, the six numbers that price a Proxmox do not.
A QEMU level asks 2 GB and 20 GB against 4 and 25.
26 tests, six guards die under mutation.
Assisted-by: claude-opus-5
(cherry picked from commit 39682cb1ce9648db91261387cae88c40c2a67837)
2026-08-28 02:43:53 -04:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
# © 2026 TechnoLibre (http://www.technolibre.ca)
|
|
|
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
|
|
|
"""Jusqu'à quel étage une QEMU dans une QEMU tient-elle ?
|
|
|
|
|
|
|
|
|
|
Le pendant de `deep_proxmox.py`, et sa raison d'être : le ralentissement du
|
|
|
|
|
quatrième étage vient du PROCESSEUR — de ce que coûte une sortie de VM sous
|
|
|
|
|
pagination imbriquée — mais le coût par étage, lui, vient de ce qu'on installe.
|
|
|
|
|
Un nœud Proxmox pose un noyau, corosync, ceph et une interface web ; un hôte
|
|
|
|
|
libvirt nu pose libvirtd et qemu-kvm. Les deux mesures ensemble séparent ce qui
|
|
|
|
|
tient au matériel de ce qui tient à la pile, deux choses que la seule mesure
|
|
|
|
|
Proxmox confond.
|
|
|
|
|
|
|
|
|
|
CE QUE CE TEST DOIT PROUVER AVANT DE MESURER
|
|
|
|
|
|
|
|
|
|
`deploy_qemu.py` ne passe jamais « --cpu host-passthrough » : il s'en remet au
|
|
|
|
|
défaut de virt-install. Et quand /dev/kvm manque, il n'échoue pas — il pose
|
|
|
|
|
« --virt-type qemu », avertit sur une ligne, et crée une VM ENTIÈREMENT ÉMULÉE.
|
|
|
|
|
Un étage émulé démarre en sept minutes et demie au lieu de quelques secondes,
|
|
|
|
|
et rien dans le code de retour ne le dit.
|
|
|
|
|
|
|
|
|
|
Sans garde, ce script mesurerait donc de la TCG empilée en croyant mesurer de
|
|
|
|
|
la virtualisation imbriquée — et rendrait un chiffre plus flatteur, et faux.
|
|
|
|
|
D'où le contrôle de chaque étage : /dev/kvm lisible, « nested » à Y, et le
|
|
|
|
|
domaine de l'enfant en type='kvm' avec un CPU host-passthrough. Un étage qui
|
|
|
|
|
échoue à cela arrête la descente au lieu de la prolonger dans le vide.
|
|
|
|
|
|
|
|
|
|
./long_test/deep_qemu.py # trois étages
|
|
|
|
|
./long_test/deep_qemu.py --depth 5 # en demander plus, sciemment
|
|
|
|
|
./long_test/deep_qemu.py --dry-run # le plan, rien de créé
|
|
|
|
|
./long_test/deep_qemu.py --detruire # défaire ce qui a été posé
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
import re
|
|
|
|
|
import shlex
|
|
|
|
|
import subprocess
|
|
|
|
|
import sys
|
[FIX] long_test : bail attendu, rallumage à froid, marge mémoire
Trois mesures faites sur une descente à cinq étages, et une conclusion de ma
part corrigée par le contre-essai.
Le bail DHCP se fait attendre. L'étage 3 était créé, en type='kvm', et
« domifaddr » ne rendait rien : l'invité n'avait pas encore demandé son
adresse — 87 s puis 94 s selon les tours, quand deploy_qemu s'accorde 90 s et
rend 0 sans l'avoir trouvée. Lu une fois, cela ne prouvait rien.
Un redémarrage demandé à l'invité peut le laisser bloqué dans son
micrologiciel : RIP immobile 46 minutes, pas un octet lu, trois vCPU à fond.
J'ai d'abord conclu que c'était la taille de la mémoire, parce que la même
machine à 2 Go démarrait. Le contre-essai à 4 Go l'a réfuté : elle démarre
aussi, à froid. La différence est le REDÉMARRAGE, pas la mémoire — à chaud
elle reste dans l'UEFI, à froid elle charge son noyau en 60 à 90 s, à 2, 3 et
4 Go. La descente rallume donc une fois par le parent, et une seule : une
boucle de rallumage cacherait un vrai échec.
La mémoire de la pile QEMU est doublée pour une autre raison, mesurée elle
aussi : l'étage 2 avec 5 Go hébergeait un invité de 4 Go et n'avait plus que
127 Mo de libre. Ce n'est pas le plancher qui compte, c'est l'écart.
--- EN ---
Three measurements from a five-level descent, and a conclusion of mine
refuted by the counter-test.
The DHCP lease takes its time. Level 3 was created, type='kvm', and
"domifaddr" returned nothing: the guest had not yet asked for its address —
87 s then 94 s depending on the run, while deploy_qemu allows itself 90 s and
returns 0 without having found it. Read once, that proved nothing.
A reboot asked of the guest can leave it stuck in its firmware: static RIP for
46 minutes, not a byte read, three vCPU at full tilt. I first concluded it was
the memory size, because the same machine booted at 2 GB. The counter-test at
4 GB refuted it: it boots too, cold. The difference is the REBOOT, not the
memory — warm it stays in UEFI, cold it loads its kernel in 60 to 90 s, at 2,
3 and 4 GB. The descent therefore power-cycles once through the parent, and
only once: a restart loop would hide a real failure.
The QEMU stack's memory is doubled for another, also measured reason: level 2
with 5 GB hosted a 4 GB guest and had 127 MB left. It is not the floor that
matters, it is the gap.
Assisted-by: claude-opus-5
(cherry picked from commit e3f60e3ddf066eb76d434bbfe6b01f2271fb1874)
2026-08-28 06:05:59 -04:00
|
|
|
import time
|
[ADD] long_test : deep_qemu, et la preuve que KVM est bien là
Le pendant de deep_proxmox : des QEMU dans des QEMU. Le ralentissement du
quatrième étage vient du PROCESSEUR, mais le coût par étage vient de ce qu'on
installe — un nœud Proxmox pose un noyau, corosync, ceph et une interface web
là où un hôte libvirt pose libvirtd. Les deux mesures ensemble séparent ce qui
tient au matériel de ce qui tient à la pile.
Ce test ne peut pas se contenter de descendre. deploy_qemu.py ne passe jamais
« --cpu host-passthrough » et, quand /dev/kvm manque, il n'échoue PAS : il pose
« --virt-type qemu », avertit sur une ligne et crée une VM entièrement ÉMULÉE —
sept minutes et demie de démarrage, aucun code de retour pour le dire. Sans
garde, la descente mesurerait de la TCG empilée en croyant mesurer de
l'imbrication, et rendrait un chiffre plus flatteur et faux.
Chaque étage doit donc PROUVER : /dev/kvm lisible, « nested » à Y, et le
domaine de l'enfant en type='kvm'. Ce qui n'a pas été lu vaut NON — un
/sys/module absent, c'est un module non chargé, pas une permission.
nesting_plan reçoit ses coûts : les constantes vCPU décrivent la physique de
l'imbrication et valent pour les deux piles, les six nombres qui chiffrent un
Proxmox non. Un étage QEMU demande 2 Go et 20 Go, contre 4 et 25.
26 tests, six garde-fous morts sous mutation.
--- EN ---
The counterpart to deep_proxmox: QEMU inside QEMU. The fourth level's slowdown
comes from the PROCESSOR, but the per-level cost comes from what you install —
a Proxmox node lays down a kernel, corosync, ceph and a web UI where a libvirt
host lays down libvirtd. Together the two measurements separate what is due to
the hardware from what is due to the stack.
This test cannot merely descend. deploy_qemu.py never passes "--cpu
host-passthrough" and, when /dev/kvm is missing, it does NOT fail: it sets
"--virt-type qemu", warns on one line and creates a fully EMULATED VM — seven
and a half minutes to boot, no exit code to say so. Unguarded, the descent
would measure stacked TCG while believing it measured nesting, and return a
more flattering, false number.
Every level must therefore PROVE: /dev/kvm readable, "nested" at Y, and the
child's domain type='kvm'. What was not read counts as NO — an absent
/sys/module means an unloaded module, not a permission problem.
nesting_plan takes its costs: the vCPU constants describe the physics of
nesting and hold for both stacks, the six numbers that price a Proxmox do not.
A QEMU level asks 2 GB and 20 GB against 4 and 25.
26 tests, six guards die under mutation.
Assisted-by: claude-opus-5
(cherry picked from commit 39682cb1ce9648db91261387cae88c40c2a67837)
2026-08-28 02:43:53 -04:00
|
|
|
|
|
|
|
|
RACINE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
|
sys.path.insert(0, RACINE)
|
|
|
|
|
sys.path.insert(0, os.path.join(RACINE, "long_test"))
|
|
|
|
|
|
|
|
|
|
from script.proxmox import nesting # noqa: E402
|
|
|
|
|
from script.proxmox import proxmox_deploy as pve # noqa: E402
|
|
|
|
|
|
|
|
|
|
import descente # noqa: E402
|
|
|
|
|
from descente import ( # noqa: E402,F401
|
|
|
|
|
DELAIS,
|
|
|
|
|
Famille,
|
|
|
|
|
a_defaire,
|
|
|
|
|
alias_etage as _alias_etage,
|
|
|
|
|
autre_descente,
|
|
|
|
|
capacite_hote,
|
|
|
|
|
cle_publique,
|
|
|
|
|
dernier_rapport,
|
|
|
|
|
descente_vivante,
|
|
|
|
|
detruire,
|
|
|
|
|
detruire_etage1,
|
|
|
|
|
dire,
|
|
|
|
|
identite_de,
|
|
|
|
|
mener,
|
|
|
|
|
module_qemu,
|
|
|
|
|
nom_etage as _nom_etage,
|
|
|
|
|
retirer_alias,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Une Debian nue : c'est elle qui recevra libvirt et qemu-kvm.
|
|
|
|
|
DISTRO = "debian"
|
|
|
|
|
NOM_BASE = "deep-qemu"
|
|
|
|
|
OUTIL = "deep_qemu"
|
|
|
|
|
|
|
|
|
|
# Le script est envoyé par scp, comme install_proxmox.sh l'est pour Proxmox :
|
|
|
|
|
# c'est NOTRE code qu'on veut éprouver, et il n'importe que la bibliothèque
|
|
|
|
|
# standard, donc il tourne dans un invité nu sans rien d'autre.
|
|
|
|
|
LOCAL_CLI = "script/qemu/deploy_qemu.py"
|
|
|
|
|
DISTANT_CLI = "/tmp/deploy_qemu.py"
|
|
|
|
|
CLE_DISTANTE = "/root/.ssh/longtest.pub"
|
|
|
|
|
|
|
|
|
|
# Trois faits, trois lignes, et l'ABSENCE d'une ligne vaut « non ». Écrit pour
|
|
|
|
|
# dash : /bin/sh sur Debian n'est pas bash.
|
|
|
|
|
CONTROLE_CMD = (
|
|
|
|
|
"if [ -r /dev/kvm ]; then echo KVM=oui; else echo KVM=non; fi; "
|
|
|
|
|
"cat /sys/module/kvm_amd/parameters/nested 2>/dev/null"
|
|
|
|
|
" | sed s/^/NESTED=/; "
|
|
|
|
|
"cat /sys/module/kvm_intel/parameters/nested 2>/dev/null"
|
|
|
|
|
" | sed s/^/NESTED=/; "
|
|
|
|
|
"df --output=avail -BG /var/lib/libvirt/images 2>/dev/null"
|
|
|
|
|
" | tail -1 | sed s/^/DISQUE=/"
|
|
|
|
|
)
|
|
|
|
|
|
[FIX] deep_qemu : listes apt, sous-réseau par étage, étape muette
Trois défauts trouvés en une heure par le premier lancement réel — c'est ce
qu'un test d'intégration doit produire.
1. « --setup-host » a échoué en ZÉRO seconde sur « Unable to locate package
qemu-system-x86 », alors que le paquet existe : la VM venait de démarrer et
ses listes ne portaient que bookworm-security. Le message envoyait chercher
des paquets, pas des listes. Même parade qu'install_proxmox.sh — arrêter
apt-daily, puis réessayer.
2. Le réseau « default » de libvirt sert 192.168.122.0/24 à TOUS les étages.
L'étage 2, dont l'adresse VENAIT de ce réseau, voyait son propre net-start
refusé : « Network is already in use by interface enp1s0 ». Un invité qui
vit dans un réseau ne peut pas servir le même. Chaque étage prend le sien,
déduit de sa profondeur absolue, et le REDÉFINIT avant de le démarrer.
3. Le mien : l'extraction du moteur avait coupé preparer_systeme sur le
« return False » de sa boucle, sans son « return True ». La fonction rendait
None, donc l'étape échouait SANS RIEN DIRE, et les deux piles étaient
cassées. L'essai à blanc ne pouvait pas le voir — il sort avant. Un test
d'AST interdit désormais qu'une étape retombe sur None.
long_test/ était introuvable hors du menu : une ligne dans CLAUDE.md, trois
entrées au CHANGELOG, deux sections au README.
--- EN ---
Three defects found in one hour by the first real run — which is what an
integration test is for.
1. "--setup-host" failed in ZERO seconds on "Unable to locate package
qemu-system-x86" though the package exists: the VM had just booted and its
lists carried only bookworm-security. The message sent us looking for
packages, not for lists. Same remedy as install_proxmox.sh — stop
apt-daily, then retry.
2. libvirt's "default" network serves 192.168.122.0/24 at EVERY level. Level 2,
whose own address CAME from that network, had its net-start refused:
"Network is already in use by interface enp1s0". A guest living inside a
network cannot serve the same one. Each level takes its own, derived from
its absolute depth, and REDEFINES it before starting it.
3. Mine: extracting the engine had cut preparer_systeme at its loop's "return
False", without the final "return True". The function returned None, so the
step failed SAYING NOTHING, and both stacks were broken. The dry run could
not see it — it exits earlier. An AST test now forbids a step from falling
through to None.
long_test/ was undiscoverable outside the menu: one line in CLAUDE.md, three
CHANGELOG entries, two README sections.
Assisted-by: claude-opus-5
(cherry picked from commit e46bad408143f7511a04ffdc6a20efdb785f4b5e)
2026-08-28 03:52:21 -04:00
|
|
|
# Les listes apt AVANT toute installation. Constaté au premier lancement
|
|
|
|
|
# réel : « --setup-host » a échoué en ZÉRO seconde sur « Unable to locate
|
|
|
|
|
# package qemu-system-x86 », alors que le paquet existe. La VM venait de
|
|
|
|
|
# démarrer, ses listes ne portaient que « bookworm-security », et un
|
|
|
|
|
# apt-get update les a complétées d'un coup.
|
|
|
|
|
#
|
|
|
|
|
# Deux causes, une seule parade : cloud-init n'a pas fini de composer
|
|
|
|
|
# /etc/apt, et apt-daily tient le verrou des listes au premier démarrage.
|
|
|
|
|
# install_proxmox.sh a la même parade, et pour la même raison — arrêter les
|
|
|
|
|
# minuteries, puis réessayer.
|
|
|
|
|
PREPARE_APT_CMD = (
|
|
|
|
|
"systemctl stop apt-daily.service apt-daily-upgrade.service"
|
|
|
|
|
" apt-daily.timer apt-daily-upgrade.timer >/dev/null 2>&1;"
|
|
|
|
|
" i=1; while [ $i -le 12 ]; do"
|
|
|
|
|
" DEBIAN_FRONTEND=noninteractive apt-get update && exit 0;"
|
|
|
|
|
" echo APT-RETRY=$i; sleep 15; i=$((i+1)); done; exit 1"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Le réseau « default » de libvirt sert 192.168.122.0/24, à TOUS les étages.
|
|
|
|
|
# Constaté au premier essai réel : l'étage 2, dont l'adresse était
|
|
|
|
|
# 192.168.122.45 — servie par le « default » de son parent — a vu son propre
|
|
|
|
|
# « net-start default » refusé net :
|
|
|
|
|
#
|
|
|
|
|
# error: internal error: Network is already in use by interface enp1s0
|
|
|
|
|
#
|
|
|
|
|
# Un invité qui vit DANS un réseau ne peut pas servir le même. Chaque étage
|
|
|
|
|
# reçoit donc son propre sous-réseau, déduit de sa PROFONDEUR : deux étages ne
|
|
|
|
|
# peuvent pas tomber sur le même, et rien n'est à deviner.
|
|
|
|
|
#
|
|
|
|
|
# 131 et au-delà : 122 est celui de libvirt et 123 celui de la machine où ce
|
|
|
|
|
# test a été écrit. Les éviter tous les deux coûte un octet.
|
|
|
|
|
RESEAU_BASE = 131
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def cidr_pour(profondeur):
|
|
|
|
|
"""Le troisième octet du sous-réseau d'un étage. Déterminé, jamais tiré."""
|
|
|
|
|
return f"192.168.{RESEAU_BASE + max(0, int(profondeur) - 1)}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def reseau_xml(prefixe, nom="default"):
|
|
|
|
|
"""Le réseau NAT d'un étage, en une ligne — pas de heredoc.
|
|
|
|
|
|
|
|
|
|
Une seule ligne parce qu'elle traverse deux couches de quoting pour
|
|
|
|
|
atterrir dans dash : un heredoc n'y survivrait pas.
|
|
|
|
|
"""
|
|
|
|
|
return (
|
|
|
|
|
f"<network><name>{nom}</name><forward mode='nat'/>"
|
|
|
|
|
f"<bridge name='virbr0' stp='on' delay='0'/>"
|
|
|
|
|
f"<ip address='{prefixe}.1' netmask='255.255.255.0'>"
|
|
|
|
|
f"<dhcp><range start='{prefixe}.10' end='{prefixe}.200'/></dhcp>"
|
|
|
|
|
f"</ip></network>"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
[ADD] long_test : deep_qemu, et la preuve que KVM est bien là
Le pendant de deep_proxmox : des QEMU dans des QEMU. Le ralentissement du
quatrième étage vient du PROCESSEUR, mais le coût par étage vient de ce qu'on
installe — un nœud Proxmox pose un noyau, corosync, ceph et une interface web
là où un hôte libvirt pose libvirtd. Les deux mesures ensemble séparent ce qui
tient au matériel de ce qui tient à la pile.
Ce test ne peut pas se contenter de descendre. deploy_qemu.py ne passe jamais
« --cpu host-passthrough » et, quand /dev/kvm manque, il n'échoue PAS : il pose
« --virt-type qemu », avertit sur une ligne et crée une VM entièrement ÉMULÉE —
sept minutes et demie de démarrage, aucun code de retour pour le dire. Sans
garde, la descente mesurerait de la TCG empilée en croyant mesurer de
l'imbrication, et rendrait un chiffre plus flatteur et faux.
Chaque étage doit donc PROUVER : /dev/kvm lisible, « nested » à Y, et le
domaine de l'enfant en type='kvm'. Ce qui n'a pas été lu vaut NON — un
/sys/module absent, c'est un module non chargé, pas une permission.
nesting_plan reçoit ses coûts : les constantes vCPU décrivent la physique de
l'imbrication et valent pour les deux piles, les six nombres qui chiffrent un
Proxmox non. Un étage QEMU demande 2 Go et 20 Go, contre 4 et 25.
26 tests, six garde-fous morts sous mutation.
--- EN ---
The counterpart to deep_proxmox: QEMU inside QEMU. The fourth level's slowdown
comes from the PROCESSOR, but the per-level cost comes from what you install —
a Proxmox node lays down a kernel, corosync, ceph and a web UI where a libvirt
host lays down libvirtd. Together the two measurements separate what is due to
the hardware from what is due to the stack.
This test cannot merely descend. deploy_qemu.py never passes "--cpu
host-passthrough" and, when /dev/kvm is missing, it does NOT fail: it sets
"--virt-type qemu", warns on one line and creates a fully EMULATED VM — seven
and a half minutes to boot, no exit code to say so. Unguarded, the descent
would measure stacked TCG while believing it measured nesting, and return a
more flattering, false number.
Every level must therefore PROVE: /dev/kvm readable, "nested" at Y, and the
child's domain type='kvm'. What was not read counts as NO — an absent
/sys/module means an unloaded module, not a permission problem.
nesting_plan takes its costs: the vCPU constants describe the physics of
nesting and hold for both stacks, the six numbers that price a Proxmox do not.
A QEMU level asks 2 GB and 20 GB against 4 and 25.
26 tests, six guards die under mutation.
Assisted-by: claude-opus-5
(cherry picked from commit 39682cb1ce9648db91261387cae88c40c2a67837)
2026-08-28 02:43:53 -04:00
|
|
|
RESEAU_CMD = (
|
|
|
|
|
"virsh -c qemu:///system net-info default 2>&1 | sed s/^/NET:/; "
|
|
|
|
|
"systemctl is-active libvirtd 2>/dev/null | sed s/^/UNITE:/"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def nom_etage(niveau):
|
|
|
|
|
return _nom_etage(niveau, NOM_BASE)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def alias_etage(niveau, parent_alias):
|
|
|
|
|
return _alias_etage(niveau, parent_alias, NOM_BASE)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_controle(texte):
|
|
|
|
|
"""Ce que le contrôle a VU. Ce qui n'a pas été lu vaut « non ».
|
|
|
|
|
|
|
|
|
|
L'absence d'une ligne n'est jamais un oui : si
|
|
|
|
|
/sys/module/kvm_amd/parameters/nested n'existe pas, c'est que le module
|
|
|
|
|
n'est pas chargé, et l'étage suivant serait émulé.
|
|
|
|
|
"""
|
|
|
|
|
propre = pve.strip_ssh_noise(texte or "")
|
|
|
|
|
nested = re.search(r"^NESTED=(\S+)", propre, re.M)
|
|
|
|
|
disque = re.search(r"^DISQUE=\s*(\d+)", propre, re.M)
|
|
|
|
|
return {
|
|
|
|
|
"kvm": bool(re.search(r"^KVM=oui\s*$", propre, re.M)),
|
|
|
|
|
# « Y » ou « 1 » selon les versions du module.
|
|
|
|
|
"nested": bool(nested and nested.group(1).strip() in ("Y", "1")),
|
|
|
|
|
"disque_go": int(disque.group(1)) if disque else 0,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_reseau(texte):
|
|
|
|
|
"""Le réseau libvirt « default » est-il actif, et libvirtd debout ?"""
|
|
|
|
|
propre = pve.strip_ssh_noise(texte or "")
|
|
|
|
|
actif = re.search(r"^NET:\s*Active:\s*(\S+)", propre, re.M | re.I)
|
|
|
|
|
unite = re.search(r"^UNITE:(\S+)", propre, re.M)
|
|
|
|
|
return {
|
|
|
|
|
"reseau": bool(actif and actif.group(1).lower() == "yes"),
|
|
|
|
|
"libvirtd": bool(unite and unite.group(1).strip() == "active"),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_domifaddr(texte):
|
|
|
|
|
"""La première adresse IPv4 d'un domaine, sans son masque, ou "".
|
|
|
|
|
|
|
|
|
|
virsh écrit un tableau ; on ne prend que les lignes qui annoncent « ipv4 »,
|
|
|
|
|
et jamais la ligne d'en-tête ni les tirets.
|
|
|
|
|
"""
|
|
|
|
|
for ligne in pve.strip_ssh_noise(texte or "").splitlines():
|
|
|
|
|
champs = ligne.split()
|
|
|
|
|
if len(champs) >= 4 and champs[2].lower() == "ipv4":
|
|
|
|
|
return champs[3].split("/")[0]
|
|
|
|
|
return ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_domaine(xml):
|
|
|
|
|
"""Le domaine tourne-t-il sous KVM, et son CPU passe-t-il l'hôte ?
|
|
|
|
|
|
|
|
|
|
Les deux comptent, et pour la même raison : un domaine type='qemu' est
|
|
|
|
|
ÉMULÉ, et un CPU qui ne passe pas les drapeaux de l'hôte ne porte pas la
|
|
|
|
|
virtualisation — l'étage suivant serait émulé à son tour, sept minutes et
|
|
|
|
|
demie de démarrage, sans qu'aucun code de retour ne le dise.
|
|
|
|
|
"""
|
|
|
|
|
propre = pve.strip_ssh_noise(xml or "")
|
|
|
|
|
domaine = re.search(r"<domain[^>]*\btype=['\"](\w+)['\"]", propre)
|
|
|
|
|
cpu = re.search(r"<cpu[^>]*\bmode=['\"]([\w-]+)['\"]", propre)
|
|
|
|
|
return {
|
|
|
|
|
"type": domaine.group(1) if domaine else "",
|
|
|
|
|
"cpu": cpu.group(1) if cpu else "",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Descente(descente.Descente):
|
|
|
|
|
"""Les verbes de QEMU/KVM. Le reste est dans `descente.Descente`."""
|
|
|
|
|
|
|
|
|
|
OUTIL = OUTIL
|
|
|
|
|
NOM_BASE = NOM_BASE
|
|
|
|
|
DISTRO = DISTRO
|
|
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------ #
|
|
|
|
|
def _envoyer_cli(self, hote):
|
|
|
|
|
"""Pose NOTRE deploy_qemu.py sur l'hôte. Rend True s'il y est.
|
|
|
|
|
|
|
|
|
|
Refait à chaque besoin plutôt qu'une fois : /tmp est vidé au
|
|
|
|
|
démarrage sur bien des systèmes, et l'installation redémarre.
|
|
|
|
|
"""
|
|
|
|
|
local = os.path.join(RACINE, LOCAL_CLI)
|
|
|
|
|
if self.dry_run:
|
|
|
|
|
print(f" scp {local} <hôte>:{DISTANT_CLI}")
|
|
|
|
|
return True
|
|
|
|
|
argv = pve.ssh_argv(hote, "")[:-1] # les options, sans la commande
|
|
|
|
|
cible = argv[-1]
|
|
|
|
|
options = argv[1:-1]
|
|
|
|
|
res = subprocess.run(
|
|
|
|
|
["scp", "-q"] + options + [local, f"{cible}:{DISTANT_CLI}"],
|
|
|
|
|
capture_output=True,
|
|
|
|
|
text=True,
|
|
|
|
|
timeout=300,
|
|
|
|
|
)
|
|
|
|
|
if res.returncode:
|
|
|
|
|
self.dire(f" ✗ scp : {res.stderr.strip()[:200]}")
|
|
|
|
|
return False
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def installer(self, hote):
|
|
|
|
|
"""libvirt et qemu-kvm, par « --setup-host ».
|
|
|
|
|
|
|
|
|
|
Le code de retour ne prouve RIEN ici : « --setup-host » rend 0 même
|
|
|
|
|
quand il s'est contenté de PROGRAMMER un redémarrage. C'est l'étape
|
|
|
|
|
suivante — redémarrer et constater que la machine est revenue — qui
|
|
|
|
|
prouve quelque chose, et le contrôle de fin d'étage qui prouve que KVM
|
|
|
|
|
est là.
|
|
|
|
|
"""
|
|
|
|
|
if self.dry_run:
|
|
|
|
|
print(f" {DISTANT_CLI} --setup-host")
|
|
|
|
|
return True
|
|
|
|
|
if not self._envoyer_cli(hote):
|
|
|
|
|
return False
|
[FIX] deep_qemu : listes apt, sous-réseau par étage, étape muette
Trois défauts trouvés en une heure par le premier lancement réel — c'est ce
qu'un test d'intégration doit produire.
1. « --setup-host » a échoué en ZÉRO seconde sur « Unable to locate package
qemu-system-x86 », alors que le paquet existe : la VM venait de démarrer et
ses listes ne portaient que bookworm-security. Le message envoyait chercher
des paquets, pas des listes. Même parade qu'install_proxmox.sh — arrêter
apt-daily, puis réessayer.
2. Le réseau « default » de libvirt sert 192.168.122.0/24 à TOUS les étages.
L'étage 2, dont l'adresse VENAIT de ce réseau, voyait son propre net-start
refusé : « Network is already in use by interface enp1s0 ». Un invité qui
vit dans un réseau ne peut pas servir le même. Chaque étage prend le sien,
déduit de sa profondeur absolue, et le REDÉFINIT avant de le démarrer.
3. Le mien : l'extraction du moteur avait coupé preparer_systeme sur le
« return False » de sa boucle, sans son « return True ». La fonction rendait
None, donc l'étape échouait SANS RIEN DIRE, et les deux piles étaient
cassées. L'essai à blanc ne pouvait pas le voir — il sort avant. Un test
d'AST interdit désormais qu'une étape retombe sur None.
long_test/ était introuvable hors du menu : une ligne dans CLAUDE.md, trois
entrées au CHANGELOG, deux sections au README.
--- EN ---
Three defects found in one hour by the first real run — which is what an
integration test is for.
1. "--setup-host" failed in ZERO seconds on "Unable to locate package
qemu-system-x86" though the package exists: the VM had just booted and its
lists carried only bookworm-security. The message sent us looking for
packages, not for lists. Same remedy as install_proxmox.sh — stop
apt-daily, then retry.
2. libvirt's "default" network serves 192.168.122.0/24 at EVERY level. Level 2,
whose own address CAME from that network, had its net-start refused:
"Network is already in use by interface enp1s0". A guest living inside a
network cannot serve the same one. Each level takes its own, derived from
its absolute depth, and REDEFINES it before starting it.
3. Mine: extracting the engine had cut preparer_systeme at its loop's "return
False", without the final "return True". The function returned None, so the
step failed SAYING NOTHING, and both stacks were broken. The dry run could
not see it — it exits earlier. An AST test now forbids a step from falling
through to None.
long_test/ was undiscoverable outside the menu: one line in CLAUDE.md, three
CHANGELOG entries, two README sections.
Assisted-by: claude-opus-5
(cherry picked from commit e46bad408143f7511a04ffdc6a20efdb785f4b5e)
2026-08-28 03:52:21 -04:00
|
|
|
# Les listes d'abord. Sans elles, « --setup-host » échoue en zéro
|
|
|
|
|
# seconde sur des paquets qui existent — et le message parle de
|
|
|
|
|
# paquets introuvables, pas de listes vides.
|
|
|
|
|
code, _o = self.executer(
|
|
|
|
|
hote,
|
|
|
|
|
PREPARE_APT_CMD,
|
|
|
|
|
self.delai("install"),
|
|
|
|
|
"apt-get update",
|
|
|
|
|
)
|
|
|
|
|
if code:
|
|
|
|
|
self.dire(" ✗ listes apt : le verrou reste tenu")
|
|
|
|
|
return False
|
[ADD] long_test : deep_qemu, et la preuve que KVM est bien là
Le pendant de deep_proxmox : des QEMU dans des QEMU. Le ralentissement du
quatrième étage vient du PROCESSEUR, mais le coût par étage vient de ce qu'on
installe — un nœud Proxmox pose un noyau, corosync, ceph et une interface web
là où un hôte libvirt pose libvirtd. Les deux mesures ensemble séparent ce qui
tient au matériel de ce qui tient à la pile.
Ce test ne peut pas se contenter de descendre. deploy_qemu.py ne passe jamais
« --cpu host-passthrough » et, quand /dev/kvm manque, il n'échoue PAS : il pose
« --virt-type qemu », avertit sur une ligne et crée une VM entièrement ÉMULÉE —
sept minutes et demie de démarrage, aucun code de retour pour le dire. Sans
garde, la descente mesurerait de la TCG empilée en croyant mesurer de
l'imbrication, et rendrait un chiffre plus flatteur et faux.
Chaque étage doit donc PROUVER : /dev/kvm lisible, « nested » à Y, et le
domaine de l'enfant en type='kvm'. Ce qui n'a pas été lu vaut NON — un
/sys/module absent, c'est un module non chargé, pas une permission.
nesting_plan reçoit ses coûts : les constantes vCPU décrivent la physique de
l'imbrication et valent pour les deux piles, les six nombres qui chiffrent un
Proxmox non. Un étage QEMU demande 2 Go et 20 Go, contre 4 et 25.
26 tests, six garde-fous morts sous mutation.
--- EN ---
The counterpart to deep_proxmox: QEMU inside QEMU. The fourth level's slowdown
comes from the PROCESSOR, but the per-level cost comes from what you install —
a Proxmox node lays down a kernel, corosync, ceph and a web UI where a libvirt
host lays down libvirtd. Together the two measurements separate what is due to
the hardware from what is due to the stack.
This test cannot merely descend. deploy_qemu.py never passes "--cpu
host-passthrough" and, when /dev/kvm is missing, it does NOT fail: it sets
"--virt-type qemu", warns on one line and creates a fully EMULATED VM — seven
and a half minutes to boot, no exit code to say so. Unguarded, the descent
would measure stacked TCG while believing it measured nesting, and return a
more flattering, false number.
Every level must therefore PROVE: /dev/kvm readable, "nested" at Y, and the
child's domain type='kvm'. What was not read counts as NO — an absent
/sys/module means an unloaded module, not a permission problem.
nesting_plan takes its costs: the vCPU constants describe the physics of
nesting and hold for both stacks, the six numbers that price a Proxmox do not.
A QEMU level asks 2 GB and 20 GB against 4 and 25.
26 tests, six guards die under mutation.
Assisted-by: claude-opus-5
(cherry picked from commit 39682cb1ce9648db91261387cae88c40c2a67837)
2026-08-28 02:43:53 -04:00
|
|
|
code, _o = self.executer(
|
|
|
|
|
hote,
|
|
|
|
|
f"python3 {DISTANT_CLI} --setup-host --assume-yes",
|
|
|
|
|
self.delai("install"),
|
|
|
|
|
"deploy_qemu --setup-host",
|
|
|
|
|
montrer=True,
|
|
|
|
|
)
|
|
|
|
|
return code == 0
|
|
|
|
|
|
|
|
|
|
def noyau_convient(self, noyau):
|
|
|
|
|
"""Tout noyau convient : c'est le REDÉMARRAGE qui compte, pas ce
|
|
|
|
|
qu'on redémarre.
|
|
|
|
|
|
|
|
|
|
Il charge les modules KVM et applique l'appartenance au groupe
|
|
|
|
|
libvirt, qui ne prend effet qu'à la SESSION SUIVANTE — sans lui,
|
|
|
|
|
virt-install retombe sur qemu:///session, où le réseau « default »
|
|
|
|
|
n'existe pas. Le moteur prouve déjà que la machine a vraiment
|
|
|
|
|
redémarré en comparant l'instant de démarrage.
|
|
|
|
|
"""
|
|
|
|
|
return bool(noyau)
|
|
|
|
|
|
|
|
|
|
def remettre_debout(self, hote):
|
|
|
|
|
"""libvirtd actif et le réseau « default » démarré."""
|
|
|
|
|
if self.dry_run:
|
|
|
|
|
print(" libvirtd + réseau default")
|
|
|
|
|
return True
|
[FIX] deep_qemu : listes apt, sous-réseau par étage, étape muette
Trois défauts trouvés en une heure par le premier lancement réel — c'est ce
qu'un test d'intégration doit produire.
1. « --setup-host » a échoué en ZÉRO seconde sur « Unable to locate package
qemu-system-x86 », alors que le paquet existe : la VM venait de démarrer et
ses listes ne portaient que bookworm-security. Le message envoyait chercher
des paquets, pas des listes. Même parade qu'install_proxmox.sh — arrêter
apt-daily, puis réessayer.
2. Le réseau « default » de libvirt sert 192.168.122.0/24 à TOUS les étages.
L'étage 2, dont l'adresse VENAIT de ce réseau, voyait son propre net-start
refusé : « Network is already in use by interface enp1s0 ». Un invité qui
vit dans un réseau ne peut pas servir le même. Chaque étage prend le sien,
déduit de sa profondeur absolue, et le REDÉFINIT avant de le démarrer.
3. Le mien : l'extraction du moteur avait coupé preparer_systeme sur le
« return False » de sa boucle, sans son « return True ». La fonction rendait
None, donc l'étape échouait SANS RIEN DIRE, et les deux piles étaient
cassées. L'essai à blanc ne pouvait pas le voir — il sort avant. Un test
d'AST interdit désormais qu'une étape retombe sur None.
long_test/ était introuvable hors du menu : une ligne dans CLAUDE.md, trois
entrées au CHANGELOG, deux sections au README.
--- EN ---
Three defects found in one hour by the first real run — which is what an
integration test is for.
1. "--setup-host" failed in ZERO seconds on "Unable to locate package
qemu-system-x86" though the package exists: the VM had just booted and its
lists carried only bookworm-security. The message sent us looking for
packages, not for lists. Same remedy as install_proxmox.sh — stop
apt-daily, then retry.
2. libvirt's "default" network serves 192.168.122.0/24 at EVERY level. Level 2,
whose own address CAME from that network, had its net-start refused:
"Network is already in use by interface enp1s0". A guest living inside a
network cannot serve the same one. Each level takes its own, derived from
its absolute depth, and REDEFINES it before starting it.
3. Mine: extracting the engine had cut preparer_systeme at its loop's "return
False", without the final "return True". The function returned None, so the
step failed SAYING NOTHING, and both stacks were broken. The dry run could
not see it — it exits earlier. An AST test now forbids a step from falling
through to None.
long_test/ was undiscoverable outside the menu: one line in CLAUDE.md, three
CHANGELOG entries, two README sections.
Assisted-by: claude-opus-5
(cherry picked from commit e46bad408143f7511a04ffdc6a20efdb785f4b5e)
2026-08-28 03:52:21 -04:00
|
|
|
# Son sous-réseau à LUI, sinon « net-start default » se heurte à
|
|
|
|
|
# l'adresse que son parent lui a servie.
|
|
|
|
|
profondeur = self.profondeur_racine + max(1, self.niveau_courant)
|
|
|
|
|
xml = reseau_xml(cidr_pour(profondeur))
|
[ADD] long_test : deep_qemu, et la preuve que KVM est bien là
Le pendant de deep_proxmox : des QEMU dans des QEMU. Le ralentissement du
quatrième étage vient du PROCESSEUR, mais le coût par étage vient de ce qu'on
installe — un nœud Proxmox pose un noyau, corosync, ceph et une interface web
là où un hôte libvirt pose libvirtd. Les deux mesures ensemble séparent ce qui
tient au matériel de ce qui tient à la pile.
Ce test ne peut pas se contenter de descendre. deploy_qemu.py ne passe jamais
« --cpu host-passthrough » et, quand /dev/kvm manque, il n'échoue PAS : il pose
« --virt-type qemu », avertit sur une ligne et crée une VM entièrement ÉMULÉE —
sept minutes et demie de démarrage, aucun code de retour pour le dire. Sans
garde, la descente mesurerait de la TCG empilée en croyant mesurer de
l'imbrication, et rendrait un chiffre plus flatteur et faux.
Chaque étage doit donc PROUVER : /dev/kvm lisible, « nested » à Y, et le
domaine de l'enfant en type='kvm'. Ce qui n'a pas été lu vaut NON — un
/sys/module absent, c'est un module non chargé, pas une permission.
nesting_plan reçoit ses coûts : les constantes vCPU décrivent la physique de
l'imbrication et valent pour les deux piles, les six nombres qui chiffrent un
Proxmox non. Un étage QEMU demande 2 Go et 20 Go, contre 4 et 25.
26 tests, six garde-fous morts sous mutation.
--- EN ---
The counterpart to deep_proxmox: QEMU inside QEMU. The fourth level's slowdown
comes from the PROCESSOR, but the per-level cost comes from what you install —
a Proxmox node lays down a kernel, corosync, ceph and a web UI where a libvirt
host lays down libvirtd. Together the two measurements separate what is due to
the hardware from what is due to the stack.
This test cannot merely descend. deploy_qemu.py never passes "--cpu
host-passthrough" and, when /dev/kvm is missing, it does NOT fail: it sets
"--virt-type qemu", warns on one line and creates a fully EMULATED VM — seven
and a half minutes to boot, no exit code to say so. Unguarded, the descent
would measure stacked TCG while believing it measured nesting, and return a
more flattering, false number.
Every level must therefore PROVE: /dev/kvm readable, "nested" at Y, and the
child's domain type='kvm'. What was not read counts as NO — an absent
/sys/module means an unloaded module, not a permission problem.
nesting_plan takes its costs: the vCPU constants describe the physics of
nesting and hold for both stacks, the six numbers that price a Proxmox do not.
A QEMU level asks 2 GB and 20 GB against 4 and 25.
26 tests, six guards die under mutation.
Assisted-by: claude-opus-5
(cherry picked from commit 39682cb1ce9648db91261387cae88c40c2a67837)
2026-08-28 02:43:53 -04:00
|
|
|
self.executer(
|
|
|
|
|
hote,
|
|
|
|
|
"systemctl enable --now libvirtd 2>/dev/null;"
|
[FIX] deep_qemu : listes apt, sous-réseau par étage, étape muette
Trois défauts trouvés en une heure par le premier lancement réel — c'est ce
qu'un test d'intégration doit produire.
1. « --setup-host » a échoué en ZÉRO seconde sur « Unable to locate package
qemu-system-x86 », alors que le paquet existe : la VM venait de démarrer et
ses listes ne portaient que bookworm-security. Le message envoyait chercher
des paquets, pas des listes. Même parade qu'install_proxmox.sh — arrêter
apt-daily, puis réessayer.
2. Le réseau « default » de libvirt sert 192.168.122.0/24 à TOUS les étages.
L'étage 2, dont l'adresse VENAIT de ce réseau, voyait son propre net-start
refusé : « Network is already in use by interface enp1s0 ». Un invité qui
vit dans un réseau ne peut pas servir le même. Chaque étage prend le sien,
déduit de sa profondeur absolue, et le REDÉFINIT avant de le démarrer.
3. Le mien : l'extraction du moteur avait coupé preparer_systeme sur le
« return False » de sa boucle, sans son « return True ». La fonction rendait
None, donc l'étape échouait SANS RIEN DIRE, et les deux piles étaient
cassées. L'essai à blanc ne pouvait pas le voir — il sort avant. Un test
d'AST interdit désormais qu'une étape retombe sur None.
long_test/ était introuvable hors du menu : une ligne dans CLAUDE.md, trois
entrées au CHANGELOG, deux sections au README.
--- EN ---
Three defects found in one hour by the first real run — which is what an
integration test is for.
1. "--setup-host" failed in ZERO seconds on "Unable to locate package
qemu-system-x86" though the package exists: the VM had just booted and its
lists carried only bookworm-security. The message sent us looking for
packages, not for lists. Same remedy as install_proxmox.sh — stop
apt-daily, then retry.
2. libvirt's "default" network serves 192.168.122.0/24 at EVERY level. Level 2,
whose own address CAME from that network, had its net-start refused:
"Network is already in use by interface enp1s0". A guest living inside a
network cannot serve the same one. Each level takes its own, derived from
its absolute depth, and REDEFINES it before starting it.
3. Mine: extracting the engine had cut preparer_systeme at its loop's "return
False", without the final "return True". The function returned None, so the
step failed SAYING NOTHING, and both stacks were broken. The dry run could
not see it — it exits earlier. An AST test now forbids a step from falling
through to None.
long_test/ was undiscoverable outside the menu: one line in CLAUDE.md, three
CHANGELOG entries, two README sections.
Assisted-by: claude-opus-5
(cherry picked from commit e46bad408143f7511a04ffdc6a20efdb785f4b5e)
2026-08-28 03:52:21 -04:00
|
|
|
" virsh -c qemu:///system net-destroy default 2>/dev/null;"
|
|
|
|
|
" virsh -c qemu:///system net-undefine default 2>/dev/null;"
|
|
|
|
|
f" printf '%s' {shlex.quote(xml)} > /tmp/reseau.xml;"
|
|
|
|
|
" virsh -c qemu:///system net-define /tmp/reseau.xml;"
|
|
|
|
|
" virsh -c qemu:///system net-start default;"
|
|
|
|
|
" virsh -c qemu:///system net-autostart default; true",
|
[ADD] long_test : deep_qemu, et la preuve que KVM est bien là
Le pendant de deep_proxmox : des QEMU dans des QEMU. Le ralentissement du
quatrième étage vient du PROCESSEUR, mais le coût par étage vient de ce qu'on
installe — un nœud Proxmox pose un noyau, corosync, ceph et une interface web
là où un hôte libvirt pose libvirtd. Les deux mesures ensemble séparent ce qui
tient au matériel de ce qui tient à la pile.
Ce test ne peut pas se contenter de descendre. deploy_qemu.py ne passe jamais
« --cpu host-passthrough » et, quand /dev/kvm manque, il n'échoue PAS : il pose
« --virt-type qemu », avertit sur une ligne et crée une VM entièrement ÉMULÉE —
sept minutes et demie de démarrage, aucun code de retour pour le dire. Sans
garde, la descente mesurerait de la TCG empilée en croyant mesurer de
l'imbrication, et rendrait un chiffre plus flatteur et faux.
Chaque étage doit donc PROUVER : /dev/kvm lisible, « nested » à Y, et le
domaine de l'enfant en type='kvm'. Ce qui n'a pas été lu vaut NON — un
/sys/module absent, c'est un module non chargé, pas une permission.
nesting_plan reçoit ses coûts : les constantes vCPU décrivent la physique de
l'imbrication et valent pour les deux piles, les six nombres qui chiffrent un
Proxmox non. Un étage QEMU demande 2 Go et 20 Go, contre 4 et 25.
26 tests, six garde-fous morts sous mutation.
--- EN ---
The counterpart to deep_proxmox: QEMU inside QEMU. The fourth level's slowdown
comes from the PROCESSOR, but the per-level cost comes from what you install —
a Proxmox node lays down a kernel, corosync, ceph and a web UI where a libvirt
host lays down libvirtd. Together the two measurements separate what is due to
the hardware from what is due to the stack.
This test cannot merely descend. deploy_qemu.py never passes "--cpu
host-passthrough" and, when /dev/kvm is missing, it does NOT fail: it sets
"--virt-type qemu", warns on one line and creates a fully EMULATED VM — seven
and a half minutes to boot, no exit code to say so. Unguarded, the descent
would measure stacked TCG while believing it measured nesting, and return a
more flattering, false number.
Every level must therefore PROVE: /dev/kvm readable, "nested" at Y, and the
child's domain type='kvm'. What was not read counts as NO — an absent
/sys/module means an unloaded module, not a permission problem.
nesting_plan takes its costs: the vCPU constants describe the physics of
nesting and hold for both stacks, the six numbers that price a Proxmox do not.
A QEMU level asks 2 GB and 20 GB against 4 and 25.
26 tests, six guards die under mutation.
Assisted-by: claude-opus-5
(cherry picked from commit 39682cb1ce9648db91261387cae88c40c2a67837)
2026-08-28 02:43:53 -04:00
|
|
|
self.delai("reparation"),
|
|
|
|
|
"libvirtd",
|
|
|
|
|
)
|
[FIX] deep_qemu : listes apt, sous-réseau par étage, étape muette
Trois défauts trouvés en une heure par le premier lancement réel — c'est ce
qu'un test d'intégration doit produire.
1. « --setup-host » a échoué en ZÉRO seconde sur « Unable to locate package
qemu-system-x86 », alors que le paquet existe : la VM venait de démarrer et
ses listes ne portaient que bookworm-security. Le message envoyait chercher
des paquets, pas des listes. Même parade qu'install_proxmox.sh — arrêter
apt-daily, puis réessayer.
2. Le réseau « default » de libvirt sert 192.168.122.0/24 à TOUS les étages.
L'étage 2, dont l'adresse VENAIT de ce réseau, voyait son propre net-start
refusé : « Network is already in use by interface enp1s0 ». Un invité qui
vit dans un réseau ne peut pas servir le même. Chaque étage prend le sien,
déduit de sa profondeur absolue, et le REDÉFINIT avant de le démarrer.
3. Le mien : l'extraction du moteur avait coupé preparer_systeme sur le
« return False » de sa boucle, sans son « return True ». La fonction rendait
None, donc l'étape échouait SANS RIEN DIRE, et les deux piles étaient
cassées. L'essai à blanc ne pouvait pas le voir — il sort avant. Un test
d'AST interdit désormais qu'une étape retombe sur None.
long_test/ était introuvable hors du menu : une ligne dans CLAUDE.md, trois
entrées au CHANGELOG, deux sections au README.
--- EN ---
Three defects found in one hour by the first real run — which is what an
integration test is for.
1. "--setup-host" failed in ZERO seconds on "Unable to locate package
qemu-system-x86" though the package exists: the VM had just booted and its
lists carried only bookworm-security. The message sent us looking for
packages, not for lists. Same remedy as install_proxmox.sh — stop
apt-daily, then retry.
2. libvirt's "default" network serves 192.168.122.0/24 at EVERY level. Level 2,
whose own address CAME from that network, had its net-start refused:
"Network is already in use by interface enp1s0". A guest living inside a
network cannot serve the same one. Each level takes its own, derived from
its absolute depth, and REDEFINES it before starting it.
3. Mine: extracting the engine had cut preparer_systeme at its loop's "return
False", without the final "return True". The function returned None, so the
step failed SAYING NOTHING, and both stacks were broken. The dry run could
not see it — it exits earlier. An AST test now forbids a step from falling
through to None.
long_test/ was undiscoverable outside the menu: one line in CLAUDE.md, three
CHANGELOG entries, two README sections.
Assisted-by: claude-opus-5
(cherry picked from commit e46bad408143f7511a04ffdc6a20efdb785f4b5e)
2026-08-28 03:52:21 -04:00
|
|
|
self.dire(f" réseau {cidr_pour(profondeur)}.0/24")
|
[ADD] long_test : deep_qemu, et la preuve que KVM est bien là
Le pendant de deep_proxmox : des QEMU dans des QEMU. Le ralentissement du
quatrième étage vient du PROCESSEUR, mais le coût par étage vient de ce qu'on
installe — un nœud Proxmox pose un noyau, corosync, ceph et une interface web
là où un hôte libvirt pose libvirtd. Les deux mesures ensemble séparent ce qui
tient au matériel de ce qui tient à la pile.
Ce test ne peut pas se contenter de descendre. deploy_qemu.py ne passe jamais
« --cpu host-passthrough » et, quand /dev/kvm manque, il n'échoue PAS : il pose
« --virt-type qemu », avertit sur une ligne et crée une VM entièrement ÉMULÉE —
sept minutes et demie de démarrage, aucun code de retour pour le dire. Sans
garde, la descente mesurerait de la TCG empilée en croyant mesurer de
l'imbrication, et rendrait un chiffre plus flatteur et faux.
Chaque étage doit donc PROUVER : /dev/kvm lisible, « nested » à Y, et le
domaine de l'enfant en type='kvm'. Ce qui n'a pas été lu vaut NON — un
/sys/module absent, c'est un module non chargé, pas une permission.
nesting_plan reçoit ses coûts : les constantes vCPU décrivent la physique de
l'imbrication et valent pour les deux piles, les six nombres qui chiffrent un
Proxmox non. Un étage QEMU demande 2 Go et 20 Go, contre 4 et 25.
26 tests, six garde-fous morts sous mutation.
--- EN ---
The counterpart to deep_proxmox: QEMU inside QEMU. The fourth level's slowdown
comes from the PROCESSOR, but the per-level cost comes from what you install —
a Proxmox node lays down a kernel, corosync, ceph and a web UI where a libvirt
host lays down libvirtd. Together the two measurements separate what is due to
the hardware from what is due to the stack.
This test cannot merely descend. deploy_qemu.py never passes "--cpu
host-passthrough" and, when /dev/kvm is missing, it does NOT fail: it sets
"--virt-type qemu", warns on one line and creates a fully EMULATED VM — seven
and a half minutes to boot, no exit code to say so. Unguarded, the descent
would measure stacked TCG while believing it measured nesting, and return a
more flattering, false number.
Every level must therefore PROVE: /dev/kvm readable, "nested" at Y, and the
child's domain type='kvm'. What was not read counts as NO — an absent
/sys/module means an unloaded module, not a permission problem.
nesting_plan takes its costs: the vCPU constants describe the physics of
nesting and hold for both stacks, the six numbers that price a Proxmox do not.
A QEMU level asks 2 GB and 20 GB against 4 and 25.
26 tests, six guards die under mutation.
Assisted-by: claude-opus-5
(cherry picked from commit 39682cb1ce9648db91261387cae88c40c2a67837)
2026-08-28 02:43:53 -04:00
|
|
|
_c, out = self.executer(
|
|
|
|
|
hote, RESEAU_CMD, self.delai("controle"), "réseau"
|
|
|
|
|
)
|
|
|
|
|
vu = parse_reseau(out)
|
|
|
|
|
self.dire(
|
|
|
|
|
f" libvirtd {'actif' if vu['libvirtd'] else 'ABSENT'},"
|
|
|
|
|
f" réseau default {'actif' if vu['reseau'] else 'ABSENT'}"
|
|
|
|
|
)
|
|
|
|
|
return vu["libvirtd"] and vu["reseau"]
|
|
|
|
|
|
[FIX] long_test : bail attendu, rallumage à froid, marge mémoire
Trois mesures faites sur une descente à cinq étages, et une conclusion de ma
part corrigée par le contre-essai.
Le bail DHCP se fait attendre. L'étage 3 était créé, en type='kvm', et
« domifaddr » ne rendait rien : l'invité n'avait pas encore demandé son
adresse — 87 s puis 94 s selon les tours, quand deploy_qemu s'accorde 90 s et
rend 0 sans l'avoir trouvée. Lu une fois, cela ne prouvait rien.
Un redémarrage demandé à l'invité peut le laisser bloqué dans son
micrologiciel : RIP immobile 46 minutes, pas un octet lu, trois vCPU à fond.
J'ai d'abord conclu que c'était la taille de la mémoire, parce que la même
machine à 2 Go démarrait. Le contre-essai à 4 Go l'a réfuté : elle démarre
aussi, à froid. La différence est le REDÉMARRAGE, pas la mémoire — à chaud
elle reste dans l'UEFI, à froid elle charge son noyau en 60 à 90 s, à 2, 3 et
4 Go. La descente rallume donc une fois par le parent, et une seule : une
boucle de rallumage cacherait un vrai échec.
La mémoire de la pile QEMU est doublée pour une autre raison, mesurée elle
aussi : l'étage 2 avec 5 Go hébergeait un invité de 4 Go et n'avait plus que
127 Mo de libre. Ce n'est pas le plancher qui compte, c'est l'écart.
--- EN ---
Three measurements from a five-level descent, and a conclusion of mine
refuted by the counter-test.
The DHCP lease takes its time. Level 3 was created, type='kvm', and
"domifaddr" returned nothing: the guest had not yet asked for its address —
87 s then 94 s depending on the run, while deploy_qemu allows itself 90 s and
returns 0 without having found it. Read once, that proved nothing.
A reboot asked of the guest can leave it stuck in its firmware: static RIP for
46 minutes, not a byte read, three vCPU at full tilt. I first concluded it was
the memory size, because the same machine booted at 2 GB. The counter-test at
4 GB refuted it: it boots too, cold. The difference is the REBOOT, not the
memory — warm it stays in UEFI, cold it loads its kernel in 60 to 90 s, at 2,
3 and 4 GB. The descent therefore power-cycles once through the parent, and
only once: a restart loop would hide a real failure.
The QEMU stack's memory is doubled for another, also measured reason: level 2
with 5 GB hosted a 4 GB guest and had 127 MB left. It is not the floor that
matters, it is the gap.
Assisted-by: claude-opus-5
(cherry picked from commit e3f60e3ddf066eb76d434bbfe6b01f2271fb1874)
2026-08-28 06:05:59 -04:00
|
|
|
def rallumer_a_froid(self, parent, nom):
|
|
|
|
|
"""« virsh destroy » puis « start » : un processus QEMU neuf.
|
|
|
|
|
|
|
|
|
|
Mesuré sur la machine bloquée : à chaud elle restait 46 minutes au
|
|
|
|
|
même pointeur d'instruction, dans son micrologiciel ; à froid elle a
|
|
|
|
|
chargé son noyau en 60 à 90 secondes, trois fois de suite, à 2, 3 et
|
|
|
|
|
4 Go. Ce n'est donc pas la taille de la mémoire — c'est la façon de
|
|
|
|
|
redémarrer.
|
|
|
|
|
"""
|
|
|
|
|
if self.dry_run or not parent:
|
|
|
|
|
return False
|
|
|
|
|
self.executer(
|
|
|
|
|
parent,
|
|
|
|
|
f"virsh -c qemu:///system destroy {nom} 2>/dev/null; true",
|
|
|
|
|
self.delai("controle"),
|
|
|
|
|
"extinction",
|
|
|
|
|
)
|
|
|
|
|
code, _o = self.executer(
|
|
|
|
|
parent,
|
|
|
|
|
f"virsh -c qemu:///system start {nom}",
|
|
|
|
|
self.delai("controle"),
|
|
|
|
|
"rallumage",
|
|
|
|
|
)
|
|
|
|
|
return code == 0
|
|
|
|
|
|
[ADD] long_test : deep_qemu, et la preuve que KVM est bien là
Le pendant de deep_proxmox : des QEMU dans des QEMU. Le ralentissement du
quatrième étage vient du PROCESSEUR, mais le coût par étage vient de ce qu'on
installe — un nœud Proxmox pose un noyau, corosync, ceph et une interface web
là où un hôte libvirt pose libvirtd. Les deux mesures ensemble séparent ce qui
tient au matériel de ce qui tient à la pile.
Ce test ne peut pas se contenter de descendre. deploy_qemu.py ne passe jamais
« --cpu host-passthrough » et, quand /dev/kvm manque, il n'échoue PAS : il pose
« --virt-type qemu », avertit sur une ligne et crée une VM entièrement ÉMULÉE —
sept minutes et demie de démarrage, aucun code de retour pour le dire. Sans
garde, la descente mesurerait de la TCG empilée en croyant mesurer de
l'imbrication, et rendrait un chiffre plus flatteur et faux.
Chaque étage doit donc PROUVER : /dev/kvm lisible, « nested » à Y, et le
domaine de l'enfant en type='kvm'. Ce qui n'a pas été lu vaut NON — un
/sys/module absent, c'est un module non chargé, pas une permission.
nesting_plan reçoit ses coûts : les constantes vCPU décrivent la physique de
l'imbrication et valent pour les deux piles, les six nombres qui chiffrent un
Proxmox non. Un étage QEMU demande 2 Go et 20 Go, contre 4 et 25.
26 tests, six garde-fous morts sous mutation.
--- EN ---
The counterpart to deep_proxmox: QEMU inside QEMU. The fourth level's slowdown
comes from the PROCESSOR, but the per-level cost comes from what you install —
a Proxmox node lays down a kernel, corosync, ceph and a web UI where a libvirt
host lays down libvirtd. Together the two measurements separate what is due to
the hardware from what is due to the stack.
This test cannot merely descend. deploy_qemu.py never passes "--cpu
host-passthrough" and, when /dev/kvm is missing, it does NOT fail: it sets
"--virt-type qemu", warns on one line and creates a fully EMULATED VM — seven
and a half minutes to boot, no exit code to say so. Unguarded, the descent
would measure stacked TCG while believing it measured nesting, and return a
more flattering, false number.
Every level must therefore PROVE: /dev/kvm readable, "nested" at Y, and the
child's domain type='kvm'. What was not read counts as NO — an absent
/sys/module means an unloaded module, not a permission problem.
nesting_plan takes its costs: the vCPU constants describe the physics of
nesting and hold for both stacks, the six numbers that price a Proxmox do not.
A QEMU level asks 2 GB and 20 GB against 4 and 25.
26 tests, six guards die under mutation.
Assisted-by: claude-opus-5
(cherry picked from commit 39682cb1ce9648db91261387cae88c40c2a67837)
2026-08-28 02:43:53 -04:00
|
|
|
def controler(self, hote):
|
|
|
|
|
"""CET étage peut-il héberger le suivant SANS l'émuler ?
|
|
|
|
|
|
|
|
|
|
Le contrôle qui donne son sens à la mesure. Sans lui, un étage sans
|
|
|
|
|
KVM ne casse pas : il bascule en émulation et continue. La descente
|
|
|
|
|
irait plus « profond » en mesurant tout autre chose — de la TCG
|
|
|
|
|
empilée, pas de la virtualisation imbriquée.
|
|
|
|
|
"""
|
|
|
|
|
if self.dry_run:
|
|
|
|
|
print(" /dev/kvm + nested=Y + place disque")
|
|
|
|
|
return True
|
|
|
|
|
code, out = self.executer(
|
|
|
|
|
hote, CONTROLE_CMD, self.delai("controle"), "kvm"
|
|
|
|
|
)
|
|
|
|
|
if code:
|
|
|
|
|
self.dire(" ✗ contrôle KVM illisible : rien conclu")
|
|
|
|
|
return False
|
|
|
|
|
vu = parse_controle(out)
|
|
|
|
|
self.dire(
|
|
|
|
|
f" /dev/kvm {'oui' if vu['kvm'] else 'NON'},"
|
|
|
|
|
f" nested {'oui' if vu['nested'] else 'NON'},"
|
|
|
|
|
f" {vu['disque_go']} Go libres"
|
|
|
|
|
)
|
|
|
|
|
if not vu["kvm"]:
|
|
|
|
|
self.dire(" ✗ pas de /dev/kvm : l'étage suivant serait ÉMULÉ")
|
|
|
|
|
return False
|
|
|
|
|
if not vu["nested"]:
|
|
|
|
|
self.dire(
|
|
|
|
|
" ✗ virtualisation imbriquée absente :"
|
|
|
|
|
" l'étage suivant serait ÉMULÉ"
|
|
|
|
|
)
|
|
|
|
|
return False
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def preparer_parent(self, parent):
|
|
|
|
|
"""Ce qu'il faut du parent : son réseau. C'est tout.
|
|
|
|
|
|
|
|
|
|
Rien à construire, contrairement à Proxmox, où il faut poser un pont
|
|
|
|
|
et un NAT dans /etc/network/interfaces : libvirt fournit déjà
|
|
|
|
|
« default », avec NAT, bail DHCP ET résolveur dnsmasq. Le défaut qui a
|
|
|
|
|
coûté cher là-bas — une VM en adresse fixe qui route mais ne résout
|
|
|
|
|
rien, et une installation qui meurt sur « apt update » sans que rien
|
|
|
|
|
ne l'explique — ne peut pas se produire ici.
|
|
|
|
|
"""
|
|
|
|
|
if self.dry_run:
|
|
|
|
|
print(" réseau default du parent")
|
|
|
|
|
return ("default",)
|
|
|
|
|
code, out = self.executer(
|
|
|
|
|
parent, RESEAU_CMD, self.delai("controle"), "réseau"
|
|
|
|
|
)
|
|
|
|
|
if code:
|
|
|
|
|
self.dire(" ✗ état du réseau illisible : rien conclu")
|
|
|
|
|
return None
|
|
|
|
|
vu = parse_reseau(out)
|
|
|
|
|
if not vu["reseau"]:
|
|
|
|
|
self.dire(
|
|
|
|
|
" ✗ le réseau « default » du parent n'est pas actif"
|
|
|
|
|
)
|
|
|
|
|
return None
|
|
|
|
|
return ("default",)
|
|
|
|
|
|
[FIX] long_test : bail attendu, rallumage à froid, marge mémoire
Trois mesures faites sur une descente à cinq étages, et une conclusion de ma
part corrigée par le contre-essai.
Le bail DHCP se fait attendre. L'étage 3 était créé, en type='kvm', et
« domifaddr » ne rendait rien : l'invité n'avait pas encore demandé son
adresse — 87 s puis 94 s selon les tours, quand deploy_qemu s'accorde 90 s et
rend 0 sans l'avoir trouvée. Lu une fois, cela ne prouvait rien.
Un redémarrage demandé à l'invité peut le laisser bloqué dans son
micrologiciel : RIP immobile 46 minutes, pas un octet lu, trois vCPU à fond.
J'ai d'abord conclu que c'était la taille de la mémoire, parce que la même
machine à 2 Go démarrait. Le contre-essai à 4 Go l'a réfuté : elle démarre
aussi, à froid. La différence est le REDÉMARRAGE, pas la mémoire — à chaud
elle reste dans l'UEFI, à froid elle charge son noyau en 60 à 90 s, à 2, 3 et
4 Go. La descente rallume donc une fois par le parent, et une seule : une
boucle de rallumage cacherait un vrai échec.
La mémoire de la pile QEMU est doublée pour une autre raison, mesurée elle
aussi : l'étage 2 avec 5 Go hébergeait un invité de 4 Go et n'avait plus que
127 Mo de libre. Ce n'est pas le plancher qui compte, c'est l'écart.
--- EN ---
Three measurements from a five-level descent, and a conclusion of mine
refuted by the counter-test.
The DHCP lease takes its time. Level 3 was created, type='kvm', and
"domifaddr" returned nothing: the guest had not yet asked for its address —
87 s then 94 s depending on the run, while deploy_qemu allows itself 90 s and
returns 0 without having found it. Read once, that proved nothing.
A reboot asked of the guest can leave it stuck in its firmware: static RIP for
46 minutes, not a byte read, three vCPU at full tilt. I first concluded it was
the memory size, because the same machine booted at 2 GB. The counter-test at
4 GB refuted it: it boots too, cold. The difference is the REBOOT, not the
memory — warm it stays in UEFI, cold it loads its kernel in 60 to 90 s, at 2,
3 and 4 GB. The descent therefore power-cycles once through the parent, and
only once: a restart loop would hide a real failure.
The QEMU stack's memory is doubled for another, also measured reason: level 2
with 5 GB hosted a 4 GB guest and had 127 MB left. It is not the floor that
matters, it is the gap.
Assisted-by: claude-opus-5
(cherry picked from commit e3f60e3ddf066eb76d434bbfe6b01f2271fb1874)
2026-08-28 06:05:59 -04:00
|
|
|
def attendre_adresse(self, parent, nom):
|
|
|
|
|
"""Le bail DHCP de l'enfant, attendu. Rend l'adresse, ou "".
|
|
|
|
|
|
|
|
|
|
ATTENDU, et non lu une fois. Constaté au troisième étage : le domaine
|
|
|
|
|
était créé, en type='kvm', et « domifaddr » ne rendait rien — l'invité
|
|
|
|
|
n'avait pas encore demandé son bail. Plus l'étage est profond, plus il
|
|
|
|
|
démarre lentement, et c'est justement ce qu'on mesure.
|
|
|
|
|
|
|
|
|
|
`deploy_qemu` attend lui-même l'adresse — 90 secondes par défaut — puis
|
|
|
|
|
rend 0 quand il ne l'a pas trouvée. Son code de sortie ne prouve donc
|
|
|
|
|
rien ici non plus.
|
|
|
|
|
"""
|
|
|
|
|
debut = time.time()
|
|
|
|
|
delai = self.delai("ssh")
|
|
|
|
|
while time.time() - debut < delai:
|
|
|
|
|
_c, sortie = self.executer(
|
|
|
|
|
parent,
|
|
|
|
|
f"virsh -c qemu:///system domifaddr {nom} --source lease",
|
|
|
|
|
DELAIS["controle"],
|
|
|
|
|
"domifaddr",
|
|
|
|
|
)
|
|
|
|
|
adresse = parse_domifaddr(sortie)
|
|
|
|
|
if adresse:
|
|
|
|
|
if time.time() - debut > 20:
|
|
|
|
|
self.dire(f" bail après {int(time.time() - debut)} s")
|
|
|
|
|
return adresse
|
|
|
|
|
time.sleep(15)
|
|
|
|
|
return ""
|
|
|
|
|
|
[ADD] long_test : deep_qemu, et la preuve que KVM est bien là
Le pendant de deep_proxmox : des QEMU dans des QEMU. Le ralentissement du
quatrième étage vient du PROCESSEUR, mais le coût par étage vient de ce qu'on
installe — un nœud Proxmox pose un noyau, corosync, ceph et une interface web
là où un hôte libvirt pose libvirtd. Les deux mesures ensemble séparent ce qui
tient au matériel de ce qui tient à la pile.
Ce test ne peut pas se contenter de descendre. deploy_qemu.py ne passe jamais
« --cpu host-passthrough » et, quand /dev/kvm manque, il n'échoue PAS : il pose
« --virt-type qemu », avertit sur une ligne et crée une VM entièrement ÉMULÉE —
sept minutes et demie de démarrage, aucun code de retour pour le dire. Sans
garde, la descente mesurerait de la TCG empilée en croyant mesurer de
l'imbrication, et rendrait un chiffre plus flatteur et faux.
Chaque étage doit donc PROUVER : /dev/kvm lisible, « nested » à Y, et le
domaine de l'enfant en type='kvm'. Ce qui n'a pas été lu vaut NON — un
/sys/module absent, c'est un module non chargé, pas une permission.
nesting_plan reçoit ses coûts : les constantes vCPU décrivent la physique de
l'imbrication et valent pour les deux piles, les six nombres qui chiffrent un
Proxmox non. Un étage QEMU demande 2 Go et 20 Go, contre 4 et 25.
26 tests, six garde-fous morts sous mutation.
--- EN ---
The counterpart to deep_proxmox: QEMU inside QEMU. The fourth level's slowdown
comes from the PROCESSOR, but the per-level cost comes from what you install —
a Proxmox node lays down a kernel, corosync, ceph and a web UI where a libvirt
host lays down libvirtd. Together the two measurements separate what is due to
the hardware from what is due to the stack.
This test cannot merely descend. deploy_qemu.py never passes "--cpu
host-passthrough" and, when /dev/kvm is missing, it does NOT fail: it sets
"--virt-type qemu", warns on one line and creates a fully EMULATED VM — seven
and a half minutes to boot, no exit code to say so. Unguarded, the descent
would measure stacked TCG while believing it measured nesting, and return a
more flattering, false number.
Every level must therefore PROVE: /dev/kvm readable, "nested" at Y, and the
child's domain type='kvm'. What was not read counts as NO — an absent
/sys/module means an unloaded module, not a permission problem.
nesting_plan takes its costs: the vCPU constants describe the physics of
nesting and hold for both stacks, the six numbers that price a Proxmox do not.
A QEMU level asks 2 GB and 20 GB against 4 and 25.
26 tests, six guards die under mutation.
Assisted-by: claude-opus-5
(cherry picked from commit 39682cb1ce9648db91261387cae88c40c2a67837)
2026-08-28 02:43:53 -04:00
|
|
|
def creer_enfant(self, parent, niveau, res, prepare, noter=None):
|
|
|
|
|
"""Une VM dans le parent, par NOTRE deploy_qemu.py.
|
|
|
|
|
|
|
|
|
|
L'ordre compte, et il n'est pas celui de Proxmox. Là-bas l'adresse est
|
|
|
|
|
exigée AVANT la création (« --ipconfig0 ») ; ici libvirt ne la donne
|
|
|
|
|
qu'APRÈS le démarrage. On note donc l'identité — le nom du domaine,
|
|
|
|
|
qui est déterminé — avant la première commande qui peut créer quoi que
|
|
|
|
|
ce soit, faute de quoi une création échouée à mi-chemin laisserait une
|
|
|
|
|
machine que le rapport ne nomme nulle part.
|
|
|
|
|
"""
|
|
|
|
|
(reseau,) = prepare
|
|
|
|
|
nom = self.nom_etage(niveau)
|
|
|
|
|
if noter:
|
|
|
|
|
noter(nom)
|
|
|
|
|
if not self._envoyer_cli(parent):
|
|
|
|
|
return None, None
|
|
|
|
|
pub = cle_publique()
|
|
|
|
|
if pub and not self.dry_run:
|
|
|
|
|
with open(pub, encoding="utf-8") as fh:
|
|
|
|
|
contenu = fh.read().strip()
|
|
|
|
|
self.executer(
|
|
|
|
|
parent,
|
|
|
|
|
f"mkdir -p /root/.ssh && printf '%s\\n'"
|
|
|
|
|
f" {shlex.quote(contenu)} > {CLE_DISTANTE}",
|
|
|
|
|
DELAIS["controle"],
|
|
|
|
|
"clé",
|
|
|
|
|
)
|
|
|
|
|
creation = (
|
|
|
|
|
f"python3 {DISTANT_CLI} --distro {DISTRO} --name {nom}"
|
|
|
|
|
f" --vcpus {res['vcpu']} --memory {res['ram']}"
|
|
|
|
|
f" --disk-size {res['disque']}G --network network={reseau}"
|
|
|
|
|
f" --ssh-key {CLE_DISTANTE} --assume-yes"
|
|
|
|
|
)
|
|
|
|
|
code, _o = self.executer(
|
|
|
|
|
parent,
|
|
|
|
|
creation,
|
|
|
|
|
self.delai("creation"),
|
|
|
|
|
"deploy_qemu",
|
|
|
|
|
montrer=True,
|
|
|
|
|
)
|
|
|
|
|
if code and not self.dry_run:
|
|
|
|
|
return None, None
|
|
|
|
|
if self.dry_run:
|
|
|
|
|
return nom, "10.10.10.150"
|
|
|
|
|
# Le domaine est-il vraiment accéléré ? « deploy_qemu » n'échoue PAS
|
|
|
|
|
# quand KVM manque : il pose --virt-type qemu et continue. Un étage
|
|
|
|
|
# émulé fausserait toute la mesure sans rien dire.
|
|
|
|
|
_c, xml = self.executer(
|
|
|
|
|
parent,
|
|
|
|
|
f"virsh -c qemu:///system dumpxml {nom}",
|
|
|
|
|
self.delai("controle"),
|
|
|
|
|
"dumpxml",
|
|
|
|
|
)
|
|
|
|
|
vu = parse_domaine(xml)
|
|
|
|
|
if vu["type"] != "kvm":
|
|
|
|
|
self.dire(
|
|
|
|
|
f" ✗ domaine type='{vu['type'] or '?'}' : cette VM est"
|
|
|
|
|
" ÉMULÉE, la mesure ne voudrait rien dire"
|
|
|
|
|
)
|
|
|
|
|
return None, None
|
|
|
|
|
self.dire(f" domaine kvm, cpu {vu['cpu'] or '?'}")
|
[FIX] long_test : bail attendu, rallumage à froid, marge mémoire
Trois mesures faites sur une descente à cinq étages, et une conclusion de ma
part corrigée par le contre-essai.
Le bail DHCP se fait attendre. L'étage 3 était créé, en type='kvm', et
« domifaddr » ne rendait rien : l'invité n'avait pas encore demandé son
adresse — 87 s puis 94 s selon les tours, quand deploy_qemu s'accorde 90 s et
rend 0 sans l'avoir trouvée. Lu une fois, cela ne prouvait rien.
Un redémarrage demandé à l'invité peut le laisser bloqué dans son
micrologiciel : RIP immobile 46 minutes, pas un octet lu, trois vCPU à fond.
J'ai d'abord conclu que c'était la taille de la mémoire, parce que la même
machine à 2 Go démarrait. Le contre-essai à 4 Go l'a réfuté : elle démarre
aussi, à froid. La différence est le REDÉMARRAGE, pas la mémoire — à chaud
elle reste dans l'UEFI, à froid elle charge son noyau en 60 à 90 s, à 2, 3 et
4 Go. La descente rallume donc une fois par le parent, et une seule : une
boucle de rallumage cacherait un vrai échec.
La mémoire de la pile QEMU est doublée pour une autre raison, mesurée elle
aussi : l'étage 2 avec 5 Go hébergeait un invité de 4 Go et n'avait plus que
127 Mo de libre. Ce n'est pas le plancher qui compte, c'est l'écart.
--- EN ---
Three measurements from a five-level descent, and a conclusion of mine
refuted by the counter-test.
The DHCP lease takes its time. Level 3 was created, type='kvm', and
"domifaddr" returned nothing: the guest had not yet asked for its address —
87 s then 94 s depending on the run, while deploy_qemu allows itself 90 s and
returns 0 without having found it. Read once, that proved nothing.
A reboot asked of the guest can leave it stuck in its firmware: static RIP for
46 minutes, not a byte read, three vCPU at full tilt. I first concluded it was
the memory size, because the same machine booted at 2 GB. The counter-test at
4 GB refuted it: it boots too, cold. The difference is the REBOOT, not the
memory — warm it stays in UEFI, cold it loads its kernel in 60 to 90 s, at 2,
3 and 4 GB. The descent therefore power-cycles once through the parent, and
only once: a restart loop would hide a real failure.
The QEMU stack's memory is doubled for another, also measured reason: level 2
with 5 GB hosted a 4 GB guest and had 127 MB left. It is not the floor that
matters, it is the gap.
Assisted-by: claude-opus-5
(cherry picked from commit e3f60e3ddf066eb76d434bbfe6b01f2271fb1874)
2026-08-28 06:05:59 -04:00
|
|
|
adresse = self.attendre_adresse(parent, nom)
|
[ADD] long_test : deep_qemu, et la preuve que KVM est bien là
Le pendant de deep_proxmox : des QEMU dans des QEMU. Le ralentissement du
quatrième étage vient du PROCESSEUR, mais le coût par étage vient de ce qu'on
installe — un nœud Proxmox pose un noyau, corosync, ceph et une interface web
là où un hôte libvirt pose libvirtd. Les deux mesures ensemble séparent ce qui
tient au matériel de ce qui tient à la pile.
Ce test ne peut pas se contenter de descendre. deploy_qemu.py ne passe jamais
« --cpu host-passthrough » et, quand /dev/kvm manque, il n'échoue PAS : il pose
« --virt-type qemu », avertit sur une ligne et crée une VM entièrement ÉMULÉE —
sept minutes et demie de démarrage, aucun code de retour pour le dire. Sans
garde, la descente mesurerait de la TCG empilée en croyant mesurer de
l'imbrication, et rendrait un chiffre plus flatteur et faux.
Chaque étage doit donc PROUVER : /dev/kvm lisible, « nested » à Y, et le
domaine de l'enfant en type='kvm'. Ce qui n'a pas été lu vaut NON — un
/sys/module absent, c'est un module non chargé, pas une permission.
nesting_plan reçoit ses coûts : les constantes vCPU décrivent la physique de
l'imbrication et valent pour les deux piles, les six nombres qui chiffrent un
Proxmox non. Un étage QEMU demande 2 Go et 20 Go, contre 4 et 25.
26 tests, six garde-fous morts sous mutation.
--- EN ---
The counterpart to deep_proxmox: QEMU inside QEMU. The fourth level's slowdown
comes from the PROCESSOR, but the per-level cost comes from what you install —
a Proxmox node lays down a kernel, corosync, ceph and a web UI where a libvirt
host lays down libvirtd. Together the two measurements separate what is due to
the hardware from what is due to the stack.
This test cannot merely descend. deploy_qemu.py never passes "--cpu
host-passthrough" and, when /dev/kvm is missing, it does NOT fail: it sets
"--virt-type qemu", warns on one line and creates a fully EMULATED VM — seven
and a half minutes to boot, no exit code to say so. Unguarded, the descent
would measure stacked TCG while believing it measured nesting, and return a
more flattering, false number.
Every level must therefore PROVE: /dev/kvm readable, "nested" at Y, and the
child's domain type='kvm'. What was not read counts as NO — an absent
/sys/module means an unloaded module, not a permission problem.
nesting_plan takes its costs: the vCPU constants describe the physics of
nesting and hold for both stacks, the six numbers that price a Proxmox do not.
A QEMU level asks 2 GB and 20 GB against 4 and 25.
26 tests, six guards die under mutation.
Assisted-by: claude-opus-5
(cherry picked from commit 39682cb1ce9648db91261387cae88c40c2a67837)
2026-08-28 02:43:53 -04:00
|
|
|
if not adresse:
|
|
|
|
|
self.dire(" ✗ créée, mais sans adresse : rien à joindre")
|
|
|
|
|
return None, None
|
|
|
|
|
self.dire(f" {nom} : {adresse}")
|
|
|
|
|
return nom, adresse
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def detruire_une(parent_alias, identite, nom, journal):
|
|
|
|
|
"""Arrête puis détruit UNE VM chez son parent, par son NOM et son UUID.
|
|
|
|
|
|
|
|
|
|
Par égalité STRICTE du nom : un filtre par sous-chaîne aurait pris une
|
|
|
|
|
« deep-qemu-lab » de production, et « --remove-all-storage » efface un
|
|
|
|
|
disque pour de bon.
|
|
|
|
|
"""
|
|
|
|
|
parent = {"target": parent_alias, "sudo": "sudo ", "jump": ""}
|
|
|
|
|
code, out = pve.run(
|
|
|
|
|
parent, "virsh -c qemu:///system list --all --name", 180
|
|
|
|
|
)
|
|
|
|
|
if code:
|
|
|
|
|
dire(f" ✗ {parent_alias} injoignable : rien touché", journal)
|
|
|
|
|
return False
|
|
|
|
|
presents = [
|
|
|
|
|
ligne.strip()
|
|
|
|
|
for ligne in pve.strip_ssh_noise(out).splitlines()
|
|
|
|
|
if ligne.strip()
|
|
|
|
|
]
|
|
|
|
|
if nom not in presents:
|
|
|
|
|
dire(f" — {nom} : absent de {parent_alias}", journal)
|
|
|
|
|
return True
|
|
|
|
|
pve.run(parent, f"virsh -c qemu:///system destroy {nom}", 300)
|
|
|
|
|
code, _o = pve.run(
|
|
|
|
|
parent,
|
|
|
|
|
f"virsh -c qemu:///system undefine {nom} --nvram --remove-all-storage",
|
|
|
|
|
600,
|
|
|
|
|
)
|
|
|
|
|
if code:
|
|
|
|
|
dire(f" ✗ {nom} sur {parent_alias} : undefine a échoué", journal)
|
|
|
|
|
return False
|
|
|
|
|
dire(f" ✓ {nom} ({identite}) sur {parent_alias}", journal)
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
FAMILLE = Famille(OUTIL, NOM_BASE, detruire_une)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def principal(argv=None):
|
|
|
|
|
return mener(
|
|
|
|
|
argv,
|
|
|
|
|
"Jusqu'à quel étage une QEMU dans une QEMU tient-elle ?",
|
|
|
|
|
FAMILLE,
|
|
|
|
|
Descente,
|
|
|
|
|
nesting.COUTS_QEMU,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
sys.exit(principal())
|