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

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

    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
    for count, levels in sorted_params:
        if nodes_count >= count:
            warn, crit = levels
            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 * total_memory_size / 100,
                      crit * total_memory_size / 100, 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,
}



#   .--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)
    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
    label = {}
    messages = []
    label['Status'] = ""
    overallStatus = str(data['overallStatus'][0])
    if overallStatus == "yellow":
        state = 1
        label['Status'] = "(!)"
    elif overallStatus in [ "red", "gray"]:
        state = 2
        label['Status'] = "(!!)"
    messages.append("Entity state: %s%s" % (overallStatus, label['Status']))

    label['powerState'] = ''
    powerState = str(data['runtime.powerState'][0])
    if powerState in ['poweredOff', 'unknown']:
        state = 2
        label['powerState'] = "(!!)"
    elif powerState == 'standBy':
        state = max(state, 1)
        label['powerState'] = "(!)"
    messages.append("Power state: %s%s" % (powerState, label['powerState']))


    return(state, ", ".join(messages))

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']
    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-----------------------------------------------------------.
#   |             __  __       _ _   _             _   _                   |
#   |            |  \/  |_   _| | |_(_)_ __   __ _| |_| |__                |
#   |            | |\/| | | | | | __| | '_ \ / _` | __| '_ \               |
#   |            | |  | | |_| | | |_| | |_) | (_| | |_| | | |              |
#   |            |_|  |_|\__,_|_|\__|_| .__/ \__,_|\__|_| |_|              |
#   |                                 |_|                                  |
#   +----------------------------------------------------------------------+


def esx_vsphere_multipath_convert(info):
    data = esx_vsphere_hostsystem_convert(info)
    data = data['config.multipathState.path']
    data = zip(data[::2], data[1::2])
    paths = {}
    for path, state in data:
        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 = "Type %s" % path_type
            for element in "active", "dead", "disabled", "standby", "unknown":
                message += ", %d %s%s" % ( state_infos[element][1], element, state_infos[element][2] )
            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"
}

