check_ncpa.in 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. #!@PYTHON@
  2. """
  3. SYNOPSIS
  4. """
  5. import sys
  6. import optparse
  7. import traceback
  8. import ssl
  9. # Python 2/3 Compatibility imports
  10. try:
  11. import json
  12. except ImportError:
  13. import simplejson as json
  14. try:
  15. import urllib.request
  16. import urllib.parse
  17. import urllib.error
  18. except ImportError:
  19. import urllib2
  20. import urllib
  21. try:
  22. urlencode = urllib.parse.urlencode
  23. except AttributeError:
  24. urlencode = urllib.urlencode
  25. try:
  26. urlopen = urllib.request.urlopen
  27. except AttributeError:
  28. urlopen = urllib2.urlopen
  29. try:
  30. urlquote = urllib.parse.quote
  31. except AttributeError:
  32. urlquote = urllib.quote
  33. import shlex
  34. import re
  35. import signal
  36. __VERSION__ = '1.1.5'
  37. def parse_args():
  38. version = 'check_ncpa.py, Version %s' % __VERSION__
  39. parser = optparse.OptionParser()
  40. parser.add_option("-H", "--hostname", help="The hostname to be connected to.")
  41. parser.add_option("-M", "--metric", default='',
  42. help="The metric to check, this is defined on client "
  43. "system. This would also be the plugin name in the "
  44. "plugins directory. Do not attach arguments to it, "
  45. "use the -a directive for that. DO NOT INCLUDE the api/ "
  46. "instruction.")
  47. parser.add_option("-P", "--port", default=5693, type="int",
  48. help="Port to use to connect to the client.")
  49. parser.add_option("-w", "--warning", default=None, type="str",
  50. help="Warning value to be passed for the check.")
  51. parser.add_option("-c", "--critical", default=None, type="str",
  52. help="Critical value to be passed for the check.")
  53. parser.add_option("-u", "--units", default=None,
  54. help="The unit prefix (k, Ki, M, Mi, G, Gi, T, Ti) for b and B unit "
  55. "types which calculates the value returned.")
  56. parser.add_option("-n", "--unit", default=None,
  57. help="Overrides the unit with whatever unit you define. "
  58. "Does not perform calculations. This changes the unit of measurement only.")
  59. parser.add_option("-a", "--arguments", default=None,
  60. help="Arguments for the plugin to be run. Not necessary "
  61. "unless you're running a custom plugin. Given in the same "
  62. "as you would call from the command line. Example: -a '-w 10 -c 20 -f /usr/local'")
  63. parser.add_option("-t", "--token", default='',
  64. help="The token for connecting.")
  65. parser.add_option("-T", "--timeout", default=60, type="int",
  66. help="Enforced timeout, will terminate plugins after "
  67. "this amount of seconds. [%default]")
  68. parser.add_option("-d", "--delta", action='store_true',
  69. help="Signals that this check is a delta check and a "
  70. "local state will kept.")
  71. parser.add_option("-l", "--list", action='store_true',
  72. help="List all values under a given node. Do not perform "
  73. "a check.")
  74. parser.add_option("-v", "--verbose", action='store_true',
  75. help='Print more verbose error messages.')
  76. parser.add_option("-D", "--debug", action='store_true',
  77. help='Print LOTS of error messages. Used mostly for debugging.')
  78. parser.add_option("-V", "--version", action='store_true',
  79. help='Print version number of plugin.')
  80. parser.add_option("-q", "--queryargs", default=None,
  81. help='Extra query arguments to pass in the NCPA URL.')
  82. parser.add_option("-s", "--secure", action='store_true', default=False,
  83. help='Require successful certificate verification. Does not work on Python < 2.7.9.')
  84. parser.add_option("-p", "--performance", action='store_true', default=False,
  85. help='Print performance data even when there is none. '
  86. 'Will print data matching the return code of this script')
  87. options, _ = parser.parse_args()
  88. if options.version:
  89. print(version)
  90. sys.exit(0)
  91. if options.arguments and options.metric and not 'plugin' in options.metric:
  92. parser.print_help()
  93. parser.error('You cannot specify arguments without running a custom plugin.')
  94. if not options.hostname:
  95. parser.print_help()
  96. parser.error("Hostname is required for use.")
  97. elif not options.metric and not options.list:
  98. parser.print_help()
  99. parser.error('No metric given, if you want to list all possible items '
  100. 'use --list.')
  101. options.metric = re.sub(r'^/?(api/)?', '', options.metric)
  102. return options
  103. # ~ The following are all helper functions. I would normally split these out into
  104. # ~ a new module but this needs to be portable.
  105. def get_url_from_options(options):
  106. host_part = get_host_part_from_options(options)
  107. arguments = get_arguments_from_options(options)
  108. return '%s?%s' % (host_part, arguments)
  109. def get_host_part_from_options(options):
  110. """Gets the address that will be queries for the JSON.
  111. """
  112. hostname = options.hostname
  113. port = options.port
  114. if not options.metric is None:
  115. metric = urlquote(options.metric)
  116. else:
  117. metric = ''
  118. arguments = get_check_arguments_from_options(options)
  119. if not metric and not arguments:
  120. api_address = 'https://%s:%d/api' % (hostname, port)
  121. else:
  122. api_address = 'https://%s:%d/api/%s/%s' % (hostname, port, metric, arguments)
  123. return api_address
  124. def get_check_arguments_from_options(options):
  125. """Gets the escaped URL for plugin arguments to be added
  126. to the end of the host URL. This is different from the get_arguments_from_options
  127. in that this is meant for the syntax when the user is calling a check, whereas the below
  128. is when GET arguments need to be added.
  129. """
  130. arguments = options.arguments
  131. if arguments is None:
  132. return ''
  133. else:
  134. lex = shlex.shlex(arguments)
  135. lex.whitespace_split = True
  136. arguments = '/'.join([urlquote(x, safe='') for x in lex])
  137. return arguments
  138. def get_arguments_from_options(options, **kwargs):
  139. """Returns the http query arguments. If there is a list variable specified,
  140. it will return the arguments necessary to query for a list.
  141. """
  142. # Note: Changed back to units due to the units being what is passed via the
  143. # API call which can confuse people if they don't match
  144. arguments = { 'token': options.token,
  145. 'units': options.units }
  146. if not options.list:
  147. arguments['warning'] = options.warning
  148. arguments['critical'] = options.critical
  149. arguments['delta'] = options.delta
  150. arguments['check'] = 1
  151. arguments['unit'] = options.unit
  152. args = list((k, v) for k, v in list(arguments.items()) if v is not None)
  153. # Get the options (comma separated)
  154. if options.queryargs:
  155. # for each comma, perform lookahead, split iff we aren't inside quotes.
  156. arguments_list = re.split(''',(?=(?:[^'"]|'[^']*'|"[^"]*")*$)''', options.queryargs)
  157. for argument in arguments_list:
  158. key, value = argument.split('=', 1)
  159. if value is not None:
  160. args.append((key, value))
  161. #~ Encode the items in the dictionary that are not None
  162. return urlencode(args)
  163. def get_json(options):
  164. """Get the page given by the options. This will call down the url and
  165. encode its finding into a Python object (from JSON).
  166. """
  167. url = get_url_from_options(options)
  168. if options.verbose:
  169. print('Connecting to: ' + url)
  170. try:
  171. ctx = ssl.create_default_context()
  172. if not options.secure:
  173. ctx.check_hostname = False
  174. ctx.verify_mode = ssl.CERT_NONE
  175. ret = urlopen(url, context=ctx)
  176. except AttributeError:
  177. ret = urlopen(url)
  178. ret = ''.join(ret)
  179. if options.verbose:
  180. print('File returned contained:\n' + ret)
  181. arr = json.loads(ret)
  182. # Fix for NCPA < 2
  183. if 'value' in arr:
  184. arr = arr['value']
  185. # We need to flip the returncode and stdout
  186. if isinstance(arr['stdout'], int) and not isinstance(arr['returncode'], int):
  187. tmp = arr['returncode']
  188. arr['returncode'] = arr['stdout']
  189. arr['stdout'] = tmp
  190. return arr
  191. def run_check(info_json):
  192. """Run a check against the remote host.
  193. """
  194. return info_json['stdout'], info_json['returncode']
  195. def show_list(info_json):
  196. """Show the list of available options.
  197. """
  198. return json.dumps(info_json, indent=4), 0
  199. def timeout_handler(threshold):
  200. def wrapped(signum, frames):
  201. stdout = "UNKNOWN: Execution exceeded timeout threshold of %ds" % threshold
  202. print(stdout)
  203. sys.exit(3)
  204. return wrapped
  205. def main():
  206. options = parse_args()
  207. # We need to ensure that we will only execute for a certain amount of
  208. # seconds.
  209. signal.signal(signal.SIGALRM, timeout_handler(options.timeout))
  210. signal.alarm(options.timeout)
  211. try:
  212. if options.version:
  213. stdout = 'The version of this plugin is %s' % __VERSION__
  214. return stdout, 0
  215. info_json = get_json(options)
  216. if options.list:
  217. return show_list(info_json)
  218. else:
  219. stdout, returncode = run_check(info_json)
  220. if options.performance and stdout.find("|") == -1:
  221. performance = " | 'status'={};1;2;".format(returncode)
  222. return "{}{}".format(stdout, performance), returncode
  223. else:
  224. return stdout, returncode
  225. except Exception as e:
  226. if options.debug:
  227. return 'The stack trace:' + traceback.format_exc(), 3
  228. elif options.verbose:
  229. return 'An error occurred:' + str(e), 3
  230. else:
  231. return 'UNKNOWN: Error occurred while running the plugin. Use the verbose flag for more details.', 3
  232. if __name__ == "__main__":
  233. stdout, returncode = main()
  234. print(stdout)
  235. sys.exit(returncode)