checks.py 10.4 KB
Newer Older
1
from .subprocess_compat import TimeoutExpired, Popen, PIPE
2
import re
3
import logging
4
from . import mails
5
from collections import Iterable
6
from datetime import datetime
7 8


Jonathan Michalon's avatar
Jonathan Michalon committed
9
class Host(object):
10
    def __init__(self, ipv4='undefined', ipv6='undefined', name=None):
Jonathan Michalon's avatar
Jonathan Michalon committed
11 12
        self.ipv4 = ipv4
        self.ipv6 = ipv6
13
        self.name = name if name is not None else "%s/%s" % (ipv4, ipv6)
Jonathan Michalon's avatar
Jonathan Michalon committed
14 15 16


class Checks(list):
17 18 19 20 21 22 23
    def add(self, checks, dests, **options):
        if not isinstance(checks, Iterable):
            checks = [checks]
        if not isinstance(dests, Iterable):
            dests = [dests]
        for check in checks:
            self += [check(d, **options) for d in dests]
Jonathan Michalon's avatar
Jonathan Michalon committed
24

25 26 27 28 29 30 31 32 33 34 35 36 37
    # this should (assuming "other" is the "older" list):
    # - pickup checks defined in both list (keep old one with its variables)
    # - email for checks in the other list but not in us (it was removed)
    def merge(self, other):
        for oldcheck in other:
            found = False
            for idx, newcheck in enumerate(self):
                if oldcheck == newcheck:
                    self[idx] = oldcheck
                    found = True
            if not found and not oldcheck.ok:
                mails.send_email_for_check(oldcheck, True)

Jonathan Michalon's avatar
Jonathan Michalon committed
38 39

class Check(object):
40
    def __init__(self, dest, **options):
41
        from . import config
42
        self.dest        = dest
43
        self._options    = options
44
        self.retry       = options.get('retry', 1)
45
        self.retry_count = 0
46 47
        self.every       = options.get('every', config.default_every)
        self.error_every = options.get('error_every', config.default_error_every)
48 49
        if self.error_every < 0:
            self.error_every = self.every
50
        self.run_count   = 0
51
        self.errmsg      = ''
52
        self.last_exec   = ''
53
        self.ok          = True
54
        self.target_name = options.pop('target_name', dest)
55
        self.timeout     = options.get('timeout', 2)
Jonathan Michalon's avatar
Jonathan Michalon committed
56 57

    def __repr__(self):
58 59 60 61
        return '<{}, {}({}) with {} (N={}/{}, R={}/{})>'.format(self.target_name,
                                                     self.__class__.__name__,
                                                     self.dest,
                                                     self._options,
62
                                                     self.run_count,
Colomban Wendling's avatar
Colomban Wendling committed
63 64
                                                     self.every,
                                                     self.retry_count,
65
                                                     self.retry)
Jonathan Michalon's avatar
Jonathan Michalon committed
66

67 68 69 70 71
    def __eq__(self, other):
        return (self.__class__.__name__ == other.__class__.__name__ and
                self.dest               == other.dest               and
                self._options           == other._options)

Jonathan Michalon's avatar
Jonathan Michalon committed
72
    def setup(self):
Colomban Wendling's avatar
Colomban Wendling committed
73
        pass
Jonathan Michalon's avatar
Jonathan Michalon committed
74 75 76 77 78 79 80

    def teardown(self):
        pass

    def check(self, host, addr):
        pass

81
    def run(self, immediate=False):
82
        self.run_count = (self.run_count + 1) % (
83
                          self.every if self.ok else self.error_every)
84
        if self.run_count == 0 or immediate:
85
            logging.debug('Running ' + str(self))
Colomban Wendling's avatar
Colomban Wendling committed
86 87
            self.setup()
            if not self.check():
88
                logging.debug('Fail: ' + str(self))
89 90
                self.retry_count += 1
                if self.retry_count >= self.retry or immediate:
91
                    if self.ok:
92
                        logging.debug('Switched to failure: ' + str(self))
93
                        self.failure_date = datetime.now()
94 95
                        self.ok = False
                        mails.send_email_for_check(self)
96
            else:
97
                logging.debug('OK: ' + str(self))
98
                if not self.ok:
99
                    logging.debug('Switched to ok: ' + str(self))
100
                    self.ok = True
101
                    mails.send_email_for_check(self)
102
                self.retry_count = 0
Colomban Wendling's avatar
Colomban Wendling committed
103
            self.teardown()
Jonathan Michalon's avatar
Jonathan Michalon committed
104 105
        return self.ok

106 107
    def exec_with_timeout(self, command, timeout=None, pattern=''):
        timeout = self.timeout if timeout is None else timeout
108 109 110 111
        self.errmsg    = ''
        self.last_exec = ' '.join(map(
                            lambda s: "'" + s.replace("'", "'\"'\"'") + "'",
                            command))
112
        try:
113
            p = Popen(map(str, command), stdout=PIPE, stderr=PIPE)
114 115 116
        except OSError as e:
            self.errmsg = 'Check not available: ' + e.strerror
            return False
Jonathan Michalon's avatar
Jonathan Michalon committed
117 118 119 120 121 122
        try:
            out, err = p.communicate(timeout=timeout)
        except TimeoutExpired:
            p.kill()
            out, err = p.communicate()
            self.errmsg += "Operation timed out\n"
123
            return False
Jonathan Michalon's avatar
Jonathan Michalon committed
124
        if p.returncode != 0:
125
            if len(out) > 0:
126 127
                self.errmsg += "stdout:\n" + \
                               out.decode(errors='replace') + '\n'
128
            if len(err) > 0:
129 130
                self.errmsg += "stderr:\n" + \
                               err.decode(errors='replace') + '\n'
131
        if re.search(pattern, str(out), flags=re.M) is None:
132 133
            self.errmsg += ("Pattern '%s' not found in reply.\nstdout: %s"
                            % (pattern, out.decode(errors='replace')))
134
            return False
Jonathan Michalon's avatar
Jonathan Michalon committed
135 136 137
        return p.returncode == 0


138
class Check4(Check):
139
    def __init__(self, host, **options):
140 141
        options.setdefault('target_name', host.name)
        super().__init__(host.ipv4, **options)
142

Jonathan Michalon's avatar
Jonathan Michalon committed
143

144
class Check6(Check):
Jonathan Michalon's avatar
Jonathan Michalon committed
145
    def __init__(self, host, **options):
146 147
        options.setdefault('target_name', host.name)
        super().__init__(host.ipv6, **options)
Jonathan Michalon's avatar
Jonathan Michalon committed
148 149 150 151


class CheckPing4(Check4):
    def check(self):
152
        command = ['/bin/ping', '-c', '1', '-W', str(self.timeout), self.dest]
Colomban Wendling's avatar
Colomban Wendling committed
153
        return self.exec_with_timeout(command, timeout=self.timeout + 1)
Jonathan Michalon's avatar
Jonathan Michalon committed
154 155 156 157


class CheckPing6(Check6):
    def check(self):
158
        command = ['/bin/ping6', '-c', '1', '-W', str(self.timeout), self.dest]
Colomban Wendling's avatar
Colomban Wendling committed
159
        return self.exec_with_timeout(command, timeout=self.timeout + 1)
Jonathan Michalon's avatar
Jonathan Michalon committed
160 161 162 163


class CheckDNSZone(Check):
    def __init__(self, zone, **options):
164 165
        options.setdefault('target_name', 'zone ' + zone)
        super().__init__(zone, **options)
Jonathan Michalon's avatar
Jonathan Michalon committed
166 167

    def check(self):
168
        command = ['check_dns_soa', '-H', self.dest]
169 170
        if self._options.get('ip_version', 0) in [4, 6]:
            command.append('-' + str(self._options['ip_version']))
Jonathan Michalon's avatar
Jonathan Michalon committed
171 172 173 174
        return self.exec_with_timeout(command)


class CheckDNSRec(Check):
175
    def check(self):
176
        command = ['dig', 'www.google.com', '@' + self.dest]
177 178 179 180 181 182 183 184 185
        return self.exec_with_timeout(command, pattern='status: NOERROR')


class CheckDNSRec4(CheckDNSRec, Check4):
    pass


class CheckDNSRec6(CheckDNSRec, Check6):
    pass
Jonathan Michalon's avatar
Jonathan Michalon committed
186 187 188 189 190 191


class CheckDNSAut(Check):
    def check(self, host, addr):
        self.errmsg = "Unimplemented"
        return False
192 193 194 195 196


class CheckHTTP(Check):
    def build_command(self):
        command = ['/usr/lib/nagios/plugins/check_http',
197
                   '-I', self.dest, '-t', str(self.timeout)]
198 199 200 201 202 203
        if 'status' in self._options:
            command += ['-e', str(self._options['status'])]
        if 'vhost' in self._options:
            command += ['-H', str(self._options['vhost'])]
        if 'string' in self._options:
            command += ['-s', str(self._options['string'])]
204 205
        if 'url' in self._options:
            command += ['-u', str(self._options['url'])]
206 207 208 209
        return command

    def check(self):
        command = self.build_command()
Colomban Wendling's avatar
Colomban Wendling committed
210
        return self.exec_with_timeout(command, timeout=self.timeout + 1)
211 212 213 214


class CheckHTTPS(CheckHTTP):
    def check(self):
215
        command = self.build_command() + ['--ssl', '--sni']
Colomban Wendling's avatar
Colomban Wendling committed
216
        return self.exec_with_timeout(command, timeout=self.timeout + 1)
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232


class CheckHTTP4(CheckHTTP, Check4):
    pass


class CheckHTTP6(CheckHTTP, Check6):
    pass


class CheckHTTPS4(CheckHTTPS, Check4):
    pass


class CheckHTTPS6(CheckHTTPS, Check6):
    pass
233 234 235 236 237


class CheckSMTP(Check):
    def build_command(self):
        command = ['/usr/lib/nagios/plugins/check_smtp',
238
                   '-H', self.dest,
239 240
                   '-f', self._options.get('from_addr',
                                           'picomon@localhost.local'),
241
                   '-t', str(self.timeout)]
242 243 244 245 246 247 248 249
        if 'command' in self._options:
            command += ['-C', str(self._options['command'])]
        if 'response' in self._options:
            command += ['-R', str(self._options['response'])]
        return command

    def check(self):
        command = self.build_command()
Colomban Wendling's avatar
Colomban Wendling committed
250
        return self.exec_with_timeout(command, timeout=self.timeout + 1)
251 252 253 254 255 256 257 258


class CheckSMTP4(CheckSMTP, Check4):
    pass


class CheckSMTP6(CheckSMTP, Check6):
    pass
259 260 261


class CheckOpenVPN(Check):
262 263 264 265 266 267 268
    def build_command(self):
        command = ['/usr/lib/nagios/plugins/check_openvpn',
                   '-p', "443",
                   '--timeout', str(self.timeout),
                   str(self.dest)]
        return command

269
    def check(self):
270
        command = self.build_command()
Colomban Wendling's avatar
Colomban Wendling committed
271
        return self.exec_with_timeout(command, timeout=self.timeout + 1)
272 273 274 275 276 277 278 279


class CheckOpenVPN4(CheckOpenVPN, Check4):
    pass


class CheckOpenVPN6(CheckOpenVPN, Check6):
    pass
280 281


282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
class CheckOpenVPNTCP(CheckOpenVPN):
    def check(self):
        command = self.build_command()
        command.insert(1, '-t')
        return self.exec_with_timeout(command, timeout=self.timeout + 1)


class CheckOpenVPNTCP4(CheckOpenVPNTCP, Check4):
    pass


class CheckOpenVPNTCP6(CheckOpenVPNTCP, Check6):
    pass


297 298 299
class CheckJabber(Check):
    def check(self):
        command = ['/usr/lib/nagios/plugins/check_jabber',
300
                   '-H', self.dest,
301 302 303 304 305 306 307 308 309 310
                   '-t', str(self.timeout)]
        return self.exec_with_timeout(command, timeout=self.timeout + 1)


class CheckJabber4(CheckJabber, Check4):
    pass


class CheckJabber6(CheckJabber, Check6):
    pass
311 312 313 314 315 316


class CheckTLSCert(Check):
    def build_command(self):
        command = ['/usr/lib/nagios/plugins/check_http',
                   '--ssl', '--sni',
317
                   '-C', str(self._options.get('warn', 7)),
318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
                   '-t', str(self.timeout)]
        if 'port' in self._options:
            command += ['-p', str(self._options['port'])]
        if 'vhost' in self._options:
            command += ['-H', str(self._options['vhost'])]
        command += [self.dest]
        return command

    def check(self):
        command = self.build_command()
        return self.exec_with_timeout(command, timeout=self.timeout + 1)


class CheckTLSCert4(CheckTLSCert, Check4):
    pass


class CheckTLSCert6(CheckTLSCert, Check6):
    pass