portabilite : monter un SECOND tenant revele trois defauts invisibles
Les deux reconstructions de la semaine rebatissaient Chezlepro sur son propre materiel : une preuve de reproductibilite, pas de portabilite. La vraie epreuve est un second tenant — Technolibre, index 11, plan distinct, voute separee, meme cluster. 1. LE CLONAGE RESOLVAIT PAR NOM. proxmox_kvm cherche une VM portant le `name` demande ; s'il en trouve une il rend `ok` et ne clone RIEN — aucune tache cote cluster. Or les noms courts sont VOLONTAIREMENT identiques d'un tenant a l'autre. `backup-01` de Technolibre tombait sur celui de Chezlepro. Mesure : id-ldap-01 et sup-01 (noms absents chez Chezlepro) passaient du premier coup, backup-01 echouait toujours. Le clonage cible desormais par VMID, via l'API. Deux defauts de ce correctif, trouves en le mesurant : le corps assemble en Jinja avec `>-` rendait une CHAINE et perdait le `pool` (VM nee hors de son pool, sans un mot) ; et l'application du gabarit de calcul expirait a 5 s, laissant la VM aux valeurs du gabarit — 2 coeurs/2 Go au lieu du plan, en silence. Corps en mapping YAML avec omit ; six tentatives espacees. Et `no_log` a masque la cause au moment ou elle servait. Les deux attentes disent maintenant ce qu'elles ont constate. 2. LE GUI DETRUISAIT DES INTRANTS. Dans ecrire_intrants, `identite` etait la seule branche sur quatre a ecrire par-dessus le disque au lieu de fusionner. Un enregistrement a supprime dns_amorcage et amorcage_acces_courriel. Le meme geste sur Chezlepro aurait mange les memes cles. 3. LE VERROU DE `raser` N'ETAIT PROUVE QUE POUR UN TENANT. Le faux cluster codait en dur les VMID de Chezlepro ; monte ailleurs, le test rendait 0 au lieu de 2. Il fabrique desormais la collision sur le plan courant. Verifie : prouver.py 34 OK sur Technolibre, test_raser vert sur les deux tenants, ansible-lint production sur le playbook de clonage. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
ac85278366
commit
3b16b8845d
4 changed files with 214 additions and 28 deletions
65
CHANGELOG.md
65
CHANGELOG.md
|
|
@ -1,5 +1,70 @@
|
||||||
# CHANGELOG — Set-OPS
|
# CHANGELOG — Set-OPS
|
||||||
|
|
||||||
|
## 2026-08-10 — Épreuve de portabilité : monter un SECOND tenant révèle trois défauts invisibles
|
||||||
|
|
||||||
|
Les deux reconstructions from-zero de la semaine rebâtissaient **Chezlepro** sur son propre
|
||||||
|
matériel : une preuve de reproductibilité, pas de portabilité. La vraie épreuve est un
|
||||||
|
**second tenant** — Technolibre, index 11, plan distinct (`id-ldap-01`, `id-sso-01`,
|
||||||
|
`sup-01`… là où Chezlepro a `idm-01`, `mon-01`), voûte séparée, sur le même cluster.
|
||||||
|
|
||||||
|
Elle a trouvé en une heure trois défauts qu'un seul tenant ne pouvait pas révéler.
|
||||||
|
|
||||||
|
### 1. Le clonage résolvait par NOM — et ne faisait rien
|
||||||
|
|
||||||
|
`community.general.proxmox_kvm` cherche d'abord une VM portant le `name` demandé. S'il en
|
||||||
|
trouve une, il conclut « elle existe déjà », rend **`ok`** et ne clone **rien** — aucune tâche
|
||||||
|
n'apparaît même côté cluster. Or les noms courts sont **volontairement identiques d'un tenant
|
||||||
|
à l'autre** : même fonction, même nom, c'est le pool qui restitue l'appartenance. Le premier
|
||||||
|
clone de Technolibre, `backup-01`, est donc tombé sur le `backup-01` de Chezlepro, n'a rien
|
||||||
|
fait, et l'attente a expiré sur une configuration qui n'existerait jamais.
|
||||||
|
|
||||||
|
**Mesuré, pas déduit** : `id-ldap-01` et `sup-01` — noms que Chezlepro n'a pas — se sont
|
||||||
|
créés du premier coup ; `backup-01` échouait systématiquement. Zéro VM créée, zéro tâche
|
||||||
|
`qmclone` au cluster.
|
||||||
|
|
||||||
|
Le clonage passe désormais par un appel d'API **ciblé par VMID** : recensement des VM, puis
|
||||||
|
`POST /nodes/<n>/qemu/<gabarit>/clone` seulement si le VMID cible est libre. Plus aucune
|
||||||
|
résolution par nom.
|
||||||
|
|
||||||
|
**Deux défauts de ce correctif, trouvés en le mesurant** — et tous deux du même genre que ce
|
||||||
|
qu'il corrige :
|
||||||
|
|
||||||
|
- le corps de la requête était assemblé en Jinja avec `>-`, ce qui rend une **chaîne** : le
|
||||||
|
`pool` s'est perdu en route et la VM est née hors de son pool, sans un mot. Réécrit en
|
||||||
|
mapping YAML avec `omit` ;
|
||||||
|
- l'application du gabarit de calcul expirait à 5 s de lecture — le nœud vient de terminer un
|
||||||
|
clone complet. La VM restait aux valeurs du gabarit (2 cœurs / 2 Go au lieu du plan), **en
|
||||||
|
silence**. Six tentatives espacées de 10 s.
|
||||||
|
|
||||||
|
Et `no_log: true` a masqué la cause au moment précis où elle servait : l'échec se lisait
|
||||||
|
« the output has been hidden », et il a fallu interroger le cluster à la main. Les deux
|
||||||
|
attentes disent maintenant ce qu'elles ont constaté, sans révéler l'en-tête d'autorisation.
|
||||||
|
|
||||||
|
### 2. Le GUI détruisait des intrants
|
||||||
|
|
||||||
|
Dans `ecrire_intrants`, la branche `identite` était **la seule sur quatre** à écrire par-dessus
|
||||||
|
le disque au lieu de fusionner. Un enregistrement du panneau a supprimé `dns_amorcage` et
|
||||||
|
`amorcage_acces_courriel` de Technolibre. Sans le premier, une VM naît sans résolution et
|
||||||
|
`apt` ne peut rien installer ; sans le second, le déploiement s'arrête sur la garde de
|
||||||
|
`amorcage_acces` (D-72). **Le même geste sur Chezlepro aurait mangé les mêmes clés.**
|
||||||
|
|
||||||
|
### 3. Le verrou de `raser` n'était prouvé que pour un tenant
|
||||||
|
|
||||||
|
Le faux cluster de `scripts/tests/test_raser.py` codait en dur les VMID de Chezlepro. Monté
|
||||||
|
sur un autre tenant, le test rendait `0` au lieu de `2` — « aucune VM du plan n'est présente,
|
||||||
|
rien à faire ». Le verrou de la seule commande destructive du moteur passait donc au vert
|
||||||
|
sans rien éprouver. Le faux cluster **fabrique désormais la collision sur le plan courant**,
|
||||||
|
quel qu'il soit.
|
||||||
|
|
||||||
|
### Ce qui relevait des données du tenant, pas du moteur
|
||||||
|
|
||||||
|
L'instance datait d'avant plusieurs évolutions, et les preuves statiques les ont toutes
|
||||||
|
attrapées **avant** le déploiement : `client_unbound` déclaré au plan alors qu'il est devenu
|
||||||
|
une intégration universelle ; `amorcage_acces_courriel` absent ; gabarit `99999` alors que le
|
||||||
|
recapturé porte `99998` — le premier clone aurait échoué ; `parefeu_interface: false`, qui
|
||||||
|
aurait laissé le pare-feu est-ouest inerte sans le dire ; `collab-01` à 1 cœur / 1 Go au lieu
|
||||||
|
du dimensionnement dérivé.
|
||||||
|
|
||||||
## 2026-08-10 — P34 : la convention « chaque document déclare son lecteur » devient une garde
|
## 2026-08-10 — P34 : la convention « chaque document déclare son lecteur » devient une garde
|
||||||
|
|
||||||
La refonte de ce matin posait une convention. Une convention qu'on n'outille pas tient tant
|
La refonte de ce matin posait une convention. Une convention qu'on n'outille pas tient tant
|
||||||
|
|
|
||||||
|
|
@ -158,27 +158,86 @@
|
||||||
state: present
|
state: present
|
||||||
when: proxmox_clone_pool | default('', true) | length > 0
|
when: proxmox_clone_pool | default('', true) | length > 0
|
||||||
|
|
||||||
- name: Cloner la VM depuis le modele
|
# LE CLONE SE CIBLE PAR VMID, JAMAIS PAR NOM — et c'est tout l'objet de ces trois
|
||||||
community.general.proxmox_kvm:
|
# taches. `community.general.proxmox_kvm` cherche d'abord une VM portant le `name`
|
||||||
api_host: "{{ proxmox_api_host_effectif }}"
|
# demande ; s'il en trouve une, il conclut « elle existe deja », rend `ok` et ne
|
||||||
api_port: "{{ proxmox_api_port_effectif | int if proxmox_api_port_effectif | length > 0 else omit }}"
|
# clone RIEN. Aucune tache n'apparait meme cote cluster.
|
||||||
api_user: "{{ proxmox_api_user_effectif }}"
|
#
|
||||||
api_token_id: "{{ proxmox_api_token_id_effectif }}"
|
# Or les noms courts sont VOLONTAIREMENT identiques d'un tenant a l'autre (meme
|
||||||
api_token_secret: "{{ proxmox_api_token_secret_effectif }}"
|
# fonction, meme nom) : c'est le pool qui restitue l'appartenance. Le premier clone
|
||||||
|
# de Technolibre — `backup-01` — est donc tombe sur le `backup-01` de Chezlepro et
|
||||||
|
# n'a rien fait, puis l'attente a expire sur une configuration qui n'existerait
|
||||||
|
# jamais. Mesure du 2026-08-10 : zero VM creee, zero tache `qmclone` au cluster.
|
||||||
|
#
|
||||||
|
# Le defaut etait invisible tant qu'un seul tenant existait. C'est exactement ce
|
||||||
|
# qu'une epreuve de portabilite doit trouver.
|
||||||
|
- name: Recenser les VM du cluster (pour cibler par VMID)
|
||||||
|
vars:
|
||||||
|
proxmox_api_racine: "https://{{ proxmox_api_host_effectif }}:{{ proxmox_api_port_effectif | default(8006, true) }}"
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ proxmox_api_racine }}/api2/json/cluster/resources?type=vm"
|
||||||
|
method: GET
|
||||||
|
headers:
|
||||||
|
Authorization: >-
|
||||||
|
PVEAPIToken={{ proxmox_api_user_effectif }}!{{ proxmox_api_token_id_effectif }}={{ proxmox_api_token_secret_effectif }}
|
||||||
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
|
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
|
||||||
node: "{{ proxmox_clone_noeud }}"
|
status_code: 200
|
||||||
clone: "{{ proxmox_clone_source_nom | default('modele-debian13') }}"
|
register: proxmox_inventaire_vm
|
||||||
vmid: "{{ proxmox_clone_vmid_modele | int }}"
|
changed_when: false
|
||||||
newid: "{{ proxmox_clone_vmid | int }}"
|
no_log: true
|
||||||
name: "{{ proxmox_clone_nom }}"
|
|
||||||
cores: "{{ proxmox_clone_coeurs | int if proxmox_clone_coeurs is defined and proxmox_clone_coeurs | string | length > 0 else omit }}"
|
- name: Retenir si le VMID cible est deja pris
|
||||||
memory: "{{ proxmox_clone_memoire | int if proxmox_clone_memoire is defined and proxmox_clone_memoire | string | length > 0 else omit }}"
|
ansible.builtin.set_fact:
|
||||||
storage: "{{ proxmox_clone_stockage | default(omit, true) }}"
|
proxmox_clone_deja_la: >-
|
||||||
format: "{{ proxmox_clone_format | default(omit, true) }}"
|
{{ (proxmox_inventaire_vm.json.data | default([]))
|
||||||
full: "{{ proxmox_clone_complet | default(true) | bool }}"
|
| selectattr('vmid', 'defined')
|
||||||
# A la CREATION seulement : l'API ne permet pas de le changer ensuite.
|
| selectattr('vmid', 'equalto', proxmox_clone_vmid | int)
|
||||||
pool: "{{ proxmox_clone_pool | default(omit, true) }}"
|
| list | length > 0 }}
|
||||||
timeout: "{{ proxmox_clone_timeout | default(600) | int }}"
|
|
||||||
|
- name: Cloner la VM depuis le modele (API directe, cible par VMID)
|
||||||
|
vars:
|
||||||
|
proxmox_api_racine: "https://{{ proxmox_api_host_effectif }}:{{ proxmox_api_port_effectif | default(8006, true) }}"
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ proxmox_api_racine }}/api2/json/nodes/{{ proxmox_clone_noeud }}/qemu/{{ proxmox_clone_vmid_modele | int }}/clone"
|
||||||
|
method: POST
|
||||||
|
headers:
|
||||||
|
Authorization: >-
|
||||||
|
PVEAPIToken={{ proxmox_api_user_effectif }}!{{ proxmox_api_token_id_effectif }}={{ proxmox_api_token_secret_effectif }}
|
||||||
|
body_format: form-urlencoded
|
||||||
|
# Corps ecrit en MAPPING YAML, pas assemble en Jinja : une expression `>-` rend
|
||||||
|
# une CHAINE, et le premier jet a ainsi perdu le `pool` en route — la VM est
|
||||||
|
# nee hors de son pool, sans un mot. `omit` retire proprement les cles vides.
|
||||||
|
body:
|
||||||
|
newid: "{{ proxmox_clone_vmid | int }}"
|
||||||
|
name: "{{ proxmox_clone_nom }}"
|
||||||
|
full: "{{ 1 if (proxmox_clone_complet | default(true) | bool) else 0 }}"
|
||||||
|
storage: "{{ proxmox_clone_stockage | default(omit, true) }}"
|
||||||
|
format: "{{ proxmox_clone_format | default(omit, true) }}"
|
||||||
|
# A la CREATION seulement : l'API ne sait pas changer le pool d'une VM
|
||||||
|
# existante par cet appel (il faut passer par le membre du pool).
|
||||||
|
pool: "{{ proxmox_clone_pool | default(omit, true) }}"
|
||||||
|
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
|
||||||
|
status_code: 200
|
||||||
|
register: proxmox_clone_lance
|
||||||
|
# Le corps ne porte aucun secret, mais l'en-tete si : `no_log` masquerait alors
|
||||||
|
# l'erreur de l'API, qui est justement ce qu'on veut lire. On ne journalise donc
|
||||||
|
# que l'ECHEC, et sans les en-tetes.
|
||||||
|
failed_when: false
|
||||||
|
no_log: true
|
||||||
|
when: not proxmox_clone_deja_la
|
||||||
|
|
||||||
|
- name: Dire pourquoi le clonage a echoue, s'il a echoue
|
||||||
|
ansible.builtin.fail:
|
||||||
|
msg: >-
|
||||||
|
Le clonage de {{ proxmox_clone_nom }} (VMID {{ proxmox_clone_vmid }}) depuis le
|
||||||
|
gabarit {{ proxmox_clone_vmid_modele }} a ete refuse par l'API Proxmox
|
||||||
|
(HTTP {{ proxmox_clone_lance.status | default('?') }}).
|
||||||
|
Verifier que le gabarit existe sur {{ proxmox_clone_noeud }}, que le pool
|
||||||
|
{{ proxmox_clone_pool | default('(aucun)', true) }} existe, et que le VMID
|
||||||
|
cible est libre.
|
||||||
|
when:
|
||||||
|
- not proxmox_clone_deja_la
|
||||||
|
- proxmox_clone_lance.status | default(0) != 200
|
||||||
|
|
||||||
# L'API de CLONAGE ignore `cores` et `memory` : le clone herite du gabarit, et le
|
# L'API de CLONAGE ignore `cores` et `memory` : le clone herite du gabarit, et le
|
||||||
# plan est contredit sans un mot. Mesure du 2026-08-06 : plan 1 coeur / 1024 Mo,
|
# plan est contredit sans un mot. Mesure du 2026-08-06 : plan 1 coeur / 1024 Mo,
|
||||||
|
|
@ -205,8 +264,23 @@
|
||||||
retries: 30
|
retries: 30
|
||||||
delay: 5
|
delay: 5
|
||||||
changed_when: false
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
no_log: true
|
no_log: true
|
||||||
|
|
||||||
|
# `no_log` masque la cause au moment ou on en a besoin : le 2026-08-10, l'echec de
|
||||||
|
# cette attente s'est lu « the output has been hidden », et il a fallu interroger le
|
||||||
|
# cluster a la main pour comprendre que le clone n'avait jamais eu lieu. On rend
|
||||||
|
# donc le verdict lisible ici, sans reveler l'en-tete d'autorisation.
|
||||||
|
- name: Dire ce que l'attente du clone a constate
|
||||||
|
ansible.builtin.fail:
|
||||||
|
msg: >-
|
||||||
|
La configuration de {{ proxmox_clone_nom }} (VMID {{ proxmox_clone_vmid }})
|
||||||
|
n'existe toujours pas apres {{ 30 * 5 }} s sur {{ proxmox_clone_noeud }}.
|
||||||
|
Le clonage n'a donc pas abouti — verifier les taches du cluster
|
||||||
|
(`/cluster/tasks`) : si aucune tache de clonage n'apparait, c'est que rien
|
||||||
|
n'a ete demande a l'API.
|
||||||
|
when: proxmox_clone_conf.status | default(0) != 200
|
||||||
|
|
||||||
- name: Appliquer le gabarit de calcul du plan (le clonage ne le fait pas)
|
- name: Appliquer le gabarit de calcul du plan (le clonage ne le fait pas)
|
||||||
community.general.proxmox_kvm:
|
community.general.proxmox_kvm:
|
||||||
api_host: "{{ proxmox_api_host_effectif }}"
|
api_host: "{{ proxmox_api_host_effectif }}"
|
||||||
|
|
@ -220,6 +294,14 @@
|
||||||
cores: "{{ proxmox_clone_coeurs | int }}"
|
cores: "{{ proxmox_clone_coeurs | int }}"
|
||||||
memory: "{{ proxmox_clone_memoire | int }}"
|
memory: "{{ proxmox_clone_memoire | int }}"
|
||||||
update: true
|
update: true
|
||||||
|
# Le noeud vient de finir un clone COMPLET : son API repond encore lentement, et
|
||||||
|
# la lecture expire a 5 s. Mesure du 2026-08-10 : « Read timed out » sur le tout
|
||||||
|
# premier clone, VM creee mais laissee aux valeurs du gabarit — 2 coeurs / 2 Go au
|
||||||
|
# lieu du plan. Une VM sous-dimensionnee en silence est pire qu'un echec franc.
|
||||||
|
register: proxmox_gabarit_calcul
|
||||||
|
until: proxmox_gabarit_calcul is succeeded
|
||||||
|
retries: 6
|
||||||
|
delay: 10
|
||||||
when:
|
when:
|
||||||
- proxmox_clone_coeurs is defined and proxmox_clone_coeurs | string | length > 0
|
- proxmox_clone_coeurs is defined and proxmox_clone_coeurs | string | length > 0
|
||||||
- proxmox_clone_memoire is defined and proxmox_clone_memoire | string | length > 0
|
- proxmox_clone_memoire is defined and proxmox_clone_memoire | string | length > 0
|
||||||
|
|
|
||||||
|
|
@ -613,8 +613,20 @@ def ecrire_intrants(payload: dict) -> None:
|
||||||
par_fichier.setdefault(fichier_de[variable], {})[variable] = _coercer_intrant(types[variable], valeur)
|
par_fichier.setdefault(fichier_de[variable], {})[variable] = _coercer_intrant(types[variable], valeur)
|
||||||
|
|
||||||
if "identite" in par_fichier:
|
if "identite" in par_fichier:
|
||||||
|
# Fusion, comme les trois autres fichiers. C'etait la SEULE branche a ecrire
|
||||||
|
# par-dessus le disque, et elle perdait donc en silence tout intrant absent du
|
||||||
|
# schema du panneau. Mesure du 2026-08-10, au pre-vol de l'epreuve de
|
||||||
|
# portabilite : un enregistrement du panneau a supprime `dns_amorcage` et
|
||||||
|
# `amorcage_acces_courriel` de Technolibre. Le premier laisse une VM naitre sans
|
||||||
|
# resolution — `apt` ne peut alors rien installer ; le second arrete le
|
||||||
|
# deploiement sur la garde de `amorcage_acces` (D-72).
|
||||||
|
#
|
||||||
|
# Le meme geste sur Chezlepro aurait mange les memes cles. Un panneau qui ne
|
||||||
|
# connait pas une valeur ne doit pas avoir le droit de la detruire.
|
||||||
|
fusion = _lire_yaml_dict(INTRANTS_IDENTITE) # preserve les cles non gerees
|
||||||
|
fusion.update(par_fichier["identite"])
|
||||||
_ecrire_intrants_fichier(
|
_ecrire_intrants_fichier(
|
||||||
INTRANTS_IDENTITE, par_fichier["identite"],
|
INTRANTS_IDENTITE, fusion,
|
||||||
"# Intrants d'IDENTITE de l'instance — SOURCE UNIQUE, partagee par tous les envs.\n"
|
"# Intrants d'IDENTITE de l'instance — SOURCE UNIQUE, partagee par tous les envs.\n"
|
||||||
"# Edite par le panneau « Intrants de base » du GUI (make inventaire-ui).\n"
|
"# Edite par le panneau « Intrants de base » du GUI (make inventaire-ui).\n"
|
||||||
"# Reference via symlink depuis inventories/<env>/group_vars/all/10-intrants.yml.\n---\n")
|
"# Reference via symlink depuis inventories/<env>/group_vars/all/10-intrants.yml.\n---\n")
|
||||||
|
|
|
||||||
|
|
@ -7,24 +7,51 @@ donc la logique derriere un faux cluster.
|
||||||
Le danger n'est pas theorique : le 2026-08-07, une VM heritee portait un VMID du plan sous
|
Le danger n'est pas theorique : le 2026-08-07, une VM heritee portait un VMID du plan sous
|
||||||
le nom `web-frontal-01`, et `proxmox_kvm` avait rapporte `ok` sans rien faire. Rase sans ce
|
le nom `web-frontal-01`, et `proxmox_kvm` avait rapporte `ok` sans rien faire. Rase sans ce
|
||||||
controle, on detruisait la machine de quelqu'un d'autre.
|
controle, on detruisait la machine de quelqu'un d'autre.
|
||||||
|
|
||||||
|
LE FAUX CLUSTER SE DERIVE DE L'INSTANCE MONTEE, il ne code aucun VMID en dur. La premiere
|
||||||
|
version en portait deux, ceux de Chezlepro (index 17) : le test passait pour ce tenant et
|
||||||
|
rendait 0 — « aucune VM du plan n'est presente, rien a faire » — des qu'on montait un tenant
|
||||||
|
dont le plan derive d'autres VMID. Trouve le 2026-08-10 en basculant sur Technolibre, au
|
||||||
|
premier pas de l'epreuve de portabilite. Un verrou prouve pour un seul tenant n'est pas
|
||||||
|
prouve : c'est exactement ce que le multi-instance est cense empecher.
|
||||||
"""
|
"""
|
||||||
import sys, pathlib
|
import pathlib
|
||||||
|
import sys
|
||||||
|
|
||||||
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
|
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
|
||||||
import raser
|
import raser # noqa: E402
|
||||||
|
|
||||||
|
BASE, NOM = raser.instance_active()
|
||||||
|
COUPLES = raser.plan_derive(BASE)
|
||||||
|
if not COUPLES:
|
||||||
|
raise SystemExit("Plan sans serveur : rien a eprouver.")
|
||||||
|
|
||||||
|
# La collision est FABRIQUEE sur le premier hote du plan courant, quel qu'il soit ; un
|
||||||
|
# second hote, correctement nomme, garantit que le refus vient bien de la collision et
|
||||||
|
# non d'un cluster vide.
|
||||||
|
(NOM_1, VMID_1), *reste = COUPLES
|
||||||
|
NOM_2, VMID_2 = reste[0] if reste else (NOM_1 + "-bis", VMID_1 + 1)
|
||||||
|
|
||||||
|
|
||||||
class FauxCluster:
|
class FauxCluster:
|
||||||
"""Le cluster repond que 117603101 s'appelle « vm-heritee-de-2019 »."""
|
"""Le cluster repond que `VMID_1` porte le nom d'une VM etrangere au plan."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def depuis_hebergeur(cls):
|
def depuis_hebergeur(cls):
|
||||||
return cls(), None
|
return cls(), None
|
||||||
|
|
||||||
def __call__(self, chemin, methode="GET", corps=None):
|
def __call__(self, chemin, methode="GET", corps=None):
|
||||||
return [{"type": "qemu", "vmid": 117603101, "name": "vm-heritee-de-2019", "node": "asgard"},
|
return [{"type": "qemu", "vmid": VMID_1, "name": "vm-heritee-de-2019",
|
||||||
{"type": "qemu", "vmid": 117101101, "name": "infra-edge-01", "node": "asgard"}]
|
"node": "asgard"},
|
||||||
|
{"type": "qemu", "vmid": VMID_2, "name": NOM_2, "node": "asgard"}]
|
||||||
|
|
||||||
def rate(self, rep):
|
def rate(self, rep):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
raser.Cluster = FauxCluster
|
raser.Cluster = FauxCluster
|
||||||
code = raser.main(["--instance", "chezlepro", "--confirmer"])
|
code = raser.main(["--instance", NOM, "--confirmer"])
|
||||||
print(f"\n>>> code de sortie : {code} (2 = refus attendu)")
|
print(f"\n>>> instance montee : {NOM} | collision fabriquee sur {VMID_1} ({NOM_1})")
|
||||||
|
print(f">>> code de sortie : {code} (2 = refus attendu)")
|
||||||
assert code == 2, "LE VERROU N'A PAS TENU"
|
assert code == 2, "LE VERROU N'A PAS TENU"
|
||||||
print(">>> le verrou tient : aucune VM n'aurait ete touchee")
|
print(">>> le verrou tient : aucune VM n'aurait ete touchee")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue