NAPALM Network Automation Python: Working with Cisco IOS and IOS-XR
Introduction
In this article we are going to see the functions and methods of NAPALM to collect data in Cisco IOS and IOS-XR routers. We will write multiple Python scripts to collect data from a network, which you can use to create reports or investigate a problem. This is a continuation of the article in which we introduced NAPALM and it is part of the adventure in which I want to show you how you can implement Network Programmability and Automation with Python and the NAPALM library.
The main content is in the following video. So start here so that you can understand some sections of the article.
English Version Video Link:
NAPALM CLI
The NAPALM installation comes with a tool that allows us to use NAPALM directly from the command line. Its use is quite simple and we can see how it is used in the command’s help menu. We execute the following command to see this menu:
napalm --help
Command output:
(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!!!
From this tool it is possible to execute configuration operations on the devices, validate configurations or states and make calls to the NAPALM methods to obtain data from the devices.
These actions are carried out by passing to the command the user arguments and the password of the device to which we are going to connect, the operating system, the IP or name of the equipment and finally the name of the method.
Let’s see an example:
napalm --user codingnetworks --password Coding.Networks1 --vendor ios IOS-R2 call get_interfaces
Command output:
{
"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
It is in this section where we will begin to see and enjoy the benefits of using NAPALM for programmability and network automation.
NAPALM Python Devices information table
We are going to write a script that connects to all the devices on our network and prints information such as the hostname, manufacturer, model, time they have in operation and the serial number of each one in a table. Below the code:
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()
In the video you will find the explanation of this code.
Let’s see the output of the 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 Interfaces Table
We are going to modify the code above to add other functionality. This functionality will allow the script to print a table with all the interfaces of each device with information about each one such as its status, description, mtu, among others. Below the code:
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()
In the video you will find the explanation of this code.
Let’s see the output of this code:
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 BGP Table
Again we are going to modify our code to add another functionality. This functionality is to print in a table all the BGP neighbors of each routers, including how many routes they are advertising and how many routes they are receiving. Below the code:
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()
Output when running the code:
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
Ready. There we have our BGP neighbor table.
ConclusiĆ³n
We have seen with interesting examples in Cisco routers the main features of NAPALM. You do not have to worry that the devices handle different operating systems or that their commands are not the same. The NAPALM abstraction layer allows us to reuse the same code with different manufacturers. This is great, right?
It’s amazing how powerful NAPALM is. As with so few lines of code we can collect so much data and present it in this way. I know that like me you must be imagining a world of applications and uses that can help you in the networks that you are responsible for maintaining and operating. So don’t think too much about it, set up your labs and start creating.
I’ve followed these sessions, very interesting as I’m a Cisco network engineer who is trying to get up to speed with using python to automate configuration tasks. I see that Napalm is a very powerful tool.
Do you have an example where the hostnames and IP’s to be accessed are in either a CSV or JSON file? I’m trying and failing to get such to work with napalm, I’ve not long started with python.
Hey, Hi Andy. Thank you for your comment.
I use CSV files to read hostnames and IPs. Create a CSV file like this:
hostname, ip_address
router1, 192.168.0.1
I use csv library to read it. I’m omitting the code to open a file, but this is an example on how to use csv to read hostnames and ip addresses:
import csv
routers = csv.DictReader(filelist)
for router in routers:
print(router[“hostname”], router[“ip_address”])