Discussion:
API for adding/managing maintenance windows
Daniel Rich
2013-03-21 15:10:42 UTC
Permalink
Daniel Rich [http://community.zenoss.org/people/sjthespian] created the discussion

"API for adding/managing maintenance windows"

To view the discussion, visit: http://community.zenoss.org/message/72542#72542

--------------------------------------------------------------
I'm writing a script to allow us to schedule maintenance windows from the command line instead of using the web gui. I have a working script that runs on the Zenoss server, but I would really like to be able to run the script on any system rather than give users access to log into to the server. However, it doesn't look like the functions to do this are exposed through either the REST or JSON APIs. Does anyone have a way to do this?
--------------------------------------------------------------

Reply to this message by replying to this email -or- go to the discussion on Zenoss Community
[http://community.zenoss.org/message/72542#72542]

Start a new discussion in zenoss-users by email
[discussions-community-forums-zenoss--***@community.zenoss.org] -or- at Zenoss Community
[http://community.zenoss.org/choose-container!input.jspa?contentType=1&containerType=14&container=2003]
cgriebel
2013-03-25 21:03:30 UTC
Permalink
cgriebel [http://community.zenoss.org/people/cgriebel] created the discussion

"Re: API for adding/managing maintenance windows"

To view the discussion, visit: http://community.zenoss.org/message/72584#72584

--------------------------------------------------------------
You can set production state by calling a URL with zenoss credentials as follows.  Make sure you get the device path and device name correct:

Set a Windows server named "myserver" to maintenance production state
https://user:***@zenoss.yoursite.com/zport/dmd/Devices/Server/Windows/devices/myserver/setProdState?state=300 https://user:***@zenoss.yoursite.com/zport/dmd/Devices/Server/Windows/devices/myserver/setProdState?state=300 mailto:***@zenoss.yoursite.com ***@zenoss.yoursite.com/zport/dmd/Devices/Server/Windows/devices/myserver/setProdState?state=300

Set it back to production
https://user:***@zenoss.yoursite.com/zport/dmd/Devices/Server/Windows/devices/myserver/setProdState?state=1000 https://user:***@zenoss.yoursite.com/zport/dmd/Devices/Server/Windows/devices/myserver/setProdState?state=1000 mailto:***@zenoss.yoursite.com ***@zenoss.yoursite.com/zport/dmd/Devices/Server/Windows/devices/myserver/setProdState?state=1000
--------------------------------------------------------------

Reply to this message by replying to this email -or- go to the discussion on Zenoss Community
[http://community.zenoss.org/message/72584#72584]

Start a new discussion in zenoss-users by email
[discussions-community-forums-zenoss--***@community.zenoss.org] -or- at Zenoss Community
[http://community.zenoss.org/choose-container!input.jspa?contentType=1&containerType=14&container=2003]
cgriebel
2013-03-25 21:07:45 UTC
Permalink
cgriebel [http://community.zenoss.org/people/cgriebel] created the discussion

"Re: API for adding/managing maintenance windows"

To view the discussion, visit: http://community.zenoss.org/message/72586#72586

--------------------------------------------------------------
You can set production state by calling a URL with zenoss credentials as
follows.  Make sure you get the device path and device name correct:


Set a Windows server named "myserver" to maintenance
https : //user: mailto:***@zenoss.yoursite.com ***@zenoss.yoursite.com/zport/dmd/Devices/Server/Windows/devices/myserver/setProdState?state=300

Set it back to production
https : //user: mailto:***@zenoss.yoursite.com ***@zenoss.yoursite.com/zport/dmd/Devices/Server/Windows/devices/myserver/setProdState?state=1000
--------------------------------------------------------------

Reply to this message by replying to this email -or- go to the discussion on Zenoss Community
[http://community.zenoss.org/message/72586#72586]

Start a new discussion in zenoss-users by email
[discussions-community-forums-zenoss--***@community.zenoss.org] -or- at Zenoss Community
[http://community.zenoss.org/choose-container!input.jspa?contentType=1&containerType=14&container=2003]
Daniel Rich
2013-03-29 16:50:45 UTC
Permalink
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/72651#72651

--------------------------------------------------------------
But setting a production state is not the same as defining a maintentance window. Maint. windows are automated -- they will auto trigger at a set time and then go back to the prior state when the window is completed. While I know I can set the production state via REST or JSON, that doesn't actually solve the problem unless I am going to make my script setup at or cron jobs.
--------------------------------------------------------------

Reply to this message by replying to this email -or- go to the discussion on Zenoss Community
[http://community.zenoss.org/message/72651#72651]

Start a new discussion in zenoss-users at Zenoss Community
[http://community.zenoss.org/choose-container!input.jspa?contentType=1&containerType=14&container=2003]
cgriebel
2013-03-29 19:38:29 UTC
Permalink
cgriebel [http://community.zenoss.org/people/cgriebel] created the discussion

"Re: API for adding/managing maintenance windows"

To view the discussion, visit: http://community.zenoss.org/message/72653#72653

--------------------------------------------------------------
You're right.  I didn't address what you're looking for.  What I gave you is a crude way to toggle device production state programmatically and outside the Zenoss interface.   Poor man's maintenance window, I suppose.

Chock
--------------------------------------------------------------

Reply to this message by replying to this email -or- go to the discussion on Zenoss Community
[http://community.zenoss.org/message/72653#72653]

Start a new discussion in zenoss-users at Zenoss Community
[http://community.zenoss.org/choose-container!input.jspa?contentType=1&containerType=14&container=2003]
Daniel Rich
2013-07-02 19:36:12 UTC
Permalink
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]
Loading...