Package Products :: Package ZenUtils :: Module MultiPathIndex |
[hide private]
[frames] | no frames]

# Source Code for Module Products.ZenUtils.MultiPathIndex

      1  ###########################################################################
      2  #
      3  # This program is part of Zenoss Core, an open source monitoring platform.
      4  # Copyright (C) 2008, 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  from types import ListType, TupleType
     14  from BTrees.IIBTree import IISet
     15  import logging
     16  LOG = logging.getLogger('ZenUtils.MultiPathIndex')
     17
     18  from Globals import DTMLFile
     19
     20  from ExtendedPathIndex import ExtendedPathIndex
     21  from Products.PluginIndexes.common import safe_callable
     22  from BTrees.OOBTree import OOSet
     23
     24 -def _isSequenceOfSequences(seq):
     25      if not seq:
     26          return False
     27      if not isinstance(seq, (TupleType, ListType)):
     28          return False
     29      for item in seq:
     30          if not isinstance(item, (TupleType, ListType)):
     31              return False
     32      return True
     33
     34 -def _recursivePathSplit(seq):
     35      if isinstance(seq, (TupleType, ListType)):
     36          return map(_recursivePathSplit, seq)
     37      if '/' in seq:
     38          return seq.split('/')
     39      else:
     40          return seq
     41
     42 -class MultiPathIndex(ExtendedPathIndex):
     43      """
     44      A path index that is capable of indexing multiple paths per object.
     45      """
     46      meta_type = "MultiPathIndex"
     47
     48 -    def search(self, path, default_level=0, depth=-1, navtree=0,
     49                                                               navtree_start=0):
     50
     51          results = ExtendedPathIndex.search(self, path, default_level, depth, navtree,
     52                                                               navtree_start)
     53          # navtree is a special mode where you get the parents as well, obviously
     54          # we can not do our special path sanity check in this case
     55          if navtree or default_level:
     56              return results
     57          return self._pathSanityCheck(path, results)
     58
     59 -    def _pathSanityCheck(self, searchPath, pathset):
     60          """Extended path index uses a series of set "buckets" to store which items belong at
     61          which point on the search path, this can sometimes cause issues if two points on the path
     62          have the same name and thus belong to the same bucket
     63          For instance:
     64          /zport/dmd/Devices/Server/Windows/WMI/devices/test-winxp-1.zenoss.loc/os/software/VMware Tools
     65          has another path at
     66          /zport/dmd/Manfacturers/VMware/...
     67
     68          and our search returns a false positive because we search for
     69          /zport/dmd/Devices/VMware
     70
     71          because we have the id for that component returned at each point along the path.
     72
     73          So this function iterates through all the return paths and makes sure the path that we are searching
     74          for is in the path that is returned
     75          """
     76          # only workds for absolute paths not relative
     77          if not isinstance(searchPath, basestring) or not searchPath.startswith('/'):
     78              return pathset
     79          rids = IISet()
     80          if not pathset:
     81              return
     82          for rid in pathset:
     83              paths = self._unindex[rid]
     84              for path in paths:
     85                  if path.startswith(searchPath):
     86                      rids.insert(rid)
     87                      break
     88
     89          return rids
     90
     91 -    def getIndexSourceNames(self):
     92          """ return names of indexed attributes """
     93          return (self.id, )
     94
     95 -    def index_object(self, docid, obj, threshold=100):
     96          """ hook for (Z)Catalog """
     97
     98          f = getattr(obj, self.id, None)
     99          if f is not None:
    100              if safe_callable(f):
    101                  try:
    102                      paths = f()
    103                  except AttributeError:
    104                      return 0
    105              else:
    106                  paths = f
    107          else:
    108              try:
    109                  paths = obj.getPhysicalPath()
    110              except AttributeError:
    111                  return 0
    112
    113          if not paths: return 0
    114          paths = _recursivePathSplit(paths)
    115          if not _isSequenceOfSequences(paths):
    116              paths = [paths]
    117
    118          if docid in self._unindex:
    119              unin = self._unindex[docid]
    120              # Migrate old versions of the index to use OOSet
    121              if isinstance(unin, set):
    122                  unin = self._unindex[docid] = OOSet(unin)
    123              for oldpath in list(unin):
    124                  if list(oldpath.split('/')) not in paths:
    125                      self.unindex_paths(docid, (oldpath,))
    126          else:
    127              self._unindex[docid] = OOSet()
    128              self._length.change(1)
    129
    130          self.index_paths(docid, paths)
    131
    132          return 1
    133
    134
    135 -    def index_paths(self, docid, paths):
    136          for path in paths:
    137              if isinstance(path, (list, tuple)):
    138                  path = '/'+ '/'.join(path[1:])
    139              comps = filter(None, path.split('/'))
    140              parent_path = '/' + '/'.join(comps[:-1])
    141
    142              for i in range(len(comps)):
    143                  self.insertEntry(comps[i], docid, i)
    144
    145              # Add terminator
    146              self.insertEntry(None, docid, len(comps)-1, parent_path, path)
    147
    148              self._unindex.setdefault(docid, OOSet()).insert(path)
    149
    150
    151 -    def unindex_paths(self, docid, paths):
    152
    153          if not self._unindex.has_key(docid):
    154              return
    155
    156          def unindex(comp, level, docid=docid, parent_path=None,
    157                      object_path=None):
    158              try:
    159                  self._index[comp][level].remove(docid)
    160
    161                  if not self._index[comp][level]:
    162                      del self._index[comp][level]
    163
    164                  if not self._index[comp]:
    165                      del self._index[comp]
    166                  # Remove parent_path and object path elements
    167                  if parent_path is not None:
    168                      self._index_parents[parent_path].remove(docid)
    169                      if not self._index_parents[parent_path]:
    170                          del self._index_parents[parent_path]
    171                  if object_path is not None:
    172                      del self._index_items[object_path]
    173              except KeyError:
    174                  # Failure
    175                  pass
    176
    177          old = set(self._unindex.get(docid, ()))
    178          mkstr = lambda path:'/'.join(path) if isinstance(path, tuple) else path
    179          paths = map(mkstr, paths)
    180          toremove = set(paths) & old
    181          tokeep = old - toremove
    182          for path in toremove:
    183              if not path.startswith('/'):
    184                  path = '/'+path
    185              comps =  path.split('/')
    186              parent_path = '/'.join(comps[:-1])
    187
    188              for level in range(len(comps[1:])):
    189                  comp = comps[level+1]
    190                  unindex(comp, level, docid, parent_path, path)
    191              # Remove the terminator
    192              level = len(comps[1:])
    193              comp = None
    194              unindex(comp, level-1, parent_path=parent_path, object_path=path)
    195
    196              self._unindex[docid].remove(path)
    197
    198          if tokeep:
    199              self.index_paths(docid, tokeep)
    200          else:
    201              # Cleared out all paths for the object
    202              self._length.change(-1)
    203              del self._unindex[docid]
    204
    205
    206 -    def unindex_object(self, docid):
    207          """ hook for (Z)Catalog """
    208          if not self._unindex.has_key(docid):
    209              return
    210          self.unindex_paths(docid, self._unindex[docid])
    211
    212      manage = manage_main = DTMLFile('dtml/manageMultiPathIndex', globals())
    213      manage_main._setName('manage_main')
    214
    215
    216  manage_addMultiPathIndexForm = DTMLFile('dtml/addMultiPathIndex', globals())
    217
    218 -def manage_addMultiPathIndex(self, id, REQUEST=None, RESPONSE=None,
    219                               URL3=None):
    220      """
    221      Add a MultiPathIndex.
    222      """
    223      return self.manage_addIndex(id, 'MultiPathIndex', extra=None,
    224                                  REQUEST=REQUEST, RESPONSE=RESPONSE,
    225                                  URL1=URL3)
    226