#!/usr/bin/env python3 """Vue d'ensemble des instances (ecosystemes) de la federation Set-OPS. Decouvre les depots freres `../*/plan/nomenclature.yml` et montre, pour chacun : - nom du depot, - index (le seed dont derive tout l'adressage), - ACTIVE (le symlink `instance` pointe-t-il ici ?), - federe (participe au reseau converge, cf. devis-reseau) — sinon bac a sable local, - production (setops_production) — pour la prudence au deploiement, - plage VLAN derivee (1000 + index x 10 + zone). Et surtout : SIGNALE toute COLLISION d'index entre instances FEDEREES — deux instances de meme index ont les memes VLAN et VMID, ce qui casse le reseau converge. C'est le garde-fou du modele multi-instances. Usage : python3 scripts/instances.py # tableau python3 scripts/instances.py --verifier # rc=2 si collision d'index entre federees """ from __future__ import annotations import argparse import sys from pathlib import Path import yaml RACINE = Path(__file__).resolve().parents[1] FRERES = RACINE.parent LIEN_INSTANCE = RACINE / "instance" def _actif() -> Path | None: try: return LIEN_INSTANCE.resolve() except OSError: return None def _production(depot: Path) -> bool | None: """Lit setops_production dans les group_vars/all de l'instance (repere de prudence).""" for inv in sorted((depot / "inventories").glob("*")) if (depot / "inventories").is_dir() else []: base = inv / "group_vars" / "all" fichiers = list(base.glob("*.yml")) if base.is_dir() else [] plat = inv / "group_vars" / "all.yml" if plat.exists(): fichiers.append(plat) for f in fichiers: try: d = yaml.safe_load(f.read_text(encoding="utf-8")) or {} except (OSError, yaml.YAMLError): continue if isinstance(d, dict) and "setops_production" in d: return bool(d["setops_production"]) return None def decouvrir() -> list[dict]: actif = _actif() instances = [] for chemin in sorted(FRERES.glob("*/plan/nomenclature.yml")): try: n = yaml.safe_load(chemin.read_text(encoding="utf-8")) or {} except (OSError, yaml.YAMLError): continue if n.get("index") is None: continue depot = chemin.parent.parent zones = sorted(int(z) for z in (n.get("categories") or {})) idx = n["index"] instances.append({ "nom": depot.name, "index": idx, "actif": actif is not None and depot.resolve() == actif, "federe": bool(n.get("federe", True)), "production": _production(depot), "vlans": (1000 + idx * 10 + zones[0], 1000 + idx * 10 + zones[-1]) if zones else None, }) instances.sort(key=lambda i: (i["index"], i["nom"])) return instances def collisions(instances: list[dict]) -> dict[int, list[str]]: """Index partages par >1 instance FEDEREE -> {index: [noms]}.""" par_index: dict[int, list[str]] = {} for i in instances: if i["federe"]: par_index.setdefault(i["index"], []).append(i["nom"]) return {idx: noms for idx, noms in par_index.items() if len(noms) > 1} def main() -> int: p = argparse.ArgumentParser(description="Vue d'ensemble des instances Set-OPS.") p.add_argument("--verifier", action="store_true", help="Ne rien afficher sauf en cas de collision d'index (rc=2).") args = p.parse_args() instances = decouvrir() coll = collisions(instances) if args.verifier: if coll: print("erreur: collision d'index entre instances FEDEREES " "(memes VLAN/VMID) :", file=sys.stderr) for idx, noms in sorted(coll.items()): print(f" - index {idx} partage par : {', '.join(noms)}", file=sys.stderr) return 2 print(f"Federation coherente : {sum(1 for i in instances if i['federe'])} " f"instance(s) federee(s), aucun index en collision.") return 0 if not instances: print("Aucune instance decouverte (depots freres avec plan/nomenclature.yml).") return 0 print(f"{'':1} {'INSTANCE':<22} {'INDEX':>5} {'VLAN':<11} {'FEDERE':<7} {'PROD':<5}") for i in instances: marque = "*" if i["actif"] else " " vlan = f"{i['vlans'][0]}-{i['vlans'][1]}" if i["vlans"] else "—" fed = "oui" if i["federe"] else "LOCAL" prod = "oui" if i["production"] else ("non" if i["production"] is False else "?") print(f"{marque} {i['nom']:<22} {i['index']:>5} {vlan:<11} {fed:<7} {prod:<5}") print("\n* = instance active (symlink 'instance'). " "Basculer : make instance-utiliser NOM=") if coll: print("\n⚠ COLLISION d'index entre instances federees :", file=sys.stderr) for idx, noms in sorted(coll.items()): print(f" index {idx} : {', '.join(noms)} — memes VLAN/VMID sur le trunk !", file=sys.stderr) return 2 return 0 if __name__ == "__main__": raise SystemExit(main())