#!/usr/bin/env python
#
# Copyright (c) 2017, 2019, SUNET
# All rights reserved.
#
#   Redistribution and use in source and binary forms, with or
#   without modification, are permitted provided that the following
#   conditions are met:
#
#     1. Redistributions of source code must retain the above copyright
#        notice, this list of conditions and the following disclaimer.
#     2. Redistributions in binary form must reproduce the above
#        copyright notice, this list of conditions and the following
#        disclaimer in the documentation and/or other materials provided
#        with the distribution.
#     3. Neither the name of SUNET nor the names of its
#        contributors may be used to endorse or promote products derived
#        from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# Author: Fredrik Thulin <fredrik@thulin.net>
#
"""
Nagios check to verify that BIRD BGP peers are up.
"""

import os
import re
import sys
import json
import yaml
import logging
import argparse
import datetime
import subprocess

default_debug = False
default_socket_fn = '/var/run/bird/bird.ctl'
default_min_peers = 2

# Nagios plugin exit status codes
STATUS = {'OK': 0,
          'WARNING': 1,
          'CRITICAL': 2,
          'UNKNOWN': 3,
          }


def parse_args():
    """
    Parse the command line arguments
    """
    parser = argparse.ArgumentParser(description = 'BIRD BGP peer status Nagios check',
                                     add_help = True,
                                     formatter_class = argparse.ArgumentDefaultsHelpFormatter,
                                     )

    parser.add_argument('--debug',
                        dest='debug',
                        action='store_true', default=default_debug,
                        help='Enable debug operation',
                        )
    parser.add_argument('-s',
                        dest='socket_fn',
                        default=default_socket_fn,
                        help='BIRD control socket filename',
                        )
    parser.add_argument('--min_peers',
                        dest='min_peers',
                        type=int,
                        default=default_min_peers,
                        help='Minimum number of established peers considered acceptable',
    )
    parser.add_argument('--ignore_peers',
                        dest='ignore_peers',
                        type=str,
                        nargs='+',
                        default=[],
                        help='Ignore problems with these peers',
    )

    return parser.parse_args()


def birdc(socket_fn, logger):
    cmd = ['/usr/bin/sudo', '/usr/sbin/birdc', '-r', '-v', '-s', socket_fn, 'show protocols']
    logger.debug('Executing command: {}'.format(cmd))
    proc = subprocess.Popen(cmd,
                            cwd='/',
                            stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT,
                            close_fds=True,
                            )
    (stdout, _stderr) = proc.communicate()
    logger.debug('Command output:\n{!r}'.format(stdout))
    if not stdout.startswith('0001 '):
        logger.error('Failed parsing output of {!s}: {!s}'.format(' '.join(cmd), exc))
        return None
    return stdout


def check_peers(args, logger):
    critical = []
    warning = []
    ok = []
    ignored = []

    peers = parse_peers(birdc(args.socket_fn, logger))
    logger.debug('Peers:\n{}'.format(peers))
    for this in peers:
        peername = this.split(' ')[0]
        if this.endswith('Established'):
            ok.extend([peername])
        elif peername in args.ignore_peers:
            ignored.extend([peername])
        else:
            # add peer name to critical list
            critical.extend([peername])

    crit_str = ', '.join(sorted(critical))
    warn_str = ', '.join(sorted(warning))
    ignored_str = ', '.join(sorted(ignored))

    if not critical:
        if len(ok) < args.min_peers:
            crit_str = 'Only {} (less than {}) peers up'.format(len(ok), args.min_peers)

    if ok:
        ok_str = '{} peers in state \'Established\''.format(len(ok))

    return crit_str, warn_str, ok_str, ignored_str


def parse_peers(data):
    in_peers = False
    res = []
    for this in data.split('\n'):
        logger.debug('Parsing {}'.format(this))
        if this.startswith('1002-'):
            in_peers = True
            continue
        if in_peers and not this.startswith(' '):
            return res
        if in_peers:
            res.extend([this.strip()])
    return res


def main(args, logger):
    critical, warning, ok, ignored = check_peers(args, logger)

    res = []
    if critical:
        res.append('CRITICAL: {!s}'.format(critical))
    if warning:
        res.append('WARNING: {!s}'.format(warning))
    if ok:
        res.append('OK: {!s}'.format(ok))

    res_str = ', '.join(res)
    if ignored:
        res_str += ' (ignored problems with peer(s): {})'.format(ignored)

    print(res_str)

    if critical:
        return STATUS['CRITICAL']
    if warning:
        return STATUS['WARNING']
    if ok:
        return STATUS['OK']

    return STATUS['UNKNOWN']


if __name__ == '__main__':
    try:
        args = parse_args()

        # This is the root log level
        level = logging.INFO
        if args.debug:
            level = logging.DEBUG
        logging.basicConfig(level = level, stream = sys.stderr,
                            format='%(asctime)s: %(threadName)s %(levelname)s %(message)s')
        logger = logging.getLogger('check_bird_peers')

        sys.exit(main(args, logger))
    except KeyboardInterrupt:
        pass
