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

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

It’s a funny world we live in.  Unless you’re hiding under a rock, there’s been a substantial push in the industry over the last few years to move away from the CLI.  As someone right in the middle of this swirling vortex of inefficiency, I’d like to suggest that it’s not so much the CLI that’s the problem, but the fact that each box is handled on an individual basis and that human beings access the API through a keyboard. Not exactly next-generation technology.

 

I’ve been spending lot of time learning python and trying to apply it to my daily tasks. I started looking at the HP IMC Network Management station a few months ago. Mainly as a way to start learning about how I can use python to access RESTFul APIs as well as gain some hands on working with JSON and XML. As an observation, it’s interesting to be that I’m using a CLI ( python ) to configure an NMS ( IMC) that I’m using to avoid using the CLI. ( network devices ).   

I’ve got a project I’m working on to try and 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. 

This is the first post in which I’ll discuss and document some of the simple functions I’m developing. I make no claims to be a programmer, or even a coder. But I’m hoping someone can find something here usefull, and possibly get inspired to start sharing whatever small project you’re working on as well. 

 

Without further ado, let’s jump in and look at some code. 

What’s an Operator

Not familiar with HP IMC?  You should be! It’s chock full of goodness and you can get a 60 day free trial here.   In IMC an Operator is someone who has the right to log into the system and perform tasks in the NMS itself.  The reason they use the word operator vs. user is that there’s a full integrated BYOD solution available as an add-on module which treats a user as resource, which of course is not the same thing as an administrator on the system. 

IMC’s got a full RBAC system as well which allows you to assign different privilege levels to your operators, from view only to root-equiv access, as well as splitting up what devices you can perform actions on, as well as segmenting what actions you’re allowed to perform. Pretty powerful stuff once you understand how the pieces go together. 

Adding an Operator in the GUI

 This is a screen capture of the dialog used to add an operator into IMC.  It’s intuitive. You put the username in the username box, you put the password in the password box. Pretty easy right?

If you know what you’re doing and you’re a reasonably good typist, you can add probably add an operator in a minute or less.  

Screen Shot 2015 04 16 at 12 19 17 PM

Where do Operators come from?

Don’t worry. This isn’t a birds and bees conversation.  One of the biggest mistakes that I see when people start into any network management system project, whether that’s Solarwinds, Cisco Prime, What’s up Gold, HP NNMi, or HP IMC, is that they don’t stop to think about what they want/need to do before they start the project.  They typically sit down, start an auto-discovery and then start cleaning up afterwards.  Not exactly the best way to ensure success in your project is it?

When I get involved in a deployment project, I try to make sure I do as much of the information gathering up front. This means I have a bunch of excel spreadsheets that I ask them to fill in before I even arrive onsite. This ensures two things:

  1. I can deliver on what the customer actually wants
  2.  I know when I’m done the project and get to walk away and submit the invoice. 

 

I won’t make any judgement call on which one of those is more important. 

 

 

My Operator Template

My operator template looks like this

NewImage

The values map to the screen shot above exactly as you would expect them to. 

Full name is the full name. Name is the login name, password is the password etc…  

The authType is a little less intuitive, although it is documented in the API docs. The authType maps to the authentication type above which allows you to choose how this specific operator is going to authenticate, through local auth, LDAP, or RADIUS. 

The operator group, which is “1” in my example, maps to the admin operator group which means that I have root-level access on the NMS and can do anything I want. Which is, of course, how it should be, right?

 

The Problem

So I’ve got a CSV file and I know it takes about one minute to create an operator because I can type and I know the system. Why am I automating this? Well, there are a couple of reasons for that.

  • Because I can and I want to gain more python experience
  • Because if I have to add ten operators, this just became ten minutes.
  • Because I already have the CSV file from the customer. Why would I type all this stuff again?
  • Because I can reuse this same format at every customer project I get involved in. 
  • Because I can blame any typos on the customer

Given time, I could add to this list, but let’s just get to the code. 

The Code

Authenticating to the Restful API

Although the auth examples in the eAPI documentation use the standard URLIB HTTP library, I’ve found that the requests library is MUCH more user friendly and easier to work with.

So I first create a couple of global variables called URL and AUTH that I will use to store the credentials.  

 

#url header to preprend on all IMC eAPI calls
url = None

#auth handler for eAPI calls
auth = None 

Now we get to the meat. I think this is pretty obvious, but this function gathers the username and password used to access the eAPI and then tests it out to make sure it’s valid. Once it’s verified as working ( The 200 OK check ). The credentials are then stored in the URL and AUTH global variables for use later on. I’m sure someone could argue that I shouldn’t be using global variables here, but it works for me. :) 
 
def imc_creds():
    ''' This function prompts user for IMC server information and credentuials and stores
    values in url and auth global variables'''
    global url, auth, r
    imc_protocol = input("What protocol would you like to use to connect to the IMC server: \n Press 1 for HTTP: \n Press 2 for HTTPS:")
    if imc_protocol == "1":
        h_url = 'http://'
    else:
        h_url = 'https://'
    imc_server = input("What is the ip address of the IMC server?")
    imc_port = input("What is the port number of the IMC server?")
    imc_user = input("What is the username of the IMC eAPI user?")
    imc_pw = input('''What is the password of the IMC eAPI user?''')
    url = h_url+imc_server+":"+imc_port
    auth = requests.auth.HTTPDigestAuth(imc_user,imc_pw)
    test_url = '/imcrs'
    f_url = url+test_url
    try:
        r = requests.get(f_url, auth=auth, headers=headers)
    except requests.exceptions.RequestException as e: #checks for reqeusts exceptions
        print ("Error:\n"+str(e))
        print ("\n\nThe IMC server address is invalid. Please try again\n\n")
        imc_creds()
    if r.status_code != 200: #checks for valid IMC credentials
        print ("Error: \n You're credentials are invalid. Please try again\n\n")
        imc_creds()
    else:
        print ("You've successfully access the IMC eAPI")
 
 
I”m using this function to gather the credentials of the operator accessing the API. By default when you first install HP IMC, these are admin/admin.    You could ask: Why don’t you just hardcode those into the script? Why bother with writing a function for this? 
Answer: Because I want to reuse this as much as possible and there are lots of things that you can do with the eAPI that you would NOT want just anyone doing. Plus, hardcoding the username and password of the NSM system that controls your entire network is just a bad idea in my books. 
 

Creating the Operators

I used the HP IMC eAPI /plat/operator POST call to as the basis for this call. 

Screen Shot 2015 04 16 at 1 06 21 PM

 

After doing a bit of testing, I arrived at a JSON array which would allow me to create an operator using the “Try it now” button in the API docs.  ( http://IMC_SERVER:PORTNUMBER/imcrs to access the online docs BTW ).

    {
"password": "access4chris",
"fullName": "Christopher Young",
"defaultAcl": "0",
"operatorGroupId": "1",
"name": "cyoung",
"authType": "0",
"sessionTimeout": "10",
"desc": "admin account"
}

Using the Try it now button, you can also see the exact URL that is used to call this API. 

The 201 response below means that it was successfully executed. ( you might want to read up on HTTP codes as it’s not quite THAT simple, but for our purposes, it will work ).

Screen Shot 2015 04 16 at 1 10 46 PM

Now that I’ve got a working JSON array and the URL I need, I’ve got all the pieces I need to put this small function together. 

You can see the first thing I do is check to see if the auth and url variables are still set to None. If they are still None I use the IMC_CREDS function from above to gather them and store them. 

 

I create another variables called headers which stores the headers for the HTTP call. By default, the HP IMC eAPI will respond with XML. After working with XML for a few months, I decided that I prefer JSON. It just seems easier for me to work with.

This piece of code takes the CSV file that we created above and decodes the CSV file into a python dictionary using the column headers as the key and any additional rows as the values. This is really cool in that I can have ten rows, 50 rows, or 100 rows and it doesn’t matter. This script will handle any reasonable number you throw at it. ( I’ve tested up to 20 ).

 

#headers forcing IMC to respond with JSON content. XML content return is the default

headers = {‘Accept’: ‘application/json’, ‘Content-Type’: ‘application/json’,’Accept-encoding’: ‘application/json’}

def create_operator():
    if auth == None or url == None: #checks to see if the imc credentials are already available
        imc_creds()
    create_operator_url = ‘/imcrs/plat/operator’
    f_url = url+create_operator_url
    with open (‘imc_operator_list.csv’) as csvfile: #opens imc_operator_list.csv file
        reader = csv.DictReader(csvfile) #decodes file as csv as a python dictionary
        for operator in reader:
            payload = json.dumps(operator, indent=4) #loads each row of the CSV as a JSON string
            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 == 409:
                print (“Operator Already Exists”)
            elif r.status_code == 201:
                print (“Operator Successfully Created”)

 Now you run this code and you’ve suddenly got all the operators in the CSV file imported into your system. 

Doing some non-scientific testing, meaning I counted in Mississippi’s, it took me about 3 seconds to create 10 operators using this method.  

Time isn’t Money

Contrary to the old saying, time isn’t actually money. We can always get more money. There’s lots of ways to do that. Time on the other hand can never be regained. It’s a finite resource and I’d like to spend as much of it as I can on things that I enjoy.  Creating Operators in an NMS doesn’t qualify.

Now, I hand off a CSV file to the customer, make them fill out all the usernames and passwords and then just run the script. they have all the responsibility for the content and all I have to do is a visual on the CSV file to make sure that they didn’t screw anything up.

 

Questions or comments or better ways to do this?  Feel free to post below. I’m always looking to learn.

 

@netmanchris