#!/usr/lib64/linuxfabrik-monitoring-plugins/venv/bin/python
# -*- coding: utf-8; py-indent-offset: 4 -*-
#
# Author:  Linuxfabrik GmbH, Zurich, Switzerland
# Contact: info (at) linuxfabrik (dot) ch
#          https://www.linuxfabrik.ch/
# License: The Unlicense, see LICENSE file.

# https://github.com/Linuxfabrik/monitoring-plugins/blob/main/CONTRIBUTING.md

"""See the check's README for more details."""

import argparse
import os
import sys

import lib.args
import lib.base
import lib.lftest
import lib.txt
from lib.globals import STATE_OK, STATE_UNKNOWN, STATE_WARN

__author__ = 'Linuxfabrik GmbH, Zurich/Switzerland'
__version__ = '2026071201'

DESCRIPTION = """Reports the state of network bonding (channel bonding) interfaces. Checks that all
slave interfaces are active and that the bonding mode and link status are healthy.
Channel bonding allows two or more network interfaces to act as one, increasing
bandwidth and providing redundancy.
Alerts when any slave interface is down or the bond is degraded.
Requires root or sudo."""


def parse_args():
    """Parse command line arguments using argparse."""
    parser = argparse.ArgumentParser(
        description=DESCRIPTION,
        epilog=lib.args.epilog(__file__),
        formatter_class=lib.args.HelpFormatter,
    )

    parser.add_argument(
        '-V',
        '--version',
        action='version',
        version=f'%(prog)s: v{__version__} by {__author__}',
    )

    parser.add_argument(
        '--always-ok',
        help=lib.args.help('--always-ok'),
        dest='ALWAYS_OK',
        action='store_true',
        default=False,
    )

    parser.add_argument(
        '--no-perfdata',
        help=lib.args.help('--no-perfdata'),
        dest='NO_PERFDATA',
        action='store_true',
        default=False,
    )

    parser.add_argument(
        '--test',
        help=lib.args.help('--test'),
        dest='TEST',
        type=lib.args.csv,
    )

    args, _ = parser.parse_known_args()
    return args


def main():
    """The main function. This is where the magic happens."""

    # parse the command line
    try:
        args = parse_args()
    except SystemExit:
        sys.exit(STATE_UNKNOWN)

    # fetch data
    if args.TEST is None:
        try:
            bonding_interfaces = os.listdir('/proc/net/bonding/')
        except OSError:
            bonding_interfaces = []
    else:
        bonding_interfaces = ['bond0']

    if len(bonding_interfaces) == 0:
        lib.base.cu('No bonding interfaces found.')

    # init some vars
    msg = ''
    perfdata = ''
    state = STATE_OK

    for bonding_interface in bonding_interfaces:
        if args.TEST is None:
            rows = lib.base.coe(
                lib.disk.read_file(f'/proc/net/bonding/{bonding_interface}')
            ).splitlines()
        else:
            # do not read a real interface, put in test data
            stdout, _, _ = lib.lftest.test(args.TEST)
            rows = stdout.splitlines()

        submsg = ''
        substate = STATE_OK
        # now, check the media-independent interface status (MII)
        bonding_mode = ''
        in_interface_section = False
        link_failure_count = '0'
        mii_status = 'up'
        slave_interface = ''
        for row in rows:
            row = row.strip()
            if not row:
                # empty line after the end of an interface section
                if in_interface_section:
                    perfdata += lib.base.get_perfdata(
                        f'{bonding_interface}_{slave_interface}_link_failure_count',
                        link_failure_count,
                        warn=1,
                        _min=0,
                    )
                    if mii_status != 'up':
                        submsg += (
                            f'    * {slave_interface}'
                            f' is {mii_status}'
                            f' with {link_failure_count}'
                            f' link'
                            f' {lib.txt.pluralize("failure", link_failure_count)}'
                            f'.\n'
                        )
                        substate = state = STATE_WARN
                    in_interface_section = False
                continue
            if ': ' not in row:
                continue
            key, value = row.split(': ')
            if key == 'Bonding Mode':
                bonding_mode = value
            if key == 'Link Failure Count':
                link_failure_count = value
            if key == 'MII Status':
                mii_status = value
            if key == 'Slave Interface':
                slave_interface = value
                in_interface_section = True
            if (
                key == 'Partner Mac Address'
                and value == '00:00:00:00:00:00'
                and bonding_mode == 'IEEE 802.3ad Dynamic link aggregation'
            ):
                submsg += (
                    '    * Could not detect the MAC Address of the switch.'
                    ' This could indicate that LACP is not configured'
                    ' properly.\n'
                )
                substate = state = STATE_WARN

        if in_interface_section:
            perfdata += lib.base.get_perfdata(
                f'{bonding_interface}_{slave_interface}_link_failure_count',
                link_failure_count,
                warn=1,
                _min=0,
            )
            if mii_status != 'up':
                submsg += (
                    f'    * {slave_interface}'
                    f' is {mii_status}'
                    f' with {link_failure_count}'
                    f' link'
                    f' {lib.txt.pluralize("failure", link_failure_count)}'
                    f'.\n'
                )
                substate = state = STATE_WARN

        # build the message
        msg += (
            f'* {lib.base.state2str(substate, suffix=" ")}'
            f'{bonding_interface} ({bonding_mode})\n' + submsg
        )

    if state == STATE_OK:
        msg = 'Everything is ok.\n\n' + msg
    else:
        msg = 'One or more errors.\n\n' + msg

    # over and out
    lib.base.oao(
        msg, state, perfdata, always_ok=args.ALWAYS_OK, no_perfdata=args.NO_PERFDATA
    )


if __name__ == '__main__':
    try:
        main()
    except Exception:
        lib.base.cu()
