Network Automation Using Unified API – Napalm

Before I joined the awesome Cisco DevNet team, I worked for Cisco on two of their biggest cloud platforms as a network engineer.

My old team and I designed and built one of biggest Cisco Data Center footprints AND we did it all manually. Yes, no automation! We mostly used notepad files as templates, with adds, moves. changes, and general fixes added to these ‘golden notepad’ files. Copying and pasting into the command line (CLI) when each Data Center came online.

If a chill went down your spine and you are shaking your head with reading that last sentence, it was as painful as you are thinking. Even the most diligent, keen eyes and obsessed engineer would find it difficult to say that copying and pasting configurations into 15 network devices, including routers, switches, firewalls, and load balancers was going to go smoothly first time. Certainly not when you have over a thousand access-lists and your firewalls are multi-context.

Time to embrace automation

It made me wonder how our SRE looked so fresh faced (not only because their average age was 20 years less than me btw!) and had time to play on the foosball table. They managed, built, and owned five times the product services that our network team did. Our SRE team had automation nailed down! We needed to become more agile and embrace network automation.

Because we had been so diligent on our new Data Center build, the ports lined up per device and everything was standardized. Our SRE team helped with the automation of the ASA firewalls which was our starting point as they wanted to be able to manage and update their services without having to rely on the network team for the changes. Like most network engineers starting network automation, Ansible was our first choice. We could automate our access lists much easier and audit these when the security team asked (which was normally mid Friday afternoon!).

Cisco acquires OpenDNS

In 2015 Cisco acquired OpenDNS. Our network teams were merged into one team. When our teams met for the first time, the OpenDNS NetEng team said/asked, “Our network is fully automated, is yours?”

“Sort of” was our reply. One of the first tasks was learning from the OpenDNS NetEng team how they fully automated their network. This was my ‘penny dropping’ moment (and what I would find later to be the turning point in my career). There is nothing better than being able to learn from someone (or team) how they achieved the goals you want to achieve.

Welcome to NAPALM

The OpenDNS NetEng used NAPALM (Network Automation and Programmability Abstraction Layer with Multivendor support), a Python library that implements a set of functions to interact with different network device Operating Systems using a unified API. NAPALM supports several methods to connect to the devices, to manipulate configurations or to retrieve data. I had been learning Python for around year, but had not done a big project with it yet. Now, here was my chance.

First, as I tell everyone now who is starting network automation, “start with the low hanging fruit – do not try and automate your whole network in one go.” Trust me here when I say, you can break a lot more things with automation and you can break them a lot quicker. (I have been there and own that t-shirt!) The first project was managing our edge routers (ASR IOS XR) using NAPALM network automation.

What can you do with NAPALM?

Configuration replace: Replace the entire running-config with a completely new configuration

Configuration merge: Merge a set of changes from a file into the running-config

Configuration compare: Compare your new proposed configuration file with the running-config. This only applies to configuration replace operations; it does not apply to merge operations

Commit: Deploy the staged configuration. This can be either an entire new file (for replace operations) or a merge file

Discard: Revert the candidate configuration file back to the current running-config; reset the merge configuration file back to an empty file

Rollback: Revert the running configuration back to a file that was saved prior to the previous commit

Let’s have a quick look at using NAPALM with one of the Cisco DevNet Always on Sandboxes, the one we’ll use here is the IOS XE on CSR Sandbox . Start by installing NAPLAM (You need to have Python 3.6+). You can install NAPALM using PIP

pip install napalm

Open the python `repl` on your machine.

$python
Python 3.6.5 (v3.6.5:f59c0932b4, Mar 28 2018, 05:52:31)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>

Start by importing the NAPALM module into Python

from napalm import get_network_driver

You can select the driver you need by doing the following:

driver = get_network_driver("ios")

Create the python code using the IOS-XE always on sandbox information.

device = driver(hostname='ios-xe-mgmt-latest.cisco.com',
... username='developer',
... password='C1sco12345',
... optional_args={'port':8181})

Next, we open a connection the device and pass the `get_interfaces` command.

device.open()
device.get_interfaces()

The information is printed below (dont’ worry if your output is not the same. As this is a always on sandbox interfaces change as people use this)

{'GigabitEthernet1': {'is_enabled': True, 'is_up': True, 'description': "MANAGEMENT INTERFACE - DON'T TOUCH ME", 'mac_address': '00:50:56:BB:E9:9C', 'last_flapped': -1.0, 'speed': 0},
'GigabitEthernet2': {'is_enabled': True, 'is_up': True, 'description': 'ConfiguredNetConf', 'mac_address': '00:50:56:BB:77:1A', 'last_flapped': -1.0, 'speed': 1000},
'GigabitEthernet3': {'is_enabled': False, 'is_up': False, 'description': 'Network Interface', 'mac_address': '00:50:56:BB:EB:1E', 'last_flapped': -1.0, 'speed': 1000}, 'Loopback99': {'is_enabled': True, 'is_up': True, 'description': 'Developers interface', 'mac_address': '', 'last_flapped': -1.0, 'speed': 8000},
'Loopback101': {'is_enabled': True, 'is_up': True, 'description': 'Created with Ansible', 'mac_address': '', 'last_flapped': -1.0, 'speed': 8000}, 'Loopback102': {'is_enabled': True, 'is_up': True, 'description': 'Created with Ansible', 'mac_address': '', 'last_flapped': -1.0, 'speed': 8000},
'Loopback199': {'is_enabled': True, 'is_up': True, 'description': 'New Loopback by Priv15 user', 'mac_address': '', 'last_flapped': -1.0, 'speed': 8000},
'Loopback211': {'is_enabled': True, 'is_up': True, 'description': 'Developers Priv15 Interface', 'mac_address': '', 'last_flapped': -1.0, 'speed': 8000},
'Loopback231': {'is_enabled': True, 'is_up': True, 'description': 'DEVELOPER PRIV15 INTERFACE', 'mac_address': '', 'last_flapped': -1.0, 'speed': 8000}, 'Loopback555': {'is_enabled':
True, 'is_up': True, 'description': 'Added by xxx', 'mac_address': '', 'last_flapped': -1.0, 'speed': 8000}, 'Loopback556': {'is_enabled': True, 'is_up': True, 'description': 'Added by GuGame', 'mac_address': '', 'last_flapped': -1.0, 'speed': 8000},
'Loopback1001': {'is_enabled': True, 'is_up': True, 'description': 'GenieLoop1001', 'mac_address': '', 'last_flapped': -1.0, 'speed': 8000},
'Loopback1150': {'is_enabled': True, 'is_up': True, 'description': 'Pod Number 1150', 'mac_address': '', 'last_flapped': -1.0, 'speed': 8000}, 'Loopback1184': {'is_enabled': True, 'is_up': True, 'description': 'New Interface Created with Genie change', 'mac_address': '', 'last_flapped': -1.0, 'speed': 8000},
'Loopback1250': {'is_enabled': True, 'is_up': True, 'description': 'Pod Number 1250', 'mac_address': '', 'last_flapped': -1.0, 'speed': 8000}, 'Loopback1350': {'is_enabled': True, 'is_up': True, 'description': 'Pod Number 1350', 'mac_address': '', 'last_flapped': -1.0, 'speed': 8000},
'Loopback1450': {'is_enabled': True, 'is_up': True, 'description': 'Pod Number 1450', 'mac_address': '', 'last_flapped': -1.0, 'speed': 8000},
'Loopback5050': {'is_enabled': True, 'is_up': True, 'description': 'Pod Number 5050', 'mac_address': '', 'last_flapped': -1.0, 'speed': 8000},
'Loopback5150': {'is_enabled': True, 'is_up': True, 'description': 'Pod Number 5150', 'mac_address': '', 'last_flapped': -1.0, 'speed': 8000}, 'Loopback5250': {'is_enabled': True, 'is_up': True, 'description': 'Pod Number 5250', 'mac_address': '', 'last_flapped': -1.0, 'speed': 8000},
'Loopback5350': {'is_enabled': True, 'is_up': True, 'description': 'Pod Number 5350', 'mac_address': '', 'last_flapped': -1.0, 'speed': 8000}, 'Port-channel1': {'is_enabled': True, 'is_up': False, 'description': 'This is a port-channel interace', 'mac_address': '00:1E:E5:65:3F:C0', 'last_flapped': -1.0, 'speed': 1000},
'Tunnel0': {'is_enabled': True, 'is_up': False, 'description': '', 'mac_address': '', 'last_flapped': -1.0, 'speed': 0}, 'Tunnel1': {'is_enabled': True, 'is_up': False, 'description': '', 'mac_address': '', 'last_flapped': -1.0, 'speed': 0},
'VirtualPortGroup0': {'is_enabled': True, 'is_up': True, 'description': '', 'mac_address': '00:1E:E5:65:3F:BD', 'last_flapped': -1.0, 'speed': 750}}

We can make the output more readable, by importing `json` and printing this in `json format`

import json
print(json.dumps(device.get_interfaces(), sort_keys=True, indent=4))

{
"GigabitEthernet1": {
"description": "MANAGEMENT INTERFACE - DON'T TOUCH ME",
"is_enabled": true,
"is_up": true,
"last_flapped": -1.0,
"mac_address": "00:50:56:BB:E9:9C",
"speed": 0
},
"GigabitEthernet2": {
"description": "ConfiguredNetConf",
"is_enabled": true,
"is_up": true,
"last_flapped": -1.0,
"mac_address": "00:50:56:BB:77:1A",
"speed": 1000
},
"GigabitEthernet3": {
"description": "Network Interface",
"is_enabled": false,
"is_up": false,
"last_flapped": -1.0,
"mac_address": "00:50:56:BB:EB:1E",
"speed": 1000
},
"Loopback1001": {
"description": "GenieLoop1001",
"is_enabled": true,
"is_up": true,
"last_flapped": -1.0,
"mac_address": "",
"speed": 8000
},
"Loopback101": {
"description": "Created with Ansible",
"is_enabled": true,
"is_up": true,
"last_flapped": -1.0,
"mac_address": "",
"speed": 8000
},
"Loopback102": {
"description": "Created with Ansible",
"is_enabled": true,
"is_up": true,
"last_flapped": -1.0,
"mac_address": "",
"speed": 8000
},
"Loopback1150": {
"description": "Pod Number 1150",
"is_enabled": true,
"is_up": true,
"last_flapped": -1.0,
"mac_address": "",
"speed": 8000
},
"Loopback1184": {
"description": "New Interface Created with Genie change",
"is_enabled": true,
"is_up": true,
"last_flapped": -1.0,
"mac_address": "",
"speed": 8000
},
"Loopback1250": {
"description": "Pod Number 1250",
"is_enabled": true,
"is_up": true,
"last_flapped": -1.0,
"mac_address": "",
"speed": 8000
},
"Loopback1350": {
"description": "Pod Number 1350",
"is_enabled": true,
"is_up": true,
"last_flapped": -1.0,
"mac_address": "",
"speed": 8000
},
"Loopback1450": {
"description": "Pod Number 1450",
"is_enabled": true,
"is_up": true,
"last_flapped": -1.0,
"mac_address": "",
"speed": 8000
},
"Loopback199": {
"description": "New Loopback by Priv15 user",
"is_enabled": true,
"is_up": true,
"last_flapped": -1.0,
"mac_address": "",
"speed": 8000
},
"Loopback211": {
"description": "Developers Priv15 Interface",
"is_enabled": true,
"is_up": true,
"last_flapped": -1.0,
"mac_address": "",
"speed": 8000
},
"Loopback231": {
"description": "DEVELOPER PRIV15 INTERFACE",
"is_enabled": true,
"is_up": true,
"last_flapped": -1.0,
"mac_address": "",
"speed": 8000
},
"Loopback5050": {
"description": "Pod Number 5050",
"is_enabled": true,
"is_up": true,
"last_flapped": -1.0,
"mac_address": "",
"speed": 8000
},
"Loopback5150": {
"description": "Pod Number 5150",
"is_enabled": true,
"is_up": true,
"last_flapped": -1.0,
"mac_address": "",
"speed": 8000
},
"Loopback5250": {
"description": "Pod Number 5250",
"is_enabled": true,
"is_up": true,
"last_flapped": -1.0,
"mac_address": "",
"speed": 8000
},
"Loopback5350": {
"description": "Pod Number 5350",
"is_enabled": true,
"is_up": true,
"last_flapped": -1.0,
"mac_address": "",
"speed": 8000
},
"Loopback555": {
"description": "Added by xxx",
"is_enabled": true,
"is_up": true,
"last_flapped": -1.0,
"mac_address": "",
"speed": 8000
},
"Loopback556": {
"description": "Added by GuGame",
"is_enabled": true,
"is_up": true,
"last_flapped": -1.0,
"mac_address": "",
"speed": 8000
},
"Loopback99": {
"description": "Developers interface",
"is_enabled": true,
"is_up": true,
"last_flapped": -1.0,
"mac_address": "",
"speed": 8000
},
"Port-channel1": {
"description": "This is a port-channel interace",
"is_enabled": true,
"is_up": false,
"last_flapped": -1.0,
"mac_address": "00:1E:E5:65:3F:C0",
"speed": 1000
},
"Tunnel0": {
"description": "",
"is_enabled": true,
"is_up": false,
"last_flapped": -1.0,
"mac_address": "",
"speed": 0
},
"Tunnel1": {
"description": "",
"is_enabled": true,
"is_up": false,
"last_flapped": -1.0,
"mac_address": "",
"speed": 0
},
"VirtualPortGroup0": {
"description": "",
"is_enabled": true,
"is_up": true,
"last_flapped": -1.0,
"mac_address": "00:1E:E5:65:3F:BD",
"speed": 750
}
}

Finally, we close the connection. It is advised to issue use the `close` to disconnect our session from the device.

device.close()

Great right? But it does not end there NAPALM’s supported network operating systems:

Arista EOS

Cisco IOS

Cisco IOS-XR

Cisco NX-OS

Juniper JunOS

Would you like to learn more?

Good news, the new NAPLAM Cisco DevNet Learning Lab is now live and ready for you to try Using NAPALM for Network Automation

Join DevNet to access the learning labs, docs, and sandboxes you need for network automation.

And check out the NetDevOpsLive! webinar series, and this expert-led, video course for learning network programmability basics.