Recently, I have been exploring NAPALM in relation to NX-OS. To connect to the device, I used the nxos driver which uses the NXAPI. This write-up contains some of the basic operations. I am using NAPALM version 2.5.0 and the NX-OS device is a Nexus7700 running 8.0(1).
Enabling the NXAPI
First we need to enable the API on the NX-OS. In addition to that, NAPALM also requires the feature scp-server feature turned on. To this end, we configure the following on the device:
feature scp-server feature nxapi no nxapi http nxapi https port 65000
We disabled HTTP access and we specified a non-standard port for HTTPs. We can verify our configuration with the following command:
nxos.lab# show feature | egrep "nxapi|scp" nxapi 1 enabled scpServer 1 enabled nxos.lab# show nxapi NX-API: Enabled Sandbox: Disabled HTTP Port: Disabled HTTPS Port: 65000
Retrieving information from the device
To verify that we can connect to the device, let’s start out issuing a CLI command:
import napalm import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) optional_args = {'port': '65000' } driver = napalm.get_network_driver('nxos') device = driver(hostname='192.0.2.1', username='admin', password='admin', optional_args=optional_args ) device.open() return_dictionary = device.cli(['show ipv6 ospfv3 neighbors', ]) device.close() s = return_dictionary['show ipv6 ospfv3 neighbors'] print(s)
The urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) was put in to mute the warning messages that were showing up on screen. Since we are using a non-standard port, we use the optional_args when we instantiate the device object to specify the port number. Note that by default, HTTPS is used for transport. If for whatever reason you absolutely want to use HTTP, then you can use the following: optional_args = {‘transport’: ‘http’ }.
When we run the script, we get the following output:
OSPFv3 Process ID 20 VRF default Total number of neighbors: 3 Neighbor ID Pri State Up Time Interface ID Interface 10.47.118.253 1 FULL/ - 3y0w 90 Vlan2 Neighbor address fe80::8e60:4fff:fee9:1141 10.47.118.243 128 FULL/ - 2y16w 4 Po3 Neighbor address fe80::86c1:c1ff:fee5:fc5 10.47.118.244 128 FULL/ - 2y16w 4 Po4 Neighbor address fe80::86c1:c1ff:feb5:78c7
When looking at the base NAPALM NetworkDriver class, I found that it has the __enter__
and __exit__
magic methods implemented. Because of this, we can implement objects using the with statement. When you use this approach, there is no need to specifically open of close the connection:
import napalm from pprint import pprint as pp import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) optional_args = {'port': '65000' } driver = napalm.get_network_driver('nxos') with driver(hostname='192.0.2.1', username='admin', password='admin', optional_args=optional_args ) as device: return_dictionary = device.cli(['show ipv6 ospfv3 neighbors', ]) s = return_dictionary['show ipv6 ospfv3 neighbors'] print(s)
There are some basic methods available to us ‘out of the box’:
import napalm from pprint import pprint as pp import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) optional_args = {'port': '65000' } driver = napalm.get_network_driver('nxos') with driver(hostname='192.0.2.1', username='admin', password='admin', optional_args=optional_args ) as device: pp(device.get_facts()) pp(device.get_bgp_neighbors()) pp(device.get_lldp_neighbors())
The nice thing about those methods is that they return structured data. Not everything is available out of the box though. The OSPF neighbors for instance are not among the ‘getters’. One quick and easy way to retrieve structured data from the NX-OS is through the use of the | json option that is available to several CLI commands.
import napalm from pprint import pprint as pp import json import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) optional_args = {'port': '65000' } driver = napalm.get_network_driver('nxos') with driver(hostname='192.0.2.1', username='admin', password='admin', optional_args=optional_args ) as device: ospf3_information = device.cli(['show ipv6 ospfv3 neighbors | json', ]) ospf_dictionary = json.loads(ospf3_information['show ipv6 ospfv3 neighbors | json']) pp(ospf_dictionary)
When we run the above example, we get the following output:
{'TABLE_ctx': {'ROW_ctx': {'TABLE_nbr': {'ROW_nbr': [{'addr': 'fe80::8e60:4fff:fee9:1141', 'drstate': '-', 'ifid': '90', 'intf': 'Vlan2', 'priority': '1', 'rid': '10.47.118.253', 'state': 'FULL', 'uptime': 'P3Y17DT3H53M19S'}, {'addr': 'fe80::86c1:c1ff:fee5:fc5', 'drstate': '-', 'ifid': '4', 'intf': 'Po3', 'priority': '128', 'rid': '10.47.118.243', 'state': 'FULL', 'uptime': 'P2Y4M5DT16H45M22S'}, {'addr': 'fe80::86c1:c1ff:feb5:78c7', 'drstate': '-', 'ifid': '4', 'intf': 'Po4', 'priority': '128', 'rid': '10.47.118.244', 'state': 'FULL', 'uptime': 'P2Y4M5DT16H20M31S'}]}, 'cname': 'default', 'nbrcount': '3', 'ptag': '20'}}}
NX-OS does not have this option implemented for all CLI commands, so your mileage may vary. The show ip interfaces brief | json command is supported for instance, but the show ip bgp summary | json is not. In those cases, it might help to look at textFSM or see if you can get by with some simple string methods.
Configuring the device
Let’s start out configuring the following ACL:
ipv6 access-list management-v6 10 remark hq 11 permit ipv6 2001:db8:1200:8800::/59 any 20 remark branch 21 permit ipv6 2001:db8:2200:8800::/59 any
We can apply it using the following script:
import napalm import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) optional_args = {'port': '65000' } driver = napalm.get_network_driver('nxos') with driver(hostname='192.0.2.1', username='admin', password='admin', optional_args=optional_args ) as device: device.load_merge_candidate(filename='/var/tmp/add_acl.cfg') print(device.compare_config()) device.commit_config()
When we run the script, the ACL is configured and the diff gets printed to screen:
sh-4.4# python3 nx_os_add_acl.py ipv6 access-list management-v6 10 remark hq 11 permit ipv6 2001:db8:1200:8800::/59 any 20 remark branch 21 permit ipv6 2001:db8:2200:8800::/59 any
This diff is informative, but it is unlike the diff you get from Juniper, Arista or IOSXR. When you use the merge operation in NAPALM, this diff is a simple comparison between the configuration that you are loading and the running configuration.
Let’s update the ACL with the following:
sh-4.4# cat /var/tmp/acl_update.cfg no ipv6 access-list management-v6 ipv6 access-list management-v6 10 remark hq 11 permit ipv6 2001:DB8:1200:8800::/59 any
The diff will output the following:
no ipv6 access-list management-v6 11 permit ipv6 2001:DB8:1200:8800::/59 any
The ‘no xxx’ is not in the configuration, so that is displayed. In the 11th sequence, I used ‘DB’ instead of ‘db’ so that is something that differs. The lines of the ACL that get removed are not detected by the _get_merge_diff() function.
Thing worth noting is that when you use the load_replace_candidate(), the way the diff happens changes. What happens when you use load_replace_candidate() is it will upload the specified configuration, which should be a valid checkpoint file, to the device. This file should contain the running configuration as well as the configuration you want to add. After this, a checkpoint file called sot_file is created. These two files are then used to perform a show diff rollback-patch file sot_file file candidate_cfg.txt.
Obtaining a diff usin the load_replace_candidate() function could be done like this:
import urllib3 import napalm urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) optional_args = {'port': '65000' } driver = napalm.get_network_driver('nxos') with driver(hostname='192.0.2.1', username='admin', password='admin', optional_args=optional_args ) as device: device.load_replace_candidate(filename='/var/tmp/example.cfg') print(device.compare_config()) device.discard_config()
The above script will only retrieve a diff and report back on the output. It will not change the configuration of the device because instead of calling commit_config(), the discard_config() method is used.
The diff will be better (not perfect), but it is a finicky process at best. It will make you appreciate Junos, EOS or IOSXR all the more.