SolarWinds Orion API & SDK – Scripting with Python (Part 3)

by

Scripting with Python

This is the third article in a series we’re calling “SolarWinds Orion API & SDK”. The first article covered concepts, purpose and how to get started with the SDK. In the second article we took a look at interaction with the API via cURL and a REST client. By now you should have a taste of what SolarWinds’ API and SDK can bring to the table. You’re understanding how to interact with the API, and you’re starting to grasp the potential.

Manually making static changes is great and all, but wouldn’t it be cool if we could wrap these into some sort of programmatic method to save us time and energy? Sure would. In this article we’ll be looking at just that. In particular, we’ll be looking at RESTful API interactions via Python. If you’re inexperienced with Python, don’t worry too much. SolarWinds provides several example scripts we can use to test these capabilities, and if you’re so inclined, reverse engineer to fit your environment.

A couple of house-keeping items: we won’t be covering Python basics. If you’re new to it, or just want to know more, I suggest you check out various online training resources, typically free of charge. Codecademy comes to mind for Python. Oh, and you’ll want to make sure you have Python installed on your system. If you’re on Mac OSX, Python is included. If you’re on Windows, download Python here.

Python

In the first article we lightly touched on the GitHub repository called orionsdk-python. This is an important repo for us, as it contains the SwisClient applet and several examples detailing its use. Assuming you have Python installed, the first step to take is installing the orionsdk package. To do this, we’ll use the Python package manager, pip. If you’re on a Mac, you can run:

sudo easy_install pip

If you’re on Windows, follow these instructions to download the get-pip.py script and execute the following command via CLI.

python get-pip.py

Now that pip is installed, we’ll install the orionsdk package.

~$ pip install orionsdk
Downloading/unpacking orionsdk
 Downloading orionsdk-0.0.4.zip
 Storing download in cache at ./.pip/cache/https%3A%2F%2Fpypi.python.org%2Fpackages%2F3b%2Fbb%2F9a3b3714ca4b69cdd247f110e975aa00e82b5d407ae00e1959381c82f94d%2Forionsdk-0.0.4.zip
 Running setup.py (path:/private/var/folders/0x/bl6mqjts76vgfbgj327sqjhh0000gn/T/pip_build_vosx/orionsdk/setup.py) egg_info for package orionsdk
 
Requirement already satisfied (use --upgrade to upgrade): six in /Library/Python/2.7/site-packages (from orionsdk)
Downloading/unpacking requests (from orionsdk)
 Downloading requests-2.11.1-py2.py3-none-any.whl (514kB): 514kB downloaded
 Storing download in cache at ./.pip/cache/https%3A%2F%2Fpypi.python.org%2Fpackages%2Fea%2F03%2F92d3278bf8287c5caa07dbd9ea139027d5a3592b0f4d14abf072f890fab2%2Frequests-2.11.1-py2.py3-none-any.whl
Installing collected packages: orionsdk, requests
 Running setup.py install for orionsdk
 
Successfully installed orionsdk requests
Cleaning up...

Taking a look at the samples available in the SDK, we have see some potentially useful scripts for adding nodes, updating custom properties, querying interfaces and unmanaging nodes. This may seem basic at first, but these are common and critical tasks within the SolarWinds realm, meaning they hold high value in the automation arena.

SolarWinds Orion API & SDK - Scripting with Python

Let’s open one of these up and take a look inside. I’ll pick on query.py.

import requests
from orionsdk import SwisClient

npm_server = 'localhost'
username = 'admin'
password = ''

verify = False
if not verify:
    from requests.packages.urllib3.exceptions import InsecureRequestWarning
    requests.packages.urllib3.disable_warnings(InsecureRequestWarning)


swis = SwisClient(npm_server, username, password)

print("Query Test:")
results = swis.query("SELECT TOP 3 NodeID, DisplayName FROM Orion.Nodes")

for row in results['results']:
    print("{NodeID:<5}: {DisplayName}".format(**row))

A little dissection tells us we’re importing “requests”, which is a simple HTTP Python library. We’re also importing SwisClient from the orionsdk package we installed a few moments ago.

import requests
from orionsdk import SwisClient

Next we create some variables

npm_server = 'localhost'
username = 'admin'
password = ''

Bypass SSL warnings (NOT recommended, but we’re on a ‘safe’ lab network so keeping here for now)

verify = False
if not verify:
    from requests.packages.urllib3.exceptions import InsecureRequestWarning
    requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

Next we invoke the SwisClient, which connects to the NPM server.

swis = SwisClient(npm_server, username, password)

Lastly, this script simply selects the top 3 nodes by NodeID and prints it to the terminal.


print("Query Test:")
results = swis.query("SELECT TOP 3 NodeID, DisplayName FROM Orion.Nodes")
for row in results['results']:
    print("{NodeID:<5}: {DisplayName}".format(**row))

As you can tell, this isn’t a very useful script. Though it lacks in practicality, it shows us how the orionsdk module works, and shows us a basic example of the SwisClient function within the orionsdk module. The SwisClient is important to understand, as it is our means to communicate with the API via Python.

If you navigate to orionsdk-python/orionsdk/swisclient.py in the GitHub repo, you’ll find the construct of the SwisClient. Without going into gross detail, you’ll find that SwisClient has methods defined for querying, invoking, creating, reading, updating and deleting. Each of these have their respective uses. If you want to query something, use the query method. If you want to create something, use the create method. The development team at SolarWinds has clearly defined and labelled these components for you. I’ll show you how to use a few of these as this article progresses.

Python Interpreter

When learning something new in Python, I often find it most valuable to hop on an interpreter, and poke around. If you’re on a Mac, you can just type ‘python’ at the terminal. If you’re in Windows, run the Python Terminal/IDE you installed earlier.

Import the same library and functions as before

>>> 
>>> import requests
>>> from orionsdk import SwisClient
>>>

Set our variables

>>>
>>> npm_server = 'orion'
>>> username = 'labuser'
>>> password = 'mypassword'
>>>

Connect to Orion

>>>
>>> swis = SwisClient(npm_server, username, password)

Use SwisClient to ‘query’

We should now be able to run a query. I’m going to search for my Cisco 2960 switch again and pull the address city like I did in the last article.

>>>
>>> swis.query("SELECT n.Uri, n.CustomProperties._Location, n.CustomProperties.Address_1, n.CustomProperties.Address_City FROM Orion.Nodes n WHERE n.Caption = 'DVARNUM_2960'")
/usr/local/lib/python2.7/site-packages/requests/packages/urllib3/connectionpool.py:838: InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/security.html
 InsecureRequestWarning)
>>>

Ah, and I received the unverified SSL error. Let’s ignore these for now by running the command below.

>>> 
>>> requests.packages.urllib3.disable_warnings()
>>> 

Try our query again

>>> swis.query("SELECT n.Uri, n.CustomProperties._Location, n.CustomProperties.Address_1, n.CustomProperties.Address_City FROM Orion.Nodes n WHERE n.Caption = 'DVARNUM_2960'")
{u'results': [{u'Address_City': u'NMS', u'_Location': u'Lab', u'Uri': u'swis://orion/Orion/Orion.Nodes/NodeID=7500', u'Address_1': u'1337 Haxor Ln'}]}

And there we have it! Our results in JSON, showing the Address_City as ‘NMS’, the _Location as ‘Lab’ and Address_1 as ‘1337 Haxor Ln’.

Use SwisClient to ‘read’

If I wanted a list of all custom properties, I could simply use the read method. Reading uses a GET method, and returns the data in JSON.

>>> swis.read('swis://orion/Orion/Orion.Nodes/NodeID=7500/CustomProperties')
(Results removed for privacy purpose)

Use SwisClient to ‘update’

Querying is fun an all, but sometimes we want to make changes and updates to nodes. Suppose I built a new super secret lab, and I’m moving my Cisco switch to the new location. I can execute an update to accomplish this. First, let’s query again to check the current location of my switch.

>>> swis.query("SELECT n.Uri, n.CustomProperties._Location, n.CustomProperties.Address_1, n.CustomProperties.Address_City FROM Orion.Nodes n WHERE n.Caption = 'DVARNUM_2960'")
{u'results': [{u'Address_City': u'NMS', u'_Location': u'Lab', u'Uri': u'swis://orion/Orion/Orion.Nodes/NodeID=7500', u'Address_1': u'1337 Haxor Ln'}]}
>>>

It’s still located in the ‘Lab’. Let’s change this location by invoking the ‘update’ method.

>>> 
>>> swis.update('swis://orion/Orion/Orion.Nodes/NodeID=7500/CustomProperties', _Location='Super Secret Lab')
>>>

Query again to see the update.

>>> swis.query("SELECT n.Uri, n.CustomProperties._Location, n.CustomProperties.Address_1, n.CustomProperties.Address_City FROM Orion.Nodes n WHERE n.Caption = 'DVARNUM_2960'")
{u'results': [{u'Address_City': u'NMS', u'_Location': u'Super Secret Lab', u'Uri': u'swis://orion/Orion/Orion.Nodes/NodeID=7500', u'Address_1': u'1337 Haxor Ln'}]}

Add a node to Orion using Python

For the next example, I’m going to show you how to add a node to Orion using Python. This is one of countless tasks you can automate, but I feel it is an important and interesting one. Again, SolarWinds provides you with a sample Python script you can build upon. This can be found here, called add_node.py.

The sample script adds a device with SNMPv2. This works just fine, but most environments these days use SNMPv3. That said, I’m going to modify this script to use my preferred SNMPv3 method. Additionally, I’d like to add some Custom Properties to my node, so I’ll append that to the script as well.

Much like the earlier example, I prefer the Python interpreter for testing Python syntax. We’ll hop on here first to run through the example. Open Python and load the necessary libraries

~/Documents/github/orionsdk-python/orionsdk$ python
>>> from __future__ import print_function
>>> import re
>>> import requests
>>> from orionsdk import SwisClient
>>>

Disable SSL warnings (if necessary)

>>> requests.packages.urllib3.disable_warnings()
>>>

Configure your NPM server, username and password. We can always mask and encrypt this in an actual script, but for now we’ll just load it into the terminal for sake of simplicity.

>>> npm_server = 'orion'
>>> username = 'labuser'
>>> password = 'x'

Next we invoke the SwisClient, which connects to the NPM server.

>>> swis = SwisClient(npm_server, username, password)
>>>

Setup your node properties, such as the IP address and SNMP information. If you’re running SNMPv2, just use the example provided by SolarWinds.

>>> ip_address = '10.10.0.42'
>>> 
>>> props = {
... 'IPAddress': ip_address,
... 'EngineID': 5,
... 'ObjectSubType': 'SNMP',
... 'SNMPVersion': 3,
... 'Caption': 'DVARNUM_2960',
... 'SNMPV3AuthKey': 'MyAuthKey33',
... 'SNMPv3AuthKeyIsPwd': True,
... 'SNMPv3AuthMethod': 'SHA1',
... 'SNMPv3PrivKey': 'MyPrivKey33',
... 'SNMPv3PrivKeyIsPwd': True,
... 'SNMPv3PrivMethod': 'AES128',
... 'SNMPV3Username': 'snmpgod'
... }

Invoke swis.create to create the node based on the properties entered above.

>>> results = swis.create('Orion.Nodes', **props)

Extract the Node ID from the results.

>>> nodeid = re.search('(\d+)$', results).group(0)
>>> nodeid
u'7607'

Our new node was added to Orion and assigned Node ID 7607. Now we need to setup some pollers. This below is pulled straight from the SDK repo.

>>> pollers_enabled = {
...     'N.Status.ICMP.Native': True,
...     'N.Status.SNMP.Native': False,
...     'N.ResponseTime.ICMP.Native': True,
...     'N.ResponseTime.SNMP.Native': False,
...     'N.Details.SNMP.Generic': True,
...     'N.Uptime.SNMP.Generic': True,
...     'N.Cpu.SNMP.HrProcessorLoad': True,
...     'N.Memory.SNMP.NetSnmpReal': True,
...     'N.AssetInventory.Snmp.Generic': True,
...     'N.Status.SNMP.Native': False,
...     'N.ResponseTime.SNMP.Native': False,
...     'N.Topology_Layer3.SNMP.ipNetToMedia': False,
...     'N.Routing.SNMP.Ipv4CidrRoutingTable': False
... }
>>> pollers = []
>>> for k in pollers_enabled:
...     pollers.append(
...     {
...     'PollerType': k,
...     'NetObject': 'N:' + nodeid,
...     'NetObjectType': 'N',
...     'NetObjectID': nodeid,
...     'Enabled': pollers_enabled[k]
...     }
... )
... 
>>> for poller in pollers:
...     print(" Adding poller type: {} with status {}... ".format(poller['PollerType'], poller['Enabled']), end="")
...     response = swis.create('Orion.Pollers', **poller)
...     print("DONE!")
... 
 Adding poller type: N.Memory.SNMP.NetSnmpReal with status True... DONE!
 Adding poller type: N.ResponseTime.ICMP.Native with status True... DONE!
 Adding poller type: N.ResponseTime.SNMP.Native with status False... DONE!
 Adding poller type: N.Status.SNMP.Native with status False... DONE!
 Adding poller type: N.Cpu.SNMP.HrProcessorLoad with status True... DONE!
 Adding poller type: N.Routing.SNMP.Ipv4CidrRoutingTable with status False... DONE!
 Adding poller type: N.Uptime.SNMP.Generic with status True... DONE!
 Adding poller type: N.Details.SNMP.Generic with status True... DONE!
 Adding poller type: N.Topology_Layer3.SNMP.ipNetToMedia with status False... DONE!
 Adding poller type: N.Status.ICMP.Native with status True... DONE!
 Adding poller type: N.AssetInventory.Snmp.Generic with status True... DONE!
>>>

All of our pollers were successfully added to the node. If we pull up the GUI, we can see that our node is there but the status is unknown.

SolarWinds Orion API & SDK - Scripting with Python

My personal results have been hit and miss on this, but this could just be the default discovery interval waiting to triggered, and patience would bring our node online. Or, instead of waiting, we can force a poll using the ‘PollNow’ verb.

>>> swis.invoke('Orion.Nodes', 'PollNow', 'N' + nodeid)

And now we’re in business.

SolarWinds Orion API & SDK - Scripting with Python

Lastly, we want to add our custom properties. To do so, I create a Python dictionary with they key/value pairs, then iterate over it and ship the new values via swis.update. I’m going to use the same custom property values we used earlier, and in the previous article (link). First, notice our custom properties are currently blank on the new node.

>>> results
u'swis://orion/Orion/Orion.Nodes/NodeID=7607'
>>>
>>> custom_props = swis.read('swis://orion/Orion/Orion.Nodes/NodeID=7607/CustomProperties')
>>> pprint.pprint(updated_custom_props)
{u'Address_1': None,
 u'Address_2': None,
 u'Address_City': None,
 u'Address_State': None,
 u'Address_Zip': None
 ...truncated

I’ll create a dictionary of the custom properties I’d like to modify.

>>> custom_props = {
... 'Address_1': 'Lab',
... 'Address_2': '1337 Haxor Ln',
... 'Address_City': 'Suite API',
... 'Address_State': 'NMS',
... 'Address_Zip': '10101',
... }
>>>

Now, will rip through this dictionary and invoke swis.update to populate the fields.

>>> for k,v in custom_props.items():
...     swis.update(results + '/CustomProperties', **{k: v})
... 
>>>

Let’s read this back from Orion and print the results.

>>> updated_custom_props = swis.read('swis://orion/Orion/Orion.Nodes/NodeID=7607/CustomProperties')
>>> pprint.pprint(updated_custom_props)
{u'Address_1': u'Lab',
 u'Address_2': u'1337 Haxor Ln',
 u'Address_City': u'Suite API',
 u'Address_State': u'NMS',
 u'Address_Zip': u'10101',

There we are!!

Wrapping this up

Now that we know this works, we can wrap it up in a Python script. This particular script will ask you for the node IP and the node name. It will also prompt you to enter your password. The script will be at the bottom of this article. Here’s showing it in action:

~$ python add_node.py
Password: 
Enter the node IP: 10.10.0.42
Enter the node name: DVARNUM_2960
Adding node 10.10.0.42... DONE!
 Adding poller type: N.Memory.SNMP.NetSnmpReal with status True... DONE!
 Adding poller type: N.ResponseTime.ICMP.Native with status True... DONE!
 Adding poller type: N.ResponseTime.SNMP.Native with status False... DONE!
 Adding poller type: N.Status.SNMP.Native with status False... DONE!
 Adding poller type: N.Cpu.SNMP.HrProcessorLoad with status True... DONE!
 Adding poller type: N.Routing.SNMP.Ipv4CidrRoutingTable with status False... DONE!
 Adding poller type: N.Uptime.SNMP.Generic with status True... DONE!
 Adding poller type: N.Details.SNMP.Generic with status True... DONE!
 Adding poller type: N.Topology_Layer3.SNMP.ipNetToMedia with status False... DONE!
 Adding poller type: N.Status.ICMP.Native with status True... DONE!
 Adding poller type: N.AssetInventory.Snmp.Generic with status True... DONE!
 Adding custom property: Address_City with value Suite API... DONE!
 Adding custom property: Address_Zip with value 10101... DONE!
 Adding custom property: Address_1 with value Lab... DONE!
 Adding custom property: Address_2 with value 1337 Haxor Ln... DONE!
 Adding custom property: Address_State with value NMS... DONE!

And here’s our node:

SolarWinds Orion API & SDK - Scripting with Python

Take this baseline, get creative, and modify as needed for your environment.

Closing Thoughts

Here we’ve demonstrated the Pythonic approach to SolarWinds SDK.  There’s certainly much more you can do with this, and ultimately you’d want to secure it for your production environment.  Utilize the example scripts in GitHub and browse Thwack for answers to your queries.  If your goal is to develop automation, Python or PowerShell are the best options for you.  Poke around the GitHub repo and you’ll find PowerShell examples similar to the ones available in Python.

Stay tuned for future articles involving the SolarWinds SDK.

Python Add Node Script Example

from __future__ import print_function
import re
import requests
from orionsdk import SwisClient
import getpass

def main():
	requests.packages.urllib3.disable_warnings()
	
	npm_server = 'orion'
	username = 'labuser'
	password = getpass.getpass()

	swis = SwisClient(npm_server, username, password)
	
	# Add the node IP
	ip_address = raw_input("Enter the node IP: ")
	node_name = raw_input("Enter the node name: ")
	
	# Setup properties for the new node
	props = {
	    'IPAddress': ip_address,
	    'EngineID': 5,
	    'ObjectSubType': 'SNMP',
	    'SNMPVersion': 3,
	    'Caption': node_name,
	    'SNMPV3AuthKey': 'MyAuthKey33',
	    'SNMPv3AuthKeyIsPwd': True,
	    'SNMPv3AuthMethod': 'SHA1',
	    'SNMPv3PrivKey': 'MyPrivKey33',
	    'SNMPv3PrivKeyIsPwd': True,
	    'SNMPv3PrivMethod': 'AES128',
	    'SNMPV3Username': 'snmpgod'
	}
	
	# Create the node
	print("Adding node {}... ".format(props['IPAddress']), end="")
	results = swis.create('Orion.Nodes', **props)
	print("DONE!")
	
	# Extract nodeID from the results
	nodeid = re.search('(\d+)$', results).group(0)

	# Setup poller status for the node
	pollers_enabled = {
		'N.Status.ICMP.Native': True,
		'N.Status.SNMP.Native': False,
		'N.ResponseTime.ICMP.Native': True,
		'N.ResponseTime.SNMP.Native': False,
		'N.Details.SNMP.Generic': True,
		'N.Uptime.SNMP.Generic': True,
		'N.Cpu.SNMP.HrProcessorLoad': True,
		'N.Memory.SNMP.NetSnmpReal': True,
		'N.AssetInventory.Snmp.Generic': True,
		'N.Topology_Layer3.SNMP.ipNetToMedia': False,
		'N.Routing.SNMP.Ipv4CidrRoutingTable': False
	}

    # Add node to pollers
	pollers = []
	for k in pollers_enabled:
		pollers.append(
			{
				'PollerType': k,
				'NetObject': 'N:' + nodeid,
				'NetObjectType': 'N',
				'NetObjectID': nodeid,
				'Enabled': pollers_enabled[k]
			}
		)

	for poller in pollers:
		print("  Adding poller type: {} with status {}... ".format(poller['PollerType'], poller['Enabled']), end="")
		response = swis.create('Orion.Pollers', **poller)
		print("DONE!")

    # Poll the node
	swis.invoke('Orion.Nodes', 'PollNow', 'N' + nodeid)
	
	# Add custom properties
	custom_props = {
	    'Address_1': 'Lab',
	    'Address_2': '1337 Haxor Ln',
	    'Address_City': 'Suite API',
	    'Address_State': 'NMS',
	    'Address_Zip': '10101',
	}
	
	for k,v in custom_props.items():
		print("  Adding custom property: {} with value {}... ".format(k, v), end="")
		swis.update(results + '/CustomProperties', **{k: v})
		print("DONE!")
	
if __name__ == '__main__':
    main()