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


# Example of output from ipmi:
# <<<ipmi>>>
# ambienttemp 25.800 degrees_C ok na na na 34.800 40.200 na
# bulk.v12-0-s0 11.940 Volts ok na 10.200 na na 13.800 na
# bulk.v3_3-s0 3.360 Volts ok na 3.000 na na 3.600 na
# bulk.v3_3-s5 3.240 Volts ok na 3.000 na na 3.600 na
# bulk.v5-s0 5.040 Volts ok na 4.500 na na 5.520 na
# bulk.v5-s5 5.040 Volts ok na 4.500 na na 5.520 na
# cpu0.dietemp 51.000 degrees_C ok na na na 70.200 73.200 na
# ...
# On another host
# mb.t_amb 24.000 degrees_C ok na na na 70.000 75.000 80.000
# mb.v_bat 2.839 Volts ok 2.340 2.527 2.621 3.307 3.510 3.697
# mb.v_+3v3stby 3.218 Volts ok 2.595 2.785 2.993 3.598 3.789 3.996
# mb.v_+3v3 3.339 Volts ok 2.595 2.785 2.993 3.598 3.789 3.996
# mb.v_+5v 5.044 Volts ok 3.484 3.978 4.498 5.486 5.980 6.500
# fp.t_amb 21.000 degrees_C ok na na na 30.000 35.000 45.000
# pdb.t_amb 21.000 degrees_C ok na na na 70.000 75.000 80.000
# io.t_amb 19.000 degrees_C ok na na na 70.000 75.000 80.000
# p0.t_core 18.000 degrees_C ok na na na 62.000 67.000 75.000
# p0.v_vdd 1.332 Volts ok 0.792 0.900 0.996 1.596 1.692 1.800

# Yet another host (HP DL 360G5)
# <<<ipmi>>>
# UID_Light 0.000 unspecified ok na na 0.000 na na na
# Int._Health_LED 0.000 unspecified ok na na 0.000 na na na
# Ext._Health_LED 0.000 unspecified ok na na 0.000 na na na
# Power_Supply_1 0.000 unspecified nc na na 0.000 na na na
# Power_Supply_2 0.000 unspecified nc na na 0.000 na na na
# Power_Supplies 0.000 unspecified nc na na 0.000 na na na
# VRM_1 0.000 unspecified cr na na 0.000 na na na
# VRM_2 0.000 unspecified cr na na 0.000 na na na
# Fan_Block_1 34.888 unspecified nc na na 75.264 na na na
# Fan_Block_2 29.792 unspecified nc na na 75.264 na na na
# Fan_Block_3 34.888 unspecified nc na na 75.264 na na na
# Fan_Blocks 0.000 unspecified nc na na 0.000 na na na
# Temp_1 39.000 degrees_C ok na na -64.000 na na na
# Temp_2 16.000 degrees_C ok na na -64.000 na na na
# Temp_3 30.000 degrees_C ok na na -64.000 na na na
# Temp_4 30.000 degrees_C ok na na -64.000 na na na
# Temp_5 25.000 degrees_C ok na na -64.000 na na na
# Temp_6 30.000 degrees_C ok na na -64.000 na na na
# Temp_7 30.000 degrees_C ok na na -64.000 na na na
# Power_Meter 180.000 Watts cr na na 384.000 na na na

# And this host has some false-criticals (PowerMeter, VirtualFan)
# <<<ipmi>>>
# Temp_1 17.000 degrees_C ok 0.000 0.000 0.000 40.000 42.000 46.000
# Temp_2 40.000 degrees_C ok 0.000 0.000 0.000 0.000 82.000 83.000
# Temp_3 44.000 degrees_C ok 0.000 0.000 0.000 0.000 82.000 83.000
# Temp_4 52.000 degrees_C ok 0.000 0.000 0.000 0.000 87.000 92.000
# Temp_5 46.000 degrees_C ok 0.000 0.000 0.000 0.000 85.000 90.000
# Temp_6 55.000 degrees_C ok 0.000 0.000 0.000 0.000 85.000 90.000
# Temp_7 51.000 degrees_C ok 0.000 0.000 0.000 0.000 85.000 90.000
# Temp_8 58.000 degrees_C ok 0.000 0.000 0.000 0.000 78.000 83.000
# Temp_9 74.000 degrees_C ok 0.000 0.000 0.000 0.000 110.000 115.000
# Temp_10 31.000 degrees_C ok 0.000 0.000 0.000 0.000 60.000 65.000
# Virtual_Fan 19.600 unspecified nc na na na na na na
# Power_Meter 236.000 Watts cr na na na na na na


# IPMI has two operation modes:
# 1. detailed
# 2. summarized
# This controls how the inventory is done. In summary-mode, the
# inventory returns one single check item 'Summary' - or nothing
# if the host does not send any IPMI information
# In Detailed mode for each sensor one item is returned.

ipmi_summarize = True
ipmi_ignore_nr = False # set to True in order to ignore entries with state 'nr'
ipmi_ignored_sensors = [] # example: [ "Power_Meter", "Virtual_Fan" ]

def ipmi_ignore_entry(name, state):
    if ipmi_ignore_nr and state == 'nr':
        return True
    for e in ipmi_ignored_sensors:
        if name.startswith(e):
            return True
    return False

def inventory_ipmi(info):
    if ipmi_summarize and len(info) > 0:
        return [ ( "Summary", None ) ]
    else:
        return [ ( line[0], None )
                 for line in info
                 if not ipmi_ignore_entry(line[0], line[3]) ]

def check_ipmi(item, params, info):
    if item == "Summary":
        return check_ipmi_summarized(info)
    else:
        return check_ipmi_detailed(item, info)

def check_ipmi_detailed(item, info):
    try:
        for name, val, unit, status, unrec_low, crit_low, \
            warn_low, warn_high, crit_high, unrec_high in info:
            if name == item:
                perfdata = [ (name, val + unit) ] # TODO: add warn and crit levels
                if status == 'ok':
                    return (0, "OK - %s is %s %s" % (name, val, unit), perfdata)
                elif status == 'nc':
                    return (1, "WARN - %s is %s %s" % (name, val, unit), perfdata)
                else:
                    return (2, "CRIT - %s is %s %s" % (name, val, unit), perfdata)
        return (3, 'UNKNOWN - item %s not found' % item)
    except:
        return (3, "UNKNOWN - invalid or incomplete  output from agent")

def check_ipmi_summarized(info):
    worst_status = 0
    warn_texts = []
    crit_texts = []
    count = 0
    ambient_count = 0
    ambient_sum = 0.0
    try:
        for name, val, unit, status, unrec_low, crit_low, \
            warn_low, warn_high, crit_high, unrec_high in info:
            # Skip datasets which have no valid data (zero value, no unit and state nc)
            if val == '0.000' and unit == 'unspecified' and status == 'nc':
                continue

            if ipmi_ignore_entry(name, status):
                continue

            text = "%s is %s" % (name, val)
            if unit != 'unspecified':
                text += ' %s' % unit
            count += 1
            if status == 'nc':
                worst_status = max(worst_status, 1)
                warn_texts.append(text)
            elif status == 'nr' and ipmi_ignore_nr:
                pass
            elif status != 'ok':
                worst_status = 2
                crit_texts.append(text)
            if "amb" in name or "Ambient" in name:
                try:
                    ambient_count += 1
                    ambient_sum += float(val)
                except:
                    pass
    except:
        return (3, "UNKNOWN - invalid or incomplete  output from agent")


    if ambient_count > 0:
        perfdata = [ ("ambient_temp", ambient_sum / ambient_count) ]
    else:
        perfdata = []

    statname = { 0: "OK", 1:"WARN", 2:"CRIT" }[worst_status]
    if worst_status == 0:
        infotext = [ "%d sensors OK" % count ]
    else:
        infotext = []
        if len(crit_texts) > 0:
            infotext.append("CRIT are: %s" % ", ".join(crit_texts))
        if len(warn_texts) > 0:
            infotext.append("WARN are: %s" % ", ".join(warn_texts))
    return (worst_status, "%s - %s" % (statname, ' - '.join(infotext)), perfdata)


check_info['ipmi'] = (check_ipmi, "IPMI Sensor %s", 1,  inventory_ipmi)

# Make sure, configuration variables needed during check time are present
# in precompiled code
check_config_variables.append("ipmi_ignore_nr")
check_config_variables.append("ipmi_ignored_sensors")
