# Runbook — Labs, stock chaud et correction (étapes 10 et 11) ## Où sont les choses | Élément | Emplacement | |---|---| | Source éditoriale des labs | `paillasse-contenus/` (dépôt de contenu, pas de code) | | Copie de travail lue par le portail et le correcteur | `labs/` à la racine de ce dépôt → synchronisée vers `/opt/labs` sur le serveur | | Catalogue (table `labs`) | `php artisan labs:synchroniser`, lancé par Ansible après chaque synchronisation de `labs/` | | Lecture d'un `lab.yaml` | `portail/app/Services/Labs/DefinitionLab.php` | | Traduction lab → gabarit Heat | `portail/app/Services/OpenStack/GenerateurTemplateHeat.php` | | Construction d'une stack (3 essais) | `portail/app/Services/OpenStack/FabriqueStack.php` | | Authentification OpenStack | `portail/app/Services/OpenStack/KeystoneAuth.php` (mot de passe — [ADR 0002](../adr/0002-mot-de-passe-pour-heat.md), rotation : [compte-service-openstack.md](compte-service-openstack.md)) | | Stock chaud | `portail/app/Services/Labs/StockChaud.php`, job `CreerStackStock`, commande `labs:remplir-stock` chaque minute — [ADR 0003](../adr/0003-stock-chaud-et-horizon.md) | | Admission (stock ou construction) | `SeanceController::admettre` → `CreerStackLab` si le stock est vide | | Suppression et expiration | `SupprimerStackLab`, commande `labs:expirer-sessions` chaque minute | | Files de jobs | Laravel Horizon, service systemd `portail-horizon` : superviseurs `sessions` (4 processus) et `stock` (2), `config/horizon.php` | | Correcteur | service systemd `correcteur` (Go, `correcteur/`, binaire `/usr/local/sbin/correcteur`), socket `/run/correcteur/correcteur.sock` (`0660 root:www-data`) | | Correction côté portail | `ClientCorrecteur`, `CorrigerSession`, table `corrections` | | Tests du parcours réel | `portail/tests/Reels/` (préparation + parcours prof/élève) | ## États d'une session de lab `en_attente` → (prof : Admettre) → `actif` tout de suite si le stock a une stack, sinon `admis` puis `actif` quand `CreerStackLab` a fini → `expire` ou `termine`. Après 3 essais ratés : `echec`, la raison est affichée au prof qui peut cliquer « Réessayer » (retour à `admis`). ## Ajouter un nouveau lab 1. Le lab existe dans `paillasse-contenus/{id}/lab.yaml`. L'ordre des réseaux d'une machine décide de l'ordre des interfaces (`ens3`, `ens4`…). Les vérifications dont l'id commence par `bonus-` ne comptent pas pour « réussi ». 2. Le copier dans `labs/{id}/lab.yaml` de ce dépôt. 3. Vérifier que les images et flavors existent (`openstack image list`, `openstack flavor list`) et que l'image a `hw_qemu_guest_agent=yes` (sinon le correcteur répond `agent_injoignable`). 4. Déployer : `cd ansible && script -q /dev/null ansible-playbook correcteur.yml -i inventory.ini` (portail + correcteur). Le lab entre au catalogue, et son stock se remplit dans la minute. 5. Tester le parcours : `portail/tests/Reels/` (voir l'en-tête des deux scripts), puis supprimer les données de test. ## Stock chaud ``` ssh paillasse "cd /var/www/portail && sudo -u www-data HOME=/tmp php8.3 artisan labs:remplir-stock" ``` Affiche, par lab : cible, prêtes, en construction, commandées, retirées, disjoncteur. Réglages (`.env` du portail, sinon valeurs de `config/labs.php`) : - `LABS_CAPACITE_VM` (15) : plafond de VM `eleve.petit`, sessions et stock compris ; - `LABS_STOCK_TAILLE` (1) : stacks prêtes par lab hors séance. Disjoncteur ouvert (« oui ») : 3 échecs définitifs de suite sur ce lab. Lire `storage/logs/laravel.log` (`CreerStackStock`), corriger la cause (image, flavor, quota…), puis le réarmer sans attendre ses 30 min : ``` sudo -u www-data HOME=/tmp php8.3 artisan tinker --execute='Illuminate\Support\Facades\Redis::del("stock:echecs:{lab}");' ``` Vider le stock d'un lab (les stacks seront supprimées par le ménage) : passer `LABS_STOCK_TAILLE=0` puis attendre une minute, ou supprimer la clé `stock:pretes:{lab}` (le ménage supprime alors les stacks orphelines après 10 min). ## Correcteur ``` systemctl status correcteur journalctl -u correcteur -f # une ligne par vérification : instance, lab, id, résultat, durée ``` Il n'accepte que `{"instance": uuid, "lab": id, "verification": id}`, refuse tout champ en plus, et n'exécute que la commande du `lab.yaml`, en root dans la VM, par `guest-exec` (10 s maximum). Avant d'exécuter, il vérifie dans les métadonnées libvirt que la VM appartient au projet `paillasse` et porte le nom de la machine visée. Codes d'erreur : `requete_invalide`, `lab_inconnu`, `verification_inconnue`, `machine_non_conforme`, `agent_injoignable`, `delai_depasse`. ## Inspecter les stacks sur le serveur ``` sudo -i export PATH=/opt/kolla-venv/bin:$PATH export OS_CLIENT_CONFIG_FILE=/etc/kolla/clouds-paillasse.yaml OS_CLOUD=paillasse-service openstack stack list # lab-session-{id}-e{n} : sessions ; stock-{lab}-{xxxxxx}-e{n} : stock openstack server list ``` ## Pièges rencontrés - **Heat refuse une application credential** (`Delegated tokens cannot manage trusts`), même `--unrestricted` : voir [ADR 0002](../adr/0002-mot-de-passe-pour-heat.md). - **Horizon garde le code et la configuration en mémoire** : le rôle Ansible le redémarre après chaque synchronisation de code ou changement de `.env`. - **Timeouts emboîtés** : job (280 s) < superviseur Horizon (300 s) < `REDIS_QUEUE_RETRY_AFTER` (330 s). Sinon Redis redonne un job encore en cours à un autre processus, et deux stacks sont construites. - **Console muette après la première touche** : `nova-serialproxy` ferme la connexion à la première trame WebSocket texte. Le proxy Go envoie tout en binaire — voir [post-mortem du 2026-10-08](../postmortems/2026-10-08-console-trames-texte.md). - `sudo -u www-data php artisan ...` exige `HOME=/tmp` (pas de `$HOME` inscriptible pour `www-data`). - Une stack appartient à une session (un élève, une tentative) ; une stack du stock n'y retourne jamais après avoir été donnée.