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

# Current server status
# vserver["status"]
# 0 - NONE:   disabled
# 1 - GREEN:  available in some capacity
# 2 - YELLOW: not currently available
# 3 - RED:    not available
# 4 - BLUE:   availability is unknown
# 5 - GREY:   unlicensed
MAP_SERVER_STATUS = {
    '0': (1, "is disabled"),
    '1': (0, "is up and available"),
    '2': (2, "is currently not available"),
    '3': (2, "is not available"),
    '4': (1, "availability is unknown"),
    '5': (3, "is unlicensed")
}

MAP_ENABLED = {
    '0': "NONE",
    '1': "enabled",
    '2': "disabled",
    '3': "disabled by parent",
}

# Check configured limits
MAP_PARAM_TO_TEXT = {
    "if_in_octets": "Incoming bytes",
    "if_out_octets": "Outgoing bytes",
    "if_total_octets": "Total bytes",
    "if_in_pkts": "Incoming packets",
    "if_out_pkts": "Outgoing packets",
    "if_total_pkts": "Total packets",
}


def get_ip_address_human_readable(ip_addr):
    """
    u'\xc2;xJ'  ->  '194.59.120.74'
    """
    ip_addr = "".join(chr(ord(x)) for x in ip_addr)
    if len(ip_addr) == 4:
        return socket.inet_ntop(socket.AF_INET, ip_addr)
    if len(ip_addr) == 16:
        return socket.inet_ntop(socket.AF_INET6, ip_addr)
    return "-"


def parse_f5_bigip_vserver(info):
    vservers = {}
    for line in info:
        instance = vservers.setdefault(
            line[0], {
                "status": line[1],
                "enabled": line[2],
                "detail": line[3],
                "ip_address": get_ip_address_human_readable(line[4]),
            })

        for key, index in [
            ("connections_duration_min", 5),
            ("connections_duration_max", 6),
            ("connections_duration_mean", 7),
            ("if_in_pkts", 8),
            ("if_out_pkts", 9),
            ("if_in_octets", 10),
            ("if_out_octets", 11),
            ("connections_rate", 12),
            ("connections", 13),
            ("packet_velocity_asic", 14),
        ]:
            try:
                value = int(line[index])
            except (IndexError, ValueError):
                continue
            instance.setdefault(key, []).append(value)
    return vservers


def inventory_f5_bigip_vserver(parsed):
    for name, vserver in parsed.items():
        if vserver["status"] in ('1', '4'):  # Green and Blue
            yield name, {}


def get_aggregated_values(vserver):
    aggregation = collections.Counter()
    now = time.time()

    counter_keys = {
        "if_in_pkts", "if_out_pkts", "if_in_octets", "if_out_octets", "connections_rate",
        "packet_velocity_asic"
    }
    # Calculate counters
    for what in counter_keys & set(vserver.keys()):
        for idx, entry in enumerate(vserver[what]):
            rate = get_rate("%s.%s" % (what, idx), now, entry)
            aggregation[what] += rate

    # Calucate min/max/sum/mean values
    for what, function in [
        ("connections_duration_min", min),
        ("connections_duration_max", max),
        ("connections", sum),
        ("connections_duration_mean", lambda x: float(sum(x) / len(x))),
    ]:
        value_list = vserver.get(what)
        if value_list:
            aggregation[what] = function(value_list)

    for unit in ("octets", "pkts"):
        in_key = "if_in_%s" % unit
        out_key = "if_out_%s" % unit
        if in_key in aggregation or out_key in aggregation:
            aggregation["if_total_%s" % unit] = aggregation[in_key] + aggregation[out_key]

    return aggregation


def iter_counter_params():
    for unit, hr_function in (
        ("octets", lambda x: get_bytes_human_readable(x, base=1000.0)),
        ("pkts", lambda x: "%s" % x),
    ):
        for direction in ("in", "out", "total"):
            for boundary in ("", "_lower"):
                yield direction, unit, boundary, hr_function


@get_parsed_item_data
def check_f5_bigip_vserver(_item, params, data):
    # Need compatibility to version with _no_params
    if params is None:
        params = {}

    enabled_state = int(data["enabled"] not in MAP_ENABLED)
    enabled_txt = MAP_ENABLED.get(data["enabled"], "in unknown state")
    yield enabled_state, "Virtual Server with IP %s is %s" % (data["ip_address"], enabled_txt)

    state_map = params.get('state', {})
    state, state_readable = MAP_SERVER_STATUS.get(data["status"],
                                                  (3, "Unhandled status (%s)" % data["status"]))
    state = state_map.get(state_readable.replace(' ', '_'), state)

    detail = data["detail"]
    # Special handling: Statement from the network team:
    # Not available => uncritical when the childrens are down
    if data["status"] == '3' and detail.lower() == "the children pool member(s) are down":
        state = state_map.get("children_pool_members_down_if_not_available", 0)

    yield state, "State %s, Detail: %s" % (state_readable, detail)

    aggregation = get_aggregated_values(data)

    if "connections" in aggregation:
        yield 0, "Client connections: %d" % aggregation["connections"], sorted(aggregation.items())
    if "connections_rate" in aggregation:
        yield 0, "Connections rate: %.2f/sec" % aggregation["connections_rate"]

    for direction, unit, boundary, hr_function in iter_counter_params():
        key = "if_%s_%s" % (direction, unit)
        levels = params.get("%s%s" % (key, boundary))
        if levels is None or key not in aggregation:
            continue
        if boundary == "_lower" and isinstance(levels, tuple):
            levels = (None, None) + levels
        state, infotext, _extra_perfdata = check_levels_backport(
            aggregation[key],
            None,
            levels,
            human_readable_func=hr_function,
            infoname=MAP_PARAM_TO_TEXT[key],
            unit="/sec",
        )
        if state:
            yield state, infotext


check_info["f5_bigip_vserver"] = {
    "parse_function"          : parse_f5_bigip_vserver,
    "check_function"          : check_f5_bigip_vserver,
    "inventory_function"      : inventory_f5_bigip_vserver,
    "group"                   : "f5_bigip_vserver",
    "service_description"     : "Virtual Server %s",
    "has_perfdata"            : True,
    "includes"                : ["backport.include"],
    "snmp_info"               : (".1.3.6.1.4.1.3375.2.2.10", [
                                    "13.2.1.1", #  0 ltmVsStatusName
                                    "13.2.1.2", #  1 ltmVsStatusAvailState
                                    "13.2.1.3", #  2 ltmVsStatusEnabledState
                                    "13.2.1.5", #  3 ltmVsStatusDetailReason
                                    "1.2.1.3",  #  4 IP Address
                                    "2.3.1.2",  #  5 min conn duration
                                    "2.3.1.3",  #  6 max conn duration
                                    "2.3.1.4",  #  7 mean connection duration
                                    "2.3.1.6",  #  8 Client Packets In
                                    "2.3.1.8",  #  9 Client Packets Out
                                    "2.3.1.7",  # 10 Client Bytes In
                                    "2.3.1.9",  # 11 Client Bytes Out
                                    "2.3.1.11", # 12 Client Total Connections
                                    "2.3.1.12", # 13 Client Current Connections
                                    "2.3.1.25", # 14 packet_velocity_asic Total Connections
                               ]),
    "snmp_scan_function"      : lambda oid: ".1.3.6.1.4.1.3375.2" in oid(".1.3.6.1.2.1.1.2.0") \
                                      and "big-ip" in oid(".1.3.6.1.4.1.3375.2.1.4.1.0").lower(),
}
