# -*- coding: utf-8 -*-
"""Support for classic nagios threshold syntax.
Nagios plugins development team and Monitoring Plugin Development team both
define what the syntax of a threshold should be, and it can be looked up here:
* http://nagiosplug.sourceforge.net/developer-guidelines.html#THRESHOLDFORMAT
* https://www.monitoring-plugins.org/doc/guidelines.html#THRESHOLDFORMAT
"""
from __future__ import absolute_import
import pynag.errors
# Map the return codes
OK = 0
WARNING = 1
CRITICAL = 2
UNKNOWN = 3
class Error(pynag.errors.PynagError):
"""Base class for errors in this module."""
class InvalidThreshold(Error):
"""Raised when an invalid threshold was provided."""
def check_threshold(value, warning=None, critical=None):
""" Checks value against warning/critical and returns Nagios exit code.
Format of range_threshold is according to:
http://nagiosplug.sourceforge.net/developer-guidelines.html#THRESHOLDFORMAT
Returns (in order of appearance):
UNKNOWN int(3) -- On errors or bad input
CRITICAL int(2) -- if value is within critical threshold
WARNING int(1) -- If value is within warning threshold
OK int(0) -- If value is outside both thresholds
Arguments:
value -- value to check
warning -- warning range
critical -- critical range
# Example Usage:
>>> check_threshold(88, warning="0:90", critical="0:95")
0
>>> check_threshold(92, warning=":90", critical=":95")
1
>>> check_threshold(96, warning=":90", critical=":95")
2
"""
if critical and not check_range(value, critical):
return CRITICAL
elif warning and not check_range(value, warning):
return WARNING
else:
return OK
def check_range(value, range_threshold=None):
""" Returns True if value is within range_threshold.
Format of range_threshold is according to:
http://nagiosplug.sourceforge.net/developer-guidelines.html#THRESHOLDFORMAT
Arguments:
value -- Numerical value to check (i.e. 70 )
range -- Range to compare against (i.e. 0:90 )
Returns:
True -- If value is inside the range
False -- If value is outside the range (alert if this happens)
False -- if invalid value is specified
Summary from plugin developer guidelines:
---------------------------------------------------------
x Generate an alert if x...
---------------------------------------------------------
10 < 0 or > 10, (outside the range of {0 .. 10})
10: < 10, (outside {10 .. ∞})
~:10 > 10, (outside the range of {-∞ .. 10})
10:20 < 10 or > 20, (outside the range of {10 .. 20})
@10:20 ≥ 10 and ≤ 20, (inside the range of {10 .. 20})
---------------------------------------------------------
# Example runs for doctest, False should mean alert
>>> check_range(78, "90") # Example disk is 78% full, threshold is 90
True
>>> check_range(5, 10) # Everything between 0 and 10 is True
True
>>> check_range(0, 10) # Everything between 0 and 10 is True
True
>>> check_range(10, 10) # Everything between 0 and 10 is True
True
>>> check_range(11, 10) # Everything between 0 and 10 is True
False
>>> check_range(-1, 10) # Everything between 0 and 10 is True
False
>>> check_range(-1, "~:10") # Everything Below 10
True
>>> check_range(11, "10:") # Everything above 10 is True
True
>>> check_range(1, "10:") # Everything above 10 is True
False
>>> check_range(0, "5:10") # Everything between 5 and 10 is True
False
>>> check_range(0, "@5:10") # Everything outside 5:10 is True
True
>>> check_range(None) # Return False if value is not a number
False
>>> check_range("10000000 PX") # What happens on invalid input
False
>>> check_range("10000000", "invalid:invalid") # What happens on invalid range
Traceback (most recent call last):
...
InvalidThreshold: Invalid threshold format: invalid:invalid
"""
# Return false if value is not a number
try:
float(value)
except Exception:
return False
# if no range_threshold is provided, assume everything is ok
if not range_threshold:
range_threshold = '~:'
range_threshold = str(range_threshold)
# If range starts with @, then we do the opposite
if range_threshold[0] == '@':
return not check_range(value, range_threshold[1:])
if range_threshold.find(':') > -1:
(start, end) = (range_threshold.split(':', 1))
# we get here if ":" was not provided in range_threshold
else:
start = ''
end = range_threshold
# assume infinity if start is not provided
if start == '~':
start = None
# assume start=0 if start is not provided
if start == '':
start = 0
# assume infinity if end is not provided
if end == '':
end = None
try:
# start is defined and value is lower than start
if start is not None and float(value) < float(start):
return False
if end is not None and float(value) > float(end):
return False
except ValueError:
raise InvalidThreshold("Invalid threshold format: %s" % range_threshold)
return True