pools Proxmox : un par tenant, dérivé de l'index (D-37, P28)
Onze des quatorze serveurs portent le même nom court chez Chezlepro et chez
Technolibre. Vérifié un par un, ce n'est pas un problème technique : tout le
reste dérive du seed et diverge (10.27.19.21 contre 10.21.19.21, VMID 117402101
contre 111402101, VLAN 1174 contre 1114, deux domaines internes), et rien n'est
indexé sur le nom court — les opérations Proxmox portent toutes un vmid, les
certificats un FQDN, et client_backup_repo vise backup-01.{{ domaine_interne }}.
Le coût est humain : la console Proxmox affiche le nom, et deux infra-pki-01 y
sont indiscernables à l'œil. Le VMID porte le tenant, encore faut-il connaître le
codage.
Un pool par tenant, dérivé du dossier d'instance et de l'index — déjà unique par
P21, donc aucun registre de plus : Chezlepro-17, Technolibre-11.
make devis-proxmox-pools rattrape la flotte existante (création du pool, puis
affectation des VM actives). Les VM créées ensuite entrent d'elles-mêmes :
make creer-vm dérive le pool par la même fonction et le passe à la création. Le
playbook crée le pool au préalable — proxmox_kvm échoue sur un pool inconnu, et
l'API ne sait pas changer le pool d'une VM existante ; c'est aussi pourquoi le
rattrapage passe par les membres.
P28 garde deux collisions : même nom de pool entre tenants, et surtout même VMID
— une machine appartenant à deux tenants serait pire qu'une homonymie.
Rien n'est renommé : les homonymes sont la preuve que la nomenclature est un vrai
gabarit. Le devis ne lit pas le cluster, il dit l'état cible et non l'écart.
27 preuves OK, 0 échec. --syntax-check du playbook de clonage.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
b0e56cbfc0
commit
60a60b6fb1
10 changed files with 298 additions and 1 deletions
43
CHANGELOG.md
43
CHANGELOG.md
|
|
@ -1,5 +1,48 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-08-03 (suite 12) — un pool Proxmox par tenant
|
||||
|
||||
Onze des quatorze serveurs portent le **même nom court** chez Chezlepro et chez
|
||||
Technolibre : `infra-pki-01`, `backup-01`, `obs-01`…
|
||||
|
||||
Vérifié un par un, ce n'est **pas** un problème technique. Tout le reste dérive du seed
|
||||
et diverge : `10.27.19.21` contre `10.21.19.21`, VMID `117402101` contre `111402101`,
|
||||
VLAN 1174 contre 1114, et deux domaines internes distincts. Et rien n'est indexé sur le
|
||||
nom court — toutes les opérations Proxmox portent un `vmid` (le `name:` n'est qu'une
|
||||
étiquette), les certificats un FQDN, et `client_backup_repo` vise
|
||||
`backup-01.{{ domaine_interne }}`, donc le serveur du tenant.
|
||||
|
||||
Le coût est **humain**, et il est réel : la console Proxmox affiche le nom. Deux
|
||||
`infra-pki-01` y sont indiscernables à l'œil, et c'est ainsi qu'on éteint la mauvaise
|
||||
machine. Le VMID porte pourtant le tenant — encore faut-il connaître le codage.
|
||||
|
||||
### Ce qui a été fait
|
||||
|
||||
Un pool par tenant, **dérivé** : dossier d'instance + `index` → `Chezlepro-17`,
|
||||
`Technolibre-11`. L'`index` étant déjà unique par P21, le nom l'est aussi — aucun
|
||||
registre de plus.
|
||||
|
||||
`make devis-proxmox-pools` produit le rattrapage de la flotte existante (création du
|
||||
pool, puis affectation des VM actives). Non destructif, à relire avant d'appliquer.
|
||||
|
||||
Les VM créées **ensuite** entrent d'elles-mêmes : `make creer-vm` dérive le pool par la
|
||||
même fonction et le passe à la création. Le playbook crée le pool au préalable — deux
|
||||
raisons : `proxmox_kvm` échoue sur un pool inconnu, et l'API **ne sait pas changer** le
|
||||
pool d'une VM existante. C'est aussi pourquoi le rattrapage passe par les membres.
|
||||
|
||||
**P28** garde deux collisions : deux tenants ne peuvent pas revendiquer le même nom de
|
||||
pool, ni le même VMID — une machine appartenant à deux tenants serait pire qu'une
|
||||
homonymie. Décision **D-37**, affirmation **AFF-110**.
|
||||
|
||||
### Ce que je n'ai pas fait
|
||||
|
||||
**Renommer les VM par tenant.** Ça casserait ce que ces homonymes prouvent : même
|
||||
fonction, même nom, partout — c'est ce qui rend un modèle réutilisable.
|
||||
|
||||
Le devis **ne lit pas le cluster** : il dit l'état cible, pas l'écart. Les commandes
|
||||
sont idempotentes, donc rejouables sans risque, mais il ne saura pas dire ce qui est
|
||||
déjà en place.
|
||||
|
||||
## 2026-08-03 (suite 11) — le cluster appartient à l'hébergeur
|
||||
|
||||
En ouvrant le panneau « Intrants de base », on trouvait côte à côte et sans distinction
|
||||
|
|
|
|||
8
Makefile
8
Makefile
|
|
@ -355,6 +355,13 @@ devis-proxmox-fw: ansible-runtime ## Devis pare-feu Proxmox (est-ouest intra-ten
|
|||
devis-proxmox-fw-verifier:
|
||||
python3 scripts/devis_proxmox_fw.py --verifier
|
||||
|
||||
.PHONY: devis-proxmox-pools devis-proxmox-pools-verifier
|
||||
devis-proxmox-pools: ansible-runtime ## Devis des pools Proxmox (un par tenant), derive du plan
|
||||
python3 scripts/devis_proxmox_pools.py $(if $(JSON),--json,)
|
||||
|
||||
devis-proxmox-pools-verifier:
|
||||
python3 scripts/devis_proxmox_pools.py --verifier
|
||||
|
||||
devis-opnsense-verifier:
|
||||
python3 scripts/devis_opnsense.py --verifier
|
||||
|
||||
|
|
@ -546,6 +553,7 @@ cloner-vm: ansible-runtime
|
|||
-e proxmox_clone_nom="$(HOTE)" \
|
||||
-e proxmox_clone_vmid="$(VMID)" \
|
||||
-e proxmox_clone_ipconfig0="$$ipconfig" \
|
||||
-e proxmox_clone_pool="$$(python3 scripts/devis_proxmox_pools.py --pool-actif)" \
|
||||
); \
|
||||
[[ -n "$(VMID_MODELE)" ]] && extra_vars+=( -e proxmox_clone_vmid_modele="$(VMID_MODELE)" ); \
|
||||
[[ -n "$(NOEUD_PROXMOX)" ]] && extra_vars+=( -e proxmox_clone_noeud="$(NOEUD_PROXMOX)" ); \
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@ Légende du statut : ✅ prouvée · 🟡 partielle · ❌ fausse · ⚪ invéri
|
|||
| AFF-107 | « Le filtrage est-ouest intra-tenant est dérivé du registre des flux, et redit la même politique que les nftables d'hôte » (`docs/sdn-evpn.md` §3) | structurelle | **P25** (`scripts/devis_proxmox_fw.py --verifier`) | ✅ | 34 groupes, 40 règles sur 2 tenants. Les deux barrières lisent le registre par les **mêmes fonctions** : elles ne peuvent pas diverger. |
|
||||
| AFF-108 | « Une intégration universelle est déclarée par le rôle, pas recopiée par serveur : aucun hôte n'y échappe et le plan ne la redit pas » (`docs/decisions-architecture.md` D-33) | structurelle | **P26** (`scripts/prouver.py`) | ✅ | L'exemption se dérive du **service rendu** (`sauf_role`), jamais d'un nom d'hôte : l'AC ne s'enrôle pas auprès d'elle-même, et l'exemption suit step-ca si on le déplace. |
|
||||
| AFF-109 | « Ce qui appartient à l'hébergeur (cluster, fabric, frontière) vit dans son dépôt, jamais recopié chez un tenant » (`docs/decisions-architecture.md` D-35) | structurelle | **P27** (`scripts/prouver.py`) | ✅ | Le chemin se dérive du symlink `underlay.yml` : l'hébergeur n'est toujours pas déclaré (D-17). Sautée si aucun underlay n'est monté. |
|
||||
| AFF-110 | « Chaque tenant a son pool Proxmox, dérivé, sans collision de nom ni de VMID » (`docs/decisions-architecture.md` D-37) | structurelle | **P28** (`scripts/devis_proxmox_pools.py --verifier`) | ✅ | Le nom dérive du dossier d'instance et de l'`index`, déjà unique par P21 — aucun registre de plus. Le devis ne lit pas le cluster : il affirme la cohérence du **plan**, pas l'état appliqué. |
|
||||
| AFF-106 | « Le panneau couvre le schéma du plan : aucun champ n'oblige à éditer le YAML » (`docs/intrants-base-gui-conception.md`) | fonctionnelle | **P19** (`scripts/couverture_gui.py`) | 🟡 | Vrai des registres du plan. **Exceptions assumées** : `reseaux` et `hotes` de l'underlay (listes de tables) restent hors du panneau ; les ports physiques et le nœud de sortie EVPN se nomment à la main. |
|
||||
|
||||
**Non enregistré volontairement.** Les devis eux-mêmes (`make devis-reseau`,
|
||||
|
|
@ -397,6 +398,7 @@ Chaque affirmation ✅ est soit **rejouée** par une preuve de `make prouver`
|
|||
| AFF-107 | **P25** (pare-feu Proxmox est-ouest) |
|
||||
| AFF-108 | **P26** (intégrations universelles : aucun hôte laissé de côté) |
|
||||
| AFF-109 | **P27** (propriété des intrants : hébergeur et tenant séparés) |
|
||||
| AFF-110 | **P28** (pools Proxmox : un par tenant) |
|
||||
|
||||
**Structurelles/doctrinales, hors harnais récurrent** (vérifiées à l'audit) : AFF-005
|
||||
(`make`=aide), AFF-014 (ciblage `groupe:&hotes_actifs`), AFF-024 (`instancier-appliquer`,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
> [`docs/audit/affirmations.md`](affirmations.md).
|
||||
|
||||
- **Instance** : `instance` — inventaire `instance/inventories/principal/hosts.yml`
|
||||
- **Verdict** : ✅ CONFORME (26 OK · 0 echec · 1 saute)
|
||||
- **Verdict** : ✅ CONFORME (27 OK · 0 echec · 1 saute)
|
||||
|
||||
## Preuves
|
||||
|
||||
|
|
@ -40,6 +40,7 @@
|
|||
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 2 tenant(s), 36 groupe(s), 56 regle(s). |
|
||||
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 14 hote(s) x 3 integration(s) universelle(s) : aucune lacune, aucune recopie (1 exemption(s) derivee(s) du service rendu). |
|
||||
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 7 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
|
||||
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 2 pool(s) Proxmox, 28 VM placee(s), aucun nom ni VMID en collision. |
|
||||
|
||||
## Couverture des affirmations ✅ du registre
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ Ce que je re-découvre sinon. **Consulter avant de concevoir un nouveau mécanis
|
|||
| Voûte au déploiement | secret jamais en clair | `ANSIBLE_VAULT_PASSWORD_FILE` / `~/.config/setops-vault-pass` ; déréférencé par `lookup('vars', <nom>)` | — |
|
||||
| Multi-instance | un dépôt par écosystème ; l'active = symlink `instance/`, les autres **découvertes par convention** (dossiers frères, aucun registre) | active : symlink `instance/` ; découverte : `scripts/instances.py` / `devis_reseau.py` (glob `../*/plan/nomenclature.yml` avec `index`) ; garde-fou collision : preuve **P21** | `multi-instances.md` |
|
||||
| Exposition → edge | app expose un FQDN public servi par un edge | `plan/domaines.yml` + `expose` (applications) | `bindings-conception.md` §4 |
|
||||
| Pools Proxmox | un pool par tenant : les noms courts de VM sont **volontairement identiques** d'un tenant à l'autre (même fonction, même nom), et seule la console Proxmox en souffrait | `scripts/devis_proxmox_pools.py` (`make devis-proxmox-pools`) ; nom dérivé de l'`index` ; garde de collision = preuve **P28** | `decisions-architecture.md` D-37 |
|
||||
| Frontière nord/sud | les flux `pair: externe` — **sautés** par le pare-feu d'hôte — sont la politique de bordure | `scripts/devis_opnsense.py` (`make devis-opnsense`) ; garde d'accès admin = preuve **P24** | `frontiere-opnsense.md` |
|
||||
|
||||
> ⚠️ **Deux directions de binding, assumées** : `app→app` côté app (instancier),
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ sont les seules vérifiables.
|
|||
| **D-17** | L'hébergeur **n'est pas déclaré** : le symlink le désigne | une seconde déclaration ouvrirait deux valeurs contradictoires | `frontiere-opnsense.md` §2 | — |
|
||||
| **D-35** | Le **cluster Proxmox** appartient à l'hébergeur ; seuls le **golden template** et les **défauts de placement** restent au tenant | recopié chez chaque tenant, l'inventaire du cluster avait déjà divergé — deux listes de stockages contradictoires pour le même matériel | `config-proxmox.md` | P27 |
|
||||
| **D-36** | Le panneau **nomme le propriétaire** de chaque section d'intrants | éditer une section « hébergeur » vaut pour tous ses tenants ; l'écran ne le disait pas | `scripts/inventory_gui.py` (`INTRANTS_SCHEMA`) | — |
|
||||
| **D-37** | Chaque tenant a son **pool Proxmox** ; les noms courts de VM restent **identiques** d'un tenant à l'autre | 11 serveurs sur 14 sont homonymes — c'est la preuve que la nomenclature est un gabarit ; le coût est humain (la console affiche le nom), et le pool le corrige sans rien renommer | `devis_proxmox_pools.py` | P28 |
|
||||
| **D-18** | Chaque tenant a un **responsable désigné** | sans lui, « qui peut décider de déménager cette organisation ? » se pose au pire moment | `migration-tenant.md` §3 | — |
|
||||
|
||||
## 3. Les secrets
|
||||
|
|
|
|||
|
|
@ -131,6 +131,23 @@
|
|||
l'environnement Python utilise par Ansible, puis relancer make creer-vm.
|
||||
when: proxmoxer_verification.rc != 0
|
||||
|
||||
- name: S'assurer que le pool du tenant existe
|
||||
# Un pool par tenant : la console Proxmox affiche le NOM, et onze serveurs
|
||||
# portent le meme d'un tenant a l'autre. Le pool restitue l'appartenance sans
|
||||
# rien renommer. Il doit exister AVANT le clone — `proxmox_kvm` echoue sur un
|
||||
# pool inconnu, et l'API ne sait pas changer le pool d'une VM deja creee.
|
||||
community.general.proxmox_pool:
|
||||
api_host: "{{ proxmox_api_host_effectif }}"
|
||||
api_port: "{{ proxmox_api_port_effectif | int if proxmox_api_port_effectif | length > 0 else omit }}"
|
||||
api_user: "{{ proxmox_api_user_effectif }}"
|
||||
api_token_id: "{{ proxmox_api_token_id_effectif }}"
|
||||
api_token_secret: "{{ proxmox_api_token_secret_effectif }}"
|
||||
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
|
||||
poolid: "{{ proxmox_clone_pool }}"
|
||||
comment: "Tenant Set-OPS — genere, ne pas renommer a la main"
|
||||
state: present
|
||||
when: proxmox_clone_pool | default('', true) | length > 0
|
||||
|
||||
- name: Cloner la VM depuis le modele
|
||||
community.general.proxmox_kvm:
|
||||
api_host: "{{ proxmox_api_host_effectif }}"
|
||||
|
|
@ -149,6 +166,8 @@
|
|||
storage: "{{ proxmox_clone_stockage | default(omit, true) }}"
|
||||
format: "{{ proxmox_clone_format | default(omit, true) }}"
|
||||
full: "{{ proxmox_clone_complet | default(true) | bool }}"
|
||||
# A la CREATION seulement : l'API ne permet pas de le changer ensuite.
|
||||
pool: "{{ proxmox_clone_pool | default(omit, true) }}"
|
||||
timeout: "{{ proxmox_clone_timeout | default(600) | int }}"
|
||||
|
||||
- name: Ajuster le reseau de la VM clonee
|
||||
|
|
@ -231,4 +250,5 @@
|
|||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "VM creee: {{ proxmox_clone_nom }} (VMID {{ proxmox_clone_vmid }})"
|
||||
- "Pool: {{ proxmox_clone_pool | default('(aucun)', true) }}"
|
||||
- "Cloud-Init ipconfig0: {{ proxmox_clone_ipconfig0 }}"
|
||||
|
|
|
|||
202
scripts/devis_proxmox_pools.py
Normal file
202
scripts/devis_proxmox_pools.py
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Devis des pools Proxmox — un pool par tenant.
|
||||
|
||||
POURQUOI. Onze des quatorze serveurs portent le MEME nom court chez deux tenants
|
||||
(`infra-pki-01`, `backup-01`, `obs-01`...). Ce n'est pas un defaut : c'est la preuve
|
||||
que la nomenclature est un vrai gabarit — meme fonction, meme nom, partout. Tout le
|
||||
reste differe et derive du seed (IP, VMID, VLAN/VNI, FQDN), et rien dans Set-OPS
|
||||
n'est indexe sur le nom court : les operations Proxmox portent toutes un `vmid`, les
|
||||
certificats un FQDN, les depots de sauvegarde vivent chez le serveur du tenant.
|
||||
|
||||
Le seul endroit ou l'homonymie se paie est HUMAIN : la console Proxmox affiche le
|
||||
NOM. Deux `infra-pki-01` y sont indiscernables a l'oeil, et c'est ainsi qu'on eteint
|
||||
la mauvaise machine. Le VMID porte pourtant le tenant (117... contre 111...), mais
|
||||
il faut connaitre le codage pour le lire.
|
||||
|
||||
Un pool par tenant restitue l'appartenance dans l'arbre du cluster, sans renommer
|
||||
quoi que ce soit. Effet secondaire utile : un pool est aussi une PORTEE DE
|
||||
PERMISSION — c'est l'objet auquel on attachera plus tard un acces par tenant.
|
||||
|
||||
NON destructif : ce script n'ecrit rien sur le cluster. Il derive du plan ce que
|
||||
l'etat cible devrait etre, et le rend a relire. `--json` sert l'API.
|
||||
|
||||
LIMITE ASSUMEE. Le devis ne LIT PAS le cluster : il ne peut donc pas dire ce qui est
|
||||
deja en place, seulement ce que le plan implique. Les commandes emises sont
|
||||
idempotentes — reappliquer un membre deja present ne fait rien.
|
||||
|
||||
Usage :
|
||||
python3 scripts/devis_proxmox_pools.py # devis lisible
|
||||
python3 scripts/devis_proxmox_pools.py --json # meme contenu, pour l'API
|
||||
python3 scripts/devis_proxmox_pools.py --verifier # garde : un pool unique par tenant
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
RACINE = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(RACINE / "scripts"))
|
||||
|
||||
from inventory_rules import ( # noqa: E402
|
||||
charger_serveurs,
|
||||
deriver_nomenclature,
|
||||
fonction_seq,
|
||||
pool_de,
|
||||
)
|
||||
from devis_reseau import DOSSIER_INSTANCES, decouvrir # noqa: E402
|
||||
|
||||
|
||||
def _plan_de(nom_instance: str) -> dict:
|
||||
p = DOSSIER_INSTANCES / nom_instance / "plan" / "serveurs.yml"
|
||||
if not p.is_file():
|
||||
return {}
|
||||
return charger_serveurs(p).get("serveurs") or {}
|
||||
|
||||
|
||||
def pool_actif() -> str:
|
||||
"""Pool du tenant ACTIF — ce que le playbook de clonage passe a la creation.
|
||||
|
||||
Meme derivation que le devis : `make creer-vm` et `make devis-proxmox-pools` ne
|
||||
peuvent pas nommer le pool differemment. Chaine vide si aucune instance n'est
|
||||
liee — l'appelant omet alors le parametre plutot que d'inventer un nom.
|
||||
"""
|
||||
instance = Path(os.environ.get("SETOPS_INSTANCE") or (RACINE / "instance"))
|
||||
nomenclature = instance / "plan" / "nomenclature.yml"
|
||||
if not nomenclature.is_file():
|
||||
return ""
|
||||
n = yaml.safe_load(nomenclature.read_text(encoding="utf-8")) or {}
|
||||
if n.get("index") is None:
|
||||
return ""
|
||||
return pool_de(instance.resolve().name, int(n["index"]))
|
||||
|
||||
|
||||
def construire(tenants: list[tuple[str, str, dict]]) -> dict:
|
||||
"""{pools: [{pool, tenant, index, membres: [{nom, vmid, etat}], sans_vmid: []}]}"""
|
||||
blocs = []
|
||||
for nom_instance, _prefixe, nomenclature in tenants:
|
||||
index = int(nomenclature["index"])
|
||||
membres, sans_vmid = [], []
|
||||
for nom, srv in sorted(_plan_de(nom_instance).items()):
|
||||
_, seq = fonction_seq(nom)
|
||||
derive = deriver_nomenclature(str(srv.get("fonction", "")), seq, nomenclature) or {}
|
||||
vmid = derive.get("vmid")
|
||||
if vmid is None:
|
||||
# Une fonction absente de la nomenclature ne derive pas de VMID : on ne
|
||||
# peut pas la placer. La taire ferait croire le pool complet.
|
||||
sans_vmid.append(nom)
|
||||
continue
|
||||
membres.append({"nom": nom, "vmid": int(vmid),
|
||||
"etat": str(srv.get("etat", "planifie"))})
|
||||
blocs.append({
|
||||
"pool": pool_de(nom_instance, index),
|
||||
"tenant": nom_instance,
|
||||
"index": index,
|
||||
"membres": membres,
|
||||
"sans_vmid": sans_vmid,
|
||||
})
|
||||
return {"pools": blocs}
|
||||
|
||||
|
||||
def rendre(devis: dict) -> str:
|
||||
out = [
|
||||
"# Devis des pools Proxmox — un pool par tenant",
|
||||
"#",
|
||||
"# Ne renomme RIEN : les noms courts identiques d'un tenant a l'autre sont",
|
||||
"# voulus (meme fonction, meme nom). Le pool restitue l'appartenance dans la",
|
||||
"# console, la ou l'oeil ne voyait que deux `infra-pki-01`.",
|
||||
"#",
|
||||
"# Ce devis derive du PLAN et ne lit pas le cluster : il dit l'etat cible, pas",
|
||||
"# l'ecart. Les commandes sont idempotentes.",
|
||||
"",
|
||||
]
|
||||
for b in devis["pools"]:
|
||||
actifs = [m for m in b["membres"] if m["etat"] == "actif"]
|
||||
out += [
|
||||
f"## {b['tenant']} — pool `{b['pool']}` (index {b['index']})",
|
||||
f"# {len(b['membres'])} VM au plan, dont {len(actifs)} active(s).",
|
||||
"",
|
||||
"### 1. Creer le pool (sans effet s'il existe)",
|
||||
f"pvesh create /pools --poolid {b['pool']} \\",
|
||||
f" --comment 'Tenant {b['tenant']} (index {b['index']}) — genere par Set-OPS'",
|
||||
"",
|
||||
"### 2. Y placer les VM",
|
||||
"# Un membre deja present est ignore par Proxmox.",
|
||||
]
|
||||
if actifs:
|
||||
out.append(f"pvesh set /pools/{b['pool']} --vms "
|
||||
+ ",".join(str(m['vmid']) for m in actifs))
|
||||
else:
|
||||
out.append("# (aucune VM active : rien a placer pour l'instant)")
|
||||
out += ["", "# VMID serveur etat"]
|
||||
for m in b["membres"]:
|
||||
marque = "" if m["etat"] == "actif" else " (pas encore creee)"
|
||||
out.append(f"# {m['vmid']:<11} {m['nom']:<22} {m['etat']}{marque}")
|
||||
if b["sans_vmid"]:
|
||||
out += ["",
|
||||
"# /!\\ Sans VMID derivable (fonction absente de la nomenclature) :",
|
||||
"# " + ", ".join(b["sans_vmid"]),
|
||||
"# Ces serveurs ne peuvent pas etre places tant que leur fonction",
|
||||
"# n'est pas declaree — les placer a la main recreerait un ecart."]
|
||||
out.append("")
|
||||
out += [
|
||||
"## Ensuite",
|
||||
"# Les VM CREEES PAR LA SUITE entrent d'elles-memes dans le pool : le playbook",
|
||||
"# de clonage le derive et le passe a la creation. Ce devis ne sert donc qu'a",
|
||||
"# rattraper la flotte deja en place — une fois.",
|
||||
"#",
|
||||
"# Note : l'API Proxmox ne permet pas de CHANGER le pool d'une VM existante par",
|
||||
"# le meme appel que la creation. Deplacer une VM d'un pool a l'autre se fait",
|
||||
"# par le membre, pas par la VM — ce qui compte le jour d'une migration.",
|
||||
]
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
def main(argv: list[str]) -> int:
|
||||
ap = argparse.ArgumentParser(description=__doc__)
|
||||
ap.add_argument("--json", action="store_true")
|
||||
ap.add_argument("--verifier", action="store_true")
|
||||
ap.add_argument("--pool-actif", action="store_true",
|
||||
help="nom du pool du tenant actif (consomme par make creer-vm)")
|
||||
args = ap.parse_args(argv)
|
||||
|
||||
if args.pool_actif:
|
||||
print(pool_actif())
|
||||
return 0
|
||||
|
||||
devis = construire(decouvrir())
|
||||
if args.verifier:
|
||||
blocs = devis["pools"]
|
||||
if not blocs:
|
||||
print("erreur: aucun tenant federe decouvert.", file=sys.stderr)
|
||||
return 2
|
||||
noms = [b["pool"] for b in blocs]
|
||||
if len(set(noms)) != len(noms):
|
||||
doublons = sorted({n for n in noms if noms.count(n) > 1})
|
||||
print(f"erreur: pool(s) en collision entre tenants : {', '.join(doublons)}",
|
||||
file=sys.stderr)
|
||||
return 2
|
||||
# Un VMID dans deux pools serait pire qu'une homonymie : la machine
|
||||
# appartiendrait a deux tenants. L'index les separe, mais on le prouve.
|
||||
vus: dict[int, str] = {}
|
||||
for b in blocs:
|
||||
for m in b["membres"]:
|
||||
if m["vmid"] in vus:
|
||||
print(f"erreur: VMID {m['vmid']} revendique par {vus[m['vmid']]} "
|
||||
f"et {b['pool']}.", file=sys.stderr)
|
||||
return 2
|
||||
vus[m["vmid"]] = b["pool"]
|
||||
total = sum(len(b["membres"]) for b in blocs)
|
||||
print(f"CONFORME : {len(blocs)} pool(s) Proxmox, {total} VM placee(s), "
|
||||
f"aucun nom ni VMID en collision.")
|
||||
return 0
|
||||
print(json.dumps(devis, indent=2, ensure_ascii=False) if args.json else rendre(devis))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv[1:]))
|
||||
|
|
@ -92,6 +92,23 @@ def integrations_de(srv: dict, services_hote: set[str] | None = None,
|
|||
return effectives | set(srv.get("integrations") or [])
|
||||
|
||||
|
||||
def pool_de(nom_instance: str, index: int) -> str:
|
||||
"""Nom du pool Proxmox d'un tenant. DERIVE, jamais saisi.
|
||||
|
||||
Onze des quatorze serveurs portent le meme nom court chez deux tenants : c'est
|
||||
la preuve que la nomenclature est un vrai gabarit, et il ne faut pas la casser.
|
||||
Mais la console Proxmox, elle, affiche le NOM — deux `infra-pki-01` y sont
|
||||
indiscernables a l'oeil, alors que tout le reste (IP, VMID, VLAN, FQDN) differe.
|
||||
Le pool restitue l'appartenance la ou elle manquait : dans l'arbre du cluster.
|
||||
|
||||
`index` est deja garanti unique dans la federation par P21 : le nom l'est donc
|
||||
aussi, sans registre supplementaire. Forme : `Chezlepro-17`, `Technolibre-11`.
|
||||
"""
|
||||
base = re.sub(r"^OPS-", "", nom_instance)
|
||||
base = re.sub(r"[^A-Za-z0-9._-]", "", base) or "tenant"
|
||||
return f"{base}-{int(index)}"
|
||||
|
||||
|
||||
def est_groupe_operationnel(groupe: str) -> bool:
|
||||
return groupe.startswith(GROUPES_OPERATIONNELS_PREFIXES)
|
||||
|
||||
|
|
|
|||
|
|
@ -378,6 +378,8 @@ PREUVES: list[dict] = [
|
|||
"func": preuve_integrations_universelles},
|
||||
{"id": "P27", "titre": "Propriete des intrants : hebergeur et tenant separes", "refs": ["AFF-109"],
|
||||
"func": preuve_propriete_des_intrants},
|
||||
{"id": "P28", "titre": "Pools Proxmox : un par tenant, sans collision", "refs": ["AFF-110"],
|
||||
"cmds": [[sys.executable, "scripts/devis_proxmox_pools.py", "--verifier"]]},
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue