Code

Add function to merge multiple XML files
[nagixsc.git] / nagixsc / __init__.py
1 import BaseHTTPServer
2 import ConfigParser
3 import SocketServer
4 import base64
5 import datetime
6 import libxml2
7 import mimetools
8 import os
9 import random
10 import shlex
11 import socket
12 import string
13 import subprocess
14 import sys
16 def debug(level, verb, string):
17         if level <= verb:
18                 print "%s: %s" % (level, string)
21 ##############################################################################
23 def available_encodings():
24         return ['base64', 'plain',]
27 def check_encoding(enc):
28         if enc in available_encodings():
29                 return True
30         else:
31                 return False
34 def decode(data, encoding):
35         if encoding == 'plain':
36                 return data
37         else:
38                 return base64.b64decode(data)
41 def encode(data, encoding=None):
42         if encoding == 'plain':
43                 return data
44         else:
45                 return base64.b64encode(data)
48 ##############################################################################
50 def read_inifile(inifile):
51         config = ConfigParser.RawConfigParser()
52         config.optionxform = str # We need case-sensitive options
53         ini_list = config.read(inifile)
55         if ini_list:
56                 return config
57         else:
58                 return False
61 ##############################################################################
63 def exec_check(host_name, service_descr, cmdline):
64         cmdarray = shlex.split(cmdline)
66         check = {}
67         check['host_name'] = host_name
68         check['service_description'] = service_descr
70         if len(cmdarray) == 0:
71                 check['output'] = 'No command line specified!'
72                 check['returncode'] = 127
73                 return check
75         try:
76                 cmd     = subprocess.Popen(cmdarray, stdout=subprocess.PIPE)
77                 check['output'] = cmd.communicate()[0].rstrip()
78                 check['returncode'] = cmd.returncode
79         except OSError:
80                 check['output'] = 'Could not execute "%s"' % cmdline
81                 check['returncode'] = 127
83         check['timestamp'] = datetime.datetime.now().strftime('%s')
84         return check
87 ##############################################################################
89 def conf2dict(config, opt_host=None, opt_service=None):
90         checks = []
92         # Sections are Hosts (not 'nagixsc'), options in sections are Services
93         hosts = config.sections()
94         if 'nagixsc' in hosts:
95                 hosts.remove('nagixsc')
97         # Filter out host/section if it exists
98         if opt_host:
99                 if opt_host in hosts:
100                         hosts = [opt_host,]
101                 else:
102                         hosts = []
104         for host in hosts:
105                 # Overwrite section/host name with '_host_name'
106                 if config.has_option(host,'_host_name'):
107                         host_name = config.get(host,'_host_name')
108                 else:
109                         host_name = host
112                 services = config.options(host)
113                 # Look for host check
114                 if '_host_check' in services and not opt_service:
115                         cmdline = config.get(host, '_host_check')
116                         check = exec_check(host_name, None, cmdline)
117                         checks.append(check)
120                 # Filter out service if given in cmd line options
121                 if opt_service:
122                         if opt_service in services:
123                                 services = [opt_service,]
124                         else:
125                                 services = []
127                 for service in services:
128                         # If option starts with '_' it may be a NagixSC option in the future
129                         if service[0] != '_':
130                                 cmdline = config.get(host, service)
132                                 check = exec_check(host_name, service, cmdline)
133                                 checks.append(check)
135         return checks
138 ##############################################################################
140 def dict2out_passive(checks, xmltimestamp, opt_pipe, opt_verb=0):
141         FORMAT_HOST = '[%s] PROCESS_HOST_CHECK_RESULT;%s;%s;%s'
142         FORMAT_SERVICE = '[%s] PROCESS_SERVICE_CHECK_RESULT;%s;%s;%s;%s'
143         count_services = 0
144         now = datetime.datetime.now().strftime('%s')
146         # Prepare
147         if opt_verb <= 2:
148                 pipe = open(opt_pipe, "w")
149         else:
150                 pipe = None
152         # Output
153         for check in checks:
154                 count_services += 1
155                 if check.has_key('timestamp'):
156                         timestamp = check['timestamp']
157                 else:
158                         timestamp = xmltimestamp
159                 count_services += 1
161                 if check['service_description'] == None or check['service_description'] == '':
162                         # Host check
163                         line = FORMAT_HOST % (now, check['host_name'], check['returncode'], check['output'].replace('\n', '\\n'))
164                 else:
165                         # Service check
166                         line =  FORMAT_SERVICE % (now, check['host_name'], check['service_description'], check['returncode'], check['output'].replace('\n', '\\n'))
168                 if pipe:
169                         pipe.write(line + '\n')
170                 debug(2, opt_verb, line)
172         # Close
173         if pipe:
174                 pipe.close()
175         else:
176                 print "Passive check results NOT written to Nagios pipe due to -vvv!"
178         return count_services
181 def dict2out_checkresult(checks, xmltimestamp, opt_checkresultdir, opt_verb):
182         count_services = 0
183         count_failed = 0
184         list_failed = []
185         chars = string.letters + string.digits
186         ctimestamp = datetime.datetime.now().ctime()
188         for check in checks:
189                 count_services += 1
190                 if check.has_key('timestamp'):
191                         timestamp = check['timestamp']
192                 else:
193                         timestamp = xmltimestamp
195                 filename = os.path.join(opt_checkresultdir, 'c' + ''.join([random.choice(chars) for i in range(6)]))
196                 try:
197                         crfile = open(filename, "w")
198                         if check['service_description'] == None or check['service_description'] == '':
199                                 # Host check
200                                 crfile.write('### Active Check Result File ###\nfile_time=%s\n\n### Nagios Service Check Result ###\n# Time: %s\nhost_name=%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, ctimestamp, check['host_name'], timestamp, timestamp, check['returncode'], check['output'].replace('\n', '\\n') ) )
201                         else:
202                                 # Service check
203                                 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, ctimestamp, check['host_name'], check['service_description'], timestamp, timestamp, check['returncode'], check['output'].replace('\n', '\\n') ) )
204                         crfile.close()
206                         # Create OK file
207                         open(filename + '.ok', 'w').close()
208                 except:
209                         count_failed += 1
210                         list_failed.append([filename, check['host_name'], check['service_description']])
212         return (count_services, count_failed, list_failed)
215 ##############################################################################
217 def read_xml(options):
218         if options.url != None:
219                 import urllib2
221                 if options.httpuser and options.httppasswd:
222                         passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
223                         passman.add_password(None, options.url, options.httpuser, options.httppasswd)
224                         authhandler = urllib2.HTTPBasicAuthHandler(passman)
225                         opener = urllib2.build_opener(authhandler)
226                         urllib2.install_opener(opener)
228                 try:
229                         response = urllib2.urlopen(options.url)
230                 except urllib2.HTTPError, error:
231                         print error
232                         sys.exit(0)
233                 except urllib2.URLError, error:
234                         print error.reason[1]
235                         sys.exit(0)
237                 doc = libxml2.parseDoc(response.read())
238                 response.close()
240         else:
241                 doc = libxml2.parseFile(options.file)
243         return doc
246 def read_xml_from_string(content):
247         return libxml2.parseDoc(content)
250 ##############################################################################
252 def xml_check_version(xmldoc):
253         # FIXME: Check XML structure
254         try:
255                 xmlnagixsc = xmldoc.xpathNewContext().xpathEval('/nagixsc')[0]
256         except:
257                 return (False, 'Not a Nag(IX)SC XML file!')
259         try:
260                 if xmlnagixsc.prop('version') != "1.0":
261                         return (False, 'Wrong version (found "%s", need "1.0") of XML file!' % xmlnagixsc.prop('version'))
262         except:
263                 return (False, 'No version information found in XML file!')
265         return (True, 'XML seems to be ok')
268 def xml_get_timestamp(xmldoc):
269         try:
270                 timestamp = int(xmldoc.xpathNewContext().xpathEval('/nagixsc/timestamp')[0].get_content())
271         except:
272                 return False
274         return timestamp
277 def xml_to_dict(xmldoc, verb=0, hostfilter=None, servicefilter=None):
278         checks = []
279         now = int(datetime.datetime.now().strftime('%s'))
280         filetimestamp = reset_future_timestamp(xml_get_timestamp(xmldoc), now)
282         if hostfilter:
283                 hosts = xmldoc.xpathNewContext().xpathEval('/nagixsc/host[name="%s"] | /nagixsc/host[name="%s"]' % (hostfilter, encode(hostfilter)))
284         else:
285                 hosts = xmldoc.xpathNewContext().xpathEval('/nagixsc/host')
287         for host in hosts:
288                 xmlhostname = host.xpathEval('name')[0]
289                 hostname = decode(xmlhostname.get_content(), xmlhostname.prop('encoding'))
290                 debug(2, verb, 'Found host "%s"' % hostname)
292                 # Look for Host check result
293                 if host.xpathEval('returncode'):
294                         retcode   = host.xpathEval('returncode')[0].get_content()
295                 else:
296                         retcode   = None
298                 if host.xpathEval('output'):
299                         xmloutput = host.xpathEval('output')[0]
300                         output    = decode(xmloutput.get_content(), xmloutput.prop('encoding')).rstrip()
301                 else:
302                         output    = None
304                 if host.xpathEval('timestamp'):
305                         timestamp = reset_future_timestamp(int(host.xpathEval('timestamp')[0].get_content()), now)
306                 else:
307                         timestamp = filetimestamp
309                 # Append only if no service filter
310                 if not servicefilter and retcode and output:
311                         checks.append({'host_name':hostname, 'service_description':None, 'returncode':retcode, 'output':output, 'timestamp':timestamp})
314                 # Look for service filter
315                 if servicefilter:
316                         services = host.xpathEval('service[description="%s"] | service[description="%s"]' % (servicefilter, encode(servicefilter)))
317                 else:
318                         services = host.xpathEval('service')
320                 # Loop over services in host
321                 for service in services:
322                         service_dict = {}
324                         xmldescr  = service.xpathEval('description')[0]
325                         xmloutput = service.xpathEval('output')[0]
327                         srvdescr = decode(xmldescr.get_content(), xmldescr.prop('encoding'))
328                         retcode  = service.xpathEval('returncode')[0].get_content()
329                         output   = decode(xmloutput.get_content(), xmloutput.prop('encoding')).rstrip()
331                         try:
332                                 timestamp = reset_future_timestamp(int(service.xpathEval('timestamp')[0].get_content()), now)
333                         except:
334                                 timestamp = filetimestamp
336                         debug(2, verb, 'Found service "%s"' % srvdescr)
338                         service_dict = {'host_name':hostname, 'service_description':srvdescr, 'returncode':retcode, 'output':output, 'timestamp':timestamp}
339                         checks.append(service_dict)
341                         debug(1, verb, 'Host: "%s" - Service: "%s" - RetCode: "%s" - Output: "%s"' % (hostname, srvdescr, retcode, output) )
343         return checks
346 def xml_from_dict(checks, encoding='base64'):
347         lasthost = None
349         db = [(check['host_name'], check) for check in checks]
350         db.sort()
352         xmldoc = libxml2.newDoc('1.0')
353         xmlroot = xmldoc.newChild(None, 'nagixsc', None)
354         xmlroot.setProp('version', '1.0')
355         xmltimestamp = xmlroot.newChild(None, 'timestamp', datetime.datetime.now().strftime('%s'))
357         for entry in db:
358                 check = entry[1]
360                 if check['host_name'] != lasthost:
361                         xmlhost = xmlroot.newChild(None, 'host', None)
362                         xmlhostname = xmlhost.newChild(None, 'name', encode(check['host_name'], encoding))
363                         lasthost = check['host_name']
365                 if check['service_description'] == '' or check['service_description'] == None:
366                         # Host check result
367                         xmlreturncode = xmlhost.newChild(None, 'returncode', str(check['returncode']))
368                         xmloutput     = xmlhost.newChild(None, 'output', encode(check['output'], encoding))
369                         xmloutput.setProp('encoding', encoding)
370                         if check.has_key('timestamp'):
371                                 xmltimestamp  = xmlhost.newChild(None, 'timestamp', str(check['timestamp']))
372                 else:
373                         # Service check result
374                         xmlservice    = xmlhost.newChild(None, 'service', None)
375                         xmlname       = xmlservice.newChild(None, 'description', encode(check['service_description'], encoding))
376                         xmlname.setProp('encoding', encoding)
377                         xmlreturncode = xmlservice.newChild(None, 'returncode', str(check['returncode']))
378                         xmloutput     = xmlservice.newChild(None, 'output', encode(check['output'], encoding))
379                         xmloutput.setProp('encoding', encoding)
380                         if check.has_key('timestamp'):
381                                 xmltimestamp  = xmlservice.newChild(None, 'timestamp', str(check['timestamp']))
383         return xmldoc
386 def xml_merge(xmldocs):
387         checks = []
388         for xmldoc in xmldocs:
389                 checks.extend(xml_to_dict(xmldoc))
390         newxmldoc = xml_from_dict(checks)
391         return newxmldoc
394 def check_mark_outdated(check, now, maxtimediff, markold):
395         timedelta = now - check['timestamp']
396         if timedelta > maxtimediff:
397                 check['output'] = 'Nag(ix)SC: Check result is %s(>%s) seconds old - %s' % (timedelta, maxtimediff, check['output'])
398                 if markold:
399                         check['returncode'] = 3
400         return check
403 def reset_future_timestamp(timestamp, now):
404         if timestamp <= now:
405                 return timestamp
406         else:
407                 return now
409 ##############################################################################
411 def encode_multipart(xmldoc, httpuser, httppasswd):
412         BOUNDARY = mimetools.choose_boundary()
413         CRLF = '\r\n'
414         L = []
415         L.append('--' + BOUNDARY)
416         L.append('Content-Disposition: form-data; name="xmlfile"; filename="xmlfile"')
417         L.append('Content-Type: application/xml')
418         L.append('')
419         L.append(xmldoc.serialize())
420         L.append('--' + BOUNDARY + '--')
421         L.append('')
422         body = CRLF.join(L)
423         content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
424         headers = {'Content-Type': content_type, 'Content-Length': str(len(body))}
426         if httpuser and httppasswd:
427                 headers['Authorization'] = 'Basic %s' % base64.b64encode(':'.join([httpuser, httppasswd]))
429         return (headers, body)
431 ##############################################################################
433 def daemonize(pidfile=None, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
434         # 1st fork
435         try:
436                 pid = os.fork()
437                 if pid > 0:
438                         sys.exit(0)
439         except OSError, e:
440                 sys.stderr.write("1st fork failed: (%d) %sn" % (e.errno, e.strerror))
441                 sys.exit(1)
442         # Prepare 2nd fork
443         os.chdir("/")
444         os.umask(0)
445         os.setsid( )
446         # 2nd fork
447         try:
448                 pid = os.fork()
449                 if pid > 0:
450                         sys.exit(0)
451         except OSError, e:
452                 sys.stderr.write("2nd fork failed: (%d) %sn" % (e.errno, e.strerror))
453                 sys.exit(1)
454         # Redirect stdin, stdout, stderr
455         sys.stdout.flush()
456         sys.stderr.flush()
457         si = file(stdin, 'r')
458         so = file(stdout, 'a+')
459         se = file(stderr, 'a+', 0)
460         os.dup2(si.fileno(), sys.stdin.fileno())
461         os.dup2(so.fileno(), sys.stdout.fileno())
462         os.dup2(se.fileno(), sys.stderr.fileno())
464         if pidfile:
465                 pid = str(os.getpid())
466                 file(pidfile, 'w+').write('%s\n' % pid)
468         return
470 ##############################################################################
472 class MyHTTPServer(BaseHTTPServer.HTTPServer):
473         def __init__(self, server_address, HandlerClass, ssl=False, sslpemfile=None):
474                 if ssl:
475                         # FIXME: SSL is in Py2.6
476                         try:
477                                 from OpenSSL import SSL
478                         except:
479                                 print 'No Python OpenSSL wrapper/bindings found!'
480                                 sys.exit(127)
482                         SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
483                         context = SSL.Context(SSL.SSLv23_METHOD)
484                         context.use_privatekey_file (sslpemfile)
485                         context.use_certificate_file(sslpemfile)
486                         self.socket = SSL.Connection(context, socket.socket(self.address_family, self.socket_type))
487                 else:
488                         SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
489                         self.socket = socket.socket(self.address_family, self.socket_type)
491                 self.server_bind()
492                 self.server_activate()
495 class MyHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
496         def setup(self):
497                 self.connection = self.request
498                 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
499                 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
501 ##############################################################################