Package Products :: Package DataCollector :: Module Plugins |
[hide private]
[frames] | no frames]

# Source Code for Module Products.DataCollector.Plugins

      1  ###########################################################################
      2  #
      3  # This program is part of Zenoss Core, an open source monitoring platform.
      4  # Copyright (C) 2007-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 or (at your
      8  # option) any later version as published by the Free Software Foundation.
      9  #
     10  # For complete information please visit: http://www.zenoss.com/oss/
     11  #
     12  ###########################################################################
     13
     14  """
     15  Load modeling and monitoring plugins from standard locations and from
     16  ZenPacks.  Most of the entry into this module is via the three functions
     17  defined at the very bottom of the file. Those functions use the singleton
     18  PluginManager objects to load the plugins.
     19
     20  Classes -
     21      PluginImportError - an exception type
     22      PluginLoader - jellyable object that has all the information neccessary
     23                     to dynamically import the plugin module and instantiate the
     24                     class that shares the module's name
     25      CoreImporter - jellyable object that is injected into a PluginLoader.
     26                     handles importing of plugins found inside Products
     27      PackImporter - same as above but for zenpack plugins
     28      BaseLoaderFactory - base class for the two loader factories
     29      CoreLoaderFactory - generates the PluginLoaders for core plugins
     30      PackLoaderFactory - generates the PluginLoaders for zenpack plugins
     31      PluginManager - there is one singleton instance of this class for modeling
     32                      plugins and another for monitoring plugins
     33
     34  Note that modPath uses a different convention for core versus zenpack plugins.
     35
     36      core: zenoss.cmd.uname
     37      zenpack: ZenPacks.zenoss.AixMonitor.modeler.plugins.zenoss.cmd.uname
     38
     39  """
     40
     41  from Products.ZenUtils.Utils import importClass, zenPath
     42  import sys
     43  import os
     44  import exceptions
     45  import imp
     46  from twisted.spread import pb
     47  import logging
     48  log = logging.getLogger('zen.Plugins')
     49
     50 -class PluginImportError(exceptions.ImportError):
     51      """
     52      Capture extra data from plugin exceptions
     53      """
     54
     55 -    def __init__(self, plugin='', traceback='' ):
     56          """
     57          Initializer
     58
     59          @param plugin: plugin name
     60          @type plugin: string
     61          @param traceback: traceback from an exception
     62          @type traceback: traceback object
     63          """
     64          self.plugin = plugin
     65          self.traceback = traceback
     66          # The following is needed for zendisc
     67          self.args = traceback
     68
     69 -class PluginLoader(pb.Copyable, pb.RemoteCopy):
     70      """
     71      Class to load plugins
     72      """
     73
     74 -    def __init__(self, package, modPath, lastModName, importer):
     75          """
     76          package - '/'-separated absolute path to the root of the plugins
     77                    modules
     78          modPath - '.'-spearated module path.  for core plugins, it is rooted
     79                    at the package.  for zenpack plugins, it starts with
     80                    'ZenPacks'
     81          lastModName - name of the last module in modPath that is not part of
     82                    of the plugin name
     83          importer - object with an importPlugin method used to import the
     84                     plugin. the implementation of the import method differs
     85                     between core and zenpack plugins
     86          """
     87          self.package = package
     88          self.modPath = modPath
     89          self.pluginName = modPath.split(lastModName + '.')[-1]
     90          self.importer = importer
     91
     92 -    def create(self):
     93          """
     94          Load and compile the code contained in the given plugin
     95          """
     96          try:
     97              try:
     98                  # Modify sys.path (some plugins depend on this to import other
     99                  # modules from the plugins root)
    100                  sys.path.insert(0, self.package)
    101                  pluginClass = self.importer.importPlugin(self.package,
    102                                                           self.modPath)
    103                  return pluginClass()
    104              except (SystemExit, KeyboardInterrupt):
    105                  raise
    106              except:
    107                  import traceback
    108                  log.debug(traceback.format_exc())
    109                  raise PluginImportError(
    110                      plugin=self.modPath,
    111                      traceback=traceback.format_exc().splitlines())
    112          finally:
    113              try:
    114                  sys.path.remove(self.package)
    115              except ValueError:
    116                  # It's already been removed
    117                  pass
    118
    119  pb.setUnjellyableForClass(PluginLoader, PluginLoader)
    120
    121 -def _coreModPaths(walker, package):
    122      "generates modPath strings for the modules in a core directory"
    123      for absolutePath, dirname, filenames in walker.walk(package):
    124          if absolutePath == package:
    125              modPathBase = []
    126          elif absolutePath.startswith(package):
    127              modPathBase = absolutePath[len(package)+1:].split(os.path.sep)
    128          else:
    129              log.debug('absolutePath must start with package: '
    130                        'absolutePath=%s, package=%s', absolutePath, package)
    131              continue
    132          for filename in filenames:
    133              if filename.endswith(".py") \
    134                      and filename[0] not in ('.', "_") \
    135                      and '#' not in filename \
    136                      and filename not in ('CollectorPlugin.py', 'DataMaps.py'):
    137                  yield '.'.join(modPathBase + [filename[:-3]])
    138
    139 -class OsWalker(object):
    140
    141 -    def walk(self, package):
    142          return os.walk(package)
    143
    144 -class CoreImporter(pb.Copyable, pb.RemoteCopy):
    145
    146 -    def importModule(self, package, modPath):
    147          fp = None
    148          # Load the plugins package using its path as the name to
    149          # avoid conflicts. slashes in the name are OK when using
    150          # the imp module.
    151          parts = modPath.split('.')
    152          path = package
    153          try:
    154              for partNo in range(1,len(parts)+1):
    155                  part = parts[partNo-1]
    156                  fp, path, description = imp.find_module(part,[path])
    157                  modSubPath = '.'.join(parts[:partNo])
    158                  mod = imp.load_module(modSubPath, fp, path, description)
    159          finally:
    160              if fp:
    161                  fp.close()
    162          return mod
    163
    164 -    def importPlugin(self, package, modPath):
    165          parts = modPath.split('.')
    166          # class name is same as module name
    167          clsname = parts[-1]
    168          mod = self.importModule(package, modPath)
    169          return getattr(mod, clsname)
    170
    171  pb.setUnjellyableForClass(CoreImporter, CoreImporter)
    172
    173 -class PackImporter(pb.Copyable, pb.RemoteCopy):
    174
    175 -    def importModule(self, package, modPath):
    176          modulePath = modPath
    177          try:
    178              classname = modulePath.split(".")[-1]
    179              try:
    180                  __import__(modulePath, globals(), locals(), classname)
    181                  mod = sys.modules[modulePath]
    182              except (ValueError, ImportError, KeyError), ex:
    183                  raise ex
    184
    185              return mod
    186          except AttributeError:
    187              raise ImportError("Failed while importing module %s" % (
    188                      modulePath))
    189
    190 -    def importPlugin(self, package, modPath):
    191          # ZenPack plugins are specified absolutely; we can import
    192          # them using the old method
    193          return importClass(modPath)
    194
    195  pb.setUnjellyableForClass(PackImporter, PackImporter)
    196
    197 -class BaseLoaderFactory(object):
    198
    199 -    def __init__(self, walker):
    200          self.walker = walker
    201
    202 -    def genLoaders(self, package, lastModName):
    203          for coreModPath in _coreModPaths(self.walker, package):
    204              yield self._createLoader(package, coreModPath, lastModName)
    205
    206 -class CoreLoaderFactory(BaseLoaderFactory):
    207
    208 -    def _createLoader(self, package, coreModPath, lastModName):
    209          return PluginLoader(package, coreModPath, lastModName, CoreImporter())
    210
    211 -class PackLoaderFactory(BaseLoaderFactory):
    212
    213 -    def __init__(self, walker, modPathPrefix):
    214          BaseLoaderFactory.__init__(self, walker)
    215          self.modPathPrefix = modPathPrefix
    216
    217 -    def _createLoader(self, package, coreModPath, lastModName):
    218          packModPath = '%s.%s' % (self.modPathPrefix, coreModPath)
    219          return PluginLoader(package, packModPath, lastModName, PackImporter())
    220
    221 -class PluginManager(object):
    222      """
    223      Manages plugin modules.  Finds plugins and returns PluginLoader instances.
    224      Keeps a cache of previously loaded plugins.
    225      """
    226
    227 -    def __init__(self, lastModName, packPath, productsPaths):
    228          """
    229          Adds PluginLoaders for plugins in productsPaths to the pluginLoaders
    230          dictionary.
    231
    232          lastModName - the directory name where the plugins are found.  this name
    233                    is appended to the following paths
    234          packPath - path to the directory that holds the plugin modules inside
    235                     a zenpack. this path is relative to the zenpack root
    236          productsPaths - list of paths to directories that hold plugin
    237                     modules. these paths are relative to $ZENHOME/Products
    238
    239          a 'path', as used here, is a tuple of directory names
    240          """
    241          self.pluginLoaders = {} # PluginLoaders by module path
    242          self.loadedZenpacks = [] # zenpacks that have been processed
    243          self.lastModName = lastModName
    244          self.packPath = packPath
    245          for path in productsPaths:
    246              package = zenPath(*('Products',) + path + (lastModName,))
    247              self._addPluginLoaders(CoreLoaderFactory(OsWalker()), package)
    248
    249 -    def getPluginLoader(self, packs, modPath):
    250          """
    251          Get the PluginLoader for a specific plugin.
    252
    253          packs - list of installed zenpacks (ZenPack instances)
    254          modPath - the module path of the plugin
    255          """
    256          if modPath not in self.pluginLoaders:
    257              self.getPluginLoaders(packs)
    258          if modPath in self.pluginLoaders:
    259              return self.pluginLoaders[modPath]
    260
    261 -    def getPluginLoaders(self, packs):
    262          """
    263          Add the PluginLoaders for the packs to the pluginLoaders dictionary.
    264          Return the values of that dictionary.
    265
    266          packs - list of installed zenpacks (ZenPack instances)
    267          """
    268          try:
    269              for pack in packs:
    270                  if pack.moduleName() not in self.loadedZenpacks:
    271                      self.loadedZenpacks.append(pack.moduleName())
    272                      modPathPrefix = '.'.join((pack.moduleName(),) +
    273                              self.packPath + (self.lastModName,))
    274                      factory = PackLoaderFactory(OsWalker(), modPathPrefix)
    275                      package = pack.path(*self.packPath + (self.lastModName,))
    276                      self._addPluginLoaders(factory, package)
    277          except:
    278              log.error('Could not load plugins from ZenPacks.'
    279                        ' One of the ZenPacks is missing or broken.')
    280              import traceback
    281              log.debug(traceback.format_exc())
    282          return self.pluginLoaders.values()
    283
    284 -    def _addPluginLoaders(self, loaderFactory, package):
    285          log.debug("Loading collector plugins from: %s", package)
    286          try:
    287              loaders = loaderFactory.genLoaders(package, self.lastModName)
    288              for loader in loaders:
    289                  self.pluginLoaders[loader.modPath] = loader
    290          except:
    291              log.error('Could not load plugins from %s', package)
    292              import traceback
    293              log.debug(traceback.format_exc())
    294
    295 -class ModelingManager(object):
    296      """
    297      this class is not intended to be instantiated. instead it is a place to
    298      hold a singleton instance of PluginManager without having them call the
    299      constructor when this module is imported.
    300      """
    301
    302      instance = None
    303
    304      @classmethod
    305 -    def getInstance(cls):
    306          if cls.instance is None:
    307              cls.instance = PluginManager(
    308                      lastModName='plugins',
    309                      packPath=('modeler',),
    310                      productsPaths=[('DataCollector',),
    311                                     ('ZenWin', 'modeler',)])
    312          return cls.instance
    313
    314 -class MonitoringManager(object):
    315      """
    316      this class is not intended to be instantiated. instead it is a place to
    317      hold a singleton instance of PluginManager without having them call the
    318      constructor when this module is imported.
    319      """
    320
    321      instance = None
    322
    323      @classmethod
    324 -    def getInstance(cls):
    325          if cls.instance is None:
    326              cls.instance = PluginManager(
    327                      lastModName='parsers',
    328                      packPath=(),
    329                      productsPaths=[('ZenRRD',)])
    330          return cls.instance
    331
    332 -def _loadPlugins(pluginManager, dmd):
    333      return pluginManager.getPluginLoaders(dmd.ZenPackManager.packs())
    334
    335 -def loadPlugins(dmd):
    336      "Get PluginLoaders for all the modeling plugins"
    337      return _loadPlugins(ModelingManager.getInstance(), dmd)
    338
    339 -def loadParserPlugins(dmd):
    340      "Get PluginLoaders for all the modeling plugins"
    341      return _loadPlugins(MonitoringManager.getInstance(), dmd)
    342
    343 -def getParserLoader(dmd, modPath):
    344      "Get a PluginLoader for the given monitoring plugin's module path"
    345      return MonitoringManager.getInstance().getPluginLoader(
    346              dmd.ZenPackManager.packs(), modPath)
    347