Daniel Rich [http://community.zenoss.org/people/sjthespian] created the discussion
"Re: API for adding/managing maintenance windows"
To view the discussion, visit: http://community.zenoss.org/message/73828#73828
--------------------------------------------------------------
I have a solution that seems to meet my needs, the secret is to use *both* JSON and REST. The JSON API will let me get a list of devices, classes, groups, etc. from the server; and the REST API will let me add a maintenance window.
While it isn't beautiful and may make some assumptions about our environment that don't translate to others, here is what I ended up with (and comments on this script are appreciated). To use this you will need to at least update ZENOSS_INSTANCE in the code to point to your server URL.
#!/usr/bin/env python
#
# Use the Zenoss JSON API to schedule device or class maintenance windows
#
import re, string
import getopt, sys
import os, pwd
import time
import datetime
import dateutil.parser
import urllib
import urllib2, base64
import json
import getpass
from subprocess import call
debug=0
ROUTERS = { 'MessagingRouter': 'messaging',
           'EventsRouter': 'evconsole',
           'ProcessRouter': 'process',
           'ServiceRouter': 'service',
           'DeviceRouter': 'device',
           'NetworkRouter': 'network',
           'TemplateRouter': 'template',
           'DetailNavRouter': 'detailnav',
           'ReportRouter': 'report',
           'MibRouter': 'mib',
           'ZenPackRouter': 'zenpack' }
class ZenossAPI():
   def __init__(self, debug=False):
       """
       Initialize the API connection, log in, and store authentication cookie
       """
       # Use the HTTPCookieProcessor as urllib2 does not save cookies by default
       self.urlOpener = urllib2.build_opener(urllib2.HTTPCookieProcessor())
       if debug: self.urlOpener.add_handler(urllib2.HTTPHandler(debuglevel=1))
       self.reqCount = 1
      Â
       # Contruct POST params and submit login.
       loginParams = urllib.urlencode(dict(
               __ac_name = ZENOSS_USERNAME,
               __ac_password = ZENOSS_PASSWORD,
               submitted = 'true',
               came_from = ZENOSS_INSTANCE + '/zport/dmd'))
       self.urlOpener.open(ZENOSS_INSTANCE + '/zport/acl_users/cookieAuthHelper/login',
                           loginParams)
      Â
   def _router_request(self, router, method, data=[]):
       global debug
       if debug > 1:
           print "_router_request(%s, %s, %s)" % (router, method, str(data))
       if router not in ROUTERS:
           raise Exception('Router "' + router + '" not available.')
       # Contruct a standard URL request for API calls
       req = urllib2.Request(ZENOSS_INSTANCE + '/zport/dmd/' +
                             ROUTERS[router] + '_router')
       # NOTE: Content-type MUST be set to 'application/json' for these requests
       req.add_header('Content-type', 'application/json; charset=utf-8')
       # Convert the request parameters into JSON
       reqData = json.dumps([dict(
                   action=router,
                   method=method,
                   data=data,
                   type='rpc',
                   tid=self.reqCount)])
      Â
       # Increment the request count ('tid'). More important if sending multiple
       # calls in a single request
       self.reqCount += 1
       # Submit the request and convert the returned JSON to objects
       return json.loads(self.urlOpener.open(req, reqData).read())
   # Use the API cookie to request a REST URL
   def RESTRequest(self, url):
       req = urllib2.Request(url)
       return(self.urlOpener.open(req).read())
  Â
   def get_devices(self, deviceClass='/zport/dmd/Devices', limit=50, start=0, params=None):
       global debug
       if debug > 1:
           print "get_devices(deviceClass=%s,limit=%d,start=%d,params=%s)" % (deviceClass, limit, start, str(params))
       return self._router_request('DeviceRouter', 'getDevices',
                                   data=[{'uid': deviceClass,
                                          'params': params,
                                          'limit': limit,
                                          'start': start}])['result']
def usage(message=''):
   if message != '':
       print message
   print """Usage: zenoss_downtime -h|--hostname hostname|deviceClass[,hostname|deviceClass[...]] [-b|--start start_time] [-e|--end end_time] [-c|--comment comment] [-i|--instance zenoss_url] [-u|--username user] [-p|--password password]
\thostname may be a comma separated list of hosts,
\t\ta device class starting with a '/Devices',
\t\ta group starting with /Groups,
\t\tor a location starting with /Locations
\tstart and end times may be any valid time, for example '12/24/2013 10:00'
\tcomment should be the reason for the downtime
example: zenoss_downtime -h /Devices -s 12:00 -e 15:00 -c "Short PM window"
"""
   sys.exit(3)
# Parse a time value, reading from stdin if it cannot be parsed
def getTime(value=0, prompt=''):
   returnTime = 0;
   while not returnTime:
       if not value:
           value = raw_input(prompt + ': ')
       if value == "now":     # Allow for "now" as a valid time
           returnTime = datetime.datetime.now()
       else:
           try:
               returnTime = dateutil.parser.parse(value)
           except:
               print "ERROR: Cannot parse %s from %s" % (prompt.lower(), a)
       if returnTime < datetime.datetime.now():
           print "ERROR: time cannot be in the past: %s" % returnTime
           returnTime = 0
           value = ''
   return(returnTime)
# Get devices
classList = []Â Â Â Â Â Â Â Â Â Â Â Â Â # List of device classes
deviceList = []Â Â Â Â Â Â Â Â Â Â Â Â # List of devices
deviceUID = {}Â Â Â Â Â Â Â Â Â Â Â Â Â # Dict of device UIDs
nextDev = 0Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â # Starting device for get_devices()
lastDev = 1Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â # Total devices to stop loop
devices = []
startTime = 0
endTime = 0
duration = 0
comment = ''
# Parse command line
try:
   options, args = getopt.getopt(sys.argv[1:],
                                 "d:h:s:e:c:i:u:p:D:",
                                 ["deviceclass=", "hostname=", "start=", "end=", "comment=", "instance=", "username=", "password=", "debug="],
                                 )
except getopt.GetoptError, err:
   usage(str(err))
   sys.exit(3)
      Â
# Zenoss connection info, including username and password
ZENOSS_INSTANCE = 'http://zenoss.example.com:8080'
ZENOSS_USERNAME = pwd.getpwuid(os.getuid()).pw_name
ZENOSS_PASSWORD = ''Â Â Â Â Â Â Â Â Â Â Â # if not in the args, it will prompt
for o, a in options:
   if o in ("-h", "--hostname"):
       devices = a.split(',')
   elif o in ("-D", "--debug"):
       debug = a
   elif o in ("-c", "--comment"):
       comment = a
   elif o in ("-s", "--start"):
       if a == "now":     # Allow for "now" as a valid time
           startTime = datetime.datetime.now()
       else:
           try:
               startTime = dateutil.parser.parse(a)
           except:
               usage("Cannot parse start time from %s" % a)
   elif o in ("-e", "--end"):
       try:
           endTime = dateutil.parser.parse(a)
       except:
           usage("Cannot parse end time from %s" % a)
   elif o in ("-u", "--instance"):
       ZENOSS_INSTANCE = a
   elif o in ("-u", "--username"):
       ZENOSS_USERNAME = a
   elif o in ("-p", "--password"):
       ZENOSS_PASSWORD = a
if not ZENOSS_PASSWORD:
   ZENOSS_PASSWORD = getpass.getpass('Password: ')
z = ZenossAPI()Â Â Â Â Â Â Â Â Â Â Â Â # Create API instance
# Get unspecified values
if not startTime:
   startTime = getTime(startTime,'Start time')
while not endTime or endTime < startTime:
   if endTime and (endTime <= startTime):
       print "ERROR: end time cannot be before start time!"
       endTime = 0
   endTime = getTime(endTime,'End time')
duration = endTime - startTime
while not comment:
   comment = raw_input('Reason for maintenance window: ')      Â
# Check for valid hostname/deviceclass
devUIDs = list()
devNames = dict()
for device in devices:
   nextDev = 0                # Starting device for get_devices()
   lastDev = 1                # Total devices to stop loop
   if re.search('^/',device): # It's a device class
       devJSON = z.get_devices(deviceClass='/zport/dmd/Devices' + device, limit=100, start=nextDev)
       if not 'devices' in devJSON or len(devJSON['devices']) <= 0:
           print("ERROR: unknown device: %s" % device)
           devices.remove(device)
       else:
           uid = '/zport/dmd/Devices' + device
           devUIDs.append(uid)
           devNames[uid] = { 'name': device.replace('/','_') }
   else:
       params = {'name': device}
       devJSON = z.get_devices(limit=100, start=nextDev, params=params)
       if not 'devices' in devJSON or len(devJSON['devices']) <= 0:
           print("ERROR: unknown device: %s" % device)
           devices.remove(device)
       else:
           devUIDs.append(devJSON['devices'][0]['uid'])
           devNames[devJSON['devices'][0]['uid']] = { 'name': device }
           devices[devices.index(device)] = devJSON['devices'][0]['name']
   if debug > 2:
       print json.dumps(devJSON, sort_keys=True,
                        indent=4, separators=(',', ': '))
if len(devUIDs) <= 0:
   usage("ERROR: you must specify at least one device or device class!");
      Â
if debug:
   print devUIDs
   print devNames
   print devices
   print startTime
   print duration
   print comment
# Use a REST call as per http://community.zenoss.org/thread/18835
# to schedule the maintenance window
timestamp = time.strftime("%Y%m%d%H%M%S",time.localtime(time.time()))
for uid in devUIDs:
   # Add the window
   #http://zenoss_host:8080/object_path/manage_addMaintenanceWindow?newId=maintenance_window_name
   addUrl = ZENOSS_INSTANCE + urllib.quote(uid + '/manage_addMaintenanceWindow') + '?newId=' + ZENOSS_USERNAME + '_' + timestamp + '_' + devNames[uid]['name']
   if debug > 1:
       print "Adding downtime with %s" % addUrl
   data = z.RESTRequest(addUrl)
# Update the window
#http://zenoss_host:8080/object_path/maintenanceWindows/maintenance_window_name/manage_editMaintenanceWindow?startDate=start_date,startHours=start_hour,startMinutes=start_min,durationDays=duration_days,durationHours=duration_hours,durationMinutes=duration_min
   editUrl = ZENOSS_INSTANCE + urllib.quote(uid + '/maintenanceWindows/' + ZENOSS_USERNAME + '_' + timestamp + '_' + devNames[uid]['name'] + '/manage_editMaintenanceWindow') + startTime.strftime('?startDate=%m/%d/%Y&startHours=%H&startMinutes=%M') + '&durationDays=' + str(duration.days) + '&durationHours=' + str(int(duration.seconds / 3600)) + '&durationMinutes=' + str(int(duration.seconds - (duration.seconds / 3600) * 3600) / 60)
   if debug > 1:
       print "Updating downtime with %s" % editUrl
   data = z.RESTRequest(editUrl)
   print 'Added maintenance window: ' + ZENOSS_USERNAME + '_' + timestamp + '_' + devNames[uid]['name']
--------------------------------------------------------------
Reply to this message by replying to this email -or- go to the discussion on Zenoss Community
[http://community.zenoss.org/message/73828#73828]
Start a new discussion in zenoss-users at Zenoss Community
[http://community.zenoss.org/choose-container!input.jspa?contentType=1&containerType=14&container=2003]