Leçon 4 — Tester et exploiter

Un incident de test, puis on efface tout

5 min de lecture

Un incident de test, puis on efface tout

Les tests sans GCP

Les outils sont testés avec des objets factices (SimpleNamespace) qui imitent juste ce que lit le code :

files/test_supervision.py
def test_lire_erreurs_construit_le_filtre_et_borne(monkeypatch):
    vus = {}
    def list_entries(filter_, order_by, max_results, page_size):
        vus.update(filtre=filter_, max=max_results)
        return [NS(timestamp=T0, severity="ERROR", payload={"message": "Timeout 1"}, labels={"service": "api"},
                   resource=NS(type="global", labels={}))]
    monkeypatch.setenv("GOOGLE_CLOUD_PROJECT", "projet-test")
    monkeypatch.setattr(agent, "_logging", lambda: NS(list_entries=list_entries))
    r = agent.lire_erreurs(heures=500, journal="lab-supervision", max_entrees=10**6)
    assert 'logName="projects/projet-test/logs/lab-supervision"' in vus["filtre"]
    assert vus["max"] == 1000
TestVérifie
test_signature_ignore_les_nombresdeux messages qui ne diffèrent que par un nombre ont la même signature
test_regrouper_et_correler_le_scenarioréutilise scenario() du générateur : 4 timeouts, 3 erreurs 503, lien de 120 s
test_pas_de_correlation_au_dela_de_5_minutes, test_lire_erreurs_..., test_compter_par_severitefenêtre, filtre et bornes, addition des points
test_agent_sans_outil_d_ecriturela liste fermée des outils

L'incident fictif

files/generer_logs_test.py
def scenario(maintenant):
    t = []
    for i in range(4):
        t.append((timedelta(minutes=10 - i), "ERROR", "api-commandes",
                  f"Timeout: connexion à commandes-db expirée après 30 s (requête {1040 + i})"))
    for i in range(3):
        t.append((timedelta(minutes=8 - i), "ERROR", "passerelle",
                  f"HTTP 503 : upstream api-commandes indisponible (tentative {i + 1})"))
    t.append((timedelta(minutes=5), "CRITICAL", "api-commandes", "Pool de connexions épuisé : 50/50 utilisées"))
    ...

logger = client.logger("lab-supervision", labels={"formation-lab": "supervision-cloud-logging"})
logger.log_struct({"message": message, "service": service}, severity=severite,
                  timestamp=maintenant - decalage, labels={...}, resource=Resource(type="global", labels={}))

log_struct écrit une entrée JSON (jsonPayload) ; un timestamp dans le passé reconstitue une chronologie sur dix minutes ; la ressource global est générique, puisque ces « services » n'existent pas.

sequenceDiagram
  participant G as "generer_logs_test.py"
  participant L as "Cloud Logging"
  participant M as "Cloud Monitoring"
  participant A as "Agent SRE"
  G->>L: 11 entrées (journal lab-supervision)
  L->>M: log_entry_count (quelques minutes)
  A->>L: lire_erreurs(1, lab-supervision)
  A->>M: compter_par_severite(1, lab-supervision)
  A-->>A: rapport
  Note over G,L: puis gcloud logging logs delete lab-supervision

Lancer l'ensemble

terminal
cd files
export GOOGLE_GENAI_USE_VERTEXAI=TRUE GOOGLE_CLOUD_PROJECT="mon-projet-gcp" GOOGLE_CLOUD_LOCATION=europe-west1
pytest -q
python generer_logs_test.py
# attendre 3 à 5 minutes pour que la métrique soit à jour
python lancer.py lab-supervision 1

Résultat obtenu

Sorties réelles du 2026-10-15 :

pytest et générateur (2026-10-15)
......                                                                   [100%]
6 passed, 5 warnings in 0.76s
11 entrées écrites dans le journal lab-supervision
gcloud logging read (2026-10-15, extrait)
TIMESTAMP                       SEVERITY  SERVICE        MESSAGE
2026-10-15T14:38:30.483475Z     CRITICAL  api-commandes  Pool de connexions épuisé : 50/50 utilisées
2026-10-15T14:37:30.483475Z     ERROR     passerelle     HTTP 503 : upstream api-commandes indisponible (tentative 3)
2026-10-15T14:36:30.483475Z     ERROR     api-commandes  Timeout: connexion à commandes-db expirée après 30 s (requête 1043)
...
2026-10-15T14:33:30.483475Z     ERROR     api-commandes  Timeout: connexion à commandes-db expirée après 30 s (requête 1040)

Le rapport complet de l'agent est montré dans les leçons précédentes. Notre grille de lecture :

CritèreVerdict sur ce run
Nombres et dates exactsoui (4, 3, 1 ; fenêtres correctes)
Cause probable identifiéeoui : timeout de commandes-db avant les 503
Corrélations présentées comme indicesoui, mais un faux lien (503 → pool épuisé)
Remédiations exécutables telles quellesnon : noms de ressources supposés (commandes-db-instance), à valider

L'agent propose « gcloud sql instances patch commandes-db-instance --tier=db-n1-standard-2 ». Que faites-vous ?

Supprimer le journal de test

files/nettoyer.sh
gcloud logging logs delete lab-supervision --project "$PROJECT_ID" --quiet
gcloud logging read 'logName:"logs/lab-supervision"' --project "$PROJECT_ID" --freshness 1d --format 'value(insertId)' | wc -l

logs delete supprime toutes les entrées du journal. Les points déjà calculés de la métrique log_entry_count restent dans Cloud Monitoring (une métrique système ne se supprime pas) : ce sont des compteurs, sans contenu.

Après nettoyer.sh, que reste-t-il de l'incident fictif ?

Résultat obtenu (nettoyage)

nettoyer.sh (2026-10-15)
Deleted [lab-supervision].
0

Exercice

Ajoutez au générateur un second scénario « nuit calme » (uniquement des entrées INFO et WARNING), puis écrivez le test sans clé qui vérifie que regrouper et correler ne renvoient rien quand on ne garde que les sévérités ERROR et plus.

Voir la solution
generer_logs_test.py
def scenario_calme(maintenant):
    return [(timedelta(minutes=9), "INFO", "api-commandes", "Démarrage du worker 1"),
            (timedelta(minutes=6), "WARNING", "passerelle", "Latence p95 élevée : 900 ms"),
            (timedelta(minutes=3), "INFO", "api-commandes", "Purge du cache terminée")]
test_supervision.py
def test_nuit_calme_aucun_incident():
    entrees = [{"horodatage": T0 - d, "severite": s, "source": src, "message": m}
               for d, s, src, m in generer_logs_test.scenario_calme(T0) if s in ("ERROR", "CRITICAL")]
    assert agent.regrouper(entrees) == []
    assert agent.correler([]) == []

Testez aussi ce cas avec le vrai modèle : un bon agent d'astreinte doit savoir dire « rien d'anormal ».

Je sais faire

  • Tester des outils Cloud Logging et Monitoring sans aucun accès réel
  • Écrire un incident fictif horodaté avec log_struct
  • Évaluer un rapport d'agent avec une grille simple
  • Supprimer un journal de test et vérifier sa disparition