Aller au contenu principal

Recette de bout en bout : de la machine à la restauration vérifiée

Ce parcours montre que le client fait tout dans une seule console, sans changer d'outil : ajouter une machine, la valider, l'amorcer par SSH, l'enrôler, créer un cluster, y installer un service, choisir son nombre d'instances, activer la haute disponibilité, configurer une sauvegarde et tester une restauration.

La preuve automatisée est le test server/tests/test_e2e_infrastructure_journey.py. Il démarre un vrai processus serveur (API console et API agent), deux vrais sshd OpenSSH sans privilège (tests/infra_sshd.py) sur des ports libres et deux agents Go statiques compilés depuis agent-go, chacun avec son propre répertoire d'état. Chaque étape passe par les routes publiques /api/v1 utilisées par la console et vérifie l'état observé, pas seulement le code HTTP. Le binaire OPA réel et l'archive etcd réelle viennent de NM_E2E_ARTIFACTS (par défaut le miroir du bac à sable, rempli par sandbox.sh bundle opa,etcd) et sont contrôlés avec la somme sha256 amont du manifeste. Si un outil ou un artefact manque (go, sshd, ssh, OPA, etcd), le test échoue avec un message clair. Il n'est jamais ignoré.

Étapes dans la console​

ÉtapeÉcranRouteCe que l'étape prouve
1. Créer le clusterClustersPOST /api/v1/clustersLe cluster apparaît dans la liste
2. Préflight des deux machinesHôtes, assistant d'amorçage SSHPOST /api/v1/hosts/preflightConnexion SSH avec la clé d'hôte vérifiée, DNS, disques ; rapport avec empreinte reproductible
3. Plan d'amorçageAssistant d'amorçage SSHPOST /api/v1/hosts/bootstrap/planStrictHostKeyChecking=yes, liste sudo exacte
4. Amorçage et enrôlementAssistant d'amorçage SSHPOST /api/v1/clusters/{id}/bootstrap-tokensLe paquet agent signé est copié par SSH sur chaque machine, l'accès temporaire est retiré, chaque jeton à usage unique enrôle un hôte et ne sert pas une deuxième fois
5. Battements des agentsAgents de nœudGET /api/v1/agents/nodes/{id}, PATCH /api/v1/hosts/{id}Les deux agents Go envoient leurs battements, les deux hôtes sont trusted dans le cluster, zones zone-a et zone-b
6. Installer un service et choisir 2 instancesServices, assistant d'installationPOST /api/v1/clusters/{id}/services/validate, POST /api/v1/clusters/{id}/servicesOPA du catalogue en mode replicated avec 2 instances : l'opération réussit, host_components compte 2 instances started avec un PID, une par hôte, et /health répond 200 sur chacune
7. Haute disponibilité validéeDisponibilitéPOST /api/v1/ha-groups, POST /api/v1/ha-groups/{id}/test-failover, GET /api/v1/ha-groups/{id}/operationsLe groupe couvre les 2 instances (membres liés à leur hôte) et vaut HA_CONFIGURED. Le test de bascule arrête réellement l'instance de l'hôte 1 par l'opération stop de l'agent, lance le test fonctionnel (smoke) du catalogue sur l'instance de l'hôte 2, redémarre l'instance arrêtée, puis enregistre le résultat : HA_VALIDATED seulement après ce passage, avec les opérations, l'état stopped observé et l'interruption mesurée
8. Sauvegarde et restauration d'un serviceSauvegardes du clusterPOST /api/v1/clusters/{id}/backups, POST /api/v1/clusters/{id}/backups/{bid}/restoreetcd du catalogue : une clé connue est écrite, sauvegardée, supprimée, restaurée, puis relue par le test avec etcdctl et comparée à l'original ; restore_status vaut passed
9. Centre de sauvegarde et restaurationCentre de sauvegardePOST /api/v1/backup-plans, /backups, /verify, POST /api/v1/restores/plan, /approve, /executeArtefact chiffré, somme sha256, vérification passed ; le demandeur ne peut ni approuver ni exécuter seul ; deux approbateurs distincts ; exécution vérifiée

Un deuxième test (test_unbound_failover_request_is_not_a_validation) vérifie qu'un groupe non lié à un service installé garde un test demandé en pending sans validation, qu'un hôte inconnu est refusé (host_not_found) et que deux membres sur le même hôte sont refusés (ha_members_same_host).

Interruption mesurée du test de bascule​

L'interruption est le temps entre la fin de l'arrêt de l'instance et la fin du premier test fonctionnel réussi sur l'autre instance. Valeurs observées sur trois exécutions du test : 0,804 s, 0,922 s et 0,127 s. Elle dépend du rythme du moteur et des agents (interrogation toutes les secondes), pas d'un basculement de trafic : OPA est sans état et les deux instances répondent en parallèle.

Exécution réelle sur le bac à sable​

Le bac à sable a été redémarré avec le code du jour (sandbox.sh down, puis up), puis le script /tmp/nm_live_journey.py a joué le parcours par l'API de la console, avec connexion par mot de passe et TOTP comme la console. Contrôle navigateur non demandé par l'utilisateur.

ÉtapeRésultat sur le bac à sable
Connexion mot de passe et TOTPRéussie
Clustersandbox, mode sandbox
Hôte enrôlé et battementsUn hôte gadhgadhi.localdomain, état healthy, dernier battement reçu
Jeton d'enrôlement à usage uniqueCréé par la console
Services installésetcd, nats, node-exporter, opa et openfga à l'état started
Test fonctionnel OPAOpération succeeded
Sauvegarde et restauration etcd avec données reluesClé écrite (OK), sauvegarde succeeded, clé supprimée puis absente, restauration succeeded, valeur relue égale à l'original, restore_status passed
2 instances, groupe HA et test de basculeNon joué : le bac à sable n'a qu'un hôte et un agent. Le groupe avec deux membres sur cet hôte est refusé avec ha_members_same_host (422). Ce parcours est prouvé par le test automatisé avec deux machines
Préflight et amorçage SSHNon joués : l'amorçage de la machine locale installerait l'agent dans /usr/local/bin et /etc/systemd. Prouvés par le test automatisé

Avant la correction ha_members_same_host, la première exécution réelle a créé sur le bac à sable un groupe opa-live-1793391747 avec ses deux membres sur le même hôte, affiché HA_CONFIGURED à tort. C'est ce défaut qui a été corrigé ; ce groupe reste dans la base du bac à sable (aucune route de suppression), il disparaît avec sandbox.sh down --purge.

Limites sur une seule machine​

  • Les deux hôtes tournent sur la même machine : l'hôte 1 répond sur 127.0.0.1 avec un FQDN aléatoire, l'hôte 2 sur une adresse 127.x.y.z aléatoire qui est aussi son FQDN, car etcd annonce le FQDN de son membre et un nom aléatoire ne se résoudrait pas. Les domaines de panne zone-a et zone-b sont logiques.
  • Les agents tournent en mode processus sous un préfixe de fichiers dans /tmp, sans systemd ni sudo.
  • Le sshd sans privilège n'accepte que le compte qui le lance : l'amorçage est prouvé avec use_sudo=false.
  • etcd est restauré en membre unique (son script de restauration refuse un cluster de plusieurs membres).
  • La cible du Centre de sauvegarde est un fichier local chiffré, déclaré comme SeaweedFS ; son exécution de restauration vérifie et déchiffre l'artefact. La restauration des données d'un service est prouvée par l'étape 8.

Lancer la recette​

cd navin-manager/server
.venv/bin/python -m pytest -q tests/test_e2e_infrastructure_journey.py