erplibre/script/todo/longtest_menu.py
Mathieu Benoit 7199a7cbb2 [ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.

D'où trois choses.

L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.

Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.

Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.

--- EN ---

The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.

Hence three things.

The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.

The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.

The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.

Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-29 01:53:03 -04:00

86 lines
3.3 KiB
Python

#!/usr/bin/env python3
# © 2026 TechnoLibre (http://www.technolibre.ca)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
"""Les tests LONGS : de vraies machines, des heures.
Ils vivent dans `LongTest/` et non dans `test/`, et ce n'est pas un rangement
de confort : le lanceur unitaire balaie `test/test_*.py` et doit rester
lançable en quelques secondes, partout. Un test qui crée dix VM n'a rien à y
faire — il le ferait échouer sur toute machine sans virtualisation, et
personne ne l'attendrait.
Ce menu ne fait que les lancer, en montrant leur sortie en direct : ces
scripts durent des heures, et une sortie capturée jusqu'à la fin ne dirait
rien pendant tout ce temps.
"""
import os
import click
from script.todo.todo_i18n import t
# Le répertoire des tests longs, à la racine du dépôt.
LONGTEST_DIR = "LongTest"
class LongTestMenuMixin:
def _longtest_script(self, nom):
"""Chemin d'un test long, ou "" s'il n'est pas là."""
chemin = os.path.join(os.getcwd(), LONGTEST_DIR, nom)
return chemin if os.path.exists(chemin) else ""
def _longtest_run(self, nom, args=""):
"""Lance un test long, sortie en DIRECT.
En direct parce qu'il dure des heures : capturer sa sortie pour
l'afficher à la fin, c'est ne rien montrer pendant tout ce temps —
et c'est justement la progression étage par étage qui intéresse.
"""
chemin = self._longtest_script(nom)
if not chemin:
print(f" ✗ {t('Script not found:')} {LONGTEST_DIR}/{nom}")
return
cmd = f"./.venv.erplibre/bin/python {chemin}"
if args:
cmd += f" {args}"
print(f"\n{t('Will execute:')} {cmd}")
self.execute.exec_command_live(cmd, source_erplibre=False)
def prompt_execute_longtest(self):
print(f"⏳ {t('Long tests: real VMs, hours. Not the unit suite.')}")
choices = [
{
"prompt_description": t(
"Nested Proxmox depth: plan only (dry-run)"
)
},
{"prompt_description": t("Nested Proxmox depth: run it")},
{"prompt_description": t("Undo what the descent created")},
]
help_info = self.fill_help_info(choices)
while True:
status = click.prompt(help_info)
print()
if status == "0":
return False
if status == "1":
self._longtest_run(
"deep_proxmox.py",
f"--depth {self._longtest_depth()} --dry-run",
)
elif status == "2":
# La profondeur est DEMANDÉE : c'est le seul réglage du test,
# et il décide de sa durée — dix étages, c'est une nuit.
self._longtest_run(
"deep_proxmox.py", f"--depth {self._longtest_depth()}"
)
elif status == "3":
self._longtest_run("deep_proxmox.py", "--detruire")
else:
print(t("Command not found !"))
def _longtest_depth(self):
"""Profondeur demandée. Dix par défaut : c'est ce qu'on veut mesurer."""
brut = input(f"{t('Depth (default 10): ')}").strip()
return int(brut) if brut.isdigit() and int(brut) > 0 else 10