#!/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-
# ails.  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

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

    # ==== 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)


    # ==== Ethernet ====
    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)

    # ==== Interconnects ====
    icnt_interfaces = {}
    for key, values in data.get("fabricDceSwEp", {}).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():
        pc_name = "/".join(values.get("Dn").split("/")[:-1])
        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)


    # 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") ],
       }
    }


    # Ethernet
    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"),
    ]:
        for index, (name, values) in enumerate(interfaces.items()):
            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
            if "portchannel" in values:
                entry[0] = (group_prefix + " " + values["portchannel"].get("Name", values["portchannel"].get("PortId", "")),\
                           str(last_index + index))
            else:
                entry[0] = str(last_index + index)
            entry[1] = item

            # Speed and OperState
            if values.get("portchannel"):
                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"]))
                speed = speed.replace("gbps", "000000000")
                operStatus = values["portchannel"].get("AdminState", "disabled") == "enabled" and \
                             values["portchannel"].get("OperState",  "down") == "up"
            else:
                speed = values.get("AdminSpeed", "").replace("gbps", "000000000")
                operStatus = values.get("AdminState", "disabled") == "enabled" and \
                             values.get("OperState",  "down") == "up"

            entry[3] = speed
            entry[4] = operStatus and "1" or "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 type(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)
        else:
            index = 0
        last_index += index + 1

    return converted


check_info["ucs_bladecenter_if"] = {
    'parse_function'          : parse_ucs_bladecenter_if,
    'inventory_function'      : lambda info: inventory_if_common(info),
    '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',
}
