From a86601d7389f2a4e3ed7f6099756471800f414b1 Mon Sep 17 00:00:00 2001 From: Sven Velt Date: Sun, 29 Nov 2009 20:26:08 +0100 Subject: [PATCH] Initial commit --- .gitignore | 3 + QUICKSTART.de.txt | 178 +++++++++++++++++++++++ dummy_read_xml.py | 72 ++++++++++ dummy_write_xml.py | 48 +++++++ examples/nagixsc_debian.conf | 16 +++ examples/nagixsc_usrlocal.conf | 16 +++ nagixsc.py | 139 ++++++++++++++++++ nagixsc_conf2xml.py | 77 ++++++++++ nagixsc_xml2nagios.py | 252 +++++++++++++++++++++++++++++++++ xml/dtd_no_timestamp.xml | 61 ++++++++ xml/dtd_test.xml | 60 ++++++++ xml/nagixsc.dtd | 21 +++ xml/nagixsc.xml | 60 ++++++++ xml/nagixsc.xsd | 28 ++++ 14 files changed, 1031 insertions(+) create mode 100644 .gitignore create mode 100644 QUICKSTART.de.txt create mode 100755 dummy_read_xml.py create mode 100755 dummy_write_xml.py create mode 100644 examples/nagixsc_debian.conf create mode 100644 examples/nagixsc_usrlocal.conf create mode 100644 nagixsc.py create mode 100755 nagixsc_conf2xml.py create mode 100755 nagixsc_xml2nagios.py create mode 100644 xml/dtd_no_timestamp.xml create mode 100644 xml/dtd_test.xml create mode 100644 xml/nagixsc.dtd create mode 100644 xml/nagixsc.xml create mode 100644 xml/nagixsc.xsd diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..75ec6e7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.pyc +.*.swp + diff --git a/QUICKSTART.de.txt b/QUICKSTART.de.txt new file mode 100644 index 0000000..6b0c3df --- /dev/null +++ b/QUICKSTART.de.txt @@ -0,0 +1,178 @@ +========== +00. Inhalt +========== + +- 01. Auf dem zu überwachenden Rechner +- 02. Auf dem Nagios + + + + + +==================================== +01. Auf dem zu überwachenden Rechner +==================================== + +----------------------------------- +01.01. Erstellen einer "conf"-Datei +----------------------------------- + +Vorbemerkung: +Zur Zeit wird ein INI-Datei ähnliches Format verwendet. Dies kann sich aber in +Zukunft noch ändern! + +Als Vorlage können die Dateien "examples/nagixsc_debian.conf" (Nagios mit +Hilfe der Debian-Packages installiert) und "examples/nagixsc_usrlocal.conf" +(selbst kompiliert nach /usr/local/nagios) dienen. + +Je Rechner, der überwacht werden soll, wird ein Abschnitt in eckigen Klammern +angelegt. Der Abschnittsname entspricht dabei dem Nagios-Hostname. Innerhalb +des Abschnitts können nun die Service Descriptions und die auszuführenden +Commands definiert werden. + +Beginnt eine Option mit einem Unterstrich/Underscore/"_", so sind dies +Nag(ix)SC-interne Definitionen und werden NICHT als Service angesehen. Bisher +gibt es folgende Definitionen: + +- "_host_name": Überschreibt den Abschnittsnamen mit dem angegebenen + Hostnamen. + +Der Abschnitt "[nagixsc]" ist für zukünftige Erweiterungen reserviert! + + +---------------------------------------------------- +01.02. Ausführen der Checks, Erstellen der XML-Datei +---------------------------------------------------- + +Zum ersten Test lässt man sich das XML am Besten an der Konsole ausgeben: + +% ./nagixsc_conf2xml.py -e plain -c examples/nagixsc.conf + +Zur Lesbarkeit wird das Encoding ausgeschaltet ("-e plain"). Dies ist nicht +für den Produktivbetrieb gedacht! + +Man sollte hier bereits erkennen, wie Nag(ix)SC "intern tickt". Jetzt wäre der +geeignete Zeitpunkt sich eine kleine, eigene Konfigurationsdatei zu schreiben. +;-) + +Hat man diese (zur Not geht's natürlich auch mit den Beispielen weiter), +erstellt man nun ein XML, welches zur Weiterverarbeitung geeignet ist: + +% ./nagixsc_conf2xml.py --c examples/nagixsc.conf -o /tmp/nagixsc.xml + +In der Praxis kann man diese XML-Datei auf einen Web-Server legen, damit sie +direkt vom Nagios aus abrufbar ist. + + +----------------------------------------- +01.03. Kontrolle der erstellten XML-Datei +----------------------------------------- + +Mit Hilfe des Skripts "dummy_read_xml.py" kann man sich den Inhalt einer +Nag(ix)SC-XML-Datei ansehen: + +% ./dummy_read_xml.py -f /tmp/nagixsc.xml + +Der Inhalt wird zuerst blockweise, dann als formatiertes Python Dict +ausgegeben. + + + + + +================== +02. Auf dem Nagios +================== + +-------------------------------- +02.01. Verschiedene Übergabewege +-------------------------------- + +Auf der Nagios-Seite gibt es im Moment 2 Wege an die Check-Ergebnisse +heranzukommen: Entweder man sorgt manuell dafür, dass die XML-Datei irgendwo +im Dateisystem lesbar ist ("-f"), oder man greift auf einen Web-Server zu +("-u"). Die Beispiele gehen von einer lokalen Datei aus. + +Das Tool "nagixsc_xml2nagios.py" kennt 5 verschiedene Arbeitsweisen, angegeben +durch den Schalter "-O": + +- "passive": Schreibt die Ergebnisse als passive Service Checks in die Nagios + Command Pipe. Es generiert keine Ausgaben und ist daher für einen + Aufruf durch Cron geeignet. + +- "passive_check": Schreibt ebenfalls passive Service Checks, die Rückgabe + erfolgt allerdings im Stil eines Nagios-Plugins (eine Zeile + Ausgabe, Exit-Code). + +- "checkresult": Schreibt die Ergebnisse aus dem XML in das Nagios + "checkresults"-Verzeichnis. Ebenfalls für Cron geeignet. + +- "checkresult_check": Siehe "checkresult", Verhalten wie ein Nagios-Plugin + (eine Zeile Ausgabe, Exit-Code) + +- "active": Durchsucht ein XML nach einer Host/Service-Kombination und beendet + sich wie ein Nagios-Plugin mit den gefundenen Daten. + +Beispielaufrufe: + +% ./nagixsc_xml2nagios.py -O passive -f /tmp/nagixsc.xml +% + +% ./nagixsc_xml2nagios.py -O passive_check -f /tmp/nagixsc.xml +Nag(ix)SC OK - Check results are 25077576 seconds old +% + +% ./nagixsc_xml2nagios.py -O checkresult -f /tmp/nagixsc.xml +% + +% ./nagixsc_xml2nagios.py -O checkresult_check -f /tmp/nagixsc.xml +Nag(ix)SC OK - Wrote checkresult files for 6 services +% + +./nagixsc_xml2nagios.py -O active -f /tmp/nagixsc.xml -D Disk_Root +DISK OK - free space: / 2167 MB (22% inode=97%);| /=7353MB;8568;9044;0;9520 +% + +Für einen selbstkompilierten Nagios und die Debian-Packages können die +"nagios.cmd" und das "checkresults"-Verzeichnis automatische gefunden werden. +Sollte dies nicht klappen, kann man sie mit "-p" (Pipe) bzw. "-r" (Resultdir) +übergeben werden. + +Will man "erstmal nur testen", so kann man "nagix_xm2nagios.py" mit "touch +/tmp/nagixsc.cmd" und "-p /tmp/nagixsc.cmd" bzw. "mkdir /tmp/nagixsc.result" +und "-r /tmp/nagixsc.result" aufrufen und die generierten External Commands +bzw. Checkresult-Dateien ansehen. + + +---------------------- +02.02. Veraltete Daten +---------------------- + +"nagixsc_xml2nagios.py" geht davon aus, dass die Daten im XML nicht älter als +vier Stunden (14400 Sekunden) sind. Sollten sie es dennoch sein, werden sie +automatisch mit dem Text "Nag(ix)SC: Check result is XX(>1440) seconds old" +gekennzeichnet. Dieses Verhalten kann mit zwei Optionen beeinflusst werden: + +- "-s SEC" gibt an, nach wie vielen Sekunden Nag(ix)SC davon ausgegen soll, + dass die Daten im XML veraltet sind und mit dem oben genannten + Hinweis versehen werden sollen. + +- "-m" sorgt dafür, dass nicht nur der Text ergänzt, sondern dass der Check im + Nagios als UNKNOWN geführt wird. + + +------------------------ +02.03. Weitere Parameter +------------------------ + +- "-S": Teste die XML-Datei vor der Verarbeitung gegen die angegebene + DTD-Datei. Eine DTD wird als "xml/nagixsc.dtd" mitgeliefert (ohne + Gewähr ;-) + +- "-H": Filtert die XML-Datei nach einem Hostnamen und gibt nur dessen Checks + zurück. Kann mit allen Varianten kombiniert werden. + +- "-D": Filtert die XML-Datei nach einer (Service) Description. In Kombination + mit "-H" wird nach einer Host/Service-Kombination gesucht. Ist kein + Host angegeben, wird der erste Host in der XML-Datei genommen. + diff --git a/dummy_read_xml.py b/dummy_read_xml.py new file mode 100755 index 0000000..d74d64d --- /dev/null +++ b/dummy_read_xml.py @@ -0,0 +1,72 @@ +#!/usr/bin/python + +#import base64 +import datetime +import libxml2 +import optparse +import sys + +parser = optparse.OptionParser() + +parser.add_option('-u', '', dest='url', help='URL of status file (xml)') +parser.add_option('-f', '', dest='file', help='(Path and) file name of status file') +parser.add_option('-s', '', dest='seconds', type='int', help='Maximum age in seconds of xml timestamp') +parser.add_option('-m', '', action='store_true', dest='markold', help='Mark (Set state) of too old checks as UNKNOWN') +parser.add_option('-v', '', action='count', dest='verb', help='Verbose output') + +parser.set_defaults(url=None) +parser.set_defaults(file='nagixsc.xml') +parser.set_defaults(seconds=14400) +parser.set_defaults(markold=False) +parser.set_defaults(verb=0) + +(options, args) = parser.parse_args() + +############################################################################## + +from nagixsc import * + +############################################################################## + +now = int(datetime.datetime.now().strftime('%s')) + +# Get URL or file +if options.url != None: + import urllib2 + + response = urllib2.urlopen(options.url) + doc = libxml2.parseDoc(response.read()) + response.close() +else: + doc = libxml2.parseFile(options.file) + + +# Check XML file basics +(status, string) = xml_check_version(doc) +debug(1, options.verb, string) +if not status: + print string + sys.exit(127) + + +# Get timestamp and check it +filetimestamp = xml_get_timestamp(doc) +if not filetimestamp: + print 'No timestamp found in XML file, exiting because of invalid XML data...' + sys.exit(127) + +timedelta = int(now) - int(filetimestamp) +debug(1, options.verb, 'Age of XML file: %s seconds, max allowed: %s seconds' % (timedelta, options.seconds)) + + +# Put XML to Python dict +checks = xml_to_dict(doc) + + +# Loop over check results and output them +for check in checks: + check = check_mark_outdated(check, now, options.seconds, options.markold) + print 'Host: %s\nService: %s\nRetCode: %s\nOutput: %r\nTimestamp: %s\n' % (check['host_name'], check['service_description'], check['returncode'], check['output'], check['timestamp']) + +import pprint +pprint.pprint(checks) diff --git a/dummy_write_xml.py b/dummy_write_xml.py new file mode 100755 index 0000000..008d0b7 --- /dev/null +++ b/dummy_write_xml.py @@ -0,0 +1,48 @@ +#!/usr/bin/python + +import nagixsc +import optparse + +parser = optparse.OptionParser() + +parser.add_option('-o', '', dest='outfile', help='(Path and) file name of status file, default STDOUT') +parser.add_option('-v', '', action='count', dest='verb', help='Verbose output') + +parser.set_defaults(outfile='-') +parser.set_defaults(verb=0) + +(options, args) = parser.parse_args() + +checks = [{'host_name': 'host1', + 'output': 'Nag(ix)SC: Check result is 24535725(>14400) seconds old - DISK CRITICAL - free space: /home 775 MB (1% inode=91%);| /home=67584MB;61523;64941;0;68359', + 'returncode': '2', + 'service_description': 'Disk_Home', + 'timestamp': 1234443420}, + {'host_name': 'host1', + 'output': 'Nag(ix)SC: Check result is 24535725(>14400) seconds old - OK - load average: 0.00, 0.00, 0.00|load1=0.000;5.000;10.000;0; load5=0.000;5.000;10.000;0; load15=0.000;5.000;10.000;0;', + 'returncode': '0', + 'service_description': 'Load', + 'timestamp': 1234443420}, + {'host_name': 'host2', + 'output': 'Nag(ix)SC: Check result is 24535735(>14400) seconds old - PROCS OK: 163 processes', + 'returncode': '0', + 'service_description': 'Procs_Total', + 'timestamp': 1234443410}, + {'host_name': 'host2', + 'output': 'Nag(ix)SC: Check result is 24535715(>14400) seconds old - SWAP OK - 79% free (1492 MB out of 1906 MB) |swap=1492MB;953;476;0;1906', + 'returncode': '0', + 'service_description': 'Swap', }, + {'host_name': 'host1', + 'output': 'Nag(ix)SC: Check result is 24535725(>14400) seconds old - DISK OK - free space: / 2167 MB (22% inode=97%);| /=7353MB;8568;9044;0;9520', + 'returncode': '0', + 'service_description': 'Disk_Root', + 'timestamp': 1234443420}, + {'host_name': 'host2', + 'output': 'Nag(ix)SC: Check result is 24535735(>14400) seconds old - USERS WARNING - 11 users currently logged in |users=11;10;15;0\n3 root sessions\n8 non-root sessions', + 'returncode': '1', + 'service_description': 'Users', + 'timestamp': 1234443410}] + +xmldoc = nagixsc.xml_from_dict(checks) +xmldoc.saveFile(options.outfile) + diff --git a/examples/nagixsc_debian.conf b/examples/nagixsc_debian.conf new file mode 100644 index 0000000..1b82196 --- /dev/null +++ b/examples/nagixsc_debian.conf @@ -0,0 +1,16 @@ +[nagixsc] +Reserved: For future use + +[host1] +_underscores_at_start: reserved +_host_name: host1 +Disk_Home: /usr/lib/nagios/plugins/check_disk -w 10% -c 5% -m -p /home +Disk_Root: /usr/lib/nagios/plugins/check_disk -w 10% -c 5% -m -p / +Load: /usr/lib/nagios/plugins/check_load -w 5,5,5 -c 10,10,10 + +[host2] +_host_name: host2.foo.bar +Procs_Total: /usr/lib/nagios/plugins/check_procs -w 200 -c 250 +Swap: /usr/lib/nagios/plugins/check_swap -w 50% -c 25% +Users: /usr/lib/nagios/plugins/check_users -w 10 -c 15 + diff --git a/examples/nagixsc_usrlocal.conf b/examples/nagixsc_usrlocal.conf new file mode 100644 index 0000000..2aac151 --- /dev/null +++ b/examples/nagixsc_usrlocal.conf @@ -0,0 +1,16 @@ +[nagixsc] +Reserved: For future use + +[host1] +_underscores_at_start: reserved +_host_name: host1 +Disk_Home: /usr/local/nagios/libexec/check_disk -w 10% -c 5% -m -p /home +Disk_Root: /usr/local/nagios/libexec/check_disk -w 10% -c 5% -m -p / +Load: /usr/local/nagios/libexec/check_load -w 5,5,5 -c 10,10,10 + +[host2] +_host_name: host2.foo.bar +Procs_Total: /usr/local/nagios/libexec/check_procs -w 200 -c 250 +Swap: /usr/local/nagios/libexec/check_swap -w 50% -c 25% +Users: /usr/local/nagios/libexec/check_users -w 10 -c 15 + diff --git a/nagixsc.py b/nagixsc.py new file mode 100644 index 0000000..4903509 --- /dev/null +++ b/nagixsc.py @@ -0,0 +1,139 @@ +import base64 +import datetime +import libxml2 + +def debug(level, verb, string): + if level <= verb: + print "%s: %s" % (level, string) + + +############################################################################## + +def available_encodings(): + return ['base64', 'plain',] + + +def decode(data, encoding): + if encoding == 'plain': + return data + else: + return base64.b64decode(data) + + +def encode(data, encoding=None): + if encoding == 'plain': + return data + else: + return base64.b64encode(data) + + +############################################################################## + +def xml_check_version(xmldoc): + # FIXME: Check XML structure + try: + xmlnagixsc = xmldoc.xpathNewContext().xpathEval('/nagixsc')[0] + except: + return (False, 'Not a Nag(IX)SC XML file!') + + try: + if xmlnagixsc.prop('version') != "1.0": + return (False, 'Wrong version (found "%s", need "1.0") of XML file!' % xmlnagixsc.prop('version')) + except: + return (False, 'No version information found in XML file!') + + return (True, 'XML seems to be ok') + + +def xml_get_timestamp(xmldoc): + try: + timestamp = int(xmldoc.xpathNewContext().xpathEval('/nagixsc/timestamp')[0].get_content()) + except: + return False + + return timestamp + + +def xml_to_dict(xmldoc, verb=0, hostfilter=None, servicefilter=None): + checks = [] + filetimestamp = xml_get_timestamp(xmldoc) + + if hostfilter: + hosts = xmldoc.xpathNewContext().xpathEval('/nagixsc/host[name="%s"] | /nagixsc/host[name="%s"]' % (hostfilter, encode(hostfilter))) + else: + hosts = xmldoc.xpathNewContext().xpathEval('/nagixsc/host') + + for host in hosts: + xmlhostname = host.xpathEval('name')[0] + hostname = decode(xmlhostname.get_content(), xmlhostname.prop('encoding')) + debug(2, verb, 'Found host "%s"' % hostname) + + if servicefilter: + services = host.xpathEval('service[description="%s"] | service[description="%s"]' % (servicefilter, encode(servicefilter))) + else: + services = host.xpathEval('service') + + for service in services: + service_dict = {} + + xmldescr = service.xpathEval('description')[0] + xmloutput = service.xpathEval('output')[0] + + srvdescr = decode(xmldescr.get_content(), xmldescr.prop('encoding')) + retcode = service.xpathEval('returncode')[0].get_content() + output = decode(xmloutput.get_content(), xmldescr.prop('encoding')).rstrip() + + try: + timestamp = int(service.xpathEval('timestamp')[0].get_content()) + except: + timestamp = filetimestamp + + debug(2, verb, 'Found service "%s"' % srvdescr) + + service_dict = {'host_name':hostname, 'service_description':srvdescr, 'returncode':retcode, 'output':output, 'timestamp':timestamp} + checks.append(service_dict) + + debug(1, verb, 'Host: "%s" - Service: "%s" - RetCode: "%s" - Output: "%s"' % (hostname, srvdescr, retcode, output) ) + + return checks + + +def xml_from_dict(checks, encoding='base64'): + lasthost = None + + db = [(check['host_name'], check) for check in checks] + db.sort() + + xmldoc = libxml2.newDoc('1.0') + xmlroot = xmldoc.newChild(None, 'nagixsc', None) + xmlroot.setProp('version', '1.0') + xmltimestamp = xmlroot.newChild(None, 'timestamp', datetime.datetime.now().strftime('%s')) + + for entry in db: + check = entry[1] + + if check['host_name'] != lasthost: + xmlhost = xmlroot.newChild(None, 'host', None) + xmlhostname = xmlhost.newChild(None, 'name', encode(check['host_name'], encoding)) + lasthost = check['host_name'] + + xmlservice = xmlhost.newChild(None, 'service', None) + xmlname = xmlservice.newChild(None, 'description', encode(check['service_description'], encoding)) + xmlname.setProp('encoding', encoding) + xmlreturncode = xmlservice.newChild(None, 'returncode', str(check['returncode'])) + xmloutput = xmlservice.newChild(None, 'output', encode(check['output'], encoding)) + xmloutput.setProp('encoding', encoding) + if check.has_key('timestamp'): + xmltimestamp = xmlservice.newChild(None, 'timestamp', str(check['timestamp'])) + + return xmldoc + + +def check_mark_outdated(check, now, maxtimediff, markold): + timedelta = now - check['timestamp'] + if timedelta > maxtimediff: + check['output'] = 'Nag(ix)SC: Check result is %s(>%s) seconds old - %s' % (timedelta, maxtimediff, check['output']) + if markold: + check['returncode'] = 3 + return check + diff --git a/nagixsc_conf2xml.py b/nagixsc_conf2xml.py new file mode 100755 index 0000000..c58ad1e --- /dev/null +++ b/nagixsc_conf2xml.py @@ -0,0 +1,77 @@ +#!/usr/bin/python + +import ConfigParser +import optparse +import subprocess +import sys + +############################################################################## + +from nagixsc import * + +############################################################################## + +checks = [] + + +parser = optparse.OptionParser() + +parser.add_option('-c', '', dest='conffile', help='Config file') +parser.add_option('-o', '', dest='outfile', help='Output file') +parser.add_option('-e', '', dest='encoding', help='Encoding ("%s")' % '", "'.join(available_encodings()) ) +parser.add_option('-v', '', action='count', dest='verb', help='Verbose output') + +parser.set_defaults(conffile='nagixsc.conf') +parser.set_defaults(outfile='-') +parser.set_defaults(encoding='base64') +parser.set_defaults(verb=0) + +(options, args) = parser.parse_args() + +############################################################################## + +if options.encoding not in available_encodings(): + print 'Wrong encoding method "%s"!' % options.encoding + print 'Could be one of: %s' % ', '.join(available_encodings) + sys.exit(127) + +############################################################################## + +config = ConfigParser.RawConfigParser() +config.optionxform = str # We need case-sensitive options +conf_list = config.read(options.conffile) + +if conf_list == []: + print 'Config file "%s" could not be read!' % options.conffile + sys.exit(127) + +# Sections are Hosts (not 'nagixsc'), options in sections are Services +hosts = config.sections() +if 'nagixsc' in hosts: + hosts.remove('nagixsc') + +for host in hosts: + if config.has_option(host,'_host_name'): + host_name = config.get(host,'_host_name') + else: + host_name = host + + for service in config.options(host): + # If option starts with '_' it may be a NagixSC option in the future + if service[0] != '_': + cmdline = config.get(host, service) + + cmd = subprocess.Popen(cmdline.split(' '), stdout=subprocess.PIPE) + output = cmd.communicate()[0].rstrip() + retcode = cmd.returncode + + checks.append({'host_name':host_name, 'service_description':service, 'returncode':retcode, 'output':output, 'timestamp':datetime.datetime.now().strftime('%s')}) + + +xmldoc = xml_from_dict(checks, options.encoding) +if options.outfile == '-': + xmldoc.saveFormatFile('-', format=1) +else: + xmldoc.saveFile(options.outfile) + + diff --git a/nagixsc_xml2nagios.py b/nagixsc_xml2nagios.py new file mode 100755 index 0000000..4296ee5 --- /dev/null +++ b/nagixsc_xml2nagios.py @@ -0,0 +1,252 @@ +#!/usr/bin/python + +#import base64 +import datetime +import libxml2 +import optparse +import os +import random +import string +import sys + +NAGIOSCMDs = [ '/usr/local/nagios/var/rw/nagios.cmd', '/var/lib/nagios3/rw/nagios.cmd', ] +CHECKRESULTDIRs = [ '/usr/local/nagios/var/spool/checkresults', '/var/lib/nagios3/spool/checkresults', ] +MODEs = [ 'passive', 'passive_check', 'checkresult', 'checkresult_check', 'active', ] + +parser = optparse.OptionParser() + +parser.add_option('-u', '', dest='url', help='URL of status file (xml)') +parser.add_option('-f', '', dest='file', help='(Path and) file name of status file') +parser.add_option('-S', '', dest='schemacheck', help='Check XML against DTD') +parser.add_option('-s', '', dest='seconds', type='int', help='Maximum age in seconds of xml timestamp') +parser.add_option('-m', '', action='store_true', dest='markold', help='Mark (Set state) of too old checks as UNKNOWN') +parser.add_option('-O', '', dest='mode', help='Where/Howto output the results ("%s")' % '", "'.join(MODEs)) +parser.add_option('-p', '', dest='pipe', help='Full path to nagios.cmd') +parser.add_option('-r', '', dest='checkresultdir', help='Full path to checkresult directory') +parser.add_option('-H', '', dest='host', help='Hostname to search for in XML file') +parser.add_option('-D', '', dest='service', help='Service description to search for in XML file') +parser.add_option('-v', '', action='count', dest='verb', help='Verbose output') + +parser.set_defaults(url=None) +parser.set_defaults(file='nagixsc.xml') +parser.set_defaults(schemacheck='') +parser.set_defaults(seconds=14400) +parser.set_defaults(markold=False) +parser.set_defaults(mode=False) +parser.set_defaults(pipe=None) +parser.set_defaults(checkresultdir=None) +parser.set_defaults(host=None) +parser.set_defaults(service=None) +parser.set_defaults(verb=0) + +(options, args) = parser.parse_args() + +############################################################################## + +from nagixsc import * + +############################################################################## + +if options.mode not in MODEs: + print 'Not an allowed mode "%s" - allowed are: %s' % (options.mode, ", ".join(MODEs)) + sys.exit(127) + +# Check command line options wrt mode +if options.mode == 'passive' or options.mode == 'passive_check': + debug(1, options.verb, 'Running in passive mode') + if options.pipe == None and options.verb <= 2: + for nagioscmd in NAGIOSCMDs: + if os.path.exists(nagioscmd): + options.pipe = nagioscmd + + if options.pipe == None and options.verb <= 2: + print 'Need full path to the nagios.cmd pipe!' + sys.exit(127) + + debug(2, options.verb, 'nagios.cmd found at %s' % options.pipe) + +elif options.mode == 'checkresult' or options.mode == 'checkresult_check': + debug(1, options.verb, 'Running in checkresult mode') + if options.checkresultdir == None and options.verb <= 2: + for crd in CHECKRESULTDIRs: + if os.path.exists(crd): + options.checkresultdir = crd + + if options.checkresultdir == None and options.verb <= 2: + print 'Need full path to the checkresultdir!' + sys.exit(127) + + debug(2, options.verb, 'Checkresult dir: %s' % options.checkresultdir) + +elif options.mode == 'active': + debug(1, options.verb, 'Running in active/plugin mode') + if options.host == None: + debug(1, options.verb, 'No host specified, using first host in XML file') + if options.service == None: + print 'No service specified on command line!' + sys.exit(127) + +############################################################################## + +now = int(datetime.datetime.now().strftime('%s')) + +# Get URL or file +if options.url != None: + import urllib2 + + response = urllib2.urlopen(options.url) + doc = libxml2.parseDoc(response.read()) + response.close() +else: + doc = libxml2.parseFile(options.file) + + +# Check XML against DTD +if options.schemacheck: + dtd = libxml2.parseDTD(None, options.schemacheck) + ctxt = libxml2.newValidCtxt() + ret = doc.validateDtd(ctxt, dtd) + if ret != 1: + print "error doing DTD validation" + sys.exit(1) + dtd.freeDtd() + del dtd + del ctxt + + +# Check XML file basics +(status, statusstring) = xml_check_version(doc) +debug(1, options.verb, statusstring) +if not status: + print statusstring + sys.exit(127) + + +# Get timestamp and check it +filetimestamp = xml_get_timestamp(doc) +if not filetimestamp: + print 'No timestamp found in XML file, exiting because of invalid XML data...' + sys.exit(127) + +timedelta = int(now) - int(filetimestamp) +debug(1, options.verb, 'Age of XML file: %s seconds, max allowed: %s seconds' % (timedelta, options.seconds)) + + +# Put XML to Python dict +checks = xml_to_dict(doc, options.verb, options.host, options.service) + +# Loop over check results and perhaps mark them as outdated +for check in checks: + check = check_mark_outdated(check, now, options.seconds, options.markold) + + +# Next steps depend on mode, output results +# MODE: passive +if options.mode == 'passive' or options.mode == 'passive_check': + # Prepare + if options.verb <= 2: + pipe = open(options.pipe, "w") + else: + pipe = None + + # Output + for check in checks: + line = '[%s] PROCESS_SERVICE_CHECK_RESULT;%s;%s;%s;%s' % (now, check['host_name'], check['service_description'], check['returncode'], check['output'].replace('\n', '\\n')) + + if pipe: + pipe.write(line + '\n') + debug(2, options.verb, '%s / %s: %s - "%s"' % (check['host_name'], check['service_description'], check['returncode'], check['output'].replace('\n', '\\n'))) + debug(3, options.verb, line) + + # Close + if pipe: + pipe.close() + else: + print "Passive check results NOT written to Nagios pipe due to -vvv!" + + # Return/Exit as a Nagios Plugin if called with mode 'passive_check' + if options.mode == 'passive_check': + returncode = 0 + returnstring = 'OK' + output = '' + + if options.markold: + if (now - filetimestamp) > options.seconds: + returnstring = 'WARNING' + output = 'Check results are %s(>%s) seconds old' % ((now-filetimestamp), options.seconds) + returncode = 1 + + if not output: + output = 'Check results are %s seconds old' % (now-filetimestamp) + + print 'Nag(ix)SC %s - %s' % (returnstring, output) + sys.exit(returncode) + +# MODE: checkresult +elif options.mode == 'checkresult' or options.mode == 'checkresult_check': + count_services = 0 + count_failed = 0 + + chars = string.letters + string.digits + + for check in checks: + count_services += 1 + if check.has_key('timestamp'): + timestamp = check['timestamp'] + else: + timestamp = xml_get_timestamp(xmldoc) + + filename = os.path.join(options.checkresultdir, 'c' + ''.join([random.choice(chars) for i in range(6)])) + try: + crfile = open(filename, "w") + crfile.write('### Active Check Result File ###\nfile_time=%s\n\n### Nagios Service Check Result ###\n# Time: %s\nhost_name=%s\nservice_description=%s\ncheck_type=0\ncheck_options=0\nscheduled_check=1\nreschedule_check=1\nlatency=0.0\nstart_time=%s.00\nfinish_time=%s.05\nearly_timeout=0\nexited_ok=1\nreturn_code=%s\noutput=%s\n' % (timestamp, datetime.datetime.now().ctime(), check['host_name'], check['service_description'], timestamp, timestamp, check['returncode'], check['output'].replace('\n', '\\n') ) ) + crfile.close() + + # Create OK file + open(filename + '.ok', 'w').close() + except: + count_failed += 1 + if options.mode == 'checkresult': + print 'Could not write checkresult files "%s(.ok)" for "%s"/"%s"!' % (filename, check['host_name'], check['service_description']) + + if options.mode == 'checkresult_check': + returnstring = '' + output = '' + if count_failed == 0: + returnstring = 'OK' + returncode = 0 + output = 'Wrote checkresult files for %s services' % count_services + elif count_failed == count_services: + returnstring = 'CRITICAL' + returncode = 2 + output = 'No checkresult files could be writen!' + else: + returnstring = 'WARNING' + returncode = 1 + output = 'Could not write %s out of %s checkresult files!' % (count_failed, count_services) + + print 'Nag(ix)SC %s - %s' % (returnstring, output) + sys.exit(returncode) + + if count_failed == 0: + sys.exit(0) + else: + sys.exit(1) + +# MODE: active +elif options.mode == 'active': + + if len(checks) > 1: + print 'Nag(ix)SC UNKNOWN - Found more (%s) than one host/service!' % len(checks) + sys.exit(3) + elif len(checks) == 0: + print 'Nag(ix)SC UNKNOWN - No check for "%s"/"%s" found in XML' % (options.host, options.service) + sys.exit(3) + + print checks[0]['output'] + sys.exit(int(checks[0]['returncode'])) + +else: + print 'Unknown mode! This should NEVER happen!' + sys.exit(127) + diff --git a/xml/dtd_no_timestamp.xml b/xml/dtd_no_timestamp.xml new file mode 100644 index 0000000..c5a3ae8 --- /dev/null +++ b/xml/dtd_no_timestamp.xml @@ -0,0 +1,61 @@ + + + + + + + + host1 + + + RGlza19Ib21l + 2 + RElTSyBDUklUSUNBTCAtIGZyZWUgc3BhY2U6IC9ob21lIDc3NSBNQiAoMSUgaW5vZGU9OTElKTt8IC9ob21lPTY3NTg0TUI7NjE1MjM7NjQ5NDE7MDs2ODM1OQo= + 1234443420 + + + + RGlza19Sb290 + 0 + RElTSyBPSyAtIGZyZWUgc3BhY2U6IC8gMjE2NyBNQiAoMjIlIGlub2RlPTk3JSk7fCAvPTczNTNNQjs4NTY4OzkwNDQ7MDs5NTIwCg== + 1234443420 + + + + TG9hZA== + 0 + T0sgLSBsb2FkIGF2ZXJhZ2U6IDAuMDAsIDAuMDAsIDAuMDB8bG9hZDE9MC4wMDA7NS4wMDA7MTAuMDAwOzA7IGxvYWQ1PTAuMDAwOzUuMDAwOzEwLjAwMDswOyBsb2FkMTU9MC4wMDA7NS4wMDA7MTAuMDAwOzA7IAo= + 1234443420 + + + + + + + host2 + + + UHJvY3NfVG90YWw= + 0 + UFJPQ1MgT0s6IDE2MyBwcm9jZXNzZXMK + 1234443410 + + + + U3dhcA== + 0 + U1dBUCBPSyAtIDc5JSBmcmVlICgxNDkyIE1CIG91dCBvZiAxOTA2IE1CKSB8c3dhcD0xNDkyTUI7OTUzOzQ3NjswOzE5MDYK + + + + VXNlcnM= + 1 + VVNFUlMgV0FSTklORyAtIDExIHVzZXJzIGN1cnJlbnRseSBsb2dnZWQgaW4gfHVzZXJzPTExOzEwOzE1OzAKMyByb290IHNlc3Npb25zCjggbm9uLXJvb3Qgc2Vzc2lvbnM= + 1234443410 + + + + + + + diff --git a/xml/dtd_test.xml b/xml/dtd_test.xml new file mode 100644 index 0000000..bfbbe99 --- /dev/null +++ b/xml/dtd_test.xml @@ -0,0 +1,60 @@ + + + + 1234443430 + + + host1 + + + RGlza19Ib21l + 2 + RElTSyBDUklUSUNBTCAtIGZyZWUgc3BhY2U6IC9ob21lIDc3NSBNQiAoMSUgaW5vZGU9OTElKTt8IC9ob21lPTY3NTg0TUI7NjE1MjM7NjQ5NDE7MDs2ODM1OQo= + 1234443420 + + + + RGlza19Sb290 + 0 + RElTSyBPSyAtIGZyZWUgc3BhY2U6IC8gMjE2NyBNQiAoMjIlIGlub2RlPTk3JSk7fCAvPTczNTNNQjs4NTY4OzkwNDQ7MDs5NTIwCg== + 1234443420 + + + + TG9hZA== + 0 + T0sgLSBsb2FkIGF2ZXJhZ2U6IDAuMDAsIDAuMDAsIDAuMDB8bG9hZDE9MC4wMDA7NS4wMDA7MTAuMDAwOzA7IGxvYWQ1PTAuMDAwOzUuMDAwOzEwLjAwMDswOyBsb2FkMTU9MC4wMDA7NS4wMDA7MTAuMDAwOzA7IAo= + 1234443420 + + + + + + + host2 + + + UHJvY3NfVG90YWw= + 0 + UFJPQ1MgT0s6IDE2MyBwcm9jZXNzZXMK + 1234443410 + + + + U3dhcA== + 0 + U1dBUCBPSyAtIDc5JSBmcmVlICgxNDkyIE1CIG91dCBvZiAxOTA2IE1CKSB8c3dhcD0xNDkyTUI7OTUzOzQ3NjswOzE5MDYK + + + + VXNlcnM= + 1 + VVNFUlMgV0FSTklORyAtIDExIHVzZXJzIGN1cnJlbnRseSBsb2dnZWQgaW4gfHVzZXJzPTExOzEwOzE1OzAKMyByb290IHNlc3Npb25zCjggbm9uLXJvb3Qgc2Vzc2lvbnM= + 1234443410 + + + + + + + diff --git a/xml/nagixsc.dtd b/xml/nagixsc.dtd new file mode 100644 index 0000000..171df16 --- /dev/null +++ b/xml/nagixsc.dtd @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/xml/nagixsc.xml b/xml/nagixsc.xml new file mode 100644 index 0000000..5444ae9 --- /dev/null +++ b/xml/nagixsc.xml @@ -0,0 +1,60 @@ + + + + 1234443430 + + + host1 + + + RGlza19Ib21l + 2 + RElTSyBDUklUSUNBTCAtIGZyZWUgc3BhY2U6IC9ob21lIDc3NSBNQiAoMSUgaW5vZGU9OTElKTt8IC9ob21lPTY3NTg0TUI7NjE1MjM7NjQ5NDE7MDs2ODM1OQo= + 1234443420 + + + + RGlza19Sb290 + 0 + RElTSyBPSyAtIGZyZWUgc3BhY2U6IC8gMjE2NyBNQiAoMjIlIGlub2RlPTk3JSk7fCAvPTczNTNNQjs4NTY4OzkwNDQ7MDs5NTIwCg== + 1234443420 + + + + TG9hZA== + 0 + T0sgLSBsb2FkIGF2ZXJhZ2U6IDAuMDAsIDAuMDAsIDAuMDB8bG9hZDE9MC4wMDA7NS4wMDA7MTAuMDAwOzA7IGxvYWQ1PTAuMDAwOzUuMDAwOzEwLjAwMDswOyBsb2FkMTU9MC4wMDA7NS4wMDA7MTAuMDAwOzA7IAo= + 1234443420 + + + + + + + host2 + + + UHJvY3NfVG90YWw= + 0 + UFJPQ1MgT0s6IDE2MyBwcm9jZXNzZXMK + 1234443410 + + + + U3dhcA== + 0 + U1dBUCBPSyAtIDc5JSBmcmVlICgxNDkyIE1CIG91dCBvZiAxOTA2IE1CKSB8c3dhcD0xNDkyTUI7OTUzOzQ3NjswOzE5MDYK + + + + VXNlcnM= + 1 + VVNFUlMgV0FSTklORyAtIDExIHVzZXJzIGN1cnJlbnRseSBsb2dnZWQgaW4gfHVzZXJzPTExOzEwOzE1OzAKMyByb290IHNlc3Npb25zCjggbm9uLXJvb3Qgc2Vzc2lvbnM= + 1234443410 + + + + + + + diff --git a/xml/nagixsc.xsd b/xml/nagixsc.xsd new file mode 100644 index 0000000..5c82b91 --- /dev/null +++ b/xml/nagixsc.xsd @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- 2.30.2