Package Products :: Package ZenModel :: Module BatchDeviceLoader |
[hide private]
[frames] | no frames]

# Source Code for Module Products.ZenModel.BatchDeviceLoader

      1  ###########################################################################
      2  #
      3  # This program is part of Zenoss Core, an open source monitoring platform.
      4  # Copyright (C) 2009, Zenoss Inc.
      5  #
      6  # This program is free software; you can redistribute it and/or modify it
      7  # under the terms of the GNU General Public License version 2 as published by
      8  # the Free Software Foundation.
      9  #
     10  # For complete information please visit: http://www.zenoss.com/oss/
     11  #
     12  ###########################################################################
     13  __doc__ = """BatchDeviceLoader.py
     14
     15  zenbatchload loads a list of devices read from a file.
     16  """
     17
     18  import sys
     19  import re
     20
     21  import Globals
     22  from transaction import commit
     23
     24  from Products.ZenUtils.ZCmdBase import ZCmdBase
     25  from Products.ZenRelations.ZenPropertyManager import iszprop
     26  from zExceptions import BadRequest
     27
     28
     29 -class BatchDeviceLoader(ZCmdBase):
     30      """
     31      Base class wrapping around dmd.DeviceLoader
     32      """
     33
     34      sample_configs = """#
     35  # Example zenbatchloader device file
     36  #
     37  # This file is formatted with one entry per line, like this:
     38  #
     39  #  /Devices/device_class_name Python-expression
     40  #  hostname Python-expression
     41  #
     42  # For organizers (ie the /Devices path), the Python-expression
     43  # is used to define defaults to be used for devices listed
     44  # after the organizer. The defaults that can be specified are:
     45  #
     46  #   * loader arguments (use the --show_options flag to show these)
     47  #   * zPropertie (from a device, use the More -> zProperties
     48  #      menu option to see the available ones.)
     49  #
     50  #      NOTE: new zProperties *cannot* be created through this file
     51  #
     52  #  The Python-expression is used to create a dictionary of settings.
     53  #  device_settings = eval( 'dict(' + python-expression + ')' )
     54  #
     55
     56
     57  # If no organizer is specified at the beginning of the file,
     58  # defaults to the /Devices/Discovered device class.
     59  device0 comments="A simple device"
     60  # All settings must be seperated by a comma.
     61  device1 comments="A simple device" zSnmpCommunity='blue', zSnmpVer='v1'
     62
     63  # Notes for this file:
     64  #  * Organizer names cannot contain spaces
     65  #  * Oraganizer names *must* start with '/'
     66  #
     67  /Devices/Server/Linux zSnmpPort=1543
     68  # Python strings can use either ' or " -- there's no difference.
     69  # As a special case, it is also possible to specify the IP address
     70  linux_device1 manageIp=10.10.10.77, zSnmpCommunity='blue', zSnmpVer="v2c"
     71  # A '\' at the end of the line allows you to place more
     72  # expressions on a new line. Don't forget the comma...
     73  linux_device2 discoverProto='none', zLinks="<a href='http://example.org'>Support site</a>",  \
     74  zTelnetEnable=True \
     75  zTelnetPromptTimeout=15.3
     76
     77  # A new organizer drops all previous settings, and allows
     78  # for new ones to be used.  Settings do not span files.
     79  /Devices/Server/Windows zWinUser="administrator", zWinPassword='fred'
     80  # Bind templates
     81  windows_device1 zDeviceTemplates=[ 'Device', 'myTemplate' ]
     82  # Override the default from the organizer setting.
     83  windows_device2 zWinUser="administrator", zWinPassword='thomas'
     84
     85  """
     86
     87 -    def __init__(self):
     88          ZCmdBase.__init__(self)
     89          self.defaults = {}
     90
     91          self.loader = self.dmd.DeviceLoader.loadDevice
     92
     93          # Create the list of options we want people to know about
     94          self.loader_args = dict.fromkeys( self.loader.func_code.co_varnames )
     95          unsupportable_args = [
     96              'REQUEST', 'device', 'self', 'xmlrpc', 'e', 'handler',
     97          ]
     98          for opt in unsupportable_args:
     99              if opt in self.loader_args:
    100                  del self.loader_args[opt]
    101
    102 -    def loadDeviceList(self, args=None):
    103          """
    104          Read through all of the files listed as arguments and
    105          return a list of device entries.
    106
    107          @parameter args: list of filenames (uses self.args is this is None)
    108          @type args: list of strings
    109          @return: list of device specifications
    110          @rtype: list of dictionaries
    111          """
    112          if args is None:
    113              args = self.args
    114
    115          device_list = []
    116          for filename in args:
    117              try:
    118                  data = open(filename,'r').readlines()
    119              except IOError:
    120                  self.log.critical("Unable to open the file '%s'" % filename)
    121                  continue
    122
    123              temp_dev_list = self.parseDevices(data)
    124              if temp_dev_list:
    125                  device_list += temp_dev_list
    126
    127          return device_list
    128
    129 -    def applyZProps(self, device, device_specs):
    130          """
    131          Apply zProperty settings (if any) to the device.
    132
    133          @parameter device: device to modify
    134          @type device: DMD device object
    135          @parameter device_specs: device creation dictionary
    136          @type device_specs: dictionary
    137          """
    138          self.log.debug( "Applying zProperties.." )
    139          # Returns a list of (key, value) pairs.
    140          # Convert it to a dictionary.
    141          dev_zprops = dict( device.zenPropertyItems() )
    142
    143          for zprop, value in device_specs.items():
    144              if not iszprop(zprop):
    145                 continue
    146
    147              if zprop in dev_zprops:
    148                  try:
    149                      device.setZenProperty(zprop, value)
    150                  except BadRequest:
    151                      self.log.warn( "Device %s zproperty %s is invalid or duplicate" % (
    152                         device_specs['deviceName'], zprop) )
    153              else:
    154                  self.log.debug( "The zproperty %s doesn't exist in %s" % (
    155                         zprop, device_specs['deviceName']))
    156
    157 -    def processDevices(self, device_list):
    158          """
    159          Read the input and process the devices
    160            * create the device entry
    161            * set zproperties
    162            * model the device
    163
    164          @parameter device_list: list of device entries
    165          @type device_list: list of dictionaries
    166          """
    167          processed = 0
    168          for device_specs in device_list:
    169              devobj = self.getDevice(device_specs)
    170              if devobj is None:
    171                  continue
    172
    173              if device_specs.get('manageIp', ''):
    174                  manageIp = device_specs['manageIp']
    175                  devobj.setManageIp(manageIp)
    176                  self.log.info("Setting %s IP address to '%s'",
    177                                devobj.id, manageIp)
    178
    179              self.applyZProps(devobj, device_specs)
    180
    181              # Default is discoverProto == 'snmp'
    182              if device_specs.get('discoverProto', '') != 'none':
    183                  # What if zSnmpCommunity isn't set in the file?
    184                  devobj.manage_snmpCommunity()
    185
    186                  # Make sure that ZODB has changes before modeling
    187                  commit()
    188                  try:
    189                      devobj.collectDevice(setlog=self.options.showModelOutput)
    190                  except (SystemExit, KeyboardInterrupt):
    191                      self.log.info("User interrupted modeling")
    192                      break
    193                  except Exception, ex:
    194                      self.log.exception("Modeling error" )
    195
    196              commit()
    197              processed += 1
    198
    199          self.log.info( "Processed %d of %d devices" % (processed, len(device_list)))
    200
    201 -    def getDevice(self, device_specs):
    202          """
    203          Find or create the specified device
    204
    205          @parameter device_specs: device creation dictionary
    206          @type device_specs: dictionary
    207          @return: device or None
    208          @rtype: DMD device object
    209          """
    210          if 'deviceName' not in device_specs:
    211              return None
    212          name = device_specs['deviceName']
    213          devobj  = self.dmd.Devices.findDevice(name)
    214          if devobj is not None:
    215              self.log.info("Found existing device %s" % name)
    216              return devobj
    217
    218          specs = {}
    219          for key in self.loader_args:
    220              if key in device_specs:
    221                  specs[key] = device_specs[key]
    222
    223          try:
    224              self.log.info("Creating device %s" % name)
    225
    226              # Do NOT model at this time
    227              specs['discoverProto'] = 'none'
    228
    229              self.loader(**specs)
    230              devobj  = self.dmd.Devices.findDevice(name)
    231              if devobj is None:
    232                  self.log.error("Unable to find newly created device %s -- skipping" \
    233                                % name)
    234          except Exception, ex:
    235              self.log.exception("Unable to load %s -- skipping" % name )
    236
    237          return devobj
    238
    239 -    def buildOptions(self):
    240          """
    241          Add our command-line options to the basics
    242          """
    243          ZCmdBase.buildOptions(self)
    244
    245          self.parser.add_option('--show_options',
    246              dest="show_options", default=False,
    247              action="store_true",
    248              help="Show the various options understood by the loader")
    249
    250          self.parser.add_option('--sample_configs',
    251              dest="sample_configs", default=False,
    252              action="store_true",
    253              help="Show an example configuration file.")
    254
    255          self.parser.add_option('--showModelOutput',
    256              dest="showModelOutput", default=True,
    257              action="store_false",
    258              help="Show modelling activity")
    259
    260 -    def parseDevices(self, data):
    261          """
    262          From the list of strings in rawDevices, construct a list
    263          of device dictionaries, ready to load into Zenoss.
    264
    265          @parameter data: list of strings representing device entries
    266          @type data: list of strings
    267          @return: list of parsed device entries
    268          @rtype: list of dictionaries
    269          """
    270          if not data:
    271              return []
    272
    273          comment = re.compile(r'#.*')
    274
    275          defaults = {'devicePath':"/Discovered" }
    276          finalList = []
    277          i = 0
    278          while i < len(data):
    279              line = data[i]
    280              line = re.sub(comment, '', line).strip()
    281              if line == '':
    282                  i += 1
    283                  continue
    284
    285              # Check for line continuation character '\'
    286              while line[-1] == '\\' and i < len(data):
    287                  i += 1
    288                  line = line[:-1] + data[i]
    289                  line = re.sub(comment, '', line).strip()
    290
    291              if line[0] == '/': # Found an organizer
    292                  defaults = self.parseDeviceEntry(line, {})
    293                  defaults['devicePath'] = defaults['deviceName']
    294                  del defaults['deviceName']
    295
    296              else:
    297                  configs = self.parseDeviceEntry(line, defaults)
    298                  if configs:
    299                      finalList.append(configs)
    300              i += 1
    301
    302          return finalList
    303
    304 -    def parseDeviceEntry(self, line, defaults):
    305          """
    306          Build a dictionary of properties from one line's input
    307
    308          @parameter line: string containing one device's info
    309          @type line: string
    310          @parameter defaults: dictionary of default settings
    311          @type defaults: dictionary
    312          @return: parsed device entry
    313          @rtype: dictionary
    314          """
    315          options = None
    316          if line.find(' ') > 0:
    317              name, options = line.split(None, 1)
    318          else:
    319              name = line
    320
    321          configs = defaults.copy()
    322          configs['deviceName'] = name
    323
    324          if options:
    325              try:
    326                  configs.update( eval( 'dict(' + options + ')' ) )
    327              except:
    328                  self.log.error( "Unable to parse the entry for %s -- skipping" % name )
    329                  self.log.error( "Raw string: %s" % options )
    330                  return None
    331
    332          return configs
    333
    334
    335  if __name__=='__main__':
    336      batchLoader = BatchDeviceLoader()
    337
    338      if batchLoader.options.show_options:
    339          print "Options = %s" % sorted( batchLoader.loader_args.keys() )
    340          help(batchLoader.loader)
    341          sys.exit(0)
    342
    343      if batchLoader.options.sample_configs:
    344          print batchLoader.sample_configs
    345          sys.exit(0)
    346
    347      device_list = batchLoader.loadDeviceList()
    348      batchLoader.processDevices(device_list)
    349