diff --git a/agent_based/cisco_asa_sensors.py b/agent_based/cisco_asa_sensors.py new file mode 100644 index 0000000000000000000000000000000000000000..240f9e3d8bcc88af62c9cabfcff2082ccc5cdf7a --- /dev/null +++ b/agent_based/cisco_asa_sensors.py @@ -0,0 +1,256 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2021-03-18 +# +# Monitor Cisco ASA temperature sensors +# +# this check is based on the cmk 2.0 checkpoint_temp check +# +# 2021-02-25: rewrite for CMK 2.x +# +# sample snmpwalk +# +# sample section +# +# + +from typing import Mapping, Dict, List, Tuple, NamedTuple + +from .agent_based_api.v1.type_defs import ( + DiscoveryResult, + CheckResult, + StringTable, +) + +from .agent_based_api.v1 import ( + register, + Service, + equals, + Result, + check_levels, + State, + SNMPTree, + startswith, + all_of, + any_of, +) + + +# ################################################################################################## +# +# ASA TEMPERATURE BASE +# +# ################################################################################################## + + +class CiscoAsaSensor(NamedTuple): + value: float + status: int + state_readable: str + unit: str + + +def parse_cisco_asa_sensors(string_table: List[StringTable]) -> Dict: + def get_state_readable(st: str) -> str: + states = { + '1': 'Ok', + '2': 'unavailable', + '3': 'nonoperational', + } + return states.get(st, st) + + def get_sensor_status(st: str) -> State: + states = { + '1': State.OK, + '2': State.WARN, + '3': State.CRIT + } + return states.get(st, State.CRIT) + + sensors = { + 'fan': {}, + 'temp': {}, + 'power': {}, + } + + for sensorname, sensortype, sensorvalue, sensorstatus, sensorunits in string_table[0]: + if sensorstatus.isdigit(): + state_readable = get_state_readable(sensorstatus) + sensorstatus = get_sensor_status(sensorstatus) + + if sensortype == '8': # Temperature + sensorname = sensorname.replace('Temperature ', '') + + sensors['temp'].update({sensorname: CiscoAsaSensor( + value=to_celsius(float(sensorvalue), sensorunits), + unit=sensorunits, + status=sensorstatus, + state_readable=state_readable, + )}) + + if sensortype == '10': # Fan + sensorname = sensorname.replace('Fan ', '') + + sensors['fan'].update({sensorname: CiscoAsaSensor( + value=int(sensorvalue), + unit=sensorunits, + status=sensorstatus, + state_readable=state_readable, + )}) + + if sensortype == '12': # Power supply + sensorname = sensorname.replace('Power ', '') + + sensors['power'].update({sensorname: CiscoAsaSensor( + status=sensorstatus, + state_readable=state_readable, + )}) + + return sensors + + +register.snmp_section( + name='cisco_asa_sensors', + parse_function=parse_cisco_asa_sensors, + fetch=[ + SNMPTree( + base='.1.3.6.1.2.1', # + oids=[ + '47.1.1.1.1.7', # ENTITY-MIB::entPhysicalName + '99.1.1.1.1', # ENTITY-SENSOR-MIB::entPhySensorType + '99.1.1.1.4', # ENTITY-SENSOR-MIB::entPhySensorValue + '99.1.1.1.5', # ENTITY-SENSOR-MIB::entPhySensorOperStatus + '99.1.1.1.6', # ENTITY-SENSOR-MIB::entPhySensorUnitsDisplay + ] + ), + ], + detect=startswith('.1.3.6.1.2.1.1.1.0', 'cisco adaptive security appliance') +) + +# ################################################################################################## +# +# ASA SENSORS TEMPERATURE +# +# ################################################################################################## + +from .utils.temperature import ( + check_temperature, + TempParamType, + to_celsius, +) + + +def discovery_cisco_asa_temp(section: Dict) -> DiscoveryResult: + for key in section['temp']: + yield Service(item=key) # , parameters={'asgNetIfSpeed': section[item].asgNetIfSpeed}) + + +def check_cisco_asa_temp(item, params: TempParamType, section) -> CheckResult: + try: + sensor = section['temp'][item] + + yield Result(state=sensor.status, summary='Status: %s' % sensor.state_readable) + + yield from check_temperature( + sensor.value, + dev_unit=sensor.unit, + dev_status=sensor.status, + dev_status_name=sensor.state_readable, + params=params, + unique_name='check_cisco_asa_temp.%s' % item, + ) + except KeyError: + pass + + +register.check_plugin( + name='cisco_asa_temp', + service_name='Temperature %s', + sections=['cisco_asa_sensors'], + discovery_function=discovery_cisco_asa_temp, + check_function=check_cisco_asa_temp, + check_default_parameters={}, + check_ruleset_name='temperature' +) + + +# ################################################################################################## +# +# ASA SENSORS FAN +# +# ################################################################################################## + +def render_rpm(value) -> str: + return '%s RPM' % str(value) + + +def discovery_cisco_asa_fan(section: Dict) -> DiscoveryResult: + for key in section['fan']: + yield Service(item=key) # , parameters={'asgNetIfSpeed': section[item].asgNetIfSpeed}) + + +def check_cisco_asa_fan(item, params: TempParamType, section) -> CheckResult: + try: + sensor = section['fan'][item] + + yield Result(state=sensor.status, summary='Status: %s' % sensor.state_readable) + + yield from check_levels( + sensor.value, + label='Fan speed', + levels_lower=params.get('levels_lower', None), + levels_upper=params.get('levels_upper', None), + metric_name='fan_speed', + render_func=lambda v: render_rpm(v), + ) + + except KeyError: + pass + + +register.check_plugin( + name='cisco_asa_fan', + service_name='Fan %s', + sections=['cisco_asa_sensors'], + discovery_function=discovery_cisco_asa_fan, + check_function=check_cisco_asa_fan, + check_default_parameters={}, + check_ruleset_name='fan_speed' +) + + +# ################################################################################################## +# +# ASA SENSORS POWER SUPPLY +# +# ################################################################################################## + +def discovery_cisco_asa_power(section: Dict) -> DiscoveryResult: + for key in section['power']: + yield Service(item=key) # , parameters={'asgNetIfSpeed': section[item].asgNetIfSpeed}) + + +def check_cisco_asa_power(item, params: TempParamType, section) -> CheckResult: + try: + sensor = section['power'][item] + + yield Result(state=sensor.status, summary='Status: %s' % sensor.state_readable) + + except KeyError: + pass + + +register.check_plugin( + name='cisco_asa_power', + service_name='Power %s', + sections=['cisco_asa_sensors'], + discovery_function=discovery_cisco_asa_power, + check_function=check_cisco_asa_power, + check_default_parameters={}, + # check_ruleset_name='power_supply' +) \ No newline at end of file diff --git a/cisco_asa_sensors.mkp b/cisco_asa_sensors.mkp new file mode 100644 index 0000000000000000000000000000000000000000..d8eafac38fe111963df4503988fe0344c3373ea1 Binary files /dev/null and b/cisco_asa_sensors.mkp differ diff --git a/packages/cisco_asa_sensors b/packages/cisco_asa_sensors new file mode 100644 index 0000000000000000000000000000000000000000..2a3a13a04363e32c200cb7c8a3b2fdc4221ec4d8 --- /dev/null +++ b/packages/cisco_asa_sensors @@ -0,0 +1,14 @@ +{'author': 'thl-cmk[at]outlook[dot]com', + 'description': 'This plugin contains checks for Cisco ASA fan, temperature ' + 'and power supply\n', + 'download_url': 'https://thl-cmk.hopto.org', + 'files': {'agent_based': ['cisco_asa_sensors.py'], + 'web': ['plugins/metrics/fan_speed.py', + 'plugins/wato/fan_speed.py']}, + 'name': 'cisco_asa_sensors', + 'num_files': 3, + 'title': 'Cisco ASA Sensors', + 'version': '20210318_v0.0.1', + 'version.min_required': '2.0.0', + 'version.packaged': '2.0.0', + 'version.usable_until': None} \ No newline at end of file diff --git a/web/plugins/metrics/fan_speed.py b/web/plugins/metrics/fan_speed.py new file mode 100644 index 0000000000000000000000000000000000000000..3180bcddf8e9b9d6c46ebcde22937b62ae098f71 --- /dev/null +++ b/web/plugins/metrics/fan_speed.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# + +from cmk.gui.i18n import _ + +from cmk.gui.plugins.metrics import ( + metric_info, + graph_info, + perfometer_info +) + +metric_info["fan_speed"] = { + "title": _("Fan speed"), + "unit": "rpm", + "color": "16/a", +} + +graph_info['fan_speed'] = { + 'title': _('Fan speed'), + 'metrics': [ + ('fan_speed', 'area'), + ], + 'scalars': [ + ('fan_speed:crit', _('crit level')), + ('fan_speed:warn', _('warn level')), + ], + +} + +perfometer_info.append({ + 'type': 'logarithmic', + 'metric': 'fan_speed', + 'half_value': 7500.0, + 'exponent': 2, +}) \ No newline at end of file diff --git a/web/plugins/wato/fan_speed.py b/web/plugins/wato/fan_speed.py new file mode 100644 index 0000000000000000000000000000000000000000..b3ff0821b7624d0eb3f3d2853dd01b9d7b303ed4 --- /dev/null +++ b/web/plugins/wato/fan_speed.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# Copyright (C) 2019 tribe29 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. + +from cmk.gui.i18n import _ +from cmk.gui.valuespec import ( + Dictionary, + DropdownChoice, + Float, + Integer, + TextAscii, + Transform, + Tuple, +) +from cmk.gui.plugins.wato import ( + RulespecGroupCheckParametersEnvironment, + CheckParameterRulespecWithItem, + rulespec_registry, +) + + +# New fan speed rule for modern fan checks that have the +# sensor type (e.g. 'CPU', 'Chassis', etc.) as the beginning of their +# item (e.g. 'CPU 1', 'Chassis 17/11'). This will replace all other +# fan speed rulesets in future. Note: those few fan speed checks +# that do *not* use an item, need to be converted to use one single +# item (other than None). +def _parameter_valuespec_temperature(): + return Transform( + Dictionary(elements=[ + ( + 'levels', + Transform( + Tuple( + title=_('Upper fan speed Levels'), + elements=[ + Float(title=_('Warning at'), unit=u'RPM'), + Float(title=_('Critical at'), unit=u'RPM'), + ], + ), + forth=lambda elems: (float(elems[0]), float(elems[1])), + ), + ), + ( + 'levels_lower', + Transform( + Tuple( + title=_('Lower fan speed Levels'), + elements=[ + Float(title=_('Warning below'), unit=u'RPM'), + Float(title=_('Critical below'), unit=u'RPM'), + ], + ), + forth=lambda elems: (float(elems[0]), float(elems[1])), + ), + ), + + # ('device_levels_handling', + # DropdownChoice( + # title=_('Interpretation of the device's own temperature status'), + # choices=[ + # ('usr', _('Ignore device's own levels')), + # ('dev', _('Only use device's levels, ignore yours')), + # ('best', _('Use least critical of your and device's levels')), + # ('worst', _('Use most critical of your and device's levels')), + # ('devdefault', _('Use device's levels if present, otherwise yours')), + # ('usrdefault', _('Use your own levels if present, otherwise the device's')), + # ], + # default_value='usrdefault', + # )), + # ( + # 'trend_compute', + # Dictionary( + # title=_('Trend computation'), + # elements=[ + # ('period', + # Integer( + # title=_('Observation period for fan speed trend computation'), + # default_value=30, + # minvalue=5, + # unit=_('minutes'), + # )), + # ('trend_levels', + # Tuple( + # title=_('Levels on fan speed increase per period'), + # elements=[ + # Integer( + # title=_('Warning at'), + # unit=u'RPM / ' + _('period'), + # default_value=5, + # ), + # Integer( + # title=_('Critical at'), + # unit=u'RPM / ' + _('period'), + # default_value=10, + # ) + # ], + # )), + # ('trend_levels_lower', + # Tuple( + # title=_('Levels on fan speed decrease per period'), + # elements=[ + # Integer( + # title=_('Warning at'), + # unit=u'RPM / ' + _('period'), + # default_value=5, + # ), + # Integer( + # title=_('Critical at'), + # unit=u'RPM / ' + _('period'), + # default_value=10, + # ) + # ], + # )), + # ('trend_timeleft', + # Tuple( + # title= + # _('Levels on the time left until a critical fann speed (upper or lower) is reached' + # ), + # elements=[ + # Integer( + # title=_('Warning if below'), + # unit=_('minutes'), + # default_value=240, + # ), + # Integer( + # title=_('Critical if below'), + # unit=_('minutes'), + # default_value=120, + # ), + # ], + # )) + # ], + # optional_keys=['trend_levels', 'trend_levels_lower', 'trend_timeleft'], + # ), + # ), + ],), + forth=lambda v: isinstance(v, tuple) and {'levels': v} or v, + ) + + +rulespec_registry.register( + CheckParameterRulespecWithItem( + check_group_name='fan_speed', + group=RulespecGroupCheckParametersEnvironment, + item_spec=lambda: TextAscii(title=_('Sensor ID'), + help=_('The identifier of the fan speed sensor.')), + match_type='dict', + parameter_valuespec=_parameter_valuespec_temperature, + title=lambda: _('Fan speed'), + )) \ No newline at end of file