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

def esx_vsphere_hostsystem_convert(info):
    data = {}
    for line in info:
        data[line[0]] = line[1:]
    return data


#.
#   .--CPU-----------------------------------------------------------------.
#   |                           ____ ____  _   _                           |
#   |                          / ___|  _ \| | | |                          |
#   |                         | |   | |_) | | | |                          |
#   |                         | |___|  __/| |_| |                          |
#   |                          \____|_|    \___/                           |
#   |                                                                      |
#   +----------------------------------------------------------------------+

esx_host_cpu_default_levels = {}

# hardware.cpuInfo.numCpuCores 12
# hardware.cpuInfo.numCpuPackages 2
# hardware.cpuInfo.numCpuThreads 24
# hardware.cpuInfo.hz 2933436846               --> In Hz per CPU Core
# summary.quickStats.overallCpuUsage 7539      --> In MHz


def inventory_esx_vsphere_hostsystem_cpu(info):
    data = esx_vsphere_hostsystem_convert(info).keys()
    if 'summary.quickStats.overallCpuUsage' in data \
        and 'hardware.cpuInfo.hz' in data\
        and 'hardware.cpuInfo.numCpuCores' in data:
        return [(None, {})]

def check_esx_vsphere_hostsystem_cpu(item, params, info):
    data = esx_vsphere_hostsystem_convert(info)

    if "summary.quickStats.overallCpuUsage" not in data:
        return

    num_sockets = int(data['hardware.cpuInfo.numCpuPackages'][0])
    num_cores = int(data['hardware.cpuInfo.numCpuCores'][0])
    num_threads = int(data['hardware.cpuInfo.numCpuThreads'][0])
    used_mhz = float(data['summary.quickStats.overallCpuUsage'][0])
    mhz_per_core = float(data['hardware.cpuInfo.hz'][0]) / 1024.0 / 1024.0
    total_mhz = mhz_per_core * num_cores

    usage = used_mhz / total_mhz * 100
    per_core = num_cores / 100.0

    infotext = "%.1f%%" % usage

    # Convert legacy parameters
    this_time = time.time()
    state, infotext, perfdata = check_cpu_util(usage, params).next()

    infotext += ", %.2fGHz/%.2fGHz" % (used_mhz / 1024.0, total_mhz / 1024.0)
    infotext += ", %d sockets, %d cores/socket, %d threads" % (
            num_sockets, num_cores / num_sockets, num_threads)

    # put number of threads as MAX value for first perf-data. This
    # is needed by the PNP template.
    perfdata_cpu = list(perfdata[0])
    perfdata_cpu[-1] = num_threads
    perfdata = [ tuple(perfdata_cpu) ] + perfdata[1:]
    return (state, infotext, perfdata)


check_info['esx_vsphere_hostsystem.cpu_usage'] = {
   "inventory_function"      : inventory_esx_vsphere_hostsystem_cpu,
   "check_function"          : check_esx_vsphere_hostsystem_cpu,
   "service_description"     : "CPU utilization",
   "group"                   : "cpu_utilization_os",
   "has_perfdata"            : True,
   "default_levels_variable" : "esx_host_cpu_default_levels",
   "includes"                : [ "cpu_util.include" ],
}


#.
#   .--Mem-Cluster---------------------------------------------------------.
#   |     __  __                       ____ _           _                  |
#   |    |  \/  | ___ _ __ ___        / ___| |_   _ ___| |_ ___ _ __       |
#   |    | |\/| |/ _ \ '_ ` _ \ _____| |   | | | | / __| __/ _ \ '__|      |
#   |    | |  | |  __/ | | | | |_____| |___| | |_| \__ \ ||  __/ |         |
#   |    |_|  |_|\___|_| |_| |_|      \____|_|\__,_|___/\__\___|_|         |
#   |                                                                      |
#   +----------------------------------------------------------------------+

def check_esx_vsphere_hostsystem_mem_cluster(item, params, info):
    data = {}
    for line in info:
        if line[0] in ["summary.quickStats.overallMemoryUsage", "hardware.memorySize", "name"]:
            data.setdefault(line[0], []).append(line[1])
    sorted_params = sorted(params, reverse = True)

    nodes_count = len(data['name'])
    total_memory_usage = sum(map(lambda x: savefloat(x) * 1024 * 1024, data['summary.quickStats.overallMemoryUsage']))
    total_memory_size  = sum(map(lambda x: savefloat(x), data['hardware.memorySize']))

    level = total_memory_usage / total_memory_size * 100
    label = ""
    state = 0
    warn_perf, crit_perf = None, None
    for count, levels in sorted_params:
        if nodes_count >= count:
            warn, crit = levels
            warn_perf = warn * total_memory_size / 100
            crit_perf = crit * total_memory_size / 100

            if level > crit:
                state = 2
                label = " (Levels at %d%%/%d%%)" % (warn, crit)
            elif level > warn:
                state = 1
                label = " (Levels at %d%%/%d%%)" % (warn, crit)
            break

    perf = [("usage", total_memory_usage, warn_perf, crit_perf, 0, total_memory_size)]
    yield state, "%d%%%s used - %s/%s" % \
            (level, label, get_bytes_human_readable(total_memory_usage),
             get_bytes_human_readable(total_memory_size)), perf


check_info['esx_vsphere_hostsystem.mem_usage_cluster'] = {
  "check_function"      : check_esx_vsphere_hostsystem_mem_cluster,
  "service_description" : "Memory used",
  "group"               : "mem_cluster",
  "has_perfdata"        : True,
}


#.
#   .--CPU-Cluster---------------------------------------------------------.
#   |        ____ ____  _   _        ____ _           _                    |
#   |       / ___|  _ \| | | |      / ___| |_   _ ___| |_ ___ _ __         |
#   |      | |   | |_) | | | |_____| |   | | | | / __| __/ _ \ '__|        |
#   |      | |___|  __/| |_| |_____| |___| | |_| \__ \ ||  __/ |           |
#   |       \____|_|    \___/       \____|_|\__,_|___/\__\___|_|           |
#   |                                                                      |
#   +----------------------------------------------------------------------+


def check_esx_vsphere_hostsystem_cpu_util_cluster(item, params, info):
    current_node = {}
    def get_node_usage(node):
        num_sockets    = int(node['hardware.cpuInfo.numCpuPackages'])
        num_cores      = int(node['hardware.cpuInfo.numCpuCores'])
        num_threads    = int(node['hardware.cpuInfo.numCpuThreads'])
        used_mhz     = float(node['summary.quickStats.overallCpuUsage'])
        mhz_per_core = float(node['hardware.cpuInfo.hz']) / 1024.0 / 1024.0
        total_mhz = mhz_per_core * num_cores
        return used_mhz, total_mhz, num_threads

    overall_used    = []
    overall_total   = []
    overall_threads = []
    for line in info:
        if line[0] in [ "hardware.cpuInfo.numCpuPackages",
                        "hardware.cpuInfo.numCpuCores",
                        "hardware.cpuInfo.numCpuThreads",
                        "summary.quickStats.overallCpuUsage",
                        "hardware.cpuInfo.hz" ]:
            current_node[line[0]] = line[1]
        if len(current_node) == 5: # 5 keys -> node complete
            node_used, node_total, node_threads = get_node_usage(current_node)
            overall_used.append(node_used)
            overall_total.append(node_total)
            overall_threads.append(node_threads)
            current_node = {}


    sum_used    = sum(overall_used)
    sum_total   = sum(overall_total)
    sum_threads = sum(overall_threads)
    usage       = sum_used / sum_total * 100
    node_count  = len(overall_used)

    # Convert legacy parameters
    this_time = time.time()


    sorted_params = sorted(params, reverse = True)
    for count, levels in sorted_params:
        if node_count >= count:
            state, infotext, perfdata = check_cpu_util(usage, levels).next()
            break
    else:
        state, infotext, perfdata = check_cpu_util(usage, None).next()

    yield 0, "%d Nodes" % node_count
    yield 0, "%.2fGHz/%.2fGHz" % (sum_used / 1024.0, sum_total / 1024.0)
    yield 0, "%d threads" % sum_threads

    # put number of threads as MAX value for first perf-data. This
    # is needed by the PNP template.
    perfdata_cpu = list(perfdata[0])
    perfdata_cpu[-1] = sum_threads
    perfdata = [ tuple(perfdata_cpu) ] + perfdata[1:]
    yield state, infotext, perfdata


check_info['esx_vsphere_hostsystem.cpu_util_cluster'] = {
   "check_function"          : check_esx_vsphere_hostsystem_cpu_util_cluster,
   "service_description"     : "CPU utilization",
   "group"                   : "cpu_utilization_cluster",
   "has_perfdata"            : True,
   "includes"                : [ "cpu_util.include" ],
}
#.
#   .--Memory--------------------------------------------------------------.
#   |               __  __                                                 |
#   |              |  \/  | ___ _ __ ___   ___  _ __ _   _                 |
#   |              | |\/| |/ _ \ '_ ` _ \ / _ \| '__| | | |                |
#   |              | |  | |  __/ | | | | | (_) | |  | |_| |                |
#   |              |_|  |_|\___|_| |_| |_|\___/|_|   \__, |                |
#   |                                                |___/                 |
#   +----------------------------------------------------------------------+


esx_host_mem_default_levels = ( 80.0, 90.0 )

def inventory_esx_vsphere_hostsystem_mem(info):
    data = esx_vsphere_hostsystem_convert(info).keys()
    if 'summary.quickStats.overallMemoryUsage' in data and 'hardware.memorySize' in data:
        return [(None, 'esx_host_mem_default_levels')]

def check_esx_vsphere_hostsystem_mem(item, params, info):
    data = esx_vsphere_hostsystem_convert(info)

    if "summary.quickStats.overallMemoryUsage" not in data:
        return

    memory_usage = savefloat(data['summary.quickStats.overallMemoryUsage'][0]) * 1024 * 1024
    memory_size  = savefloat(data['hardware.memorySize'][0])
    level = memory_usage / memory_size * 100

    warn, crit = params
    state = 0
    label = ''
    if level > crit:
        state = 2
        label = " (Levels at %d%%/%d%%)" % (warn, crit)
    elif level > warn:
        state = 1
        label = " (Levels at %d%%/%d%%)" % (warn, crit)

    message = "%d%%%s used - %s/%s" % \
    (level, label, get_bytes_human_readable(memory_usage), get_bytes_human_readable(memory_size))
    perf = [("usage", memory_usage, warn * memory_size / 100, crit * memory_size / 100, 0, memory_size)]
    return(state, message, perf)


check_info['esx_vsphere_hostsystem.mem_usage'] = {
  "inventory_function"  : inventory_esx_vsphere_hostsystem_mem,
  "check_function"      : check_esx_vsphere_hostsystem_mem,
  "service_description" : "Memory used",
  "group"               : "esx_host_memory",
  "has_perfdata"        : True
}

#.
#   .--State---------------------------------------------------------------.
#   |                       ____  _        _                               |
#   |                      / ___|| |_ __ _| |_ ___                         |
#   |                      \___ \| __/ _` | __/ _ \                        |
#   |                       ___) | || (_| | ||  __/                        |
#   |                      |____/ \__\__,_|\__\___|                        |
#   |                                                                      |
#   +----------------------------------------------------------------------+


def inventory_esx_vsphere_hostsystem_state(info):
    data = esx_vsphere_hostsystem_convert(info).keys()
    if 'runtime.inMaintenanceMode' in data:
        return [(None, None)]

def check_esx_vsphere_hostsystem_state(_no_item, _no_params, info):
    data = esx_vsphere_hostsystem_convert(info)
    state = 0
    if "overallStatus" not in data:
        return

    overallStatus = str(data['overallStatus'][0])
    if overallStatus == "yellow":
        state = 1
    elif overallStatus in [ "red", "gray"]:
        state = 2
    yield state, "Entity state: " + overallStatus

    state = 0
    powerState = str(data['runtime.powerState'][0])
    if powerState in ['poweredOff', 'unknown']:
        state = 2
    elif powerState == 'standBy':
        state = 1
    yield state, "Power state: " + powerState

check_info['esx_vsphere_hostsystem.state'] = {
  "inventory_function"  : inventory_esx_vsphere_hostsystem_state,
  "check_function"      : check_esx_vsphere_hostsystem_state,
  "service_description" : "Overall state",
}
#.
#   .--Maintenance---------------------------------------------------------.
#   |       __  __       _       _                                         |
#   |      |  \/  | __ _(_)_ __ | |_ ___ _ __   __ _ _ __   ___ ___        |
#   |      | |\/| |/ _` | | '_ \| __/ _ \ '_ \ / _` | '_ \ / __/ _ \       |
#   |      | |  | | (_| | | | | | ||  __/ | | | (_| | | | | (_|  __/       |
#   |      |_|  |_|\__,_|_|_| |_|\__\___|_| |_|\__,_|_| |_|\___\___|       |
#   |                                                                      |
#   +----------------------------------------------------------------------+
#   |                                                                      |
#   '----------------------------------------------------------------------'

def inventory_esx_vsphere_hostsystem_maintenance(info):
    data = esx_vsphere_hostsystem_convert(info)
    if 'runtime.inMaintenanceMode' in data:
        current_state = str(data['runtime.inMaintenanceMode'][0]).lower()
        return [(None, { 'target_state' : current_state })]

def check_esx_vsphere_hostsystem_maintenance(_no_item, params, info):
    data = esx_vsphere_hostsystem_convert(info)
    target_state = params['target_state']

    if "runtime.inMaintenanceMode" not in data:
        return

    current_state = str(data['runtime.inMaintenanceMode'][0]).lower()
    state = 0
    if target_state != current_state:
        state = 2
    if current_state == "true":
        return state, "System running is in Maintenance mode"
    else:
        return state, "System not in Maintenance mode"

check_info['esx_vsphere_hostsystem.maintenance'] = {
  "inventory_function"  : inventory_esx_vsphere_hostsystem_maintenance,
  "check_function"      : check_esx_vsphere_hostsystem_maintenance,
  "service_description" : "Maintenance Mode",
  "group"               : "esx_hostystem_maintenance",
}

#.
#   .--Multipath-----------------------------------------------------------.
#   |             __  __       _ _   _             _   _                   |
#   |            |  \/  |_   _| | |_(_)_ __   __ _| |_| |__                |
#   |            | |\/| | | | | | __| | '_ \ / _` | __| '_ \               |
#   |            | |  | | |_| | | |_| | |_) | (_| | |_| | | |              |
#   |            |_|  |_|\__,_|_|\__|_| .__/ \__,_|\__|_| |_|              |
#   |                                 |_|                                  |
#   +----------------------------------------------------------------------+

# 5.1
# fc.20000024ff2e1b4c:21000024ff2e1b4c-fc.500a098088866d7e:500a098188866d7e-naa.60a9800044314f68553f436779684544 active
# unknown.vmhba0-unknown.2:0-naa.6b8ca3a0facdc9001a2a27f8197dd718 active
# 5.5
# fc.20000024ff3708ec:21000024ff3708ec-fc.500a098088866d7e:500a098188866d7e-naa.60a9800044314f68553f436779684544 active
# fc.500143802425a24d:500143802425a24c-fc.5001438024483280:5001438024483288-naa.5001438024483280 active
# >= version 6.0
# vmhba32:C0:T0:L0 active

def esx_vsphere_multipath_convert(info):
    data = esx_vsphere_hostsystem_convert(info)
    paths = {}
    data = data.get('config.multipathState.path')
    if not data:
        return paths

    data = zip(data[::2], data[1::2])
    for path, state in data:
        # ESX 6.0 uses a different format, e.g "vmhba32:C0:T0:L0 active"
        if len(path.split(":")) == 4:
            real_path = ":".join(path.split(":")[:3])
            paths.setdefault(real_path, [])
            # Note: There is no path type information available in these esx paths
            paths[real_path].append((state, None))
            continue

        path_tokens = path.split('-')
        if "." not in path_tokens[-1]:
            continue # invalid format / unknown type
        path_type, path_id = path_tokens[-1].split('.')

        if path_type in ['naa', 'eui']:
            hw_type = path.split('.')[0]
            if hw_type != 'unknown':
                paths.setdefault(path_id, [])
                paths[path_id].append((state, "%s/%s" % (path_type, hw_type)))

    return paths

def inventory_esx_vsphere_hostsystem_multipath(info):
    data = esx_vsphere_multipath_convert(info).items()
    return [ (x, None) for x, y  in data]

def check_esx_vsphere_hostsystem_multipath(item, params, info):
    state_infos = {
        # alert_state, count, info
        "active"   : [0, 0, ""],
        "dead"     : [2, 0, ""],
        "disabled" : [1, 0, ""],
        "standby"  : [0, 0, ""],
        "unknown"  : [2, 0, ""]
    }

    state   = 0
    message = ""

    for path, states in esx_vsphere_multipath_convert(info).items():
        if path == item:
            # Collect states
            for path_state, path_type in states:
                state_item = state_infos.get(path_state)
                if state_item:
                    state_item[1] += 1
                    state = max(state_item[0], state)

            # Check warn, critical
            if not params or type(params) == list:
                if state_infos["standby"][1] > 0 and \
                   state_infos["standby"][1] != state_infos["active"][1]:
                    standby_label = "(!)"
                    state = max(state_infos["standby"][0], state)
            else:
                state = 0
                for state_name, state_values in state_infos.items():
                    if params.get(state_name):
                        limits = params.get(state_name)
                        if len(limits) == 2:
                            warn_max, crit_max = limits
                            crit_min, warn_min = 0, 0
                        else:
                            crit_min, warn_min, warn_max, crit_max = limits

                        extra_info = ""
                        count = state_values[1]
                        if count < crit_min:
                            state = max(state, 2)
                            state_values[2] = "(!!)(less than %d)" % crit_min
                        elif count > crit_max:
                            state = max(state, 2)
                            state_values[2] = "(!!)(more than %d)" % crit_max
                        elif count < warn_min:
                            state = max(state, 1)
                            state_values[2] = "(!)(less than %d)" % warn_min
                        elif count > warn_max:
                            state = max(state, 1)
                            state_values[2] = "(!)(more than %d)" % warn_max

            # Output message
            message = ""
            if path_type:
                message += "Type %s, " % path_type

            element_text = []
            for element in "active", "dead", "disabled", "standby", "unknown":
                element_text.append("%d %s%s" % ( state_infos[element][1], element, state_infos[element][2]))
            message += ", ".join(element_text)
            break
    else:
        return 3, "Path not found in agent output"

    return (state, message)

check_info['esx_vsphere_hostsystem.multipath'] = {
  "inventory_function"  : inventory_esx_vsphere_hostsystem_multipath,
  "check_function"      : check_esx_vsphere_hostsystem_multipath,
  "service_description" : "Multipath %s",
  "group"               : "multipath_count"
}

