In the first couple of posts in this series, we created some operators, then we added some devices. In this post we’re going to look at something a little more complicated. In this post, we’re going to link a couple of different python functions together to meet the requirement which is to change a device from one category to another.
A bit about Device Categories
For those of you haven’t used HP’s Intelligent Management Centre before, the system automatically categorizes any discovered device, usually based upon SNMP sysobjectid. What this means is that when you run an auto discovery, the majority of your infrastructure will be properly classified right out of the box.
This works great for devices which are SNMP enabled, as well as some other devices like ESX machines ( Virtual Devices ) which use SOAP as the management protocol, but it fails pretty miserably when dealing with devices which don’t support anything more than PING. IMC’s default behaviour is to put anything which doesn’t respond to SNMP in the Desktop Category.
IP Phones aren’t smart
I’ve had customers who decided to discover all of their expensive IP phones and suddenly found out that none of them were classified properly. The problem with IP phones is that they are usually pretty stupid devices. Low memory, weak CPUs. Most of the processing/thinking is done by the PBX. I’ve never seen an IP phone that supports SNMP.
In this case we have two choices
- Manually change each IP phone from the Desktop category into the Voice category one device at a time.
- Use the RESTful API and a bit of code to do it wihile we go have a coffee
Filtering First
So the first thing we need to do is to identify the IP phones from the rest of the devices in the Desktop category. Thankfully, this is where having a well designed network can come in REALLY handy. Most Voice networks are designed so that the IP phones are automatically put into a voice vlan. This means that all phones SHOULD be in the same layer 3 network range.
The first piece of code we need to write is a simple piece of code which will allow the user to identify which of the categories they want to filter by. Although this might sound strange, we actually need to make sure that we only grab the DESKTOP devices in a specific subnet range. Imagine if you accidentally move the router or switch for this subnet into the voice category too. Really sucks leaving when you lose your router, right?
In this piece of code, we’re simply creating a dictionary which creates the link between the categoryId, which is the number IMC internally uses to identify the defined categories and the labels we humans use to identify them. In a nutshell, this simply prints out the available categories. Why you ask? Because the human running this script needs to known which categories they want to filter by.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def print_dev_category(): | |
categories = [{"categoryId":"0", "dev_type":"router"}, | |
{"categoryId":"1", "dev_type":"switch"}, | |
{"categoryId":"2", "dev_type":"server"}, | |
{"categoryId":"3", "dev_type":"security"}, | |
{"categoryId":"4", "dev_type":'storage' }, | |
{"categoryId":"5", "dev_type":"wireless"}, | |
{"categoryId":"6", "dev_type": "voice"}, | |
{"categoryId":"7", "dev_type":'printer'}, | |
{"categoryId":"8", "dev_type":'ups'}, | |
{"categoryId":"9", "dev_type":"desktop"}, | |
{"categoryId":"10", "dev_type":"other"}, | |
{"categoryId":"11", "dev_type":"surveillance"}, | |
{"categoryId":"12", "dev_type":"video"}, | |
{"categoryId":"13", "dev_type":"module"}, | |
{"categoryId":"14", "dev_type":"virtualdev"}, | |
{"categoryId":"15", "dev_type":"Load Balancer"}, | |
{"categoryId":"16", "dev_type":"sdn_ctrl"} | |
] | |
for i in categories: | |
print ("For "+i["dev_type"]+", Please press: "+i["categoryId"]) |
This next performs two different functions
- Filters all known devices by the categories listed above
- Filters all known devices by an IP range.
Combining the two of them we’re able to easily fine all of the devices that have been classified in the Desktop Category in the L3 subnet of the IP phones and return that as a dictionary which contains, among other things, the device IDs which we’ll need in the next step.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def filter_dev_category(): | |
if auth == None or url == None: # checks to see if the imc credentials are already available | |
imc_creds() | |
global r | |
category = None | |
ip_range = None | |
get_dev_list_url = None | |
filter_by_cat = input("Do you want to filter by device category?\nY/N: ") | |
if filter_by_cat.lower() == "y": | |
print_dev_category() | |
category = input("Please select the device category: ") | |
get_dev_list_url = ("/imcrs/plat/res/device?resPrivilegeFilter=false&category="+category+"&start=0&size=10000&orderBy=id&desc=false&total=false") | |
#return get_dev_list_url | |
filter_by_ip = input("Do you want to filter by IP address network range?\nY/N: ") | |
if filter_by_ip.lower() == "y": | |
ip_range = input("What is the ip network range?\n Example: 10.101.16.\nFuzzy search is acceptible: ") | |
if category == None: | |
get_dev_list_url = ("/imcrs/plat/res/device?resPrivilegeFilter=false&ip="+ip_range+"&start=0&size=5&orderBy=id&desc=false&total=false") | |
else: | |
get_dev_list_url = ("/imcrs/plat/res/device?resPrivilegeFilter=false&category="+category+"&ip="+ip_range+"&start=0&size=10000&orderBy=id&desc=false&total=false") | |
f_url = url + get_dev_list_url | |
payload = None | |
r = requests.get(f_url, auth=auth, headers=headers) #creates the URL using the payload variable as the contents | |
r.status_code | |
if r.status_code == 200: | |
dev_list = (json.loads(r.text))["device"] | |
return dev_list | |
else: | |
print ("An Error has occured") |
Putting it all together
So the last piece of code is where the magic actually happens.
First we assign the output of the filtering function above into the variable called dev_list. Essentially, this is a list of devices which meet the search criteria of the filtering function.
In our case, this means the list will consists of all the IP phones in the specific subnet that we filtered.
From there, we use the items in dev_list as input into a for loop which changes them into the new category. ( see how we use the same print_category function from above? )
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def change_dev_category(): | |
global dev_list | |
dev_list = filter_dev_category() | |
for dev in dev_list: | |
print ("system name: "+ dev['sysName']+'\nCurrent Category: '+dev['devCategoryImgSrc']+ | |
'\nIP address: '+ dev['ip']+'\nSystem Description: '+dev['sysDescription']+ '\n\n') | |
print_dev_category() | |
cat_id = input("What is the category to which you want to move the selected device: ") | |
for i in dev_list: | |
dev_id = i["id"] | |
change_dev_cat_url = "/imcrs/plat/res/device/"+dev_id+"/updateCategory" | |
f_url = url + change_dev_cat_url | |
payload = '''{ "categoryId" : "'''+cat_id+'''" }''' | |
r = requests.put(f_url, data=payload, auth=auth, headers=headers) #creates the URL using the payload variable as the contents | |
if r.status_code == 204: | |
continue | |
else: | |
print (r.status_code) | |
print ("An Error has occured") |
Wrapping it up
Hopefully this is a fairly useful example of how putting a few basic python functions together can help to substantially cut down on the amount of time it takes to perform what’s really a simple task. That’s the whole point of automation right?
As I continue learning, I can already see there a bunch of ways to improve this code, but hopefully having a simple working example will help people who are a couple of steps behind me on this journey take another step forward.
If you’re ahead of me on this journey and have suggestions, please feel free to comment!