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

# info = [
# [['12.109.103.48.50.49.52.49.56.49.54.52.57', '10', 'AP-RTSG01-R29'],
#  ['12.109.103.48.50.49.52.49.56.48.55.57.50', '7',  'AP-D16-Schwille'],
#  ...
# ],
# [['12.109.103.48.50.49.52.49.56.49.54.52.57.1',
#   '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0'],
#  ['12.109.103.48.50.49.52.49.56.48.55.57.50.2',
#   '878622', '722521169', '259010759', '80213977575', '53258427', '3911398497', '84355', '78', '160792', '0', '-116'],
#  ...
# ]]

# parsed = {
# 'Y21': {
#   'end_oid': '12.109.103.48.50.49.51.52.51.50.48.55.50',
#   'radios': {
#       '1': {'counters': [1443734995, 1796570108261, 3424375497, 558931548065, 7034540503, 3954342568339, 88605696, 21530, 548422790],
#             'noise_floor': -104,
#             'sessions': 1,
#       },
#       '2': {'counters': [51392622, 58779322688, 1354946125, 359126216495, 453487399, 211359829109, 1937805, 454, 14888291],
#             'noise_floor': -115,
#             'sessions': 0,
#       },
#   },
#   'status': '7',
# },
# ...
# }

def parse_juniper_trpz_aps_sessions(info):
    parsed = {}
    for access_points, radios in zip(info[::2], info[1::2]):
        for node_info, end_oid, status, name in access_points:
            name = name.replace('AP-','')
            parsed.setdefault(name, {
                'end_oid' : end_oid,
                'radios'  : {},
            })

            if status != "10":
                # 10 means that that accesspoint is located on the
                # other cluster part. Use custered_services to see all APs
                parsed[name].update({
                    'status'      : status,
                    'active_node' : node_info, })
            else:
                parsed[name].setdefault("passive_node", node_info)

        for data in radios:
            radio_node = data[0]
            radio_oid, radio_number = data[1].rsplit('.', 1)
            for ap_name, ap_data in parsed.items():
                if radio_oid == ap_data.get('end_oid') and \
                   radio_node == ap_data.get('active_node'):
                    ap_data['radios'][radio_number] = {
                        'counters'    : data[1:-2],
                        'sessions'    : data[-2],
                        'noise_floor' : data[-1],
                    }

    return parsed


def inventory_juniper_trpz_aps_sessions(parsed):
    # A failover condition occurs when a primary controller goes down or fails for any reason.
    # Then, a second controller takes over the operation.
    # Failover is also called controller redundancy.
    return [ (key, None) for key, values in parsed.items() if values.get('status') ]


def check_juniper_trpz_aps_sessions(item, _no_params, parsed):
    if item not in parsed:
        yield 1, "Access point not reachable"
        return

    data = parsed[item]
    state, ap_status = {
        "1"  : (2, "cleared"),
        "2"  : (1, "init"),
        "3"  : (2, "boot started"),
        "4"  : (2, "image downloaded"),
        "5"  : (2, "connect failed"),
        "6"  : (1, "configuring"),
        "7"  : (0, "operational"),
        "10" : (0, "redundant"),
        "20" : (2, "conn outage"),
    }.get(data.get('status'), (3, 'unknown'))

    nodes_info = ""
    if data.get('active_node') is not None:
        nodes_info += data['active_node']
    if data.get('passive_node') is not None:
        nodes_info += "/%s" % data['passive_node']
    if nodes_info:
        nodes_info = "[%s] " % nodes_info
    yield state, "%sStatus: %s" % (nodes_info, ap_status)

    now = time.time()
    infotexts   = []
    ap_rates    = [0, 0, 0, 0, 0, 0, 0, 0, 0]
    ap_sessions = 0
    noise_floor_radios = []

    for radio_number, radio_data in data.get('radios', {}).items():
        try:
            radio_data_noise_floor = int(radio_data['noise_floor'])
            noise_floor_radios.append(radio_data_noise_floor)
        except:
            radio_data_noise_floor = "None"

        try:
            radio_data_sessions = int(radio_data['sessions'])
            ap_sessions        += radio_data_sessions
        except:
            radio_data_sessions = "None"

        radio_rates = [0, 0, 0, 0, 0, 0, 0, 0, 0]
        for nr, counter in enumerate(radio_data['counters']):
            try:
                radio_rate = get_rate("juniper_trpz_aps_sessions.%s.%s.%d" % \
                             (item, radio_number, nr), now, int(counter))
                radio_rates.append(radio_rate)
                ap_rates[nr] += radio_rate
            except:
                pass

        infotexts.append(("Radio %s: Input: %s/s, Output: %s/s, " + \
                          "Errors: %d, Resets: %d, Retries: %d, Sessions: %s, Noise: %s dBm") % \
                         (radio_number,
                          get_bytes_human_readable(radio_rates[5]),
                          get_bytes_human_readable(radio_rates[1] + radio_rates[3]),
                          radio_rates[6], radio_rates[7], radio_rates[8],
                          radio_data_sessions, radio_data_noise_floor))

    perfdata = [
        ("if_out_unicast",            ap_rates[0]),
        ("if_out_unicast_octets",     ap_rates[1]),
        ("if_out_non_unicast",        ap_rates[2]),
        ("if_out_non_unicast_octets", ap_rates[3]),
        ("if_in_pkts",                ap_rates[4]),
        ("if_in_octets",              ap_rates[5]),
        ("wlan_physical_errors",      ap_rates[6]),
        ("wlan_resets",               ap_rates[7]),
        ("wlan_retries",              ap_rates[8]),
        ("total_sessions",            ap_sessions), ]

    if noise_floor_radios:
        perfdata.append(("noise_floor", max(noise_floor_radios)))

    yield 0, ", ".join(infotexts), perfdata


check_info["juniper_trpz_aps_sessions"] = {
    "parse_function"            : parse_juniper_trpz_aps_sessions,
    "inventory_function"        : inventory_juniper_trpz_aps_sessions,
    "check_function"            : check_juniper_trpz_aps_sessions,
    "service_description"       : "Access Point %s",
    "has_perfdata"              : True,
    "node_info"                 : True,
    "snmp_scan_function"        : lambda oid: oid(".1.3.6.1.2.1.1.2.0").startswith(".1.3.6.1.4.1.14525.3.1") or \
                                              oid(".1.3.6.1.2.1.1.2.0").startswith(".1.3.6.1.4.1.14525.3.3"),
    "snmp_info"                 : [
            ( ".1.3.6.1.4.1.14525.4.5.1.1.2.1", [
                OID_END,
                "5",    # trpzApStatApStatusMacApState         -> status of access point
                "8",    # trpzApStatApStatusMacApName          -> name of access point
            ]),
            ( ".1.3.6.1.4.1.14525.4.5.1.1.10.1", [
                OID_END,
                "3",    # trpzApStatRadioOpStatsTxUniPkt       -> unicast packets transmitted
                "4",    # trpzApStatRadioOpStatsTxUniOct       -> octets transmitted in unicast packets
                "5",    # trpzApStatRadioOpStatsTxMultiPkt     -> multicast packets transmitted
                "6",    # trpzApStatRadioOpStatsTxMultiOct     -> octets transmitted in multicast packets
                "7",    # trpzApStatRadioOpStatsRxPkt          -> packets received
                "8",    # trpzApStatRadioOpStatsRxOctet        -> octets received
                "11",   # trpzApStatRadioOpStatsPhyErr         -> nr. physical errors occurred
                "12",   # trpzApStatRadioOpStatsResetCount     -> nr. reset operations
                "14",   # trpzApStatRadioOpStatsRxRetriesCount -> nr. transmission retries
                "15",   # trpzApStatRadioOpStatsUserSessions   -> current client sessions
                "16",   # trpzApStatRadioOpStatsNoiseFloor     -> noise floor (dBm)
            ]),
        ],
}
