Skip to content

Navigation Menu

Sign in
Sign up

Repository files navigation

cucm-call-forwarding

Gestion des renvois d'appel inter-sites sur Cisco CUCM : détection de chaînes et de boucles, classification graduée de l'état réel, et écritures compensées sur une API qui n'a pas de transactions.

Réimplémentation générique et publiable d'un outil interne qui a remplacé une demande par ticket au support par une action autonome des sites.

Le problème

Poser un renvoi d'appel sur un site n'est pas la modification d'un objet unique. C'est trois écritures cohérentes :

  1. le point d'entrée du site reçoit une destination, une classe de service et un délai de déclenchement ;
  2. deux motifs de traduction, dans deux partitions distinctes, interceptent normalement l'appel en amont pour l'annonce et la résolution d'annuaire — ils doivent être neutralisés pendant la durée du renvoi.

Neutraliser n'est pas supprimer : le motif est renommé avec un préfixe qui le rend non composable. L'objet, sa partition et toute sa configuration sont préservés, l'opération est réversible et atomique par objet, et une interruption ne laisse aucun orphelin.

AXL n'offre aucune transaction. Trois écritures, aucune garantie d'atomicité : l'échec partiel est un état réel qu'il faut traiter.

Ce que fait ce dépôt

Module Rôle
numbers Normalisation des numéros saisis, refus explicite de l'inconnu
chains Suivi du graphe de renvois : chaînes, boucles, sorties externes
state Classification graduée de l'état réel d'un site
operations Orchestration des trois écritures et compensation vérifiée
axl Adaptateur SOAP — le seul module qui sait que c'est du Cisco
filestore Backend sur fichier, pour tourner sans CUCM
cli Interface en ligne de commande

Essayer sans CUCM

Il n'existe pas de double de test pour AXL. Le dépôt embarque donc un backend sur fichier JSON qui satisfait le même protocole, ce qui rend l'outil exécutable par n'importe qui après un clone :

pip install -e ".[dev]"
python -m cucm_forwarding.cli survey
SITE_A Agence A rest idle
SITE_B Agence B forwarded forwarded to 1003 (ring timeout 12s)
 └─ forwards to SITE_C, which is itself forwarded to an external number: SITE_B -> SITE_C -> (external)
SITE_C Agence C forwarded forwarded to 00612345678 (ring timeout 12s)
SITE_D Agence D drifted idle, with leftovers that will be normalised: calling search space is 'CSS_STALE', expected 'CSS_REST'
SITE_E Agence E broken translation patterns are neutralised but no forward destination is set: inbound routing is cut with nothing catching the calls

Les écritures sont en simulation par défaut :

python -m cucm_forwarding.cli set SITE_A 1002 # affiche, n'écrit rien
python -m cucm_forwarding.cli set SITE_A 1002 --apply # écrit
python -m cucm_forwarding.cli clear SITE_B --apply

Les trois idées qui comptent

L'état d'un site n'est pas binaire

Un outil qui suppose que chaque site est soit au repos, soit proprement renvoyé se trompe sur quelques pourcents d'un parc réel — et se trompe dans le sens coûteux : il refuse des sites légitimes, ou écrase silencieusement une configuration délibérée.

La classification est donc graduée. Un écart cosmétique (délai non standard, classe de service résiduelle) est normalisé en silence. Un état contradictoire est refusé et remonté, parce qu'aucune manipulation dans le portail ne peut le corriger :

  • motifs neutralisés sans destination de renvoi → le routage entrant est coupé et rien ne rattrape les appels ;
  • destination posée avec les motifs toujours actifs → ils interceptent l'appel avant que le renvoi s'applique : le renvoi est configuré et inerte.

Les deux sont la trace d'une opération manuelle interrompue.

Deux renvois indépendants se composent

Un site renvoyé vers un deuxième, lui-même renvoyé ailleurs, fait aboutir les appels à un endroit que personne n'a choisi et que ni l'un ni l'autre des opérateurs ne voit.

La détection a manqué deux cas lors de sa première écriture, tous deux observés en production :

  • un renvoi visant un site par son numéro public plutôt que par son numéro interne — le site ressemblait alors à une destination externe et la chaîne devenait invisible ;
  • une chaîne quittant le parc après deux sauts, qui n'est pas plus longue qu'un renvoi ordinaire : la longueur seule ne peut donc pas servir de test.

Les deux sont couverts par des tests. Le comportement retenu est d'avertir sans bloquer — une chaîne est parfois voulue — sauf la boucle directe, qui est refusée.

Une compensation qu'on ne croit pas sur parole

Sans transaction, la seule garantie possible est la compensation au mieux : capture de l'état avant, application séquentielle, et en cas d'échec annulation en ordre inverse.

Le point important est que l'annulation est vérifiée par une relecture, plutôt que déduite du fait que les appels d'annulation n'ont pas levé d'erreur. Un backend qui accepte chaque appel sans rien changer produirait sinon un « tout est rentré dans l'ordre » entièrement faux — c'est un des cas de test.

Si la compensation échoue à son tour, le site reste dans un état intermédiaire. Ce risque est tracé, pas éliminé : il est signalé, journalisé, et distingué par son code de sortie.

Code Signification
0 Appliqué, ou rien à faire
2 Refusé avant toute écriture
3 Échec en cours, site rétabli et rétablissement vérifié
4 Échec en cours, état intermédiaire — intervention humaine

Lectures en SQL, écritures via l'API objet

AXL expose un point d'entrée d'exécution SQL. Il est utilisé pour toutes les lectures de masse : une vue d'ensemble du parc coûte quelques requêtes contre plusieurs centaines d'appels unitaires.

Aucune écriture ne passe par là. Une écriture directe contournerait la validation métier de l'éditeur et, surtout, la propagation de la configuration vers les nœuds de traitement d'appel : la base changerait pendant que les téléphones continueraient de se comporter comme avant. La fonction d'accès SQL refuse d'ailleurs toute requête qui n'est pas un SELECT, en défense en profondeur.

Utiliser un vrai CUCM

Le WSDL AXL est distribué par Cisco avec le produit et n'est pas redistribuable : il n'est donc pas dans ce dépôt. Récupérez-le depuis votre propre grappe (interface d'administration → Plugins), puis :

from cucm_forwarding.axl import AxlClient
client = AxlClient(
 host="cucm.example.local",
 username="svc-axl",
 password=os.environ["AXL_PASSWORD"],
 wsdl_path="wsdl/AXLAPI.wsdl",
)
print(client.check())

Trois pièges, tous rencontrés :

Le client se construit sans qu'aucun paquet n'atteigne le serveur. Le WSDL étant local, l'instanciation réussit toujours. Journaliser « connecté » à ce stade n'a aucune valeur ; check() fait un vrai aller-retour.

Un refus d'autorisation revient en HTML. Le client SOAP ne sait pas l'interpréter et le traduit par une faute sans rapport. Si le message paraît absurde, vérifiez que le compte porte bien le rôle d'accès à l'API AXL — les groupes génériques d'accès API ne l'impliquent pas.

L'adresse de service déclarée dans le WSDL n'est pas la vraie. Le binding doit être instancié explicitement contre l'endpoint de la grappe.

AxlClient.read_state() est volontairement laissé non implémenté : faire correspondre un identifiant de site aux objets d'un plan de numérotation est spécifique à chaque parc.

Tests

pytest --cov=cucm_forwarding

83 tests, 91 % de couverture. Toute la logique décidable — normalisation, chaînes, classification, compensation — est isolée de la couche réseau et testée sur jeux de données fictifs, y compris les chemins d'échec :

  • panne injectée sur chacune des trois écritures, avec vérification que le site revient exactement à son état initial ;
  • annulation qui échoue à son tour → INCONSISTENT ;
  • annulation qui prétend réussir sans rien changer → détectée par la relecture.

C'est la stratégie choisie précisément parce qu'il n'existe pas de double AXL : faute de pouvoir simuler le système, on rend testable tout ce qui n'a pas besoin de lui.

Limites assumées

  • La concurrence n'est pas traitée. Deux opérateurs agissant simultanément sur le même site produiraient un résultat indéterminé. Un verrou par site est nécessaire.
  • Le processus de réconciliation périodique n'est pas inclus ici.
  • La compensation n'a été éprouvée que par injection de panne.
  • Un verdict de chaîne dépend de l'exactitude de l'inventaire : une entrée dont le numéro n'existe pas dans le plan de numérotation produirait un renvoi silencieusement inopérant.

Licence

MIT — voir LICENSE.

About

Renvois d'appel inter-sites sur Cisco CUCM : détection de chaînes et de boucles, classification graduée de l'état, écritures compensées et vérifiées sur une API sans transactions

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

AltStyle によって変換されたページ (->オリジナル) /