#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (C) 2019 tribe29 GmbH - License: GNU General Public License v2
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
# conditions defined in the file COPYING, which is part of this source code package.

# settings for inventory: which ports should be inventorized
qlogic_fcport_inventory_opstates = ["1", "3"]
qlogic_fcport_inventory_admstates = ["1", "3"]


# this function is needed to have the same port IDs in Checkmk
# as within the Management interface of the device
def qlogic_fcport_generate_port_id(port_id):
    major, minor = port_id.split(".", 1)
    minor = int(minor) - 1
    port_id = "%s.%s" % (major, minor)
    return port_id


def inventory_qlogic_fcport(info):
    inventory = []

    for (
        port_id,
        _oper_mode,
        admin_status,
        oper_status,
        _link_failures,
        _sync_losses,
        _prim_seq_proto_errors,
        _invalid_tx_words,
        _invalid_crcs,
        _address_id_errors,
        _link_reset_ins,
        _link_reset_outs,
        _ols_ins,
        _ols_outs,
        _c2_in_frames,
        _c2_out_frames,
        _c2_in_octets,
        _c2_out_octets,
        _c2_discards,
        _c2_fbsy_frames,
        _c2_frjt_frames,
        _c3_in_frames,
        _c3_out_frames,
        _c3_in_octets,
        _c3_out_octets,
        _c3_discards,
    ) in info:

        # There are devices out there which are totally missing the status related
        # SNMP tables. In this case we add all interfaces.
        if (admin_status == "" and oper_status == "") or (
            admin_status in qlogic_fcport_inventory_admstates
            and oper_status in qlogic_fcport_inventory_opstates
        ):
            inventory.append((qlogic_fcport_generate_port_id(port_id), None))

    return inventory


def check_qlogic_fcport(item, _no_params, info):
    for (
        port_id,
        oper_mode,
        admin_status,
        oper_status,
        link_failures,
        sync_losses,
        prim_seq_proto_errors,
        invalid_tx_words,
        invalid_crcs,
        address_id_errors,
        link_reset_ins,
        link_reset_outs,
        ols_ins,
        ols_outs,
        c2_in_frames,
        c2_out_frames,
        c2_in_octets,
        c2_out_octets,
        c2_discards,
        c2_fbsy_frames,
        c2_frjt_frames,
        c3_in_frames,
        c3_out_frames,
        c3_in_octets,
        c3_out_octets,
        c3_discards,
    ) in info:

        port_id = qlogic_fcport_generate_port_id(port_id)
        if port_id == item:
            status = 0
            perfdata = []
            message = "Port %s" % port_id

            # fcFxPortPhysAdminStatus
            if admin_status == "1":
                message += " AdminStatus: online"
                status = 0
            elif admin_status == "2":
                message += " AdminStatus: offline (!!)"
                status = 2
            elif admin_status == "3":
                message += " AdminStatus: testing (!)"
                status = 1
            elif admin_status == "":
                # Is not a possible valid value in the MIB, but some devices don't
                # provide status information at all (SNMP table missing).
                message += " AdminStatus: not reported"
                status = 0
            else:
                message += " unknown AdminStatus %s (!)" % admin_status
                status = 1

            # fcFxPortPhysOperStatus
            if oper_status == "1":
                message += ", OperStatus: online"
                status = max(status, 0)
            elif oper_status == "2":
                message += ", OperStatus: offline (!!)"
                status = max(status, 2)
            elif oper_status == "3":
                message += ", OperStatus: testing (!)"
                status = max(status, 1)
            elif oper_status == "4":
                message += ", OperStatus: linkFailure (!!)"
                status = max(status, 2)
            elif admin_status == "":
                # Is not a possible valid value in the MIB, but some devices don't
                # provide status information at all (SNMP table missing).
                message += ", OperStatus: not reported"
                status = 0
            else:
                message += ", unknown OperStatus %s (!)" % oper_status
                status = max(status, 1)

            # fcFxPortOperMode (for display only)
            if oper_mode == "2":
                message += ", OperMode: fPort"
            elif oper_mode == "3":
                message += ", OperMode: flPort"

            # Counters
            this_time = time.time()

            # Bytes/sec in and out
            in_octets = int(c2_in_octets) + int(c3_in_octets)
            out_octets = int(c2_out_octets) + int(c3_out_octets)

            in_octet_rate = get_rate("qlogic_fcport.in_octets.%s" % port_id, this_time, in_octets)
            out_octet_rate = get_rate(
                "qlogic_fcport.out_octets.%s" % port_id, this_time, out_octets
            )

            message += ", In: %s/s" % get_bytes_human_readable(in_octet_rate)
            message += ", Out: %s/s" % get_bytes_human_readable(out_octet_rate)

            perfdata.append(("in", in_octet_rate))
            perfdata.append(("out", out_octet_rate))

            # Frames in and out
            in_frames = int(c2_in_frames) + int(c3_in_frames)
            out_frames = int(c2_out_frames) + int(c3_out_frames)

            in_frame_rate = get_rate("qlogic_fcport.in_frames.%s" % port_id, this_time, in_frames)
            out_frame_rate = get_rate(
                "qlogic_fcport.out_frames.%s" % port_id, this_time, out_frames
            )

            message += ", in frames: %s/s" % in_frame_rate
            message += ", out frames: %s/s" % out_frame_rate

            perfdata.append(("rxframes", in_frame_rate))
            perfdata.append(("txframes", out_frame_rate))

            # error rates
            discards = int(c2_discards) + int(c3_discards)
            error_sum = 0
            for descr, counter, value in [
                ("Link Failures", "link_failures", link_failures),
                ("Sync Losses", "sync_losses", sync_losses),
                ("PrimitSeqErrors", "prim_seq_proto_errors", prim_seq_proto_errors),
                ("Invalid TX Words", "invalid_tx_words", invalid_tx_words),
                ("Invalid CRCs", "invalid_crcs", invalid_crcs),
                ("Address ID Errors", "address_id_errors", address_id_errors),
                ("Link Resets In", "link_reset_ins", link_reset_ins),
                ("Link Resets Out", "link_reset_outs", link_reset_outs),
                ("Offline Sequences In", "ols_ins", ols_ins),
                ("Offline Sequences Out", "ols_outs", ols_outs),
                ("Discards", "discards", discards),
                ("F_BSY frames", "c2_fbsy_frames", c2_fbsy_frames),
                ("F_RJT frames", "c2_frjt_frames", c2_frjt_frames),
            ]:
                value = int(value)
                per_sec = get_rate("qlogic_fcport.%s.%s" % (counter, port_id), this_time, value)
                perfdata.append((counter, per_sec))
                error_sum += per_sec

                if per_sec > 0:
                    message += ", %s: %s/s" % (descr, per_sec)
            if error_sum == 0:
                message += ", no protocol errors"

            return status, message, perfdata

    return 3, "Port %s not found" % item


check_info["qlogic_fcport"] = {
    "check_function": check_qlogic_fcport,
    "inventory_function": inventory_qlogic_fcport,
    "service_description": "FC Port %s",
    "has_perfdata": True,
    "snmp_info": (
        ".1.3.6.1.2.1.75.1",
        [
            OID_END,  # Index of the Port
            "2.1.1.3",  # fcFxPortOperMode
            "2.2.1.1",  # fcFxPortPhysAdminStatus
            "2.2.1.2",  # fcFxPortPhysOperStatus
            "3.1.1.1",  # fcFxPortLinkFailures
            "3.1.1.2",  # fcFxPortSyncLosses
            "3.1.1.4",  # fcFxPortPrimSeqProtoErrors
            "3.1.1.5",  # fcFxPortInvalidTxWords
            "3.1.1.6",  # fcFxPortInvalidCrcs
            "3.1.1.8",  # fcFxPortAddressIdErrors
            "3.1.1.9",  # fcFxPortLinkResetIns
            "3.1.1.10",  # fcFxPortLinkResetOuts
            "3.1.1.11",  # fcFxPortOlsIns
            "3.1.1.12",  # fcFxPortOlsOuts
            "4.2.1.1",  # fcFxPortC2InFrames
            "4.2.1.2",  # fcFxPortC2OutFrames
            "4.2.1.3",  # fcFxPortC2InOctets
            "4.2.1.4",  # fcFxPortC2OutOctets
            "4.2.1.5",  # fcFxPortC2Discards
            "4.2.1.6",  # fcFxPortC2FbsyFrames
            "4.2.1.7",  # fcFxPortC2FrjtFrames
            "4.3.1.1",  # fcFxPortC3InFrames
            "4.3.1.2",  # fcFxPortC3OutFrames
            "4.3.1.3",  # fcFxPortC3InOctets
            "4.3.1.4",  # fcFxPortC3OutOctets
            "4.3.1.5",  # fcFxPortC3Discards
        ],
    ),
    # .1.3.6.1.4.1.3873.1.14 Qlogic-Switch
    # .1.3.6.1.4.1.3873.1.8  Qlogic-4Gb SAN Switch Module for IBM BladeCenter
    # .1.3.6.1.4.1.3873.1.12 QLogic 8 Gb and 4/8 Gb Intelligent Pass-thru Module
    # .1.3.6.1.4.1.3873.1.9  QLogic SANBox 5802 FC Switch
    # .1.3.6.1.4.1.3873.1.11 HP StorageWorks 8/20q Fibre Channel Switch
    "snmp_scan_function": lambda oid: oid(".1.3.6.1.2.1.1.2.0").startswith(".1.3.6.1.4.1.3873.1.14")
    or oid(".1.3.6.1.2.1.1.2.0").startswith(".1.3.6.1.4.1.3873.1.8")
    or oid(".1.3.6.1.2.1.1.2.0").startswith(".1.3.6.1.4.1.3873.1.11")
    or oid(".1.3.6.1.2.1.1.2.0").startswith(".1.3.6.1.4.1.3873.1.12")
    or oid(".1.3.6.1.2.1.1.2.0").startswith(".1.3.6.1.4.1.3873.1.9"),
    "group": "qlogic_fcport",
}
