From f1e35939eb9f360e0d8a34d9ddc9314ba756598d Mon Sep 17 00:00:00 2001 From: Sven Velt Date: Thu, 9 Sep 2010 18:52:56 +0200 Subject: [PATCH] Initial commit Signed-off-by: Sven Velt --- .gitignore | 3 + check_apaches.py | 67 +++++ check_naf.py | 361 +++++++++++++++++++++++++ monitoringplugin.py | 626 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1057 insertions(+) create mode 100644 .gitignore create mode 100755 check_apaches.py create mode 100755 check_naf.py create mode 100644 monitoringplugin.py 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/check_apaches.py b/check_apaches.py new file mode 100755 index 0000000..78ff811 --- /dev/null +++ b/check_apaches.py @@ -0,0 +1,67 @@ +#!/usr/bin/python + +from monitoringplugin import MonitoringPlugin + +import re +import urllib2 + +plugin = MonitoringPlugin(pluginname='check_apaches', tagforstatusline='APACHE', description='Check Apache workers', version='0.1') + +plugin.add_cmdlineoption('-H', '', 'host', 'Hostname/IP to check', default='localhost') +plugin.add_cmdlineoption('-p', '', 'port', 'port to connect', default=None) +plugin.add_cmdlineoption('-P', '', 'proto', 'protocol to use', default='http') +plugin.add_cmdlineoption('-u', '', 'url', 'path to "server-status"', default='/server-status') +plugin.add_cmdlineoption('-a', '', 'httpauth', 'HTTP Username and Password, separated by ":"') +plugin.add_cmdlineoption('-w', '', 'warn', 'warning thresold', default='20') +plugin.add_cmdlineoption('-c', '', 'crit', 'warning thresold', default='50') + +plugin.parse_cmdlineoptions() + +if plugin.options.proto not in ['http', 'https']: + plugin.back2nagios(3, 'Unknown protocol "' + plugin.options.proto + '"') + +if not plugin.options.port: + if plugin.options.proto == 'https': + plugin.options.port = '443' + else: + plugin.options.port = '80' + +url = plugin.options.proto + '://' + plugin.options.host + ':' + plugin.options.port + '/' + plugin.options.url + '?auto' +plugin.verbose(1, 'Status URL: ' + url) + +if plugin.options.httpauth: + httpauth = plugin.options.httpauth.split(':') + if len(httpauth) != 2: + plugin.back2nagios(3, 'Wrong format of authentication data! Need "USERNAME:PASSWORD", got "' + plugin.options.httpauth + '"') + passman = urllib2.HTTPPasswordMgrWithDefaultRealm() + passman.add_password(None, url, httpauth[0], httpauth[1]) + authhandler = urllib2.HTTPBasicAuthHandler(passman) + opener = urllib2.build_opener(authhandler) + urllib2.install_opener(opener) + +try: + data = urllib2.urlopen(url).read() +except urllib2.HTTPError, e: + plugin.back2nagios(2, 'Could not read data! ' + str(e)) +except urllib2.URLError, e: + plugin.back2nagios(2, 'Could not connect to server!') + +plugin.verbose(2, 'Got data:\n' + data) + +try: + idle = int(re.search('Idle(?:Workers|Servers): (\d+)\n', data).group(1)) + busy = int(re.search('Busy(?:Workers|Servers): (\d+)\n', data).group(1)) +except: + plugin.back2nagios(2, 'Could not analyze data!') + +returncode = plugin.value_wc_to_returncode(busy, plugin.options.warn, plugin.options.crit) + +plugin.add_output(str(busy) + ' busy workers, ' + str(idle) + ' idle') + +plugin.add_returncode(returncode) + +plugin.format_add_performancedata('busy', busy, '', warn=plugin.options.warn, crit=plugin.options.crit, min=0.0) +plugin.format_add_performancedata('idle', idle, '') + +plugin.exit() + diff --git a/check_naf.py b/check_naf.py new file mode 100755 index 0000000..39a82c4 --- /dev/null +++ b/check_naf.py @@ -0,0 +1,361 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +##################################################################### +# (c) 2006-2010 by Sven Velt and team(ix) GmbH, Nuernberg, Germany # +# sv@teamix.net # +# # +# This file is part of check_naf (FKA check_netappfiler) # +# # +# check_naf 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, either version 2 of the License, # +# or (at your option) any later version. # +# # +# check_naf 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 check_naf. If not, see . # +##################################################################### + +from monitoringplugin import SNMPMonitoringPlugin + +class CheckNAF(SNMPMonitoringPlugin): + OID = { + 'CPU_Arch': '.1.3.6.1.4.1.789.1.1.11.0', + 'CPU_Time_Busy': '.1.3.6.1.4.1.789.1.2.1.3.0', + 'CPU_Time_Idle': '.1.3.6.1.4.1.789.1.2.1.5.0', + 'CPU_Context_Switches': '.1.3.6.1.4.1.789.1.2.1.8.0', + + 'Disks_Total': '.1.3.6.1.4.1.789.1.6.4.1.0', + 'Disks_Active': '.1.3.6.1.4.1.789.1.6.4.2.0', + 'Disks_Reconstructing': '.1.3.6.1.4.1.789.1.6.4.3.0', + 'Disks_ReconstParity': '.1.3.6.1.4.1.789.1.6.4.4.0', + 'Disks_Scrubbing': '.1.3.6.1.4.1.789.1.6.4.6.0', + 'Disks_Failed': '.1.3.6.1.4.1.789.1.6.4.7.0', + 'Disks_Spare': '.1.3.6.1.4.1.789.1.6.4.8.0', + 'Disks_ZeroDisks': '.1.3.6.1.4.1.789.1.6.4.9.0', + 'Disks_Failed_Descr': '.1.3.6.1.4.1.789.1.6.4.10.0', + + 'Global_Status': '.1.3.6.1.4.1.789.1.2.2.4.0', + 'Global_Status_Message': '.1.3.6.1.4.1.789.1.2.2.25.0', + + 'NVRAM_Status': '.1.3.6.1.4.1.789.1.2.5.1.0', + + 'Model': '.1.3.6.1.4.1.789.1.1.5.0', + 'ONTAP_Version': '.1.3.6.1.4.1.789.1.1.2.0', + + 'df_FS_Index': '.1.3.6.1.4.1.789.1.5.4.1.1', + 'df_FS_Name': '.1.3.6.1.4.1.789.1.5.4.1.2', + 'df_FS_Mounted_On': '.1.3.6.1.4.1.789.1.5.4.1.10', + 'df_FS_Status': '.1.3.6.1.4.1.789.1.5.4.1.20', + 'df_FS_Type': '.1.3.6.1.4.1.789.1.5.4.1.23', + + 'df_FS_kBTotal': ['.1.3.6.1.4.1.789.1.5.4.1.29', '.1.3.6.1.4.1.789.1.5.4.1.15', '.1.3.6.1.4.1.789.1.5.4.1.14',], + 'df_FS_kBUsed': ['.1.3.6.1.4.1.789.1.5.4.1.30', '.1.3.6.1.4.1.789.1.5.4.1.17', '.1.3.6.1.4.1.789.1.5.4.1.16',], + 'df_FS_kBAvail': ['.1.3.6.1.4.1.789.1.5.4.1.31', '.1.3.6.1.4.1.789.1.5.4.1.19', '.1.3.6.1.4.1.789.1.5.4.1.18',], + + 'df_FS_INodeUsed': '.1.3.6.1.4.1.789.1.5.4.1.7', + 'df_FS_INodeFree': '.1.3.6.1.4.1.789.1.5.4.1.8', + + 'df_FS_MaxFilesAvail': '.1.3.6.1.4.1.789.1.5.4.1.11', + 'df_FS_MaxFilesUsed': '.1.3.6.1.4.1.789.1.5.4.1.12', + 'df_FS_MaxFilesPossible': '.1.3.6.1.4.1.789.1.5.4.1.13', + } + + OWC = { + 'Global_Status': ( (3,), (4,), (5,6), ), + 'NVRAM_Status': ( (1,9), (2,5,8), (3,4,6), ), + } + + Status2String = { + 'CPU_Arch' : { '1' : 'x86', '2' : 'alpha', '3' : 'mips', '4' : 'sparc', '5' : 'amd64', }, + 'NVRAM_Status' : { '1' : 'ok', '2' : 'partiallyDischarged', '3' : 'fullyDischarged', '4' : 'notPresent', '5' : 'nearEndOfLife', '6' : 'atEndOfLife', '7' : 'unknown', '8' : 'overCharged', '9' : 'fullyCharged', }, + 'df_FS_Status' : { '1' : 'unmounted', '2' : 'mounted', '3' : 'frozen', '4' : 'destroying', '5' : 'creating', '6' : 'mounting', '7' : 'unmounting', '8' : 'nofsinfo', '9' : 'replaying', '10': 'replayed', }, + 'df_FS_Type' : { '1' : 'traditionalVolume', '2' : 'flexibleVolume', '3' : 'aggregate', }, + } + + + def map_status_to_returncode(self, value, mapping): + for returncode in xrange(0,3): + if value in mapping[returncode]: + return returncode + return 3 + + + def check_cpu(self, warn='', crit=''): + cpu_arch = self.SNMPGET(self.OID['CPU_Arch']) + cpu_timebusy = int(self.SNMPGET(self.OID['CPU_Time_Busy'])) + # cputimeidle = int(self.SNMPGET(self.OID['CPU_Time_Idle'])) + cpu_cs = self.SNMPGET(self.OID['CPU_Context_Switches']) + + returncode = self.value_wc_to_returncode(cpu_timebusy, warn, crit) + output = 'CPU ' + str(cpu_timebusy) + '% busy, CPU architecture: ' + self.Status2String['CPU_Arch'].get(cpu_arch) + perfdata = [] + pd = {'label':'nacpu', 'value':cpu_timebusy, 'unit':'%', 'min':0, 'max':100} + if warn: + pd['warn'] = warn + if crit: + pd['crit'] = crit + perfdata.append(pd) + perfdata.append({'label':'nacs', 'value':cpu_cs, 'unit':'c'}) + + return self.remember_check('cpu', returncode, output, perfdata=perfdata) + + + def check_disk(self, target='failed', warn='', crit=''): + di_total = int(self.SNMPGET(self.OID['Disks_Total'])) + di_active = int(self.SNMPGET(self.OID['Disks_Active'])) + di_reconstructing = int(self.SNMPGET(self.OID['Disks_Reconstructing'])) + di_reconstparity = int(self.SNMPGET(self.OID['Disks_ReconstParity'])) + # di_scrubbing = int(self.SNMPGET(self.OID['Disks_Scrubbing'])) + di_failed = int(self.SNMPGET(self.OID['Disks_Failed'])) + di_spare = int(self.SNMPGET(self.OID['Disks_Spare'])) + # di_zerodisks = int(self.SNMPGET(self.OID['Disks_ZeroDisks'])) + + di_reconstr = di_reconstructing + di_reconstparity + + if target == 'spare': + returncode = self.value_wc_to_returncode(di_spare, warn, crit) + output = str(di_spare) + ' spare disk' + if di_spare > 1: + output += 's' + else: + returncode = self.value_wc_to_returncode(di_failed, warn, crit) + if returncode == 0: + output = 'No failed disks' + else: + output = self.SNMPGET(self.OID['Disks_Failed_Descr']) + + perfdata = [] + perfdata.append({'label':'nadisk_total', 'value':di_total, 'unit':'', 'min':0}) + perfdata.append({'label':'nadisk_active', 'value':di_active, 'unit':'', 'min':0}) + pd = {'label':'nadisk_spare', 'value':di_spare, 'unit':'', 'min':0} + if warn and target=='spare': + pd['warn'] = warn + if crit and target=='spare': + pd['crit'] = crit + perfdata.append(pd) + pd = {'label':'nadisk_failed', 'value':di_failed, 'unit':'', 'min':0} + if warn and target=='failed': + pd['warn'] = warn + if crit and target=='failed': + pd['crit'] = crit + perfdata.append(pd) + + return self.remember_check('disk', returncode, output, perfdata=perfdata, target=target) + + def check_global(self): + model = self.SNMPGET(self.OID['Model']) + globalstatus = int(self.SNMPGET(self.OID['Global_Status'])) + globalstatusmsg = self.SNMPGET(self.OID['Global_Status_Message'])[:255] + + returncode = self.map_status_to_returncode(globalstatus, self.OWC['Global_Status']) + output = model + ': ' + globalstatusmsg + + return self.remember_check('global', returncode, output) + + + def check_nvram(self): + nvramstatus = int(self.SNMPGET(self.OID['NVRAM_Status'])) + + returncode = self.map_status_to_returncode(nvramstatus, self.OWC['NVRAM_Status']) + output = 'NVRAM battery status is "' + self.Status2String['NVRAM_Status'].get(str(nvramstatus)) + '"' + + return self.remember_check('nvram', returncode, output) + + + def check_version(self): + model = self.SNMPGET(self.OID['Model']) + ontapversion = self.SNMPGET(self.OID['ONTAP_Version']) + + return self.remember_check('version', 0, model + ': ' + ontapversion) + + + def common_vol_idx(self, volume): + if volume.endswith('.snapshot'): + return None + + idx = str(self.find_in_table(self.OID['df_FS_Index'], self.OID['df_FS_Name'] , volume)) + sn_idx = int(idx) + 1 + + return (idx, sn_idx) + + + def check_vol_data(self, volume, warn, crit): + (idx, sn_idx) = self.common_vol_idx(volume) + + fs_total = long(self.SNMPGET(self.OID['df_FS_kBTotal'], idx)) * 1024L + fs_used = long(self.SNMPGET(self.OID['df_FS_kBUsed'], idx)) * 1024L + # fs_avail = long(self.SNMPGET(self.OID['df_FS_kBAvail'], idx)) * 1024L + sn_total = long(self.SNMPGET(self.OID['df_FS_kBTotal'], sn_idx)) * 1024L + sn_used = long(self.SNMPGET(self.OID['df_FS_kBUsed'], sn_idx)) * 1024L + # sn_avail = long(self.SNMPGET(self.OID['df_FS_kBAvail'], sn_idx)) * 1024L + + mountedon = self.SNMPGET(self.OID['df_FS_Mounted_On'] + "." + idx) + status = self.Status2String['df_FS_Status'].get(self.SNMPGET(self.OID['df_FS_Status'] + "." + idx)) + fstype = self.Status2String['df_FS_Type'].get(self.SNMPGET(self.OID['df_FS_Type'] + "." + idx)) + + # print [mountedon, status, fstype] + + fs_pctused = float(fs_used) / float(fs_total) * 100.0 + + warn = self.range_dehumanize(warn, fs_total) + crit = self.range_dehumanize(crit, fs_total) + + returncode = self.value_wc_to_returncode(fs_used, warn, crit) + output = volume + ': Used ' + self.value_to_human_binary(fs_used, 'B') + output += ' (' + '%3.1f' % fs_pctused + '%)'+ ' out of ' + self.value_to_human_binary(fs_total, 'B') + target = volume.replace('/vol/', '')[:-1] + perfdata = [] + perfdata.append({'label':'navdu_' + target, 'value':fs_used, 'unit':'B', 'warn':warn, 'crit':crit, 'min':0}) + perfdata.append({'label':'navdt_' + target, 'value':fs_total, 'unit':'B'}) + perfdata.append({'label':'navsu_' + target, 'value':sn_used, 'unit':'B', 'min':0}) + perfdata.append({'label':'navst_' + target, 'value':sn_total, 'unit':'B'}) + + return self.remember_check('vol_data', returncode, output, perfdata=perfdata, target=target) + + + def check_vol_snap(self, volume, warn, crit): + (idx, sn_idx) = self.common_vol_idx(volume) + + # fs_total = long(self.SNMPGET(self.OID['df_FS_kBTotal'], idx)) * 1024L + # fs_used = long(self.SNMPGET(self.OID['df_FS_kBUsed'], idx)) * 1024L + # fs_avail = long(self.SNMPGET(self.OID['df_FS_kBAvail'], idx)) * 1024L + sn_total = long(self.SNMPGET(self.OID['df_FS_kBTotal'], sn_idx)) * 1024L + sn_used = long(self.SNMPGET(self.OID['df_FS_kBUsed'], sn_idx)) * 1024L + # sn_avail = long(self.SNMPGET(self.OID['df_FS_kBAvail'], sn_idx)) * 1024L + + sn_pctused = float(sn_used) / float(sn_total) * 100.0 + + warn = self.range_dehumanize(warn, sn_total) + crit = self.range_dehumanize(crit, sn_total) + + returncode = self.value_wc_to_returncode(sn_used, warn, crit) + output = volume + '.snapshot: Used ' + self.value_to_human_binary(sn_used, 'B') + output += ' (' + '%3.1f' % sn_pctused + '%)'+ ' out of ' + self.value_to_human_binary(sn_total, 'B') + target = volume.replace('/vol/', '')[:-1] + perfdata = [] + perfdata.append({'label':'navsu_' + target, 'value':sn_used, 'unit':'B', 'warn':warn, 'crit':crit, 'min':0}) + perfdata.append({'label':'navst_' + target, 'value':sn_total, 'unit':'B'}) + + return self.remember_check('vol_snap', returncode, output, perfdata=perfdata, target=target) + + + def check_vol_inode(self, volume, warn, crit): + (idx, sn_idx) = self.common_vol_idx(volume) + + in_used = long(self.SNMPGET(self.OID['df_FS_INodeUsed'] + '.' + idx)) + in_free = long(self.SNMPGET(self.OID['df_FS_INodeFree'] + '.' + idx)) + in_total = in_used + in_free + + in_pctused = float(in_used) / float(in_total) * 100.0 + + warn = self.range_dehumanize(warn, in_total) + crit = self.range_dehumanize(crit, in_total) + + returncode = self.value_wc_to_returncode(in_used, warn, crit) + output = volume + ': Used inodes ' + self.value_to_human_si(in_used) + output += ' (' + '%3.1f' % in_pctused + '%)'+ ' out of ' + self.value_to_human_si(in_total) + target = volume.replace('/vol/', '')[:-1] + perfdata = [] + perfdata.append({'label':'naviu_' + target, 'value':in_used, 'unit':None, 'warn':warn, 'crit':crit, 'min':0}) + perfdata.append({'label':'navit_' + target, 'value':in_total, 'unit':None}) + + return self.remember_check('vol_inode', returncode, output, perfdata=perfdata, target=target) + + + def check_vol_files(self, volume, warn, crit): + (idx, sn_idx) = self.common_vol_idx(volume) + + fi_avail = long(self.SNMPGET(self.OID['df_FS_MaxFilesAvail'] + '.' + idx)) + fi_used = long(self.SNMPGET(self.OID['df_FS_MaxFilesUsed'] + '.' + idx)) + fi_possible = long(self.SNMPGET(self.OID['df_FS_MaxFilesPossible'] + '.' + idx)) + fi_total = fi_used + fi_avail + + fi_pctused = float(fi_used) / float(fi_total) * 100.0 + + warn = self.range_dehumanize(warn, fi_total) + crit = self.range_dehumanize(crit, fi_total) + + returncode = self.value_wc_to_returncode(fi_used, warn, crit) + output = volume + ': Used files ' + self.value_to_human_si(fi_used) + output += ' (' + '%3.1f' % fi_pctused + '%)'+ ' out of ' + self.value_to_human_si(fi_total) + output += ', may raised to ' + self.value_to_human_si(fi_possible) + target = volume.replace('/vol/', '')[:-1] + perfdata = [] + perfdata.append({'label':'navfu_' + target, 'value':fi_used, 'unit':None, 'warn':warn, 'crit':crit, 'min':0}) + perfdata.append({'label':'navft_' + target, 'value':fi_total, 'unit':None}) + + return self.remember_check('vol_files', returncode, output, perfdata=perfdata, target=target) + + + + + + +def main(): + plugin = CheckNAF(pluginname='check_naf', tagforstatusline='NAF', description=u'Monitoring NetApp™ FAS systems', version='0.9') + + plugin.add_cmdlineoption('', '--check', 'check', 'check or list of checks', default='') + plugin.add_cmdlineoption('', '--target', 'target', 'target or list of targets', default='') + plugin.add_cmdlineoption('-w', '', 'warn', 'warning thresold or a list of it', default='') + plugin.add_cmdlineoption('-c', '', 'crit', 'warning thresold or a list of it', default='') + plugin.parse_cmdlineoptions() + + plugin.prepare_snmp() + + if ',' in plugin.options.check: + checks = plugin.options.check.split(',') + checks.reverse() + else: + checks = [plugin.options.check,] + + if ',' in plugin.options.target: + targets = plugin.options.target.split(',') + else: + targets = [plugin.options.target,] + + while len(checks): + check = checks.pop() + + if check == 'global': + result = plugin.check_global() + elif check == 'cpu': + result = plugin.check_cpu() + elif check == 'disk': + result = plugin.check_disk(target='spare') + elif check == 'nvram': + result = plugin.check_nvram() + elif check == 'version': + result = plugin.check_version() + + elif check.startswith('vol_'): + combinedchecks = [check,] + while len(checks) > 0 and checks[0].startswith('vol_'): + combinedchecks.append(checks.pop()) + + for target in targets: + for check in combinedchecks: + if check == 'vol_data': + result = plugin.check_vol_data(target, plugin.options.warn, plugin.options.crit) + elif check == 'vol_snap': + result = plugin.check_vol_snap(target, plugin.options.warn, plugin.options.crit) + elif check =='vol_inode': + result = plugin.check_vol_inode(target, plugin.options.warn, plugin.options.crit) + elif check =='vol_files': + result = plugin.check_vol_files(target, plugin.options.warn, plugin.options.crit) + + # from pprint import pprint + # pprint(plugin.dump_brain()) + + plugin.brain2output() + plugin.exit() + +if __name__ == '__main__': + main() + +#vim: ts=4 sw=4 foldmethod=indent diff --git a/monitoringplugin.py b/monitoringplugin.py new file mode 100644 index 0000000..4a78ca2 --- /dev/null +++ b/monitoringplugin.py @@ -0,0 +1,626 @@ +__version__ = '0.0.100802' +__all__ = ['MonitoringPlugin', 'SNMPMonitoringPlugin'] + +import optparse, os, re, sys + +try: + import netsnmp +except ImportError: + pass + +class MonitoringPlugin(object): + + RETURNSTRINGS = { 0: "OK", 1: "WARNING", 2: "CRITICAL", 3: "UNKNOWN", 127: "UNKNOWN" } + RETURNCODE = { 'OK': 0, 'WARNING': 1, 'CRITICAL': 2, 'UNKNOWN': 3 } + + returncode_priority = [2, 1, 3, 0] + + powers_binary = ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi'] + powers_binary_lower = [ p.lower() for p in powers_binary] + powers_si = ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z'] + powers_si_lower = [ p.lower() for p in powers_si] + + + def __init__(self, *args, **kwargs): + self.__pluginname = kwargs.get('pluginname') or '' + self.__version = kwargs.get('version') or None + self.__tagforstatusline = kwargs.get('tagforstatusline') or '' + self.__tagforstatusline = self.__tagforstatusline.replace('|', ' ') + self.__description = kwargs.get('description') or None + + self.__output = [] + self.__multilineoutput = [] + self.__performancedata = [] + self.__returncode = [] + + self.__brain_checks = [] + self.__brain_perfdata = [] + self.__brain_perfdatalabels = [] + + self.__optparser = optparse.OptionParser(version=self.__version, description=self.__description) + self._cmdlineoptions_parsed = False + + + def add_cmdlineoption(self, shortoption, longoption, dest, help, **kwargs): + self.__optparser.add_option(shortoption, longoption, dest=dest, help=help, **kwargs) + + + def parse_cmdlineoptions(self): + if self._cmdlineoptions_parsed: + return +# self.__optparser.add_option('-V', '--version', action='version', help='show version number and exit') + self.__optparser.add_option('-v', '--verbose', dest='verbose', help='Verbosity, more for more ;-)', action='count') + + (self.options, self.args) = self.__optparser.parse_args() + self._cmdlineoptions_parsed = True + + + def range_to_limits(self, range): + # Check if we must negate result + if len(range) > 0 and range[0] == '@': + negate = True + range = range[1:] + else: + negate = False + + # Look for a ':'... + if range.find(':') >= 0: + # ... this is a range + (low, high) = range.split(':') + + if not low: + low = float(0.0) + elif low[0] == '~': + low = float('-infinity') + else: + low = float(low) + + if high: + high = float(high) + else: + high = float('infinity') + + elif len(range) == 0: + low = float('-infinity') + high = float('infinity') + + else: + # ... this is just a number + low = float(0.0) + high = float(range) + + return (low, high, negate) + + + def value_in_range(self, value, range): + (low, high, negate) = self.range_to_limits(range) + + if value < low or value > high: + result = False + else: + result = True + + if negate: + result = not result + + return result + + + def value_wc_to_returncode(self, value, range_warn, range_crit): + if not self.value_in_range(value, range_crit): + return 2 + elif not self.value_in_range(value, range_warn): + return 1 + + return 0 + + + def is_float(self, string): + try: + float(string) + return True + except ValueError: + return False + + + def special_value_wc_to_returncode(self, value, warn, crit): + # Special add on: WARN > CRIT + if self.is_float(warn) and self.is_float(crit) and float(warn) > float(crit): + # Test if value is *smaller* than thresholds + warn = '@0:' + warn + crit = '@0:' + crit + + return self.value_wc_to_returncode(value, warn, crit) + + + def add_output(self, value): + self.__output.append(value) + + + def add_multilineoutput(self, value): + self.__multilineoutput.append(value) + + + def format_performancedata(self, label, value, unit, *args, **kwargs): + label = label.lstrip().rstrip() + if re.search('[=\' ]', label): + label = '\'' + label + '\'' + perfdata = label + '=' + str(value) + if unit: + perfdata += str(unit).lstrip().rstrip() + for key in ['warn', 'crit', 'min', 'max']: + perfdata += ';' + if key in kwargs: + perfdata += str(kwargs[key]) + + return perfdata + + + def add_performancedata(self, perfdata): + self.__performancedata.append(perfdata) + + + def format_add_performancedata(self, label, value, unit, *args, **kwargs): + self.add_performancedata(self.format_performancedata(label, value, unit, *args, **kwargs)) + + + def add_returncode(self, value): + self.__returncode.append(value) + + + def tagtarget(self, tag, target): + if target: + return str(tag) + ':' + str(target) + else: + return str(tag) + + + def remember_check(self, tag, returncode, output, multilineoutput=None, perfdata=None, target=None): + check = {} + check['tag'] = tag + check['returncode'] = returncode + check['output'] = output + check['multilineoutout'] = multilineoutput + check['perfdata'] = perfdata + check['target'] = target + + self.remember_perfdata(perfdata) + + self.__brain_checks.append(check) + + return check + + + def remember_perfdata(self, perfdata=None): + if perfdata: + for pd in perfdata: + if pd['label'] in self.__brain_perfdatalabels: + pdidx = self.__brain_perfdatalabels.index(pd['label']) + self.__brain_perfdata[pdidx] = pd + else: + self.__brain_perfdata.append(pd) + self.__brain_perfdatalabels.append(pd['label']) + + + def dump_brain(self): + return (self.__brain_checks, self.__brain_perfdata) + + + def brain2output(self): + out = [[], [], [], []] + for check in self.__brain_checks: + tagtarget = self.tagtarget(check['tag'], check.get('target')) + returncode = check.get('returncode') or 0 + self.add_returncode(returncode) + + out[returncode].append(tagtarget) + #if returncode == 0: + # self.add_output(tagtarget) + #else: + # self.add_output(tagtarget + '(' + check.get('output') + ') ') + + self.add_multilineoutput(self.RETURNSTRINGS[returncode] + ' ' + tagtarget + ' - ' + check.get('output')) + if check.get('multilineoutput'): + self.add_multilineoutput(check.get('multilineoutput')) + + statusline = [] + for retcode in [2, 1, 3, 0]: + if len(out[retcode]): + statusline.append(str(len(out[retcode])) + ' ' + self.RETURNSTRINGS[retcode] + ': ' + '/'.join(out[retcode])) + statusline = ', '.join(statusline) + self.add_output(statusline) + + for pd in self.__brain_perfdata: + self.format_add_performancedata(**pd) + + + def value_to_human_binary(self, value, unit=''): + for power in self.powers_binary: + if value < 1024.0: + return "%3.1f%s%s" % (value, power, unit) + value /= 1024.0 + if float(value) not in [float('inf'), float('-inf')]: + return "%3.1fYi%s" % (value, unit) + else: + return value + + + def value_to_human_si(self, value, unit=''): + for power in self.powers_si: + if value < 1000.0: + return "%3.1f%s%s" % (value, power, unit) + value /= 1000.0 + if float(value) not in [float('inf'), float('-inf')]: + return "%3.1fY%s" % (value, unit) + else: + return value + + + def human_to_number(self, value, total=None): + if total: + if not self.is_float(total): + total = self.human_to_number(total) + + if self.is_float(value): + return float(value) + elif value[-1] == '%': + if total: + return float(value[:-1])/100.0 * float(total) + else: + return float(value[:-1]) + elif value[-1].lower() in self.powers_si_lower: + return 1000.0 ** self.powers_si_lower.index(value[-1].lower()) * float(value[:-1]) + elif value[-2:].lower() in self.powers_binary_lower: + return 1024.0 ** self.powers_binary_lower.index(value[-2:].lower()) * float(value[:-2]) + else: + return value + + + def range_dehumanize(self, range, total=None): + newrange = '' + + if len(range): + if range[0] == '@': + newrange += '@' + range = range[1:] + + parts = range.split(':') + newrange += str(self.human_to_number(parts[0], total)) + + if len(parts) > 1: + newrange += ':' + str(self.human_to_number(parts[1], total)) + + if range != newrange: + self.verbose(3, 'Changed range/thresold from "' + range + '" to "' + newrange + '"') + + return newrange + else: + return '' + + + def verbose(self, level, output): + if level <= self.options.verbose: + print 'V' + str(level) + ': ' + output + + + def exit(self): + for returncode in self.returncode_priority: + if returncode in self.__returncode: + break + + self.back2nagios(returncode, statusline=self.__output, multiline=self.__multilineoutput, performancedata=self.__performancedata) + + + def back2nagios(self, returncode, statusline=None, multiline=None, performancedata=None, subtag=None, exit=True): + # FIXME: Make 'returncode' also accept strings + # Build status line + out = self.__tagforstatusline + if subtag: + out += '(' + subtag.replace('|', ' ') + ')' + out += ' ' + self.RETURNSTRINGS[returncode] + + # Check if there's a status line text and build it + if statusline: + out += ' - ' + if type(statusline) == str: + out += statusline + elif type(statusline) in [list, tuple]: + out += ', '.join(statusline).replace('|', ' ') + + # Check if we have multi line output and build it + if multiline: + if type(multiline) == str: + out += '\n' + multiline.replace('|', ' ') + elif type(multiline) in [list, tuple]: + out += '\n' + '\n'.join(multiline).replace('|', ' ') + + # Check if there's perfdata + if performancedata: + out += '|' + if type(performancedata) == str: + out += performancedata + elif type(performancedata) in [list, tuple]: + out += ' '.join(performancedata).replace('|', ' ') + + # Exit program or return output line(s) + if exit: + print out + sys.exit(returncode) + else: + return (returncode, out) + +############################################################################## + +class SNMPMonitoringPlugin(MonitoringPlugin): + + def __init__(self, *args, **kwargs): + # Same as "MonitoringPlugin.__init__(*args, **kwargs)" but a little bit more flexible + #super(MonitoringPlugin, self).__init__(*args, **kwargs) + MonitoringPlugin.__init__(self, *args, **kwargs) + + self.add_cmdlineoption('-H', '', 'host', 'Host to check', default='127.0.0.1') + self.add_cmdlineoption('-P', '', 'snmpversion', 'SNMP protocol version', default='1') + self.add_cmdlineoption('-C', '', 'snmpauth', 'SNMP v1/v2c community OR SNMP v3 quadruple', default='public') + self.add_cmdlineoption('', '--snmpcmdlinepath', 'snmpcmdlinepath', 'Path to "snmpget" and "snmpwalk"', default='/usr/bin') + # FIXME + self.add_cmdlineoption('', '--nonetsnmp', 'nonetsnmp', 'Do not use NET-SNMP python bindings', action='store_true') + # self.__optparser.add_option('', '--nonetsnmp', dest='nonetsnmp', help='Do not use NET-SNMP python bindings', action='store_true') + + self.__SNMP_Cache = {} + + self.__use_netsnmp = False + self.__prepared_snmp = False + + + def prepare_snmp(self): + if not self._cmdlineoptions_parsed: + self.parse_cmdlineoptions() + + if not self.options.nonetsnmp: + try: + import netsnmp + self.__use_netsnmp = True + except ImportError: + pass + + if self.__use_netsnmp: + self.verbose(1, 'Using NET-SNMP Python bindings') + + self.SNMPGET_wrapper = self.__SNMPGET_netsnmp + self.SNMPWALK_wrapper = self.__SNMPWALK_netsnmp + + if self.options.snmpversion == '2c': + self.options.snmpversion = '2' + + else: + self.verbose(1, 'Using NET-SNMP command line tools') + + self.SNMPGET_wrapper = self.__SNMPGET_cmdline + self.SNMPWALK_wrapper = self.__SNMPWALK_cmdline + + # Building command lines + self.__CMDLINE_get = os.path.join(self.options.snmpcmdlinepath, 'snmpget') + ' -OqevtU ' + self.__CMDLINE_walk = os.path.join(self.options.snmpcmdlinepath, 'snmpwalk') + ' -OqevtU ' + + if self.options.snmpversion in [1, 2, '1', '2', '2c']: + if self.options.snmpversion in [2, '2']: + self.options.snmpversion = '2c' + self.__CMDLINE_get += ' -v' + str(self.options.snmpversion) + ' -c' + self.options.snmpauth + ' ' + self.__CMDLINE_walk += ' -v' + str(self.options.snmpversion) + ' -c' + self.options.snmpauth + ' ' + elif options.snmpversion == [3, '3']: + # FIXME: Better error handling + try: + snmpauth = self.options.snmpauth.split(':') + self.__CMDLINE_get += ' -v3 -l' + snmpauth[0] + ' -u' + snmpauth[1] + ' -a' + snmpauth[2] + ' -A' + snmpauth[3] + ' ' + self.__CMDLINE_walk += ' -v3 -l' + snmpauth[0] + ' -u' + snmpauth[1] + ' -a' + snmpauth[2] + ' -A' + snmpauth[3] + ' ' + except: + self.back2nagios(3, 'Could not build SNMPv3 command line, need "SecLevel:SecName:AuthProtocol:AuthKey"') + else: + self.back2nagios(3, 'Unknown SNMP version "' + str(self.options.snmpversion) + '"') + + self.__CMDLINE_get += ' ' + self.options.host + ' %s 2>/dev/null' + self.__CMDLINE_walk += ' ' + self.options.host + ' %s 2>/dev/null' + + self.verbose(3, 'Using commandline: ' + self.__CMDLINE_get) + self.verbose(3, 'Using commandline: ' + self.__CMDLINE_walk) + + self.__prepared_snmp = True + + + def find_index_for_value(self, list_indexes, list_values, wanted): + self.verbose(2, 'Look for "' + str(wanted) + '"') + + index = None + + if len(list_indexes) != len(list_values): + self.verbose(1, 'Length of index and value lists do not match!') + return None + + try: + index = list_values.index(wanted) + index = list_indexes[index] + except ValueError: + pass + + if index: + self.verbose(2, 'Found "' + str(wanted) +'" with index "' + str(index) + '"') + else: + self.verbose(2, 'Nothing found!') + + return str(index) + + + def find_in_table(self, oid_index, oid_values, wanted): + self.verbose(2, 'Look for "' + str(wanted) + '" in "' + str(oid_values) +'"') + + index = None + indexes = list(self.SNMPWALK(oid_index)) + values = list(self.SNMPWALK(oid_values)) + + if len(indexes) != len(values): + self.back2nagios(3, 'Different data from 2 SNMP Walks!') + + return str(self.find_index_for_value(indexes, values, wanted)) + + + def SNMPGET(self, baseoid, idx=None, exitonerror=True): + if type(baseoid) in (list, tuple) and idx != None: + idx = str(idx) + + if self.options.snmpversion in [1, '1']: + value_low = long(self.SNMPGET_wrapper(baseoid[1] + '.' + idx, exitonerror=exitonerror)) + if value_low < 0L: + value_low += 2 ** 32 + + value_hi = long(self.SNMPGET_wrapper(baseoid[2] + '.' + idx, exitonerror=exitonerror)) + if value_hi < 0L: + value_hi += 2 ** 32 + + return value_hi * 2L ** 32L + value_low + + elif self.options.snmpversion in [2, 3, '2', '2c', '3']: + return long(self.SNMPGET_wrapper(baseoid[0] + '.' + idx, exitonerror=exitonerror)) + + elif type(baseoid) in (str, ) and idx != None: + return self.SNMPGET_wrapper(baseoid + str(idx), exitonerror=exitonerror) + else: + return self.SNMPGET_wrapper(baseoid, exitonerror=exitonerror) + + + def SNMPWALK(self, baseoid, exitonerror=True): + return self.SNMPWALK_wrapper(baseoid, exitonerror=exitonerror) + + + def __SNMPGET_netsnmp(self, oid, exitonerror=True): + if not self.__prepared_snmp: + self.prepare_snmp() + + if oid in self.__SNMP_Cache: + self.verbose(2, "%40s -> (CACHED) %s" % (oid, self.__SNMP_Cache[oid])) + return self.__SNMP_Cache[oid] + + result = netsnmp.snmpget(oid, Version=int(self.options.snmpversion), DestHost=self.options.host, Community=self.options.snmpauth)[0] + + if not result: + if exitonerror: + self.back2nagios(3, 'Timeout or no answer from "%s" looking for "%s"' % (self.options.host, oid)) + else: + return None + + self.__SNMP_Cache[oid] = result + + self.verbose(2, "%40s -> %s" % (oid, result)) + return result + + + def __SNMPWALK_netsnmp(self, oid, exitonerror=True): + if not self.__prepared_snmp: + self.prepare_snmp() + + if oid in self.__SNMP_Cache: + self.verbose(2, "%40s -> (CACHED) %s" % (oid, self.__SNMP_Cache[oid])) + return self.__SNMP_Cache[oid] + + result = netsnmp.snmpwalk(oid, Version=int(self.options.snmpversion), DestHost=self.options.host, Community=self.options.snmpauth) + + if not result: + if exitonerror: + self.back2nagios(3, 'Timeout or no answer from "%s" looking for "%s"' % (self.options.host, oid)) + else: + return None + + self.__SNMP_Cache[oid] = result + + self.verbose(2, "%40s -> %s" % (oid, result)) + return result + + + def __SNMPGET_cmdline(self, oid, exitonerror=True): + if not self.__prepared_snmp: + self.prepare_snmp() + + cmdline = self.__CMDLINE_get % oid + self.verbose(2, cmdline) + + cmd = os.popen(cmdline) + out = cmd.readline().rstrip().replace('"','') + retcode = cmd.close() + + if retcode: + if not exitonerror: + return None + if retcode == 256: + self.back2nagios(3, 'Timeout - no SNMP answer from "' + self.options.host + '"') + elif retcode ==512: + self.back2nagios(3, 'OID "' + oid + '" not found') + else: + self.back2nagios(3, 'Unknown error code "' + str(retcode) + '" from command line utils') + + self.verbose(1, out) + return out + + + def __SNMPWALK_cmdline(self, oid, exitonerror=True): + if not self.__prepared_snmp: + self.prepare_snmp() + + cmdline = self.__CMDLINE_walk % oid + self.verbose(2, cmdline) + + cmd = os.popen(cmdline) + out = cmd.readlines() + retcode = cmd.close() + + if retcode: + if not exitonerror: + return None + if retcode == 256: + self.back2nagios(3, 'Timeout - no SNMP answer from "' + self.options.host + '"') + elif retcode ==512: + self.back2nagios(3, 'OID "' + oid + '" not found') + else: + self.back2nagios(3, 'Unknown error code "' + str(retcode) + '" from command line utils') + + for line in range(0,len(out)): + out[line] = out[line].rstrip().replace('"','') + + self.verbose(1, out) + return out + + +############################################################################## + +def main(): + myplugin = MonitoringPlugin(pluginname='check_testplugin', tagforstatusline='TEST') + + from pprint import pprint + pprint(myplugin.back2nagios(0, 'Nr. 01: Simple plugin', exit=False) ) + pprint(myplugin.back2nagios(0, 'Nr. 02: Simple plugin with sub tag', subtag='MySubTag', exit=False) ) + + pprint(myplugin.back2nagios(0, 'Nr. 10: Exit Code OK', exit=False) ) + pprint(myplugin.back2nagios(1, 'Nr. 11: Exit Code WARNING', exit=False) ) + pprint(myplugin.back2nagios(2, 'Nr. 12: Exit Code CRITICAL', exit=False) ) + pprint(myplugin.back2nagios(3, 'Nr. 13: Exit Code UNKNOWN', exit=False) ) + + ret = myplugin.back2nagios(0, 'Nr. 20: Plugin with string-based multiline output', 'Line 2\nLine 3\nLine4', exit=False) + print ret[1] + print 'Returncode: ' + str(ret[0]) + ret = myplugin.back2nagios(0, 'Nr. 21: Plugin with list-based multiline output', ['Line 2', 'Line 3', 'Line4'], exit=False) + print ret[1] + print 'Returncode: ' + str(ret[0]) + ret = myplugin.back2nagios(0, 'Nr. 22: Plugin with tuple-based multiline output', ('Line 2', 'Line 3', 'Line4'), exit=False) + print ret[1] + print 'Returncode: ' + str(ret[0]) + + myplugin.add_performancedata('Val1', 42, '') + myplugin.add_performancedata('Val2', 23, 'c', warn=10, crit=20, min=0, max=100) + myplugin.add_performancedata('Val 3', '2342', 'c', warn=10, crit=20, min=0, max=100) + pprint(myplugin.back2nagios(0, 'Nr. 30: With perfdatas', exit=False) ) + + myplugin.back2nagios(0, 'Nr. 99: Exit test suite with OK') + + +if __name__ == '__main__': + main() + +#vim: ts=4 sw=4