Code

Initial commit
authorSven Velt <sven@velt.de>
Sun, 29 Nov 2009 19:26:08 +0000 (20:26 +0100)
committerSven Velt <sven@velt.de>
Sun, 29 Nov 2009 19:26:08 +0000 (20:26 +0100)
14 files changed:
.gitignore [new file with mode: 0644]
QUICKSTART.de.txt [new file with mode: 0644]
dummy_read_xml.py [new file with mode: 0755]
dummy_write_xml.py [new file with mode: 0755]
examples/nagixsc_debian.conf [new file with mode: 0644]
examples/nagixsc_usrlocal.conf [new file with mode: 0644]
nagixsc.py [new file with mode: 0644]
nagixsc_conf2xml.py [new file with mode: 0755]
nagixsc_xml2nagios.py [new file with mode: 0755]
xml/dtd_no_timestamp.xml [new file with mode: 0644]
xml/dtd_test.xml [new file with mode: 0644]
xml/nagixsc.dtd [new file with mode: 0644]
xml/nagixsc.xml [new file with mode: 0644]
xml/nagixsc.xsd [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..75ec6e7
--- /dev/null
@@ -0,0 +1,3 @@
+*.pyc
+.*.swp
+
diff --git a/QUICKSTART.de.txt b/QUICKSTART.de.txt
new file mode 100644 (file)
index 0000000..6b0c3df
--- /dev/null
@@ -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 (executable)
index 0000000..d74d64d
--- /dev/null
@@ -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 (executable)
index 0000000..008d0b7
--- /dev/null
@@ -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 (file)
index 0000000..1b82196
--- /dev/null
@@ -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 (file)
index 0000000..2aac151
--- /dev/null
@@ -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 (file)
index 0000000..4903509
--- /dev/null
@@ -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 (executable)
index 0000000..c58ad1e
--- /dev/null
@@ -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 (executable)
index 0000000..4296ee5
--- /dev/null
@@ -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 (file)
index 0000000..c5a3ae8
--- /dev/null
@@ -0,0 +1,61 @@
+<?xml version="1.0"?>
+
+<!DOCTYPE nagixsc SYSTEM "nagixsc.dtd">
+
+<nagixsc version="1.0">
+
+       <host>
+               <name encoding="plain">host1</name>
+
+               <service>
+                       <description encoding="base64">RGlza19Ib21l</description>
+                       <returncode>2</returncode>
+                       <output encoding="base64">RElTSyBDUklUSUNBTCAtIGZyZWUgc3BhY2U6IC9ob21lIDc3NSBNQiAoMSUgaW5vZGU9OTElKTt8IC9ob21lPTY3NTg0TUI7NjE1MjM7NjQ5NDE7MDs2ODM1OQo=</output>
+                       <timestamp>1234443420</timestamp>
+               </service>
+
+               <service>
+                       <description encoding="base64">RGlza19Sb290</description>
+                       <returncode>0</returncode>
+                       <output encoding="base64">RElTSyBPSyAtIGZyZWUgc3BhY2U6IC8gMjE2NyBNQiAoMjIlIGlub2RlPTk3JSk7fCAvPTczNTNNQjs4NTY4OzkwNDQ7MDs5NTIwCg==</output>
+                       <timestamp>1234443420</timestamp>
+               </service>
+
+               <service>
+                       <description encoding="base64">TG9hZA==</description>
+                       <returncode>0</returncode>
+                       <output encoding="base64">T0sgLSBsb2FkIGF2ZXJhZ2U6IDAuMDAsIDAuMDAsIDAuMDB8bG9hZDE9MC4wMDA7NS4wMDA7MTAuMDAwOzA7IGxvYWQ1PTAuMDAwOzUuMDAwOzEwLjAwMDswOyBsb2FkMTU9MC4wMDA7NS4wMDA7MTAuMDAwOzA7IAo=</output>
+                       <timestamp>1234443420</timestamp>
+               </service>
+
+       </host>
+
+
+       <host>
+               <name encoding="plain">host2</name>
+
+               <service>
+                       <description encoding="base64">UHJvY3NfVG90YWw=</description>
+                       <returncode>0</returncode>
+                       <output encoding="base64">UFJPQ1MgT0s6IDE2MyBwcm9jZXNzZXMK</output>
+                       <timestamp>1234443410</timestamp>
+               </service>
+
+               <service>
+                       <description encoding="base64">U3dhcA==</description>
+                       <returncode>0</returncode>
+                       <output encoding="base64">U1dBUCBPSyAtIDc5JSBmcmVlICgxNDkyIE1CIG91dCBvZiAxOTA2IE1CKSB8c3dhcD0xNDkyTUI7OTUzOzQ3NjswOzE5MDYK</output>
+               </service>
+
+               <service>
+                       <description encoding="base64">VXNlcnM=</description>
+                       <returncode>1</returncode>
+                       <output encoding="base64">VVNFUlMgV0FSTklORyAtIDExIHVzZXJzIGN1cnJlbnRseSBsb2dnZWQgaW4gfHVzZXJzPTExOzEwOzE1OzAKMyByb290IHNlc3Npb25zCjggbm9uLXJvb3Qgc2Vzc2lvbnM=</output>
+                       <timestamp>1234443410</timestamp>
+               </service>
+
+       </host>
+
+
+</nagixsc>
+
diff --git a/xml/dtd_test.xml b/xml/dtd_test.xml
new file mode 100644 (file)
index 0000000..bfbbe99
--- /dev/null
@@ -0,0 +1,60 @@
+<?xml version="1.0"?>
+<!DOCTYPE nagixsc SYSTEM "nagixsc.dtd">
+<nagixsc version="1.0">
+       <timestamp>1234443430</timestamp>
+
+       <host>
+               <name encoding="plain">host1</name>
+
+               <service>
+                       <description encoding="base64">RGlza19Ib21l</description>
+                       <returncode>2</returncode>
+                       <output encoding="base64">RElTSyBDUklUSUNBTCAtIGZyZWUgc3BhY2U6IC9ob21lIDc3NSBNQiAoMSUgaW5vZGU9OTElKTt8IC9ob21lPTY3NTg0TUI7NjE1MjM7NjQ5NDE7MDs2ODM1OQo=</output>
+                       <timestamp>1234443420</timestamp>
+               </service>
+
+               <service>
+                       <description encoding="base64">RGlza19Sb290</description>
+                       <returncode>0</returncode>
+                       <output encoding="base64">RElTSyBPSyAtIGZyZWUgc3BhY2U6IC8gMjE2NyBNQiAoMjIlIGlub2RlPTk3JSk7fCAvPTczNTNNQjs4NTY4OzkwNDQ7MDs5NTIwCg==</output>
+                       <timestamp>1234443420</timestamp>
+               </service>
+
+               <service>
+                       <description encoding="base64">TG9hZA==</description>
+                       <returncode>0</returncode>
+                       <output encoding="base64">T0sgLSBsb2FkIGF2ZXJhZ2U6IDAuMDAsIDAuMDAsIDAuMDB8bG9hZDE9MC4wMDA7NS4wMDA7MTAuMDAwOzA7IGxvYWQ1PTAuMDAwOzUuMDAwOzEwLjAwMDswOyBsb2FkMTU9MC4wMDA7NS4wMDA7MTAuMDAwOzA7IAo=</output>
+                       <timestamp>1234443420</timestamp>
+               </service>
+
+       </host>
+
+
+       <host>
+               <name encoding="plain">host2</name>
+
+               <service>
+                       <description encoding="base64">UHJvY3NfVG90YWw=</description>
+                       <returncode>0</returncode>
+                       <output encoding="base64">UFJPQ1MgT0s6IDE2MyBwcm9jZXNzZXMK</output>
+                       <timestamp>1234443410</timestamp>
+               </service>
+
+               <service>
+                       <description encoding="base64">U3dhcA==</description>
+                       <returncode>0</returncode>
+                       <output encoding="base64">U1dBUCBPSyAtIDc5JSBmcmVlICgxNDkyIE1CIG91dCBvZiAxOTA2IE1CKSB8c3dhcD0xNDkyTUI7OTUzOzQ3NjswOzE5MDYK</output>
+               </service>
+
+               <service>
+                       <description encoding="base64">VXNlcnM=</description>
+                       <returncode>1</returncode>
+                       <output encoding="base64">VVNFUlMgV0FSTklORyAtIDExIHVzZXJzIGN1cnJlbnRseSBsb2dnZWQgaW4gfHVzZXJzPTExOzEwOzE1OzAKMyByb290IHNlc3Npb25zCjggbm9uLXJvb3Qgc2Vzc2lvbnM=</output>
+                       <timestamp>1234443410</timestamp>
+               </service>
+
+       </host>
+
+
+</nagixsc>
+
diff --git a/xml/nagixsc.dtd b/xml/nagixsc.dtd
new file mode 100644 (file)
index 0000000..171df16
--- /dev/null
@@ -0,0 +1,21 @@
+<!ELEMENT nagixsc (timestamp,host+)>
+<!ATTLIST nagixsc version CDATA #IMPLIED >
+
+<!ELEMENT timestamp (#PCDATA)>
+
+<!ELEMENT host (name,service+)>
+<!ATTLIST host encoding CDATA #IMPLIED >
+
+<!ELEMENT name (#PCDATA)>
+<!ATTLIST name encoding CDATA #IMPLIED >
+
+<!ELEMENT service (description,returncode,output,timestamp?)>
+
+<!ELEMENT description (#PCDATA)>
+<!ATTLIST description encoding CDATA #IMPLIED >
+
+<!ELEMENT returncode (#PCDATA)>
+
+<!ELEMENT output (#PCDATA)>
+<!ATTLIST output encoding CDATA #IMPLIED >
+
diff --git a/xml/nagixsc.xml b/xml/nagixsc.xml
new file mode 100644 (file)
index 0000000..5444ae9
--- /dev/null
@@ -0,0 +1,60 @@
+<?xml version="1.0"?>
+
+<nagixsc version="1.0">
+       <timestamp>1234443430</timestamp>
+
+       <host>
+               <name encoding="plain">host1</name>
+
+               <service>
+                       <description encoding="base64">RGlza19Ib21l</description>
+                       <returncode>2</returncode>
+                       <output encoding="base64">RElTSyBDUklUSUNBTCAtIGZyZWUgc3BhY2U6IC9ob21lIDc3NSBNQiAoMSUgaW5vZGU9OTElKTt8IC9ob21lPTY3NTg0TUI7NjE1MjM7NjQ5NDE7MDs2ODM1OQo=</output>
+                       <timestamp>1234443420</timestamp>
+               </service>
+
+               <service>
+                       <description encoding="base64">RGlza19Sb290</description>
+                       <returncode>0</returncode>
+                       <output encoding="base64">RElTSyBPSyAtIGZyZWUgc3BhY2U6IC8gMjE2NyBNQiAoMjIlIGlub2RlPTk3JSk7fCAvPTczNTNNQjs4NTY4OzkwNDQ7MDs5NTIwCg==</output>
+                       <timestamp>1234443420</timestamp>
+               </service>
+
+               <service>
+                       <description encoding="base64">TG9hZA==</description>
+                       <returncode>0</returncode>
+                       <output encoding="base64">T0sgLSBsb2FkIGF2ZXJhZ2U6IDAuMDAsIDAuMDAsIDAuMDB8bG9hZDE9MC4wMDA7NS4wMDA7MTAuMDAwOzA7IGxvYWQ1PTAuMDAwOzUuMDAwOzEwLjAwMDswOyBsb2FkMTU9MC4wMDA7NS4wMDA7MTAuMDAwOzA7IAo=</output>
+                       <timestamp>1234443420</timestamp>
+               </service>
+
+       </host>
+
+
+       <host>
+               <name encoding="plain">host2</name>
+
+               <service>
+                       <description encoding="base64">UHJvY3NfVG90YWw=</description>
+                       <returncode>0</returncode>
+                       <output encoding="base64">UFJPQ1MgT0s6IDE2MyBwcm9jZXNzZXMK</output>
+                       <timestamp>1234443410</timestamp>
+               </service>
+
+               <service>
+                       <description encoding="base64">U3dhcA==</description>
+                       <returncode>0</returncode>
+                       <output encoding="base64">U1dBUCBPSyAtIDc5JSBmcmVlICgxNDkyIE1CIG91dCBvZiAxOTA2IE1CKSB8c3dhcD0xNDkyTUI7OTUzOzQ3NjswOzE5MDYK</output>
+               </service>
+
+               <service>
+                       <description encoding="base64">VXNlcnM=</description>
+                       <returncode>1</returncode>
+                       <output encoding="base64">VVNFUlMgV0FSTklORyAtIDExIHVzZXJzIGN1cnJlbnRseSBsb2dnZWQgaW4gfHVzZXJzPTExOzEwOzE1OzAKMyByb290IHNlc3Npb25zCjggbm9uLXJvb3Qgc2Vzc2lvbnM=</output>
+                       <timestamp>1234443410</timestamp>
+               </service>
+
+       </host>
+
+
+</nagixsc>
+
diff --git a/xml/nagixsc.xsd b/xml/nagixsc.xsd
new file mode 100644 (file)
index 0000000..5c82b91
--- /dev/null
@@ -0,0 +1,28 @@
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+       <xsd:element name="nagixsc" minOccurs="1" maxOccurs="1">
+               <xsd:complexType>
+                       <xsd:sequence>
+                               <xsd:element name="timestamp" type="xsd:long" minOccurs="1" maxOccurs="1" />
+
+                               <xsd:element name="host" type="xsd:string" minOccurs="1" maxOccurs="unbounded">
+                                       <xsd:sequence>
+
+                                               <xsd:element name="service" type="xsd:string" minOccurs="1" maxOccurs="unbounded">
+                                                       <xsd:sequence>
+                                                               <xsd:element name="description" type="xsd:string" minOccurs="1" maxOccurs="1" />
+                                                               <xsd:element name="returncode" type="xsd:string" minOccurs="1" maxOccurs="1" />
+                                                               <xsd:element name="output" type="xsd:int" minOccurs="1" maxOccurs="1" />
+                                                               <xsd:element name="timestamp" type="xsd:string" minOccurs="0" maxOccurs="1" />
+                                                       </xsd:sequence>
+                                               </xsd:element>
+
+                                       </xsd:sequence>
+                               </xsd:element>
+
+                       </xsd:sequence>
+               </xsd:complexType>
+
+       </xsd:element>
+
+</xsd:schema>
+