From b603f24611d18ce81d009c9ce1d67163cd9ac973 Mon Sep 17 00:00:00 2001
From: "th.l" <thl-cmk@outlook.com>
Date: Wed, 15 Sep 2021 20:37:01 +0200
Subject: [PATCH] update project

---
 agent_based/ospf_neighbor.py         | 272 +++++++++++++++++++++++++++
 ospf_neighbor.mkp                    | Bin 7249 -> 7629 bytes
 packages/ospf_neighbor               |  28 ++-
 web/plugins/metrics/ospf_neighbor.py |  39 ++--
 web/plugins/wato/ospf_neighbor.py    | 205 ++++++++++----------
 5 files changed, 415 insertions(+), 129 deletions(-)
 create mode 100644 agent_based/ospf_neighbor.py

diff --git a/agent_based/ospf_neighbor.py b/agent_based/ospf_neighbor.py
new file mode 100644
index 0000000..0b1662a
--- /dev/null
+++ b/agent_based/ospf_neighbor.py
@@ -0,0 +1,272 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+#
+###############################################################################
+#    This program 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, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+###############################################################################
+
+###############################################################################
+# $Id: ospf_neighbor 288 2012-07-10 11:06:38Z twollner $
+# Descr: OSPF Neighbor State check_mk check
+# $Author: twollner $
+# $Date: 2012-07-10 13:06:38 +0200 (Tue, 10 Jul 2012) $
+# $Rev: 288 $
+###############################################################################
+# Author: Thomas Wollner (tw@wollner-net.de)
+###############################################################################
+#
+# changes by: thl-cmk[at]outlook[dot]com
+# url       : https://thl-cmk.hopto.org
+#
+#  2018-06-15: changed item from neighbor id to neighbor ip
+#              added events as perfdata (incl. metrics file)
+#              moved part of the output to long output
+#              a little code cleanup to better match coding guide lines
+#  2019-11-03: moved 'events' from infotext to longoutput
+#  2020-07-26: added parse section, alias, wato for alias and state
+#  2021-09-15: rewritten for CMK 2.0
+#
+###############################################################################
+
+# Example Agent Output:
+# OSPF-MIB
+
+# 1.3.6.1.2.1.14.10.1.1.172.20.2.214.0 = IpAddress: 172.20.2.214
+# 1.3.6.1.2.1.14.10.1.2.172.20.2.214.0 = INTEGER: 0
+# 1.3.6.1.2.1.14.10.1.3.172.20.2.214.0 = IpAddress: 192.168.1.2
+# 1.3.6.1.2.1.14.10.1.4.172.20.2.214.0 = INTEGER: 2
+# 1.3.6.1.2.1.14.10.1.5.172.20.2.214.0 = INTEGER: 1
+# 1.3.6.1.2.1.14.10.1.6.172.20.2.214.0 = INTEGER: 8
+# 1.3.6.1.2.1.14.10.1.7.172.20.2.214.0 = Counter32: 6
+# 1.3.6.1.2.1.14.10.1.8.172.20.2.214.0 = Gauge32: 0
+# 1.3.6.1.2.1.14.10.1.9.172.20.2.214.0 = INTEGER: 1
+# 1.3.6.1.2.1.14.10.1.10.172.20.2.214.0 = INTEGER: 1
+# 1.3.6.1.2.1.14.10.1.11.172.20.2.214.0 = INTEGER: 2
+#
+# sample parsed
+# {
+#  '172.17.108.52': {'helperage': '', 'prio': '1', 'permanence': 'dynamic', 'helperstatus': '', 'options': '2',
+#                    'state': '8', 'hellosup': 'false', 'helperexitreason': '', 'events': 6, 'rtrid': '10.250.128.130'},
+#  '172.17.108.60': {'helperage': '', 'prio': '1', 'permanence': 'dynamic', 'helperstatus': '', 'options': '2',
+#                    'state': '8', 'hellosup': 'false', 'helperexitreason': '', 'events': 6, 'rtrid': '10.253.128.101'},
+#  '172.17.108.58': {'helperage': '', 'prio': '1', 'permanence': 'dynamic', 'helperstatus': '', 'options': '2',
+#                    'state': '8', 'hellosup': 'false', 'helperexitreason': '', 'events': 12, 'rtrid': '172.17.0.2'},
+#  '172.17.108.49': {'helperage': '', 'prio': '1', 'permanence': 'dynamic', 'helperstatus': '', 'options': '2',
+#                    'state': '8', 'hellosup': 'false', 'helperexitreason': '', 'events': 9, 'rtrid': '172.17.0.2'}
+# }
+#
+
+from dataclasses import dataclass
+from typing import Dict
+
+from cmk.base.plugins.agent_based.agent_based_api.v1 import (
+    register,
+    Service,
+    Result,
+    State,
+    SNMPTree,
+    exists,
+    Metric,
+)
+from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
+    DiscoveryResult,
+    CheckResult,
+    StringTable,
+)
+
+
+@dataclass
+class OspfNeighbor:
+    rtrid: str
+    options: str
+    prio: str
+    state: str
+    events: int
+    permanence: str
+    hellosup: str
+    helperstatus: str
+    helperage: str
+    helperexitreason: str
+
+
+def parse_ospf_neighbor(string_table: StringTable) -> Dict[str, OspfNeighbor]:
+    def ospf_nbr_hellosuppressed(st: str) -> str:
+        names = {'1': 'true',
+                 '2': 'false'}
+        return names.get(st, st)
+
+    def ospf_nbr_permanence(st: str) -> str:
+        names = {'1': 'dynamic',
+                 '2': 'permanent'}
+        return names.get(st, st)
+
+    def ospf_nbr_helperstatus(st: str) -> str:
+        names = {'1': 'notHelping',
+                 '2': 'helping'}
+        return names.get(st, st)
+
+    def ospf_nbr_helperexitreason(st: str) -> str:
+        names = {'1': 'none',
+                 '2': 'inProgress',
+                 '3': 'completed',
+                 '4': 'timedOut',
+                 '5': 'topologyChanged'}
+        return names.get(st, st)
+
+    def ospf_nbr_options(st: str) -> str:
+        """
+        A bit mask corresponding to the neighbor's options field.
+        Bit 0, if set, indicates that the system will operate on Type of Service metrics other than TOS 0.
+               If zero, the neighbor will ignore all metrics except the TOS 0 metric.
+        Bit 1, if set, indicates that the associated area accepts and operates on external information;
+               if zero, it is a stub area.
+        Bit 2, if set, indicates that the system is capable of routing IP multicast datagrams, that is that it
+               implements the multicast extensions to OSPF.
+        Bit 3, if set, indicates that the associated area is an NSSA. These areas are capable of carrying type-7
+               external advertisements, which are translated into type-5 external advertisements at NSSA borders.
+        """
+        try:
+            st = ord(st)
+        except TypeError:
+            return 'unknown'
+
+        options = []
+        for key, value in [
+            (1, 'non TOS 0 service metrics accepted'),
+            (2, 'not a stub area'),
+            (4, 'IP multicast routing capable'),
+            (8, 'is NSSA'),
+        ]:
+            if st & key == key:
+                options.append(value)
+
+        options = ', '.join(options)
+        if options == '':
+            return 'unknown'
+        else:
+            return options
+
+    parsed = {}
+    for ip, rtrid, options, prio, state, events, permanence, hellosup, helperstatus, helperage, \
+        helperexitreason in string_table:
+        parsed[ip] = OspfNeighbor(
+            rtrid=rtrid,
+            options=ospf_nbr_options(options),
+            prio=prio,
+            state=state,
+            events=int(events),
+            permanence=ospf_nbr_permanence(str(permanence)),
+            hellosup=ospf_nbr_hellosuppressed(hellosup),
+            helperstatus=ospf_nbr_helperstatus(helperstatus),
+            helperage=helperage,
+            helperexitreason=ospf_nbr_helperexitreason(helperexitreason),
+        )
+    return parsed
+
+
+def discovery_ospf_neighbor(section: Dict[str, OspfNeighbor]) -> DiscoveryResult:
+    for neighbor in section.keys():
+        yield Service(item=neighbor)
+
+
+def check_ospf_neighbor(item, params, section: Dict[str, OspfNeighbor]) -> CheckResult:
+    def ospf_nbr_state(st):
+        names = {'1': 'down',
+                 '2': 'attempt',
+                 '3': 'init',
+                 '4': 'twoWay',
+                 '5': 'exchangeStart',
+                 '6': 'exchange',
+                 '7': 'loading',
+                 '8': 'full'}
+        return names.get(st, 'unknown: %s' % st)
+
+    # default checkmk states for ospfNbrState
+    neighborstate = {
+        '1': 2,  # down
+        '2': 1,  # attempt
+        '3': 1,  # init
+        '4': 0,  # twoWay
+        '5': 1,  # exchangeStart
+        '6': 1,  # exchange
+        '7': 1,  # loading
+        '8': 0,  # full
+    }
+
+    notFoundState = 2
+
+    for neighbour, neighbourAlias, neighbourNotFoundState in params.get('peer_list', []):
+        if item == neighbour:
+            yield Result(state=State.OK, summary=f'[{neighbourAlias}]')
+            notFoundState = neighbourNotFoundState
+
+    try:
+        neighbor = section[item]
+    except KeyError:
+        yield Result(state=State(notFoundState), notice='Item not found in SNMP data')
+        return 
+
+    yield Result(state=State.OK, summary=f'Neighbor ID: {neighbor.rtrid}')
+
+    neighborstate.update(params.get('neighborstate', neighborstate))  # update neighborstatus with params
+
+    yield Result(state=State(neighborstate.get(neighbor.state, 3)), summary=f'Status {ospf_nbr_state(neighbor.state)}')
+
+    yield Metric(value=neighbor.events, name='ospf_neighbor_ospf_events')
+
+    for text, value in [
+        ('options', neighbor.options),
+        ('priority', neighbor.prio),
+        ('permanence', neighbor.permanence),
+        ('hello suppressed', neighbor.hellosup),
+        ('helper status', neighbor.helperstatus),
+        ('helper age', neighbor.helperage),
+        ('helper exit reason', neighbor.helperexitreason),
+    ]:
+        if value != '':
+            yield Result(state=State.OK, notice=f'Neighbor {text}: {value}')
+
+
+register.snmp_section(
+    name='ospf_neighbor',
+    parse_function=parse_ospf_neighbor,
+    fetch=SNMPTree(
+        base='.1.3.6.1.2.1.14.10.1',  # OSPF-MIB::ospfNbrEntry
+        oids=[
+            '1',  # 'ospfNbrIpAddr'
+            '3',  # 'ospfNbrRtrId'
+            '4',  # 'ospfNbrOptions'
+            '5',  # 'ospfNbrPriority'
+            '6',  # 'ospfNbrState
+            '7',  # 'ospfNbrEvents'
+            '10',  # 'ospfNbrPermanence'
+            '11',  # 'ospfNbrHelloSuppressed'
+            '12',  # 'ospfNbrRestartHelperStatus'
+            '13',  # 'ospfNbrRestartHelperAge'
+            '14',  # 'ospfNbrRestartHelperExitReason'
+        ]
+    ),
+    detect=exists('.1.3.6.1.2.1.14.10.1.1.*')
+)
+
+register.check_plugin(
+    name='ospf_neighbor',
+    service_name='OSPF neighbor %s',
+    discovery_function=discovery_ospf_neighbor,
+    check_function=check_ospf_neighbor,
+    check_default_parameters={
+    },
+    check_ruleset_name='ospf_neighbor',
+)
diff --git a/ospf_neighbor.mkp b/ospf_neighbor.mkp
index 6c4dc70afda94f7814e4fc23f3cc38a047992696..f53c528d4152ff900f6478b5c36304833055d6d2 100644
GIT binary patch
literal 7629
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
O=HmVz8Nvtx!hZla!|jg%

literal 7249
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
A@&Et;

diff --git a/packages/ospf_neighbor b/packages/ospf_neighbor
index 8b478bd..b05a2c8 100644
--- a/packages/ospf_neighbor
+++ b/packages/ospf_neighbor
@@ -1,13 +1,23 @@
-{'author': u'Thomas Wollner',
- 'description': u'OSPF Neighborship State Check\nchanges by thl-cmk[at]outlook[dot]com]\n2018-06-15: changed item from neighbor id to neighbor address\n            added events as perfdata (incl. metrics file)\n            moved part of the output to long output\n2020-07-26: added parse section, alias, wato for alias and state\n',
- 'download_url': 'http://exchange.check-mk.org/',
- 'files': {'checkman': ['ospf_neighbor'],
-           'checks': ['ospf_neighbor'],
+{'author': 'Th.L. (thl-cmk[at]outlook[dot]com)',
+ 'description': 'based on OSPF Neighbor State Check by Thomas Wollner\n'
+                '\n'
+                'changes by thl-cmk[at]outlook[dot]com]\n'
+                '2018-06-15: changed item from neighbor id to neighbor '
+                'address\n'
+                '            added events as perfdata (incl. metrics file)\n'
+                '            moved part of the output to long output\n'
+                '2020-07-26: added parse section, alias, wato for alias and '
+                'state\n'
+                '2021-09-15: rewritten for CMK 2.0\n',
+ 'download_url': 'https://thl-cmk.hopto.org',
+ 'files': {'agent_based': ['ospf_neighbor.py'],
+           'checkman': ['ospf_neighbor'],
            'web': ['plugins/metrics/ospf_neighbor.py',
                    'plugins/wato/ospf_neighbor.py']},
  'name': 'ospf_neighbor',
  'num_files': 4,
- 'title': u'OSPF Neighbor State Check',
- 'version': '20200726.v1.3',
- 'version.min_required': '1.2.8b8',
- 'version.packaged': '1.4.0p38'}
\ No newline at end of file
+ 'title': 'OSPF Neighbor State Check',
+ 'version': '20210915.v1.4',
+ 'version.min_required': '2.0.0',
+ 'version.packaged': '2021.07.14',
+ 'version.usable_until': None}
\ No newline at end of file
diff --git a/web/plugins/metrics/ospf_neighbor.py b/web/plugins/metrics/ospf_neighbor.py
index b157f66..5ed4d45 100644
--- a/web/plugins/metrics/ospf_neighbor.py
+++ b/web/plugins/metrics/ospf_neighbor.py
@@ -1,18 +1,18 @@
-#!/usr/bin/python
-# -*- encoding: utf-8; py-indent-offset: 4 -*-
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
 #
 # OSPF neighbor metrics plugin
 #
 # Author: Th.L.
 # Date  : 2018-06-15
 #
+from cmk.gui.i18n import _
 
-#####################################################################################################################
-#
-# define units for OSPF neighbor perfdata
-#
-#####################################################################################################################
-
+from cmk.gui.plugins.metrics import (
+    metric_info,
+    graph_info,
+    perfometer_info
+)
 
 #####################################################################################################################
 #
@@ -26,29 +26,18 @@ metric_info['ospf_neighbor_ospf_events'] = {
     'color': '26/a',
 }
 
-
-######################################################################################################################
-#
-# map OSPF neighbor perfdata to metric
-#
-######################################################################################################################
-
-check_metrics['check_mk-ospf_neighbor'] = {
-    'ospf_events': {'name': 'ospf_neighbor_ospf_events', },
-}
-
 ######################################################################################################################
 #
 # how to graph perdata for OSPF neighbor
 #
 ######################################################################################################################
 
-graph_info.append({
+graph_info['ospf_neighbor_ospf_events'] = {
     'title': _('OSPF neighbor events'),
     'metrics': [
         ('ospf_neighbor_ospf_events', 'area'),
     ],
-})
+}
 
 ######################################################################################################################
 #
@@ -57,7 +46,7 @@ graph_info.append({
 ######################################################################################################################
 
 perfometer_info.append({
-        'type': 'linear',
-        'segments': ['ospf_neighbor_ospf_events'],
-        'total': 100,
-    })
+    'type': 'linear',
+    'segments': ['ospf_neighbor_ospf_events'],
+    'total': 100,
+})
diff --git a/web/plugins/wato/ospf_neighbor.py b/web/plugins/wato/ospf_neighbor.py
index 898e6c3..7b4122f 100644
--- a/web/plugins/wato/ospf_neighbor.py
+++ b/web/plugins/wato/ospf_neighbor.py
@@ -1,5 +1,5 @@
-#!/usr/bin/python
-# -*- encoding: utf-8; py-indent-offset: 4 -*-
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
 #
 # License: GNU General Public License v2
 #
@@ -10,98 +10,113 @@
 # wato plugin for ospf_neighbor check
 #
 #
+from cmk.gui.i18n import _
+from cmk.gui.valuespec import (
+    Dictionary,
+    TextAscii,
+    ListOf,
+    Tuple,
+    TextUnicode,
+    MonitoringState,
+    Transform,
+)
 
-register_check_parameters(
-    subgroup_networking,
-    'ospf_neighbor',
-    _('OSPF neighbor'),
-    Dictionary(
-        elements=[
-            ('neighborstate',
-             Dictionary(
-                 title=_('State to report for OSPF neighbor state'),
-                 elements=[
-                     ('1',
-                      MonitoringState(
-                          title=_('1 - down'),
-                          default_value=2,
-                      ),
-                      ),
-                     ('2',
-                      MonitoringState(
-                          title=_('2 - attempt'),
-                          default_value=1,
-                      ),
-                      ),
-                     ('3',
-                      MonitoringState(
-                          title=_('3 - init'),
-                          default_value=1,
-                      ),
-                      ),
-                     ('4',
-                      MonitoringState(
-                          title=_('4 - twoWay'),
-                          default_value=0,
-                      ),
-                      ),
-                     ('5',
-                      MonitoringState(
-                          title=_('5 - exchangeStart'),
-                          default_value=1,
-                      ),
-                      ),
-                     ('6',
-                      MonitoringState(
-                          title=_('6 - exchange'),
-                          default_value=1,
-                      ),
-                      ),
-                     ('7',
-                      MonitoringState(
-                          title=_('7 - loading'),
-                          default_value=1,
-                      ),
-                      ),
-                     ('8',
-                      MonitoringState(
-                          title=_('8 - full'),
-                          default_value=0,
-                      ),
-                      ),
-                 ]
-             )
-             ),
-            ('peer_list',
-             ListOf(
-                 Tuple(
-                     title=('OSPF Neighbors'),
+from cmk.gui.plugins.wato import (
+    CheckParameterRulespecWithItem,
+    rulespec_registry,
+    RulespecGroupCheckParametersNetworking,
+)
+
+
+def _parameter_valuespec_ospf_neighbor():
+    return Transform(
+        Dictionary(
+            elements=[
+                ('neighborstate',
+                 Dictionary(
+                     title=_('State to report for OSPF neighbor state'),
                      elements=[
-                         TextUnicode(
-                             title=_('OSPF Neighbor IP address'),
-                             help=_('The configured value must match a OSPF Neighbor item reported by the monitored '
-                                    'device. For example: "10.10.10.10"'),
-                             allow_empty=False,
-                         ),
-                         TextUnicode(
-                             title=_('OSPF Neighbor Alias'),
-                             help=_('You can configure an individual alias here for the OSPF Neighbor matching '
-                                    'the text configured in the "OSPF Neighbor IP address" field. The alias will '
-                                    'be shown in the infotext'),
-                             allow_empty=False,
-                         ),
-                         MonitoringState(
-                             default_value=2,
-                             title=_('State if not found'),
-                             help=_('You can configure an individual state if the OSPF Neighbor matching the text '
-                                    'configured in the "OSPF Neighbor IP address" field is not found')
-                         )]),
-                 add_label=_('Add OSPF Neighbor'),
-                 movable=False,
-                 title=_('OSPF Neighbor specific configuration'),
-             )),
-        ],
-    ),
-    TextAscii(title=_('OSPF Neighbor IP address')),
-    match_type='dict',
-)
\ No newline at end of file
+                         ('1',
+                          MonitoringState(
+                              title=_('1 - down'),
+                              default_value=2,
+                          )),
+                         ('2',
+                          MonitoringState(
+                              title=_('2 - attempt'),
+                              default_value=1,
+                          )),
+                         ('3',
+                          MonitoringState(
+                              title=_('3 - init'),
+                              default_value=1,
+                          )),
+                         ('4',
+                          MonitoringState(
+                              title=_('4 - twoWay'),
+                              default_value=0,
+                          )),
+                         ('5',
+                          MonitoringState(
+                              title=_('5 - exchangeStart'),
+                              default_value=1,
+                          )),
+                         ('6',
+                          MonitoringState(
+                              title=_('6 - exchange'),
+                              default_value=1,
+                          )),
+                         ('7',
+                          MonitoringState(
+                              title=_('7 - loading'),
+                              default_value=1,
+                          )),
+                         ('8',
+                          MonitoringState(
+                              title=_('8 - full'),
+                              default_value=0,
+                          )),
+                     ])
+                 ),
+                ('peer_list',
+                 ListOf(
+                     Tuple(
+                         title=('OSPF Neighbors'),
+                         elements=[
+                             TextUnicode(
+                                 title=_('OSPF Neighbor IP address'),
+                                 help=_(
+                                     'The configured value must match a OSPF Neighbor item reported by the monitored '
+                                     'device. For example: "10.10.10.10"'),
+                                 allow_empty=False,
+                             ),
+                             TextUnicode(
+                                 title=_('OSPF Neighbor Alias'),
+                                 help=_('You can configure an individual alias here for the OSPF Neighbor matching '
+                                        'the text configured in the "OSPF Neighbor IP address" field. The alias will '
+                                        'be shown in the infotext'),
+                                 allow_empty=False,
+                             ),
+                             MonitoringState(
+                                 default_value=2,
+                                 title=_('State if not found'),
+                                 help=_('You can configure an individual state if the OSPF Neighbor matching the text '
+                                        'configured in the "OSPF Neighbor IP address" field is not found')
+                             )]),
+                     add_label=_('Add OSPF Neighbor'),
+                     movable=False,
+                     title=_('OSPF Neighbor specific configuration'),
+                 )),
+            ],
+        ))
+
+
+rulespec_registry.register(
+    CheckParameterRulespecWithItem(
+        check_group_name='ospf_neighbor',
+        group=RulespecGroupCheckParametersNetworking,
+        item_spec=lambda: TextAscii(title=_('OSPF Neighbor IP address'), ),
+        match_type='dict',
+        parameter_valuespec=_parameter_valuespec_ospf_neighbor,
+        title=lambda: _('OSPF neighbor'),
+    ))
-- 
GitLab