1
2
3
4
5
6
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 }
62
64
65 if isinstance(val, basestring):
66 return '"%s"' % val.replace('"', '\"')
67 else:
68 return val
69
75
81
87
93
100
101
102 negativeModes = (
103 '!',
104 '!~',
105 '!^',
106 )
109
110 return "'%s'" % "''".join(s.split("'"))
111
112 -class Error(Exception): pass
113
115 "Base class for converting to/from javascript"
116 type = 'unknown'
117
120
122 return '%s:{type:"%s",label:"%s"}' % (name, self.type, self.label)
123
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
197
199 return dict(self.options).get(value, 'Unknown')
200
202 return dict([(v, l) for l, v in self.options]).get(value, -1)
203
204 - def toJS(self, operator, value):
215
221
223 return '%s:{type:"%s",label:"%s", options:%r}' % (
224 name, self.type, self.label, [s[1] for s in self.options])
225
234
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
277
280
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
291
293 if mode == '':
294 return "%s like %s" % (name, q('%|' + v + '%'))
295 else:
296 return "%s not like %s" % (name, q('%|' + v + '%'))
297
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
330
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
344 "Convert to/from javascript and where clause elements for enumerated types"
345 type='cselect'
346
347 - def toJS(self, operator, value):
349
355
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 '''
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)
419
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
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
467 """
468 Exception thrown when a where clause fails conversion to a Python expression.
469 """
470 pass
471
473
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
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
511 if tree[0][0] in ('and', 'or'):
512 rule = rule[1:-1]
513 return rule
514
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