checks.py 8.47 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 33 34
        self._options    = options
        self.retry       = options.get('retry', 0)
        self.retry_count = 0
35 36 37
        self.every       = options.get('every', config.default_every)
        self.error_every = options.get('error_every', config.default_error_every)
        self.run_count   = 0
38 39
        self.errmsg      = ''
        self.ok          = True
40
        self.target_name = options.get('target_name', 'Unknown')
41
        self.timeout     = options.get('timeout', 2)
Jonathan Michalon's avatar
Jonathan Michalon committed
42 43

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

    def setup(self):
52
        pass
Jonathan Michalon's avatar
Jonathan Michalon committed
53 54 55 56 57 58 59

    def teardown(self):
        pass

    def check(self, host, addr):
        pass

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

83 84
    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
85
        self.errmsg = ''
86 87 88 89 90
        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
91 92 93 94 95 96
        try:
            out, err = p.communicate(timeout=timeout)
        except TimeoutExpired:
            p.kill()
            out, err = p.communicate()
            self.errmsg += "Operation timed out\n"
97
            return False
Jonathan Michalon's avatar
Jonathan Michalon committed
98
        if p.returncode != 0:
99
            if len(out) > 0:
100 101
                self.errmsg += "stdout:\n" + \
                               out.decode(errors='replace') + '\n'
102
            if len(err) > 0:
103 104
                self.errmsg += "stderr:\n" + \
                               err.decode(errors='replace') + '\n'
105
        if re.search(pattern, str(out), flags=re.M) is None:
106 107
            self.errmsg += ("Pattern '%s' not found in reply.\nstdout: %s"
                            % (pattern, out.decode(errors='replace')))
108
            return False
Jonathan Michalon's avatar
Jonathan Michalon committed
109 110 111 112
        return p.returncode == 0


class CheckIP(Check):
113 114 115 116
    def __init__(self, host, **options):
        super().__init__(**options)
        self.target_name = host.name

Jonathan Michalon's avatar
Jonathan Michalon committed
117
    def __repr__(self):
118
        return '<%s on %s>' % (super().__repr__(), self.addr)
Jonathan Michalon's avatar
Jonathan Michalon committed
119 120 121 122


class Check4(CheckIP):
    def __init__(self, host, **options):
123
        super().__init__(host, **options)
Jonathan Michalon's avatar
Jonathan Michalon committed
124 125 126 127 128
        self.addr = host.ipv4


class Check6(CheckIP):
    def __init__(self, host, **options):
129
        super().__init__(host, **options)
Jonathan Michalon's avatar
Jonathan Michalon committed
130 131 132 133 134
        self.addr = host.ipv6


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


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


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

    def __repr__(self):
152
        return '<%s for %s>' % (super().__repr__(), self.zone)
Jonathan Michalon's avatar
Jonathan Michalon committed
153 154

    def check(self):
155 156 157
        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
158 159 160 161
        return self.exec_with_timeout(command)


class CheckDNSRec(Check):
162 163 164 165 166 167 168 169 170 171 172
    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
173 174 175 176 177 178


class CheckDNSAut(Check):
    def check(self, host, addr):
        self.errmsg = "Unimplemented"
        return False
179 180 181 182 183


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

    def check(self):
        command = self.build_command()
Colomban Wendling's avatar
Colomban Wendling committed
197
        return self.exec_with_timeout(command, timeout=self.timeout + 1)
198 199 200 201 202


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


class CheckHTTP4(CheckHTTP, Check4):
    pass


class CheckHTTP6(CheckHTTP, Check6):
    pass


class CheckHTTPS4(CheckHTTPS, Check4):
    pass


class CheckHTTPS6(CheckHTTPS, Check6):
    pass
220 221 222 223 224 225


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


class CheckSMTP4(CheckSMTP, Check4):
    pass


class CheckSMTP6(CheckSMTP, Check6):
    pass
246 247 248 249 250 251 252 253 254 255 256 257


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
258
        return self.exec_with_timeout(command, timeout=self.timeout + 1)
259 260 261 262 263 264 265 266


class CheckOpenVPN4(CheckOpenVPN, Check4):
    pass


class CheckOpenVPN6(CheckOpenVPN, Check6):
    pass
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282


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