# Troubleshooting Guide: TR-069 Exchange Viewer Ce guide documente les problèmes courants du système de visualisation des échanges TR-069/CWMP, leurs causes et solutions. ## Table des matières 1. [Déconnexions SSE](#1-déconnexions-sse) 2. [Timeouts MongoDB et récupération](#2-timeouts-mongodb-et-récupération) 3. [Diagnostic de perte de messages](#3-diagnostic-de-perte-de-messages) 4. [Vérification des index et du TTL](#4-vérification-des-index-et-du-ttl) 5. [Problèmes frontend (SequenceViewer)](#5-problèmes-frontend-sequenceviewer) 6. [Feature flags et rollback](#6-feature-flags-et-rollback) --- ## 1. Déconnexions SSE ### 1.1 Causes courantes | Cause | Symptôme | Fréquence | |-------|----------|-----------| | **Timeout d'inactivité (5 min)** | Déconnexion après 5 minutes sans nouvel échange | Normal — par design | | **Timeout absolu (2 h)** | Déconnexion après 2 heures de connexion continue | Normal — par design | | **Proxy/reverse-proxy buffering** | Messages SSE arrivent en burst au lieu de temps réel | Courant avec nginx | | **Perte réseau** | Indicateur "disconnected" dans le viewer | Variable | | **SSE désactivé (feature flag)** | Erreur 503 à la connexion SSE | Intentionnel | | **Device non tracé** | Erreur 404 à la connexion SSE | Erreur utilisateur | ### 1.2 Solutions #### Timeout d'inactivité / absolu Le système est conçu pour fermer les connexions inactives. Le client se reconnecte automatiquement via le mécanisme `Last-Event-ID` — aucun message n'est perdu. Pour ajuster les timeouts : ```bash # Augmenter le timeout d'inactivité (défaut: 300000ms = 5 min) EXCHANGE_STORE_SSE_INACTIVITY_TIMEOUT_MS=600000 # Augmenter le timeout absolu (défaut: 7200000ms = 2h) EXCHANGE_STORE_SSE_ABSOLUTE_TIMEOUT_MS=14400000 ``` #### Proxy/reverse-proxy buffering Si un reverse-proxy (nginx, HAProxy) est en place, il peut bufferiser les réponses SSE. Le header `X-Accel-Buffering: no` est déjà envoyé par le serveur, mais vérifiez aussi la configuration nginx : ```nginx location /api/v1/tr069-trace/ { proxy_buffering off; proxy_cache off; proxy_set_header Connection ''; proxy_http_version 1.1; chunked_transfer_encoding off; } ``` #### Client qui ne se reconnecte pas Le composable `useExchangeStream` utilise l'`EventSource` natif avec retry automatique (3 secondes par défaut). Si la reconnexion ne fonctionne pas : 1. Vérifiez que le device est toujours en trace active 2. Vérifiez les logs serveur pour des erreurs 404 ou 503 3. Vérifiez la valeur `retry` dans la configuration SSE : ```bash # Intervalle de reconnexion envoyé au client (défaut: 3000ms) EXCHANGE_STORE_SSE_RETRY_INTERVAL_MS=3000 ``` ### 1.3 Diagnostic dans les logs Cherchez les entrées `[SSE]` dans les logs applicatifs : ```bash docker compose logs app | grep '\[SSE\]' ``` Messages attendus : - `[SSE] Connection closed { deviceId: "...", reason: "inactivity" }` — timeout normal - `[SSE] Connection closed { deviceId: "...", reason: "absolute" }` — timeout absolu normal - `[SSE] Connection closed { deviceId: "...", reason: "client_disconnect" }` — client a fermé - `[SSE] Error fetching missed exchanges for reconnection` — problème MongoDB lors du rattrapage --- ## 2. Timeouts MongoDB et récupération ### 2.1 Scénarios de timeout | Scénario | Impact | Comportement | |----------|--------|--------------| | **Write timeout (persistance)** | Message potentiellement perdu | Retry (critique) ou discard (non-critique) | | **Read timeout (API >5s)** | Résultats partiels retournés | `isTruncated: true` dans la réponse | | **Connection lost** | Toutes les opérations échouent | Les sessions CWMP continuent sans log | ### 2.2 Write timeout — Retry automatique Le `ExchangeStoreService` gère les échecs d'écriture selon la criticité du message : **Messages critiques** (Inform, GetParameterValues, SetParameterValuesResponse, Fault, etc.) : - 3 tentatives avec backoff exponentiel : 1s, 2s, 4s - Si toutes échouent : log error + discard (session CWMP non impactée) **Messages non-critiques** (Empty, Unknown) : - Aucun retry — discard immédiat + log warning Vérifier les échecs de retry : ```bash docker compose logs app | grep '\[ExchangeStore\] All retries exhausted' docker compose logs app | grep '\[ExchangeStore\] Critical message persistence failed' ``` Pour ajuster la politique de retry : ```bash # Nombre max de tentatives (défaut: 3) EXCHANGE_STORE_RETRY_MAX_ATTEMPTS=3 # Délai de base en ms pour le backoff (défaut: 1000) # Résultat : 1000ms, 2000ms, 4000ms EXCHANGE_STORE_RETRY_BASE_DELAY_MS=1000 ``` ### 2.3 Read timeout — Résultats partiels Si une requête API dépasse 5 secondes, le contrôleur retourne les résultats partiels avec un flag : ```json { "exchanges": [...], "metadata": { "isTruncated": true, "total": 4500 } } ``` **Causes courantes :** - Index manquant sur `(deviceId, timestamp)` — voir section 4 - Trop de documents pour le device (proche du cap 5000) - Charge MongoDB élevée (vérifier les métriques) Pour ajuster le timeout de lecture : ```bash # Timeout en ms (défaut: 5000) EXCHANGE_STORE_READ_TIMEOUT_MS=5000 ``` ### 2.4 Vérifier la connectivité MongoDB ```bash # Se connecter au shell MongoDB docker compose exec mongodb mongosh # Vérifier le statut db.adminCommand({ serverStatus: 1 }).connections # Vérifier la latence db.adminCommand({ ping: 1 }) ``` --- ## 3. Diagnostic de perte de messages ### 3.1 Arbre de décision ``` Message manquant dans le viewer ? │ ├─ Le device est-il en trace ? │ └─ Non → Activer la trace d'abord. Les messages ne sont PAS persistés hors trace. │ ├─ Le feature flag FEATURE_EXCHANGE_PERSISTENCE est-il à true ? │ └─ Non → Activer la persistance : FEATURE_EXCHANGE_PERSISTENCE=true │ ├─ Le message apparaît-il dans MongoDB mais pas dans le viewer ? │ └─ Oui → Problème SSE (voir section 1) ou problème frontend (voir section 5) │ ├─ Le message n'apparaît PAS dans MongoDB ? │ ├─ Vérifier les logs de retry : grep "All retries exhausted" │ ├─ Vérifier le cap (5000 messages) : les anciens messages sont supprimés │ └─ Vérifier le TTL (24h) : les messages >24h sont auto-supprimés │ └─ Message tronqué (XML incomplet) ? └─ Le rawXml dépassait 2MB → grep "rawXml exceeds 2MB" dans les logs ``` ### 3.2 Vérifier les messages en base ```bash # Se connecter au shell MongoDB docker compose exec mongodb mongosh # Compter les messages pour un device use adam_acs db.cwmp_exchanges.countDocuments({ deviceId: "" }) # Voir les derniers messages db.cwmp_exchanges.find({ deviceId: "" }).sort({ timestamp: -1 }).limit(5).pretty() # Voir la séquence max (dernier message) db.cwmp_exchange_counters.findOne({ _id: "" }) ``` ### 3.3 Vérifier les logs de persistance ```bash # Chercher les erreurs de persistance docker compose logs app | grep '\[ExchangeStore\]' # Messages spécifiques docker compose logs app | grep 'Failed to persist exchange' docker compose logs app | grep 'Non-critical message persistence failed' docker compose logs app | grep 'All retries exhausted' docker compose logs app | grep 'Message cap enforced' docker compose logs app | grep 'rawXml exceeds 2MB' ``` ### 3.4 Message supprimé par le cap Le système maintient un maximum de 5000 messages par device. Quand ce cap est dépassé, les messages les plus anciens sont supprimés. ```bash # Vérifier si le cap a été atteint docker compose logs app | grep 'Message cap enforced' ``` Pour ajuster le cap : ```bash # Nombre max de messages par device (défaut: 5000) EXCHANGE_STORE_MAX_MESSAGES=5000 ``` ### 3.5 Message supprimé par le TTL MongoDB supprime automatiquement les documents dont `createdAt` dépasse 24 heures (via l'index TTL). ```bash # Vérifier les documents les plus anciens docker compose exec mongodb mongosh --eval " use adam_acs; db.cwmp_exchanges.find({}, { createdAt: 1, deviceId: 1 }).sort({ createdAt: 1 }).limit(3).pretty() " ``` --- ## 4. Vérification des index et du TTL ### 4.1 Lister les index existants ```bash docker compose exec mongodb mongosh --eval " use adam_acs; db.cwmp_exchanges.getIndexes().forEach(idx => printjson(idx)) " ``` **Index attendus :** | Nom | Champs | Options | |-----|--------|---------| | `_id_` | `{ _id: 1 }` | — | | `idx_deviceId_timestamp` | `{ deviceId: 1, timestamp: 1 }` | — | | `idx_deviceId_sequenceNumber` | `{ deviceId: 1, sequenceNumber: 1 }` | `unique: true` | | `idx_createdAt_ttl` | `{ createdAt: 1 }` | `expireAfterSeconds: 86400` | ### 4.2 Vérifier l'index TTL ```bash docker compose exec mongodb mongosh --eval " use adam_acs; const indexes = db.cwmp_exchanges.getIndexes(); const ttlIdx = indexes.find(i => i.name === 'idx_createdAt_ttl'); if (ttlIdx) { print('TTL index OK — expireAfterSeconds:', ttlIdx.expireAfterSeconds); } else { print('ERREUR: TTL index manquant !'); } " ``` ### 4.3 Recréer les index manuellement (si manquants) Si des index sont manquants après un incident : ```bash docker compose exec mongodb mongosh --eval " use adam_acs; // Index composite (deviceId + timestamp) db.cwmp_exchanges.createIndex( { deviceId: 1, timestamp: 1 }, { name: 'idx_deviceId_timestamp' } ); // Index unique (deviceId + sequenceNumber) db.cwmp_exchanges.createIndex( { deviceId: 1, sequenceNumber: 1 }, { unique: true, name: 'idx_deviceId_sequenceNumber' } ); // Index TTL (24h) db.cwmp_exchanges.createIndex( { createdAt: 1 }, { expireAfterSeconds: 86400, name: 'idx_createdAt_ttl' } ); print('Tous les index recréés.'); " ``` ### 4.4 Vérifier le statut du TTL monitor Le TTL monitor de MongoDB tourne par défaut toutes les 60 secondes. Pour vérifier qu'il fonctionne : ```bash docker compose exec mongodb mongosh --eval " const status = db.adminCommand({ serverStatus: 1 }); print('TTL deletedDocuments:', status.metrics.ttl.deletedDocuments); print('TTL passes:', status.metrics.ttl.passes); " ``` Si `passes` n'augmente pas, le TTL monitor pourrait être bloqué (rare, nécessite un restart MongoDB). ### 4.5 Modifier le TTL Pour changer la durée du TTL sans recréer l'index : ```bash docker compose exec mongodb mongosh --eval " use adam_acs; db.runCommand({ collMod: 'cwmp_exchanges', index: { name: 'idx_createdAt_ttl', expireAfterSeconds: 172800 // 48h } }); " ``` N'oubliez pas de mettre à jour la variable d'environnement pour cohérence : ```bash EXCHANGE_STORE_TTL_SECONDS=172800 ``` ### 4.6 Vérifier la collection counters La collection `cwmp_exchange_counters` stocke les séquences atomiques par device : ```bash docker compose exec mongodb mongosh --eval " use adam_acs; db.cwmp_exchange_counters.find().forEach(doc => { print(doc._id, '→ seq:', doc.seq); }) " ``` Si un compteur est incohérent avec les données réelles : ```bash # Recalculer le max sequenceNumber pour un device docker compose exec mongodb mongosh --eval " use adam_acs; const deviceId = ''; const maxSeq = db.cwmp_exchanges.find({ deviceId }).sort({ sequenceNumber: -1 }).limit(1).toArray(); if (maxSeq.length > 0) { db.cwmp_exchange_counters.updateOne( { _id: deviceId }, { \$set: { seq: maxSeq[0].sequenceNumber } } ); print('Counter reset to', maxSeq[0].sequenceNumber); } " ``` --- ## 5. Problèmes frontend (SequenceViewer) ### 5.1 Le viewer ne se connecte pas au SSE **Causes possibles :** 1. Le device n'est pas en trace → activer la trace d'abord 2. Le feature flag `FEATURE_SSE_ENABLED` est à `false` → erreur 503 3. Problème CORS si accès depuis un domaine différent **Diagnostic :** Ouvrir la console navigateur (F12) et chercher les erreurs EventSource. ### 5.2 Les messages ne s'affichent pas en temps réel Si les messages arrivent dans MongoDB mais pas dans le viewer : 1. Vérifier l'indicateur de connexion : doit être "connected" (vert) 2. Si "disconnected" (rouge) : voir section 1 3. Si "paused" (jaune) : l'auto-scroll est désactivé car l'utilisateur a scrollé vers le haut — cliquer "Resume auto-scroll" ### 5.3 Performance lente avec beaucoup de messages Le viewer utilise du virtual scrolling pour >200 messages. Si la performance se dégrade : - Le cap client est fixé à 1000 messages en mémoire (les plus anciens sont purgés côté client) - Si le nombre total dépasse 5000 en base, les plus anciens sont purgés côté serveur - Utiliser "Clear exchanges" pour repartir d'un état vide ### 5.4 L'inspecteur XML affiche du contenu brut (non formaté) Si le XML n'est pas mis en forme : - Le XML est peut-être malformé → l'inspecteur affiche le contenu brut en fallback - Vérifier si le message a un `rawXml` vide (cas "Empty") --- ## 6. Feature flags et rollback ### 6.1 Flags disponibles | Variable d'environnement | Défaut | Effet si `false`/`file` | |--------------------------|--------|-------------------------| | `FEATURE_EXCHANGE_PERSISTENCE` | `true` | Les échanges ne sont pas écrits en MongoDB | | `FEATURE_SSE_ENABLED` | `true` | L'endpoint SSE retourne 503, le client utilise le REST polling (5s) | | `FEATURE_EXCHANGE_API_SOURCE` | `mongodb` | Si `file`, l'API lit depuis l'ancien système de fichiers | ### 6.2 Procédure de rollback rapide En cas de problème critique : ```bash # 1. Désactiver la persistance (les sessions CWMP continuent normalement) FEATURE_EXCHANGE_PERSISTENCE=false # 2. Désactiver le SSE (le frontend utilise le polling REST) FEATURE_SSE_ENABLED=false # 3. Revenir à la source fichier pour l'API FEATURE_EXCHANGE_API_SOURCE=file ``` Redémarrer l'application après modification : ```bash docker compose restart app ``` ### 6.3 Vérifier l'état actuel des flags Les valeurs actives sont loguées au démarrage de l'application. Vous pouvez aussi interroger le health check : ```bash curl -s http://localhost:8080/api/v1/health/tr069-exchanges | python3 -m json.tool ``` --- ## Commandes utiles — Résumé rapide ```bash # Status général docker compose logs app --tail 20 | grep -E '\[ExchangeStore\]|\[SSE\]' # Compter les échanges d'un device docker compose exec mongodb mongosh --eval "use adam_acs; db.cwmp_exchanges.countDocuments({deviceId:''})" # Vérifier les index docker compose exec mongodb mongosh --eval "use adam_acs; db.cwmp_exchanges.getIndexes().forEach(i=>printjson(i))" # Vérifier le TTL monitor docker compose exec mongodb mongosh --eval "printjson(db.adminCommand({serverStatus:1}).metrics.ttl)" # Tester la connexion SSE manuellement curl -N -H "Accept: text/event-stream" http://localhost:8080/api/v1/tr069-trace//exchanges/stream # Tester la reconnexion avec Last-Event-ID curl -N -H "Accept: text/event-stream" -H "Last-Event-ID: 42" http://localhost:8080/api/v1/tr069-trace//exchanges/stream # Purger les échanges d'un device curl -X DELETE http://localhost:8080/api/v1/tr069-trace//exchanges ```