SD-Access Programmability Part I - creating a Nornir network inventory from DNAC

I hear that absence makes the heart grow fonder. Did you miss me?


I wanted to kick this section off with an extensive look at all options to programmatically interact with Cisco DNA Center (DNAC, going forward). But I don't want to re-hash what's already out there - my friend Robert Csapo (https://twitter.com/robertcsapo) already has an amazing post on Medium for this that you should absolutely read (and he's articulated this far better than I ever could). You can find it here.



Note: the only thing I'd add to this is that since the time of his writing, there has been an official Ansible collection released for DNAC (https://galaxy.ansible.com/cisco/dnac) as well as a Terraform provider (https://registry.terraform.io/providers/cisco-en-programmability/dnacenter/latest/docs) with related resources. If this is your cup of tea, then these are now available as well.


So, instead, we're going to jump right in. My goal through this broader section is to use Python to automate some interesting use cases that I've been thinking of, over the past year or so, for DNAC and SD-Access.


In this particular post, we're going to build a network inventory from DNAC, that can in turn be leveraged by Nornir and Scrapli. Why build a network inventory, why Nornir and why Scrapli?


Some of the initial use cases I have in mind involve a lot of screen scraping and more specifically, configuration parsing. I could use a northbound API that DNAC exposes for gathering the configuration of network devices in it's inventory (/dna/intent/api/v1/network-device/{networkDeviceId}/config) - but this makes things a little more complicated and slow. I would first need to gather a list of all network devices and their IDs (/dna/intent/api/v1/network-device/), then feed those IDs into the configuration API. Couple this with our current DNAC API rate limits and this approach doesn't scale really well.


Thus, I decided to go the traditional route - screen scraping by connecting to the devices directly via SSH. I still need a list of network devices though - this is where Nornir comes in. Like Ansible, Nornir is an automation framework (https://nornir.readthedocs.io/en/latest/) but the key difference is that it can be used natively in Python. You're not forced to learn the idiosyncrasies of a domain specific language (that is not relevant to anything outside of it).


Nornir also allows for concurrency - remember, speed becomes a factor once you think of scaling up your network devices. Between Scrapli and Netmiko, I don't think you can really go wrong with your choice. Scrapli is more modern and was written specifically with speed in mind (the author's own words), thus Scrapli it is.


For this post, we're going to be using the following topology, which is fully integrated and managed by my DNAC:


So, the first thing I need is a list of devices from the DNAC inventory. DNAC exposes an API for this, which we will use here. All business APIs for DNAC can be found by navigating to Platform -> Developer Toolkit. An example screenshot below:




The API that we're interested in is - '/dna/intent/api/v1/network-device/'. DNAC APIs function with the concept of an authorized token. Thus, every API must have an 'X-Auth-Token' header, with the authorized token as it's value.


This token can be generated using the Authentication API:




I have the following function that accesses the API (taking DNAC IP address, username and password as an input) and returns the authorized token.





This token can now be used to access all subsequent APIs. Remember, the DNAC token is only valid for 60 minutes post which a new token must be generated again. With this token, I can use the network device API to get a list of all network devices. The following function does this:





So, what does the API response look like? Let's take a look at this using pdb (python debugger). I have added a breakpoint just before the return statement.




The response is a dictionary with one key called 'response'. Because the output is too long, I'll simply add a screenshot of it here:





Thus, the API is simply returning a list of dictionaries, where each dictionary is a network device and it's properties (like software version, IP address and so on). If no network devices exist, an empty list is returned.


Next up is the code to convert this list of network devices retrieved from DNAC into a dictionary that can be used to convert into a hosts file for Nornir. This function is simply looping through the list of dictionaries we pass into it, and saving the IP address of the network device and the role into another dictionary.




The parsed dictionary we return looks like this:


A feedback that I got from Dmitry Figol for this specific snippet of code is that there are several instances where I re-use device['hostname']. A good practice here would be to just assign this to a variable and define the complete dictionary inline itself. It would look something like this:




And that's all of the functions that are needed here. The final piece we'll look at is the main function.


In the main function, we'll take several inputs from the user:


  1. IP address of DNAC, along with username and password. This is needed to generate the authentication token for all APIs.

  2. Credentials to login to network devices - this will eventually be added to the defaults file that Nornir uses.

  3. File path to store the hosts and defaults file that will be generated as part of this script.


Let's walk through this now - I'll be breaking it into segments.


First, we gather several inputs from the user. A try/except block is used to generate an authentication token from DNAC. If this fails, a custom exception is raised.




Next, we get the list of network devices from DNAC using the function we defined earlier. If no devices were found, we return out.


Once we have this list, we parse through it (again, using the function we defined earlier) to convert it into a dictionary that can be used to generate a Nornir hosts file.




Next, we take this dictionary and convert it into a hosts.yaml file for Nornir to use. Again, this is inside a try/except block to ensure we catch any potential issues with file opening and write permissions. Once the file is open, the yaml.dump method is used to write into a yaml file - this method takes a Python dictionary and converts into a yaml format. This is why we had our network devices stored as a dictionary earlier.



We repeat the same process for the defaults file as well. Let's execute the entire code now.



This correctly generates the following files in the specified path:



Finally, let's run a very simple Nornir script that proves this works. The goal is to just run the command 'show vrf' against the inventory that we just created for Nornir.



The output of this is:



The entire code can be found on github - https://github.com/aninchat/nornir_inventory_from_dnac


Being a network automation beginner (beginner is probably an understatement too), I wanted to write about potential use cases I see in my current line of work (DNAC and SD-Access). I also wanted to break down my own thinking and in particular, feedback that I get from architects like Dmitry that helps me improve my thinking when writing/reviewing code.


I hope this was informative.

1,285 views0 comments