Automating your NMS build using Python and Restful APIs Part 2 – Adding Devices though Auto-Discovery


This is the second in a series of posts where I’ll be using a RESTful API to automate a bunch of the initial deployment functions within my NMS. There are a bunch of reasons to do this that are right for the business. Being able to push information gathering onto the customer, being able to use lower-skilled ( and hence lower paid!) resources to do higher level tasks. Being able to be more efficient in your delivery, undercut the competitors on price and over deliver on quality. It’s a really good project to sink my teeth and use some of my growing coding skills to make a difference to the business. 

Other posts in this series

Automating your NMS build using Python and Restful APIs Part 1 – Creating Operators

 

Adding Devices

A Network Management System without devices is a sad, sad thing. It’s just a lonely piece of code with no purpose in life. Empty graphs and arm notifications with nothing to notify. Today we’re going to look at adding devices through launching a basic network range auto discovery. In a follow up post, I’ll be adding device through a CSV import which is my preferred method. 

One thing which I will not be covering in this post to save some time and space is how to authenticate to the NMS system which is covered here

Launching an Autodiscovery

One of the worst things about installing an NMS in a customers environment is the inevitable scope creep that happens. The major reason that this happens is that most customers looking for an NMS are doing so because they really don’t have a clue what’s in their network, and potentially not even how they are configured. They know it’s kinda working most of the time, but not much more than that.

Typically, you have a statement of work to discover N number of devices. The customer says  “I don’t know all the IP addresses, why don’t you just discover this range of IP addresses. So you launch a discovery from your tool and suddenly, you’ve got 2*N devices in your database!  And because they are in your database, you’re now responsible for getting them up and running. Even though your contract is for only N devices, you don’t want to disappoint your customer right? 

There other things that can easily go wrong in an auto discovery as well like

  • Mis-matched SNMP strings: You have the SNMP read, but not the write strings.

This can cause some unpredictable results that are SOOO fun to troubleshoot.  

  • Missing CLI credentials: You have SNMP strings, but not the CLI strings.

There are some things that are only accessible through CLI. It’s great that we’re moving into an era where decent programatic APIs are available devices, but for the mass majority of your infrastructure, there’s a ton of functions which are only available through the good old command line interface.  If you don’t have the right credentials, you’re going to have problems. In my experience, unless they have a centralized authentication system in place, like Cisco ACS, FreeRADIUS or other; you’re probably going to have to troubleshoot half of the device credentials. 

Let’s look at the code. 

First I’ll show the entire function complete, and then we’ll break down the sections individually.  Again, I’ll be skipping the imc_creds() function as it’s covered in the earlier post. 

 import requests, json, sys, time, subprocess, csv, os, ipaddress 

 def plat_auto_discover():

    if auth == None or url == None: # checks to see if the imc credentials are already available
    imc_creds()
    auto_discover_url = '/imcrs/plat/res/autodiscover/start'
    f_url = url + auto_discover_url
    network_address = input(
    '''What is the the network address of the range you wish to discover?\nPlease input the address in the format "192.168.0.0/24": ''')
    # end_address = input('''What is the last address of the network range you wish to discover?\nIPv4 Address: ''')
    try:
        network_address = ipaddress.ip_network(network_address)
    except ValueError:
        print("You have entered an invalid network address. Please try again.")
        time.sleep(2)
        print ('\n'*80)
        plat_auto_discover()
    payload = ''' {
    "mode": "0",
    "ipSection": {
        "begin": "''' + str(network_address[1]) + '''",
        "end": "''' + str(network_address[-2]) + '''"
        },
    "discoverNonSnmpDevice": "true",
    "pingAll": "true"
    }
    '''
    r = requests.post(f_url, data=payload, auth=auth, headers=headers) #creates the URL using the payload variable as the contents
    if r.status_code == 200:
        print ("Auto-Discovery Successfully Started")
    else:
        print ("An Error has occured")

 Gathering the network range

So the first task here is gathering the actual network range that we want to run the auto discover over. For this example, we’ll keep this very basic and use a simple 192.168.0.0/24 network. This is going to cause the system to scan the entire 254 hosts of the subnet.  One of the other things to keep in mind here is that I’m going to use the default system templates for SNMP and Telnet which is why I’m not gathering them here. This isn’t magic, right?

So I’m using the input function to gather the IP address network range that I want to discover. I’m also using the python ipaddress standard library to caste the user input as a network_address to ensure that it’s a valid input to the final function. 

network_address = input(
    ”’What is the the network address of the range you wish to discover?\nPlease input the address in the format “192.168.0.0/24″: ”’)
    # end_address = input(”’What is the last address of the network range you wish to discover?\nIPv4 Address: ”’)
    try:
        network_address = ipaddress.ip_network(network_address)
    except ValueError:
        print(“You have entered an invalid network address. Please try again.”)
        time.sleep(2)
        print (‘\n’*80)
        plat_auto_discover()

So essentially, this code performs three steps

  1. Gathers the desired network address discovery range in the 192.168.0.0/24 format
  2. Attempts to use the ipaddress.ip_network method from the python ipaddress library to test if the user input was valid, and stores it in the  variable network_address
  3. If the ipaddress.ip_network method fails, this will raise an error and re-run the function to gather input in the right format.

The other nice thing about storing the user input as a ipaddress.ip_network object means that we can easily gather the start and end of this subnet based on the subnet math that’s included in the library. I’d always prefer  to have someone/something else do the binary math. 🙂

Creating the JSON Array

This particular API uses a JSON array in the HTTP message body to gather all of the information used to launch the auto-discovery.

payload = ”’

{
    “mode”: “0”,
    “ipSection”: {
        “begin”: “”’ + str(network_address[1]) + ”'”,
        “end”: “”’ + str(network_address[-2]) + ”'”
        },
    “discoverNonSnmpDevice”: “true”,
    “pingAll”: “true”
}
”’

I’m assuming you’re somewhat comfortable with working with strings in python here. The only thing that’s a little funny is the two str(network_address[1]) and str(network_address[-2]) lines.  This is the magic part of the ipaddress library that I mentioned above. These two lines of code do all the binary math for you. For those who aren’t used to subnet math, the first address in any IP network range is actually the network address, so we don’t want the 1, not 0, will be the first valid IP address in the range.  The last address in the range is actually the broadcast address, which is why we use -1 and not -2.  Pretty obvious when you think about it, right?

The network_address object is actually of the ‘ipaddress.IPv4Network’ class, so I’m using the str method to ensure that it’s a valid string when I’m adding joining it to the other text to create the payload. 

You can see from the following print statement that the first and last address in the range are exactly what we expect them to be. 

>>> print (json.dumps(json.loads(payload),indent=4))
{
"pingAll": "true",
"mode": "0",
"discoverNonSnmpDevice": "true",
"ipSection": {
"begin": "192.168.0.1",
"end": "192.168.0.254"
}
}

 

Sending the Request

So the last part of this code is just sending the actual request to the web server and seeing what happens.

 

r = requests.post(f_url, data=payload, auth=auth, headers=headers) #creates the URL using the payload variable as the contents 
    if r.status_code == 200:
        print ("Auto-Discovery Successfully Started")
    else:
        print ("An Error has occured")

 
Using the requests library POST method, we create the HTTP call and include the PAYLOAD that we created above as the data for the message body of this request. 
I’m also evaluating the return of the request here. If the HTTP response code is 200 OK, then everything is good. If it’s anything else, then there’s an issue. As with almost any code on the planet, we could probably do a lot more error handling here, but more my purposes, this is more than ok.
 
 
Do you have better ways of doing anything I’ve got here?  Comments are welcome
 
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s