L’automatisation du réseau est grandement représentée par la gestion des configurations, que ce soit de manière totale ou partielle, ou par la récupération, le traitement et l’affichage d’informations.
Cependant, un autre aspect de l’automatisation est également très important, mais souvent méconnu : La réactivité à un évènement, ou « event-driven automation ».
L’« event-driven automation » permet de pouvoir rendre un réseau réactif à un évènement.
Nous pourrions cité par exemple le Network Analytics Engine.
Cependant, ArubaOS-CX possède la capacité de rentrer dans ce modèle d’automatisation par un autre biais que le Network Analytics Engine : Les Notifications Subscriptions.
Cette ressource, joignable au travers d’une API, permet à un script externe de pouvoir souscrire à la surveillance du changement d’état de n’importe quelle ressource d’ArubaOS-CX, pour peu qu’elle soit disponible au travers d’une API.
Pour se faire, le script externe doit ouvrir un websocket sécurisé (WSS), et écouter en temps réel et en continu, de manière à pouvoir recevoir la notification du switch dès que ce dernier détecte un changement d’état de la ressource surveillée.
Nous allons donc tester tout cela.
Tout d’abord, nous allons faire une approche sur ce type de notification via une interface graphique, afin d’illustrer ce concept plus facilement.
Nous allons donc essayer via l’extension « Simple WebsocketClient » de Chrome.
Tout d’abord, nous allons ouvrir un canal sécurisé, via WebSocket (WSS), vers l'équipement ArubaOS-CX qui nous intéresse. Ici un 8320.
Pour ce premier test, nous utilisons un client WebSocket qui est une extension disponible pour Google Chrome : Simple WebSocket Client.
On utilise donc l'URL avec le format "wss://<@IP_CX>/rest/v1/notification". Ce qui nous donne :

Bien entendu, il est nécessaire d’ouvrir une session API au préalable. Pour ce test, nous avons donc ouvert une session sur la WebUI de notre 8320 dans un autre onglet, ce qui nous permet de bénéficier du token existant dans le navigateur.
Une fois le canal ouvert, le status est donc en « opened ».
Nous pouvons alors envoyer notre demande de souscription aux notifications liées à un des éléments du switch.
Pour se faire, il est nécessaire d’envoyer, via un JSON, l’élément auquel on souhaite souscrire (« topic »), ainsi que le type de notification :
{
"topics": [
{
"name": "/rest/v1/system/interfaces/1%2F1%2F2?attributes=admin_state"
}
],
"type": "subscribe"
}
Ce qui nous donne alors :

Nous avons ici un retour de 'équipement, au format JSON, qui nous indique que notre souscription aux notifications souhaitées est bien "success"
Faisons un test en désactivant l’interface 1/1/2, et voyons si nous avons un retour :

Nous avons recu instantanément un JSON de la part de notre équipement, avec un message :
- De type "notification"
- Concernant la ressource "topicname"
- indiquant qu'elle a changé de valeur/état ("operation":"modified")
- Avec comme nouvelle valeur/état "values"
Maintenant, allons un peu plus loin en intégrant toutes ces notions dans un script persistant, et permettent d'étendre les possibilités en termes d'actions.
Pour faire ces tests, nous allons prendre le script websocket-client.py, qui est disponible dans la documentation « ArubaOS-CX REST API Guide », disponible sur l’Aruba Support Portal.
Quelques explications sur ce script :
- Ce script utilise un module de Python, nommé « tornado », qui est un micro-framework web intégrant nativement la gestion des websocket sécurisés (wss).
- Le script prend l’URI de la ressource à surveiller, passée en argument lors du lancement du script, et l’utilise pour créer un objet « client » - Cela introduit la notion de Classe, notion primordiale pour tout ce qui touche à la Programmation Orientée Objet (POO)
- L’objet « client », lors de sa création, ouvre une session avec le switch ciblé, et souscrit à la ressource – Il utilise pour cela la fonction « websocket_client » du module tornado.
- Il lance alors une boucle infinie (« While true »), et attend toutes les notifications reçues de la part de l’équipement. Le premier message reçu, en réponse à la demande de souscription, est testé, en vérifiant qu’il est bien au format JSON. Si ce n’est pas le cas, alors le script s’arrête. Si c’est bon, alors le canal est maintenu, et le script attend les notifications afin de les traiter et les afficher.
Ce script, dans sa conception initiale, permet uniquement d’afficher les notifications reçues de l’équipement.
Afin de tester cette première approche, nous pouvons utiliser la ligne de commande suivante :
python3 websocket-client.py wss://10.105.11.1/rest/v1/notification /rest/v1/system/interfaces/1%2F1%2F2
Avec cette commande, qui lance le script websocket-client.py, nous donnons en argument 2 éléments distincts :
- Adresse WSS pour l’ouverture et la souscription à une notification.
- URI source de notification – Globalement, l’élément que nous souhaitons surveiller, et qui fera l’objet d’une notification vers notre script des lors qu’un changement est détecté.
Lorsque l’on fait un « show events » afin de voir les logs associés à la souscription, voici ce que nous pouvons voir :
2019-07-12:14:55:10.232688|hpe-restd|LOG_DEBUG|cookie not found
2019-07-12:14:55:10.265948|hpe-restd|LOG_INFO|Authenticated user admin with privilege level 15
2019-07-12:14:55:10.541862|hpe-restd|LOG_DEBUG|Authorization succeeded for user admin, for resource RoleDefault, with action GET
2019-07-12:14:55:10.615906|hpe-restd|LOG_INFO|Received subscribe message: {"type": "subscribe", "topics": [{"name": "/rest/v1/system/interfaces/1%2F1%2F2 "}]}
Si on modifie le script existant, en ajoutant l’affichage du message de réponse à la demande de souscription :
while True:
msg = yield self.ws.read_message()
self.count = self.count + 1
if self.count == 1:
print("Reponse à la subscription :
{}".format(msg))
msg_json = json.loads(msg)
subs_id = msg_json['subscriber_name']
print("Subscriber ID : {}".format(subs_id))
msg_in_json = self.check_if_JSON(msg)
Nous obtenons alors :
trying to connect
connected
Reponse à la subscription :
{"type":"success","data":[{"topicname":"/rest/v1/system/interfaces/1%2F1%2F2","resources":[{"operation":"","uri":"/rest/v1/system/interfaces/1%2F1%2F2","values":{"acl_init_status":"success","admin_state":"up","asic_config_state":{"asic_config_state_down_reason":"waiting","ready":false,<…intégralité de la réponse…….>"udld_rx_dropped":0,"udld_tx":0},"user_config":{"admin":"up"},"vsx_force_shutdown":false,"vsx_status":{}}}]}],"subscriber_name":"tut8uvi62c5n","subscription_name":"vnfrjc92jf"}
Subscriber ID : tut8uvi62c5n
PASS - Initial return JSON
Qu’est-ce que cela nous indique :
- La souscription est réalisée avec succès ("type":"success") – Le canal WSS est donc ouvert, et en attente de notifications.
- Le dictionnaire « data » indique la ressource à laquelle nous venons de souscrire, ainsi que l’état actuel de la ressource (les valeurs ont été tronquées, pour des notions de taille), l’ID donné au subscriber (en l’occurrence notre script), ainsi que l’ID donné à la souscription (Un subscriber pouvant avoir plusieurs souscriptions)
Dorénavant, vous avez donc, dans votre terminal, l’ensemble des mises à jour d’informations liées à cette interface. Cela va des updates LLDP, au changement de statut administratif, en passant par les variations de files d’attentes.
Globalement, tout ce qui est lié à l’interface et qui, si une valeur change, provoquera une notification. Par exemple :
{"type":"notification","data":[{"topicname":"/rest/v1/system/interfaces/1%2F1%2F2","resources":[{"operation":"modified","uri":"/rest/v1/system/interfaces/1%2F1%2F2","values":{"l1_next_state":"ready","l1_state":{"l1_state_down_reason":"","ready":true}}}]}]}
Comme déjà évoqué, le message, de type « notification », rappelle la ressource à laquelle nous avons souscrite, ainsi que :
- Le type d’opération survenue sur cette ressource (« modified »)
- La clé exacte qui a été modifiée (ici : « l1_next_state »), ainsi que sa nouvelle valeur.
Lorsque nous surveillons l’activité du script une fois lancé, nous avons énormément d’informations qui nous sont remontées, mais ceci est un peu trop verbeux, et compliqué à trier.
Afin de réduire le nombre d’éléments remontés, nous allons nous arrêter sur l’état du port, d’un point de vue administratif.
Pour cela, nous allons utiliser l’attribut « admin_state » dans l’URI à laquelle nous souscrivons :
python3 websocket-client.py wss://10.105.11.1/rest/v1/notification /rest/v1/system/interfaces/1%2F1%2F2?attributes=admin_state

Nous voyons bien que cet appel est beaucoup moins verbeux, puisque nous surveillons l’etat administratif du port.
Nous allons donc faire un « shutdown » sur ce port.
Nous sommes alors tout de suite informés par une notification que le statut du port a changé, et que la nouvelle valeur de la clé « admin_state » est down :

Si nous réactivons l’interface :

Bien entendu, avec le script fourni dans le REST Configuration Guide, il est possible de souscrire à plusieurs ressources en simultané. Par exemple, si nous souscrivons aux notifications de surveillance de l’état administratif des ports 1/1/1 et 1/1/2 :

Si nous désactivons le port 1/1/1 :

Et si nous désactivons le port 1/1/2 :

Allons un peu plus loin maintenant dans l’utilisation des websockets, notamment en augmentant un peu le niveau d’action en fonction d’un évènement.
Tout d’abord, modifions notre code de la manière suivante :
dict_json = json.loads(msg)
port_name = dict_json["data"][0]["resources"][0]['uri'].rsplit("/", 1)[1].replace("%2F","/")
if dict_json["data"][0]["resources"][0]["values"]["admin_state"] == 'down':
print("L'interface {} est passé du statut up au statut down ".format(port_name))
else:
print("L'interface {} est passé du statut down au statut up ".format(port_name))
lldp_nei = self.getLLDP(dict_json["data"][0]["resources"][0]['uri'])
print("Le neighbor LLDP est : {} - Adresse IP : {}".format(lldp_nei[0]["neighbor_info"]['chassis_name'], lldp_nei[0]["neighbor_info"]['mgmt_ip_list']))
Voici la fonction getLLDP() :
def getLLDP(self, port):
url_lldp = "https://10.105.11.1{}/lldp_neighbors?depth=2".format(port)
get_lldp = get(url_lldp, headers=self.cookie_header, verify=False)
return get_lldp.json()
Cette amélioration permettra de pouvoir :
- Avoir un affichage clair de l’évènement, et non pas juste un dictionnaire
- Lorsque le port monte, nous vérifions les informations LLDP de l’equipement qui vient de se connecter, et nous affichons alors son adresse IP et son hostname.
Si nous faisons un shutdown de l’interface :

Si nous réactivons l’interface :

Nous avons donc maintenant une interface un peu plus explicite sur les évènements qui se passent.
Allons encore un peu plus loin en intégrant une interaction avec une solution tierce, par exemple pour envoyer un message sur Slack lorsqu’un évènement se passe.
Nous modifions alors le code pour intégrer l’envoi d’un message dans Slack. De nombreux modules existent aujourd’hui pour permettre ce type d’opérations. Je vous laisse donc intégrer ce code dans votre script.
Mais l’idée est la suivante :
Si on regarde de plus près, on désactive alors l’interface 1/1/2 :

Si maintenant on réactive l’interface 1/1/2 :

Nous avons donc dorénavant un système de monitoring basé sur les évènements, donc extrêmement réactif, et permettant en plus l’interaction avec des produits tiers, garantissant ainsi une meilleure interactivité.
Mais bien entendu, les possibilités de ce type de mécanismes sont énormes. Nous pourrions également ajouter dans notre exemple une vérification/validation que l’équipement qui est connecté en face est bien celui attendu, en se basant sur les données d’une Source of Truth (SoT).
Pour finir, n’oublier pas qu’ArubaOS-CX, en plus de supporter les websockets qui permettent une automatisation basée sur les évènements par une solution tierces, dispose d’une solution équivalente, intégrée et native : Le Network Analytics Engine