Vertiv/Geist Watchdog Check

CMK version: 2.2.0p6
OS version: Oracle Linux 9.2

Hello, I am trying to monitor a Watchdog device that has external/remote sensors using Check_MK. I have been trying to modify the existing check to allow the external sensors to be detected, and was able to do so, but I’m not able to make all sensors be detected at the same time. Could anyone assist with this?

Here is the original (slightly modified to format the information better for us) check:

#!/usr/bin/env python3
# Copyright (C) 2019 Checkmk GmbH - License: GNU General Public License v2
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
# conditions defined in the file COPYING, which is part of this source code package.

# NOTE: Careful when replacing the *-import below with a more specific import. This can cause
# problems because it might remove variables needed for accessing discovery rulesets.
from cmk.base.check_legacy_includes.temperature import *  # pylint: disable=wildcard-import,unused-wildcard-import

# very odd and confusing example outputs:

# version 3.0.0
# .1.3.6.1.4.1.21239.5.1.1.2.0 3.0.0
# .1.3.6.1.4.1.21239.5.1.1.7.0 1
# .1.3.6.1.4.1.21239.5.1.2.1.3.1 Data Center 1
# .1.3.6.1.4.1.21239.5.1.2.1.5.1 1
# .1.3.6.1.4.1.21239.5.1.2.1.4.1 "91 54 06 9D C9 54 06 9D E9 C9 06 9D BD 9B 06 9D "
# .1.3.6.1.4.1.21239.5.1.2.1.6.1 199
# .1.3.6.1.4.1.21239.5.1.2.1.7.1 36
# .1.3.6.1.4.1.21239.5.1.2.1.8.1 44
#
# version 3.2.0
# .1.3.6.1.4.1.21239.5.1.1.2.0 3.2.0
# .1.3.6.1.4.1.21239.5.1.1.7.0 1
# .1.3.6.1.4.1.21239.5.1.2.1.1.1 1
# .1.3.6.1.4.1.21239.5.1.2.1.2.1 41D88039003580C3
# .1.3.6.1.4.1.21239.5.1.2.1.3.1 RSGLDN Watchdog 15
# .1.3.6.1.4.1.21239.5.1.2.1.4.1 1
# .1.3.6.1.4.1.21239.5.1.2.1.5.1 173
# .1.3.6.1.4.1.21239.5.1.2.1.6.1 46
# .1.3.6.1.4.1.21239.5.1.2.1.7.1 56


def _translate_availability(availability):
    return {
        "0": (2, "Missing"),
        "1": (0, "Present"),
        "2": (1, "Partially Unavailable"),
    }[availability]


def _parse_legacy_line(line, temp_unit):
    """
    >>> [i for i in _parse_legacy_line(['1', 'blah', '2CD', '1', '30', '20', '8'], 'C')]
    [('general', {'Watchdog 1': {'descr': 'blah', 'availability': (0, 'available')}}), ('temp', {'Temperature 1': ('30', 'C')}), ('humidity', {'Humidity 1': '20'}), ('dew', {'Dew point 1': ('8', 'C')})]
    """
    sensor_id = line[0]
    yield "general", {
        "Sensor %s"
        % sensor_id: {"descr": line[1], "availability": _translate_availability(line[3])},
    }
    yield "temp", {"Temperature %s" % sensor_id: (line[4], temp_unit)}
    yield "humidity", {"Humidity %s" % sensor_id: line[5]}
    yield "dew", {"Dew point %s" % sensor_id: (line[6], temp_unit)}


def _parse_line(line, temp_unit):
    """
    >>> [i for i in _parse_line(['1', 'blah', '1', '30', '20', '8'], 'C')]
    [('general', {'Watchdog 1': {'descr': 'blah', 'availability': (0, 'available')}}), ('temp', {'Temperature 1': ('30', 'C')}), ('humidity', {'Humidity 1': '20'}), ('dew', {'Dew point 1': ('8', 'C')})]
    """
    sensor_id = line[1]
    yield "general", {
        "%s Sensor"
        % sensor_id: {"descr": line[1], "availability": _translate_availability(line[2])},
    }
    yield "temp", {"%s Temperature" % sensor_id: (line[3], temp_unit)}
    yield "humidity", {"%s Humidity" % sensor_id: line[4]}
    yield "dew", {"%s Dew Point" % sensor_id: (line[5], temp_unit)}


def parse_watchdog_sensors(info):

    parsed = {}

    general, data = info
    if not general:
        return parsed

    temp_unit = {
        "1": "C",
        "0": "F",
        "": "C",
    }[general[0][1]]

    version = int(general[0][0].replace(".", ""))

    if version <= 300:
        line_parser = _parse_legacy_line
    else:
        line_parser = _parse_line

    for line in data:
        for sensor_type, parsed_line in line_parser(line, temp_unit):
            parsed.setdefault(sensor_type, {}).update(parsed_line)

    return parsed


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


def inventory_watchdog_sensors(parsed):
    for key in parsed.get("general", {}):
        yield (key, {})


def check_watchdog_sensors(item, params, parsed):

    data = parsed.get("general", {}).get(item)

    if not data:
        return

    descr = data["descr"]
    state, state_readable = data["availability"]

    yield state, state_readable

    if not descr == "":
        yield 0, "Location: %s" % descr


check_info["watchdog_sensors"] = {
    "parse_function": parse_watchdog_sensors,
    "inventory_function": inventory_watchdog_sensors,
    "check_function": check_watchdog_sensors,
    "service_description": "%s",
    "snmp_info": [
        (
            ".1.3.6.1.4.1.21239.5.1.1",
            [
                "2",  # GEIST-V4-MIB::productVersion
                "7",  # GEIST-V4-MIB::temperatureUnits
            ],
        ),
        (
            ".1.3.6.1.4.1.21239.5.1",
            [
                OID_END,
                "2.1.3",  # GEIST-V4-MIB::internalName
                "2.1.4",  # GEIST-V4-MIB::internalLabel    but internalAvail    if version 3.2.0
                "2.1.5",  # GEIST-V4-MIB::internalAvail    but internalTemp     if version 3.2.0
                "2.1.6",  # GEIST-V4-MIB::internalTemp     but internalHumidity if version 3.2.0
                "2.1.7",  # GEIST-V4-MIB::internalHumidity but internalDewPoint if version 3.2.0
                "2.1.8",  # GEIST-V4-MIB::internalDewPoint but empty            if version 3.2.0
            ],
        ),
    ],
    "snmp_scan_function": lambda oid: oid(".1.3.6.1.2.1.1.2.0").startswith(".1.3.6.1.4.1.21239.5.1")
    or oid(".1.3.6.1.2.1.1.2.0").startswith(".1.3.6.1.4.1.21239.42.1"),  # firmware upgrade
}

# .
#   .--temp----------------------------------------------------------------.
#   |                       _                                              |
#   |                      | |_ ___ _ __ ___  _ __                         |
#   |                      | __/ _ \ '_ ` _ \| '_ \                        |
#   |                      | ||  __/ | | | | | |_) |                       |
#   |                       \__\___|_| |_| |_| .__/                        |
#   |                                        |_|                           |
#   +----------------------------------------------------------------------+
#   |                                                                      |
#   '----------------------------------------------------------------------'


def inventory_watchdog_sensors_temp(parsed):
    for key in parsed.get("temp", {}):
        yield (key, {})


def check_watchdog_sensors_temp(item, params, parsed):

    data = parsed.get("temp", {}).get(item)

    if not data:
        return None

    temperature_str, unit = data
    return check_temperature(
        float(temperature_str) / 10.0,
        params,
        "check_watchdog_sensors.%s" % item,
        dev_unit=unit.lower(),
    )


check_info["watchdog_sensors.temp"] = {
    "inventory_function": inventory_watchdog_sensors_temp,
    "check_function": check_watchdog_sensors_temp,
    "service_description": "%s ",
    "has_perfdata": True,
    "group": "temperature",
}

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

factory_settings["watchdog_sensors_humidity_default_levels"] = {
    "levels": (50, 55),
    "levels_lower": (10, 15),
}


def inventory_watchdog_sensors_humidity(parsed):
    for key in parsed.get("humidity", {}):
        yield (key, {})


def check_watchdog_sensors_humidity(item, params, parsed):

    data = parsed.get("humidity", {}).get(item)

    if not data:
        return

    humidity = int(data)
    warn, crit = params["levels"]
    warn_lower, crit_lower = params["levels_lower"]

    yield 0, "%.1f%%" % humidity, [("humidity", humidity, warn, crit)]

    if humidity >= crit:
        yield 2, "warn/crit at %s/%s" % (warn, crit)
    elif humidity <= crit_lower:
        yield 2, "warn/crit at %s/%s" % (warn, crit)
    elif humidity >= warn:
        yield 1, "warn/crit below %s/%s" % (warn, crit)
    elif humidity <= warn_lower:
        yield 1, "warn/crit below %s/%s" % (warn, crit)


check_info["watchdog_sensors.humidity"] = {
    "inventory_function": inventory_watchdog_sensors_humidity,
    "check_function": check_watchdog_sensors_humidity,
    "service_description": "%s",
    "has_perfdata": True,
    "group": "humidity",
    "default_levels_variable": "watchdog_sensors_humidity_default_levels",
}

# .
#   .--dew-----------------------------------------------------------------.
#   |                             _                                        |
#   |                          __| | _____      __                         |
#   |                         / _` |/ _ \ \ /\ / /                         |
#   |                        | (_| |  __/\ V  V /                          |
#   |                         \__,_|\___| \_/\_/                           |
#   |                                                                      |
#   +----------------------------------------------------------------------+
#   |                                                                      |
#   '----------------------------------------------------------------------'


def inventory_watchdog_sensors_dew(parsed):
    for key in parsed.get("dew", {}):
        yield (key, {})


def check_watchdog_sensors_dew(item, params, parsed):

    data = parsed.get("dew", {}).get(item)

    if not data:
        return

    dew = float(data[0]) / 10.0
    unit = data[1]
    if unit == "F":
        dew = 5.0 / 9.0 * (dew - 32)
    yield check_temperature(dew, params, "check_watchdog_sensors.%s" % item)


check_info["watchdog_sensors.dew"] = {
    "inventory_function": inventory_watchdog_sensors_dew,
    "check_function": check_watchdog_sensors_dew,
    "service_description": "%s",
    "has_perfdata": True,
    "group": "temperature",
}

Here is the modified version that gives results for the external sensors but not the onboard ones (note the different OIDs):

#!/usr/bin/env python3
# Copyright (C) 2019 Checkmk GmbH - License: GNU General Public License v2
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
# conditions defined in the file COPYING, which is part of this source code package.

# NOTE: Careful when replacing the *-import below with a more specific import. This can cause
# problems because it might remove variables needed for accessing discovery rulesets.
from cmk.base.check_legacy_includes.temperature import *  # pylint: disable=wildcard-import,unused-wildcard-import

# very odd and confusing example outputs:

# version 3.0.0
# .1.3.6.1.4.1.21239.5.1.1.2.0 3.0.0
# .1.3.6.1.4.1.21239.5.1.1.7.0 1
# .1.3.6.1.4.1.21239.5.1.2.1.3.1 Data Center 1
# .1.3.6.1.4.1.21239.5.1.2.1.5.1 1
# .1.3.6.1.4.1.21239.5.1.2.1.4.1 "91 54 06 9D C9 54 06 9D E9 C9 06 9D BD 9B 06 9D "
# .1.3.6.1.4.1.21239.5.1.2.1.6.1 199
# .1.3.6.1.4.1.21239.5.1.2.1.7.1 36
# .1.3.6.1.4.1.21239.5.1.2.1.8.1 44
#
# version 3.2.0
# .1.3.6.1.4.1.21239.5.1.1.2.0 3.2.0
# .1.3.6.1.4.1.21239.5.1.1.7.0 1
# .1.3.6.1.4.1.21239.5.1.2.1.1.1 1
# .1.3.6.1.4.1.21239.5.1.2.1.2.1 41D88039003580C3
# .1.3.6.1.4.1.21239.5.1.2.1.3.1 RSGLDN Watchdog 15
# .1.3.6.1.4.1.21239.5.1.2.1.4.1 1
# .1.3.6.1.4.1.21239.5.1.2.1.5.1 173
# .1.3.6.1.4.1.21239.5.1.2.1.6.1 46
# .1.3.6.1.4.1.21239.5.1.2.1.7.1 56


def _translate_availability(availability):
    return {
        "0": (2, "Missing"),
        "1": (0, "Present"),
        "2": (1, "Partially Unavailable"),
    }[availability]


def _parse_legacy_line(line, temp_unit):
    """
    >>> [i for i in _parse_legacy_line(['1', 'blah', '2CD', '1', '30', '20', '8'], 'C')]
    [('general', {'Watchdog 1': {'descr': 'blah', 'availability': (0, 'available')}}), ('temp', {'Temperature 1': ('30', 'C')}), ('humidity', {'Humidity 1': '20'}), ('dew', {'Dew point 1': ('8', 'C')})]
    """
    sensor_id = line[0]
    yield "general", {
        "Sensor %s"
        % sensor_id: {"descr": line[1], "availability": _translate_availability(line[3])},
    }
    yield "temp", {"Temperature %s" % sensor_id: (line[4], temp_unit)}
#    yield "humidity", {"Humidity %s" % sensor_id: line[5]}
#    yield "dew", {"Dew point %s" % sensor_id: (line[6], temp_unit)}


def _parse_line(line, temp_unit):
    """
    >>> [i for i in _parse_line(['1', 'blah', '1', '30', '20', '8'], 'C')]
    [('general', {'Watchdog 1': {'descr': 'blah', 'availability': (0, 'available')}}), ('temp', {'Temperature 1': ('30', 'C')}), ('humidity', {'Humidity 1': '20'}), ('dew', {'Dew point 1': ('8', 'C')})]
    """
    sensor_id = line[1]
    yield "general", {
        "%s Sensor"
        % sensor_id: {"descr": line[1], "availability": _translate_availability(line[2])},
    }
    yield "temp", {"%s Temperature" % sensor_id: (line[3], temp_unit)}
#    yield "humidity", {"%s Humidity" % sensor_id: line[4]}
#    yield "dew", {"%s Dew Point" % sensor_id: (line[5], temp_unit)}


def parse_watchdog_sensors(info):

    parsed = {}

    general, data = info
    if not general:
        return parsed

    temp_unit = {
        "1": "C",
        "0": "F",
        "": "C",
    }[general[0][1]]

    version = int(general[0][0].replace(".", ""))

    if version <= 300:
        line_parser = _parse_legacy_line
    else:
        line_parser = _parse_line

    for line in data:
        for sensor_type, parsed_line in line_parser(line, temp_unit):
            parsed.setdefault(sensor_type, {}).update(parsed_line)

    return parsed


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


def inventory_watchdog_sensors(parsed):
    for key in parsed.get("general", {}):
        yield (key, {})


def check_watchdog_sensors(item, params, parsed):

    data = parsed.get("general", {}).get(item)

    if not data:
        return

    descr = data["descr"]
    state, state_readable = data["availability"]

    yield state, state_readable

    if not descr == "":
        yield 0, "Location: %s" % descr


check_info["watchdog_sensors"] = {
    "parse_function": parse_watchdog_sensors,
    "inventory_function": inventory_watchdog_sensors,
    "check_function": check_watchdog_sensors,
    "service_description": "%s",
    "snmp_info": [
        (
            ".1.3.6.1.4.1.21239.5.1.1",
            [
                "2",  # GEIST-V4-MIB::productVersion
                "7",  # GEIST-V4-MIB::temperatureUnits
            ],
        ),
        (
            ".1.3.6.1.4.1.21239.5.1",
            [
                OID_END,
                "4.1.3",  # GEIST-V4-MIB::tempSensorLabel
                "4.1.4",  # GEIST-V4-MIB::tempSensorAvail
                "4.1.5",  # GEIST-V4-MIB::tempSensorTemp
#                "4.1.6",  # GEIST-V4-MIB::internalTemp     but internalHumidity if version 3.2.0
#                "4.1.7",  # GEIST-V4-MIB::internalHumidity but internalDewPoint if version 3.2.0
#                "4.1.8",  # GEIST-V4-MIB::internalDewPoint but empty            if version 3.2.0
            ],
        ),
    ],
    "snmp_scan_function": lambda oid: oid(".1.3.6.1.2.1.1.2.0").startswith(".1.3.6.1.4.1.21239.5.1")
    or oid(".1.3.6.1.2.1.1.2.0").startswith(".1.3.6.1.4.1.21239.42.1"),  # firmware upgrade
}

# .
#   .--temp----------------------------------------------------------------.
#   |                       _                                              |
#   |                      | |_ ___ _ __ ___  _ __                         |
#   |                      | __/ _ \ '_ ` _ \| '_ \                        |
#   |                      | ||  __/ | | | | | |_) |                       |
#   |                       \__\___|_| |_| |_| .__/                        |
#   |                                        |_|                           |
#   +----------------------------------------------------------------------+
#   |                                                                      |
#   '----------------------------------------------------------------------'


def inventory_watchdog_sensors_temp(parsed):
    for key in parsed.get("temp", {}):
        yield (key, {})


def check_watchdog_sensors_temp(item, params, parsed):

    data = parsed.get("temp", {}).get(item)

    if not data:
        return None

    temperature_str, unit = data
    return check_temperature(
        float(temperature_str) / 10.0,
        params,
        "check_watchdog_sensors.%s" % item,
        dev_unit=unit.lower(),
    )


check_info["watchdog_sensors.temp"] = {
    "inventory_function": inventory_watchdog_sensors_temp,
    "check_function": check_watchdog_sensors_temp,
    "service_description": "%s ",
    "has_perfdata": True,
    "group": "temperature",
}

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

factory_settings["watchdog_sensors_humidity_default_levels"] = {
    "levels": (50, 55),
    "levels_lower": (10, 15),
}


def inventory_watchdog_sensors_humidity(parsed):
    for key in parsed.get("humidity", {}):
        yield (key, {})


def check_watchdog_sensors_humidity(item, params, parsed):

    data = parsed.get("humidity", {}).get(item)

    if not data:
        return

    humidity = int(data)
    warn, crit = params["levels"]
    warn_lower, crit_lower = params["levels_lower"]

    yield 0, "%.1f%%" % humidity, [("humidity", humidity, warn, crit)]

    if humidity >= crit:
        yield 2, "warn/crit at %s/%s" % (warn, crit)
    elif humidity <= crit_lower:
        yield 2, "warn/crit at %s/%s" % (warn, crit)
    elif humidity >= warn:
        yield 1, "warn/crit below %s/%s" % (warn, crit)
    elif humidity <= warn_lower:
        yield 1, "warn/crit below %s/%s" % (warn, crit)


check_info["watchdog_sensors.humidity"] = {
    "inventory_function": inventory_watchdog_sensors_humidity,
    "check_function": check_watchdog_sensors_humidity,
    "service_description": "%s",
    "has_perfdata": True,
    "group": "humidity",
    "default_levels_variable": "watchdog_sensors_humidity_default_levels",
}

# .
#   .--dew-----------------------------------------------------------------.
#   |                             _                                        |
#   |                          __| | _____      __                         |
#   |                         / _` |/ _ \ \ /\ / /                         |
#   |                        | (_| |  __/\ V  V /                          |
#   |                         \__,_|\___| \_/\_/                           |
#   |                                                                      |
#   +----------------------------------------------------------------------+
#   |                                                                      |
#   '----------------------------------------------------------------------'


def inventory_watchdog_sensors_dew(parsed):
    for key in parsed.get("dew", {}):
        yield (key, {})


def check_watchdog_sensors_dew(item, params, parsed):

    data = parsed.get("dew", {}).get(item)

    if not data:
        return

    dew = float(data[0]) / 10.0
    unit = data[1]
    if unit == "F":
        dew = 5.0 / 9.0 * (dew - 32)
    yield check_temperature(dew, params, "check_watchdog_sensors.%s" % item)


check_info["watchdog_sensors.dew"] = {
    "inventory_function": inventory_watchdog_sensors_dew,
    "check_function": check_watchdog_sensors_dew,
    "service_description": "%s",
    "has_perfdata": True,
    "group": "temperature",
}

Any help is much appreciated!

To say anything a SNMPwalk of the device is needed.
Or better said the part of the snmp data that is used by the check.

If i look at the MIB file for this device then i see that an extra table exists for internal sensors.
→ .1.3.6.1.4.1.21239.5.1.2.1.5 (Internal Temp) and .1.3.6.1.4.1.21239.5.1.2.1.6 (Internal Humidity)
The original check only used this internal table.
I would recommend to use the original check and make one extra check that fetches the external sensors only.

That was my goal at one point, I just couldn’t figure out exactly how to accomplish it. I put my custom check in and the original check no longer runs. How do you go about having multiple checks on one host?

Also, when I was trying to make an additional check, I had trouble trying to get the check to use the standard “temperature” WATO settings page that the other temperature related checks do (picture attached). I just get a “Could not detect type of manpage: watchdog_sensors_temp_ext. Maybe the check is missing” what is the way to get a check to use the built-in temperature settings page? I read something about the check just needing to use the check_temperature() function which mine did.


image

Disregard, I figured it out! The name I had in the check_info[] section was not matching what I had in checkman.

Hi, Im wondering if you would be able to share exactly how you made this work? I am also trying to get other sensors read and have no idea where to start