Package Products :: Package ZenEvents :: Module WhereClause |
[hide private]
[frames] | no frames]

# Source Code for Module Products.ZenEvents.WhereClause

      1  ##############################################################################
      2  #
      3  # Copyright (C) Zenoss, Inc. 2007, all rights reserved.
      4  #
      5  # This content is made available according to terms specified in
      6  # License.zenoss under the directory where your Zenoss product is installed.
      7  #
      8  ##############################################################################
      9
     10
     11  import types
     12  import logging
     13  from Products.ZenUtils.jsonutils import json
     14  from zenoss.protocols.protobufs import zep_pb2 as eventConstants
     15
     16  OLD_SYSLOG_MAPPING = {
     17      0: eventConstants.SYSLOG_PRIORITY_DEBUG,
     18      1: eventConstants.SYSLOG_PRIORITY_INFO,
     19      2: eventConstants.SYSLOG_PRIORITY_NOTICE,
     20      3: eventConstants.SYSLOG_PRIORITY_WARNING,
     21      4: eventConstants.SYSLOG_PRIORITY_ERR,
     22      5: eventConstants.SYSLOG_PRIORITY_CRIT,
     23      6: eventConstants.SYSLOG_PRIORITY_ALERT,
     24      7: eventConstants.SYSLOG_PRIORITY_EMERG,
     25  }
     26
     27  log = logging.getLogger('zen.WhereClause')
     28
     29  new_name_mapping = {
     30      'eventClass':'evt.event_class',
     31      'summary':'evt.summary',
     32      'message':'evt.message',
     33      'eventKey':'evt.event_key',
     34      'agent':'evt.agent',
     35      'manager':'evt.monitor',
     36      'severity':'evt.severity',
     37      'eventState':'evt.status',
     38      'count':'evt.count',
     39      'prodState':'dev.production_state',
     40      'device':'elem.name',
     41      'devicePriority':'dev.priority',
     42      'component':'sub_elem.name',
     43      'eventClassKey':'evt.event_class_key',
     44      'priority':'evt.syslog_priority',
     45      'facility':'evt.syslog_facility',
     46      'ntevid':'evt.nt_event_code',
     47      'ownerId':'evt.current_user_name',
     48      'deviceClass':'dev.device_class',
     49      'systems':'dev.systems',
     50      'deviceGroups':'dev.groups',
     51      'ipAddress':'dev.ip_address',
     52      'location':'dev.location',
     53  }
     54
     55
     56 -def getName(str):
     57      # lookup the old to new map
     58      if str in new_name_mapping:
     59          return new_name_mapping[str]
     60      # don't know a better way to error yet.
     61      return str
     62
     63 -def getValue(val):
     64      # escape things
     65      if isinstance(val, basestring):
     66          return '"%s"' % val.replace('"', '\"')
     67      else:
     68          return val
     69
     70 -def getEndsWith(name, value):
     71      return "{name}.endswith({value})".format(
     72              name=getName(name),
     73              value=getValue(value)
     74              )
     75
     76 -def getStartsWith(name, value):
     77      return "{name}.startswith({value})".format(
     78              name=getName(name),
     79              value=getValue(value)
     80              )
     81
     82 -def getIn(name, value):
     83      return "{value} in {name}".format(
     84              name=getName(name),
     85              value=getValue(value)
     86              )
     87
     88 -def getNotIn(name, value):
     89      return "{value} not in {name}".format(
     90              name=getName(name),
     91              value=getValue(value)
     92              )
     93
     94 -def getEquality(name, op, value):
     95      return "{name} {op} {value}".format(
     96              name=getName(name),
     97              op=op,
     98              value=getValue(value)
     99              )
    100
    101
    102  negativeModes = (
    103  '!', # is not
    104  '!~', # does not contain
    105  '!^',   # does not begin with
    106  )
    107
    108 -def q(s):
    109      # turn string "fo'o" -> "'fo''o'"
    110      return "'%s'" % "''".join(s.split("'"))
    111
    112 -class Error(Exception): pass
    113
    114 -class WhereJavaScript:
    115      "Base class for converting to/from javascript"
    116      type = 'unknown'
    117
    118 -    def __init__(self, label):
    119          self.label = label
    120
    121 -    def genProperties(self, name):
    122          return '%s:{type:"%s",label:"%s"}' % (name, self.type, self.label)
    123
    124 -    def buildClause(self, name, value, mode):
    125          foundNegativeMatch = False
    126          result = []
    127          for v in value:
    128              if not v: return None
    129              if mode in negativeModes: foundNegativeMatch = True
    130              result.append(self.buildClause1(name, v, mode))
    131          if not result:
    132              return None
    133          if foundNegativeMatch:
    134              return ' and '.join(result)
    135          else:
    136              return ' or '.join(result)
    137
    138 -class Text(WhereJavaScript):
    139      "Convert to/from javascript for text entries"
    140      type = 'text'
    141
    142 -    def toJS(self, mode, value):
    143          if mode == 'like':
    144              if value.startswith('%') and not value.endswith('%'):
    145                  return '$', [value[1:]]
    146              elif not value.startswith('%') and value.endswith('%'):
    147                  return '^', [value[:-1]]
    148              elif value.startswith('%') and value.endswith('%'):
    149                  return '~', [value[1:-1]]
    150          if mode == 'not like':
    151              return '!~', [value[1:-1]]
    152          if mode == '=':
    153              return '', [value]
    154          if mode == '!=':
    155              return '!', [value]
    156
    157 -    def buildPython(self, name, mode, value):
    158          if mode == 'like':
    159              if value.startswith('%') and not value.endswith('%'):
    160                  return getEndsWith(name, value[1:])
    161              elif not value.startswith('%') and value.endswith('%'):
    162                  return getStartsWith(name, value[:-1])
    163              elif value.startswith('%') and value.endswith('%'):
    164                  return getIn(name, value[1:-1])
    165          if mode == 'not like':
    166              return getNotIn(name, value[1:-1])
    167          if mode == '=':
    168              return getEquality(name, '==', value)
    169          if mode == '!=':
    170              return getEquality(name, '!=', value)
    171
    172 -    def buildClause1(self, name, v, mode):
    173          if mode == '~':
    174              return "%s like %s" % (name, q('%' + v + '%'))
    175          if mode == '^':
    176              return "%s like %s" % (name, q(v + '%'))
    177          if mode == '$':
    178              return "%s like %s" % (name, q('%' + v))
    179          if mode == '!~':
    180              return "%s not like %s" % (name, q('%' + v + '%'))
    181          if mode == '':
    182              return "%s = %s" % (name, q(v))
    183          if mode == '!':
    184              return "%s != %s" % (name, q(v))
    185
    186
    187 -class Select(WhereJavaScript):
    188      "Convert to/from javascript and where clause element for select entries"
    189      type = 'select'
    190
    191 -    def __init__(self, label, options):
    192          WhereJavaScript.__init__(self, label)
    193          if options:
    194              if type(options[0]) != type(()):
    195                  options = zip(range(len(options)), options)
    196          self.options = options
    197
    198 -    def labelFromValue(self, value):
    199          return dict(self.options).get(value, 'Unknown')
    200
    201 -    def valueFromLabel(self, value):
    202          return dict([(v, l) for l, v in self.options]).get(value, -1)
    203
    204 -    def toJS(self, operator, value):
    205          if operator == '=':
    206              return ('', [self.labelFromValue(value)])
    207          if operator == '!=':
    208              return ('!', [self.labelFromValue(value)])
    209          result = []
    210          if operator in ('<', '>', '<=', '>='):
    211              for i, name in self.options:
    212                  if eval('%d %s %d' % (i, operator, value)):
    213                      result.append(name)
    214          return ('', result)
    215
    216 -    def buildPython(self, name, operator, value):
    217          if operator == '=':
    218              return getEquality(name, '==', value)
    219          else:
    220              return getEquality(name, operator, value)
    221
    222 -    def genProperties(self, name):
    223          return '%s:{type:"%s",label:"%s", options:%r}' % (
    224              name, self.type, self.label, [s[1] for s in self.options])
    225
    226 -    def buildClause1(self, name, v, mode):
    227          v = self.valueFromLabel(v)
    228          if type(v) in types.StringTypes:
    229              v = q(v)
    230          if mode == '':
    231              return "%s = %s" % (name, v)
    232          else:
    233              return "%s != %s" % (name, v)
    234
    235 -    def buildMultiValuePython(self, name, mode, value):
    236
    237          value = value.strip('%|')
    238
    239          # if mode == 'like':
    240          #     if value.startswith('%') and not value.endswith('%'):
    241          #         return 'any(d.endswith({value}) for d in {name})'.format(value=getValue(bare_value), name=getName(name))
    242          #     elif not value.startswith('%') and value.endswith('%'):
    243          #         return 'any(d.startswith({value}) for d in {name})'.format(value=getValue(bare_value), name=getName(name))
    244          #     elif value.startswith('%') and value.endswith('%'):
    245          #         return 'any({value} in d for d in {name})'.format(value=getValue(bare_value), name=getName(name))
    246          #     else:
    247          #         return getIn(name, bare_value)
    248          #
    249          # if mode == 'not like':
    250          #     if value.startswith('%') and not value.endswith('%'):
    251          #         return 'not any(d.endswith({value}) for d in {name})'.format(value=getValue(bare_value), name=getName(name))
    252          #     elif not value.startswith('%') and value.endswith('%'):
    253          #         return 'not any(d.startswith({value}) for d in {name})'.format(value=getValue(bare_value), name=getName(name))
    254          #     elif value.startswith('%') and value.endswith('%'):
    255          #         return 'not any({value} in d for d in {name})'.format(value=getValue(bare_value), name=getName(name))
    256          #     else:
    257          #         return getNotIn(name, bare_value)
    258
    259          if mode == 'like':
    260              return getIn(name, value)
    261          if mode == 'not like':
    262              return getNotIn(name, value)
    263
    264
    265 -class Compare(WhereJavaScript):
    266      "Convert to/from javascript and where clause elements for numeric comparisons"
    267      type = 'compare'
    268
    269 -    def toJS(self, operator, value):
    270          return operator, [value]
    271
    272 -    def buildPython(self, name, operator, value):
    273          if operator == '=':
    274              return getEquality(name, '==', value)
    275          else:
    276              return getEquality(name, operator, value)
    277
    278 -    def buildClause1(self, name, v, mode):
    279          return "%s %s %s" % (name, mode, v)
    280
    281
    282 -class DeviceGroup(Select):
    283 -    def toJS(self, operator, value):
    284          if operator == 'like':
    285              return ['', [value[2:-1]]]
    286          if operator == 'not like':
    287              return ['!', [value[2:-1]]]
    288
    289 -    def buildPython(self, name, operator, value):
    290          return self.buildMultiValuePython(name, operator, value)
    291
    292 -    def buildClause1(self, name, v, mode):
    293          if mode == '':
    294              return "%s like %s" % (name, q('%|' + v + '%'))
    295          else:
    296              return "%s not like %s" % (name, q('%|' + v + '%'))
    297
    298 -class EventClass(Select):
    299      type = 'evtClass'
    300
    301 -    def toJS(self, operator, value):
    302          value = value.rstrip('%')
    303          if operator == '=':
    304              return ['', [value]]
    305          if operator == '!=':
    306              return ['!', [value]]
    307          if operator == 'like':
    308              return ['^', [value]]
    309          if operator == 'not like':
    310              return ['!^', [value]]
    311
    312 -    def buildPython(self, name, operator, value):
    313
    314          mode = operator
    315          if mode == 'like':
    316              if value.startswith('%') and not value.endswith('%'):
    317                  return getEndsWith(name, value[1:])
    318              elif not value.startswith('%') and value.endswith('%'):
    319                  return getStartsWith(name, value[:-1])
    320              elif value.startswith('%') and value.endswith('%'):
    321                  return getIn(name, value[1:-1])
    322              else:
    323                  return
    324          if mode == 'not like':
    325              return getNotIn(name, value[1:-1])
    326          if mode == '=':
    327              return getEquality(name, '==', value)
    328          if mode == '!=':
    329              return getEquality(name, '!=', value)
    330
    331 -    def buildClause1(self, name, v, mode):
    332          if mode == '':
    333              return "%s = %s" % (name, q(v))
    334          elif mode == '^':
    335              return "%s like %s" % (name, q(v + '%'))
    336          elif mode == '!^':
    337              return "%s not like %s" % (name, q(v + '%'))
    338          else:
    339              return "%s != %s" % (name, q(v))
    340
    341
    342
    343 -class Enumerated(Select):
    344      "Convert to/from javascript and where clause elements for enumerated types"
    345      type='cselect'
    346
    347 -    def toJS(self, operator, value):
    348          return operator, [self.labelFromValue(value)]
    349
    350 -    def buildPython(self, name, operator, value):
    351          if operator == '=':
    352              return getEquality(name, '==', value)
    353          else:
    354              return getEquality(name, operator, value)
    355
    356 -    def buildClause1(self, name, v, mode):
    357          return "%s %s %s" % (name, mode, self.valueFromLabel(v))
    358
    359  _Definitions = r'''
    360  def u(s):
    361      # turn string "'fo''o'" -> "fo'o"
    362      c = s[0]
    363      s = c.join(s.split(c+c))
    364      return s[1:-1]
    365
    366  '''
    367
    368  _ParseSpec = r'''
    369  parser WhereClause:
    370      ignore:    "[ \r\t\n]+"
    371      token END: "$"
    372      token NUM: "[0-9]+"
    373      token VAR: "[a-zA-Z0-9_]+"
    374      token BIN: ">=|<=|==|=|<|>|!=|<>"
    375      token STR: r'"([^\\"]+|\\.)*"'
    376      token STR2: r"'([^\\']+'{2,}|[^\\']+|\\.)*'"
    377
    378      rule goal:    andexp ? END       {{ return locals().get('andexp', None) }}
    379
    380      rule andexp:  orexp              {{ e = orexp }}
    381                    ( "and" orexp      {{ e = ('and', e, orexp) }}
    382                    )*                 {{ return e }}
    383
    384      rule orexp:   binary             {{ e = binary }}
    385                    ( "or" binary      {{ e = ('or', e, binary) }}
    386                    )*                 {{ return e }}
    387
    388      rule binary:  term               {{ e = term }}
    389                    ( BIN term         {{ e = (BIN, e, term) }}
    390                      | 'like' term    {{ e = ('like', e, term) }}
    391                      | 'not' 'like' term
    392                                       {{ e = ('not like', e, term) }}
    393                    )*                 {{ return e }}
    394
    395      rule term:    NUM                {{ return int(NUM) }}
    396                    | VAR              {{ return VAR }}
    397                    | STR              {{ return u(STR) }}
    398                    | STR2             {{ return u(STR2) }}
    399                    | "\\(" andexp "\\)" {{ return andexp }}
    400  '''
    401
    402 -class _Parser:
    403 -    def __init__(self, spec):
    404          from yapps import grammar, yappsrt
    405          from StringIO import StringIO
    406
    407          scanner = grammar.ParserDescriptionScanner(spec)
    408          parser = grammar.ParserDescription(scanner)
    409          parser = yappsrt.wrap_error_reporter(parser, 'Parser')
    410          parser.preparser = _Definitions
    411          parser.output = StringIO()
    412          parser.generate_output()
    413          exec parser.output.getvalue() in self.__dict__
    414
    415  where = _Parser(_ParseSpec)
    416
    417  @json
    418 -def toJavaScript(meta, clause):
    419      # sql is case insensitive, map column names to lower-case versions
    420      lmeta = dict([(n.lower(), n) for n in meta.keys()])
    421      tree = where.parse('goal', clause)
    422
    423      def recurse(root, result):
    424          if type(root) == types.TupleType:
    425              n = len(root)
    426              if n == 1:
    427                  recurse(root[0], result)
    428              op, name, value = root
    429              if op in ('and', 'or'):
    430                  recurse(root[1], result)
    431                  recurse(root[2], result)
    432              else:
    433                  name = lmeta.get(name.lower(), None)
    434                  if name is not None:
    435                      op, value = meta[name].toJS(op, value)
    436                      result.append([name, op, value])
    437
    438      result = []
    439      recurse(tree, result)
    440      result.sort()
    441      return result
    442
    443 -def collapse(tree):
    444      """
    445      Collapses adjacent and/ors into one statement.
    446      and(and(a, b), c) becomes and(a, b, c).
    447      """
    448      def _collapse(term, curopr=None, oprstack=None):
    449          op = term[0]
    450          if op == curopr:
    451              _collapse(term[1], op, oprstack)
    452              _collapse(term[2], op, oprstack)
    453          elif op in ('and', 'or'):
    454              s1, s2 = [], []
    455              _collapse(term[1], op, s1)
    456              _collapse(term[2], op, s2)
    457              combined = [op] + s1 + s2
    458              oprstack.append(combined)
    459          else:
    460              oprstack.append(term)
    461
    462      exprs = []
    463      _collapse(tree, None, exprs)
    464      return exprs
    465
    466 -class PythonConversionException(Exception):
    467      """
    468      Exception thrown when a where clause fails conversion to a Python expression.
    469      """
    470      pass
    471
    472 -def toPython(meta, clause):
    473      # sql is case insensitive, map column names to lower-case versions
    474      lmeta = dict([(n.lower(), n) for n in meta.keys()])
    475      tree = where.parse('goal', clause)
    476
    477      def recurse(root, result):
    478          if isinstance(root, (list, tuple)):
    479              op = root[0]
    480              if op in ('and', 'or'):
    481                  all_sub_results = []
    482                  for clause in root[1:]:
    483                      sub_result = []
    484                      recurse(clause, sub_result)
    485                      all_sub_results.extend(sub_result)
    486                  result.append('(' + (' %s ' % op).join(all_sub_results) + ')')
    487              else:
    488                  name, value = root[1], root[2]
    489                  orig_name = name.lower()
    490                  name = lmeta.get(orig_name, None)
    491                  if name is not None:
    492                      # Special case - ntevid must be convertable to integer and operator must be == or !=
    493                      if orig_name == 'ntevid':
    494                          if op not in ('=','!='):
    495                              raise PythonConversionException('Unable to migrate ntevid starts/ends-with clause')
    496                          try:
    497                              value = int(value)
    498                          except ValueError:
    499                              raise PythonConversionException('Failed to convert ntevid to integer')
    500                      if orig_name == 'priority':
    501                          value = OLD_SYSLOG_MAPPING[value]
    502
    503                      python_statement = meta[name].buildPython(name, op, value)
    504                      result.append('(%s)' % python_statement)
    505
    506      result = []
    507      tree = collapse(tree)
    508      recurse(tree[0], result)
    509      rule = result[0]
    510      # And's and Or's already add parentheses - we need to strip the outermost parens
    511      if tree[0][0] in ('and', 'or'):
    512          rule = rule[1:-1]
    513      return rule
    514
    515
    516 -def fromFormVariables(meta, form):
    517      result = []
    518      for n, attrType in meta.items():
    519          if form.has_key(n):
    520              value = form[n]
    521              if type(value) != type([]):
    522                  value = [value]
    523              mode = form.get(n + "_mode", None)
    524              clause = attrType.buildClause(n, value, mode)
    525              if clause:
    526                  result.append('(%s)' % clause)
    527      return ' and '.join(result)
    528
    529
    530  if __name__ == '__main__':
    531      meta = {}
    532      toJavaScript(meta, 'severity = 3 or severity = 4')
    533      toJavaScript(meta, "severity >= 4 and eventState = 0 and prodState = 1000")
    534      toJavaScript(meta, "severity >= 2 and eventState = 0 and prodState = 1000")
    535      print toJavaScript(meta, '(prodState = 1000) and (eventState = 0 or eventState = 1) and (severity >= 3)')
    536      print fromFormVariables(meta,
    537                              dict(severity='Info',
    538                                   severity_mode='>',
    539                                   eventState='New',
    540                                   eventState_mode='=',
    541                                   prodState='Production',
    542                                   prodState_mode='='))
    543