contexte : materialisation M2, les commandes du site nomment leur locataire
locataire-creer, locataire-raser et placement-plan TENANT= lisent la face reseau du locataire, sans monter son depot comme instance. flotte-creer, creer-vm et raser acceptent TENANT= ; sans lui, l'ancien chemin est inchange. P94 : creer, raser et placer par la face visent les memes machines, VMID, valeurs et ponts que l'ancien chemin, chacun mesure par sa commande lancee a part. 116 controles dans test_contexte.py, temoins compris. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
4532a98707
commit
8294e5dc71
11 changed files with 274 additions and 21 deletions
|
|
@ -188,7 +188,7 @@ Si `ansible-lint` n’est pas disponible, le signaler clairement. Ne pas invente
|
|||
## Écrire, puis relire (D-68)
|
||||
|
||||
`--syntax-check` et `ansible-lint` prouvent que le dépôt est cohérent **avec lui-même**.
|
||||
C'est aussi ce que font les 93 preuves de `make prouver` : elles lisent le dépôt, sans le
|
||||
C'est aussi ce que font les 94 preuves de `make prouver` : elles lisent le dépôt, sans le
|
||||
moindre appel réseau. **Aucune ne demande au système déployé s'il ressemble à ce que le
|
||||
dépôt annonce.**
|
||||
|
||||
|
|
|
|||
36
CHANGELOG.md
36
CHANGELOG.md
|
|
@ -1,5 +1,41 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-10-05 (98) — Matérialisation, M2 : les commandes du site nomment leur locataire
|
||||
|
||||
**Le chemin** (`docs/conception-contextes.md`, matérialisation M2) : le runner du site crée,
|
||||
rase et confronte le placement d'un locataire **nommé**, d'après sa face réseau, sans monter
|
||||
son dépôt comme instance.
|
||||
|
||||
**Fait** :
|
||||
- `make locataire-creer TENANT=<dépôt> CONFIRMER=true` remplace
|
||||
`SETOPS_INSTANCE=… make flotte-creer`. `flotte-creer` et `creer-vm` acceptent `TENANT=` :
|
||||
la liste des machines actives (`contexte.py --hotes-actifs`) et les paramètres de clonage
|
||||
(`contexte.py --parametres-clonage … --hote`) viennent de la face. Sans `TENANT`, l'ancien
|
||||
chemin est inchangé, et l'instance reste exigée.
|
||||
- `make locataire-raser TENANT=<dépôt> CONFIRMER=true INSTANCE=<nom court>` (et
|
||||
`raser TENANT=`) : `raser.py --locataire` prend les couples (hôte, VMID) dans la face. Les
|
||||
quatre verrous restent : liste, refus d'un VMID au nom étranger, `CONFIRMER`, nom tapé.
|
||||
- `make placement-plan TENANT=<dépôt>` : `devis_placement.py --locataire` lit les valeurs
|
||||
de placement (`placement`, désormais dans la face) et les ponts des machines publiées.
|
||||
- Les deux cibles entrent au runbook du site (P83).
|
||||
|
||||
**Éprouvé** :
|
||||
- Lignes de paramètres de clonage **identiques à l'octet** aux appels de l'ancien chemin, pour
|
||||
les 26 machines ; listes de machines actives identiques.
|
||||
- **P94**, « Créer, raser et placer un locataire par sa face visent les mêmes machines » :
|
||||
chaque intrant comparé à la commande de l'ancien chemin, lancée à part : `lister-actifs`
|
||||
(création), `plan_derive` (le plan, via `serveurs.py lister`) pour raser, `proxmox.yml` et
|
||||
les ponts dérivés de l'inventaire (placement). Aucun écart sur les deux locataires.
|
||||
- `test_contexte.py`, 116 contrôles : un VMID, un état, un pont, une valeur de placement
|
||||
altérés dans la face se voient, chacun du côté qu'il touche.
|
||||
- `make -n` : `TENANT=` appelle les nouvelles options sans instance montée ; sans `TENANT`
|
||||
et sans instance, refus.
|
||||
- `make verifier` conforme, **94/94**. Les trois documents comptent 94 preuves.
|
||||
|
||||
**Pas éprouvé ici** : la confrontation au cluster. Depuis le poste, l'API Proxmox refuse la
|
||||
connexion (errno 111) aux **deux** chemins ; une sortie identique sur un refus ne prouve rien.
|
||||
Elle se fera depuis le runner du site (M3), puis par reconstruction (M4).
|
||||
|
||||
## 2026-10-05 (97) — Matérialisation, M1 : la face publie les paramètres de clonage
|
||||
|
||||
**La décision de l'exploitant** : la seconde voie, fidèle au modèle — le **site** matérialise
|
||||
|
|
|
|||
36
Makefile
36
Makefile
|
|
@ -746,8 +746,8 @@ proxmox-fw-activer-vm: ansible-runtime ## Active le pare-feu d'UNE VM en le prou
|
|||
|
||||
.PHONY: proxmox-fw-eprouver proxmox-fw-activer-vm
|
||||
.PHONY: placement-plan sdn-plan sdn-appliquer
|
||||
placement-plan: ansible-runtime ## Le noeud, stockage, pont et gabarit du tenant existent-ils sur ce cluster ? (aucune ecriture)
|
||||
python3 scripts/devis_placement.py
|
||||
placement-plan: ansible-runtime ## Le noeud, stockage, pont et gabarit du tenant existent-ils sur ce cluster ? (aucune ecriture) [TENANT=<depot>]
|
||||
python3 scripts/devis_placement.py $(if $(TENANT),--locataire $(TENANT))
|
||||
|
||||
sdn-plan: ansible-runtime ## Ecart entre le SDN EVPN (+ sortie des VRF) et son devis (aucune ecriture)
|
||||
python3 scripts/appliquer_sdn.py
|
||||
|
|
@ -1138,14 +1138,23 @@ define VAULT_UNE_FOIS
|
|||
endef
|
||||
|
||||
|
||||
flotte-creer: _instance-requise ## Cree les VM manquantes de la flotte depuis le plan — PARALLELE=n
|
||||
# LE SITE MATERIALISE UN LOCATAIRE NOMME, D'APRES SA FACE (2026-10-05, docs/conception-contextes.md,
|
||||
# materialisation M2). Avec `TENANT=<depot>`, la liste des machines et leurs parametres de clonage
|
||||
# viennent de `face-reseau.yml` que le locataire publie — rendus a l'octet pres comme
|
||||
# `parametres-proxmox` (P93) — et aucune instance n'est montee. Sans `TENANT`, l'ancienne lecture
|
||||
# de l'instance montee (le poste, le laboratoire).
|
||||
LISTE_ACTIFS = $(if $(TENANT),python3 scripts/contexte.py --hotes-actifs $(TENANT),python3 scripts/inventory_host.py --inventaire $(INVENTAIRE_PRODUCTION) lister-actifs)
|
||||
PARAMS_CLONAGE = $(if $(TENANT),python3 scripts/contexte.py --parametres-clonage $(TENANT) --hote,python3 scripts/inventory_host.py --inventaire $(INVENTAIRE_PRODUCTION) parametres-proxmox --hote)
|
||||
INSTANCE_OU_LOCATAIRE = $(if $(TENANT),,_instance-requise)
|
||||
|
||||
flotte-creer: $(INSTANCE_OU_LOCATAIRE) ## Cree les VM manquantes de la flotte depuis le plan — PARALLELE=n
|
||||
@set -e; \
|
||||
if [[ "$(CONFIRMER)" != "true" ]]; then \
|
||||
printf '%s\n' 'Refus: creation de TOUTES les VM actives du plan (clone Proxmox).'; \
|
||||
printf '%s\n' 'Relancer avec CONFIRMER=true.'; \
|
||||
exit 2; \
|
||||
fi; \
|
||||
hotes="$$(python3 scripts/inventory_host.py --inventaire $(INVENTAIRE_PRODUCTION) lister-actifs)"; \
|
||||
hotes="$$($(LISTE_ACTIFS))"; \
|
||||
if [[ -z "$$hotes" ]]; then printf '%s\n' 'Refus: aucun hote actif dans le plan.'; exit 2; fi; \
|
||||
par="$(PARALLELE)"; par="$${par:-4}"; \
|
||||
tmp="$$(mktemp -d)"; trap 'rm -rf "$$tmp"' EXIT; \
|
||||
|
|
@ -1154,7 +1163,7 @@ flotte-creer: _instance-requise ## Cree les VM manquantes de la flotte depuis le
|
|||
n=0; \
|
||||
for h in $$hotes; do \
|
||||
printf '\n=== Creation VM: %s ===\n' "$$h"; \
|
||||
( $(MAKE) creer-vm HOTE="$$h" > "$$tmp/$$h.log" 2>&1; printf '%s' "$$?" > "$$tmp/$$h.rc" ) & \
|
||||
( $(MAKE) creer-vm HOTE="$$h" $(if $(TENANT),TENANT="$(TENANT)") > "$$tmp/$$h.log" 2>&1; printf '%s' "$$?" > "$$tmp/$$h.rc" ) & \
|
||||
n=$$((n+1)); \
|
||||
if (( n >= par )); then wait -n || true; n=$$((n-1)); fi; \
|
||||
done; \
|
||||
|
|
@ -1169,6 +1178,13 @@ flotte-creer: _instance-requise ## Cree les VM manquantes de la flotte depuis le
|
|||
if (( echec )); then printf '\nAu moins une VM n a pas ete creee — rien ne continue.\n'; exit 2; fi; \
|
||||
printf '\nToutes les VM actives sont creees.\n'
|
||||
|
||||
locataire-creer: ## Le SITE cree les VM d'un locataire d'apres sa face reseau — TENANT=<depot> CONFIRMER=true [PARALLELE=n]
|
||||
@if [[ -z "$(TENANT)" ]]; then printf '%s\n' 'Refus: TENANT=<depot du locataire> requis.'; exit 2; fi
|
||||
@$(MAKE) --no-print-directory flotte-creer TENANT="$(TENANT)" CONFIRMER="$(CONFIRMER)" PARALLELE="$(PARALLELE)"
|
||||
locataire-raser: ## DESTRUCTIF : le SITE rase les VM d'un locataire d'apres sa face reseau — TENANT=<depot> CONFIRMER=true INSTANCE=<nom court> [HOTE=h]
|
||||
@if [[ -z "$(TENANT)" ]]; then printf '%s\n' 'Refus: TENANT=<depot du locataire> requis.'; exit 2; fi
|
||||
@$(MAKE) --no-print-directory raser TENANT="$(TENANT)" CONFIRMER="$(CONFIRMER)" INSTANCE="$(INSTANCE)" HOTE="$(HOTE)"
|
||||
|
||||
# --- LES MACHINES DU SITE -----------------------------------------------------
|
||||
#
|
||||
# UN SITE N'EST PAS UN PLAN. Un tenant se derive de son `index` ; un site n'en a pas et
|
||||
|
|
@ -1434,8 +1450,8 @@ _attendre-flotte: ansible-runtime
|
|||
printf 'Flotte joignable.\n'
|
||||
|
||||
.PHONY: reconstruire
|
||||
raser: ansible-runtime _instance-requise ## DESTRUCTIF : detruit les VM derivees du plan — exige CONFIRMER=true ET INSTANCE=<nom>
|
||||
@python3 scripts/raser.py $(if $(INSTANCE),--instance $(INSTANCE)) $(if $(HOTE),--hote $(HOTE)) $(if $(filter true,$(CONFIRMER)),--confirmer)
|
||||
raser: ansible-runtime $(INSTANCE_OU_LOCATAIRE) ## DESTRUCTIF : detruit les VM derivees du plan — exige CONFIRMER=true ET INSTANCE=<nom> [TENANT=<depot>]
|
||||
@python3 scripts/raser.py $(if $(TENANT),--locataire $(TENANT)) $(if $(INSTANCE),--instance $(INSTANCE)) $(if $(HOTE),--hote $(HOTE)) $(if $(filter true,$(CONFIRMER)),--confirmer)
|
||||
|
||||
# `make flux` D'ABORD, et ce n'est pas une precaution : sans lui, `instance/flux-genere/`
|
||||
# est VIDE et le socle deploie nftables en `policy drop` SANS AUCUNE REGLE DERIVEE. La
|
||||
|
|
@ -1660,13 +1676,13 @@ cloner-vm: ansible-runtime ## Clone une VM depuis le gabarit dore — HOTE=<nom>
|
|||
fi; \
|
||||
ansible-playbook -i localhost, $(PLAYBOOK_PROXMOX_CLONER_VM) "$${vault_args[@]}" "$${extra_vars[@]}"
|
||||
|
||||
creer-vm: _instance-requise ## Cree une VM et attend qu'elle soit joignable — HOTE=<nom>
|
||||
creer-vm: $(INSTANCE_OU_LOCATAIRE) ## Cree une VM et attend qu'elle soit joignable — HOTE=<nom> [TENANT=<depot>]
|
||||
@set -e; \
|
||||
if [[ -z "$(HOTE)" ]]; then \
|
||||
printf '%s\n' 'Refus: relancer avec HOTE=nom_hote (declare dans le plan).'; \
|
||||
exit 2; \
|
||||
fi; \
|
||||
params="$$(python3 scripts/inventory_host.py --inventaire $(INVENTAIRE_PRODUCTION) parametres-proxmox --hote $(HOTE))"; \
|
||||
params="$$($(PARAMS_CLONAGE) $(HOTE))"; \
|
||||
eval "$$params"; \
|
||||
export SETOPS_CLES_AMORCAGE; \
|
||||
$(MAKE) cloner-vm \
|
||||
|
|
@ -1707,7 +1723,7 @@ creer-vm: _instance-requise ## Cree une VM et attend qu'elle soit joignable —
|
|||
@# ATTENDRE LA DISPONIBILITE est un autre geste, deplace chez `deployer` : cloud-init
|
||||
@# et dpkg se guettent juste avant de configurer, par celui qui va configurer.
|
||||
@if [[ "$(ATTENDRE)" != "false" ]]; then \
|
||||
params="$$(python3 scripts/inventory_host.py --inventaire $(INVENTAIRE_PRODUCTION) parametres-proxmox --hote $(HOTE))"; \
|
||||
params="$$($(PARAMS_CLONAGE) $(HOTE))"; \
|
||||
eval "$$params"; \
|
||||
python3 scripts/attendre_materialisation.py \
|
||||
--vmid "$$SETOPS_VMID" --ip "$$SETOPS_IP" --hote "$(HOTE)" \
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ make placement-plan # chaque VM est-elle là où le plan la met
|
|||
|
||||
## Le trou qu'il comble
|
||||
|
||||
`scripts/prouver.py` porte 93 preuves (dont une conditionnelle, sautée sans la clé de la voûte). Elles sont toutes **statiques** : elles lisent le
|
||||
`scripts/prouver.py` porte 94 preuves (dont une conditionnelle, sautée sans la clé de la voûte). Elles sont toutes **statiques** : elles lisent le
|
||||
dépôt. Zéro appel réseau, zéro SSH, zéro `ansible`. Elles établissent que le dépôt est
|
||||
cohérent **avec lui-même** — que les handlers existent, que les intrants ont un
|
||||
propriétaire, que rien n'est codé en dur.
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ Nommer un point ouvert vaut mieux qu'une ligne rassurante sans mécanisme derri
|
|||
Aucune de ces responsabilités n'est laissée à la parole :
|
||||
|
||||
```
|
||||
make prouver le dépôt est-il cohérent avec lui-même (93 preuves, zéro réseau)
|
||||
make prouver le dépôt est-il cohérent avec lui-même (94 preuves, zéro réseau)
|
||||
make remise-verifier le second temps de la remise est-il fait, ou en retard ? (P80)
|
||||
make certificats-plan ce que le disque porte contre ce que la mémoire sert
|
||||
make expositions-plan chaque service publié répond-il, et depuis où
|
||||
|
|
|
|||
|
|
@ -224,6 +224,21 @@ runbooks:
|
|||
- cible: routes-fabric-etat
|
||||
nature: mesure
|
||||
pourquoi: "Une route de zone manquante est invisible jusqu'au premier invite qui la traverse."
|
||||
- cible: locataire-creer
|
||||
nature: ecriture
|
||||
variables: [TENANT, PARALLELE]
|
||||
fixes: {CONFIRMER: "true"}
|
||||
pourquoi: >-
|
||||
Cloner les VM d'un locataire NOMME, d'apres la face reseau qu'il publie : le
|
||||
runner du site materialise sans monter son depot. Memes machines, memes
|
||||
parametres que `flotte-creer` sur l'instance montee (P93, P94).
|
||||
- cible: locataire-raser
|
||||
nature: destructif
|
||||
variables: [TENANT, INSTANCE]
|
||||
fixes: {CONFIRMER: "true"}
|
||||
pourquoi: >-
|
||||
Detruire les VM d'un locataire NOMME, d'apres sa face : les memes VMID que le
|
||||
plan derive (P94). Son nom court s'ecrit en toutes lettres, comme pour `raser`.
|
||||
- cible: site-raser
|
||||
nature: destructif
|
||||
variables: [SITE]
|
||||
|
|
|
|||
|
|
@ -575,6 +575,10 @@ class Locataire(Ecosysteme):
|
|||
if isinstance(c, dict)}},
|
||||
"dns_public": dns_public,
|
||||
"sauvegarde": {"cle_publique": cle_sauvegarde},
|
||||
# Ses valeurs de placement (noeud, stockage, pont par defaut, gabarit, options de
|
||||
# clone) : non sensibles, les jetons vivent dans sa voute. Le devis du placement les
|
||||
# confronte au cluster.
|
||||
"placement": _yaml(self.dossier_inventaire() / "group_vars" / "proxmox.yml"),
|
||||
"flux": flux,
|
||||
"conditions": conditions,
|
||||
}
|
||||
|
|
@ -1125,6 +1129,24 @@ def verifier_face_publiee(locataire: "Locataire") -> list[str]:
|
|||
return []
|
||||
|
||||
|
||||
def lignes_clonage(face: dict, hote: str) -> list[str]:
|
||||
"""Les lignes shell que `parametres-proxmox` rend pour cet hote — depuis la face publiee.
|
||||
Meme forme, a l'octet pres : `SETOPS_X='valeur'`, une valeur pouvant tenir sur plusieurs
|
||||
lignes (les cles d'amorcage). `make creer-vm TENANT=` les evalue comme les autres."""
|
||||
m = (face.get("machines") or {}).get(hote)
|
||||
if m is None:
|
||||
raise SystemExit(f"Hote absent de la face reseau publiee : {hote}.")
|
||||
c = m.get("clonage") or {}
|
||||
if "erreur" in c:
|
||||
raise SystemExit(c["erreur"])
|
||||
return [f"{k}='{v}'" for k, v in c.items()]
|
||||
|
||||
|
||||
def hotes_actifs(face: dict) -> list[str]:
|
||||
"""Les machines actives que la face publie, dans l'ordre de `inventory_host lister-actifs`."""
|
||||
return [h for h, m in (face.get("machines") or {}).items() if m.get("etat") == "actif"]
|
||||
|
||||
|
||||
def verifier_clonage(locataire: "Locataire", face: dict | None = None) -> list[str]:
|
||||
"""Machine par machine, la face publiee porte-t-elle les parametres de clonage que rend
|
||||
`inventory_host.py parametres-proxmox` sur l'inventaire du locataire — la commande meme que
|
||||
|
|
@ -1154,6 +1176,62 @@ def verifier_clonage(locataire: "Locataire", face: dict | None = None) -> list[s
|
|||
return ecarts
|
||||
|
||||
|
||||
def couples_a_raser(face: dict) -> list[tuple[str, int]]:
|
||||
"""(nom d'hote, vmid) des machines que la face publie : ce que `raser --locataire` detruit."""
|
||||
return sorted((h, int(m["materialisation"]["proxmox_vmid"]))
|
||||
for h, m in (face.get("machines") or {}).items()
|
||||
if (m.get("materialisation") or {}).get("proxmox_vmid"))
|
||||
|
||||
|
||||
def ponts_publies(face: dict) -> set[str]:
|
||||
"""Les ponts ou les machines publiees atterrissent : ce que `placement-plan` confronte."""
|
||||
return {str(m["materialisation"]["proxmox_pont"])
|
||||
for m in (face.get("machines") or {}).values()
|
||||
if (m.get("materialisation") or {}).get("proxmox_pont")}
|
||||
|
||||
|
||||
def verifier_materialisation(locataire: "Locataire", face: dict | None = None) -> list[str]:
|
||||
"""Les commandes du site qui NOMMENT leur locataire (materialisation M2) tirent-elles de sa
|
||||
face les memes intrants que l'ancien chemin, l'instance montee ?
|
||||
|
||||
Chaque cote est mesure par la commande meme de l'ancien chemin, a part :
|
||||
- machines a creer : `inventory_host.py lister-actifs` (ce que lit `flotte-creer`) ;
|
||||
- machines a raser : `raser.plan_derive` (le plan, via `serveurs.py lister`) ;
|
||||
- placement et ponts : `devis_placement` sur l'instance (ses valeurs, ses ponts derives).
|
||||
"""
|
||||
import subprocess
|
||||
import devis_placement
|
||||
import raser
|
||||
f = face if face is not None else (locataire.face_publiee() or {})
|
||||
ecarts: list[str] = []
|
||||
r = subprocess.run([sys.executable, "scripts/inventory_host.py", "--inventaire",
|
||||
str(locataire.inventaire()), "lister-actifs"],
|
||||
capture_output=True, text=True, cwd=RACINE)
|
||||
if r.returncode != 0:
|
||||
ecarts.append(f"lister-actifs refuse ({(r.stderr or '').strip().splitlines()[-1:]})")
|
||||
elif sorted(hotes_actifs(f)) != sorted(r.stdout.split()):
|
||||
diff = sorted(set(hotes_actifs(f)) ^ set(r.stdout.split()))
|
||||
ecarts.append(f"machines a creer : la face differe de lister-actifs ({', '.join(diff)})")
|
||||
# Le plan du locataire VISE, pas l'inventaire que `make` exporte pour l'instance montee :
|
||||
# `serveurs.py` refuse, a raison, un SETOPS_INVENTAIRE hors de l'instance demandee.
|
||||
garde = os.environ.pop("SETOPS_INVENTAIRE", None)
|
||||
try:
|
||||
plan = sorted(raser.plan_derive(locataire.depot))
|
||||
finally:
|
||||
if garde is not None:
|
||||
os.environ["SETOPS_INVENTAIRE"] = garde
|
||||
if couples_a_raser(f) != plan:
|
||||
diff = sorted(set(couples_a_raser(f)) ^ set(plan))
|
||||
ecarts.append(f"machines a raser : la face differe du plan ({diff[:4]})")
|
||||
place = _yaml(locataire.dossier_inventaire() / "group_vars" / "proxmox.yml")
|
||||
if dict(f.get("placement") or {}) != place:
|
||||
ecarts.append("placement : la face differe de group_vars/proxmox.yml")
|
||||
derives = devis_placement._ponts_derives(locataire.depot)
|
||||
if ponts_publies(f) != derives:
|
||||
ecarts.append(f"ponts : la face differe de l'inventaire ({sorted(ponts_publies(f) ^ derives)})")
|
||||
return ecarts
|
||||
|
||||
|
||||
def verifier_inventaire_sans_site(locataire: "Locataire") -> list[str]:
|
||||
"""L'inventaire du locataire se genere-t-il SANS son site, a l'octet pres ?
|
||||
|
||||
|
|
@ -1258,6 +1336,11 @@ def main(argv: list[str] | None = None) -> int:
|
|||
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
||||
ap.add_argument("--json", action="store_true")
|
||||
ap.add_argument("--fiche", metavar="LOCATAIRE", help="la fiche que son hebergeur lui destine")
|
||||
ap.add_argument("--parametres-clonage", metavar="LOCATAIRE",
|
||||
help="les parametres de clonage d'un hote (--hote), depuis sa face publiee")
|
||||
ap.add_argument("--hote", help="avec --parametres-clonage")
|
||||
ap.add_argument("--hotes-actifs", metavar="LOCATAIRE",
|
||||
help="ses machines actives, depuis sa face publiee (une par ligne)")
|
||||
ap.add_argument("--publier-faces", action="store_true",
|
||||
help="chaque locataire ecrit sa face reseau dans son depot")
|
||||
ap.add_argument("--deposer-fiches", action="store_true",
|
||||
|
|
@ -1298,6 +1381,18 @@ def main(argv: list[str] | None = None) -> int:
|
|||
return 1
|
||||
print(yaml.safe_dump(site.fiche_pour(loc), allow_unicode=True, sort_keys=False), end="")
|
||||
return 0
|
||||
if args.parametres_clonage or args.hotes_actifs:
|
||||
loc = Locataire.charger(args.parametres_clonage or args.hotes_actifs)
|
||||
face = loc.face_publiee()
|
||||
if face is None:
|
||||
raise SystemExit(f"{loc.nom} n'a pas publie sa face reseau ({NOM_FICHIER_FACE_RESEAU}).")
|
||||
if args.hotes_actifs:
|
||||
print("\n".join(hotes_actifs(face)))
|
||||
return 0
|
||||
if not args.hote:
|
||||
raise SystemExit("--hote requis avec --parametres-clonage.")
|
||||
print("\n".join(lignes_clonage(face, args.hote)))
|
||||
return 0
|
||||
if args.publier_faces:
|
||||
locataires = sorted({loc for _s, loc in couples_hebergeur()}, key=lambda l: l.nom)
|
||||
if not locataires:
|
||||
|
|
|
|||
|
|
@ -115,10 +115,13 @@ def _ponts_derives(base: Path | None) -> set[str]:
|
|||
|
||||
|
||||
def constater(api: Cluster, place: dict, base: Path | None = None,
|
||||
routage: str = "sdn") -> list[dict]:
|
||||
"""Confronte chaque valeur au cluster. Renvoie une ligne par objet."""
|
||||
routage: str = "sdn", ponts: set[str] | None = None) -> list[dict]:
|
||||
"""Confronte chaque valeur au cluster. Renvoie une ligne par objet.
|
||||
|
||||
`ponts` : ceux que la face reseau du locataire publie (`--locataire`) ; sans eux, ceux de
|
||||
l'inventaire de l'instance montee."""
|
||||
lignes: list[dict] = []
|
||||
place = {**place, "_instance": base}
|
||||
place = {**place, "_instance": base, "_ponts": ponts}
|
||||
|
||||
noeuds = [n.get("node") for n in _liste(api, "/nodes")]
|
||||
voulu = place.get("proxmox_clone_noeud")
|
||||
|
|
@ -148,7 +151,8 @@ def constater(api: Cluster, place: dict, base: Path | None = None,
|
|||
#
|
||||
# D-80 corrigee le 2026-08-13 dit deja que la liaison de placement est nœud, stockage
|
||||
# et gabarit — le pont se derive. Le devis, lui, continuait de compter quatre objets.
|
||||
ponts_derives = sorted(_ponts_derives(place.get("_instance")))
|
||||
ponts_derives = sorted(place["_ponts"] if place.get("_ponts") is not None
|
||||
else _ponts_derives(place.get("_instance")))
|
||||
if routage == "sdn":
|
||||
vnets = {v.get("vnet") for v in _liste(api, "/cluster/sdn/vnets")}
|
||||
manquants = [p for p in ponts_derives if p not in vnets]
|
||||
|
|
@ -216,9 +220,23 @@ def afficher(lignes: list[dict], source: Path | None, depot: Path) -> bool:
|
|||
def main() -> int:
|
||||
ap = argparse.ArgumentParser(description=__doc__)
|
||||
ap.add_argument("--verifier", action="store_true", help="code de sortie seulement")
|
||||
ap.add_argument("--locataire", help="le locataire NOMME, d'apres sa face reseau publiee "
|
||||
"(le site, sans monter son depot)")
|
||||
a = ap.parse_args()
|
||||
|
||||
place, source = placement_du_tenant()
|
||||
ponts = None
|
||||
if a.locataire:
|
||||
# LE SITE CONFRONTE LE PLACEMENT D'UN LOCATAIRE NOMME (2026-10-05, materialisation M2) :
|
||||
# ses valeurs de placement et les ponts de ses machines viennent de sa face publiee.
|
||||
import contexte
|
||||
face = contexte.Locataire.charger(a.locataire).face_publiee()
|
||||
if face is None:
|
||||
print(f"{a.locataire} n'a pas publie sa face reseau.", file=sys.stderr)
|
||||
return 2
|
||||
place, source = dict(face.get("placement") or {}), None
|
||||
ponts = contexte.ponts_publies(face)
|
||||
else:
|
||||
place, source = placement_du_tenant()
|
||||
if not place:
|
||||
print("Aucun placement declare par le tenant monte — rien a confronter.")
|
||||
return 0
|
||||
|
|
@ -230,7 +248,7 @@ def main() -> int:
|
|||
|
||||
base = source.parents[3] if source else None
|
||||
routage = underlay_mod.routage_tenants(underlay_mod.charger())
|
||||
lignes = constater(api, place, base, routage)
|
||||
lignes = constater(api, place, base, routage, ponts)
|
||||
if a.verifier:
|
||||
mauvais = [l for l in lignes if not l["existe"]]
|
||||
if mauvais:
|
||||
|
|
|
|||
|
|
@ -4895,6 +4895,28 @@ def preuve_clonage_publie() -> tuple[bool, str]:
|
|||
return True, f"{n} machine(s) : la face publie exactement les parametres de clonage de l'inventaire."
|
||||
|
||||
|
||||
def preuve_materialisation_par_la_face() -> tuple[bool, str]:
|
||||
"""Les commandes du site qui nomment leur locataire tirent-elles de sa face les memes
|
||||
intrants que l'ancien chemin ?
|
||||
|
||||
POURQUOI (2026-10-05, `docs/conception-contextes.md`, materialisation M2). `locataire-creer`,
|
||||
`locataire-raser` et `placement-plan TENANT=` ne montent plus le depot du locataire : ils
|
||||
lisent sa face. Creer, raser et placer doivent donc viser exactement les memes machines,
|
||||
VMID, valeurs et ponts que `flotte-creer`, `raser` et `placement-plan` sur l'instance montee
|
||||
— chacun mesure par la commande de l'ancien chemin, lancee a part.
|
||||
"""
|
||||
sys.path.insert(0, str(RACINE / "scripts"))
|
||||
import contexte as _ctx
|
||||
locataires = sorted({l for _s, l in _ctx.couples_hebergeur()}, key=lambda l: l.nom)
|
||||
if not locataires:
|
||||
return True, "Aucun locataire frere : aucune materialisation a confronter."
|
||||
ecarts = [f"{l.nom} : {e}" for l in locataires for e in _ctx.verifier_materialisation(l)]
|
||||
if ecarts:
|
||||
return False, f"{len(ecarts)} ecart(s) : " + " | ".join(ecarts[:4])
|
||||
return True, (f"{len(locataires)} locataire(s) : creer, raser et placer visent par la face "
|
||||
f"les memes machines que par l'instance montee.")
|
||||
|
||||
|
||||
PREUVES: list[dict] = [
|
||||
{"id": "P01", "titre": "Lint (ansible-lint)", "refs": ["AFF-006"],
|
||||
"cmds": [["ansible-lint", "-q"]]},
|
||||
|
|
@ -5101,6 +5123,8 @@ PREUVES: list[dict] = [
|
|||
"refs": [], "func": preuve_face_publiee},
|
||||
{"id": "P93", "titre": "La face publie, machine par machine, les parametres de clonage de l'inventaire",
|
||||
"refs": [], "func": preuve_clonage_publie},
|
||||
{"id": "P94", "titre": "Creer, raser et placer un locataire par sa face visent les memes machines",
|
||||
"refs": [], "func": preuve_materialisation_par_la_face},
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -59,6 +59,16 @@ def instance_active() -> tuple[Path, str]:
|
|||
return reel, nom_court(reel)
|
||||
|
||||
|
||||
def couples_publies(locataire: str) -> tuple[Path, list[tuple[str, int]]]:
|
||||
"""(dossier, [(nom d'hote, vmid)]) d'apres la face reseau que le locataire publie."""
|
||||
import contexte
|
||||
loc = contexte.Locataire.charger(locataire)
|
||||
face = loc.face_publiee()
|
||||
if face is None:
|
||||
raise SystemExit(f"{locataire} n'a pas publie sa face reseau : rien a raser par elle.")
|
||||
return loc.depot, contexte.couples_a_raser(face)
|
||||
|
||||
|
||||
def plan_derive(base: Path) -> list[tuple[str, int]]:
|
||||
"""(nom d'hote, vmid) tels que le PLAN les derive — la seule source légitime."""
|
||||
env = dict(os.environ, SETOPS_INSTANCE=str(base))
|
||||
|
|
@ -102,10 +112,19 @@ def main(argv: list[str] | None = None) -> int:
|
|||
ap.add_argument("--instance", help="nom de l'ecosysteme a raser (doit correspondre au monte)")
|
||||
ap.add_argument("--hote", help="ne raser QUE cet hote (les quatre verrous s'appliquent)")
|
||||
ap.add_argument("--confirmer", action="store_true")
|
||||
ap.add_argument("--locataire", help="le locataire NOMME, d'apres sa face reseau publiee "
|
||||
"(le site, sans monter son depot)")
|
||||
a = ap.parse_args(argv)
|
||||
|
||||
base, nom_actif = instance_active()
|
||||
couples = plan_derive(base)
|
||||
if a.locataire:
|
||||
# LE SITE RASE UN LOCATAIRE NOMME (2026-10-05, materialisation M2) : ses machines et leurs
|
||||
# VMID viennent de sa face publiee, sans monter son depot. Les quatre verrous restent :
|
||||
# le nom court a taper en derive, comme pour l'instance montee.
|
||||
base, couples = couples_publies(a.locataire)
|
||||
nom_actif = nom_court(base)
|
||||
else:
|
||||
base, nom_actif = instance_active()
|
||||
couples = plan_derive(base)
|
||||
if a.hote:
|
||||
# Les quatre verrous restent en vigueur : on ne fait que RESTREINDRE la liste
|
||||
# derivee du plan. Raser un seul hote sert a eprouver un gabarit ou a reprendre
|
||||
|
|
@ -155,6 +174,7 @@ def main(argv: list[str] | None = None) -> int:
|
|||
if not a.confirmer:
|
||||
print(f"\n{len(presentes)} VM seraient DETRUITES, disques compris.\n"
|
||||
f"Relancer avec : make raser CONFIRMER=true INSTANCE={nom_actif}"
|
||||
+ (f" TENANT={a.locataire}" if a.locataire else "")
|
||||
+ (f" HOTE={a.hote}" if a.hote else ""))
|
||||
return 0
|
||||
|
||||
|
|
|
|||
|
|
@ -655,6 +655,34 @@ def clonage_publie() -> None:
|
|||
verifier(bool(C.verifier_clonage(loc, f)), f"un parametre de clonage altere ({h}, memoire) se voit")
|
||||
|
||||
|
||||
def materialisation_par_la_face() -> None:
|
||||
"""Creer, raser et placer par la face visent les memes intrants que par l'instance montee
|
||||
— et une face alteree (VMID, etat, pont, placement) se voit, chacune a part."""
|
||||
import copy
|
||||
couples = C.couples_hebergeur()
|
||||
if not couples:
|
||||
print("SAUTE aucun site frere : aucune materialisation a confronter")
|
||||
return
|
||||
for _s, loc in couples:
|
||||
e = C.verifier_materialisation(loc)
|
||||
verifier(not e, f"{loc.nom} : creer, raser et placer par la face = par l'instance"
|
||||
+ (f" ({e[0]})" if e else ""))
|
||||
loc = couples[0][1]
|
||||
h = sorted(loc.face_publiee()["machines"])[0]
|
||||
def altere(fn):
|
||||
f = copy.deepcopy(loc.face_publiee())
|
||||
fn(f)
|
||||
return C.verifier_materialisation(loc, f)
|
||||
e = altere(lambda f: f["machines"][h]["materialisation"].__setitem__("proxmox_vmid", 999999999))
|
||||
verifier(any("raser" in x for x in e), f"un VMID altere ({h}) se voit cote raser")
|
||||
e = altere(lambda f: f["machines"][h].__setitem__("etat", "retire"))
|
||||
verifier(any("creer" in x for x in e), f"un etat altere ({h}) se voit cote creation")
|
||||
e = altere(lambda f: f["machines"][h]["materialisation"].__setitem__("proxmox_pont", "zzpont"))
|
||||
verifier(any("ponts" in x for x in e), f"un pont altere ({h}) se voit cote placement")
|
||||
e = altere(lambda f: f.__setitem__("placement", {**(f.get("placement") or {}), "proxmox_noeud": "zz"}))
|
||||
verifier(any("placement" in x for x in e), "une valeur de placement alteree se voit")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
d = Path(tmp)
|
||||
|
|
@ -678,6 +706,7 @@ def main() -> int:
|
|||
pools_complets()
|
||||
pools_et_tunnels_lisent_la_face()
|
||||
clonage_publie()
|
||||
materialisation_par_la_face()
|
||||
if ECHECS:
|
||||
print(f"\n{len(ECHECS)} echec(s).")
|
||||
return 1
|
||||
|
|
|
|||
Loading…
Reference in a new issue