The Assimilation Project  based on Assimilation version 1.1.7.1474836767
monitoring.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # vim: smartindent tabstop=4 shiftwidth=4 expandtab number colorcolumn=100
3 #
4 # This file is part of the Assimilation Project.
5 #
6 # Copyright (C) 2013,2014 Assimilation Systems Limited - author Alan Robertson <alanr@unix.sh>
7 #
8 # The Assimilation software is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # The Assimilation software is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with the Assimilation Project software. If not, see http://www.gnu.org/licenses/
20 #
21 '''
22 This file implements Monitoring-related classes.
23 
24 MonitorAction is a class that represents currently active monitoring actions.
25 
26 MonitoringRule and its subclasses implement the logic to automatically create monitoring
27 rules for certain kinds of services automatically.
28 '''
29 
30 
31 import os, re, time
32 import sys
33 from AssimCclasses import pyConfigContext
34 from frameinfo import FrameTypes, FrameSetTypes
35 from graphnodes import GraphNode, RegisterGraphClass
36 from graphnodeexpression import GraphNodeExpression, ExpressionContext
37 from assimevent import AssimEvent
38 from cmadb import CMAdb
39 from consts import CMAconsts
40 from store import Store
41 from AssimCtypes import REQCLASSNAMEFIELD, CONFIGNAME_TYPE, REQPROVIDERNAMEFIELD \
42 , REQENVIRONNAMEFIELD, CONFIGNAME_INSTANCE, REQREASONENUMNAMEFIELD, CONFIGNAME_INTERVAL \
43 , CONFIGNAME_TIMEOUT , REQOPERATIONNAMEFIELD, REQIDENTIFIERNAMEFIELD, REQNAGIOSPATH \
44 , CONFIGNAME_WARNTIME \
45 , REQRCNAMEFIELD, REQSIGNALNAMEFIELD, REQARGVNAMEFIELD, REQSTRINGRETNAMEFIELD \
46 , EXITED_TIMEOUT, EXITED_SIGNAL, EXITED_NONZERO, EXITED_HUNG, EXITED_ZERO, EXITED_INVAL
47 #
48 #
49 # too many instance attributes
50 # pylint: disable=R0902
51 @RegisterGraphClass
52 class MonitorAction(GraphNode):
53  '''Class representing monitoring actions
54  '''
55  request_id = time.time()
56  @staticmethod
58  'Return our key attributes in order of significance (sort order)'
59  return ['monitorname', 'domain']
60 
61 
62  # R0913: too many arguments
63  # pylint: disable=R0913
64  def __init__(self, domain, monitorname, monitorclass, monitortype, interval
65  , timeout, warntime=None, provider=None, arglist=None, argv=None):
66  'Create the requested monitoring rule object.'
67  GraphNode.__init__(self, domain)
68  self.monitorname = monitorname
69  self.monitorclass = monitorclass
70  self.monitortype = monitortype
71  self.interval = int(interval)
72  self.timeout = int(timeout)
73  self.warntime = int(warntime) if warntime is not None else self.timeout/2
74  self.provider = provider
75  self.isactive = False
76  self.isworking = True
77  self.reason = 'initial monitor creation'
78  self.request_id = MonitorAction.request_id
79  self.argv=argv
80  MonitorAction.request_id += 1
81  if arglist is None:
82  self.arglist = None
83  self._arglist = {}
84  elif isinstance(arglist, list):
85  listlen = len(arglist)
86  if (listlen % 2) != 0:
87  raise(ValueError('arglist list must have an even number of elements'))
88  self._arglist = {}
89  for j in range(0, listlen, 2):
90  self._arglist[arglist[j]] = arglist[j+1]
91  self.arglist = arglist
92  else:
93  self._arglist = arglist
94  self.arglist = []
95  for name in self._arglist:
96  self.arglist.append(name)
97  self.arglist.append(str(self._arglist[name]))
98  def longname(self):
99  'Return a long name for the type of monitoring this rule provides'
100  if self.provider is not None:
101  return '%s::%s:%s' % (self.monitorclass, self.provider, self.monitortype)
102  return '%s:%s' % (self.monitorclass, self.monitortype)
103 
104  def shortname(self):
105  'Return a short name for the type of monitoring this rule provides'
106  return self.monitortype
107 
108  def activate(self, monitoredentity, runon=None):
109  '''Relate this monitoring action to the given entity, and start it on the 'runon' system
110  Parameters
111  ----------
112  monitoredentity : GraphNode
113  The graph node which we are monitoring either a service or a ManagedSystem
114  runon : Drone
115  The particular Drone which is running this monitoring action.
116  Defaults to 'monitoredentity'
117  '''
118  from droneinfo import Drone
119  if runon is None:
120  runon = monitoredentity
121  assert isinstance(monitoredentity, GraphNode)
122  assert isinstance(runon, Drone)
123  CMAdb.store.relate_new(self, CMAconsts.REL_monitoring, monitoredentity)
124  CMAdb.store.relate_new(runon, CMAconsts.REL_hosting, self)
125  if self.monitorclass == 'NEVERMON':
126  # NEVERMON is a class that doesn't monitor anything
127  # A bit like the The Pirates Who Don't Do Anything
128  # So, we create the node in the graph, but don't activate it, don't
129  # send a message to the server to try and monitor it...
130  # And we never go to Boston in the fall...
131  CMAdb.log.info("Avast! Those Scurvy 'Pirates That Don't Do Anything'"
132  " spyed lounging around on %s"
133  % (str(runon)))
134  else:
135  reqjson = self.construct_mon_json()
136  CMAdb.transaction.add_packet(runon.destaddr(), FrameSetTypes.DORSCOP, reqjson
137  , frametype=FrameTypes.RSCJSON)
138  self.isactive = True
139  CMAdb.log.info('Monitoring of service %s activated' % (self.monitorname))
140 
141  def deactivate(self):
142  '''Deactivate this monitoring action. Does not remove relationships from the graph'''
143  from droneinfo import Drone
144  reqjson = self.construct_mon_json()
145  for drone in CMAdb.store.load_related(self, CMAconsts.REL_hosting, Drone):
146  CMAdb.transaction.add_packet(drone.primary_ip(), FrameSetTypes.STOPRSCOP
147  , reqjson, frametype=FrameTypes.RSCJSON)
148  self.isactive = False
149 
150 
151  findquery = None
152  @staticmethod
153  def find(name, domain=None):
154  'Iterate through a series of MonitorAction nodes matching the criteria'
155  if MonitorAction.findquery is None:
156  MonitorAction.findquery = 'START m=node:MonitorAction({q}) RETURN m'
157  name = Store.lucene_escape(name)
158  qvalue = '%s:%s' % (name, '*' if domain is None else domain)
159  return CMAdb.store.load_cypher_nodes(MonitorAction.findquery, MonitorAction
160  , params={'q': qvalue})
161 
162  @staticmethod
163  def find1(name, domain=None):
164  'Return the MonitorAction node matching the criteria'
165  for ret in MonitorAction.find(name, domain):
166  return ret
167  return None
168 
169  @staticmethod
170  def logchange(origaddr, monmsgobj):
171  '''
172  Make the necessary changes to the monitoring data when a particular
173  monitoring action changes status (to success or to failure)
174  This includes locating the MonitorAction object in the database.
175 
176  Parameters
177  ----------
178  origaddr: pyNetAddr
179  address where monitoring action originated
180  monmsgobj: pyConfigContext
181  object containing the monitoring message
182  '''
183 
184  rscname = monmsgobj[CONFIGNAME_INSTANCE]
185  monnode = MonitorAction.find1(rscname)
186  if monnode is None:
187  CMAdb.log.critical('Could not locate monitor node for %s from %s'
188  % (str(monmsgobj), str(origaddr)))
189  else:
190  monnode.monitorchange(origaddr, monmsgobj)
191 
192  def monitorchange(self, origaddr, monmsgobj):
193  '''
194  Make the necessary changes to the monitoring data when a particular
195  monitoring action changes status (to success or to failure)
196 
197  Parameters
198  ----------
199  origaddr: pyNetAddr
200  address where monitoring action originated
201  monmsgobj: pyConfigContext
202  object containing the monitoring message
203  '''
204  success = False
205  fubar = False
206  reason_enum = monmsgobj[REQREASONENUMNAMEFIELD]
207  if reason_enum == EXITED_ZERO:
208  success = True
209  explanation = 'is now operational'
210  elif reason_enum == EXITED_NONZERO:
211  explanation = 'monitoring failed with return code %s' % monmsgobj[REQRCNAMEFIELD]
212  if REQSTRINGRETNAMEFIELD in monmsgobj:
213  explanation += ': %s' % str(monmsgobj[REQSTRINGRETNAMEFIELD])
214  elif reason_enum == EXITED_SIGNAL:
215  explanation = 'monitoring was killed by signal %s' % monmsgobj[REQSIGNALNAMEFIELD]
216  elif reason_enum == EXITED_HUNG:
217  explanation = 'monitoring could not be killed'
218  elif reason_enum == EXITED_TIMEOUT:
219  explanation = 'monitoring timed out'
220  elif reason_enum == EXITED_INVAL:
221  explanation = 'invalid monitoring request'
222  else:
223  explanation = 'GOT REAL WEIRD (%d)' % int(reason_enum)
224  fubar = True
225  rscname = monmsgobj[CONFIGNAME_INSTANCE]
226  msg = 'Service %s %s' % (rscname, explanation)
227  self.isworking = success and not fubar
228  self.reason = explanation
229  print >> sys.stderr, 'MESSAGE:', msg
230  if fubar:
231  CMAdb.log.critical(msg)
232  else:
233  extrainfo = {'comment': explanation, 'origaddr': origaddr
234  , 'resourcename': rscname, 'monmsg': monmsgobj}
235  if success:
236  CMAdb.log.info(msg)
237  AssimEvent(self, AssimEvent.OBJUP, extrainfo=extrainfo)
238  else:
239  CMAdb.log.warning(msg)
240  AssimEvent(self, AssimEvent.OBJDOWN, extrainfo=extrainfo)
241 
242  def construct_mon_json(self, operation='monitor'):
243  '''
244  Parameters
245  ----------
246  Returns
247  ----------
248  JSON string representing this particular monitor action.
249  '''
250  if self.arglist is None:
251  arglist_str = ''
252  else:
253  arglist_str = ', "%s": {' % (REQENVIRONNAMEFIELD)
254  comma = ''
255  for arg in self._arglist:
256  arglist_str += '%s"%s":"%s"' % (comma, str(arg)
257  , str(self._arglist[arg]))
258  comma = ', '
259  arglist_str += '}'
260 
261  if self.provider is None:
262  provider_str = ''
263  else:
264  provider_str = ', "%s":"%s"' % (REQPROVIDERNAMEFIELD, self.provider)
265  path_str = ''
266  if hasattr(self, 'nagiospath'):
267  path_str = ', "%s": %s' % (REQNAGIOSPATH, getattr(self, 'nagiospath'))
268  path_str = path_str.replace("u'", '"')
269  path_str = path_str.replace("'", '"')
270  argv_str = ''
271  if self.argv is not None:
272  argv_str = ', "%s": %s' % (REQARGVNAMEFIELD, getattr(self, 'argv'))
273  argv_str = argv_str.replace("u'", '"')
274  argv_str = argv_str.replace("'", '"')
275 
276  json = (
277  '{"%s": %d, "%s":"%s", "%s":"%s", "%s":"%s", "%s":"%s", "%s":%d, "%s": %d,"%s":%d%s%s%s%s}'
278  %
279  ( REQIDENTIFIERNAMEFIELD, self.request_id
280  , REQOPERATIONNAMEFIELD, operation
281  , REQCLASSNAMEFIELD, self.monitorclass
282  , CONFIGNAME_TYPE, self.monitortype
283  , CONFIGNAME_INSTANCE, self.monitorname
284  , CONFIGNAME_INTERVAL, self.interval
285  , CONFIGNAME_TIMEOUT, self.timeout
286  , CONFIGNAME_WARNTIME, self.warntime
287  , provider_str, arglist_str, path_str, argv_str))
288  #print >> sys.stderr, 'MONITOR JSON:', str(pyConfigContext(init=json))
289  return str(pyConfigContext(init=json))
290 
291 
292 class MonitoringRule(object):
293  '''Abstract base class for implementing monitoring rules
294 
295  This particular class implements it for simple regex matching on arbitrary regexes
296  on fields on ProcessNodes or Drones -- in reality on any set of graph nodes.
297 
298  The question is how many subclasses should there be? It's obvious that we need to have
299  specialized subclasses for Java, Python and other interpreted languages.
300  What comes to mind to implement first is simple monitoring using LSB scripts.
301 
302  The reason for doing this is that they don't take any parameters, so all you have to do
303  is match the pattern and then know what init script name is used for that service on that OS.
304 
305  It does vary by Linux distribution, for example...
306  So... (cmd-pattern, arg-pattern, OS-pattern, distro-pattern) see to be the things that
307  uniquely determine what init script to use to monitor that particular resource type.
308 
309  The second question that comes to mind is how to connect these rules into a rule
310  hierarchy.
311 
312  For example, if the command name matches Java, then invoke the java rules.
313 
314  If a java rule matches Neo4j, then return the Neo4j monitoring action
315  and so on...
316 
317  Note that for Java, the monitoring rule is likely to match the _last_ argument
318  in the argument string...
319  arg[-1] - python-style
320 
321  If it matches python, then match it against the second argument (arg[1])
322 
323  '''
324  NOMATCH = 0 # Did not match this rule
325  NEVERMATCH = 1 # Matched this 'un-rule' - OK not to monitor this service
326  PARTMATCH = 2 # Partial match - we match this rule, but need more config info
327  LOWPRIOMATCH = 3 # We match - but we aren't a very good monitoring method
328  MEDPRIOMATCH = 4 # We match - but we could be a better monitoring method
329  HIGHPRIOMATCH = 5 # We match and we are a good monitoring method
330 
331  monitor_objects = {'service': {}, 'host': {}}
332 
333  def __init__(self, monitorclass, tuplespec, objclass='service'):
334  '''It is constructed from an list of tuples, each one of which represents
335  a value expression and a regular expression. Each value expression
336  is a specification to a GraphNode 'get' operation - or a more complex GraphNodeExpression.
337  Each regular expression is a specification of a regex to match against the
338  corresponding field expression.
339  By default, all regexes are anchored (implicitly start with ^)
340  This rule can only apply if all the RegExes match.
341  NOTE: It can still fail to apply even if the RegExes all match.
342  '''
343  if tuplespec is None or len(tuplespec) == 0:
344  raise ValueError('Improper tuplespec')
345 
346  self.monitorclass = monitorclass
347  self.objclass = objclass
348  self._tuplespec = []
349  if not(hasattr(self, 'nvpairs')):
350  self.nvpairs = {}
351  for tup in tuplespec:
352  if len(tup) < 2 or len(tup) > 3:
353  raise ValueError('Improperly formed constructor argument')
354  try:
355  if len(tup) == 3:
356  flags = tup[2]
357  regex = re.compile(tup[1], flags)
358  else:
359  regex = re.compile(tup[1])
360  except:
361  raise ValueError('Improperly formed regular expression: %s'
362  % tup[1])
363  self._tuplespec.append((tup[0], regex))
364 
365  # Register us in the grand and glorious set of all monitoring rules
366  if self.objclass not in self.monitor_objects:
367  raise ValueError('Monitor object class %s is not recognized' % self.objclass)
368  monrules = self.monobjclass(self.objclass)
369  if monitorclass not in monrules:
370  monrules[monitorclass] = []
371  monrules[monitorclass].append(self)
372 
373  @staticmethod
374  def monobjclass(mtype='service'):
375  'Return the monitoring objects that go with this service type'
376  return MonitoringRule.monitor_objects[mtype]
377 
378  def tripletuplecheck(self, triplespec):
379  'Validate and remember things for our child triple tuple specifications'
380  self.nvpairs = {}
381  tuplespec = []
382  for tup in triplespec:
383  tuplen = len(tup)
384  if tuplen == 4:
385  (name, expression, regex, flags) = tup
386  if regex is not None:
387  tuplespec.append((expression, regex, flags))
388  elif tuplen == 3:
389  (name, expression, regex) = tup
390  if regex is not None:
391  tuplespec.append((expression, regex))
392  elif tuplen == 2:
393  (name, expression) = tup
394  elif tuplen == 1:
395  name = tup[0]
396  expression = None
397  else:
398  raise ValueError('Invalid tuple length (%d)' % tuplen)
399  if name is not None and name != '-':
400  self.nvpairs[name] = expression
401  return tuplespec
402 
403 
404  def specmatch(self, context):
405  '''Return a MonitorAction if this rule can be applies in this context (GraphNodes)
406  Note that the GraphNodes that we're given at the present time are typically expected to be
407  the Drone node for the node it's running on and the Process node for the process
408  to be monitored.
409  We return (MonitoringRule.NOMATCH, None) on no match
410  '''
411  #print >> sys.stderr, 'SPECMATCH BEING EVALED:', self._tuplespec, self.__class__
412  for tup in self._tuplespec:
413  expression = tup[0]
414  value = GraphNodeExpression.evaluate(expression, context)
415  if value is None:
416  #print >> sys.stderr, 'NOMATCH from expression %s =>[%s]' % (expression, value)
417  return (MonitoringRule.NOMATCH, None)
418  # We now have a complete set of values to match against our regexes...
419  for tup in self._tuplespec:
420  name = tup[0]
421  regex = tup[1]
422  #CMAdb.log.debug('TUPLE BEING EVALED ("%s","%s")' % (name, regex.pattern))
423  val = str(GraphNodeExpression.evaluate(name, context))
424  #CMAdb.log.debug('EXPRESSION %s => %s' % (name, val))
425  #print >> sys.stderr, 'value, REGEX BEING EVALED ("%s","%s")' % (val, regex.pattern)
426  if not regex.match(val):
427  #print >> sys.stderr, 'NOMATCH from regex [%s] [%s]' % (regex.pattern, val)
428  #CMAdb.log.debug('NOMATCH from regex [%s] [%s]:%s'
429  # % (regex.pattern, val, type(val)))
430  return (MonitoringRule.NOMATCH, None)
431  # We now have a matching set of values to give our monitoring constructor
432  #print >> sys.stderr, 'CALLING CONSTRUCTACTION:', self._tuplespec
433  ret = self.constructaction(context)
434  #CMAdb.log.debug('GOT MATCH: CONSTRUCTACTION => %s' % str(ret))
435  return ret
436 
437 
438 
439  def constructaction(self, context):
440  '''Return a tuple consisting of a tuple as noted:
441  (MonitoringRule.PRIORITYVALUE, MonitorActionArgs, optional-information)
442  If PRIORITYVALUE is NOMATCH, the MonitorAction will be None -- and vice versa
443 
444  If PRIORITYVALUE IS PARTMATCH, then more information is needed to determine
445  exactly how to monitor it. In this case, the optional-information element
446  will be present and is a list the names of the fields whose values have to
447  be supplied in order to monitor this service/host/resource.
448 
449  If PRIORITYVALUE is LOWPRIOMATCH or HIGHPRIOMATCH, then there is no optional-information
450  element in the tuple.
451 
452  A LOWPRIOMATCH method of monitoring is presumed to do a minimal form of monitoring.
453  A HIGHPRIOMATCH method of monitoring is presumed to do a more complete job of
454  monitoring - presumably acceptable, and preferred over a LOWPRIOMATCH.
455  a NEVERMATCH match means that we know about this type of service, and
456  that it's OK for it to not be monitored.
457 
458  MonitorActionArgs is a hash table giving the values of the various arguments
459  that you need to give to the MonitorAction constuctor. It will always supply
460  the values of the monitorclass and monitortype argument. If needed, it will
461  also supply the values of the provider and arglist arguments.
462 
463  If PRIORITYVALUE is PARTMATCH, then it will supply an incomplete 'arglist' value.
464  and optional-information list consists of a list of those arguments whose values
465  cannot be determined automatically from the given value set.
466 
467  The caller still needs to come up with these arguments for the MonitorAction constructor
468  monitorname - a unique name for this monitoring action
469  interval - how often to run this monitoring action
470  timeout - what timeout to use before declaring monitoring failure
471 
472  Not that the implementation in the base class does nothing, and will result in
473  raising a NotImplementedError exception.
474  '''
475  raise NotImplementedError('Abstract class')
476 
477  @staticmethod
478  def ConstructFromString(s, objclass='service'):
479  '''
480  Construct a MonitoringRule from a string parameter.
481  It will construct the appropriate subclass depending on its input
482  string. Note that the input is JSON -- with whole-line comments.
483  A whole line comment has to _begin_ with a #.
484  '''
485  obj = pyConfigContext(s)
486  #print >> sys.stderr, 'CONSTRUCTING MONITORING RULE FROM', obj
487  if obj is None:
488  raise ValueError('Invalid JSON: %s' % s)
489  if 'class' not in obj:
490  raise ValueError('Must have class value')
491  legit = {'ocf':
492  {'class': True, 'type': True, 'classconfig': True, 'provider': True},
493  'lsb':
494  {'class': True, 'type': True, 'classconfig': True},
495  'nagios':
496  {'class': True, 'type': True, 'classconfig': True
497  , 'prio': True, 'initargs': True, 'objclass': True},
498  'NEVERMON':
499  {'class': True, 'type': True, 'classconfig': True}
500  }
501  rscclass = obj['class']
502  if rscclass not in legit:
503  raise ValueError('Illegal class value: %s' % rscclass)
504 
505  l = legit[obj['class']]
506  for key in l.keys():
507  if l[key] and key not in obj:
508  raise ValueError('%s object must have %s field' % (rscclass, key))
509  for key in obj.keys():
510  if key not in l:
511  raise ValueError('%s object cannot have a %s field' % (rscclass, key))
512 
513  objclass = obj.get('objclass', 'service')
514  if rscclass == 'lsb':
515  return LSBMonitoringRule(obj['type'], obj['classconfig'])
516  if rscclass == 'ocf':
517  return OCFMonitoringRule(obj['provider'], obj['type'], obj['classconfig'])
518  if rscclass == 'nagios':
519  return NagiosMonitoringRule(obj['type'], obj['prio']
520  , obj['initargs'], obj['classconfig'], objclass=objclass)
521  if rscclass == 'NEVERMON':
522  return NEVERMonitoringRule(obj['type'], obj['classconfig'])
523 
524  raise ValueError('Invalid resource class ("class" = "%s")' % rscclass)
525 
526  @staticmethod
528  '''Create a cache of all our available monitoring agents - and return it'''
529  if not hasattr(context, 'get') or not hasattr(context, 'objects'):
530  context = ExpressionContext(context)
531  #CMAdb.log.debug('CREATING AGENT CACHE (%s)' % str(context))
532  for node in context.objects:
533  if hasattr(node, '_agentcache'):
534  # Keep pylint from getting irritated...
535  #CMAdb.log.debug('AGENT ATTR IS %s' % getattr(node, '_agentcache'))
536  return getattr(node, '_agentcache')
537  if not hasattr(node, '__iter__') or '_init_monitoringagents' not in node:
538  #CMAdb.log.debug('SKIPPING AGENT NODE (%s)' % (str(node)))
539  #if hasattr(node, 'keys'):
540  # CMAdb.log.debug('SKIPPING AGENT NODE keys: (%s)' % (str(node.keys())))
541  continue
542  agentobj = pyConfigContext(node['_init_monitoringagents'])
543  agentobj = agentobj['data']
544  ret = {}
545  for cls in agentobj.keys():
546  agents = agentobj[cls]
547  for agent in agents:
548  if cls not in ret:
549  ret[cls] = {}
550  ret[cls][agent] = True
551  setattr(node, '_agentcache', ret)
552  #CMAdb.log.debug('AGENT CACHE IS: %s' % str(ret))
553  return ret
554  #CMAdb.log.debug('RETURNING NO AGENT CACHE AT ALL!')
555  return {}
556 
557 
558  @staticmethod
559  def findbestmatch(context, preferlowoverpart=True, objclass='service'):
560  '''
561  Find the best match among the complete collection of MonitoringRules
562  against this particular set of graph nodes.
563 
564  Return value
565  -------------
566  A tuple of (priority, config-info). When we can't find anything useful
567  in the complete set of rules, we return (MonitoringRule.NOMATCH, None)
568 
569  Parameters
570  ----------
571  context: ExpressionContext
572  The set of graph nodes with relevant attributes. This is normally the
573  ProcessNode being monitored and the Drone the services runs on
574  preferlowoverpath: Bool
575  True if you the caller prefers a LOWPRIOMATCH result over a PARTMATCH result.
576  This should be True for automated monitoring and False for possibly manually
577  configured monitoring. The default is to assume that we'd rather
578  automated monitoring running now over finding a human to fill in the
579  other parameters.
580 
581  Of course, we always prefer a HIGHPRIOMATCH monitoring method first
582  and a MEDPRIOMATCH if that's not available.
583  '''
584  rsctypes = ['ocf', 'nagios', 'lsb', 'NEVERMON'] # Priority ordering...
585  # This will make sure the priority list above is maintained :-D
586  # Nagios rules can be of a variety of monitoring levels...
587  mon_objects = MonitoringRule.monobjclass(objclass)
588  if len(rsctypes) < len(mon_objects.keys()):
589  raise RuntimeError('Update rsctypes list in findbestmatch()!')
590 
591  bestmatch = (MonitoringRule.NOMATCH, None)
592 
593  # Search the rule types in priority order
594  for rtype in rsctypes:
595  if rtype not in mon_objects:
596  continue
597  # Search every rule of class 'rtype'
598  for rule in mon_objects[rtype]:
599  match = rule.specmatch(context)
600  #print >> sys.stderr, 'GOT A MATCH------------->', match
601  prio = match[0]
602  if prio == MonitoringRule.NOMATCH:
603  continue
604  if prio == MonitoringRule.HIGHPRIOMATCH:
605  return match
606  bestprio = bestmatch[0]
607  if bestprio == MonitoringRule.NOMATCH:
608  bestmatch = match
609  continue
610  if prio == bestprio:
611  continue
612  # We have different priorities - which do we prefer?
613  if preferlowoverpart:
614  if prio == MonitoringRule.LOWPRIOMATCH:
615  bestmatch = match
616  elif prio == MonitoringRule.PARTMATCH:
617  bestmatch = match
618  return bestmatch
619 
620  @staticmethod
621  def findallmatches(context, objclass='service'):
622  '''
623  We return all possible matches as seen by our complete and wonderful set of
624  MonitoringRules.
625  '''
626  result = []
627  mon_objects = MonitoringRule.monobjclass(objclass)
628  keys = mon_objects.keys()
629  keys.sort()
630  for rtype in keys:
631  for rule in mon_objects[rtype]:
632  match = rule.specmatch(context)
633  if match[0] != MonitoringRule.NOMATCH:
634  result.append(match)
635  return result
636 
637 
638  @staticmethod
639  def ConstructFromFileName(filename):
640  '''
641  Construct a MonitoringRule from a filename parameter.
642  It will construct the appropriate subclass depending on its input
643  string. Note that the input is JSON -- with # comments.
644  '''
645  f = open(filename, 'r')
646  s = f.read()
647  f.close()
648  return MonitoringRule.ConstructFromString(s)
649 
650  @staticmethod
651  def load_tree(rootdirname, pattern=r".*\.mrule$", followlinks=False):
652  '''
653  Add a set of MonitoringRules to our universe from a directory tree
654  using the ConstructFromFileName function.
655  Return: None
656  '''
657  tree = os.walk(rootdirname, topdown=True, onerror=None, followlinks=followlinks)
658  pat = re.compile(pattern)
659  for walktuple in tree:
660  (dirpath, dirnames, filenames) = walktuple
661  dirnames.sort()
662  filenames.sort()
663  for filename in filenames:
664  if not pat.match(filename):
665  continue
666  path = os.path.join(dirpath, filename)
667  MonitoringRule.ConstructFromFileName(path)
668 
670 
671  '''Class for implementing monitoring rules for sucky LSB style init script monitoring
672  '''
673  def __init__(self, servicename, tuplespec):
674  self.servicename = servicename
675  MonitoringRule.__init__(self, 'lsb', tuplespec)
676 
677  def constructaction(self, context):
678  '''Construct arguments
679  '''
680  agentcache = MonitoringRule.compute_available_agents(context)
681  if 'lsb' not in agentcache or self.servicename not in agentcache['lsb']:
682  return (MonitoringRule.NOMATCH, None)
683  return (MonitoringRule.LOWPRIOMATCH
684  , { 'monitorclass': 'lsb'
685  , 'monitortype': self.servicename
686  , 'rscname': 'lsb_' + self.servicename # There can only be one
687  , 'provider': None
688  , 'arglist': None}
689  )
690 
692 
693  '''Class for implementing monitoring rules for things that should never be monitored
694  This is mostly things like Skype and friends which are started by users not
695  by the system.
696  '''
697  def __init__(self, servicename, tuplespec):
698  self.servicename = servicename
699  MonitoringRule.__init__(self, 'NEVERMON', tuplespec)
700 
701  def constructaction(self, context):
702  '''Construct arguments
703  '''
704  return (MonitoringRule.NEVERMATCH
705  , { 'monitorclass': 'NEVERMON'
706  , 'monitortype': self.servicename
707  , 'provider': None
708  , 'arglist': None}
709  )
710 
711 
712 
714  '''Class for implementing monitoring rules for OCF style init script monitoring
715  OCF == Open Cluster Framework
716  '''
717 
718  def __init__(self, provider, rsctype, triplespec):
719  '''
720  Parameters
721  ----------
722  provider: str
723  The OCF provider name for this resource
724  This is the directory name this resource is found in
725  rsctype: str
726  The OCF resource type for this resource (service)
727  This is the same as the script name for the resource
728 
729  triplespec: list
730  Similar to but wider than the MonitoringRule tuplespec
731  (name, expression, regex, regexflags(optional))
732 
733  'name' is the name of an OCF RA parameter or None or '-'
734  'expression' is an expression for computing the value for that name
735  'regex' is a regular expression that the value of 'expression' has to match
736  'regexflags' is the optional re flages for 'regex'
737 
738  If there is no name to go with the tuple, then the name is given as None or '-'
739  If there is no regular expression to go with the name, then the expression
740  and remaining tuple elements are missing. This can happen if there
741  is no mechanical way to determine this value from discovery information.
742  If there is a name and expression but no regex, the regex is assumed to be '.'
743  '''
744  self.provider = provider
745  self.rsctype = rsctype
746 
747  tuplespec = self.tripletuplecheck(triplespec)
748  MonitoringRule.__init__(self, 'ocf', tuplespec)
749 
750 
751  def constructaction(self, context):
752  '''Construct arguments to give MonitorAction constructor
753  We can either return a complete match (HIGHPRIOMATCH)
754  or an incomplete match (PARTMATCH) if we can't find
755  all the parameter values in the nodes we're given
756  We can also return NOMATCH if some things don't match
757  at all.
758  '''
759  agentcache = MonitoringRule.compute_available_agents(context)
760  agentpath = '%s/%s' % (self.provider, self.rsctype)
761  if 'ocf' not in agentcache or agentpath not in agentcache['ocf']:
762  #print >> sys.stderr, "OCF agent %s not in agent cache" % agentpath
763  return (MonitoringRule.NOMATCH, None)
764  #
765  missinglist = []
766  arglist = {}
767  # Figure out what we know how to supply and what we need to ask
768  # a human for -- in order to properly monitor this resource
769  for name in self.nvpairs:
770  if name.startswith('?'):
771  optional = True
772  exprname = name[1:]
773  else:
774  optional = False
775  exprname=name
776  expression = self.nvpairs[name] # NOT exprname
777  val = GraphNodeExpression.evaluate(expression, context)
778  #print >> sys.stderr, 'CONSTRUCTACTION.eval(%s) => %s' % (expression, val)
779  if val is None and not optional:
780  missinglist.append(exprname)
781  else:
782  arglist[exprname] = str(val)
783  if len(missinglist) == 0:
784  # Hah! We can automatically monitor it!
785  return (MonitoringRule.HIGHPRIOMATCH
786  , { 'monitorclass': 'ocf'
787  , 'monitortype': self.rsctype
788  , 'provider': self.provider
789  , 'arglist': arglist
790  }
791  )
792  else:
793  # We can monitor it with some more help from a human
794  # Better incomplete than a sharp stick in the eye ;-)
795  return (MonitoringRule.PARTMATCH
796  , { 'monitorclass': 'ocf'
797  , 'monitortype': self.rsctype
798  , 'provider': self.provider
799  , 'arglist': arglist
800  }
801  , missinglist
802  )
803 
805  '''Class for implementing monitoring rules for Nagios style init script monitoring
806  '''
807  priomap = {
808  'low': MonitoringRule.LOWPRIOMATCH,
809  'med': MonitoringRule.MEDPRIOMATCH,
810  'high': MonitoringRule.HIGHPRIOMATCH,
811  }
812  def __init__(self, rsctype, prio, initargs, triplespec, objclass='service'):
813  '''
814  Parameters
815  ----------
816  rsctype: str
817  The OCF resource type for this resource (service)
818  This is the same as the script name for the resource
819 
820  prio: str
821  The priority of this Nagios agent: 'low', 'med', or 'high'
822 
823  initargs: list
824  Initial arguments given unconditionally to the Nagios agent
825 
826  triplespec: list
827  Similar to but wider than the MonitoringRule tuplespec
828  (name, expression, regex, regexflags(optional))
829 
830  'name' is the name of an environment RA parameter or flag name, or None or '-'
831  'expression' is an expression for computing the value for that name
832  'regex' is a regular expression that the value of 'expression' has to match
833  'regexflags' is the optional re flages for 'regex'
834 
835  If there is no name to go with the tuple, then the name is given as None or '-'
836  If there is no regular expression to go with the name, then the expression
837  and remaining tuple elements are missing. This can happen if there
838  is no mechanical way to determine this value from discovery information.
839  If there is a name and expression but no regex, the regex is assumed to be '.'
840  '''
841  if prio not in self.priomap:
842  raise ValueError('Priority %s is not a valid priority' % prio)
843 
844  self.rsctype = rsctype
845  self.argv = None
846  self.initargs = initargs
847  self.prio = self.priomap[prio]
848  tuplespec = self.tripletuplecheck(triplespec)
849  MonitoringRule.__init__(self, 'nagios', tuplespec, objclass=objclass)
850 
851 
852  # [R0912:NagiosMonitoringRule.constructaction] Too many branches (13/12)
853  # pylint: disable=R0912
854  def constructaction(self, context):
855  '''Construct arguments to give MonitorAction constructor
856  We can either return a complete match (HIGHPRIOMATCH)
857  or an incomplete match (PARTMATCH) if we can't find
858  all the parameter values in the nodes we're given
859  We can also return NOMATCH if some things don't match
860  at all.
861  '''
862  agentcache = MonitoringRule.compute_available_agents(context)
863  if 'nagios' not in agentcache or str(self.rsctype) not in agentcache['nagios']:
864  #CMAdb.log.debug('CONSTRUCTACTION.NOMATCH(%s) %s' % (self.rsctype, type(self.rsctype)))
865  #CMAdb.log.debug('AGENTCACHE: %s' % (str(agentcache)))
866  return (MonitoringRule.NOMATCH, None)
867  #
868  missinglist = []
869  arglist = {}
870  argv = []
871  if self.initargs:
872  argv = self.initargs
873  if self.argv:
874  argv.extend(self.argv)
875  final_argv = []
876  # Figure out what we know how to supply and what we need to ask
877  # a human for -- in order to properly monitor this resource
878  for name in self.nvpairs:
879  # This code is very similar to the OCF code - but not quite the same...
880  if name.startswith('?'):
881  optional = True
882  exprname = name[1:]
883  else:
884  optional = False
885  exprname=name
886  expression = self.nvpairs[exprname]
887  #CMAdb.log.debug('exprname is %s, expression is %s' % (exprname,expression))
888  val = GraphNodeExpression.evaluate(expression, context)
889  #CMAdb.log.debug('CONSTRUCTACTION.eval(%s) => %s' % (expression, val))
890  if val is None:
891  if not optional:
892  missinglist.append(exprname)
893  else:
894  continue
895  if exprname.startswith('-'):
896  argv.append(exprname)
897  argv.append(str(val))
898  elif exprname == '__ARGV__':
899  final_argv.append(str(val))
900  else:
901  arglist[exprname] = str(val)
902  argv.extend(final_argv)
903  if len(arglist) == 0:
904  arglist = None
905  if not argv:
906  argv = None
907  if len(missinglist) == 0:
908  # Hah! We can automatically monitor it!
909  return (self.prio
910  , { 'monitorclass': 'nagios'
911  , 'monitortype': self.rsctype
912  , 'provider': None
913  , 'argv': argv
914  , 'arglist': arglist
915  }
916  )
917  else:
918  # We can monitor it with some more help from a human
919  # Better incomplete than a sharp stick in the eye ;-)
920  return (MonitoringRule.PARTMATCH
921  , { 'monitorclass': 'nagios'
922  , 'monitortype': self.rsctype
923  , 'provider': None
924  , 'argv': argv
925  , 'arglist': arglist
926  }
927  , missinglist
928  )
929 if __name__ == '__main__':
930  # pylint: disable=C0413
931  from graphnodes import ProcessNode
932 
933  # R0903 too few public methods (it's test code!)
934  #pylint: disable=R0903
935  class FakeDrone(dict):
936  'Fake Drone class just for monitoring agents'
937 
938  @staticmethod
939  def get(_unused_name, ret):
940  'Fake Drone get function'
941  return ret
942 
943  def __init__(self, obj):
944  'Fake Drone init for knowing monitoring agents'
945  dict.__init__(self)
946  self['_init_monitoringagents'] = obj
947 
948  fdrone = FakeDrone({
949  'data': {
950  'lsb': {
951  'neo4j-service',
952  'ssh'
953  },
954  'nagios':{
955  'check_ssh',
956  'check_sensors',
957  },
958  'ocf': {
959  'assimilation/neo4j',
960  'heartbeat/oracle',
961  },
962  }
963  })
964  neolsbargs = (
965  ('$argv[0]', r'.*/[^/]*java[^/]*$'), # Might be overkill
966  ('$argv[3]', r'-server$'), # Probably overkill
967  ('$argv[-1]', r'org\.neo4j\.server\.Bootstrapper$'),
968  )
969  neorule = LSBMonitoringRule('neo4j-service', neolsbargs)
970  neoocfargs = (
971  (None, "@basename()", "java$"),
972  (None, "$argv[-1]", "org\\.neo4j\\.server\\.Bootstrapper$"),
973  #("ipport", "@serviceipport()", "..."),
974  ("neo4j_home", "@argequals(-Dneo4j.home)", "/"),
975  ("neo4j", "@basename(@argequals(-Dneo4j.home))",".")
976  )
977  neoocfrule = OCFMonitoringRule('assimilation', 'neo4j', neoocfargs)
978 
979  #ProcessNode:
980  # (domain, processname, host, pathname, argv, uid, gid, cwd, roles=None):
981  sshnode = ProcessNode('global', 'fred', 'servidor', '/usr/bin/sshd', ['/usr/bin/sshd', '-D' ]
982  , 'root', 'root', '/', roles=(CMAconsts.ROLE_server,))
983  setattr(sshnode, 'procinfo'
984  , '{"listenaddrs":{"0.0.0.0:22":"tcp", ":::22":"tcp6"}}')
985 
986  lsbsshargs = (
987  # This means one of our nodes should have a value called
988  # pathname, and it should end in '/sshd'
989  ('$pathname', '.*/sshd$'),
990  )
991  lsbsshrule = LSBMonitoringRule('ssh', lsbsshargs)
992 
993  nagiossshargs = (
994  (None, '@basename()', '.*sshd$'),
995  ('-p', '@serviceport()', '[0-9]+'),
996  ('__ARGV__', '@serviceip()', '.'),
997  )
998  nagiossshrule = NagiosMonitoringRule('check_ssh', 'med', ['-t', '3600'], nagiossshargs)
999 
1000  nagiossensorsargs = ((None, 'hascmd(sensors)', "True"),)
1001  nagiossensorsrule = NagiosMonitoringRule('check_sensors', 'med', [], nagiossensorsargs
1002  , objclass='host')
1003 
1004 
1005  udevnode = ProcessNode('global', 'fred', 'servidor', '/usr/bin/udevd', ['/usr/bin/udevd']
1006  , 'root', 'root', '/', roles=(CMAconsts.ROLE_server,))
1007 
1008 
1009  neoprocargs = ("/usr/bin/java", "-cp"
1010  , "/var/lib/neo4j/lib/concurrentlinkedhashmap-lru-1.3.1.jar:"
1011  "/var/lib/neo4j/lib/geronimo-jta_1.1_spec-1.1.1.jar:/var/lib/neo4j/lib/lucene-core-3.6.2.jar"
1012  ":/var/lib/neo4j/lib/neo4j-cypher-2.0.0-M04.jar"
1013  ":/var/lib/neo4j/lib/neo4j-graph-algo-2.0.0-M04.jar"
1014  ":/var/lib/neo4j/lib/neo4j-graph-matching-2.0.0-M04.jar"
1015  ":/var/lib/neo4j/lib/neo4j-jmx-2.0.0-M04.jar"
1016  ":/var/lib/neo4j/lib/neo4j-kernel-2.0.0-M04.jar"
1017  ":/var/lib/neo4j/lib/neo4j-lucene-index-2.0.0-M04.jar"
1018  ":/var/lib/neo4j/lib/neo4j-shell-2.0.0-M04.jar"
1019  ":/var/lib/neo4j/lib/neo4j-udc-2.0.0-M04.jar"
1020  "/var/lib/neo4j/system/lib/neo4j-server-2.0.0-M04-static-web.jar:"
1021  "AND SO ON:"
1022  "/var/lib/neo4j/system/lib/slf4j-api-1.6.2.jar:"
1023  "/var/lib/neo4j/conf/", "-server", "-XX:"
1024  "+DisableExplicitGC"
1025  , "-Dorg.neo4j.server.properties=conf/neo4j-server.properties"
1026  , "-Djava.util.logging.config.file=conf/logging.properties"
1027  , "-Dlog4j.configuration=file:conf/log4j.properties"
1028  , "-XX:+UseConcMarkSweepGC"
1029  , "-XX:+CMSClassUnloadingEnabled"
1030  , "-Dneo4j.home=/var/lib/neo4j"
1031  , "-Dneo4j.instance=/var/lib/neo4j"
1032  , "-Dfile.encoding=UTF-8"
1033  , "org.neo4j.server.Bootstrapper")
1034 
1035  neonode = ProcessNode('global', 'fred', 'servidor', '/usr/bin/java', neoprocargs
1036  , 'root', 'root', '/', roles=(CMAconsts.ROLE_server,))
1037  withsensors = pyConfigContext('{"_init_commands" : {"data": {"sensors": null}}}')
1038  nosensors = pyConfigContext('{"_init_commands": {"data": {"bash": null}}}')
1039 
1040  oracleocfargs = (
1041  (None, "@basename()", "oracle$"),
1042  ("sid", "@argmatch(\"ora_pmon_(.*)\")", "..*")
1043  )
1044  oracleocfrule = OCFMonitoringRule('heartbeat', 'oracle', oracleocfargs)
1045  oraclenode = ProcessNode('global', 'fred', 'servidor', '/usr/bin/oracle'
1046  , ['ora_pmon_InstanceName'], 'oracle', 'oracle', '/', roles=(CMAconsts.ROLE_server,))
1047 
1048  tests = [
1049  (oracleocfrule.specmatch(ExpressionContext((oraclenode, fdrone))),
1050  MonitoringRule.HIGHPRIOMATCH),
1051  (lsbsshrule.specmatch(ExpressionContext((sshnode, fdrone))), MonitoringRule.LOWPRIOMATCH),
1052  (lsbsshrule.specmatch(ExpressionContext((udevnode, fdrone))), MonitoringRule.NOMATCH),
1053  (lsbsshrule.specmatch(ExpressionContext((neonode, fdrone))), MonitoringRule.NOMATCH),
1054  (neorule.specmatch(ExpressionContext((sshnode, fdrone))), MonitoringRule.NOMATCH),
1055  (neorule.specmatch(ExpressionContext((neonode, fdrone))), MonitoringRule.LOWPRIOMATCH),
1056  (neoocfrule.specmatch(ExpressionContext((neonode, fdrone))), MonitoringRule.HIGHPRIOMATCH),
1057  (neoocfrule.specmatch(ExpressionContext((neonode, fdrone))), MonitoringRule.HIGHPRIOMATCH),
1058  (nagiossshrule.specmatch(ExpressionContext((sshnode, fdrone))),
1059  MonitoringRule.MEDPRIOMATCH),
1060  (nagiossshrule.specmatch(ExpressionContext((udevnode, fdrone))), MonitoringRule.NOMATCH),
1061  (nagiossshrule.specmatch(ExpressionContext((neonode, fdrone))), MonitoringRule.NOMATCH),
1062  (nagiossensorsrule.specmatch(ExpressionContext((withsensors, fdrone))),
1063  MonitoringRule.MEDPRIOMATCH),
1064  (nagiossensorsrule.specmatch(ExpressionContext((nosensors, fdrone))),
1065  MonitoringRule.NOMATCH),
1066  ]
1067  fieldmap = {'monitortype': str, 'arglist':dict, 'monitorclass': str, 'provider':str}
1068  for count in range(0, len(tests)):
1069  testresult = tests[count][0]
1070  expected = tests[count][1]
1071  assert (testresult[0] == expected)
1072  if testresult[0] == MonitoringRule.NOMATCH:
1073  assert testresult[1] is None
1074  else:
1075  assert isinstance(testresult[1], dict)
1076  for field in fieldmap.keys():
1077  assert field in testresult[1]
1078  fieldvalue = testresult[1][field]
1079  assert fieldvalue is None or isinstance(fieldvalue, fieldmap[field])
1080  assert testresult[1]['monitorclass'] in ('ocf', 'lsb', 'nagios')
1081  if testresult[1]['monitorclass'] == 'ocf':
1082  assert testresult[1]['provider'] is not None
1083  assert isinstance(testresult[1]['arglist'], dict)
1084  elif testresult[1]['monitorclass'] == 'lsb':
1085  assert testresult[1]['provider'] is None
1086  assert testresult[1]['arglist'] is None
1087  assert isinstance(testresult[1]['rscname'], str)
1088  if testresult[0] == MonitoringRule.PARTMATCH:
1089  assert testresult[1]['monitorclass'] in ('ocf',)
1090  assert len(testresult) == 3
1091  assert isinstance(testresult[2], (list, tuple)) # List of missing fields...
1092  assert len(testresult[2]) > 0
1093 
1094  print "Test %s passes [%s]." % (count, testresult)
1095 
1096  print 'Documentation of functions available for use in match expressions:'
1097  longest = 0
1098  for (funcname, description) in GraphNodeExpression.FunctionDescriptions():
1099  if len(funcname) > longest:
1100  longest = len(funcname)
1101  fmt = '%%%ds: %%s' % longest
1102  pad = (longest +2) * ' '
1103  fmt2 = pad + '%s'
1104 
1105  for (funcname, description) in GraphNodeExpression.FunctionDescriptions():
1106  descriptions = description.split('\n')
1107  print fmt % (funcname, descriptions[0])
1108  for descr in descriptions[1:]:
1109  print fmt2 % descr
1110 
1111  MonitoringRule.load_tree("monrules")
def constructaction(self, context)
Definition: monitoring.py:439
def __init__(self, servicename, tuplespec)
Definition: monitoring.py:673
def tripletuplecheck(self, triplespec)
Definition: monitoring.py:378
def findbestmatch(context, preferlowoverpart=True, objclass='service')
Definition: monitoring.py:559
def construct_mon_json(self, operation='monitor')
Definition: monitoring.py:242
def __init__(self, rsctype, prio, initargs, triplespec, objclass='service')
Definition: monitoring.py:812
def compute_available_agents(context)
Definition: monitoring.py:527
def __init__(self, obj)
Definition: monitoring.py:943
def ConstructFromString(s, objclass='service')
Definition: monitoring.py:478
def findallmatches(context, objclass='service')
Definition: monitoring.py:621
def __init__(self, domain, monitorname, monitorclass, monitortype, interval, timeout, warntime=None, provider=None, arglist=None, argv=None)
Definition: monitoring.py:65
def logchange(origaddr, monmsgobj)
Definition: monitoring.py:170
def activate(self, monitoredentity, runon=None)
Definition: monitoring.py:108
def __init__(self, monitorclass, tuplespec, objclass='service')
Definition: monitoring.py:333
def specmatch(self, context)
Definition: monitoring.py:404
def constructaction(self, context)
Definition: monitoring.py:677
def find1(name, domain=None)
Definition: monitoring.py:163
def __init__(self, provider, rsctype, triplespec)
Definition: monitoring.py:718
def find(name, domain=None)
Definition: monitoring.py:153
def load_tree(rootdirname, pattern=r".*\.mrule$", followlinks=False)
Definition: monitoring.py:651
def constructaction(self, context)
Definition: monitoring.py:854
def __init__(self, servicename, tuplespec)
Definition: monitoring.py:697
def monobjclass(mtype='service')
Definition: monitoring.py:374
def monitorchange(self, origaddr, monmsgobj)
Definition: monitoring.py:192
def constructaction(self, context)
Definition: monitoring.py:751
def ConstructFromFileName(filename)
Definition: monitoring.py:639
def constructaction(self, context)
Definition: monitoring.py:701
def get(_unused_name, ret)
Definition: monitoring.py:939