#!/usr/bin/python
# -*- encoding: utf-8; py-indent-offset: 4 -*-
# +------------------------------------------------------------------+
# |             ____ _               _        __  __ _  __           |
# |            / ___| |__   ___  ___| | __   |  \/  | |/ /           |
# |           | |   | '_ \ / _ \/ __| |/ /   | |\/| | ' /            |
# |           | |___| | | |  __/ (__|   <    | |  | | . \            |
# |            \____|_| |_|\___|\___|_|\_\___|_|  |_|_|\_\           |
# |                                                                  |
# | Copyright Mathias Kettner 2013             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.

# <<ucs_bladecenter_if:sep(9)>>>
# fcStats Dn sys/switch-A/slot-1/switch-fc/port-37/stats  BytesRx 2411057759048   BytesTx 1350394110752   Suspect no
# fcStats Dn sys/switch-A/slot-1/switch-fc/port-40/stats  BytesRx 0   BytesTx 0   Suspect no
# fcErrStats      Dn sys/switch-B/slot-1/switch-fc/port-47/err-stats      CrcRx 0 DiscardRx 0     DiscardTx 0
# fcErrStats      Dn sys/switch-B/slot-1/switch-fc/port-48/err-stats      CrcRx 0 DiscardRx 0     DiscardTx 0
# fabricFcSanEp   Dn fabric/san/A/phys-slot-1-port-40     EpDn sys/switch-A/slot-1/switch-fc/port-40      AdminState disabled     OperState up
# fabricFcSanEp   Dn fabric/san/A/phys-slot-1-port-41     EpDn sys/switch-A/slot-1/switch-fc/port-41      AdminState disabled     OperState up

# Example interfaces
# fibrechannel:
# 'sys/switch-A/slot-1/switch-fc/port-38':
#           {'AdminState': 'enabled',
#            'BytesRx': '51789849113704',
#            'BytesTx': '15914991789936',
#            'CrcRx': '1',
#            'DiscardRx': '0',
#            'DiscardTx': '0',
#            'EpDn': 'sys/switch-A/slot-1/switch-fc/port-38',
#            'OperState': 'up',
#            'PacketsRx': '26771306796',
#            'PacketsTx': '8735571946',
#            'PortId': '38',
#            'Rx': '1',
#            'SlotId': '1',
#            'Suspect': 'no',
#            'SwitchId': 'A',
#            'Tx': '0'},
# ethernet:
# 'sys/switch-A/slot-1/switch-ether/port-18':
#           {'AdminState': 'enabled',
#            'Dn': 'fabric/lan/A/pc-1/ep-slot-1-port-18',
#            'EpDn': 'sys/switch-A/slot-1/switch-ether/port-18',
#            'OperState': 'up',
#            'PortId': '18',
#            'SlotId': '1',
#            'SwitchId': 'A',
#            'etherRxStats': {'BroadcastPackets': '116544272',
#                             'Dn': 'sys/switch-A/slot-1/switch-ether/port-18/rx-stats',
#                             'MulticastPackets': '560456841',
#                             'TotalBytes': '53066141169147',
#                             'UnicastPackets': '138412352259'},
#            'etherTxStats': {'BroadcastPackets': '4922247',
#                             'Dn': 'sys/switch-A/slot-1/switch-ether/port-18/tx-stats',
#                             'MulticastPackets': '82743790',
#                             'TotalBytes': '79420242621595',
#                             'UnicastPackets': '135007642584'},
# interconnect:
# 'sys/switch-A/slot-1/switch-ether/port-2':
#            {'AdminState': 'enabled',
#             'Dn': 'fabric/server/sw-A/pc-1025/ep-slot-1-port-2',
#             'EpDn': 'sys/switch-A/slot-1/switch-ether/port-2',
#             'OperState': 'up',
#             'PortId': '2',
#             'SlotId': '1',
#             'SwitchId': 'A',
#             'etherErrStats': {'Dn': 'sys/switch-A/slot-1/switch-ether/port-2/err-stats',
#                               'OutDiscard': '0',
#                               'Rcv': '0'},
#             'etherRxStats': {'BroadcastPackets': '50432549',
#                              'Dn': 'sys/switch-A/slot-1/switch-ether/port-2/rx-stats',
#                              'MulticastPackets': '80349542',
#                              'TotalBytes': '50633308808192',
#                              'UnicastPackets': '53535107978'},
#             'etherTxStats': {'BroadcastPackets': '4892153',
#                              'Dn': 'sys/switch-A/slot-1/switch-ether/port-2/tx-stats',
#                              'MulticastPackets': '328878878',
#                              'TotalBytes': '97004901202254',
#                              'UnicastPackets': '79555260499'},
#             'portchannel': {'AdminState': 'enabled',
#                             'Dn': 'fabric/server/sw-A/pc-1025',
#                             'OperSpeed': '10gbps',
#                             'OperState': 'up',
#                             'PortId': '1025',
#                             'members': 4}},

# We need to fill this structure
#    converted = [
#        [], #  0 ifIndex                   0
#        [], #  1 ifDescr                   1
#        [], #  2 ifType                    2
#        [], #  3 ifHighSpeed               .. 1000 means 1Gbit
#        [], #  4 ifOperStatus              4
#        [], #  5 ifHCInOctets              5
#        [], #  6 ifHCInUcastPkts           6
#        [], #  7 ifHCInMulticastPkts       7
#        [], #  8 ifHCInBroadcastPkts       8
#        [], #  9 ifInDiscards              9
#        [], # 10 ifInErrors               10
#        [], # 11 ifHCOutOctets            11
#        [], # 12 ifHCOutUcastPkts         12
#        [], # 13 ifHCOutMulticastPkts     13
#        [], # 14 ifHCOutBroadcastPkts     14
#        [], # 15 ifOutDiscards            15
#        [], # 16 ifOutErrors              16
#        [], # 17 ifOutQLen                17
#        [], # 18 ifAlias                  18
#        [], # 19 ifPhysAddress            19
#    ]

# Specify which values are to put into the resulting interface
tableindex = {
    "fibrechannel": {
        # a list of class  and    field
        2: "6",  # This means Ethernet. We should set the real type here
        # if.include does not support the interface type 56
        5: [("fcStats", "BytesRx")],
        6: [("fcStats", "PacketsRx")],
        9: [("fcErrStats", "DiscardRx")],
        10: [("fcErrStats", "Rx"), ("fcErrStats", "CrcRx")],
        11: [("fcStats", "BytesTx")],
        12: [("fcStats", "PacketsTx")],
        15: [("fcErrStats", "DiscardTx")],
        16: [("fcErrStats", "Tx")]
    },
    "ethernet": {
        2: "6",
        5: [("etherRxStats", "TotalBytes")],
        6: [("etherRxStats", "UnicastPackets")],
        7: [("etherRxStats", "MulticastPackets")],
        8: [("etherRxStats", "BroadcastPackets")],
        10: [("etherErrStats", "Rcv")],
        11: [("etherTxStats", "TotalBytes")],
        12: [("etherTxStats", "UnicastPackets")],
        13: [("etherTxStats", "MulticastPackets")],
        14: [("etherTxStats", "BroadcastPackets")],
        15: [("etherErrStats", "OutDiscard")],
    },
    "interconnect": {
        2: "6",
        5: [("etherRxStats", "TotalBytes")],
        6: [("etherRxStats", "UnicastPackets")],
        7: [("etherRxStats", "MulticastPackets")],
        8: [("etherRxStats", "BroadcastPackets")],
        10: [("etherErrStats", "Rcv")],
        11: [("etherTxStats", "TotalBytes")],
        12: [("etherTxStats", "UnicastPackets")],
        13: [("etherTxStats", "MulticastPackets")],
        14: [("etherTxStats", "BroadcastPackets")],
        15: [("etherErrStats", "OutDiscard")],
    }
}


def parse_ucs_bladecenter_if(info):
    data = ucs_bladecenter_convert_info(info)

    fc_interfaces = _parse_fc_interfaces(data)
    eth_interfaces = _parse_eth_interfaces(data)
    icnt_interfaces = _parse_icnt_interfaces(data)

    converted = []
    last_index = 0
    for what, group_prefix, interfaces, item_template in [
        ("fibrechannel", "Fibrechannel-Group", fc_interfaces, "Slot %s FC-Switch %s Port %s"),
        ("ethernet", "Ethernet-Group", eth_interfaces, "Slot %s Switch %s Port %s"),
        ("interconnect", "Interconnect-Group", icnt_interfaces, "Slot %s IC-Switch %s Port %s"),
    ]:
        index = 0
        for index, (_name, values) in enumerate(interfaces.iteritems()):
            item = item_template % (values.get("SlotId"), values.get("SwitchId"),
                                    values.get("PortId"))
            entry = ['0'] * 20
            converted.append(entry)

            # Interfaces in portchannels are automatically grouped by within if.include
            # Grouped interfaces are identified when the type of the ifIndex field is a tuple
            # Speed and OperState
            if "portchannel" in values:
                port_name = values["portchannel"].get("Name")
                if not port_name:
                    port_name = values["portchannel"].get("PortId", "")

                entry[0] = (group_prefix + " " + port_name, str(last_index + index))
                speed = values["portchannel"].get("AdminSpeed") or values["portchannel"].get(
                    "OperSpeed")
                # It looks like that the AdminSpeed of a portchannel is the speed of one member
                # speed = str(int(float(speed.replace("gbps", "000000000")) / values["portchannel"]["members"]))
                entry[3] = speed.replace("gbps", "000000000")
                operStatus = values["portchannel"].get("AdminState", "disabled") == "enabled" and \
                             values["portchannel"].get("OperState",  "down") == "up"
            else:
                entry[0] = str(last_index + index)
                entry[3] = values.get("AdminSpeed", "").replace("gbps", "000000000")
                operStatus = values.get("AdminState", "disabled") == "enabled" and \
                             values.get("OperState",  "down") == "up"

            entry[1] = item
            entry[4] = "1" if operStatus else "2"
            entry[18] = item  # ifAlias
            entry[19] = ''  # MAC address not known here
            for table_index, ctr_keys in tableindex.get(what).items():
                ctr_value = 0
                # On summing keys there is a possiblility to overlook some counter wraps.
                # Right now, it's only Recv-Errors (therefore unlikely). We can live with that
                if not isinstance(ctr_keys, list):  # fixed value
                    ctr_value = ctr_keys
                else:
                    for ctr_class, ctr_key in ctr_keys:  # compute value from data
                        if ctr_class:
                            ctr_value += int(values[ctr_class].get(ctr_key, "0"))
                        else:
                            ctr_value += int(values.get(ctr_key, "0"))
                entry[table_index] = str(ctr_value)

        last_index += index + 1

    return converted


def _parse_fc_interfaces(data):
    """Fibrechannels"""
    fc_interfaces = {}
    for key, values in data.get("fabricFcSanEp", {}).items():
        fc_interfaces.setdefault(values["EpDn"], {}).update(values)

    # TODO: fabricFcSanPc
    # TODO: fabricFcSanPcEp
    for what, cut in [("fcStats", 6), ("fcErrStats", 10)]:
        if what in data:
            for key, values in data[what].items():
                fc_name = key[:-cut]
                if fc_name in fc_interfaces:
                    fc_interfaces[fc_name].setdefault(what, {})
                    fc_interfaces[key[:-cut]][what].update(values)

    return fc_interfaces


def _parse_eth_interfaces(data):
    eth_interfaces = {}
    for key, values in data.get("fabricEthLanEp", {}).items():
        eth_interfaces.setdefault(values["EpDn"], {}).update(values)

    # Get info for each portchannel
    eth_pc_info = {}
    for key, values in data.get("fabricEthLanPc", {}).items():
        eth_pc_info[key] = values

    # Ethernet-Portchannel Members
    for key, values in data.get("fabricEthLanPcEp", {}).items():
        pc_name = "/".join(values.get("Dn").split("/")[:-1])
        values["portchannel"] = eth_pc_info[pc_name]
        eth_pc_info[pc_name].setdefault("members", 0)
        eth_pc_info[pc_name]["members"] += 1
        eth_interfaces.setdefault(values["EpDn"], {}).update(values)

    for what, cut in [("etherRxStats", 9), ("etherTxStats", 9), ("etherErrStats", 10)]:
        if what in data:
            for key, values in data[what].items():
                eth_name = key[:-cut]
                if eth_name in eth_interfaces:
                    eth_interfaces[eth_name].setdefault(what, {})
                    eth_interfaces[key[:-cut]][what].update(values)

    return eth_interfaces


def _parse_icnt_interfaces(data):
    icnt_interfaces = {}
    for key, values in data.get("fabricDceSwEp", {}).items():
        icnt_interfaces.setdefault(values["EpDn"], {}).update(values)

    for key, values in data.get("fabricDceSwSrvEp", {}).items():
        icnt_interfaces.setdefault(values["EpDn"], {}).update(values)

    # Get info for each portchannel
    icnt_pc_info = {}
    for key, values in data.get("fabricDceSwSrvPc", {}).items():
        icnt_pc_info[key] = values

    # Interconnect-Portchannel Members
    for key, values in data.get("fabricDceSwSrvPcEp", {}).items():
        if len(values.get("Dn").split("/")[:-1]) == 4:
            pc_name = "/".join(values.get("Dn").split("/")[:-1])
        else:
            pc_name = "/".join(values.get("Dn").split("/")[:-2])
        values["portchannel"] = icnt_pc_info[pc_name]
        icnt_pc_info[pc_name].setdefault("members", 0)
        icnt_pc_info[pc_name]["members"] += 1
        icnt_interfaces.setdefault(values["EpDn"], {}).update(values)

    for what, cut in [("etherRxStats", 9), ("etherTxStats", 9), ("etherErrStats", 10)]:
        if what in data:
            for key, values in data[what].items():
                eth_name = key[:-cut]
                if eth_name in icnt_interfaces:
                    icnt_interfaces[eth_name].setdefault(what, {})
                    icnt_interfaces[key[:-cut]][what].update(values)

    return icnt_interfaces


def inventory_ucs_bladecenter_if(info):
    return inventory_if_common(info)


check_info["ucs_bladecenter_if"] = {
    'parse_function': parse_ucs_bladecenter_if,
    'inventory_function': inventory_ucs_bladecenter_if,
    'check_function': check_if_common,
    'service_description': 'Interface %s',
    'has_perfdata': True,
    'group': 'if',
    'includes': ['ucs_bladecenter.include', 'if.include'],
    'default_levels_variable': 'if_default_levels',
}
