checks.py 8.64 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='192.0.2.1', ipv6='2001:db8::1', 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 17 18 19

    def __repr__(self):
        return '<Host ipv4="%s" ipv6="%s">' % (self.ipv4, self.ipv6)


class Checks(list):
20 21 22 23 24 25 26
    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
27 28 29 30


class Check(object):
    def __init__(self, **options):
31
        from . import config
32
        self._options    = options
33
        self.retry       = options.get('retry', 1)
34
        self.retry_count = 0
35 36
        self.every       = options.get('every', config.default_every)
        self.error_every = options.get('error_every', config.default_error_every)
37 38
        if self.error_every < 0:
            self.error_every = self.every
39
        self.run_count   = 0
40 41
        self.errmsg      = ''
        self.ok          = True
42
        self.target_name = options.get('target_name', 'Unknown')
43
        self.timeout     = options.get('timeout', 2)
Jonathan Michalon's avatar
Jonathan Michalon committed
44 45

    def __repr__(self):
46
        return '{:<15s} N={}/{}, R={}/{}, {}'.format(self.__class__.__name__,
47
                                                     self.run_count,
Colomban Wendling's avatar
Colomban Wendling committed
48 49 50 51
                                                     self.every,
                                                     self.retry_count,
                                                     self.retry,
                                                     self._options)
Jonathan Michalon's avatar
Jonathan Michalon committed
52 53

    def setup(self):
Colomban Wendling's avatar
Colomban Wendling committed
54
        pass
Jonathan Michalon's avatar
Jonathan Michalon committed
55 56 57 58 59 60 61

    def teardown(self):
        pass

    def check(self, host, addr):
        pass

62
    def run(self, immediate=False):
63
        self.run_count = (self.run_count + 1) % (
64
                          self.every if self.ok else self.error_every)
65
        if self.run_count == 0 or immediate:
66
            logging.debug('Running ' + str(self))
Colomban Wendling's avatar
Colomban Wendling committed
67 68
            self.setup()
            if not self.check():
69
                logging.debug('Fail: ' + str(self))
70 71
                self.retry_count += 1
                if self.retry_count >= self.retry or immediate:
72
                    if self.ok:
73
                        logging.debug('Switched to failure: ' + str(self))
74
                        self.failure_date = datetime.now()
75 76
                        self.ok = False
                        mails.send_email_for_check(self)
77
            else:
78
                logging.debug('OK: ' + str(self))
79
                if not self.ok:
80
                    logging.debug('Switched to ok: ' + str(self))
81
                    self.ok = True
82
                    mails.send_email_for_check(self)
83
                self.retry_count = 0
Colomban Wendling's avatar
Colomban Wendling committed
84
            self.teardown()
Jonathan Michalon's avatar
Jonathan Michalon committed
85 86
        return self.ok

87 88
    def exec_with_timeout(self, command, timeout=None, pattern=''):
        timeout = self.timeout if timeout is None else timeout
Jonathan Michalon's avatar
Jonathan Michalon committed
89
        self.errmsg = ''
90 91 92 93 94
        try:
            p = Popen(command, stdout=PIPE, stderr=PIPE)
        except OSError as e:
            self.errmsg = 'Check not available: ' + e.strerror
            return False
Jonathan Michalon's avatar
Jonathan Michalon committed
95 96 97 98 99 100
        try:
            out, err = p.communicate(timeout=timeout)
        except TimeoutExpired:
            p.kill()
            out, err = p.communicate()
            self.errmsg += "Operation timed out\n"
101
            return False
Jonathan Michalon's avatar
Jonathan Michalon committed
102
        if p.returncode != 0:
103
            if len(out) > 0:
104 105
                self.errmsg += "stdout:\n" + \
                               out.decode(errors='replace') + '\n'
106
            if len(err) > 0:
107 108
                self.errmsg += "stderr:\n" + \
                               err.decode(errors='replace') + '\n'
109
        if re.search(pattern, str(out), flags=re.M) is None:
110 111
            self.errmsg += ("Pattern '%s' not found in reply.\nstdout: %s"
                            % (pattern, out.decode(errors='replace')))
112
            return False
Jonathan Michalon's avatar
Jonathan Michalon committed
113 114 115 116
        return p.returncode == 0


class CheckIP(Check):
117 118 119 120
    def __init__(self, host, **options):
        super().__init__(**options)
        self.target_name = host.name

Jonathan Michalon's avatar
Jonathan Michalon committed
121
    def __repr__(self):
122
        return '<%s on %s>' % (super().__repr__(), self.addr)
Jonathan Michalon's avatar
Jonathan Michalon committed
123 124 125 126


class Check4(CheckIP):
    def __init__(self, host, **options):
127
        super().__init__(host, **options)
Jonathan Michalon's avatar
Jonathan Michalon committed
128 129 130 131 132
        self.addr = host.ipv4


class Check6(CheckIP):
    def __init__(self, host, **options):
133
        super().__init__(host, **options)
Jonathan Michalon's avatar
Jonathan Michalon committed
134 135 136 137 138
        self.addr = host.ipv6


class CheckPing4(Check4):
    def check(self):
139
        command = ['/bin/ping', '-c', '1', '-W', str(self.timeout), self.addr]
Colomban Wendling's avatar
Colomban Wendling committed
140
        return self.exec_with_timeout(command, timeout=self.timeout + 1)
Jonathan Michalon's avatar
Jonathan Michalon committed
141 142 143 144


class CheckPing6(Check6):
    def check(self):
145
        command = ['/bin/ping6', '-c', '1', '-W', str(self.timeout), self.addr]
Colomban Wendling's avatar
Colomban Wendling committed
146
        return self.exec_with_timeout(command, timeout=self.timeout + 1)
Jonathan Michalon's avatar
Jonathan Michalon committed
147 148 149 150 151 152


class CheckDNSZone(Check):
    def __init__(self, zone, **options):
        super().__init__(**options)
        self.zone = zone
153
        self.target_name = "zone '%s'" % zone
Jonathan Michalon's avatar
Jonathan Michalon committed
154 155

    def __repr__(self):
156
        return '<%s for %s>' % (super().__repr__(), self.zone)
Jonathan Michalon's avatar
Jonathan Michalon committed
157 158

    def check(self):
159 160 161
        command = ['check_dns_soa', '-H', self.zone]
        if self._options.get('ip_version', 0) in [4, 6]:
            command.append('-' + str(self._options['ip_version']))
Jonathan Michalon's avatar
Jonathan Michalon committed
162 163 164 165
        return self.exec_with_timeout(command)


class CheckDNSRec(Check):
166 167 168 169 170 171 172 173 174 175 176
    def check(self):
        command = ['dig', 'www.google.com', '@' + self.addr]
        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
177 178 179 180 181 182


class CheckDNSAut(Check):
    def check(self, host, addr):
        self.errmsg = "Unimplemented"
        return False
183 184 185 186 187


class CheckHTTP(Check):
    def build_command(self):
        command = ['/usr/lib/nagios/plugins/check_http',
188
                   '-I', self.addr, '-t', str(self.timeout)]
189 190 191 192 193 194
        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'])]
195 196
        if 'url' in self._options:
            command += ['-u', str(self._options['url'])]
197 198 199 200
        return command

    def check(self):
        command = self.build_command()
Colomban Wendling's avatar
Colomban Wendling committed
201
        return self.exec_with_timeout(command, timeout=self.timeout + 1)
202 203 204 205 206


class CheckHTTPS(CheckHTTP):
    def check(self):
        command = self.build_command() + ['--ssl']
Colomban Wendling's avatar
Colomban Wendling committed
207
        return self.exec_with_timeout(command, timeout=self.timeout + 1)
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223


class CheckHTTP4(CheckHTTP, Check4):
    pass


class CheckHTTP6(CheckHTTP, Check6):
    pass


class CheckHTTPS4(CheckHTTPS, Check4):
    pass


class CheckHTTPS6(CheckHTTPS, Check6):
    pass
224 225 226 227 228 229


class CheckSMTP(Check):
    def build_command(self):
        command = ['/usr/lib/nagios/plugins/check_smtp',
                   '-H', self.addr,
230 231
                   '-f', self._options.get('from_addr',
                                           'picomon@localhost.local'),
232
                   '-t', str(self.timeout)]
233 234 235 236 237 238 239 240
        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
241
        return self.exec_with_timeout(command, timeout=self.timeout + 1)
242 243 244 245 246 247 248 249


class CheckSMTP4(CheckSMTP, Check4):
    pass


class CheckSMTP6(CheckSMTP, Check6):
    pass
250 251 252 253 254 255 256 257 258 259 260 261


class CheckOpenVPN(Check):
    def check(self):
        command = ['/usr/lib/nagios/plugins/check_udp',
                   '-H', self.addr,
                   '-p', "1194",
                   '-m', "1",
                   '-M', "ok",  # actualy just having a reply is enough
                   '-s', "\x38\x01\x01\x01\x01\x01\x01\x01\x42",
                   '-e', "@",
                   '-t', str(self.timeout)]
Colomban Wendling's avatar
Colomban Wendling committed
262
        return self.exec_with_timeout(command, timeout=self.timeout + 1)
263 264 265 266 267 268 269 270


class CheckOpenVPN4(CheckOpenVPN, Check4):
    pass


class CheckOpenVPN6(CheckOpenVPN, Check6):
    pass
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286


class CheckJabber(Check):
    def check(self):
        command = ['/usr/lib/nagios/plugins/check_jabber',
                   '-H', self.addr,
                   '-t', str(self.timeout)]
        return self.exec_with_timeout(command, timeout=self.timeout + 1)


class CheckJabber4(CheckJabber, Check4):
    pass


class CheckJabber6(CheckJabber, Check6):
    pass