From 6a214058491cb060c8f354054ac2b8dc9fc49b7c Mon Sep 17 00:00:00 2001
From: "Th.L" <thl-cmk@outlook.com>
Date: Thu, 18 Mar 2021 15:28:40 +0100
Subject: [PATCH] update project

---
 agent_based/cisco_asa_sensors.py | 256 +++++++++++++++++++++++++++++++
 cisco_asa_sensors.mkp            | Bin 0 -> 3935 bytes
 packages/cisco_asa_sensors       |  14 ++
 web/plugins/metrics/fan_speed.py |  36 +++++
 web/plugins/wato/fan_speed.py    | 153 ++++++++++++++++++
 5 files changed, 459 insertions(+)
 create mode 100644 agent_based/cisco_asa_sensors.py
 create mode 100644 cisco_asa_sensors.mkp
 create mode 100644 packages/cisco_asa_sensors
 create mode 100644 web/plugins/metrics/fan_speed.py
 create mode 100644 web/plugins/wato/fan_speed.py

diff --git a/agent_based/cisco_asa_sensors.py b/agent_based/cisco_asa_sensors.py
new file mode 100644
index 0000000..240f9e3
--- /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
GIT binary patch
literal 3935
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

literal 0
HcmV?d00001

diff --git a/packages/cisco_asa_sensors b/packages/cisco_asa_sensors
new file mode 100644
index 0000000..2a3a13a
--- /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 0000000..3180bcd
--- /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 0000000..b3ff082
--- /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
-- 
GitLab