NAPALM Network Automation Python: Trabajando con Cisco IOS y IOS-XR
Introducción
En este artículo vamos conocer las funciones y métodos de NAPALM para colectar datos en routers Cisco IOS y IOS-XR. Escribiremos múltiples scripts en Python para colectar datos de una red, que podrás usar para crear reportes o investigar sobre algún problema. Este es una continuación del artículo en el que introdujimos a NAPALM y es parte de la aventura en la que quiero enseñarles como puedes implementar Programabilidad y Automatización de Redes con Python y la librería NAPALM.
El contenido principal está en el siguiente video. Así que empieza por aquí para que puedas entender algunas secciones del artículo.
NAPALM CLI
La instalación de NAPALM viene con una herramienta que nos permite utilizar NAPALM directamente desde la línea de comandos. Su uso es bastante simple y podemos ver como se utiliza en el menú de ayuda del comando. Ejecutamos el siguiente comando para ver este menú:
napalm --help
Salida del comando:
(napalm_practice) malvarez@Coding-Networks:~$ napalm --help
usage: napalm [-h] [--user USER] [--password PASSWORD] --vendor VENDOR
[--optional_args OPTIONAL_ARGS] [--debug]
hostname {configure,call,validate} ...
Command line tool to handle configuration on devices using NAPALM.The script
will print the diff on the screen
positional arguments:
hostname Host where you want to deploy the configuration.
optional arguments:
-h, --help show this help message and exit
--user USER, -u USER User for authenticating to the host. Default: user
running the script.
--password PASSWORD, -p PASSWORD
Password for authenticating to the host.If you do not
provide a password in the CLI you will be prompted.
--vendor VENDOR, -v VENDOR
Host Operating System.
--optional_args OPTIONAL_ARGS, -o OPTIONAL_ARGS
String with comma separated key=value pairs passed via
optional_args to the driver.
--debug Enables debug mode; more verbosity.
actions:
{configure,call,validate}
configure Perform a configuration operation
call Call a napalm method
validate Validate configuration/state
Automate all the things!!!
Desde esta herramienta es posible ejecutar operaciones de configuración a los dispositivos, validar configuraciones o estados y realizar llamadas a los métodos de NAPALM para obtener datos de los dispositivos.
Estas acciones se realizan pasando al comando los argumentos del usuario y la contraseña del dispositivo al que nos vamos a conectar, el sistema operativo, la IP o nombre del equipo y finalmente el nombre del método.
Veamos un ejemplo:
napalm --user codingnetworks --password Coding.Networks1 --vendor ios IOS-R2 call get_interfaces
Salida:
{
"GigabitEthernet0/0": {
"is_enabled": true,
"is_up": true,
"description": "to_IOS-SW2 Gi0/1",
"mac_address": "50:00:00:01:00:00",
"last_flapped": -1.0,
"mtu": 1500,
"speed": 1000
},
"GigabitEthernet0/0.30": {
"is_enabled": true,
"is_up": true,
"description": "to_IOS-SW2 Gi0/1 Vlan 30",
"mac_address": "50:00:00:01:00:00",
"last_flapped": -1.0,
"mtu": 1500,
"speed": 1000
},
"GigabitEthernet0/1": {
"is_enabled": false,
"is_up": false,
"description": "",
"mac_address": "50:00:00:01:00:01",
"last_flapped": -1.0,
"mtu": 1500,
"speed": 1000
},
NAPALM Python
Es en esta sección es donde empezaremos a ver y disfrutar de las bondades de utilizar NAPALM para programabilidad y automatización de redes.
NAPALM Python Tabla de Informaciones
Vamos a escribir un script que se conecte a todos los dispositivos de nuestra red y nos imprima en una tabla informaciones como el hostname, fabricante, modelo, tiempo que tienen en operación y el número de serial de cada uno. A continuación el código:
import napalm from tabulate import tabulate def main(): driver_ios = napalm.get_network_driver("ios") driver_iosxr = napalm.get_network_driver("iosxr") device_list = [["ios-sw2","ios", "switch"],["ios-r2", "ios", "router"], ["ios-r3", "ios", "router"],["iosxr-r1", "iosxr", "router"],["ios-sw1", "ios", "switch"]] network_devices = [] for device in device_list: if device[1] == "ios": network_devices.append( driver_ios( hostname = device[0], username = "codingnetworks", password = "Coding.Networks1" ) ) elif device[1] == "iosxr": network_devices.append( driver_iosxr( hostname = device[0], username = "codingnetworks", password = "Coding.Networks1" ) ) devices_table = [["hostname", "vendor", "model", "uptime", "serial_number"]] for device in network_devices: print("Connecting to {} ...".format(device.hostname)) device.open() print("Getting device facts") device_facts = device.get_facts() devices_table.append([device_facts["hostname"], device_facts["vendor"], device_facts["model"], device_facts["uptime"], device_facts["serial_number"] ]) device.close() print("Done.") print(tabulate(devices_table, headers="firstrow")) if __name__ == '__main__': main()
En el video se encuentra la explicación de este código.
Veamos la salida del Script:
Connecting to ios-sw2 ...
Getting device facts
Done.
Connecting to ios-r2 ...
Getting device facts
Done.
Connecting to ios-r3 ...
Getting device facts
Done.
Connecting to iosxr-r1 ...
Getting device facts
'list' object has no attribute 'xpath'
'list' object has no attribute 'xpath'
'list' object has no attribute 'xpath'
Done.
Connecting to ios-sw1 ...
Getting device facts
Done.
hostname vendor model uptime serial_number
---------- -------- ------- -------- ---------------------
IOS-SW2 Cisco IOSv 34020 9162JOW336J
IOS-R2 Cisco IOSv 34020 9MJNBPR604WW2WN1QPABR
IOS-R3 Cisco IOSv 34080 9BO8LXD6KLX8ZSB7M0PPF
IOSXR-R1 Cisco 34146
IOS-SW1 Cisco IOSv 34080 9CGHIOOXX08
NAPALM Python Tabla de Interfaces
Vamos a modificar el código anterior para agregar otra funcionalidad. Esta funcionalidad permitirá que el script nos imprima una tabla con todas las interfaces de cada equipo con informaciones de cada una como su estado, descripción, mtu, entre otras. A continuación el código:
import napalm from tabulate import tabulate def main(): driver_ios = napalm.get_network_driver("ios") driver_iosxr = napalm.get_network_driver("iosxr") device_list = [["ios-sw2","ios", "switch"],["ios-r2", "ios", "router"], ["ios-r3", "ios", "router"],["iosxr-r1", "iosxr", "router"],["ios-sw1", "ios", "switch"]] network_devices = [] for device in device_list: if device[1] == "ios": network_devices.append( driver_ios( hostname = device[0], username = "codingnetworks", password = "Coding.Networks1" ) ) elif device[1] == "iosxr": network_devices.append( driver_iosxr( hostname = device[0], username = "codingnetworks", password = "Coding.Networks1" ) ) devices_table = [["hostname", "vendor", "model", "uptime", "serial_number"]] devices_table_int = [["hostname","interface","is_up", "is_enabled", "description", "speed", "mtu", "mac_address"]] for device in network_devices: print("Connecting to {} ...".format(device.hostname)) device.open() print("Getting device facts") device_facts = device.get_facts() devices_table.append([device_facts["hostname"], device_facts["vendor"], device_facts["model"], device_facts["uptime"], device_facts["serial_number"] ]) print("Getting device interfaces") device_interfaces = device.get_interfaces() for interface in device_interfaces: devices_table_int.append([device_facts["hostname"], interface, device_interfaces[interface]['is_up'], device_interfaces[interface]['is_enabled'], device_interfaces[interface]['description'], device_interfaces[interface]['speed'], device_interfaces[interface]['mtu'], device_interfaces[interface]['mac_address'] ]) device.close() print("Done.") print(tabulate(devices_table, headers="firstrow")) print() print(tabulate(devices_table_int, headers="firstrow")) if __name__ == '__main__': main()
En el video se encuentra la explicación de este código.
Veamos la salida de este código:
Connecting to ios-sw2 ...
Getting device facts
Getting device interfaces
Done.
Connecting to ios-r2 ...
Getting device facts
Getting device interfaces
Done.
Connecting to ios-r3 ...
Getting device facts
Getting device interfaces
Done.
Connecting to iosxr-r1 ...
Getting device facts
'list' object has no attribute 'xpath'
'list' object has no attribute 'xpath'
'list' object has no attribute 'xpath'
Getting device interfaces
Done.
Connecting to ios-sw1 ...
Getting device facts
Getting device interfaces
Done.
hostname vendor model uptime serial_number
---------- -------- ------- -------- ---------------------
IOS-SW2 Cisco IOSv 34080 9162JOW336J
IOS-R2 Cisco IOSv 34140 9MJNBPR604WW2WN1QPABR
IOS-R3 Cisco IOSv 34140 9BO8LXD6KLX8ZSB7M0PPF
IOSXR-R1 Cisco 34223
IOS-SW1 Cisco IOSv 34140 9CGHIOOXX08
hostname interface is_up is_enabled description speed mtu mac_address
---------- ------------------------- ------- ------------ ------------------------------ ------- ----- -----------------
IOS-SW2 GigabitEthernet0/0 True True 1000 1500 50:00:00:03:00:00
IOS-SW2 GigabitEthernet0/1 True True Connected to IOSXR-R1 L2-Trunk 1000 1500 50:00:00:03:00:01
IOS-SW2 GigabitEthernet0/2 True True 1000 1500 50:00:00:03:00:02
IOS-SW2 GigabitEthernet0/3 True True to_PC_1 1000 1500 50:00:00:03:00:03
IOS-SW2 GigabitEthernet1/0 True True 1000 1500 50:00:00:03:00:04
IOS-SW2 GigabitEthernet1/1 True True 1000 1500 50:00:00:03:00:05
IOS-SW2 GigabitEthernet1/2 True True 1000 1500 50:00:00:03:00:06
IOS-SW2 GigabitEthernet1/3 True True 1000 1500 50:00:00:03:00:07
IOS-SW2 Vlan10 True True Management 10.10.10.10/24 1000 1500 50:00:00:03:80:0A
IOS-R2 GigabitEthernet0/0 True True to_IOS-SW2 Gi0/1 1000 1500 50:00:00:01:00:00
IOS-R2 GigabitEthernet0/0.30 True True to_IOS-SW2 Gi0/1 Vlan 30 1000 1500 50:00:00:01:00:00
IOS-R2 GigabitEthernet0/1 False False 1000 1500 50:00:00:01:00:01
IOS-R2 GigabitEthernet0/2 True True to_IOSXR-R1 Gi0/0/0/2 External 1000 1500 50:00:00:01:00:02
IOS-R2 GigabitEthernet0/3 False False 1000 1500 50:00:00:01:00:03
IOS-R2 Loopback0 True True 8000 1514
IOS-R3 GigabitEthernet0/0 True True to_IOSXR-R1 Gi0/0/0/0 External 1000 1500 50:00:00:02:00:00
IOS-R3 GigabitEthernet0/1 False False 1000 1500 50:00:00:02:00:01
....
NAPALM Python Tabla de BGP
Nuevamente vamos a modificar nuestro código para agregar otra funcionalidad más. Esta funcionalidad es para que imprimir en una tabla todos los vecinos BGP de cada routers, incluyendo cuantas rutas están anunciando y cuantos rutas están recibiendo. A continuación el código:
import napalm from tabulate import tabulate def main(): driver_ios = napalm.get_network_driver("ios") driver_iosxr = napalm.get_network_driver("iosxr") device_list = [["ios-sw2","ios", "switch"],["ios-r2", "ios", "router"], ["ios-r3", "ios", "router"],["iosxr-r1", "iosxr", "router"],["ios-sw1", "ios", "switch"]] network_devices = [] for device in device_list: if device[1] == "ios": network_devices.append( driver_ios( hostname = device[0], username = "codingnetworks", password = "Coding.Networks1" ) ) elif device[1] == "iosxr": network_devices.append( driver_iosxr( hostname = device[0], username = "codingnetworks", password = "Coding.Networks1" ) ) devices_table = [["hostname", "vendor", "model", "uptime", "serial_number"]] devices_table_int = [["hostname","interface","is_up", "is_enabled", "description", "speed", "mtu", "mac_address"]] devices_table_bgp = [["hostname", "neighbor", "remote-as", "status", "sent prefixes", "received prefixes"]] for device in network_devices: print("Connecting to {} ...".format(device.hostname)) device.open() print("Getting device facts") device_facts = device.get_facts() devices_table.append([device_facts["hostname"], device_facts["vendor"], device_facts["model"], device_facts["uptime"], device_facts["serial_number"] ]) print("Getting device interfaces") device_interfaces = device.get_interfaces() for interface in device_interfaces: devices_table_int.append([device_facts["hostname"], interface, device_interfaces[interface]['is_up'], device_interfaces[interface]['is_enabled'], device_interfaces[interface]['description'], device_interfaces[interface]['speed'], device_interfaces[interface]['mtu'], device_interfaces[interface]['mac_address'] ]) if not "SW" in device_facts["hostname"]: print("Getting device BGP Neighbors") device_bgp_peers = device.get_bgp_neighbors() address_fam = "ipv4 unicast" if "IOSXR" in device_facts["hostname"]: address_fam = "ipv4" for bgp_neighbor in device_bgp_peers['global']['peers']: devices_table_bgp.append([device_facts["hostname"], bgp_neighbor, device_bgp_peers['global']['peers'][bgp_neighbor]['remote_as'], device_bgp_peers['global']['peers'][bgp_neighbor]['is_up'], device_bgp_peers['global']['peers'][bgp_neighbor]['address_family'][address_fam]['sent_prefixes'], device_bgp_peers['global']['peers'][bgp_neighbor]['address_family'][address_fam]['received_prefixes'] ]) device.close() print("Done.") print(tabulate(devices_table, headers="firstrow")) print() print(tabulate(devices_table_int, headers="firstrow")) print() print(tabulate(devices_table_bgp, headers="firstrow")) if __name__ == '__main__': main()
Salida al correr el código:
Connecting to ios-sw2 ...
Getting device facts
Getting device interfaces
Done.
Connecting to ios-r2 ...
Getting device facts
Getting device interfaces
Done.
Connecting to ios-r3 ...
Getting device facts
Getting device interfaces
Done.
Connecting to iosxr-r1 ...
Getting device facts
'list' object has no attribute 'xpath'
'list' object has no attribute 'xpath'
'list' object has no attribute 'xpath'
Getting device interfaces
Done.
Connecting to ios-sw1 ...
Getting device facts
Getting device interfaces
Done.
hostname vendor model uptime serial_number
---------- -------- ------- -------- ---------------------
IOS-SW2 Cisco IOSv 34080 9162JOW336J
IOS-R2 Cisco IOSv 34140 9MJNBPR604WW2WN1QPABR
IOS-R3 Cisco IOSv 34140 9BO8LXD6KLX8ZSB7M0PPF
IOSXR-R1 Cisco 34223
IOS-SW1 Cisco IOSv 34140 9CGHIOOXX08
hostname interface is_up is_enabled description speed mtu mac_address
---------- ------------------------- ------- ------------ ------------------------------ ------- ----- -----------------
IOS-SW2 GigabitEthernet0/0 True True 1000 1500 50:00:00:03:00:00
IOS-SW2 GigabitEthernet0/1 True True Connected to IOSXR-R1 L2-Trunk 1000 1500 50:00:00:03:00:01
IOS-SW2 GigabitEthernet0/2 True True 1000 1500 50:00:00:03:00:02
IOS-SW2 GigabitEthernet0/3 True True to_PC_1 1000 1500 50:00:00:03:00:03
IOS-SW2 GigabitEthernet1/0 True True 1000 1500 50:00:00:03:00:04
IOS-SW2 GigabitEthernet1/1 True True 1000 1500 50:00:00:03:00:05
IOS-SW2 GigabitEthernet1/2 True True 1000 1500 50:00:00:03:00:06
IOS-SW2 GigabitEthernet1/3 True True 1000 1500 50:00:00:03:00:07
IOS-SW2 Vlan10 True True Management 10.10.10.10/24 1000 1500 50:00:00:03:80:0A
IOS-R2 GigabitEthernet0/0 True True to_IOS-SW2 Gi0/1 1000 1500 50:00:00:01:00:00
IOS-R2 GigabitEthernet0/0.30 True True to_IOS-SW2 Gi0/1 Vlan 30 1000 1500 50:00:00:01:00:00
IOS-R2 GigabitEthernet0/1 False False 1000 1500 50:00:00:01:00:01
IOS-R2 GigabitEthernet0/2 True True to_IOSXR-R1 Gi0/0/0/2 External 1000 1500 50:00:00:01:00:02
IOS-R2 GigabitEthernet0/3 False False 1000 1500 50:00:00:01:00:03
IOS-R2 Loopback0 True True 8000 1514
IOS-R3 GigabitEthernet0/0 True True to_IOSXR-R1 Gi0/0/0/0 External 1000 1500 50:00:00:02:00:00
IOS-R3 GigabitEthernet0/1 False False 1000 1500 50:00:00:02:00:01
Listo ahí tenemos nuestra tabla de vecinos de BGP.
Conclusión
Hemos visto con ejemplos interesantes en routers Cisco las características principales de NAPALM. No tienes que preocuparte de que los dispositivos manejen diferentes sistemas operativos o que sus comandos no son los mismos. La capa de abstracción de NAPALM nos permite reutilizar el mismo código con los diferentes fabricantes. Esto genial, verdad?
Es sorprendente lo poderoso que es NAPALM. Como con tan pocas líneas de código podemos colectar tantos datos y presentarlos de esta manera. Sé que al igual que yo ustedes deben estar imaginando un mundo de aplicaciones y usos que le pueden ayudar en las redes que ustedes son responsables de mantener y operar. Así que no lo piensen mucho, armen sus laboratorios y empiecen a crear.