Programmation Cisco NX-OS

Il existe plusieurs façon de programmer NX-OS dont celles au travers d’APIs, sachant que celles-ci pourraient se classer en deux catégories :

  • Controller APIs, dans laquelle on trouve Application Centric Infrastructure (ACI)
  • Device APIs, dans laquelle on trouve NX-API (CLI et REST) et les standards « Model Driven » NETCONF, RESTCONF et gNMI/gRPC

Entre la date de publication de NETCONF (2006) et celle de RESTCONF (2017), des implémentations d’approches plus personnelles ont été créées : NX-API CLI et NX-API REST. Le passage aux protocoles NETCONF, RESTCONF et gNMI s’appuyant sur des modèles YANG se distingue, dans la littérature, par l’utilisation du terme Open NX-OS.

C’est sur un commutateur Cisco Nexus 9300v version 9.3.5, injecté dans le simulateur EVE-NG que l’ensemble des requêtes sont générées.

NX-API CLI

NX-API CLI offre la possibilité d’incorporer des commandes CLI Cisco NX-OS dans un format de données structuré (JSON ou XML) pour être exécutées sur le commutateur via un transport HTTP ou HTTPS. Les données renvoyées seront également formatées en JSON ou XML, ce qui facilite l’analyse des données avec des langages de programmation modernes. NX-API CLI s’active sur le commutateur à l’aide de la commande feature nxapi. On notera que NX-API CLI ne supporte que l’opération POST.

Le plus simple est de parcourir les possibilités sur l’interface « NX-API Sandbox » accessible en HTTPS à l’adresse du commutateur.

NX-API Sandbox
NX-API Sandbox

En parcourant les différentes options, les requêtes sont construites automatiquement et les réponses aparaissent. Des onglets permettent d’obtenir des exemples de programmation en langage Python2.7, Python3, Java, JavaScript et Golang.

Pour les adeptes de Postman, il est tout à fait possible de construire la requête avec une entête contenant la clé Authorization et la clé Content-Type positionnée à la valeur « application/json ». Le « body » contient la requête proposée par Sandbox au format JSON. L’URL est de la forme : https://192.168.1.99:443/ins

NX-API CLI Postman
Postman NX-API CLI

De la même manière, il est possible d’obtenir des exemples de programmation dans différents langages. On note que l’exemple en Python est moins élaboré que celui proposé par Sandbox.

NX-API REST

NX-API REST permet une approche basée sur un modèle de données stockées sous forme d’objets dans la MIT (Management Information Tree) pour la configuration et la gestion du réseau. NX-API CLI et NX-API REST utilisent le serveur HTTP NGINX. NX-API REST peut être vu comme une évolution de NX-API CLI, les données pouvant être également encodées en XML ou JSON. Il faut noter que NX-API REST supporte ls trois méthodes GET, DELETE et POST.

L’authorization est traitée par une API spécifique qui renvoie un token pouvant être utilisé par les requêtes suivantes. Pour parcourir les MO (Management Objects) dans la MIT, un outil est accessible directement sur le commutateur à l’adresse : https://192.168.1.99/visore.html. Chaque objet est identifié dans la MIT par un nom unique appelé dn (Distinguished name).

NX-API REST Visore
NX-API Visore

L’interface Sandbox permet également de tester des requêtes NX-API REST, mais Postman reste l’outil le plus adapté même si, dans ce cas, il doit être utilisé d’une façon plus avancée que pour NX-API CLI. En effet, une première requête POST est nécessaire en spécifiant le username/password pour récupérer un token qui va être utilisé de façon automatisée dans les requêtes suivantes en le passant dans le « header » avec la clé « x-auth-token ». L’URL est construite à l’aide des informations fournies par Visore et une information est nécessaire dans le « body » si on souhaite faire une opération PUT. Voici un exemple de programme Python qui rend plus lisible le processus d’accès en deux temps.

import requests
import json
from urllib3 import disable_warnings
from urllib3.exceptions import InsecureRequestWarning
from pprint import pprint


auth_url = "https://192.168.1.99/api/mo/aaaLogin.json"
auth_body = {'aaaUser': {'attributes': {
    'name': 'admin', 'pwd': 'admin'}}}
int_url = 'https://192.168.1.99/api/mo/sys/intf/phys-[eth1/33].json'
headers = {'content-type': 'application/json'}

if __name__ == '__main__':
    # disable InsecureRequestWarning
    disable_warnings(InsecureRequestWarning)
    try:
        # login and set cookie
        auth_response = requests.post(
            auth_url, json=auth_body, timeout=5, verify=False).json()
        token = auth_response['imdata'][0]['aaaLogin']['attributes']['token']
        cookies = {}
        cookies['APIC-Cookie'] = token
        # get interface information
        get_response = requests.get(
            int_url, headers=headers, cookies=cookies, verify=False).json()
        pprint(get_response)
    except requests.exceptions.RequestException as e:
        raise SystemExit(e)
NX-API REST get requests
NX-API REST Python get requests

NETCONF/YANG et RESTCONF/YANG

L’activation de NETCONF se fait à l’aide de la commande feature netconf. Les « capabilities » retournées sur le 9000v en 9.3 sont très limitées. En effet, il n’y a qu’un seul module natif Cisco : cisco-nx-os-device?revision=2020-07-20. Une analyse de l’arborescence dans le catalogue YANG, met en évidence qu’il s’agit d’un module contenant l’ensemble des items.

NX-OS native YANG module
http://cisco.com/ns/yang/cisco-nx-os-device?revision=2020-07-20

Pour accéder aux modules standards OpenConfig, il faudrait les installer sous la forme d’un RPM complémentaire. Sur la base du module présent, un script Python permet de récupérer la configuration de l’interface Eth1/1 sous la forme d’un dictionnaire Python.

---
- host: 192.168.1.99
  port: 830
  username: admin
  password: admin
  name: nexus
  payloads:
    expression: >
        <filter>
          <System xmlns="http://cisco.com/ns/yang/cisco-nx-os-device">
            <intf-items>
              <phys-items>
                <PhysIf-list>
                  <id>{interface_name}</id>
                </PhysIf-list>
              </phys-items>
            </intf-items>
          </System>
        </filter>
#!/usr/bin/env python
from ncclient import manager
from ncclient.operations import RPCError
import xmltodict
import yaml
from pprint import pprint


if __name__ == '__main__':
    # get parameters from yaml file
    with open('hosts.yaml') as f:
        hosts = yaml.load(f, Loader=yaml.FullLoader)
    # iterate onto the list
    for host in hosts:
        # connect to netconf agent
        with manager.connect(host=host['host'],
                             port=host['port'],
                             username=host['username'],
                             password=host['password'],
                             timeout=90,
                             hostkey_verify=False,
                             device_params={'name': host['name']}) as m:
            # execute netconf operation
            try:
                # get Python dict information from rpc expression filter
                response = m.get(host['payloads']['expression'].format(
                    interface_name='eth1/1'))
                python_dict = xmltodict.parse(response.xml)[
                    'rpc-reply']['data']
                pprint(python_dict)

            except RPCError as e:
                print(e._raw)

En plus de la fonction feature nxapi déjà activée plus haut, il convient d’utiliser la fonction feature restconf pour avoir accès à l’agent RESTCONF de NX-OS. Postman et Sandbox permettent de mettre au point la requête qui peut être utilisée dans un programme Python.

#!/usr/bin/env python
import requests
from urllib3 import disable_warnings
from urllib3.exceptions import InsecureRequestWarning


url = "https://192.168.1.99:443/restconf/data/Cisco-NX-OS-device:System/intf-items/phys-items/"
headers = {
    'Content-Type': 'application/yang-data+xml',
    'Authorization': 'Basic YWRtaW46YWRtaW4='
}
payload = {}

if __name__ == '__main__':
    # disable InsecureRequestWarning
    disable_warnings(InsecureRequestWarning)
    try:
        response = requests.request(
            "GET", url, headers=headers, verify=False, data=payload)
        print(response.text)
    except requests.exceptions.RequestException as e:
        raise SystemExit(e)