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.