4
0

check_ncpa.in 9.8 KB

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