|
@@ -0,0 +1,300 @@
|
|
|
|
|
+#!@PYTHON@
|
|
|
|
|
+"""
|
|
|
|
|
+SYNOPSIS
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+"""
|
|
|
|
|
+import sys
|
|
|
|
|
+import optparse
|
|
|
|
|
+import traceback
|
|
|
|
|
+import ssl
|
|
|
|
|
+
|
|
|
|
|
+# Python 2/3 Compatibility imports
|
|
|
|
|
+
|
|
|
|
|
+try:
|
|
|
|
|
+ import json
|
|
|
|
|
+except ImportError:
|
|
|
|
|
+ import simplejson as json
|
|
|
|
|
+
|
|
|
|
|
+try:
|
|
|
|
|
+ import urllib.request
|
|
|
|
|
+ import urllib.parse
|
|
|
|
|
+ import urllib.error
|
|
|
|
|
+except ImportError:
|
|
|
|
|
+ import urllib2
|
|
|
|
|
+ import urllib
|
|
|
|
|
+
|
|
|
|
|
+try:
|
|
|
|
|
+ urlencode = urllib.parse.urlencode
|
|
|
|
|
+except AttributeError:
|
|
|
|
|
+ urlencode = urllib.urlencode
|
|
|
|
|
+
|
|
|
|
|
+try:
|
|
|
|
|
+ urlopen = urllib.request.urlopen
|
|
|
|
|
+except AttributeError:
|
|
|
|
|
+ urlopen = urllib2.urlopen
|
|
|
|
|
+
|
|
|
|
|
+try:
|
|
|
|
|
+ urlquote = urllib.parse.quote
|
|
|
|
|
+except AttributeError:
|
|
|
|
|
+ urlquote = urllib.quote
|
|
|
|
|
+
|
|
|
|
|
+import shlex
|
|
|
|
|
+import re
|
|
|
|
|
+import signal
|
|
|
|
|
+
|
|
|
|
|
+__VERSION__ = '1.1.0'
|
|
|
|
|
+
|
|
|
|
|
+def pretty(d, indent=0, indenter=' ' * 4):
|
|
|
|
|
+ info_str = ''
|
|
|
|
|
+ for key, value in list(d.items()):
|
|
|
|
|
+ info_str += indenter * indent + str(key)
|
|
|
|
|
+ if isinstance(value, dict):
|
|
|
|
|
+ info_str += '/\n'
|
|
|
|
|
+ info_str += pretty(value, indent + 1, indenter)
|
|
|
|
|
+ else:
|
|
|
|
|
+ info_str += ': ' + str(value) + '\n'
|
|
|
|
|
+ return info_str
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def parse_args():
|
|
|
|
|
+ version = 'check_ncpa.py, Version %s' % __VERSION__
|
|
|
|
|
+
|
|
|
|
|
+ parser = optparse.OptionParser()
|
|
|
|
|
+ parser.add_option("-H", "--hostname", help="The hostname to be connected to.")
|
|
|
|
|
+ parser.add_option("-M", "--metric", default='',
|
|
|
|
|
+ help="The metric to check, this is defined on client "
|
|
|
|
|
+ "system. This would also be the plugin name in the "
|
|
|
|
|
+ "plugins directory. Do not attach arguments to it, "
|
|
|
|
|
+ "use the -a directive for that. DO NOT INCLUDE the api/ "
|
|
|
|
|
+ "instruction.")
|
|
|
|
|
+ parser.add_option("-P", "--port", default=5693, type="int",
|
|
|
|
|
+ help="Port to use to connect to the client.")
|
|
|
|
|
+ parser.add_option("-w", "--warning", default=None, type="str",
|
|
|
|
|
+ help="Warning value to be passed for the check.")
|
|
|
|
|
+ parser.add_option("-c", "--critical", default=None, type="str",
|
|
|
|
|
+ help="Critical value to be passed for the check.")
|
|
|
|
|
+ parser.add_option("-u", "--units", default=None,
|
|
|
|
|
+ help="The unit prefix (k, Ki, M, Mi, G, Gi, T, Ti) for b and B unit "
|
|
|
|
|
+ "types which calculates the value returned.")
|
|
|
|
|
+ parser.add_option("-n", "--unit", default=None,
|
|
|
|
|
+ help="Overrides the unit with whatever unit you define. "
|
|
|
|
|
+ "Does not perform calculations. This changes the unit of measurement only.")
|
|
|
|
|
+ parser.add_option("-a", "--arguments", default=None,
|
|
|
|
|
+ help="Arguments for the plugin to be run. Not necessary "
|
|
|
|
|
+ "unless you're running a custom plugin. Given in the same "
|
|
|
|
|
+ "as you would call from the command line. Example: -a '-w 10 -c 20 -f /usr/local'")
|
|
|
|
|
+ parser.add_option("-t", "--token", default='',
|
|
|
|
|
+ help="The token for connecting.")
|
|
|
|
|
+ parser.add_option("-T", "--timeout", default=60, type="int",
|
|
|
|
|
+ help="Enforced timeout, will terminate plugins after "
|
|
|
|
|
+ "this amount of seconds. [%default]")
|
|
|
|
|
+ parser.add_option("-d", "--delta", action='store_true',
|
|
|
|
|
+ help="Signals that this check is a delta check and a "
|
|
|
|
|
+ "local state will kept.")
|
|
|
|
|
+ parser.add_option("-l", "--list", action='store_true',
|
|
|
|
|
+ help="List all values under a given node. Do not perform "
|
|
|
|
|
+ "a check.")
|
|
|
|
|
+ parser.add_option("-v", "--verbose", action='store_true',
|
|
|
|
|
+ help='Print more verbose error messages.')
|
|
|
|
|
+ parser.add_option("-D", "--debug", action='store_true',
|
|
|
|
|
+ help='Print LOTS of error messages. Used mostly for debugging.')
|
|
|
|
|
+ parser.add_option("-V", "--version", action='store_true',
|
|
|
|
|
+ help='Print version number of plugin.')
|
|
|
|
|
+ parser.add_option("-q", "--queryargs", default=None,
|
|
|
|
|
+ help='Extra query arguments to pass in the NCPA URL.')
|
|
|
|
|
+ parser.add_option("-s", "--secure", action='store_true', default=False,
|
|
|
|
|
+ help='Require successful certificate verification. Does not work on Python < 2.7.9.')
|
|
|
|
|
+ parser.add_option("-p", "--performance", action='store_true', default=False,
|
|
|
|
|
+ help='Print performance data even when there is none. '
|
|
|
|
|
+ 'Will print data matching the return code of this script')
|
|
|
|
|
+ options, _ = parser.parse_args()
|
|
|
|
|
+
|
|
|
|
|
+ if options.version:
|
|
|
|
|
+ print(version)
|
|
|
|
|
+ sys.exit(0)
|
|
|
|
|
+
|
|
|
|
|
+ if options.arguments and options.metric and not 'plugin' in options.metric:
|
|
|
|
|
+ parser.print_help()
|
|
|
|
|
+ parser.error('You cannot specify arguments without running a custom plugin.')
|
|
|
|
|
+
|
|
|
|
|
+ if not options.hostname:
|
|
|
|
|
+ parser.print_help()
|
|
|
|
|
+ parser.error("Hostname is required for use.")
|
|
|
|
|
+
|
|
|
|
|
+ elif not options.metric and not options.list:
|
|
|
|
|
+ parser.print_help()
|
|
|
|
|
+ parser.error('No metric given, if you want to list all possible items '
|
|
|
|
|
+ 'use --list.')
|
|
|
|
|
+
|
|
|
|
|
+ options.metric = re.sub(r'^/?(api/)?', '', options.metric)
|
|
|
|
|
+
|
|
|
|
|
+ return options
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+# ~ The following are all helper functions. I would normally split these out into
|
|
|
|
|
+# ~ a new module but this needs to be portable.
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def get_url_from_options(options):
|
|
|
|
|
+ host_part = get_host_part_from_options(options)
|
|
|
|
|
+ arguments = get_arguments_from_options(options)
|
|
|
|
|
+ return '%s?%s' % (host_part, arguments)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def get_host_part_from_options(options):
|
|
|
|
|
+ """Gets the address that will be queries for the JSON.
|
|
|
|
|
+
|
|
|
|
|
+ """
|
|
|
|
|
+ hostname = options.hostname
|
|
|
|
|
+ port = options.port
|
|
|
|
|
+
|
|
|
|
|
+ if not options.metric is None:
|
|
|
|
|
+ metric = urlquote(options.metric)
|
|
|
|
|
+ else:
|
|
|
|
|
+ metric = ''
|
|
|
|
|
+
|
|
|
|
|
+ arguments = get_check_arguments_from_options(options)
|
|
|
|
|
+ if not metric and not arguments:
|
|
|
|
|
+ api_address = 'https://%s:%d/api' % (hostname, port)
|
|
|
|
|
+ else:
|
|
|
|
|
+ api_address = 'https://%s:%d/api/%s/%s' % (hostname, port, metric, arguments)
|
|
|
|
|
+
|
|
|
|
|
+ return api_address
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def get_check_arguments_from_options(options):
|
|
|
|
|
+ """Gets the escaped URL for plugin arguments to be added
|
|
|
|
|
+ to the end of the host URL. This is different from the get_arguments_from_options
|
|
|
|
|
+ in that this is meant for the syntax when the user is calling a check, whereas the below
|
|
|
|
|
+ is when GET arguments need to be added.
|
|
|
|
|
+
|
|
|
|
|
+ """
|
|
|
|
|
+ arguments = options.arguments
|
|
|
|
|
+ if arguments is None:
|
|
|
|
|
+ return ''
|
|
|
|
|
+ else:
|
|
|
|
|
+ lex = shlex.shlex(arguments)
|
|
|
|
|
+ lex.whitespace_split = True
|
|
|
|
|
+ arguments = '/'.join([urlquote(x, safe='') for x in lex])
|
|
|
|
|
+ return arguments
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def get_arguments_from_options(options, **kwargs):
|
|
|
|
|
+ """Returns the http query arguments. If there is a list variable specified,
|
|
|
|
|
+ it will return the arguments necessary to query for a list.
|
|
|
|
|
+
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ # Note: Changed back to units due to the units being what is passed via the
|
|
|
|
|
+ # API call which can confuse people if they don't match
|
|
|
|
|
+ arguments = { 'token': options.token,
|
|
|
|
|
+ 'units': options.units }
|
|
|
|
|
+
|
|
|
|
|
+ if not options.list:
|
|
|
|
|
+ arguments['warning'] = options.warning
|
|
|
|
|
+ arguments['critical'] = options.critical
|
|
|
|
|
+ arguments['delta'] = options.delta
|
|
|
|
|
+ arguments['check'] = 1
|
|
|
|
|
+ arguments['unit'] = options.unit
|
|
|
|
|
+
|
|
|
|
|
+ if options.queryargs:
|
|
|
|
|
+ for argument in options.queryargs.split(','):
|
|
|
|
|
+ key, value = argument.split('=')
|
|
|
|
|
+ arguments[key] = value
|
|
|
|
|
+
|
|
|
|
|
+ #~ Encode the items in the dictionary that are not None
|
|
|
|
|
+ return urlencode(dict((k, v) for k, v in list(arguments.items()) if v is not None))
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def get_json(options):
|
|
|
|
|
+ """Get the page given by the options. This will call down the url and
|
|
|
|
|
+ encode its finding into a Python object (from JSON).
|
|
|
|
|
+
|
|
|
|
|
+ """
|
|
|
|
|
+ url = get_url_from_options(options)
|
|
|
|
|
+
|
|
|
|
|
+ if options.verbose:
|
|
|
|
|
+ print('Connecting to: ' + url)
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ ctx = ssl.create_default_context()
|
|
|
|
|
+ if not options.secure:
|
|
|
|
|
+ ctx.check_hostname = False
|
|
|
|
|
+ ctx.verify_mode = ssl.CERT_NONE
|
|
|
|
|
+ ret = urlopen(url, context=ctx)
|
|
|
|
|
+ except AttributeError:
|
|
|
|
|
+ ret = urlopen(url)
|
|
|
|
|
+
|
|
|
|
|
+ ret = ''.join(ret)
|
|
|
|
|
+
|
|
|
|
|
+ if options.verbose:
|
|
|
|
|
+ print('File returned contained:\n' + ret)
|
|
|
|
|
+
|
|
|
|
|
+ arr = json.loads(ret)
|
|
|
|
|
+
|
|
|
|
|
+ if 'value' in arr:
|
|
|
|
|
+ return arr['value']
|
|
|
|
|
+
|
|
|
|
|
+ return arr
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def run_check(info_json):
|
|
|
|
|
+ """Run a check against the remote host.
|
|
|
|
|
+
|
|
|
|
|
+ """
|
|
|
|
|
+ return info_json['stdout'], info_json['returncode']
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def show_list(info_json):
|
|
|
|
|
+ """Show the list of available options.
|
|
|
|
|
+
|
|
|
|
|
+ """
|
|
|
|
|
+ return pretty(info_json), 0
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def timeout_handler(threshold):
|
|
|
|
|
+ def wrapped(signum, frames):
|
|
|
|
|
+ stdout = "UNKNOWN: Execution exceeded timeout threshold of %ds" % threshold
|
|
|
|
|
+ print stdout
|
|
|
|
|
+ sys.exit(3)
|
|
|
|
|
+ return wrapped
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def main():
|
|
|
|
|
+ options = parse_args()
|
|
|
|
|
+
|
|
|
|
|
+ # We need to ensure that we will only execute for a certain amount of
|
|
|
|
|
+ # seconds.
|
|
|
|
|
+ signal.signal(signal.SIGALRM, timeout_handler(options.timeout))
|
|
|
|
|
+ signal.alarm(options.timeout)
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+
|
|
|
|
|
+ if options.version:
|
|
|
|
|
+ stdout = 'The version of this plugin is %s' % __VERSION__
|
|
|
|
|
+ return stdout, 0
|
|
|
|
|
+
|
|
|
|
|
+ info_json = get_json(options)
|
|
|
|
|
+ if options.list:
|
|
|
|
|
+ return show_list(info_json)
|
|
|
|
|
+ else:
|
|
|
|
|
+ stdout, returncode = run_check(info_json)
|
|
|
|
|
+ if options.performance and stdout.find("|") == -1:
|
|
|
|
|
+ performance = " | 'status'={};1;2;".format(returncode)
|
|
|
|
|
+ return "{}{}".format(stdout, performance), returncode
|
|
|
|
|
+ else:
|
|
|
|
|
+ return stdout, returncode
|
|
|
|
|
+ except Exception, e:
|
|
|
|
|
+ if options.debug:
|
|
|
|
|
+ return 'The stack trace:' + traceback.format_exc(), 3
|
|
|
|
|
+ elif options.verbose:
|
|
|
|
|
+ return 'An error occurred:' + str(e), 3
|
|
|
|
|
+ else:
|
|
|
|
|
+ return 'UNKNOWN: Error occurred while running the plugin. Use the verbose flag for more details.', 3
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+if __name__ == "__main__":
|
|
|
|
|
+ stdout, returncode = main()
|
|
|
|
|
+ print(stdout)
|
|
|
|
|
+ sys.exit(returncode)
|