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


# NOTE: Devices of type 3850 with firmware versions 3.2.0SE, 3.2.1, 3.2.2
# have been observed to display a tenth of the actual temperature value.
# A firmware update on the device fixes this.

# CISCO-ENTITY-SENSOR-MIB::entSensorScale

cisco_entity_exponents = {
    "1"  : -24,  #     1:yocto
    "2"  : -21,  #     2:zepto
    "3"  : -18,  #     3:atto
    "4"  : -15,  #     4:femto
    "5"  : -12,  #     5:pico
    "6"  : -9,   #     6:nano
    "7"  : -6,   #     7:micro
    "8"  : -3,   #     8:milli
    "9"  : 0,    #     9:units
    "10" : 3,    #     10:kilo
    "11" : 6,    #     11:mega
    "12" : 9,    #     12:giga
    "13" : 12,   #     13:tera
    "14" : 18,   #     14:exa
    "15" : 15,   #     15:peta
    "16" : 21,   #     16:zetta
    "17" : 24,   #     17:yotta
}


def parse_cisco_temperature(info):
    # CISCO-ENTITY-SENSOR-MIB::entSensorStatus
    map_states = {
        "1": (0, "OK"),
        "2": (3, "unavailable"),
        "3": (3, "non-operational"),
    }

    # CISCO-ENVMON-MIB
    map_envmon_states = {
        '1': (0, "normal"),
        '2': (1, "warning"),
        '3': (2, "critical"),
        '4': (2, "shutdown"),
        '5': (3, "not present"),
        '6': (3, "not functioning"),
    }

    description_info, state_info, levels_info, perfstuff = info

    # Create dict of sensor descriptions
    descriptions = dict(description_info)

    # Create dict with thresholds
    thresholds = {}
    for sensor_id, sensortype, scalecode, magnitude, value, sensorstate in state_info:
        thresholds.setdefault(sensor_id, [])

    for endoid, level in levels_info:
        # endoid is e.g. 21549.9 or 21459.10
        sensor_id, subid = endoid.split('.')
        thresholds.setdefault(sensor_id, []).append(level)

    # Parse OIDs described by CISCO-ENTITY-SENSOR-MIB
    entity_parsed = {}
    for sensor_id, sensortype, scalecode, magnitude, value, sensorstate in state_info:
        if sensor_id in descriptions:
            descr = descriptions[sensor_id]
        else:
            descr = sensor_id

        if not descr:
            continue

        entity_parsed.setdefault(sensortype, {})
        sensor_attrs = {
            'descr': descr,
            'raw_dev_state': sensorstate, # used in discovery function
            'dev_state': map_states.get(sensorstate, (3, 'unknown[%s]' % sensorstate))
        }

        if sensorstate == '1':
            scale = 10**int(magnitude)
            scale *= 10 ** (-1 * cisco_entity_exponents[scalecode])
            sensor_attrs['reading'] = float(value) / scale
            if len(thresholds[sensor_id]) in [ 2, 4 ]:
                warnraw, critraw = thresholds[sensor_id][0:2]
                # Some devices deliver these values in the wrong order
                dev_levels = ( min(float(warnraw)/scale, float(critraw)/scale),
                               max(float(warnraw)/scale, float(critraw)/scale) )
            else:
                dev_levels = None
            sensor_attrs['dev_levels'] = dev_levels
            entity_parsed[sensortype].setdefault(sensor_id, sensor_attrs)

    parsed = {}
    for sensor_id, statustext, temp, max_temp, state in perfstuff:
        parsed.setdefault('8', {})
        if sensor_id in descriptions and sensor_id in entity_parsed.get('8', {}):
            # if this sensor is already in the dictionary, ensure we use the same name
            item = descriptions[sensor_id]
            prev_description = cisco_sensor_item(statustext, sensor_id)
            # also register the name we would have used up to 1.2.8b4, so we can give
            # the user a proper info message.
            # It's the little things that show you care
            parsed['8'][prev_description] = {"obsolete": True}
        else:
            item = cisco_sensor_item(statustext, sensor_id)

        temp_sensor_attrs = {
            'raw_dev_state': state,
            'dev_state': map_envmon_states.get(state, (3, 'unknown[%s]' % state)),
        }

        try:
            temp_sensor_attrs['reading'] = int(temp)
            if max_temp and int(max_temp):
                temp_sensor_attrs['dev_levels'] = (int(max_temp), int(max_temp))
            else:
                temp_sensor_attrs['dev_levels'] = None
        except:
            temp_sensor_attrs['dev_state'] = (3, 'sensor defect')

        parsed['8'].setdefault(item, temp_sensor_attrs)

    for sensor_type, sensors in entity_parsed.iteritems():
        for sensor_attrs in sensors.values():
            # Do not overwrite found sensors from perfstuff loop
            parsed.setdefault(sensor_type, {}).setdefault(sensor_attrs['descr'], sensor_attrs)

    return parsed


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


def inventory_cisco_temperature(parsed):
    for item, value in parsed.get('8', {}).iteritems():
        if not value.get("obsolete", False):
            yield item, {}


def check_cisco_temperature(item, params, parsed):
    temp_parsed = parsed.get('8', {})
    if item in temp_parsed:
        data = temp_parsed[item]
        if data.get("obsolete", False):
            return 3, "This sensor is obsolete, please rediscover"
        else:
            state, state_readable = data['dev_state']
            return check_temperature(data['reading'], params, "cisco_temperature_%s" % item,
                        dev_levels=data['dev_levels'],
                        dev_status=state, dev_status_name=state_readable)


check_info['cisco_temperature'] = {
    "parse_function"     : parse_cisco_temperature,
    "inventory_function" : inventory_cisco_temperature,
    "check_function"     : check_cisco_temperature,
    "service_description": "Temperature %s",
    "group"              : "temperature",
    "has_perfdata"       : True,
    "snmp_scan_function" : lambda oid: "cisco" in oid(".1.3.6.1.2.1.1.1.0").lower() and \
                                    ( oid(".1.3.6.1.4.1.9.9.91.1.1.1.1.*") != None or
                                      oid(".1.3.6.1.4.1.9.9.13.1.3.1.3.*") != None ),
    "snmp_info"          : [
                               # cisco_temp_sensor data
                               ( ".1.3.6.1.2.1.47.1.1.1.1", [
                                 OID_END,
                                 CACHED_OID(2), # Description of the sensor
                               ]),

                               # Type and current state
                               ( ".1.3.6.1.4.1.9.9.91.1.1.1.1", [
                                 OID_END,
                                 1, # CISCO-ENTITY-SENSOR-MIB::entSensorType
                                 2, # CISCO-ENTITY-SENSOR-MIB::entSensorScale
                                 3, # CISCO-ENTITY-SENSOR-MIB::entSensorPrecision
                                 4, # CISCO-ENTITY-SENSOR-MIB::entSensorValue
                                 5, # CISCO-ENTITY-SENSOR-MIB::entSensorStatus
                               ]),

                               # Threshold
                               ( ".1.3.6.1.4.1.9.9.91.1.2.1.1", [
                                 OID_END,
                                 4, # Thresholds
                               ]),

                               # cisco_temp_perf data
                               ( ".1.3.6.1.4.1.9.9.13.1.3.1", [ # CISCO-SMI
                                 OID_END,
                                 2, # ciscoEnvMonTemperatureStatusDescr
                                 3, # ciscoEnvMonTemperatureStatusValue
                                 4, # ciscoEnvMonTemperatureThreshold
                                 6, # ciscoEnvMonTemperatureState
                               ]),
                            ],
    "includes"          : [ "temperature.include", 'cisco_sensor_item.include' ],
}


#.
#   .--dom-----------------------------------------------------------------.
#   |                            _                                         |
#   |                         __| | ___  _ __ ___                          |
#   |                        / _` |/ _ \| '_ ` _ \                         |
#   |                       | (_| | (_) | | | | | |                        |
#   |                        \__,_|\___/|_| |_| |_|                        |
#   |                                                                      |
#   +----------------------------------------------------------------------+
#   | digital optical monitoring                                           |
#   '----------------------------------------------------------------------'


def inventory_cisco_temperature_dom(parsed):
    parsed_dom = parsed.get('14', {})
    for item, attrs in parsed_dom.iteritems():
        if attrs.get('raw_dev_state') == '1':
            yield item, {}


def check_cisco_temperature_dom(item, params, parsed):
    # TODO perf, precision, severity, etc.
    parsed_dom = parsed.get('14', {})
    if item in parsed_dom:
        data = parsed_dom[item]
        reading = data['reading']
        state, state_readable = data['dev_state']
        yield state, 'Status: %s' % state_readable

        infotext = "%s dBm" % reading
        perfdata = ['power_level', reading]
        state = 0
        if data.get('dev_levels'):
            warn, crit = data['dev_levels']
            perfdata += [warn, crit]
            if reading >= crit:
                state = 2
            elif reading >= warn:
                state = 1
            if state:
                infotext += " (warn/crit at %s/%s dBm)" % (warn, crit)
        yield state, infotext, [perfdata]


check_info['cisco_temperature.dom'] = {
    "inventory_function" : inventory_cisco_temperature_dom,
    "check_function"     : check_cisco_temperature_dom,
    "service_description": "DOM %s",
    "has_perfdata"       : True,
    "includes"           : ['cisco_sensor_item.include'],
}
