File: classic_threshold_syntax.py

package info (click to toggle)
pynag 1.1.2+dfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: bullseye, sid
  • size: 1,324 kB
  • sloc: python: 9,792; makefile: 200
file content (157 lines) | stat: -rw-r--r-- 5,327 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# -*- 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