contexte : materialisation M1, la face publie les parametres de clonage (P93)
Chaque machine porte ce que parametres-proxmox rend, par la meme fonction. P93 compare a la commande lancee a part pour les 26 machines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
40af2d1ec6
commit
4532a98707
7 changed files with 120 additions and 4 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 92 preuves de `make prouver` : elles lisent le dépôt, sans le
|
||||
C'est aussi ce que font les 93 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.**
|
||||
|
||||
|
|
|
|||
28
CHANGELOG.md
28
CHANGELOG.md
|
|
@ -1,5 +1,33 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 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
|
||||
les VM d'un locataire depuis sa face réseau, sans monter son dépôt. Plan validé avec lui :
|
||||
M1 la face publie les paramètres de clonage ; M2 des commandes du site qui nomment leur
|
||||
locataire (`locataire-creer`, `locataire-raser`, `placement-plan TENANT=`), à appels
|
||||
identiques ; M3 la reconstruction les emploie ; M4 la preuve par reconstruction.
|
||||
|
||||
**Le constat** : la matérialisation tourne **déjà** sur le runner du site, mais elle y monte le
|
||||
dépôt du locataire comme instance (`SETOPS_INSTANCE=/opt/setops/OPS-x make flotte-creer`), et
|
||||
`make creer-vm` lit son inventaire par `inventory_host.py parametres-proxmox`.
|
||||
|
||||
**Fait (M1)** : chaque machine de la face porte `clonage`, exactement ce que `parametres-proxmox`
|
||||
rend, calculé par la **même** fonction : VMID, adresse, masque, passerelle, VLAN, pont, stockage,
|
||||
disque, nœud, cœurs, mémoire, DNS d'amorçage, domaine, et les clés d'amorçage — **publiques**
|
||||
(vérifié : `ssh-ed25519` du runner du site et du runner du locataire, aucun bloc privé). Faces
|
||||
republiées.
|
||||
|
||||
**Éprouvé** :
|
||||
- **P93**, « La face publie, machine par machine, les paramètres de clonage de l'inventaire » :
|
||||
la face publiée comparée à la commande `parametres-proxmox` elle-même, lancée à part pour
|
||||
chacune des 26 machines. Aucun écart.
|
||||
- Le témoin a d'abord signalé un écart sur `ops-01` : la seule machine à deux clés d'amorçage,
|
||||
sur deux lignes, que ma lecture de la commande coupait au premier retour à la ligne. Le
|
||||
défaut était dans le témoin ; il lit désormais la sortie comme le shell (`shlex`).
|
||||
- `test_contexte.py`, 110 contrôles : un paramètre altéré dans la face se voit.
|
||||
- `make verifier` conforme, **93/93**. Les trois documents comptent 93 preuves.
|
||||
|
||||
## 2026-10-05 (96) — Étape 3 : les pools et les tunnels d'administration lisent la face réseau
|
||||
|
||||
**Le chemin** (`docs/conception-contextes.md` §6), étape 3, matérialisation, premiers temps :
|
||||
|
|
|
|||
|
|
@ -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 92 preuves (dont une conditionnelle, sautée sans la clé de la voûte). Elles sont toutes **statiques** : elles lisent le
|
||||
`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
|
||||
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 (92 preuves, zéro réseau)
|
||||
make prouver le dépôt est-il cohérent avec lui-même (93 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ù
|
||||
|
|
|
|||
|
|
@ -450,12 +450,28 @@ class Locataire(Ecosysteme):
|
|||
groupes = {g: sorted(h for h in _hotes_du_groupe(data, g) if h in ips)
|
||||
for g in sorted(_enfants(data)) if est_groupe_operationnel(g)} if data else {}
|
||||
actifs = set(_hotes_du_groupe(data, "hotes_actifs")) if data else set()
|
||||
# LES PARAMETRES DE CLONAGE de chaque machine (M1 de la materialisation, 2026-10-05) :
|
||||
# exactement ce que `make creer-vm` evalue aujourd'hui (`parametres-proxmox`), calcule
|
||||
# ici par la MEME fonction. Le runner du site clonera d'apres eux, sans monter ce depot.
|
||||
from inventory_host import parametres_proxmox_hote
|
||||
|
||||
def clonage(h: str) -> dict:
|
||||
try:
|
||||
lignes = parametres_proxmox_hote(data, h, self.inventaire())
|
||||
except ValueError as e:
|
||||
return {"erreur": str(e)}
|
||||
out = {}
|
||||
for ligne in lignes:
|
||||
cle, _, valeur = ligne.partition("=")
|
||||
out[cle] = valeur[1:-1] if valeur.startswith("'") and valeur.endswith("'") else valeur
|
||||
return out
|
||||
machines = {
|
||||
h: {"ip": ip,
|
||||
"etat": "actif" if h in actifs else "planifie",
|
||||
"groupes": sorted(g for g, membres in groupes.items() if h in membres),
|
||||
"materialisation": {k: v for k, v in sorted((hv.get(h) or {}).items())
|
||||
if k.startswith("proxmox_")}}
|
||||
if k.startswith("proxmox_")},
|
||||
"clonage": clonage(h)}
|
||||
for h, ip in sorted(ips.items())}
|
||||
# Les reseaux d'administration que le locataire DECLARE (l'intrant), et son tunnel.
|
||||
intrant: list[str] = []
|
||||
|
|
@ -1109,6 +1125,35 @@ def verifier_face_publiee(locataire: "Locataire") -> list[str]:
|
|||
return []
|
||||
|
||||
|
||||
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
|
||||
`make creer-vm` evalue ? Lancee a part, pour chaque machine : un temoin independant."""
|
||||
import subprocess
|
||||
f = face if face is not None else (locataire.face_publiee() or {})
|
||||
ecarts: list[str] = []
|
||||
for h, m in sorted((f.get("machines") or {}).items()):
|
||||
r = subprocess.run([sys.executable, "scripts/inventory_host.py", "--inventaire",
|
||||
str(locataire.inventaire()), "parametres-proxmox", "--hote", h],
|
||||
capture_output=True, text=True, cwd=RACINE)
|
||||
if r.returncode != 0:
|
||||
ecarts.append(f"{h} : parametres-proxmox refuse ({(r.stderr or '').strip().splitlines()[-1:]})")
|
||||
continue
|
||||
# Lu COMME LE SHELL le lit (`eval` dans `make creer-vm`) : une valeur entre apostrophes
|
||||
# peut tenir sur plusieurs lignes (deux cles d'amorcage sur le runner du locataire).
|
||||
import shlex
|
||||
attendu = {}
|
||||
for jeton in shlex.split(r.stdout):
|
||||
cle, _, valeur = jeton.partition("=")
|
||||
if cle.startswith("SETOPS_"):
|
||||
attendu[cle] = valeur
|
||||
publie = m.get("clonage") or {}
|
||||
if publie != attendu:
|
||||
diff = sorted(k for k in set(publie) | set(attendu) if publie.get(k) != attendu.get(k))
|
||||
ecarts.append(f"{h} : la face differe de parametres-proxmox ({', '.join(diff)})")
|
||||
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 ?
|
||||
|
||||
|
|
|
|||
|
|
@ -4874,6 +4874,27 @@ def preuve_face_publiee() -> tuple[bool, str]:
|
|||
return True, f"{len(locataires)} locataire(s) : face reseau publiee, a jour."
|
||||
|
||||
|
||||
def preuve_clonage_publie() -> tuple[bool, str]:
|
||||
"""Chaque machine publiee porte-t-elle les parametres de clonage que rend
|
||||
`parametres-proxmox` sur l'inventaire du locataire ?
|
||||
|
||||
POURQUOI (2026-10-05, `docs/conception-contextes.md`, materialisation M1). Le runner du site
|
||||
va cloner les VM d'un locataire d'apres sa face, sans monter son depot. Elle doit donc porter
|
||||
exactement ce que `make creer-vm` evalue aujourd'hui : VMID, adresse, pont, ressources, noeud,
|
||||
DNS, domaine, cles d'amorcage (publiques). Compare a la commande meme, 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 : aucun parametre de clonage a confronter."
|
||||
ecarts = [f"{l.nom} : {e}" for l in locataires for e in _ctx.verifier_clonage(l)]
|
||||
if ecarts:
|
||||
return False, f"{len(ecarts)} ecart(s) : " + " | ".join(ecarts[:4])
|
||||
n = sum(len((l.face_publiee() or {}).get("machines") or {}) for l in locataires)
|
||||
return True, f"{n} machine(s) : la face publie exactement les parametres de clonage de l'inventaire."
|
||||
|
||||
|
||||
PREUVES: list[dict] = [
|
||||
{"id": "P01", "titre": "Lint (ansible-lint)", "refs": ["AFF-006"],
|
||||
"cmds": [["ansible-lint", "-q"]]},
|
||||
|
|
@ -5078,6 +5099,8 @@ PREUVES: list[dict] = [
|
|||
"refs": [], "func": preuve_inventaire_portable},
|
||||
{"id": "P92", "titre": "Chaque locataire a publie sa face reseau a jour",
|
||||
"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},
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -636,6 +636,25 @@ def pools_et_tunnels_lisent_la_face() -> None:
|
|||
f"les tunnels d'administration lisent la face publiee ({len(pairs)} pair(s), puis 0)")
|
||||
|
||||
|
||||
def clonage_publie() -> None:
|
||||
"""La face publie, machine par machine, les parametres de clonage de `parametres-proxmox`
|
||||
— et une valeur alteree se voit."""
|
||||
import copy
|
||||
couples = C.couples_hebergeur()
|
||||
if not couples:
|
||||
print("SAUTE aucun site frere : aucun parametre de clonage a confronter")
|
||||
return
|
||||
for _s, loc in couples:
|
||||
e = C.verifier_clonage(loc)
|
||||
verifier(not e, f"{loc.nom} : la face porte les parametres de clonage de chaque machine"
|
||||
+ (f" ({e[0]})" if e else ""))
|
||||
loc = couples[0][1]
|
||||
f = copy.deepcopy(loc.face_publiee())
|
||||
h = sorted(f["machines"])[0]
|
||||
f["machines"][h]["clonage"]["SETOPS_MEMOIRE"] = "1"
|
||||
verifier(bool(C.verifier_clonage(loc, f)), f"un parametre de clonage altere ({h}, memoire) se voit")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
d = Path(tmp)
|
||||
|
|
@ -658,6 +677,7 @@ def main() -> int:
|
|||
decouverte_lit_la_face()
|
||||
pools_complets()
|
||||
pools_et_tunnels_lisent_la_face()
|
||||
clonage_publie()
|
||||
if ECHECS:
|
||||
print(f"\n{len(ECHECS)} echec(s).")
|
||||
return 1
|
||||
|
|
|
|||
Loading…
Reference in a new issue