The Assimilation Project  based on Assimilation version 1.1.7.1474836767
monitoringdiscovery.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # vim: smartindent tabstop=4 shiftwidth=4 expandtab number
3 #
4 # This file is part of the Assimilation Project.
5 #
6 # Author: Alan Robertson <alanr@unix.sh>
7 # Copyright (C) 2013 - Assimilation Systems Limited
8 #
9 # Free support is available from the Assimilation Project community - http://assimproj.org
10 # Paid support is available from Assimilation Systems Limited - http://assimilationsystems.com
11 #
12 # The Assimilation software is free software: you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation, either version 3 of the License, or
15 # (at your option) any later version.
16 #
17 # The Assimilation software is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
21 #
22 # You should have received a copy of the GNU General Public License
23 # along with the Assimilation Project software. If not, see http://www.gnu.org/licenses/
24 #
25 #
26 
27 '''
28 Discovery Listener infrastructure
29 This is the base class for code that wants to hear about various
30 discovery packets as they arrive.
31 
32 More details are documented in the DiscoveryListener class
33 '''
34 import sys, hashlib
35 from monitoring import MonitoringRule, MonitorAction
36 from systemnode import SystemNode
37 from store import Store
38 
39 from graphnodes import ProcessNode
40 from discoverylistener import DiscoveryListener
41 from cmaconfig import ConfigFile
42 
43 @SystemNode.add_json_processor
44 class TCPDiscoveryGenerateMonitoring(DiscoveryListener):
45  'Class for generating and activating monitoring from the TCP discovery data'
46  prio = DiscoveryListener.PRI_OPTION
47  wantedpackets = ('tcpdiscovery',)
48 
49  def processpkt(self, drone, _unused_srcaddr, jsonobj, _discoverychanged):
50  '''Send commands to monitor services for this Systems's listening processes
51  We ignore discoverychanged because we always want to monitor even if a system
52  has just come up with the same discovery as before it went down.'''
53 
54  drone.monitors_activated = True
55  #self.log.debug('In TCPDiscoveryGenerateMonitoring::processpkt for %s with %s (%s)'
56  # % (drone, _discoverychanged, str(jsonobj)))
57  data = jsonobj['data'] # The data portion of the JSON message
58  for procname in data.keys(): # List of nanoprobe-assigned names of processes...
59  procinfo = data[procname]
60  processproc = self.store.load_or_create(ProcessNode, domain=drone.domain
61  , processname=procname, host=drone.designation
62  , pathname=procinfo.get('exe', 'unknown'), argv=procinfo.get('cmdline', 'unknown')
63  , uid=procinfo.get('uid','unknown'), gid=procinfo.get('gid', 'unknown')
64  , cwd=procinfo.get('cwd', '/'))
65  if 'listenaddrs' not in procinfo:
66  # We only monitor services, not clients...
67  continue
68  montuple = MonitoringRule.findbestmatch((processproc, drone))
69  if montuple[0] == MonitoringRule.NOMATCH:
70  processproc.is_monitored = False
71  print >> sys.stderr, "**don't know how to monitor %s" % str(processproc.argv)
72  self.log.warning('No rules to monitor %s service %s'
73  % (drone.designation, str(processproc.argv)))
74  elif montuple[0] == MonitoringRule.PARTMATCH:
75  processproc.is_monitored = False
76  print >> sys.stderr, (
77  'Automatic monitoring not possible for %s -- %s is missing %s'
78  % (str(processproc.argv), str(montuple[1]), str(montuple[2])))
79  self.log.warning('Insufficient information to monitor %s service %s'
80  '. %s is missing %s'
81  % (drone.designation, str(processproc.argv)
82  , str(montuple[1]), str(montuple[2])))
83  else:
84  processproc.is_monitored = True
85  agent = montuple[1]
86  self._add_service_monitoring(drone, processproc, agent)
87  if agent['monitorclass'] == 'NEVERMON':
88  print >> sys.stderr, ('NEVER monitor %s' % (str(agent['monitortype'])))
89  else:
90  print >> sys.stderr, ('START monitoring %s using %s agent'
91  % (agent['monitortype'], agent['monitorclass']))
92 
93  # pylint - too many local variables
94  # pylint: disable=R0914
95  def _add_service_monitoring(self, drone, monitoredservice, moninfo):
96  '''
97  We start the monitoring of 'monitoredservice' using the information
98  in 'moninfo' - which came from MonitoringRule.constructaction()
99  and is based on discovery and general rules for that monitoring action
100  Moninfo includes the following kinds of metadata:
101  - identification parameters - class, provider, type
102  - environment variables
103  - command line arguments
104  '''
105  monitorclass = moninfo['monitorclass']
106  monitortype = moninfo['monitortype']
107  monitorprovider = moninfo.get('provider', None)
108  if monitorprovider is not None:
109  classtype = "%s::%s:%s" % (monitorclass, moninfo['provider'], monitortype)
110  else:
111  classtype = "%s::%s" % (monitorclass, monitortype)
112  # Compute interval and timeout - based on global 'config'
113  agent_params = ConfigFile.agent_params(self.config, 'monitoring',
114  classtype, drone.designation)
115  # This produces the following metadata:
116  # - class-independent parameters: repeat, timeout, etc
117  # - environment variables
118  # - command line arguments
119  # These are merged together with the moninfo parameters in the following way
120  # - Class-independent variables are taken from 'params' if conflicting
121  # - Environment variables are taken from 'params' if there's a conflict
122  # - command line arguments from params come first, followed by the
123  # any that come from 'moninfo'
124  parameters = agent_params['parameters']
125  paraminterval = parameters['repeat']
126  paramtimeout = parameters['timeout']
127  paramwarntime = parameters['warn']
128  paramargv = parameters.get('argv', [])
129  paramenv = parameters.get('env', {})
130  monargv = moninfo.get('argv', [])
131  monenv = moninfo.get('arglist', {})
132  argv = [] # pyConfigContext doesn't handle arrays well...
133  if paramargv is not None:
134  argv.extend(paramargv)
135  if monargv is not None:
136  argv.extend(monargv)
137  environ = {}
138  for envset in (monenv, paramenv):
139  if envset is not None:
140  for env in envset:
141  environ[env] = envset[env]
142 
143  # Make up a monitor name that should be unique to us -- but reproducible
144  # We create the monitor name from the host name, the monitor class,
145  # monitoring type and a hash of the arguments to the monitoring agent
146  # Note that this is different from the hashed service/entity name, since we could
147  # have multiple ways of monitoring the same entity
148  d = hashlib.md5()
149  # pylint mistakenly thinks md5 objects don't have update member
150  # pylint: disable=E1101
151  d.update('%s:%s:%s:%s'
152  % (drone.designation, monitorclass, monitortype, monitorprovider))
153  if environ is not None:
154  names = environ.keys()
155  names.sort()
156  for name in names:
157  # pylint thinks md5 objects don't have update member
158  # pylint: disable=E1101
159  d.update('"%s": "%s"' % (name, environ[name]))
160  for arg in argv:
161  d.update('"%s"' % str(arg))
162 
163  monitorname = ('%s:%s:%s::%s'
164  % (drone.designation, monitorclass, monitortype, d.hexdigest()))
165  monnode = self.store.load_or_create(MonitorAction, domain=drone.domain
166  , monitorname=monitorname, monitorclass=monitorclass
167  , monitortype=monitortype, interval=paraminterval, timeout=paramtimeout
168  , warntime=paramwarntime
169  , provider=monitorprovider
170  , arglist = environ if environ else None
171  , argv = argv if argv else None) # Neo4j restriction...
172  if monitorclass == 'nagios':
173  monnode.nagiospath = self.config['monitoring']['nagiospath']
174  if not Store.is_abstract(monnode):
175  print >> sys.stderr, ('Previously monitored %s on %s'
176  % (monitortype, drone.designation))
177  monnode.activate(monitoredservice, drone)
178 
179 @SystemNode.add_json_processor
181  '''This class performs host-level monitoring.
182  For the moment, that's only using Nagios agents.
183  '''
184  def processpkt(self, drone, _unused_srcaddr, _unused_jsonobj, _discoverychanged):
185  '''Send commands to monitor host aspects for the given System.
186  We ignore discoverychanged because we always want to monitor even if a system
187  has just come up with the same discovery as before it went down.
188  '''
189 
190  drone.monitors_activated = True
191  #self.log.debug('In DiscoveryGenerateHostMonitoring::processpkt for %s with %s (%s)'
192  # % (drone, _discoverychanged, str(_unused_jsonobj)))
193  montuples = MonitoringRule.findallmatches((drone,), objclass='host')
194  for montuple in montuples:
195  if montuple[0] == MonitoringRule.NOMATCH:
196  continue
197  elif montuple[0] == MonitoringRule.PARTMATCH:
198  print >> sys.stderr, (
199  'Automatic host monitoring of %s not possible with %s: missing %s'
200  % (drone.designation, str(montuple[1]), str(montuple[2])))
201  self.log.warning('Insufficient information to monitor host %s'
202  ' using %s: %s is missing.'
203  % (drone.designation, str(montuple[1]), str(montuple[2])))
204  else:
205  agent = montuple[1]
206  self._add_service_monitoring(drone, drone, agent)
207  print >> sys.stderr, ('START monitoring host %s using %s:%s agent'
208  % (drone.designation, agent['monitorclass'], agent['monitortype']))
def _add_service_monitoring(self, drone, monitoredservice, moninfo)
def processpkt(self, drone, _unused_srcaddr, _unused_jsonobj, _discoverychanged)
def processpkt(self, drone, _unused_srcaddr, jsonobj, _discoverychanged)