ajoute le stock chaud, la correction automatique et les nouveaux essais de création de stack
Stock de stacks prêtes par lab dans Redis (prise atomique en Lua, ménage par comparaison avec Heat, disjoncteur), files gérées par Horizon, séances planifiables. Correcteur Go sur l'hyperviseur : socket Unix, commandes lues dans le lab.yaml uniquement, exécution par l'agent invité QEMU. Une création de stack ratée est supprimée et réessayée deux fois, puis la session passe en échec, visible du prof. Corrige aussi trois défauts des étapes précédentes : la console se coupait à la première frappe d'un navigateur (trames texte), l'élève ne pouvait pas rejoindre une séance, la déconnexion n'avait pas de jeton CSRF. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
ca2f6f3b7a
commit
80b6c0722d
71 changed files with 2959 additions and 170 deletions
|
|
@ -378,3 +378,75 @@ La leçon de cette étape dépasse Heat : dans Keystone, un jeton porte une hist
|
|||
**Suite**
|
||||
|
||||
Étape 11 : stock chaud de VM (files Redis) et correction automatique (agent invité QEMU).
|
||||
|
||||
## 2026-10-08 — Entre les étapes 10 et 11 : questions d'Adam sur le compte de service
|
||||
|
||||
**Fait**
|
||||
|
||||
- `--unrestricted` testé sur le serveur réel : une application credential `--unrestricted` échoue avec la même erreur Keystone que la restreinte (`Delegated tokens cannot manage trusts`). Le mot de passe reste la seule option pour Heat. Ajouté à l'ADR 0002.
|
||||
- Contrôle du compte `paillasse-service` : seul rôle `member` sur `paillasse` (`reader` en découle, rôle impliqué), mot de passe de 32 octets aléatoires, `clouds-paillasse.yaml` en `600 root:root`. Il manquait le runbook de rotation : écrit (`docs/runbooks/compte-service-openstack.md`) et testé par une vraie rotation. Le classifieur de permissions a bloqué la fin de la rotation (écriture de secrets) : Adam a lancé lui-même le playbook et l'effacement du cache, puis j'ai vérifié l'authentification sans afficher de secret.
|
||||
- Défaut corrigé : un changement de `.env` par Ansible ne redémarrait pas le worker de file, qui aurait gardé l'ancien mot de passe en mémoire.
|
||||
|
||||
**Incident (de ma part)**
|
||||
|
||||
En créant l'application credential de test `--unrestricted`, j'ai demandé une sortie `-f json` qui a affiché son secret en clair, puis je l'ai écrit dans un fichier temporaire. La credential et le fichier ont été supprimés juste après le test : le secret est révoqué et sans valeur. Règle pour la suite : toute commande qui crée un secret doit l'envoyer directement dans un fichier `600` sur le serveur, sans jamais l'afficher, même pour un test.
|
||||
|
||||
## 2026-10-08 — Étape 11 : stock chaud et correction automatique
|
||||
|
||||
**Fait**
|
||||
|
||||
- **Fiabilité des créations** (demande d'Adam) : `FabriqueStack`, commune aux sessions et au stock. Un essai raté supprime sa stack et lève une exception ; Laravel relance le job 2 fois (après 15 puis 45 s), chaque essai sous son propre nom (`-e1`, `-e2`, `-e3`). Avant un essai, les restes des précédents sont supprimés (cas d'un processus tué). Après le 3ᵉ échec : session en `echec`, raison visible du prof, bouton « Réessayer ». L'admission est protégée contre le double clic par une mise à jour SQL conditionnelle.
|
||||
- **Stock chaud** : une liste Redis de stacks prêtes par lab, prise atomique par un script Lua, cible = 1 stack par lab + élèves des séances qui commencent dans 10 min ou déjà ouvertes, plafond de capacité, ménage des stacks orphelines par comparaison avec Heat, disjoncteur après 3 échecs. Séances planifiables (date et heure, heure de Paris convertie en UTC) : le stock monte au nombre d'élèves 10 min avant. Détails et alternatives : ADR 0003.
|
||||
- **Laravel Horizon 5.50** remplace le `queue:work` unique : superviseurs `sessions` (4 processus) et `stock` (2), tableau de bord fermé. Timeouts emboîtés : job 280 s < superviseur 300 s < `retry_after` 330 s (avant : 90 s, un job long aurait été exécuté deux fois).
|
||||
- **Correcteur** (Go, choix d'Adam) : service systemd root, groupe `www-data`, socket Unix `0660`, identité du client contrôlée par `SO_PEERCRED`. N'accepte que `{instance, lab, verification}` (champs en plus refusés), lit la commande dans le `lab.yaml`, vérifie dans les métadonnées libvirt que la VM appartient au projet `paillasse` et correspond à la machine visée, exécute par `guest-exec` via `docker exec nova_libvirt virsh` (10 s max). Ne renvoie jamais la sortie de la commande, seulement réussi/échoué et un code d'erreur. Installation de Go sortie dans un rôle `go` partagé avec le proxy console.
|
||||
- **Côté élève** : bouton « Vérifier mon travail », note, détail par vérification avec le `message_echec` du lab (ou un message technique si la VM ne répond pas). La page d'attente interroge l'état chaque seconde au lieu de se recharger toutes les 5 s. Côté prof : note dans la salle d'attente.
|
||||
- 7 nouveaux tests Pest (18 au total), 4 tests Go.
|
||||
|
||||
**Calcul de la capacité (`LABS_CAPACITE_VM` = 15)**
|
||||
|
||||
- Mesuré sans VM : 15,5 Go utilisés par Kolla et le portail sur 32 Go, 16,5 Go disponibles.
|
||||
- Mesuré avec les 3 VM d'une stack de stock au repos : 260 à 290 Mo réels par VM (sur 768 Mo alloués).
|
||||
- Pire cas retenu (chaque VM utilise toute sa RAM) : (16,5 Go − 2 Go de marge pour l'hôte) / (768 Mo + ≈ 200 Mo de surcoût QEMU) ≈ 15 VM. Au repos, le serveur en tiendrait trois fois plus : décider d'un surengagement (et de KSM) sera le sujet de l'étape 13, mesures en charge à l'appui. Stock par défaut : 1 stack par lab (3 VM pour le lab 08, 20 % du plafond).
|
||||
|
||||
**Commandes clés**
|
||||
|
||||
```
|
||||
cd ansible && script -q /dev/null ansible-playbook correcteur.yml -i inventory.ini # portail + correcteur
|
||||
ssh paillasse "cd /var/www/portail && sudo -u www-data HOME=/tmp php8.3 artisan labs:remplir-stock"
|
||||
ssh paillasse "journalctl -u correcteur -f"
|
||||
```
|
||||
|
||||
**Problèmes rencontrés et solution**
|
||||
|
||||
1. **La première touche frappée coupait la console** (défaut de l'étape 9) : `nova-serialproxy` ferme la connexion à la première trame WebSocket texte, et xterm.js envoie du texte. Le test de l'étape 9 envoyait du binaire et ne l'avait pas vu. Le proxy envoie désormais tout en binaire. Post-mortem : `docs/postmortems/2026-10-08-console-trames-texte.md`.
|
||||
2. **Aucun bouton pour qu'un élève rejoigne une séance** (défaut de l'étape 8, masqué aux étapes 9 et 10 où la session était créée en base) : ajouté au tableau de bord élève.
|
||||
3. **« Se déconnecter » renvoyait 419** : les deux formulaires du layout n'avaient pas de `@csrf`. Corrigé, et un test vérifie la présence du jeton (Laravel désactive le contrôle CSRF pendant les tests, il fallait tester le HTML).
|
||||
4. Une VM du stock démarrée depuis longtemps n'affiche rien sur sa console série : option `reveil` dans `console-reelle.js`, qui envoie un retour chariot à la connexion (côté élève seulement).
|
||||
5. Les séances n'avaient pas d'heure de début : colonne `prevue_a` et formulaire de planification.
|
||||
6. Larastan (PHPStan niveau 6, exigé par le CLAUDE.md) n'est pas installé depuis l'étape 8 : dette notée, pas traitée ici.
|
||||
7. Durée : l'étape a dépassé 1 h 30 (trois défauts d'étapes précédentes trouvés et corrigés en route), sans blocage.
|
||||
|
||||
**Critère vérifié (sur le serveur réel, `portail/tests/Reels/parcours-lab.py`, joué comme deux navigateurs)**
|
||||
|
||||
```
|
||||
1. Admission → invite de connexion : 4.06 s (OK, critère < 5 s)
|
||||
machines ouvertes : pc-a, routeur, pc-b
|
||||
2. Sans routage activé : « Pas encore réussi » 6/10 (vérification en 5.8 s)
|
||||
- Le routeur ne transmet pas les paquets : regarde net.ipv4.ip_forward.
|
||||
- PC-A ne joint pas PC-B. Vérifie les adresses, les passerelles et le routage, dans cet ordre.
|
||||
- La configuration du routeur disparaîtra au redémarrage (sysctl et fichier d'interfaces).
|
||||
3. Routage activé : « Réussi » 10/10 (vérification en 4.7 s)
|
||||
```
|
||||
|
||||
- Une seule mesure du temps d'ouverture, et pessimiste (le script récupère les 3 consoles l'une après l'autre, le navigateur en parallèle). Le p95 sur une série reste à établir à l'étape 13.
|
||||
- Échec provoqué (lab temporaire dont la stack ne peut pas se construire) : `heat-engine.log` montre `lab-session-5-e1`, `-e2`, `-e3` chacune `CREATE FAILED` puis `DELETE COMPLETE` ; session en `echec` 75 s après l'admission, aucune stack restante. Le stock du même lab : disjoncteur ouvert après les échecs, plus aucune commande.
|
||||
- Correcteur interrogé directement : vérification légitime exécutée ; machine non conforme, uuid hors projet, chemin `../../etc`, champ `commande` ajouté et vérification inconnue tous refusés ; un utilisateur autre que `www-data` ou root ne peut pas ouvrir le socket.
|
||||
- Données, lab temporaire et fichiers de test supprimés (accord d'Adam). État final : une stack de stock pour le lab 08, rien d'autre.
|
||||
|
||||
**Comprendre**
|
||||
|
||||
Le stock chaud transforme une attente de 30 s en une attente de 4 s en faisant le travail lent *avant* que quelqu'un en ait besoin — comme un restaurant qui prépare ses fonds de sauce le matin. La difficulté n'est pas de construire d'avance, c'est de ne jamais donner deux fois la même chose ni de perdre ce qu'on a construit : d'où le script Lua (Redis exécute « retirer de la liste et marquer comme en cours d'attribution » d'un seul bloc) et le ménage qui compare Redis à Heat. Côté correction, la règle de sécurité est simple et non négociable : le portail désigne une vérification par son nom, il n'envoie jamais de commande ; le correcteur, seul à toucher les VM, ne fait confiance qu'au `lab.yaml` et vérifie même que la VM désignée est bien celle du lab. Enfin, cette étape montre l'intérêt d'un test qui imite vraiment l'utilisateur : trois défauts des étapes précédentes, invisibles pour les tests « techniques », sont apparus dès qu'un script a joué le parcours comme un navigateur.
|
||||
|
||||
**Suite**
|
||||
|
||||
Étape 12 : domaine, HTTPS et sécurité. **Arrêt obligatoire avant de commencer** : Adam doit me donner le domaine (il l'achète et configure le DNS lui-même).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue