54 Commits
v1.4 ... v2.1.1

Author SHA1 Message Date
Markus Opolka
e3ac06864d Merge pull request #68 from ccin2p3/feature/load_default_ca_certs
[TLS] Always load system default C.A files
2021-01-22 10:39:33 +01:00
Rémi Ferrand
63542f3226 If TLS is enabled, context now loads the system default C.A files
* This allows system wide deployed C.A to be used without any further
  configuration.
2021-01-21 12:02:41 +01:00
Markus Opolka
cdb2474ee0 Update README 2020-11-24 20:27:40 +01:00
Markus Opolka
2821a1ae66 Merge pull request #66 from drewkerrigan/array-bug
Fix conditional check on empty data.
2020-09-14 10:36:14 +02:00
Markus Opolka
831bfdf97b Merge pull request #65 from alesc/patch-1
Update icinga2_check_command_definition.conf
2020-09-12 08:21:01 +02:00
alesc
f612277772 Update icinga2_check_command_definition.conf
small error in icinga2 conf definition, --key_metricS does not exist --key_metric does.
2020-09-11 10:18:02 +02:00
Markus Opolka
1f440e0ff5 Fix conditional check on empty data.
Fixes issue #64
2020-07-15 08:07:16 +02:00
Markus Opolka
c23ebac77a Merge pull request #63 from drewkerrigan/v2-1
Release 2.1
2020-07-03 09:58:55 +02:00
Markus Opolka
a014944981 Merge pull request #62 from drewkerrigan/key-equals-empty
Add handling of empty JSON return values
2020-07-03 09:04:53 +02:00
Markus Opolka
47a37556ba Merge branch 'v2-1' into key-equals-empty 2020-07-03 09:03:21 +02:00
Markus Opolka
41279cad2c Merge pull request #60 from drewkerrigan/http-json
Parse JSON on HTTPError, if JSON in response
2020-07-03 09:02:14 +02:00
Markus Opolka
f7c0472cdc Add JSON parsing on HTTPError
- Only if response contains JSON
2020-07-02 11:35:23 +02:00
Markus Opolka
25fb340bbb Add handling of empty JSON return values
- Will now throw a CRITICAL
2020-06-26 10:39:37 +02:00
Markus Opolka
47bdea7fc5 Add spaces to debug output 2020-06-26 10:31:20 +02:00
Markus Opolka
866a12ea07 Add JSON parsing on HTTPError
- Only if response contains JSON
2020-06-19 14:26:59 +02:00
Markus Opolka
d1e585b2dd Adjust pylint for new function 2020-06-19 13:16:07 +02:00
Markus Opolka
941afeed89 Move main entrypoint to own function for simpler testing 2020-06-19 13:12:51 +02:00
Markus Opolka
b9a583f281 Add Makefile and requirements.txt for easier testing 2020-06-19 13:12:48 +02:00
Markus Opolka
4c89a8a93d Update README 2020-04-03 11:53:00 +02:00
Markus Opolka
73557b3657 Merge pull request #58 from bb-Ricardo/next-release
added icinga2 command definitions
2020-04-03 11:50:29 +02:00
Ricardo Bartels
aad2376ac0 added missing cli args for V2.0 2020-04-02 08:39:42 +02:00
Markus Opolka
219e99386c Merge pull request #57 from drewkerrigan/v2.0
Release V2.0
2020-03-31 18:15:06 +02:00
Markus Opolka
0cbbf41b9c Update README 2020-03-23 09:11:22 +01:00
Markus Opolka
dd952fd571 Replace deprecated encodestring
- Fixes #56
2020-03-18 08:47:27 +01:00
Markus Opolka
83ee5062f5 [wip] Add pylint and fix pylint issues 2020-03-18 08:18:07 +01:00
Markus Opolka
c90b0323f5 Show returned JSON in OK Status when performance data is requested 2020-03-18 07:41:23 +01:00
Markus Opolka
1ac160e8c2 Add boilerplate for CLI tests 2020-03-18 07:41:23 +01:00
Markus Opolka
6fc41612c4 Add unittest for debugprint 2020-03-18 07:41:23 +01:00
Markus Opolka
f567c1ca0c Add unittest for metric key alias 2020-03-18 07:41:23 +01:00
Markus Opolka
2c98e840e8 Extend unittest coverage 2020-03-18 07:41:18 +01:00
Markus Opolka
1a9e1e9048 Add unittest for NagiosHelper 2020-03-15 09:45:18 +01:00
Markus Opolka
4f1d29dc7e Merge pull request #55 from marxin/fix-python38-warnings
Fix new Python3.8 warnings:
2020-03-12 14:17:34 +01:00
Martin Liska
404890d918 Fix new Python3.8 warnings:
./check_http_json.py:186: SyntaxWarning: "is" with a literal. Did you mean "=="?
  if elemData is (None, 'not_found'):
./check_http_json.py:189: SyntaxWarning: "is not" with a literal. Did you mean "!="?
  if subElemKey is not '':
2020-03-12 09:37:53 +01:00
Markus Opolka
e7cf7ca8fb Merge pull request #54 from drewkerrigan/fix-issue-43
Add value_separator option to specify how JSON values are being split
2020-03-09 20:39:00 +01:00
Markus Opolka
71cbd98e79 Add value_separator option to specify how JSON values are being split
- Fixes issue 43
2020-03-08 11:28:27 +01:00
Markus Opolka
5c416cd0c0 Merge pull request #53 from drewkerrigan/fix-issue-34
Add boundary check for SubArrayElement function
2020-03-05 19:51:55 +01:00
Markus Opolka
e4801227bf Add boundary check for SubArrayElement function
- Fixes Issue 34
2020-03-05 10:05:32 +01:00
Markus Opolka
b7c0b0595e Add unittest for argsparse 2020-03-03 12:12:52 +01:00
Markus Opolka
375da5d605 Add test case for key_value_list_unknown 2020-03-03 11:59:36 +01:00
Markus Opolka
95912246a2 Add and format some doc_strings 2020-03-03 11:44:55 +01:00
Markus Opolka
ba9d9b1c39 Use Python3 in GitHub Action 2020-03-03 10:04:03 +01:00
Markus Opolka
3f81e32b29 Add CI Badge to README 2020-03-03 10:02:51 +01:00
Markus Opolka
f97759f1bd Add Coverage report 2020-03-03 10:02:51 +01:00
Markus Opolka
e95daad8ff Add GitHub Action for Unit Test 2020-03-03 10:02:49 +01:00
Markus Opolka
174686a980 Move test to separat file 2020-03-03 09:48:10 +01:00
Markus Opolka
24889384b0 Add gitignore file 2020-02-16 09:55:58 +01:00
Markus Opolka
21f48681c9 Merge pull request #50 from marxin/port-to-python3
Port to Python3.
2020-02-16 09:37:00 +01:00
Markus Opolka
2196dba761 Merge pull request #51 from marxin/document-asterisk
Document syntax of array selector: (*).field_name.
2020-02-03 16:06:54 +01:00
marxin
c2435a8cbf Document syntax of array selector: (*).field_name. 2020-02-01 21:47:08 +01:00
Martin Liska
2289fb2af3 Port to Python3 (#48).
I used 2to3 script and then I clean up result of the conversion.
2020-01-28 12:13:30 +01:00
Markus Opolka
2541223cde Merge pull request #49 from drewkerrigan/fix-ssl
Fix --insecure option
2020-01-28 10:52:11 +01:00
Markus Opolka
209aaef041 Fix inconsistent use of tabs/spaces 2020-01-28 10:42:22 +01:00
Markus Opolka
65c3bd2a25 Set default context variable to -k --insecure option 2020-01-28 10:41:29 +01:00
Ricardo Bartels
26a1b3dbe8 added icinga2 command definitions 2019-05-10 11:21:34 +02:00
14 changed files with 898 additions and 276 deletions

27
.github/workflows/unittest.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: CI
on: [push, pull_request]
jobs:
gitHubActionForPytest:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.6, 3.7, 3.8]
name: GitHub Action
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Lint
run: |
pip3 install --upgrade pip wheel setuptools
pip3 install pylint
python3 -m pylint check_http_json.py
- name: Unit Test
run: |
python3 -m unittest discover
- name: Coverage
run: |
pip3 install coverage
python3 -m coverage run -m unittest discover
python3 -m coverage report -m --include check_http_json.py

64
.gitignore vendored Normal file
View File

@@ -0,0 +1,64 @@
#Emacs
\#*
.\#*
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
.venv/
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
# Sphinx documentation
docs/_build/
# PyBuilder
target/
#Ipython Notebook
.ipynb_checkpoints

5
.pylintrc Normal file
View File

@@ -0,0 +1,5 @@
# pylint config
[MESSAGES CONTROL]
disable=line-too-long, redefined-outer-name, too-many-arguments, too-many-instance-attributes, fixme, invalid-name, superfluous-parens, missing-function-docstring, missing-module-docstring, multiple-imports, no-else-return, too-many-return-statements, too-many-branches, too-many-statements
[MASTER]
ignore-patterns=^test.*

View File

@@ -1,3 +1,5 @@
![CI](https://github.com/drewkerrigan/nagios-http-json/workflows/CI/badge.svg)
# Nagios Json Plugin # Nagios Json Plugin
This is a generic plugin for Nagios which checks json values from a given HTTP endpoint against argument specified rules and determines the status and performance data for that service. This is a generic plugin for Nagios which checks json values from a given HTTP endpoint against argument specified rules and determines the status and performance data for that service.
@@ -18,7 +20,7 @@ Executing `./check_http_json.py -h` will yield the following details:
usage: check_http_json.py [-h] [-d] [-s] -H HOST [-k] [-V] [--cacert CACERT] usage: check_http_json.py [-h] [-d] [-s] -H HOST [-k] [-V] [--cacert CACERT]
[--cert CERT] [--key KEY] [-P PORT] [-p PATH] [--cert CERT] [--key KEY] [-P PORT] [-p PATH]
[-t TIMEOUT] [-B AUTH] [-D DATA] [-A HEADERS] [-t TIMEOUT] [-B AUTH] [-D DATA] [-A HEADERS]
[-f SEPARATOR] [-f FIELD_SEPARATOR] [-F VALUE_SEPARATOR]
[-w [KEY_THRESHOLD_WARNING [KEY_THRESHOLD_WARNING ...]]] [-w [KEY_THRESHOLD_WARNING [KEY_THRESHOLD_WARNING ...]]]
[-c [KEY_THRESHOLD_CRITICAL [KEY_THRESHOLD_CRITICAL ...]]] [-c [KEY_THRESHOLD_CRITICAL [KEY_THRESHOLD_CRITICAL ...]]]
[-e [KEY_LIST [KEY_LIST ...]]] [-e [KEY_LIST [KEY_LIST ...]]]
@@ -36,7 +38,7 @@ Generic Nagios plugin which checks json values from a given endpoint against
argument specified rules and determines the status and performance data for argument specified rules and determines the status and performance data for
that service. that service.
Version: 1.4.0 (2019-05-09) Version: 2.0.0 (2020-03-22)
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
@@ -60,6 +62,8 @@ optional arguments:
-f SEPARATOR, --field_separator SEPARATOR -f SEPARATOR, --field_separator SEPARATOR
JSON Field separator, defaults to "."; Select element JSON Field separator, defaults to "."; Select element
in an array with "(" ")" in an array with "(" ")"
-F SEPARATOR, --value_separator SEPARATOR
JSON Value separator, defaults to ":";
-w [KEY_THRESHOLD_WARNING [KEY_THRESHOLD_WARNING ...]], --warning [KEY_THRESHOLD_WARNING [KEY_THRESHOLD_WARNING ...]] -w [KEY_THRESHOLD_WARNING [KEY_THRESHOLD_WARNING ...]], --warning [KEY_THRESHOLD_WARNING [KEY_THRESHOLD_WARNING ...]]
Warning threshold for these values Warning threshold for these values
(key1[>alias],WarnRange key2[>alias],WarnRange). (key1[>alias],WarnRange key2[>alias],WarnRange).
@@ -133,6 +137,21 @@ optional arguments:
} }
] ]
**Data for keys of all items in a list** `(*).capacity.value`:
[
{
"capacity": {
"value": 1000
}
},
{
"capacity": {
"value": 2200
}
}
]
**Data for separator** `-f _` **and key** `(0)_gauges_jvm.buffers.direct.capacity_value`: **Data for separator** `-f _` **and key** `(0)_gauges_jvm.buffers.direct.capacity_value`:
[ [
@@ -191,7 +210,7 @@ More info about Nagios Range format and Units of Measure can be found at [https:
### Requirements ### Requirements
* Python 2.7 * Python 3.6+
### Configuration ### Configuration
@@ -226,7 +245,10 @@ define command{
``` ```
More info about options in Usage. ## Icinga2 configuration
The Icinga2 command definition can be found here: (contrib/icinga2_check_command_definition.conf)
## License ## License

View File

@@ -1,4 +1,14 @@
#!/usr/bin/python2.7 #!/usr/bin/env python3
import urllib.request, urllib.error, urllib.parse
import base64
import json
import argparse
import sys
import ssl
from pprint import pprint
from urllib.error import HTTPError
from urllib.error import URLError
plugin_description = \ plugin_description = \
""" """
@@ -9,26 +19,19 @@ argument specified rules and determines the status and performance data for
that service. that service.
""" """
import urllib2
import base64
import json
import argparse
import sys
import ssl
from pprint import pprint
from urllib2 import HTTPError
from urllib2 import URLError
OK_CODE = 0 OK_CODE = 0
WARNING_CODE = 1 WARNING_CODE = 1
CRITICAL_CODE = 2 CRITICAL_CODE = 2
UNKNOWN_CODE = 3 UNKNOWN_CODE = 3
__version__ = '1.4.0' __version__ = '2.0.0'
__version_date__ = '2019-05-09' __version_date__ = '2020-03-22'
class NagiosHelper: class NagiosHelper:
"""Help with Nagios specific status string formatting.""" """
Help with Nagios specific status string formatting.
"""
message_prefixes = {OK_CODE: 'OK', message_prefixes = {OK_CODE: 'OK',
WARNING_CODE: 'WARNING', WARNING_CODE: 'WARNING',
CRITICAL_CODE: 'CRITICAL', CRITICAL_CODE: 'CRITICAL',
@@ -38,17 +41,23 @@ class NagiosHelper:
critical_message = '' critical_message = ''
unknown_message = '' unknown_message = ''
def getMessage(self): def getMessage(self, message=''):
"""Build a status-prefixed message with optional performance data """
generated externally""" Build a status-prefixed message with optional performance data
text = "%s: Status %s." % (self.message_prefixes[self.getCode()], generated externally
self.message_prefixes[self.getCode()]) """
text += self.warning_message
text += self.critical_message message += self.warning_message
text += self.unknown_message message += self.critical_message
message += self.unknown_message
code = self.message_prefixes[self.getCode()]
output = "{code}: Status {code}. {message}".format(code=code, message=message.strip())
if self.performance_data: if self.performance_data:
text += "|%s" % self.performance_data output = "{code}: {perf_data} Status {code}. {message}|{perf_data}".format(
return text code=code,
message=message.strip(),
perf_data=self.performance_data)
return output.strip()
def getCode(self): def getCode(self):
code = OK_CODE code = OK_CODE
@@ -69,19 +78,23 @@ class NagiosHelper:
def append_unknown(self, unknown_message): def append_unknown(self, unknown_message):
self.unknown_message += unknown_message self.unknown_message += unknown_message
def append_metrics(self, (performance_data, def append_metrics(self, metrics):
warning_message, critical_message)): (performance_data, warning_message, critical_message) = metrics
self.performance_data += performance_data self.performance_data += performance_data
self.append_warning(warning_message) self.append_warning(warning_message)
self.append_critical(critical_message) self.append_critical(critical_message)
class JsonHelper: class JsonHelper:
"""Perform simple comparison operations against values in a given """
JSON dict""" Perform simple comparison operations against values in a given
def __init__(self, json_data, separator): JSON dict
"""
def __init__(self, json_data, separator, value_separator):
self.data = json_data self.data = json_data
self.separator = separator self.separator = separator
self.value_separator = value_separator
self.arrayOpener = '(' self.arrayOpener = '('
self.arrayCloser = ')' self.arrayCloser = ')'
@@ -91,7 +104,6 @@ class JsonHelper:
remainingKey = key[separatorIndex + 1:] remainingKey = key[separatorIndex + 1:]
if partialKey in data: if partialKey in data:
return self.get(remainingKey, data[partialKey]) return self.get(remainingKey, data[partialKey])
else:
return (None, 'not_found') return (None, 'not_found')
def getSubArrayElement(self, key, data): def getSubArrayElement(self, key, data):
@@ -99,6 +111,7 @@ class JsonHelper:
index = int(key[key.find(self.arrayOpener) + index = int(key[key.find(self.arrayOpener) +
1:key.find(self.arrayCloser)]) 1:key.find(self.arrayCloser)])
remainingKey = key[key.find(self.arrayCloser + self.separator) + 2:] remainingKey = key[key.find(self.arrayCloser + self.separator) + 2:]
if key.find(self.arrayCloser + self.separator) == -1: if key.find(self.arrayCloser + self.separator) == -1:
remainingKey = key[key.find(self.arrayCloser) + 1:] remainingKey = key[key.find(self.arrayCloser) + 1:]
if subElemKey in data: if subElemKey in data:
@@ -106,6 +119,8 @@ class JsonHelper:
return self.get(remainingKey, data[subElemKey][index]) return self.get(remainingKey, data[subElemKey][index])
else: else:
return (None, 'not_found') return (None, 'not_found')
if index >= len(data):
return (None, 'not_found')
else: else:
if not subElemKey: if not subElemKey:
return self.get(remainingKey, data[index]) return self.get(remainingKey, data[index])
@@ -114,7 +129,7 @@ class JsonHelper:
def equals(self, key, value): def equals(self, key, value):
return self.exists(key) and \ return self.exists(key) and \
str(self.get(key)) in value.split(':') str(self.get(key)) in value.split(self.value_separator)
def lte(self, key, value): def lte(self, key, value):
return self.exists(key) and float(self.get(key)) <= float(value) return self.exists(key) and float(self.get(key)) <= float(value)
@@ -132,9 +147,12 @@ class JsonHelper:
return (self.get(key) != (None, 'not_found')) return (self.get(key) != (None, 'not_found'))
def get(self, key, temp_data=''): def get(self, key, temp_data=''):
"""Can navigate nested json keys with a dot format """
(Element.Key.NestedKey). Returns (None, 'not_found') if not found""" Can navigate nested json keys with a dot format
if temp_data: (Element.Key.NestedKey). Returns (None, 'not_found') if not found
"""
if temp_data != '':
data = temp_data data = temp_data
else: else:
data = self.data data = self.data
@@ -153,7 +171,7 @@ class JsonHelper:
if key.find(self.arrayOpener) != -1: if key.find(self.arrayOpener) != -1:
return self.getSubArrayElement(key, data) return self.getSubArrayElement(key, data)
else: else:
if key in data: if isinstance(data, dict) and key in data:
return data[key] return data[key]
else: else:
return (None, 'not_found') return (None, 'not_found')
@@ -167,10 +185,10 @@ class JsonHelper:
subElemKey = key[:key.find('(*)')-1] subElemKey = key[:key.find('(*)')-1]
remainingKey = key[key.find('(*)')+3:] remainingKey = key[key.find('(*)')+3:]
elemData = self.get(subElemKey) elemData = self.get(subElemKey)
if elemData is (None, 'not_found'): if elemData == (None, 'not_found'):
keys.append(key) keys.append(key)
return keys return keys
if subElemKey is not '': if subElemKey != '':
subElemKey = subElemKey + '.' subElemKey = subElemKey + '.'
for i in range(len(elemData)): for i in range(len(elemData)):
newKey = subElemKey + '(' + str(i) + ')' + remainingKey newKey = subElemKey + '(' + str(i) + ')' + remainingKey
@@ -192,17 +210,24 @@ def _getKeyAlias(original_key):
class JsonRuleProcessor: class JsonRuleProcessor:
"""Perform checks and gather values from a JSON dict given rules """
and metrics definitions""" Perform checks and gather values from a JSON dict given rules
and metrics definitions
"""
def __init__(self, json_data, rules_args): def __init__(self, json_data, rules_args):
self.data = json_data self.data = json_data
self.rules = rules_args self.rules = rules_args
separator = '.' separator = '.'
value_separator = ':'
if self.rules.separator: if self.rules.separator:
separator = self.rules.separator separator = self.rules.separator
self.helper = JsonHelper(self.data, separator) if self.rules.value_separator:
debugPrint(rules_args.debug, "rules:%s" % rules_args) value_separator = self.rules.value_separator
debugPrint(rules_args.debug, "separator:%s" % separator) self.helper = JsonHelper(self.data, separator, value_separator)
debugPrint(rules_args.debug, "rules: %s" % rules_args)
debugPrint(rules_args.debug, "separator: %s" % separator)
debugPrint(rules_args.debug, "value_separator: %s" % value_separator)
self.metric_list = self.expandKeys(self.rules.metric_list) self.metric_list = self.expandKeys(self.rules.metric_list)
self.key_threshold_warning = self.expandKeys( self.key_threshold_warning = self.expandKeys(
self.rules.key_threshold_warning) self.rules.key_threshold_warning)
@@ -222,7 +247,7 @@ class JsonRuleProcessor:
def expandKeys(self, src): def expandKeys(self, src):
if src is None: if src is None:
return return []
dest = [] dest = []
for key in src: for key in src:
newKeys = self.helper.expandKey(key, []) newKeys = self.helper.expandKey(key, [])
@@ -243,7 +268,7 @@ class JsonRuleProcessor:
for kv in equality_list: for kv in equality_list:
k, v = kv.split(',') k, v = kv.split(',')
key, alias = _getKeyAlias(k) key, alias = _getKeyAlias(k)
if (self.helper.equals(key, v) == False): if not self.helper.equals(key, v):
failure += " Key %s mismatch. %s != %s" % (alias, v, failure += " Key %s mismatch. %s != %s" % (alias, v,
self.helper.get(key)) self.helper.get(key))
return failure return failure
@@ -253,7 +278,7 @@ class JsonRuleProcessor:
for kv in equality_list: for kv in equality_list:
k, v = kv.split(',') k, v = kv.split(',')
key, alias = _getKeyAlias(k) key, alias = _getKeyAlias(k)
if (self.helper.equals(key, v) == True): if self.helper.equals(key, v):
failure += " Key %s match found. %s == %s" % (alias, v, failure += " Key %s match found. %s == %s" % (alias, v,
self.helper.get(key)) self.helper.get(key))
return failure return failure
@@ -321,6 +346,8 @@ class JsonRuleProcessor:
def checkCritical(self): def checkCritical(self):
failure = '' failure = ''
if not self.data:
failure = " Empty JSON data."
if self.key_threshold_critical is not None: if self.key_threshold_critical is not None:
failure += self.checkThresholds(self.key_threshold_critical) failure += self.checkThresholds(self.key_threshold_critical)
if self.key_value_list_critical is not None: if self.key_value_list_critical is not None:
@@ -338,8 +365,11 @@ class JsonRuleProcessor:
return unknown return unknown
def checkMetrics(self): def checkMetrics(self):
"""Return a Nagios specific performance metrics string given keys """
and parameter definitions""" Return a Nagios specific performance metrics string given keys
and parameter definitions
"""
metrics = '' metrics = ''
warning = '' warning = ''
critical = '' critical = ''
@@ -380,18 +410,23 @@ class JsonRuleProcessor:
return ("%s" % metrics, warning, critical) return ("%s" % metrics, warning, critical)
def parseArgs(): def parseArgs(args):
"""
CLI argument definitions and parsing
"""
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description = plugin_description + '\n\nVersion: %s (%s)' description=plugin_description + '\n\nVersion: %s (%s)'
%(__version__, __version_date__), %(__version__, __version_date__),
formatter_class=argparse.RawDescriptionHelpFormatter) formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument('-d', '--debug', action='store_true', parser.add_argument('-d', '--debug', action='store_true',
help='debug mode') help='debug mode')
parser.add_argument('-s', '--ssl', action='store_true', parser.add_argument('-s', '--ssl', action='store_true',
help='use TLS to connect to remote host') help='use TLS to connect to remote host')
parser.add_argument('-H', '--host', dest='host', parser.add_argument('-H', '--host', dest='host',
required=not ('-V' in sys.argv or '--version' in sys.argv), required=not ('-V' in args or '--version' in args),
help='remote host to query') help='remote host to query')
parser.add_argument('-k', '--insecure', action='store_true', parser.add_argument('-k', '--insecure', action='store_true',
help='do not check server SSL certificate') help='do not check server SSL certificate')
@@ -416,6 +451,8 @@ def parseArgs():
parser.add_argument('-f', '--field_separator', dest='separator', parser.add_argument('-f', '--field_separator', dest='separator',
help='''JSON Field separator, defaults to "."; help='''JSON Field separator, defaults to ".";
Select element in an array with "(" ")"''') Select element in an array with "(" ")"''')
parser.add_argument('-F', '--value_separator', dest='value_separator',
help='''JSON Value separator, defaults to ":"''')
parser.add_argument('-w', '--warning', dest='key_threshold_warning', parser.add_argument('-w', '--warning', dest='key_threshold_warning',
nargs='*', nargs='*',
help='''Warning threshold for these values help='''Warning threshold for these values
@@ -474,218 +511,33 @@ def parseArgs():
(key[>alias],UnitOfMeasure,WarnRange, (key[>alias],UnitOfMeasure,WarnRange,
CriticalRange).''') CriticalRange).''')
return parser.parse_args() return parser.parse_args(args)
def debugPrint(debug_flag, message, pretty_flag=False): def debugPrint(debug_flag, message, pretty_flag=False):
"""
Print debug messages if -d (debug_flat ) is set.
"""
if debug_flag: if debug_flag:
if pretty_flag: if pretty_flag:
pprint(message) pprint(message)
else: else:
print(message) print(message)
if __name__ == "__main__" and len(sys.argv) >= 2 and sys.argv[1] == 'UnitTest':
import unittest
class RulesHelper: def main(cliargs):
separator = '.' """
debug = False Main entrypoint for CLI
key_threshold_warning = None """
key_value_list = None
key_value_list_not = None
key_list = None
key_threshold_critical = None
key_value_list_critical = None
key_value_list_not_critical = None
key_value_list_unknown = None
key_list_critical = None
metric_list = None
def dash_m(self, data): args = parseArgs(cliargs)
self.metric_list = data
return self
def dash_e(self, data):
self.key_list = data
return self
def dash_E(self, data):
self.key_list_critical = data
return self
def dash_q(self, data):
self.key_value_list = data
return self
def dash_Q(self, data):
self.key_value_list_critical = data
return self
def dash_y(self, data):
self.key_value_list_not = data
return self
def dash_Y(self, data):
self.key_value_list_not_critical = data
return self
def dash_w(self, data):
self.key_threshold_warning = data
return self
def dash_c(self, data):
self.key_threshold_critical = data
return self
class UnitTest(unittest.TestCase):
rules = RulesHelper()
def check_data(self, args, jsondata, code):
data = json.loads(jsondata)
nagios = NagiosHelper() nagios = NagiosHelper()
processor = JsonRuleProcessor(data, args) context = None
nagios.append_warning(processor.checkWarning())
nagios.append_critical(processor.checkCritical())
nagios.append_metrics(processor.checkMetrics())
self.assertEqual(code, nagios.getCode())
def test_metrics(self):
self.check_data(RulesHelper().dash_m(['metric,,1:4,1:5']),
'{"metric": 5}', WARNING_CODE)
self.check_data(RulesHelper().dash_m(['metric,,1:5,1:4']),
'{"metric": 5}', CRITICAL_CODE)
self.check_data(RulesHelper().dash_m(['metric,,1:5,1:5,6,10']),
'{"metric": 5}', CRITICAL_CODE)
self.check_data(RulesHelper().dash_m(['metric,,1:5,1:5,1,4']),
'{"metric": 5}', CRITICAL_CODE)
self.check_data(RulesHelper().dash_m(['metric,s,@1:4,@6:10,1,10']),
'{"metric": 5}', OK_CODE)
self.check_data(RulesHelper().dash_m(['(*).value,s,1:5,1:5']),
'[{"value": 5},{"value": 100}]', CRITICAL_CODE)
def test_exists(self):
self.check_data(RulesHelper().dash_e(['nothere']),
'{"metric": 5}', WARNING_CODE)
self.check_data(RulesHelper().dash_E(['nothere']),
'{"metric": 5}', CRITICAL_CODE)
self.check_data(RulesHelper().dash_e(['metric']),
'{"metric": 5}', OK_CODE)
def test_equality(self):
self.check_data(RulesHelper().dash_q(['metric,6']),
'{"metric": 5}', WARNING_CODE)
self.check_data(RulesHelper().dash_Q(['metric,6']),
'{"metric": 5}', CRITICAL_CODE)
self.check_data(RulesHelper().dash_q(['metric,5']),
'{"metric": 5}', OK_CODE)
def test_non_equality(self):
self.check_data(RulesHelper().dash_y(['metric,6']),
'{"metric": 6}', WARNING_CODE)
self.check_data(RulesHelper().dash_Y(['metric,6']),
'{"metric": 6}', CRITICAL_CODE)
self.check_data(RulesHelper().dash_y(['metric,5']),
'{"metric": 6}', OK_CODE)
def test_warning_thresholds(self):
self.check_data(RulesHelper().dash_w(['metric,5']),
'{"metric": 5}', OK_CODE)
self.check_data(RulesHelper().dash_w(['metric,5:']),
'{"metric": 5}', OK_CODE)
self.check_data(RulesHelper().dash_w(['metric,~:5']),
'{"metric": 5}', OK_CODE)
self.check_data(RulesHelper().dash_w(['metric,1:5']),
'{"metric": 5}', OK_CODE)
self.check_data(RulesHelper().dash_w(['metric,@5']),
'{"metric": 6}', OK_CODE)
self.check_data(RulesHelper().dash_w(['metric,@5:']),
'{"metric": 4}', OK_CODE)
self.check_data(RulesHelper().dash_w(['metric,@~:5']),
'{"metric": 6}', OK_CODE)
self.check_data(RulesHelper().dash_w(['metric,@1:5']),
'{"metric": 6}', OK_CODE)
self.check_data(RulesHelper().dash_w(['metric,5']),
'{"metric": 6}', WARNING_CODE)
self.check_data(RulesHelper().dash_w(['metric,5:']),
'{"metric": 4}', WARNING_CODE)
self.check_data(RulesHelper().dash_w(['metric,~:5']),
'{"metric": 6}', WARNING_CODE)
self.check_data(RulesHelper().dash_w(['metric,1:5']),
'{"metric": 6}', WARNING_CODE)
self.check_data(RulesHelper().dash_w(['metric,@5']),
'{"metric": 5}', WARNING_CODE)
self.check_data(RulesHelper().dash_w(['metric,@5:']),
'{"metric": 5}', WARNING_CODE)
self.check_data(RulesHelper().dash_w(['metric,@~:5']),
'{"metric": 5}', WARNING_CODE)
self.check_data(RulesHelper().dash_w(['metric,@1:5']),
'{"metric": 5}', WARNING_CODE)
self.check_data(RulesHelper().dash_w(['(*).value,@1:5']),
'[{"value": 5},{"value": 1000}]', WARNING_CODE)
def test_critical_thresholds(self):
self.check_data(RulesHelper().dash_c(['metric,5']),
'{"metric": 5}', OK_CODE)
self.check_data(RulesHelper().dash_c(['metric,5:']),
'{"metric": 5}', OK_CODE)
self.check_data(RulesHelper().dash_c(['metric,~:5']),
'{"metric": 5}', OK_CODE)
self.check_data(RulesHelper().dash_c(['metric,1:5']),
'{"metric": 5}', OK_CODE)
self.check_data(RulesHelper().dash_c(['metric,@5']),
'{"metric": 6}', OK_CODE)
self.check_data(RulesHelper().dash_c(['metric,@5:']),
'{"metric": 4}', OK_CODE)
self.check_data(RulesHelper().dash_c(['metric,@~:5']),
'{"metric": 6}', OK_CODE)
self.check_data(RulesHelper().dash_c(['metric,@1:5']),
'{"metric": 6}', OK_CODE)
self.check_data(RulesHelper().dash_c(['metric,5']),
'{"metric": 6}', CRITICAL_CODE)
self.check_data(RulesHelper().dash_c(['metric,5:']),
'{"metric": 4}', CRITICAL_CODE)
self.check_data(RulesHelper().dash_c(['metric,~:5']),
'{"metric": 6}', CRITICAL_CODE)
self.check_data(RulesHelper().dash_c(['metric,1:5']),
'{"metric": 6}', CRITICAL_CODE)
self.check_data(RulesHelper().dash_c(['metric,@5']),
'{"metric": 5}', CRITICAL_CODE)
self.check_data(RulesHelper().dash_c(['metric,@5:']),
'{"metric": 5}', CRITICAL_CODE)
self.check_data(RulesHelper().dash_c(['metric,@~:5']),
'{"metric": 5}', CRITICAL_CODE)
self.check_data(RulesHelper().dash_c(['metric,@1:5']),
'{"metric": 5}', CRITICAL_CODE)
self.check_data(RulesHelper().dash_c(['(*).value,@1:5']),
'[{"value": 5},{"value": 1000}]', CRITICAL_CODE)
def test_separator(self):
rules = RulesHelper()
rules.separator = '_'
self.check_data(
rules.dash_q(
['(0)_gauges_jvm.buffers.direct.capacity(1)_value,1234']),
'''[{ "gauges": { "jvm.buffers.direct.capacity": [
{"value": 215415},{"value": 1234}]}}]''',
OK_CODE)
self.check_data(
rules.dash_q(
['(*)_gauges_jvm.buffers.direct.capacity(1)_value,1234']),
'''[{ "gauges": { "jvm.buffers.direct.capacity": [
{"value": 215415},{"value": 1234}]}},
{ "gauges": { "jvm.buffers.direct.capacity": [
{"value": 215415},{"value": 1235}]}}]''',
WARNING_CODE)
unittest.main()
exit(0)
"""Program entry point"""
if __name__ == "__main__":
args = parseArgs()
nagios = NagiosHelper()
if args.version: if args.version:
print('Version: %s - Date: %s' % (__version__, __version_date__)) print('Version: %s - Date: %s' % (__version__, __version_date__))
exit(0) sys.exit(0)
if args.ssl: if args.ssl:
url = "https://%s" % args.host url = "https://%s" % args.host
@@ -698,30 +550,31 @@ if __name__ == "__main__":
context.verify_mode = ssl.CERT_NONE context.verify_mode = ssl.CERT_NONE
else: else:
context.verify_mode = ssl.CERT_OPTIONAL context.verify_mode = ssl.CERT_OPTIONAL
context.load_default_certs()
if args.cacert: if args.cacert:
try: try:
context.load_verify_locations(args.cacert) context.load_verify_locations(args.cacert)
except ssl.SSLError: except ssl.SSLError:
nagios.append_unknown( nagios.append_unknown(
''' Error loading SSL CA cert "%s"!''' 'Error loading SSL CA cert "%s"!'
% args.cacert) % args.cacert)
if args.cert: if args.cert:
try: try:
context.load_cert_chain(args.cert,keyfile=args.key) context.load_cert_chain(args.cert, keyfile=args.key)
except ssl.SSLError: except ssl.SSLError:
if args.key: if args.key:
nagios.append_unknown( nagios.append_unknown(
''' Error loading SSL cert. Make sure key "%s" belongs to cert "%s"!''' 'Error loading SSL cert. Make sure key "%s" belongs to cert "%s"!'
% (args.key, args.cert)) % (args.key, args.cert))
else: else:
nagios.append_unknown( nagios.append_unknown(
''' Error loading SSL cert. Make sure "%s" contains the key as well!''' 'Error loading SSL cert. Make sure "%s" contains the key as well!'
% (args.cert)) % (args.cert))
if nagios.getCode() != OK_CODE: if nagios.getCode() != OK_CODE:
print(nagios.getMessage()) print(nagios.getMessage())
exit(nagios.getCode()) sys.exit(nagios.getCode())
else: else:
url = "http://%s" % args.host url = "http://%s" % args.host
@@ -729,13 +582,16 @@ if __name__ == "__main__":
url += ":%s" % args.port url += ":%s" % args.port
if args.path: if args.path:
url += "/%s" % args.path url += "/%s" % args.path
debugPrint(args.debug, "url:%s" % url) debugPrint(args.debug, "url:%s" % url)
json_data = '' json_data = ''
try: try:
req = urllib2.Request(url) req = urllib.request.Request(url)
req.add_header("User-Agent", "check_http_json") req.add_header("User-Agent", "check_http_json")
if args.auth: if args.auth:
base64str = base64.encodestring(args.auth).replace('\n', '') authbytes = str(args.auth).encode()
base64str = base64.encodebytes(authbytes).decode().replace('\n', '')
req.add_header('Authorization', 'Basic %s' % base64str) req.add_header('Authorization', 'Basic %s' % base64str)
if args.headers: if args.headers:
headers = json.loads(args.headers) headers = json.loads(args.headers)
@@ -743,19 +599,23 @@ if __name__ == "__main__":
for header in headers: for header in headers:
req.add_header(header, headers[header]) req.add_header(header, headers[header])
if args.timeout and args.data: if args.timeout and args.data:
response = urllib2.urlopen(req, timeout=args.timeout, response = urllib.request.urlopen(req, timeout=args.timeout,
data=args.data, context=context) data=args.data, context=context)
elif args.timeout: elif args.timeout:
response = urllib2.urlopen(req, timeout=args.timeout, response = urllib.request.urlopen(req, timeout=args.timeout,
context=context) context=context)
elif args.data: elif args.data:
response = urllib2.urlopen(req, data=args.data, context=context) response = urllib.request.urlopen(req, data=args.data, context=context)
else: else:
response = urllib2.urlopen(req, context=context) response = urllib.request.urlopen(req, context=context)
json_data = response.read() json_data = response.read()
except HTTPError as e: except HTTPError as e:
# Try to recover from HTTP Error, if there is JSON in the response
if "json" in e.info().get_content_subtype():
json_data = e.read()
else:
nagios.append_unknown(" HTTPError[%s], url:%s" % (str(e.code), url)) nagios.append_unknown(" HTTPError[%s], url:%s" % (str(e.code), url))
except URLError as e: except URLError as e:
nagios.append_critical(" URLError[%s], url:%s" % (str(e.reason), url)) nagios.append_critical(" URLError[%s], url:%s" % (str(e.reason), url))
@@ -777,6 +637,11 @@ if __name__ == "__main__":
# Print Nagios specific string and exit appropriately # Print Nagios specific string and exit appropriately
print(nagios.getMessage()) print(nagios.getMessage())
exit(nagios.getCode()) sys.exit(nagios.getCode())
if __name__ == "__main__":
# Program entry point
main(sys.argv[1:])
#EOF #EOF

View File

@@ -0,0 +1,106 @@
object CheckCommand "http_json" {
import "plugin-check-command"
command = [ PluginDir + "/check_http_json.py" ]
arguments = {
"--host" = {
value = "$address$"
description = "Hostname or address of the interface to query"
required = true
}
"--port" = {
value = "$http_json_port$"
description = "TCP port number"
}
"--path" = {
value = "$http_json_path$"
description = "URL path to query (i.e.: /v1/service/xyz)"
}
"--timeout" = {
value = "$http_json_timeout$"
description = "Connection timeout (seconds)"
}
"--basic-auth" = {
value = "$http_json_basic_auth$"
description = "Basic auth string 'username:password'"
}
"--ssl" = {
set_if = "$http_json_ssl$"
description = "use TLS to connect to remote host"
}
"--insecure" = {
set_if = "$http_json_insecure$"
description = "do not check server SSL certificate"
}
"--cacert" = {
value = "$http_json_cacert_file$"
description = "path of cacert file to validate server cert"
}
"--cert" = {
value = "$http_json_cert_file$"
description = "client certificate in PEM format"
}
"--key" = {
value = "$http_json_key_file$"
description = "client certificate key file in PEM format ( if not bundled into the cert )"
}
"--data" = {
value = "$http_json_post_data$"
description = "the http payload to send as a POST"
}
"--headers" = {
value = "$http_json_headers$"
description = "additional http headers in JSON format to send with the request"
}
"--field_separator" = {
value = "$http_json_field_separator$"
description = "JSON Field separator, defaults to '.'; Select element in an array with '(' ')'"
}
"--value_separator" = {
value = "$http_json_value_separator$"
description = "JSON Value separator, defaults to ':'"
}
"--warning" = {
value = "$http_json_warning$"
description = "Warning threshold for these values, WarningRange is in the format [@]start:end"
}
"--critical" = {
value = "$http_json_critical$"
description = "Critical threshold for these values, CriticalRange is in the format [@]start:end"
}
"--key_exists" = {
value = "$http_json_key_exists$"
description = "Checks existence of these keys to determine status. Return warning if key is not present."
}
"--key_exists_critical" = {
value = "$http_json_key_exists_critical$"
description = "Checks existence of these keys to determine status. Return critical if key is not present."
}
"--key_equals" = {
value = "$http_json_key_equals$"
description = "Checks equality of these keys and values. Return warning if equality check fails"
}
"--key_equals_critical" = {
value = "$http_json_key_equals_critical$"
description = "Checks equality of these keys and values. Return critical if equality check fails"
}
"--key_equals_unknown" = {
value = "$http_json_key_equals_unknown$"
description = "Checks equality of these keys and values. Return unknown if equality check fails"
}
"--key_not_equals" = {
value = "$http_json_key_not_equals$"
description = "Checks equality of these keys and values (key[>alias],value key2,value2) to determine status. Multiple key values can be delimited with colon (key,value1:value2). Return warning if equality check succeeds."
}
"--key_not_equals_critical" = {
value = "$http_json_key_not_equals_critical$"
description = "Checks equality of these keys and values (key[>alias],value key2,value2) to determine status. Multiple key values can be delimited with colon (key,value1:value2). Return critical if equality check succeeds."
}
"--key_metric" = {
value = "$http_json_key_metric$"
description = "Gathers the values of these keys"
}
}
}

9
makefile Normal file
View File

@@ -0,0 +1,9 @@
.PHONY: lint test coverage
lint:
python3 -m pylint check_http_json.py
test:
python3 -m unittest discover
coverage:
python3 -m coverage run -m unittest discover
python3 -m coverage report -m --include check_http_json.py

0
test/__init__.py Normal file
View File

2
test/requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
coverage==5.0.3
pylint==2.4.4

34
test/test_args.py Normal file
View File

@@ -0,0 +1,34 @@
#!/usr/bin/env python3
import unittest
import sys
sys.path.append('..')
from check_http_json import *
class ArgsTest(unittest.TestCase):
"""
Tests for argsparse
"""
def test_parser_defaults(self):
parser = parseArgs(['-H', 'foobar'])
self.assertFalse(parser.debug)
self.assertFalse(parser.ssl)
self.assertFalse(parser.insecure)
def test_parser_with_debug(self):
parser = parseArgs(['-H', 'foobar', '-d'])
self.assertTrue(parser.debug)
def test_parser_with_port(self):
parser = parseArgs(['-H', 'foobar', '-P', '8888'])
self.assertEqual(parser.port, '8888')
def test_parser_with_separator(self):
parser = parseArgs(['-H', 'foobar', '-f', '_', '-F', '_'])
self.assertEqual(parser.separator, '_')
self.assertEqual(parser.value_separator, '_')

View File

@@ -0,0 +1,296 @@
#!/usr/bin/env python3
import json
import unittest
from unittest.mock import patch
import sys
sys.path.append('..')
from check_http_json import *
OK_CODE = 0
WARNING_CODE = 1
CRITICAL_CODE = 2
UNKNOWN_CODE = 3
class RulesHelper:
separator = '.'
value_separator = ':'
debug = False
key_threshold_warning = None
key_value_list = None
key_value_list_not = None
key_list = None
key_threshold_critical = None
key_value_list_critical = None
key_value_list_not_critical = None
key_value_list_unknown = None
key_list_critical = None
metric_list = None
def dash_m(self, data):
self.metric_list = data
return self
def dash_e(self, data):
self.key_list = data
return self
def dash_E(self, data):
self.key_list_critical = data
return self
def dash_q(self, data):
self.key_value_list = data
return self
def dash_Q(self, data):
self.key_value_list_critical = data
return self
def dash_y(self, data):
self.key_value_list_not = data
return self
def dash_Y(self, data):
self.key_value_list_not_critical = data
return self
def dash_U(self, data):
self.key_value_list_unknown = data
return self
def dash_w(self, data):
self.key_threshold_warning = data
return self
def dash_c(self, data):
self.key_threshold_critical = data
return self
class UtilTest(unittest.TestCase):
"""
Tests for the util fucntions
"""
rules = RulesHelper()
def check_data(self, args, jsondata, code):
data = json.loads(jsondata)
nagios = NagiosHelper()
processor = JsonRuleProcessor(data, args)
nagios.append_warning(processor.checkWarning())
nagios.append_critical(processor.checkCritical())
nagios.append_metrics(processor.checkMetrics())
nagios.append_unknown(processor.checkUnknown())
self.assertEqual(code, nagios.getCode())
def test_metrics(self):
self.check_data(RulesHelper().dash_m(['metric,,1:4,1:5']),
'{"metric": 5}', WARNING_CODE)
self.check_data(RulesHelper().dash_m(['metric,,1:5,1:4']),
'{"metric": 5}', CRITICAL_CODE)
self.check_data(RulesHelper().dash_m(['metric,,1:5,1:5,6,10']),
'{"metric": 5}', CRITICAL_CODE)
self.check_data(RulesHelper().dash_m(['metric,,1:5,1:5,1,4']),
'{"metric": 5}', CRITICAL_CODE)
self.check_data(RulesHelper().dash_m(['metric,s,@1:4,@6:10,1,10']),
'{"metric": 5}', OK_CODE)
self.check_data(RulesHelper().dash_m(['(*).value,s,1:5,1:5']),
'[{"value": 5},{"value": 100}]', CRITICAL_CODE)
self.check_data(RulesHelper().dash_m(['metric>foobar,,1:4,1:5']),
'{"metric": 5}', WARNING_CODE)
def test_unknown(self):
self.check_data(RulesHelper().dash_U(['metric,0']),
'{"metric": 3}', UNKNOWN_CODE)
def test_exists(self):
self.check_data(RulesHelper().dash_e(['nothere']),
'{"metric": 5}', WARNING_CODE)
self.check_data(RulesHelper().dash_E(['nothere']),
'{"metric": 5}', CRITICAL_CODE)
self.check_data(RulesHelper().dash_e(['metric']),
'{"metric": 5}', OK_CODE)
def test_equality(self):
self.check_data(RulesHelper().dash_q(['metric,6']),
'{"metric": 5}', WARNING_CODE)
self.check_data(RulesHelper().dash_Q(['metric,6']),
'{"metric": 5}', CRITICAL_CODE)
self.check_data(RulesHelper().dash_q(['metric,5']),
'{"metric": 5}', OK_CODE)
def test_equality_colon(self):
"""
See https://github.com/drewkerrigan/nagios-http-json/issues/43
"""
rules = RulesHelper()
rules.value_separator = '_'
# This should not fail
self.check_data(rules.dash_q(['metric,foo:bar']),
'{"metric": "foo:bar"}', OK_CODE)
def test_non_equality(self):
self.check_data(RulesHelper().dash_y(['metric,6']),
'{"metric": 6}', WARNING_CODE)
self.check_data(RulesHelper().dash_Y(['metric,6']),
'{"metric": 6}', CRITICAL_CODE)
self.check_data(RulesHelper().dash_y(['metric,5']),
'{"metric": 6}', OK_CODE)
def test_warning_thresholds(self):
self.check_data(RulesHelper().dash_w(['metric,5']),
'{"metric": 5}', OK_CODE)
self.check_data(RulesHelper().dash_w(['metric,5:']),
'{"metric": 5}', OK_CODE)
self.check_data(RulesHelper().dash_w(['metric,~:5']),
'{"metric": 5}', OK_CODE)
self.check_data(RulesHelper().dash_w(['metric,1:5']),
'{"metric": 5}', OK_CODE)
self.check_data(RulesHelper().dash_w(['metric,@5']),
'{"metric": 6}', OK_CODE)
self.check_data(RulesHelper().dash_w(['metric,@5:']),
'{"metric": 4}', OK_CODE)
self.check_data(RulesHelper().dash_w(['metric,@~:5']),
'{"metric": 6}', OK_CODE)
self.check_data(RulesHelper().dash_w(['metric,@1:5']),
'{"metric": 6}', OK_CODE)
self.check_data(RulesHelper().dash_w(['metric,5']),
'{"metric": 6}', WARNING_CODE)
self.check_data(RulesHelper().dash_w(['metric,5:']),
'{"metric": 4}', WARNING_CODE)
self.check_data(RulesHelper().dash_w(['metric,~:5']),
'{"metric": 6}', WARNING_CODE)
self.check_data(RulesHelper().dash_w(['metric,1:5']),
'{"metric": 6}', WARNING_CODE)
self.check_data(RulesHelper().dash_w(['metric,@5']),
'{"metric": 5}', WARNING_CODE)
self.check_data(RulesHelper().dash_w(['metric,@5:']),
'{"metric": 5}', WARNING_CODE)
self.check_data(RulesHelper().dash_w(['metric,@~:5']),
'{"metric": 5}', WARNING_CODE)
self.check_data(RulesHelper().dash_w(['metric,@1:5']),
'{"metric": 5}', WARNING_CODE)
self.check_data(RulesHelper().dash_w(['(*).value,@1:5']),
'[{"value": 5},{"value": 1000}]', WARNING_CODE)
def test_critical_thresholds(self):
self.check_data(RulesHelper().dash_c(['metric,5']),
'{"metric": 5}', OK_CODE)
self.check_data(RulesHelper().dash_c(['metric,5:']),
'{"metric": 5}', OK_CODE)
self.check_data(RulesHelper().dash_c(['metric,~:5']),
'{"metric": 5}', OK_CODE)
self.check_data(RulesHelper().dash_c(['metric,1:5']),
'{"metric": 5}', OK_CODE)
self.check_data(RulesHelper().dash_c(['metric,@5']),
'{"metric": 6}', OK_CODE)
self.check_data(RulesHelper().dash_c(['metric,@5:']),
'{"metric": 4}', OK_CODE)
self.check_data(RulesHelper().dash_c(['metric,@~:5']),
'{"metric": 6}', OK_CODE)
self.check_data(RulesHelper().dash_c(['metric,@1:5']),
'{"metric": 6}', OK_CODE)
self.check_data(RulesHelper().dash_c(['metric,5']),
'{"metric": 6}', CRITICAL_CODE)
self.check_data(RulesHelper().dash_c(['metric,5:']),
'{"metric": 4}', CRITICAL_CODE)
self.check_data(RulesHelper().dash_c(['metric,~:5']),
'{"metric": 6}', CRITICAL_CODE)
self.check_data(RulesHelper().dash_c(['metric,1:5']),
'{"metric": 6}', CRITICAL_CODE)
self.check_data(RulesHelper().dash_c(['metric,@5']),
'{"metric": 5}', CRITICAL_CODE)
self.check_data(RulesHelper().dash_c(['metric,@5:']),
'{"metric": 5}', CRITICAL_CODE)
self.check_data(RulesHelper().dash_c(['metric,@~:5']),
'{"metric": 5}', CRITICAL_CODE)
self.check_data(RulesHelper().dash_c(['metric,@1:5']),
'{"metric": 5}', CRITICAL_CODE)
self.check_data(RulesHelper().dash_c(['(*).value,@1:5']),
'[{"value": 5},{"value": 1000}]', CRITICAL_CODE)
def test_separator(self):
rules = RulesHelper()
rules.separator = '_'
self.check_data(
rules.dash_q(
['(0)_gauges_jvm.buffers.direct.capacity(1)_value,1234']),
'''[{ "gauges": { "jvm.buffers.direct.capacity": [
{"value": 215415},{"value": 1234}]}}]''',
OK_CODE)
self.check_data(
rules.dash_q(
['(*)_gauges_jvm.buffers.direct.capacity(1)_value,1234']),
'''[{ "gauges": { "jvm.buffers.direct.capacity": [
{"value": 215415},{"value": 1234}]}},
{ "gauges": { "jvm.buffers.direct.capacity": [
{"value": 215415},{"value": 1235}]}}]''',
WARNING_CODE)
def test_array_with_missing_element(self):
"""
See https://github.com/drewkerrigan/nagios-http-json/issues/34
"""
rules = RulesHelper()
# This should simply work
data = '[{"Node": "there"}]'
self.check_data(rules.dash_q(['(0).Node,there']), data, OK_CODE)
# This should warn us
data = '[{"Node": "othervalue"}]'
self.check_data(rules.dash_q(['(0).Node,there']), data, WARNING_CODE)
# # This should not throw an IndexError
data = '[{"Node": "foobar"}]'
self.check_data(rules.dash_q(['(0).Node,foobar', '(1).Node,missing']), data, WARNING_CODE)
self.check_data(rules.dash_q(['(0).Node,foobar', '(1).Node,missing', '(2).Node,alsomissing']), data, WARNING_CODE)
# This should not throw a KeyError
data = '{}'
self.check_data(rules.dash_q(['(0).Node,foobar', '(1).Node,missing']), data, CRITICAL_CODE)
def test_subelem(self):
rules = RulesHelper()
data = '{"foo": {"foo": {"foo": "bar"}}}'
self.check_data(rules.dash_E(['foo.foo.foo.foo.foo']), data, CRITICAL_CODE)
def test_subarrayelem_missing_elem(self):
rules = RulesHelper()
data = '[{"capacity": {"value": 1000}},{"capacity": {"value": 2200}}]'
self.check_data(rules.dash_E(['(*).capacity.value']), data, OK_CODE)
self.check_data(rules.dash_E(['(*).capacity.value.too_deep']), data, CRITICAL_CODE)
# Should not throw keyerror
self.check_data(rules.dash_E(['foo']), data, CRITICAL_CODE)
def test_empty_key_value_array(self):
"""
https://github.com/drewkerrigan/nagios-http-json/issues/61
"""
rules = RulesHelper()
# This should simply work
data = '[{"update_status": "finished"},{"update_status": "finished"}]'
self.check_data(rules.dash_q(['(*).update_status,finished']), data, OK_CODE)
# This should warn us
data = '[{"update_status": "finished"},{"update_status": "failure"}]'
self.check_data(rules.dash_q(['(*).update_status,finished']), data, WARNING_CODE)
# This should throw an error
data = '[]'
self.check_data(rules.dash_q(['(*).update_status,warn_me']), data, CRITICAL_CODE)

44
test/test_cli.py Normal file
View File

@@ -0,0 +1,44 @@
#!/usr/bin/env python3
import unittest
import unittest.mock as mock
import sys
import os
sys.path.append('..')
from check_http_json import debugPrint
class CLITest(unittest.TestCase):
"""
Tests for CLI
"""
def setUp(self):
"""
Defining the exitcodes
"""
self.exit_0 = 0 << 8
self.exit_1 = 1 << 8
self.exit_2 = 2 << 8
self.exit_3 = 3 << 8
def test_debugprint(self):
with mock.patch('builtins.print') as mock_print:
debugPrint(True, 'debug')
mock_print.assert_called_once_with('debug')
def test_debugprint_pprint(self):
with mock.patch('check_http_json.pprint') as mock_pprint:
debugPrint(True, 'debug', True)
mock_pprint.assert_called_once_with('debug')
def test_cli_without_params(self):
command = '/usr/bin/env python3 check_http_json.py > /dev/null 2>&1'
status = os.system(command)
self.assertEqual(status, self.exit_2)

97
test/test_main.py Normal file
View File

@@ -0,0 +1,97 @@
#!/usr/bin/env python3
import unittest
import unittest.mock as mock
import sys
import os
sys.path.append('..')
from check_http_json import main
class MockResponse():
def __init__(self, status_code=200, content='{"foo": "bar"}'):
self.status_code = status_code
self.content = content
def read(self):
return self.content
class MainTest(unittest.TestCase):
"""
Tests for Main
"""
@mock.patch('builtins.print')
def test_main_version(self, mock_print):
args = ['--version']
with self.assertRaises(SystemExit) as test:
main(args)
mock_print.assert_called_once()
self.assertEqual(test.exception.code, 0)
@mock.patch('builtins.print')
@mock.patch('urllib.request.urlopen')
def test_main_with_ssl(self, mock_request, mock_print):
args = '-H localhost --ssl'.split(' ')
mock_request.return_value = MockResponse()
with self.assertRaises(SystemExit) as test:
main(args)
self.assertEqual(test.exception.code, 0)
@mock.patch('builtins.print')
@mock.patch('urllib.request.urlopen')
def test_main_with_parse_error(self, mock_request, mock_print):
args = '-H localhost'.split(' ')
mock_request.return_value = MockResponse(content='not JSON')
with self.assertRaises(SystemExit) as test:
main(args)
self.assertTrue('Parser error' in str(mock_print.call_args))
self.assertEqual(test.exception.code, 3)
@mock.patch('builtins.print')
def test_main_with_url_error(self, mock_print):
args = '-H localhost'.split(' ')
with self.assertRaises(SystemExit) as test:
main(args)
self.assertTrue('URLError' in str(mock_print.call_args))
self.assertEqual(test.exception.code, 3)
@mock.patch('builtins.print')
@mock.patch('urllib.request.urlopen')
def test_main_with_http_error_no_json(self, mock_request, mock_print):
args = '-H localhost'.split(' ')
mock_request.return_value = MockResponse(content='not JSON', status_code=503)
with self.assertRaises(SystemExit) as test:
main(args)
self.assertTrue('Parser error' in str(mock_print.call_args))
self.assertEqual(test.exception.code, 3)
@mock.patch('builtins.print')
@mock.patch('urllib.request.urlopen')
def test_main_with_http_error_valid_json(self, mock_request, mock_print):
args = '-H localhost'.split(' ')
mock_request.return_value = MockResponse(status_code=503)
with self.assertRaises(SystemExit) as test:
main(args)
self.assertEqual(test.exception.code, 0)

51
test/test_nagioshelper.py Normal file
View File

@@ -0,0 +1,51 @@
#!/usr/bin/env python3
import json
import unittest
from unittest.mock import patch
import sys
sys.path.append('..')
from check_http_json import *
class NagiosHelperTest(unittest.TestCase):
"""
Tests for the NagiosHelper
"""
def test_getcode_default(self):
helper = NagiosHelper()
self.assertEqual(0, helper.getCode())
def test_getcode_warning(self):
helper = NagiosHelper()
helper.warning_message = 'foobar'
self.assertEqual(1, helper.getCode())
def test_getcode_critical(self):
helper = NagiosHelper()
helper.critical_message = 'foobar'
self.assertEqual(2, helper.getCode())
def test_getcode_unknown(self):
helper = NagiosHelper()
helper.unknown_message = 'foobar'
self.assertEqual(3, helper.getCode())
def test_getmessage_default(self):
helper = NagiosHelper()
self.assertEqual('OK: Status OK.', helper.getMessage())
def test_getmessage_perfomance_data(self):
helper = NagiosHelper()
helper.performance_data = 'foobar'
self.assertEqual('OK: foobar Status OK. |foobar', helper.getMessage())