ajoute la liaison Laravel-OpenStack et le modèle Heat du lab 08

Un gabarit Heat généré depuis chaque lab.yaml, une stack par session de
lab (création à l'admission, suppression à l'expiration via des jobs
Redis et un worker systemd dédié). Passage du compte de service à une
authentification par mot de passe : Heat refuse de créer un trust à
partir d'un jeton d'application credential (voir ADR 0002).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Adam Belkacemi 2026-10-08 10:14:24 +02:00
parent 8742bde807
commit 67a754f733
34 changed files with 1095 additions and 91 deletions

View file

@ -346,3 +346,35 @@ La vraie contrainte de cette étape, c'est qu'une console série ne supporte qu'
**Suite**
Étape 10 : liaison Laravel ↔ OpenStack (client HTTP ou `php-opencloud/openstack`) et modèle Heat du lab 08 — poser la vraie mécanique qu'on a seulement simulée à la main à cette étape (associer une VM à une session de lab).
## 2026-10-08 — Étape 10 : liaison Laravel ↔ OpenStack et modèle Heat du lab 08
**Fait**
- `GenerateurTemplateHeat` : traduit un `lab.yaml` (format paillasse-contenus : réseaux + machines, chaque machine listant les réseaux auxquels elle est reliée, dans l'ordre) en un vrai gabarit Heat (HOT) — un réseau/sous-réseau Neutron par réseau du lab (`port_security_enabled: false`, `enable_dhcp: false` : pas de pare-feu réseau ni de DHCP, les élèves configurent eux-mêmes, aucune route vers l'extérieur), un port + une instance Nova par machine, les ports attachés dans l'ordre déclaré (c'est cet ordre qui décide si une interface sera `ens3` ou `ens4` dans la VM), une sortie Heat par machine pour récupérer son identifiant une fois la stack créée.
- `HeatClient` (créer/lire/supprimer une stack) et `KeystoneAuth` (authentification Keystone partagée, jeton mis en cache 50 min) : voir [ADR 0002](adr/0002-mot-de-passe-pour-heat.md), la vraie découverte de cette étape.
- Une stack Heat par session de lab (par élève, par tentative), pas par classe : `CreerStackLab` (job Redis, déclenché à l'admission de l'élève) génère le gabarit, crée la stack, attend `CREATE_COMPLETE` (jusqu'à 2 minutes), enregistre une `LabSessionMachine` (nom, identifiant d'instance) par machine du lab. `SupprimerStackLab` (job Redis, déclenché à l'expiration) supprime la stack et ses VM, et classe la session en `expire` ou `terminee` selon le cas.
- `ExpirerSessionsLab` (commande Artisan `labs:expirer-sessions`), planifiée chaque minute via `Schedule::command()` dans `routes/console.php` + un cron système qui appelle `artisan schedule:run`.
- Les deux jobs ont besoin d'un worker : service systemd `portail-worker` (`queue:work`), déployé et redémarré par Ansible à chaque déploiement de code (sans ce redémarrage, un worker déjà lancé continue de faire tourner l'ancien code en mémoire).
- `labs/` : nouveau dossier à la racine du dépôt, un sous-dossier par lab, copié par Ansible vers `/opt/labs` sur le serveur (distinct de `paillasse-contenus/`, qui reste la source éditoriale ; `labs/` est la copie de travail que lit le portail). Lab 08 (deux réseaux locaux reliés par un routeur, 3 machines) copié en premier.
- Salle d'attente élève et console prof passées à plusieurs terminaux (un onglet par VM de la session), avec un préfixe de route supplémentaire (`{labSessionMachine}`) pour identifier quelle VM ouvrir.
**Problèmes rencontrés et solution**
1. **La vraie découverte de l'étape** : création de stack Heat refusée (`500`, trace dans `heat-engine.log` : « Delegated tokens cannot manage trusts »). Le compte de service du portail utilisait une application credential (choix de l'étape 6) ; Heat a besoin de créer un trust Keystone pour gérer le cycle de vie d'une stack, et Keystone interdit par construction à un jeton déjà délégué (une application credential en est un) d'en déléguer un second. Vérifié avec un compte à mot de passe classique avant de rien changer en dur ; confirmé avec Adam ; appliqué partout (voir ADR 0002). Un seul mécanisme d'authentification désormais pour tout ce que le portail demande à OpenStack.
2. `openstack user set --password` avec le cloud `kolla-admin` et `OS_PROJECT_NAME=paillasse` → `401` : même limitation que l'étape 6 (le compte `admin` n'a pas de rôle sur le projet `paillasse`, donc pas de jeton scopé à ce projet). Pas réinvestigué, contourné en utilisant directement le cloud `paillasse-service`.
3. `sudo -u www-data php artisan tinker` échouait (« Writing to directory /var/www/.config/psysh is not allowed ») : `www-data` n'a pas de `$HOME` inscriptible. Contourné avec `HOME=/tmp` à chaque appel.
**Critère vérifié (sur le serveur réel)**
- Classe, élève, séance de test créés ; admission de l'élève sur le lab 08 → `CreerStackLab` exécuté en 26 s → stack `CREATE_COMPLETE`, 3 VM créées. `ip link show` dans chaque VM confirme la topologie exacte du `lab.yaml` : le routeur a 2 interfaces (`ens3`, `ens4`), les deux postes (`pc-a`, `pc-b`) n'en ont qu'une chacun, sur des réseaux différents.
- Les 3 terminaux se sont ouverts côté élève, chacun relié à la bonne VM.
- Vérification allant au-delà du critère minimal : expiration forcée (`expire_a` mis dans le passé) → `labs:expirer-sessions` → `SupprimerStackLab` exécuté en 1 s → stack et 3 VM confirmées absentes (`openstack stack list` / `server list` vides) → session classée `expire`. Le cycle de vie complet (création, usage, expiration automatique) fonctionne de bout en bout sans intervention manuelle.
**Comprendre**
La leçon de cette étape dépasse Heat : dans Keystone, un jeton porte une histoire (« comment a-t-il été obtenu ? »), pas seulement une identité et des droits. Une application credential produit un jeton marqué comme délégué ; peu importe les droits du compte derrière, ce jeton ne pourra jamais en déléguer un second — c'est une protection contre les chaînes de délégation, pas un détail de configuration qu'on pourrait activer. Avant d'ajouter un service OpenStack qui gère un cycle de vie (Heat aujourd'hui ; Octavia, Magnum plus tard), il faut vérifier s'il a besoin d'un trust — et donc d'un compte à mot de passe, jamais d'une application credential.
**Suite**
Étape 11 : stock chaud de VM (files Redis) et correction automatique (agent invité QEMU).