diff --git a/nagios/check_solr.py b/nagios/check_solr.py new file mode 100755 index 0000000..ba0b042 --- /dev/null +++ b/nagios/check_solr.py @@ -0,0 +1,338 @@ +#!/usr/bin/env python + +''' +Nagios compatible plugin to check Solr via Solr HTTP API + +require Python >= 2.6 +''' + +from optparse import OptionParser +import xml.etree.ElementTree as ET +import urllib2 +import tempfile +import os.path + +EXIT_CODE = { + 'OK': 0, + 'WARN': 1, + 'CRIT': 2, + 'UNKNOWN': 3, +} + +def prepareOpts(): + ''' + Parse option from the shell + ''' + + cmds = { + 'ping' : 'create a ping to solr API', + 'dataimportDocumentsProcessed': 'check that the dataimport handler is not processing the same document for too long', + 'indexBehindMaster': 'check the difference between the slave index and the master' + } + epliog = 'Type of Checks:' + for k in cmds: + epliog += '\n {0}:\t{1}'.format(k, cmds[k]) + + def help(): + parser.print_help() + print '' + print epliog + + def err( string ): + print 'Error: {0}'.format( string ) + help() + exit(1) + + parser = OptionParser() + parser.add_option('-u', '--url', dest='url', type='string', help='solr url', default=None) + parser.add_option('-U', '--user', dest='user', type='string', help='username', default=None) + parser.add_option('-P', '--passwd', dest='passwd', type='string', help='password', default=None) + parser.add_option('-t', '--timeout', dest='timeout', type='float', help='how many seconds to wait for each http request', default=5) + parser.add_option('-T', '--type', dest='type', type='choice', choices=cmds.keys(), help='what to check: {0}'.format(', '.join(cmds.keys())) ) + parser.add_option('-C', '--core', dest='core', type='string', help='core id', default=None) + parser.add_option('-d', '--handler', dest='handler', type='string', help='dataimport handler name', default=None) + parser.add_option('-c', '--critical', dest='critical', type='int', help='warning threshold (implies -T indexBehindMaster)', default=None) + parser.add_option('-w', '--warning', dest='warning', type='int', help='critical threshold (implies -T indexBehindMaster)', default=None) + parser.add_option('-m', '--tmpdir', dest='tmpdir', type='string', help='absolute path to a writeable directory on the server', default=tempfile.gettempdir()) + (opts, args) = parser.parse_args() + + # Input Validation + if not opts: + help() + exit(1) + if not opts.type: + err('missing -T') + if (opts.user and not opts.passwd) or (not opts.user and opts.passwd): + err('missing username or password') + if not opts.url: + err('missing solr http url') + if opts.type == 'dataimportDocumentsProcessed': + if opts.core is None: + err('missing core id !') + if opts.handler is None: + err('missing handler name !') + if opts.type == 'indexBehindMaster': + if opts.critical is None or opts.warning is None: + err('missing -w or -c') + if opts.warning > opts.critical: + err('-w can not be greater than -c') + + return opts + + +class SolrMonitor(): + ''' + Monitor Apache Solr via http + ''' + + def __init__(self, url, timeout=5, username=None, passwd=None): + self.url = url + self.timeout = timeout + self.username = username + self.passwd = passwd + self.memfile = 'check_solr_data' + + if self.url.endswith('/'): + self.url = self.url[:-1] + + self._get_auth() + + def _get_auth(self): + ''' + Build an Auth opener for HTTP connection + ''' + if not self.username or not self.passwd: + return + basic = urllib2.HTTPBasicAuthHandler() + basic.add_password( + realm='Solr', + uri=self.url, + user=self.username, + passwd=self.passwd + ) + digest = urllib2.HTTPDigestAuthHandler() + digest.add_password( + realm='Solr', + uri=self.url, + user=self.username, + passwd=self.passwd + ) + + urllib2.install_opener( + urllib2.build_opener(basic, digest)) + + def _getXmlData(self, url): + ''' + create an http request to url and return the data + in case of a problem return None + ''' + + try: + return ET.fromstring( + urllib2.urlopen( + url, + timeout=self.timeout + ).read() + ) + except urllib2.URLError: + return None + + def _eval_ping(self, res, opts): + ''' + Evaluate the ping test + ''' + + if res: + return { + 'exit': EXIT_CODE['OK'], + 'msg': 'OK: Solr Ping is up' + } + else: + return { + 'exit': EXIT_CODE['CRIT'], + 'msg': 'OK: Solr Ping is down' + } + + def _eval_dataimportDocumentsProcessed(self, res, opts): + ''' + Evaluate the dataimportDocumentsProcessed test + ''' + firstTimeResponse = { + 'exit': EXIT_CODE['UNKNOWN'], + 'msg': 'UNKNOWN: looks like the first time we are using this check, creating local cache' + } + memFile = os.path.join(opts.tmpdir, self.memfile) + if not os.path.isfile(memFile): + with open( memFile, 'w' ) as f: + f.write(str(res)) + return firstTimeResponse + + if res < 0: + return { + 'exit': EXIT_CODE['UNKNOWN'], + 'msg': 'UNKNOWN: could not query solr for index status' + } + + fh = open( memFile, 'r+' ) + prev = fh.read() + fh.seek(0) + fh.write(str(res)) + fh.close() + if not prev: + return firstTimeResponse + prev = int(prev) + + if prev != res or res == 0: + return { + 'exit': EXIT_CODE['OK'], + 'msg': 'OK: Solr is indexing {0} docs now and before {1}'.format( + res, prev + ) + } + else: + return { + 'exit': EXIT_CODE['CRIT'], + 'msg': 'CRIT: Solr is still indexing {0} docs since the last check'.format(res) + } + + def _eval_indexBehindMaster(self, res, opts): + ''' + Evaluate the indexBehindMaster test + ''' + + msg='' + if res < opts.warning: + msg='OK' + elif res >= opts.warning and res <= opts.critical: + msg='WARN' + else: + msg='CRIT' + return { + 'exit': EXIT_CODE[msg], + 'msg': '{0}: Solr Slave is {1} behind then master'.format( + msg, res + ) + } + + def ping(self): + ''' + Check if solr ping returns True + ''' + + ret = False + root = self._getXmlData(self.url + '/admin/ping') + if root is None: + return False + + if root.find('str').text == 'OK': + ret = True + + return ret + + def dataimportDocumentsProcessed(self, core, handler): + ''' + Return the number of processed documents + from the dataimport handler + + url: http://solr:port/solr/core0/dataimportName?command=status + ''' + + url = '{0}/{1}/{2}?command=status'.format( + self.url, + core, + handler + ) + root = self._getXmlData(url) + if root is None: + return -1 + + for lst in root.findall('lst'): + if lst.attrib['name'] == 'statusMessages': + for str in lst.findall('str'): + if str.attrib['name'] == 'Total Documents Processed': + return int(str.text) + + return -1 + +# Python 2.7 +# return int( +# root.findall( +# "lst[@name='statusMessages']/str[@name='Total Documents Processed']" +# )[0].text +# ) + + def indexBehindMaster(self): + ''' + Returns the difference bewteen the slave index + and the master replicable index + ''' + + slave = None + master = None + root = self._getXmlData( + self.url + '/replication?command=details' + ) + if root is None: + return -1 + + for lst in root.findall('lst'): + if lst.attrib['name'] == 'details': + + # Slave + for lng in lst.findall('long'): + if lng.attrib['name'] == 'indexVersion': + slave = long(lng.text) + break + + # Master + for lstm in lst.findall('lst'): + if lstm.attrib['name'] == 'slave': + for lstms in lstm.findall('lst'): + if lstms.attrib['name'] == 'masterDetails': + for lstMaster in lstms.findall('lst'): + if lstMaster.attrib['name'] == 'master': + for rep in lstMaster.findall('long'): + if rep.attrib['name'] == 'replicableVersion': + master = long(rep.text) + break + + if master and slave: + break + +# Python 2.7 +# slave = root.findall( +# "./*[@name='details']/arr[@name='commits']/lst/long[@name='indexVersion']" +# )[0].text +# master = root.findall( +# "./lst[@name='details']/lst[@name='slave']/lst[@name='masterDetails']/lst[@name='master']/long[@name='replicableVersion']" +# )[0].text + return long(master - slave) + + @staticmethod + def main(): + ''' + Main function + ''' + + opts = prepareOpts() + solr = SolrMonitor( opts.url, opts.timeout, opts.user, opts.passwd ) + + method = getattr(solr, opts.type) + k = {} + if opts.core: + k.update({'core': opts.core}) + if opts.handler: + k.update({'handler': opts.handler}) + res = method(**k) + + eval_method = getattr( + solr, '_eval_{0}'.format(opts.type) + ) + ret = eval_method(res, opts) + print ret['msg'] + exit(ret['exit']) + + + +if __name__ == '__main__': + SolrMonitor.main()