Overview
One of the more daunting tasks of managing a larger network is keeping track of all your devices – both physically, and from a network monitoring perspective. When I arrived on the job 3 years ago, the first major task I laid down for myself was implementing both an asset management system, as well as a network monitoring system, to ensure we always knew what we had, and if it was functioning properly.
I decided almost immediately that Drupal was the right candidate for the job of asset management. There are a number of commercial IT/helpdesk systems out there which work great, but they are usually fairly expensive with recurring licensing costs, and my history with them has always been shaky. Plus, I find myself not always using all the functionality I paid for. I knew with my Drupal experience, I could get something comparable up in almost no time – this is not a discredit to IT packages, but moreso the power of the Drupal framework.
Network Monitoring – Cue Zenoss
Now that I had the hardware DB taken care of, I needed a NMS for monitoring. Originally I was planning on Nagios, but a contractor who works for us (now friend) had introduced me to Zenoss, another open source alternative. Zenoss is awesome – is absolutely has its quirks, and is not the most intuitive system to learn, but once things are up and running it’s great – and tremendously powerful. So the choice was made.
Now – I had both pieces, but I absolutely hate entering data twice, and the interoperability guy in me loves integrating systems. So I decided to write a script that would sync our Drupal database with Zenoss. Drupal would serve as our master system, and any hardware we entered into it would automatically port over to Zenoss. Any changes or deletions we made (IP address, location, name, etc) would sync over as well.
The below script performs this synchronization. Some warnings up front – I’m not a Python guy by any means, I specifically learned it for this script, so I apologize for any slopping coding or obvious Python-y mistakes. I’ve tried to thoroughly comment it to document how to use it and how it works. Hopefully it can help some others out as well!
# Description: Sync devices to be monitored from Drupal to Zenoss # # Setup Work: Create a (or use an existing) content type that houses your hardware items to be monitored. # They should have CCK fields for the IP address of the device, the name, and the type of # device it is. The device type will determine the Zenoss class the script adds it to, and hence # the kind of monitoring it will receive (e.g. Linux server, switch, ping only, etc) # # Additionally, in Zenoss, create a custom property field that will house the nid of the Drupal # node. This serves as the foreign key and will be used to link the item in Drupal to its entry in Zenoss # # Usage: This script should be run from zendmd, and may be run once or periodically. We run it every 20 minutes from # a cron job. # It will create new entries in Zenoss for items not yet imported, delete ones that no longer exist in # Drupal (it will only delete ones that were originally imported from Drupal), and will update ones that have # been updated (type, IP, location, etc). # # Note: Excuse all the extra commits - we experienced some issues with data not being saved, and I threw some extra in # there - they're almost definitely not necessaryimport MySQLdb # Take a taxonomy term from Drupal identifying the type of monitoring to be done, # and convert it to the appropriate Zenoss class path. Update these to whatever terms # and Zenoss class paths that make sense for your environment. We setup ones for # Linux and Windows servers, switches, waps, UPSes, PDUes, etc, as can be seen. def getClassPath(passType): if passType.lower() == "windows": return "/Server/Windows" elif passType.lower() == "linux": return "/Server/Linux" elif passType.lower() == "switch": return "/Network/Switch" elif passType.lower() == "mwap": return "/Network/WAP/Managed" elif passType.lower() == "uwap": return "/Network/WAP/Unmanaged" elif passType.lower() == "ups": return "/Power/UPS" elif passType.lower() == "pdu": return "/Power/PDU" elif passType.lower() == "camera": return "/Camera" elif passType.lower() == "cphone": return "/Network/Telephone/Crash" elif passType.lower() == "sphone": return "/Network/Telephone/Standard" elif passType.lower() == "printer": return "/Printer" elif passType.lower() == "converter": return "/Network/Converter" elif passType.lower() == "ping": return "/Ping" return "/Ping" # Connect to Drupal's MySQL DB (Replace these values with the appropriate ones for your system) imsConn = MySQLdb.connect(DRUPAL_MYSQL_SERVER, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DB) imsCursor = imsConn.cursor() # Execute the query to grab all your items to be monitored. In our case, we have a node type called "hardware" that had CCK fields identifying the IP address, # the type of hardware (a taxonomy term that dictated the Zenoss class of the item - see getClassPath above), a physical location, etc. # You'll want to change the specific table/field names, but the inner join will probably stay, as you'll want to grab both the node and CCK fields that belong to it. imsCursor.execute(""" SELECT node.nid, content_type_hardware.field_hardware_dns_value, content_type_hardware.field_hardware_location_value, content_type_hardware.field_hardware_ip_value, content_type_hardware.field_hardware_monitor_type_value, content_type_hardware.field_hardware_switchname_value, content_type_hardware.field_hardware_switchport_value FROM node INNER JOIN content_type_hardware ON node.nid = content_type_hardware.nid """) # Loop through all returned records - Check for additions, changes, and removals while (1): #tempRow is our current hardware item record tempRow = imsCursor.fetchone() if tempRow == None: # No more entries, break out of loop break else: # Search Zenoss records for the nid of the hardware item. A custom field will need to be created in Zenoss to serve # as this foreign key. In our case, we used MHTIMSID - but you can use anything you'd like - just be sure to create the field in Zenoss. found = False for d in dmd.Devices.getSubDevices(): if d.cMHTIMSID != "": if int(d.cMHTIMSID) == tempRow[0]: found = True break if found == False: # Hardware item not found, add it if it's monitored if tempRow[4] != None: dmd.DeviceLoader.loadDevice(("%s.yourdomain.com" % tempRow[1]).lower(), getClassPath(tempRow[4]), "", "", # tag="", serialNumber="", "", "", "", # zSnmpCommunity="", zSnmpPort=161, zSnmpVer=None, "", 1000, "%s (%s - %s)" % (tempRow[2], tempRow[5], tempRow[6]), # rackSlot=0, productionState=1000, comments="", "", "", # hwManufacturer="", hwProductName="" (neither or both), "", "", # osManufacturer="", osProductName="" (neither or both). "", "", "", #locationPath="",groupPaths=[],systemPaths=[], "localhost", # performanceMonitor="localhost', "none") tempDevice = find(("%s.yourdomain.com" % tempRow[1]).lower()) tempDevice.setManageIp(tempRow[3]) commit() # Save nid to Zenoss record (to serve as foreign key) for syncing tempDevice._setProperty("cMHTIMSID","MHTIMS ID","string") tempDevice.cMHTIMSID = tempRow[0]; commit() else: # Hardware item found - delete, update, or do nothing if tempRow[4] == None: # Delete if not set to monitor dmd.Devices.removeDevices(d.id) else: # Update DNS and IP to current values if d.getDeviceName() != ("%s.yourdomain.com" % tempRow[1]).lower(): d.renameDevice(("%s.yourdomain.com" % tempRow[1]).lower()) if d.getManageIp() != tempRow[3]: d.setManageIp(tempRow[3]) commit() # Change class if not set to "Manual" (We setup a taxonomy term called "Manual" that would turn off automatic Zenoss class selection during syncing # and allow us to manually specificy the class of the device. if tempRow[4] != "Manual": d.changeDeviceClass(getClassPath(tempRow[4])) commit() # Update comments (location change) d.comments = "%s (%s - %s)" % (tempRow[2], tempRow[5], tempRow[6]) commit() # Save any missed changes commit() # Close connection to database imsCursor.close() imsConn.close()