Quantcast
Channel: Zenoss Community: Message List
Viewing all articles
Browse latest Browse all 783

Re: API for adding/managing maintenance windows

$
0
0

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']

Viewing all articles
Browse latest Browse all 783

Trending Articles