#!/usr/bin/python
# -*- encoding: utf-8; py-indent-offset: 4 -*-
# +------------------------------------------------------------------+
# |             ____ _               _        __  __ _  __           |
# |            / ___| |__   ___  ___| | __   |  \/  | |/ /           |
# |           | |   | '_ \ / _ \/ __| |/ /   | |\/| | ' /            |
# |           | |___| | | |  __/ (__|   <    | |  | | . \            |
# |            \____|_| |_|\___|\___|_|\_\___|_|  |_|_|\_\           |
# |                                                                  |
# | Copyright Mathias Kettner 2017             mk@mathias-kettner.de |
# +------------------------------------------------------------------+
#
# This file is part of Check_MK.
# The official homepage is at http://mathias-kettner.de/check_mk.
#
# check_mk is free software;  you can redistribute it and/or modify it
# under the  terms of the  GNU General Public License  as published by
# the Free Software Foundation in version 2.  check_mk is  distributed
# in the hope that it will be useful, but WITHOUT ANY WARRANTY;  with-
# out even the implied warranty of  MERCHANTABILITY  or  FITNESS FOR A
# PARTICULAR PURPOSE. See the  GNU General Public License for more de-
# tails. You should have  received  a copy of the  GNU  General Public
# License along with GNU Make; see the file  COPYING.  If  not,  write
# to the Free Software Foundation, Inc., 51 Franklin St,  Fifth Floor,
# Boston, MA 02110-1301 USA.


def parse_brocade_sfp(info):
    parsed = {}

    isl_ports = map(lambda x: int(x[0]), info[1])

    for fcport_info, values in zip(info[0], info[2]):

        # Observed in the wild: Either all of the values are present
        # or none of them.
        if values[0] == "NA":
            continue

        port_index = int(fcport_info[0])

        parsed[port_index] = {
            "port_name"         : fcport_info[4],
            "temp"              : int(values[0]),          # °C
            "phystate"          : int(fcport_info[1]),
            "opstate"           : int(fcport_info[2]),
            "admstate"          : int(fcport_info[3]),
            "voltage"           : float(values[1]) / 1000, # mV -> V
            "current"           : float(values[2]) / 1000, # mA -> A
            "rx_power"          : float(values[3]),        # dBm
            "tx_power"          : float(values[4]),        # dBm
            "is_isl"            : bool(port_index in isl_ports),
        }

    return parsed


def inventory_brocade_sfp(parsed):
    settings = host_extra_conf_merged(host_name(), brocade_fcport_inventory)
    number_of_ports = len(parsed)
    for port_index, port_info in parsed.iteritems():
        if brocade_fcport_inventory_this_port(admstate=port_info["admstate"],
                                              phystate=port_info["phystate"],
                                              opstate=port_info["opstate"],
                                              settings=settings):
            yield brocade_fcport_getitem(
                        number_of_ports=number_of_ports,
                        index=port_index,
                        portname=port_info["port_name"],
                        is_isl=port_info["is_isl"],
                        settings=settings), {}


#   .--Temperature---------------------------------------------------------.
#   |     _____                                   _                        |
#   |    |_   _|__ _ __ ___  _ __   ___ _ __ __ _| |_ _   _ _ __ ___       |
#   |      | |/ _ \ '_ ` _ \| '_ \ / _ \ '__/ _` | __| | | | '__/ _ \      |
#   |      | |  __/ | | | | | |_) |  __/ | | (_| | |_| |_| | | |  __/      |
#   |      |_|\___|_| |_| |_| .__/ \___|_|  \__,_|\__|\__,_|_|  \___|      |
#   |                       |_|                                            |
#   '----------------------------------------------------------------------'

def check_brocade_sfp_temp(item, params, parsed):

    port_index = int(item.split()[0]) + 1 # TODO: Move this magical plucking apart of the
    port_info = parsed[port_index]        #       item to brocade.include and do the same
                                          #       for brocade.fcport.

    return check_temperature(port_info["temp"], params, item)


check_info['brocade_sfp.temp'] = {
    'inventory_function'        : inventory_brocade_sfp,
    'check_function'            : check_brocade_sfp_temp,
    'service_description'       : "SFP Temperature %s",
    'has_perfdata'              : True,
    'group'                     : "temperature",
    'includes'                  : [ "brocade.include", "temperature.include" ],
}

#.
#   .--Power level - Main check--------------------------------------------.
#   |          ____                          _                _            |
#   |         |  _ \ _____      _____ _ __  | | _____   _____| |           |
#   |         | |_) / _ \ \ /\ / / _ \ '__| | |/ _ \ \ / / _ \ |           |
#   |         |  __/ (_) \ V  V /  __/ |    | |  __/\ V /  __/ |           |
#   |         |_|   \___/ \_/\_/ \___|_|    |_|\___| \_/ \___|_|           |
#   |                                                                      |
#   +----------------------------------------------------------------------+
#   | Also includes information about current and voltage                  |
#   '----------------------------------------------------------------------'

def check_brocade_sfp(item, params, parsed):

    # NOTE: We do not use the generic check_levels function from
    #       the API because its behaviour is undesirable, such as
    #       not providing an infotext or perfdata when no levels
    #       are set.
    def check_levels(value, perf_name, params, infotext_prefix, unit):
        infotext = infotext_prefix + ("%.2f %s" % (value, unit))
        if not params:
            return 0, infotext, [ (perf_name, value) ]
        else:
            crit_lower, warn_lower, warn, crit = params
            perfdata = [ (perf_name, value, warn, crit) ]
            if value >= crit or value < crit_lower:
                status = 2
            elif value >= warn or value < warn_lower:
                status = 1
            else:
                status = 0
            return status, infotext, perfdata

    port_index = int(item.split()[0]) + 1 # TODO: Move this magical plucking apart of the
    port_info = parsed[port_index]        #       item to brocade.include and do the same
                                          #       for brocade.fcport.

    yield check_levels(port_info["rx_power"], "input_signal_power_dbm", params.get("rx_power"), "Rx: ", "dBm")
    yield check_levels(port_info["tx_power"], "output_signal_power_dbm", params.get("tx_power"), "Tx: ", "dBm")
    yield check_levels(port_info["current"], "current", params.get("current"), "current: ", "A")
    yield check_levels(port_info["voltage"], "voltage", params.get("voltage"), "voltage: ", "V")


check_info['brocade_sfp'] = {
    'parse_function'        : parse_brocade_sfp,
    'inventory_function'    : inventory_brocade_sfp,
    'check_function'        : check_brocade_sfp,
    'service_description'   : 'SFP %s',
    'snmp_scan_function'    : lambda oid: oid(".1.3.6.1.2.1.1.2.0").startswith(".1.3.6.1.4.1.1588.2.1.1") \
                                        and oid(".1.3.6.1.4.1.1588.2.1.1.1.6.2.1.*") != None,
    'snmp_info'             : [ ( ".1.3.6.1.4.1.1588.2.1.1.1.6.2.1", [
                                    CACHED_OID("1"),  # swFCPortIndex
                                    CACHED_OID("3"),  # swFCPortPhyState
                                    CACHED_OID("4"),  # swFCPortOpStatus
                                    CACHED_OID("5"),  # swFCPortAdmStatus
                                    CACHED_OID("36"), # swFCPortName  (not supported by all devices)
                                ]),

                                # Information about Inter-Switch-Links (contains baud rate of port)
                                ( ".1.3.6.1.4.1.1588.2.1.1.1.2.9.1", [
                                    CACHED_OID("2"), # swNbMyPort
                                ]),

                                # NOTE: It appears that the port name and index in connUnitPortEntry
                                #       are identical to the ones in the table used by
                                #       brocade_fcport. We work on this assumption for the time being,
                                #       meaning we use the same table as in brocade_fcport (see above)
                                #       which we need anyway for PhyState, OpStatus and AdmStatus.
                                #       Please check the connUnitPortEntry table (.1.3.6.1.3.94.1.10.1)
                                #       should you come across a device for which this assumption
                                #       does not hold.
                                ('.1.3.6.1.4.1.1588.2.1.1.1.28.1.1', [ # FA-EXT-MIB::swSfpStatEntry
                                                                       # AUGMENTS {connUnitPortEntry}
                                            "1",   # swSfpTemperature
                                            "2",   # swSfpVoltage
                                            "3",   # swSfpCurrent
                                            "4",   # swSfpRxPower
                                            "5",   # swSfpTxPower
                                        ]),
                              ],
    'has_perfdata'          : True,
    'includes'              : [ "brocade.include" ],
    'group'                 : "brocade_sfp",
}
