X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=nagixsc%2F__init__.py;h=030b86da2805ddf724cf9892fb49d4e6a5a2d280;hb=700904cb0a254d0230fb829f7c0dacfe84676f6d;hp=825d41d25196eebff8ab1cb3240bda92f2a70526;hpb=e9b31f439a14be0c6a562108ec947a2d78e06107;p=nagixsc.git diff --git a/nagixsc/__init__.py b/nagixsc/__init__.py index 825d41d..030b86d 100644 --- a/nagixsc/__init__.py +++ b/nagixsc/__init__.py @@ -1,17 +1,36 @@ +# Nag(ix)SC -- __init__.py +# +# Copyright (C) 2009-2010 Sven Velt +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; only version 2 of the License is applicable. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + import BaseHTTPServer import ConfigParser import SocketServer import base64 -import datetime import libxml2 import mimetools import os import random import shlex +import signal import socket import string import subprocess import sys +import time +import urllib2 def debug(level, verb, string): if level <= verb: @@ -20,6 +39,11 @@ def debug(level, verb, string): ############################################################################## +class ExecTimeoutError(Exception): + pass + +############################################################################## + def available_encodings(): return ['base64', 'plain',] @@ -60,7 +84,10 @@ def read_inifile(inifile): ############################################################################## -def exec_check(host_name, service_descr, cmdline): +def exec_timeout_handler(signum, frame): + raise ExecTimeoutError + +def exec_check(host_name, service_descr, cmdline, cmdprefix='', timeout=None, timeout_returncode=2): cmdarray = shlex.split(cmdline) check = {} @@ -72,15 +99,41 @@ def exec_check(host_name, service_descr, cmdline): check['returncode'] = 127 return check + check['commandline'] = cmdline + check['command'] = cmdarray[0].split(os.path.sep)[-1] + + if cmdprefix: + check['fullcommandline'] = cmdprefix + ' ' + cmdline + cmdarray = shlex.split(cmdprefix) + cmdarray + else: + check['fullcommandline'] = cmdline + + if timeout: + signal.signal(signal.SIGALRM, exec_timeout_handler) + signal.alarm(timeout) + try: - cmd = subprocess.Popen(cmdarray, stdout=subprocess.PIPE) + cmd = subprocess.Popen(cmdarray, stdout=subprocess.PIPE) check['output'] = cmd.communicate()[0].rstrip() check['returncode'] = cmd.returncode except OSError: check['output'] = 'Could not execute "%s"' % cmdline check['returncode'] = 127 + except ExecTimeoutError: + check['output'] = 'Plugin timed out after %s seconds' % timeout + check['returncode'] = timeout_returncode + + if timeout: + signal.alarm(0) + try: + if sys.version_info >= (2, 6): + cmd.terminate() + else: + os.kill(cmd.pid, 15) + except OSError: + pass - check['timestamp'] = datetime.datetime.now().strftime('%s') + check['timestamp'] = str(long(time.time())) return check @@ -89,6 +142,30 @@ def exec_check(host_name, service_descr, cmdline): def conf2dict(config, opt_host=None, opt_service=None): checks = [] + # Read "plugin_timeout" from "[nagixsc]", default "None" (no timeout) + try: + timeout = config.getint('nagixsc','plugin_timeout') + except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): + timeout = None + + # Read "plugin_timeout_returncode" from "[nagixsc]", default "2" (CRITICAL) + try: + timeout_returncode = config.getint('nagixsc','plugin_timeout_returncode') + except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): + timeout_returncode = 2 + + # Read "add_pnp4nagios_template_hint" from "[nagixsc]", default "False" + try: + add_pnp4nagios_template_hint = config.getboolean('nagixsc','add_pnp4nagios_template_hint') + except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): + add_pnp4nagios_template_hint = False + + # Read "command_prefix" from "[nagixsc]", default "" (empty string) + try: + cmdprefix_conffile = config.get('nagixsc','command_prefix') + except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): + cmdprefix_conffile = '' + # Sections are Hosts (not 'nagixsc'), options in sections are Services hosts = config.sections() if 'nagixsc' in hosts: @@ -110,10 +187,18 @@ def conf2dict(config, opt_host=None, opt_service=None): services = config.options(host) + # Look for host/section specific "command_prefix" + if '_command_prefix' in services: + cmdprefix = config.get(host, '_command_prefix') + else: + cmdprefix = cmdprefix_conffile + # Look for host check if '_host_check' in services and not opt_service: cmdline = config.get(host, '_host_check') - check = exec_check(host_name, None, cmdline) + check = exec_check(host_name, None, cmdline, cmdprefix, timeout, timeout_returncode) + if add_pnp4nagios_template_hint and '|' in check['output']: + check['output'] += ' [%s]' % check['command'] checks.append(check) @@ -129,7 +214,9 @@ def conf2dict(config, opt_host=None, opt_service=None): if service[0] != '_': cmdline = config.get(host, service) - check = exec_check(host_name, service, cmdline) + check = exec_check(host_name, service, cmdline, cmdprefix, timeout, timeout_returncode) + if add_pnp4nagios_template_hint and '|' in check['output']: + check['output'] += ' [%s]' % check['command'] checks.append(check) return checks @@ -141,7 +228,7 @@ def dict2out_passive(checks, xmltimestamp, opt_pipe, opt_verb=0): FORMAT_HOST = '[%s] PROCESS_HOST_CHECK_RESULT;%s;%s;%s' FORMAT_SERVICE = '[%s] PROCESS_SERVICE_CHECK_RESULT;%s;%s;%s;%s' count_services = 0 - now = datetime.datetime.now().strftime('%s') + now = str(long(time.time())) # Prepare if opt_verb <= 2: @@ -156,14 +243,13 @@ def dict2out_passive(checks, xmltimestamp, opt_pipe, opt_verb=0): timestamp = check['timestamp'] else: timestamp = xmltimestamp - count_services += 1 if check['service_description'] == None or check['service_description'] == '': # Host check - line = FORMAT_HOST % (now, check['host_name'], check['returncode'], check['output'].replace('\n', '\\n')) + line = FORMAT_HOST % (timestamp, check['host_name'], check['returncode'], check['output'].replace('\n', '\\n')) else: # Service check - line = FORMAT_SERVICE % (now, check['host_name'], check['service_description'], check['returncode'], check['output'].replace('\n', '\\n')) + line = FORMAT_SERVICE % (timestamp, check['host_name'], check['service_description'], check['returncode'], check['output'].replace('\n', '\\n')) if pipe: pipe.write(line + '\n') @@ -178,12 +264,13 @@ def dict2out_passive(checks, xmltimestamp, opt_pipe, opt_verb=0): return count_services -def dict2out_checkresult(checks, xmltimestamp, opt_checkresultdir, opt_verb): +def dict2out_checkresult(checks, xmltimestamp, opt_checkresultdir, opt_verb=0): count_services = 0 count_failed = 0 list_failed = [] chars = string.letters + string.digits - ctimestamp = datetime.datetime.now().ctime() + ctimestamp = time.ctime() + random.seed() for check in checks: count_services += 1 @@ -216,7 +303,6 @@ def dict2out_checkresult(checks, xmltimestamp, opt_checkresultdir, opt_verb): def read_xml(options): if options.url != None: - import urllib2 if options.httpuser and options.httppasswd: passman = urllib2.HTTPPasswordMgrWithDefaultRealm() @@ -247,6 +333,34 @@ def read_xml_from_string(content): return libxml2.parseDoc(content) +def write_xml(xmldoc, outfile, httpuser=None, httppasswd=None): + if outfile.startswith('http'): + (headers, body) = encode_multipart(xmldoc, httpuser, httppasswd) + response = urllib2.urlopen(urllib2.Request(outfile, body, headers)).read() + return response + + elif outfile == '-': + xmldoc.saveFormatFile('-', format=1) + return None + + else: + xmldoc.saveFile(outfile) + return None + + +def write_xml_or_die(xmldoc, outfile, httpuser=None, httppasswd=None): + try: + response = write_xml(xmldoc, outfile, httpuser, httppasswd) + except urllib2.HTTPError, error: + print error + sys.exit(11) + except urllib2.URLError, error: + print error.reason[1] + sys.exit(12) + + return response + + ############################################################################## def xml_check_version(xmldoc): @@ -276,7 +390,7 @@ def xml_get_timestamp(xmldoc): def xml_to_dict(xmldoc, verb=0, hostfilter=None, servicefilter=None): checks = [] - now = int(datetime.datetime.now().strftime('%s')) + now = long(time.time()) filetimestamp = reset_future_timestamp(xml_get_timestamp(xmldoc), now) if hostfilter: @@ -352,7 +466,7 @@ def xml_from_dict(checks, encoding='base64'): 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')) + xmltimestamp = xmlroot.newChild(None, 'timestamp', str(long(time.time()))) for entry in db: check = entry[1] @@ -408,7 +522,7 @@ def reset_future_timestamp(timestamp, now): ############################################################################## -def encode_multipart(xmldoc, httpuser, httppasswd): +def encode_multipart(xmldoc, httpuser=None, httppasswd=None): BOUNDARY = mimetools.choose_boundary() CRLF = '\r\n' L = [] @@ -475,23 +589,34 @@ def daemonize(pidfile=None, stdin='/dev/null', stdout='/dev/null', stderr='/dev/ ############################################################################## -class MyHTTPServer(BaseHTTPServer.HTTPServer): +if 'ForkingMixIn' in SocketServer.__dict__: + MixInClass = SocketServer.ForkingMixIn +else: + MixInClass = SocketServer.ThreadingMixIn + +class MyHTTPServer(MixInClass, BaseHTTPServer.HTTPServer): def __init__(self, server_address, HandlerClass, ssl=False, sslpemfile=None): + SocketServer.BaseServer.__init__(self, server_address, HandlerClass) + if ssl: - # FIXME: SSL is in Py2.6 try: - from OpenSSL import SSL + import ssl + self.socket = ssl.wrap_socket(socket.socket(self.address_family, self.socket_type), keyfile=sslpemfile, certfile=sslpemfile) + except: - print 'No Python OpenSSL wrapper/bindings found!' - sys.exit(127) - - SocketServer.BaseServer.__init__(self, server_address, HandlerClass) - context = SSL.Context(SSL.SSLv23_METHOD) - context.use_privatekey_file (sslpemfile) - context.use_certificate_file(sslpemfile) - self.socket = SSL.Connection(context, socket.socket(self.address_family, self.socket_type)) + + try: + from OpenSSL import SSL + except: + print 'No Python SSL or OpenSSL wrapper/bindings found!' + sys.exit(127) + + context = SSL.Context(SSL.SSLv23_METHOD) + context.use_privatekey_file (sslpemfile) + context.use_certificate_file(sslpemfile) + self.socket = SSL.Connection(context, socket.socket(self.address_family, self.socket_type)) + else: - SocketServer.BaseServer.__init__(self, server_address, HandlerClass) self.socket = socket.socket(self.address_family, self.socket_type) self.server_bind() @@ -506,3 +631,86 @@ class MyHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): ############################################################################## +def prepare_socket(socket_path): + try: + if socket_path.startswith('/'): + s_family=socket.AF_UNIX + s_sockaddr = socket_path + elif socket_path.startswith('unix:'): + s_family=socket.AF_UNIX + s_sockaddr = socket_path[5:] + elif socket_path.find(':') >= 0: + s_port = socket_path.split(':')[-1] + s_host = ':'.join(socket_path.split(':')[:-1]) + if s_host.startswith('[') and s_host.endswith(']'): + s_host = s_host[1:-1] + (s_family, s_socktype, s_proto, s_canonname, s_sockaddr) = socket.getaddrinfo(s_host, s_port, 0, socket.SOCK_STREAM)[0] + else: + return None + except: + return None + + return (s_family, s_sockaddr) + + +def read_socket(s_opts, commands): + # print '%20s => %s %s' % (sock, s_family, s_sockaddr) + + s = socket.socket(s_opts[0], socket.SOCK_STREAM) + s.connect(s_opts[1]) + for line in commands: + if not line.endswith('\n'): + line += '\n' + s.send(line) + s.shutdown(socket.SHUT_WR) + + answer = '' + try: + while True: + s.settimeout(10) + data = s.recv(32768) + if data: + answer += data + else: + break + except socket.timeout: + return '' + + return answer + + +def livestatus2dict(s_opts, host=None, service=None): + checks = [] + + # Get host information only if NO service specified + if not service: + commands = [] + commands.append('GET hosts\n') + commands.append('Columns: name state plugin_output long_plugin_output last_check\n') + if host: + commands.append('Filter: name = %s' % host) + answer = read_socket(s_opts, commands) + + for line in answer.split('\n')[:-1]: + line = line.split(';') + checks.append({'host_name':line[0], 'service_description':None, 'returncode':line[1], 'output':'\n'.join([line[2], line[3]]).rstrip(), 'timestamp':str(line[4])}) + + # Get service information(s) + commands = [] + commands.append('GET services\n') + commands.append('Columns: host_name description state plugin_output long_plugin_output last_check\n') + if host: + commands.append('Filter: host_name = %s' % host) + if service: + commands.append('Filter: description = %s' % service) + + answer = read_socket(s_opts, commands) + + for line in answer.split('\n')[:-1]: + line = line.split(';') + checks.append({'host_name':line[0], 'service_description':line[1], 'returncode':line[2], 'output':'\n'.join([line[3], line[4]]).rstrip(), 'timestamp':str(line[5])}) + + + return checks +############################################################################## +