Set-OPS-Public/scripts/tests/test_verifier_depot.py
Daniel Allaire 0f7c89fbb5 sondes de sauvegarde : juger le plus recent instantane ; temoin Loki hors index
restic snapshots --latest 1 rend un instantane par groupe (machine et chemins) : la sonde gardait le premier, l'ancien, depuis qu'un jeu a ete ajoute a site-mon-01 (EN RETARD a tort, PERIME promis a 50 h). Meme motif cote serveur_backup. Le temoin de Loki ne compte plus l'index compacte. Journal (114), (115).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 17:35:28 -04:00

91 lines
3.9 KiB
Python

#!/usr/bin/env python3
"""La sonde du depot juge le PLUS RECENT instantane, pas le premier groupe que restic rend.
POURQUOI (2026-10-08). `restic snapshots --latest 1` rend le dernier instantane de chaque
groupe — machine ET liste de chemins. Ajouter un jeu a une machine change sa liste : deux
groupes. La sonde gardait `d[0]` : sur site-mon-01, au lendemain de l'arrivee de prometheus,
loki et icinga, elle annoncait « EN RETARD : 26 h » sur une sauvegarde de 2 h 30, et aurait
annonce « PERIME » a 50 h. La sonde rendue depuis son vrai gabarit, `restic` et `curl` doubles.
"""
from __future__ import annotations
import datetime
import json
import os
import stat
import subprocess
import sys
import tempfile
from pathlib import Path
import jinja2
RACINE = Path(__file__).resolve().parents[2]
GABARIT = RACINE / "roles/client_backup/templates/verifier-mon-depot.sh.j2"
ECHECS: list[str] = []
def verifier(cond: bool, msg: str) -> None:
print(("OK " if cond else "ECHEC ") + msg)
if not cond:
ECHECS.append(msg)
def executable(p: Path, texte: str) -> None:
p.write_text(texte)
p.chmod(p.stat().st_mode | stat.S_IXUSR)
def lancer(d: Path, groupes: list[dict]) -> dict:
bin_ = d / "bin"
bin_.mkdir(exist_ok=True)
(d / "groupes.json").write_text(json.dumps(groupes))
executable(bin_ / "restic", f"""#!/bin/bash
[[ "$1" == --retry-lock ]] && shift 2
case "$1" in
snapshots) cat "{d}/groupes.json" ;;
stats) echo '{{"total_size": 1234}}' ;;
ls) echo '{{"struct_type":"node","type":"file"}}' ;;
esac
""")
executable(bin_ / "curl", f"""#!/bin/bash
while (( $# )); do [[ "$1" == -d ]] && echo "$2" > "{d}/verdict.json"; shift; done
echo '{{"results":[{{"code": 200}}]}}'
""")
(d / "mdp").write_text("x")
rendu = jinja2.Environment(undefined=jinja2.StrictUndefined).from_string(GABARIT.read_text()).render(
client_backup_attente_verrou="30m", client_backup_repo="sftp:r@d:h", client_backup_icinga_hote="mon-01",
domaine_interne="exemple.internal", client_backup_ttl_icinga=21600, client_backup_age_warn_h=26,
client_backup_age_crit_h=50, inventory_hostname="site-mon-01", client_backup_jobs=[{"chemins": [str(d / "x")]}],
client_backup_ca_verification="/dev/null", client_backup_icinga_utilisateur="u",
).replace("/etc/setops/icinga-api.pass", str(d / "mdp")).replace("/etc/setops/restic.pass", str(d / "mdp"))
executable(d / "sonde.sh", rendu)
subprocess.run(["bash", str(d / "sonde.sh")], env={**os.environ, "PATH": f"{bin_}:{os.environ['PATH']}"},
capture_output=True, text=True)
return json.loads((d / "verdict.json").read_text()) if (d / "verdict.json").exists() else {}
def heure(il_y_a_h: float) -> str:
t = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(hours=il_y_a_h)
return t.astimezone().isoformat(timespec="microseconds")[:-6] + "123" + t.astimezone().isoformat()[-6:]
def main() -> int:
with tempfile.TemporaryDirectory() as d:
v = lancer(Path(d), [{"time": heure(30), "paths": ["/var/backups/setops/postgresql"]},
{"time": heure(1), "paths": ["/var/backups/setops/postgresql", "/var/backups/setops/loki"]}])
verifier(v.get("exit_status") == 0 and "OK" in v.get("plugin_output", ""),
f"deux groupes, l'ancien en premier : la sonde juge le plus recent ({v.get('plugin_output')})")
with tempfile.TemporaryDirectory() as d:
v = lancer(Path(d), [{"time": heure(30), "paths": ["/a"]}])
verifier(v.get("exit_status") == 1 and "EN RETARD" in v.get("plugin_output", ""),
f"un seul groupe, vieux de 30 h : toujours EN RETARD ({v.get('plugin_output')})")
if ECHECS:
print(f"\n{len(ECHECS)} echec(s).")
return 1
print("\nLa sonde juge le plus recent instantane, quel que soit l'ordre des groupes.")
return 0
if __name__ == "__main__":
sys.exit(main())