published: 15th of September 2017
Napalm is a network automation library written in python that abstracts the differences between libraries such as Juniper's pyez and Arista's pyeapi bringing a common interface across many API's.
Napalm is well supported in the network community, originally started by David Barroso and Elisa Jasinska Napalm now has a long list of contributors. Napalm supports a large number of device types and configuration methods. Be sure the check the documentation to ensure your devices are supported.
In this lab I will setup a Centos minimal server to install Napalm then connect to and extract data from a Cisco Nexus 9K, Juniper SRX and Arista EOS images. We will also cover applying device configuration with Napalm.
I ran into issues with SSL certificates when using the napalm-eos module. The default python install on centos 7 is python 2.7.5 which is quite old and was the source of the problems. See the following link for a workaround if you insist on using python 2.7.5 . You can follow this post to upgrade the python install to a modern version on Centos 7 minimal.
Install required YUM packages
sudo yum install -y epel-release
sudo yum group install -y "Development tools"
sudo yum install -y gcc libffi-devel libxml2-devel libxslt-devel openssl openssl-devel sshpass python-virtualenv
# if did not install python from suggested blog post
sudo yum install -y gcc libffi-devel libxml2-devel libxslt-devel python2-pip python-devel openssl openssl-devel sshpass python-setuptools python-virtualenv
Dependancy installation instructions for yum and apt based systems can be found in the official docs.
Upgrade pip and setuptools
sudo pip2.7 install -U pip setuptools
Create a virtual environment and install Napalm
# as normal user
mkdir envs
virtualenv -p python2.7 envs/napalm-test
source envs/napalm-test/bin/activate
# inside virtual environment
pip install -U pip setuptools
pip install napalm
To connect to devices, access to the API's needs to be enabled.
# junos
system {
services {
netconf {
ssh;
}
}
}
# eos
config
management api http-commands
no shutdown
protocol https
!
# nexus
conf t
feature scp-server
feature nxapi
interface mgmt 0
vrf context managment
!
You will also need to configure the interfaces with IP addresses, I will leave this as an exercise for the reader.
Connecting to a device requires you to know a few things about the device. IE the device type and the login credentials. Each supported device type has a driver that is used to setup the connection and perform actions on the devices.
# in python IDLE session
from napalm_base import get_network_driver
nxos_driver = get_network_driver('nxos')
nxos = nxos_driver('169.254.1.11', 'vagrant', 'vagrant')
junos_driver = get_network_driver('junos')
junos = junos_driver('169.254.1.12', 'root', 'Juniper')
eos_driver = get_network_driver('eos')
eos = eos_driver('169.254.1.13', 'vagrant', 'vagrant')
Now that the connection properties are setup we can connect to the devices.
nxos.open()
junos.open()
eos.open()
Each driver has a number of methods for retrieving data from and configuring devices. View the list of supported methods for each device type in the docs.
Now that we are connected its time to use some of the available methods.
nxos.get_facts()
# output
{'fqdn': 'nxosv',
u'hostname': 'nxosv',
u'interface_list': ['mgmt0',
'Ethernet1/1',
'Ethernet1/2',
'Ethernet1/3',
'Ethernet1/4',
'Ethernet1/5',
'Ethernet1/6',
'Ethernet1/7',
'Ethernet1/8',
'Ethernet1/9',
'Ethernet1/10',
'Ethernet1/11',
'Ethernet1/12',
'Ethernet1/13',
'Ethernet1/14',
'Ethernet1/15',
'Ethernet1/16',
'Ethernet1/17',
'Ethernet1/18',
'Ethernet1/19',
'Ethernet1/20',
'Ethernet1/21',
'Ethernet1/22',
'Ethernet1/23',
'Ethernet1/24',
'Ethernet1/25',
'Ethernet1/26',
'Ethernet1/27',
'Ethernet1/28',
'Ethernet1/29',
'Ethernet1/30',
'Ethernet1/31',
'Ethernet1/32',
'Ethernet1/33',
'Ethernet1/34',
'Ethernet1/35',
'Ethernet1/36',
'Ethernet1/37',
'Ethernet1/38',
'Ethernet1/39',
'Ethernet1/40',
'Ethernet1/41',
'Ethernet1/42',
'Ethernet1/43',
'Ethernet1/44',
'Ethernet1/45',
'Ethernet1/46',
'Ethernet1/47',
'Ethernet1/48',
'Ethernet1/49',
'Ethernet1/50',
'Ethernet1/51',
'Ethernet1/52',
'Ethernet1/53',
'Ethernet1/54',
'Ethernet1/55',
'Ethernet1/56',
'Ethernet1/57',
'Ethernet1/58',
'Ethernet1/59',
'Ethernet1/60',
'Ethernet1/61',
'Ethernet1/62',
'Ethernet1/63',
'Ethernet1/64'],
u'model': 'NX-OSv Chassis',
u'os_version': '7.0(3)I6(1)',
u'serial_number': '9VV6Z8H5H4H',
'uptime': 17801,
u'vendor': u'Cisco'}
junos.get_interfaces()
# output
{'.local.': {u'description': u'',
u'is_enabled': True,
u'is_up': True,
u'last_flapped': -1.0,
u'mac_address': u'Unspecified',
u'speed': -1},
'dsc': {u'description': u'',
u'is_enabled': True,
u'is_up': True,
u'last_flapped': -1.0,
u'mac_address': u'Unspecified',
u'speed': -1},
'ge-0/0/0': {u'description': u'',
u'is_enabled': True,
u'is_up': True,
u'last_flapped': 31272.0,
u'mac_address': u'08:00:27:AE:F4:51',
u'speed': 1000},
'ge-0/0/1': {u'description': u'',
u'is_enabled': True,
u'is_up': True,
u'last_flapped': 31272.0,
u'mac_address': u'08:00:27:82:DC:49',
u'speed': 1000},
'gr-0/0/0': {u'description': u'',
u'is_enabled': True,
u'is_up': True,
u'last_flapped': -1.0,
u'mac_address': u'None',
u'speed': 800},
'gre': {u'description': u'',
u'is_enabled': True,
u'is_up': True,
u'last_flapped': -1.0,
u'mac_address': u'None',
u'speed': -1},
'ip-0/0/0': {u'description': u'',
u'is_enabled': True,
u'is_up': True,
u'last_flapped': -1.0,
u'mac_address': u'None',
u'speed': 800},
'ipip': {u'description': u'',
u'is_enabled': True,
u'is_up': True,
u'last_flapped': -1.0,
u'mac_address': u'None',
u'speed': -1},
'irb': {u'description': u'',
u'is_enabled': True,
u'is_up': True,
u'last_flapped': -1.0,
u'mac_address': u'4C:96:14:10:01:30',
u'speed': -1},
'lo0': {u'description': u'',
u'is_enabled': True,
u'is_up': True,
u'last_flapped': -1.0,
u'mac_address': u'Unspecified',
u'speed': -1},
'lsi': {u'description': u'',
u'is_enabled': True,
u'is_up': True,
u'last_flapped': -1.0,
u'mac_address': u'Unspecified',
u'speed': -1},
'lsq-0/0/0': {u'description': u'',
u'is_enabled': True,
u'is_up': True,
u'last_flapped': 31273.0,
u'mac_address': u'None',
u'speed': -1},
'lt-0/0/0': {u'description': u'',
u'is_enabled': True,
u'is_up': True,
u'last_flapped': -1.0,
u'mac_address': u'02:96:14:10:01:33',
u'speed': 800},
'mt-0/0/0': {u'description': u'',
u'is_enabled': True,
u'is_up': True,
u'last_flapped': -1.0,
u'mac_address': u'None',
u'speed': 800},
'mtun': {u'description': u'',
u'is_enabled': True,
u'is_up': True,
u'last_flapped': -1.0,
u'mac_address': u'None',
u'speed': -1},
'pimd': {u'description': u'',
u'is_enabled': True,
u'is_up': True,
u'last_flapped': -1.0,
u'mac_address': u'None',
u'speed': -1},
'pime': {u'description': u'',
u'is_enabled': True,
u'is_up': True,
u'last_flapped': -1.0,
u'mac_address': u'None',
u'speed': -1},
'pp0': {u'description': u'',
u'is_enabled': True,
u'is_up': True,
u'last_flapped': -1.0,
u'mac_address': u'Unspecified',
u'speed': -1},
'ppd0': {u'description': u'',
u'is_enabled': True,
u'is_up': True,
u'last_flapped': -1.0,
u'mac_address': u'None',
u'speed': 800},
'ppe0': {u'description': u'',
u'is_enabled': True,
u'is_up': True,
u'last_flapped': -1.0,
u'mac_address': u'None',
u'speed': 800},
'sp-0/0/0': {u'description': u'',
u'is_enabled': True,
u'is_up': True,
u'last_flapped': 31273.0,
u'mac_address': u'Unspecified',
u'speed': 800},
'st0': {u'description': u'',
u'is_enabled': True,
u'is_up': True,
u'last_flapped': -1.0,
u'mac_address': u'None',
u'speed': -1},
'tap': {u'description': u'',
u'is_enabled': True,
u'is_up': True,
u'last_flapped': -1.0,
u'mac_address': u'Unspecified',
u'speed': -1},
'vlan': {u'description': u'',
u'is_enabled': True,
u'is_up': False,
u'last_flapped': 31282.0,
u'mac_address': u'00:00:00:00:00:00',
u'speed': 1000}}
eos.get_environment()
# output
{u'cpu': {0: {u'%usage': 5.3}},
u'fans': {},
u'memory': {u'available_ram': 66772, u'used_ram': 1830768},
u'power': {},
u'temperature': {}}
The great thing about Napalm is that it abstracts all the differences between the various API's and returns data in the same structure no matter the device type. Consider the get_arp_table() method. This method returns the arp table from nxos , junos and eos all in the same format making it much eaiser to reconcile the data in our code.
nxos.get_arp_table()
# output
[{u'age': 1015.0,
u'interface': u'Ethernet1/1',
u'ip': u'169.254.1.10',
u'mac': u'08:00:27:FE:41:B6'},
{u'age': 917.0,
u'interface': u'Ethernet1/1',
u'ip': u'169.254.1.12',
u'mac': u'08:00:27:82:DC:49'},
{u'age': 558.0,
u'interface': u'Ethernet1/1',
u'ip': u'169.254.1.13',
u'mac': u'08:00:27:EB:40:24'}]
junos.get_arp_table()
# output
[{'age': 491.0,
'interface': u'ge-0/0/0.0',
'ip': u'10.0.2.2',
'mac': u'52:54:00:12:35:02'},
{'age': 1104.0,
'interface': u'ge-0/0/1.0',
'ip': u'169.254.1.10',
'mac': u'08:00:27:FE:41:B6'},
{'age': 851.0,
'interface': u'ge-0/0/1.0',
'ip': u'169.254.1.11',
'mac': u'08:00:27:6C:EE:14'},
{'age': 832.0,
'interface': u'ge-0/0/1.0',
'ip': u'169.254.1.13',
'mac': u'08:00:27:EB:40:24'}]
eos.get_arp_table()
# output
[{u'age': 0.0,
u'interface': u'Ethernet1',
u'ip': u'169.254.1.10',
u'mac': u'08:00:27:FE:41:B6'},
{u'age': 0.0,
u'interface': u'Ethernet1',
u'ip': u'169.254.1.11',
u'mac': u'08:00:27:6C:EE:14'},
{u'age': 0.0,
u'interface': u'Ethernet1',
u'ip': u'169.254.1.12',
u'mac': u'08:00:27:82:DC:49'},
{u'age': 0.0,
u'interface': u'Management1',
u'ip': u'10.0.2.2',
u'mac': u'52:54:00:12:35:02'},
{u'age': 0.0,
u'interface': u'Management1',
u'ip': u'10.0.2.3',
u'mac': u'52:54:00:12:35:03'}]
When configuring devices there are a number of ways to do it with Napalm. The general workflow is to load a candidate configuration, compare the configuration diff, if the changes are acceptable commit the changes.
Be sure the check the configuration support matrix for the methods supported by your devices.
Firstly we need to define a configuration. Configuration can either be defined as a variable or loaded from a file. In this case I will create a configuration variable.
nxos_ntp = 'ntp server 1.1.1.1 use-vrf default'
Once the configuration is defined we will load a candidate configuration. It is possible to either replace the whole configuration or merge with the existing configuration. In this instance I will merge the configuration with the current configuration.
nxos.load_merge_candidate(config=nxos_ntp)
Once the candidate configuration is loaded, we can compare it to the running configuration to be sure that it is what we intended.
nxos.compare_config()
# output
'ntp server 1.1.1.1 use-vrf default'
Once we are happy with the changes it is time to commit the configuration.
nxos.commit_config()
To confirm the changes were successful lets check the running configuration.
config = nxos.get_config()
print(config['running'])
# output
!Command: show running-config
. <snip>
.
.
ntp server 1.1.1.1 use-vrf default
.
.
. <snip>
line vty
As you can see the NTP server is now apart of the running config.
Napalm is a fantastic library with a broad range of supported devices across multiple vendors. The library is well maintained and the developers are very helpful. I suggest reading over the official documentation and having a play around with the library to see how it can add value to your organisation. If you have questions the best place to ask is over at the network.toCode() slack channel.
http://napalm.readthedocs.io/en/latest/
https://github.com/napalm-automation/napalm
https://eos.arista.com/arista-eapi-101/
http://www.juniper.net/documentation/en_US/junos/topics/topic-map/netconf-ssh-connection.html