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.
We’d love to hear what you think. Ask a question or leave a comment below.
And stay connected with Cisco DevNet on social!
Twitter @CiscoDevNet | Facebook | LinkedIn
Visit the new Developer Video Channel
Hi Stuart,
Thanks for the heads-up regarding the NAPALM Learning Labs!!
Question: is there a way to leverage NAPALM under the following conditions?
1) All network elements are ONLY accessible via a pair of "jump" servers; think "bastion hosts".
2) The two jump servers are locked down, and have no tools to assist with automation.
3) Access to the jump servers is via SSH, but port-forwarding is disabled, so there's no way to use a jump server as a pass-through gateway.
Thoughts?
Thanks!!
Vince S
Hey Vince, thanks for taking to time to read the blog! This should already exist for Netmiko-based SSH NAPALM drivers, i checked their documents. You would need to use `optional_args` dictionary to provide all of the additional Netmiko arguments. Mircea Ulinic
who is one of the awesome devs at the NAPALM project wrote this great blog – https://mirceaulinic.net/2016-11-17-network-orchestration-with-salt-and-napalm/
HTH
Take care!
Hello Stuart, thank you for this great tutorial about NAPALM, it's very easy and informative as well. Can you advise me about automation in optical network, I run DWDM Core network with many services carried over wavelength. Is there any API of method to connect to those devices and manipulate configuration as in Cisco IOS or it's locked to vendor only.
thanks a gain for NAPALM tutorial.
Hey Hany – check the driver support for Napalm here https://napalm.readthedocs.io/en/latest/support/