From e033d4b7263d022fe3aa3ed66ca80ae509663dd3 Mon Sep 17 00:00:00 2001
From: "th.l" <thl-cmk@outlook.com>
Date: Sat, 18 Sep 2021 20:50:59 +0200
Subject: [PATCH] update project

---
 agent_based/ospfv3_area.py        |  77 ++------
 agent_based/ospfv3_general.py     |  92 ++-------
 agent_based/ospfv3_interface.py   |  72 ++-----
 agent_based/ospfv3_neighbor.py    | 113 ++---------
 agent_based/ospfv3_virtuallink.py | 130 +++----------
 agent_based/utils/ospfv3.py       | 306 ++++++++++++++++++++++++++++++
 ospfv3.mkp                        | Bin 9372 -> 9626 bytes
 packages/ospfv3                   |   5 +-
 web/plugins/metrics/ospfv3.py     |   2 +-
 9 files changed, 399 insertions(+), 398 deletions(-)
 create mode 100644 agent_based/utils/ospfv3.py

diff --git a/agent_based/ospfv3_area.py b/agent_based/ospfv3_area.py
index bbf909a..fc87fa8 100644
--- a/agent_based/ospfv3_area.py
+++ b/agent_based/ospfv3_area.py
@@ -43,7 +43,7 @@
 # .1.3.6.1.2.1.191.1.2.1.16.0 = INTEGER: 2
 # .1.3.6.1.2.1.191.1.2.1.16.3 = INTEGER: 2
 #
-# sample info
+# sample string_table
 # [
 #  ['0', '1', '17', '4', '0', '18', '646544', '2', '1', '2', '3', '0', '0', '1', '2'],
 #  ['1', '1', '3', '1', '0', '7', '317410', '2', '1', '2', '3', '0', '0', '1', '2']
@@ -69,6 +69,16 @@ from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
     StringTable,
 )
 
+from cmk.base.plugins.agent_based.utils.ospfv3 import (
+    ospf_area_impotasextern,
+    ospf_area_summary,
+    ospf_area_translatorrole,
+    ospf_area_translatorrolestate,
+    ospf_area_submetrictype,
+    ospf_area_teeenabled,
+    get_area_type
+)
+
 
 @dataclass
 class OspfV3Area:
@@ -90,61 +100,6 @@ class OspfV3Area:
 
 
 def parse_ospfv3_area(string_table: StringTable) -> Dict[str, OspfV3Area]:
-    def ospf_area_impotasextern(st: str) -> str:
-        names = {'1': 'import External',
-                 '2': 'import no External',
-                 '3': 'import Nssa',
-                 }
-        return names.get(st, st)
-
-    def ospf_area_summary(st: str) -> str:
-        names = {'1': 'no Area Summary',
-                 '2': 'send Area Summary'}
-        return names.get(st, st)
-
-    def ospf_area_translatorrole(st: str) -> str:
-        names = {
-            '1': 'always',
-            '2': 'candidate',
-        }
-        return names.get(st, st)
-
-    def ospf_area_translatorrolestate(st: str) -> str:
-        names = {
-            '1': 'enabled',
-            '2': 'elected',
-            '3': 'disabled',
-        }
-        return names.get(st, st)
-
-    def ospf_area_submetrictype(st: str) -> str:
-        names = {
-            '1': 'ospfv3 Metric',
-            '2': 'comparable Cost',
-            '3': 'non Comparable',
-        }
-        return names.get(st, st)
-
-    def ospf_area_teeenabled(st: str) -> str:
-        names = {
-            '1': 'enabled',
-        }
-        return names.get(st, 'disabled')
-
-    def get_area_type(AreaImportAsExtern: str, AreaSummary: str) -> str:
-        areatype = 'UNKNOWN'
-        if int(AreaImportAsExtern) == 1 and int(AreaSummary) == 2:
-            areatype = 'normal'
-        if int(AreaImportAsExtern) == 2 and int(AreaSummary) == 1:
-            areatype = 'totaly subby'
-        if int(AreaImportAsExtern) == 2 and int(AreaSummary) == 2:
-            areatype = 'stubby'
-        if int(AreaImportAsExtern) == 3 and int(AreaSummary) == 2:
-            areatype = 'NSSA'
-        if int(AreaImportAsExtern) == 3 and int(AreaSummary) == 1:
-            areatype = 'totaly NSSA'
-        return areatype
-
     areas = {}
     for area in string_table:
         AreaId, AreaImportAsExtern, AreaSpfRuns, AreaBdrRtrCount, AreaAsBdrRtrCount, AreaScopeLsaCount, \
@@ -194,12 +149,10 @@ def check_ospfv3_area(item, params, section: Dict[str, OspfV3Area]) -> CheckResu
             value=value,
             label=label,
             metric_name=f'ospfv3_area_{metric}',
+            render_func=lambda v: f'{v:.0f}'
         )
-    yield Result(state=State.OK, summary=f'Area type: {area.AreaType}')
-    yield Result(state=State.OK, summary=f'area scope LSA checksum: {area.AreaScopeLsaCksumSum}')
-
-    yield Metric(name='ospfv3_area_nssatranslatorevents', value=area.AreaNssaTranslatorEvents)
-
+    yield Result(state=State.OK, notice=f'Area type: {area.AreaType}')
+    yield Result(state=State.OK, notice=f'Area scope LSA checksum: {area.AreaScopeLsaCksumSum}')
     yield Result(state=State.OK, notice=f'Area import as extern: {area.AreaImportAsExtern}')
     yield Result(state=State.OK, notice=f'Area summary: {area.AreaSummary}')
     yield Result(state=State.OK, notice=f'Area stub metric: {area.AreaStubMetric}')
@@ -209,6 +162,8 @@ def check_ospfv3_area(item, params, section: Dict[str, OspfV3Area]) -> CheckResu
     yield Result(state=State.OK, notice=f'Area NSSA stub metric type: {area.AreaStubMetricType}')
     yield Result(state=State.OK, notice=f'Area Traffic engineering  {area.AreaTEEnabled}')
 
+    yield Metric(name='ospfv3_area_nssatranslatorevents', value=area.AreaNssaTranslatorEvents)
+
 
 register.snmp_section(
     name='ospfv3_area',
diff --git a/agent_based/ospfv3_general.py b/agent_based/ospfv3_general.py
index 7e0594b..c6f909e 100644
--- a/agent_based/ospfv3_general.py
+++ b/agent_based/ospfv3_general.py
@@ -38,7 +38,7 @@
 # .1.3.6.1.2.1.191.1.1.24.0 = 0
 # .1.3.6.1.2.1.191.1.1.25.0 = 0
 #
-# sample info
+# sample string_table
 # [['0', '16843038', '1', '3', '1', '2', '0', '0', '256', '51', '0', '100000', '1', '1']]
 
 from dataclasses import dataclass
@@ -53,7 +53,6 @@ from cmk.base.plugins.agent_based.agent_based_api.v1 import (
     exists,
     Metric,
     OIDEnd,
-    check_levels,
 )
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
     DiscoveryResult,
@@ -61,6 +60,14 @@ from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
     StringTable,
 )
 
+from cmk.base.plugins.agent_based.utils.ospfv3 import (
+    ospf_stub_router_advertisement,
+    ospf_admin_status,
+    ospf_abr_asbr_status,
+    ospf_stub_router_support,
+    render_ipv4_neighbor_id,
+)
+
 
 @dataclass
 class OspfV3General:
@@ -80,69 +87,6 @@ class OspfV3General:
 
 
 def parse_ospfv3_general(string_table: StringTable) -> Optional[OspfV3General]:
-    def ospf_stub_router_advertisement(st: str) -> str:
-        names = {'1': 'do Not Advertise',
-                 '2': 'advertise'}
-        return names.get(st, st)
-
-    def ospf_exit_reason(st: str) -> str:
-        names = {
-            '1': 'none',
-            '2': 'in Progress',
-            '3': 'completed',
-            '4': 'timed Out',
-            '5': 'topology Changed',
-        }
-        return names.get(st, st)
-
-    def ospf_restart_status(st: str) -> str:
-        names = {
-            '1': 'not Restarting',
-            '2': 'planned Restart',
-            '3': 'unplanned Restart',
-        }
-        return names.get(st, st)
-
-    def ospf_restart_support(st: str) -> str:
-        names = {
-            '1': 'none',
-            '2': 'planned Only',
-            '3': 'planned And Unplanned',
-        }
-        return names.get(st, st)
-
-    def ospf_admin_status(st: str) -> str:
-        names = {
-            '1': 'enabled',
-            '2': 'disabled',
-        }
-        return names.get(st, st)
-
-    def ospf_abr_asbr_status(st: str) -> str:
-        names = {
-            '1': 'yes',
-            '2': 'no',
-        }
-        return names.get(st, st)
-
-    def ospf_stub_router_support(st: str) -> str:
-        names = {
-            '1': 'yes',
-            '2': 'no',
-        }
-        return names.get(st, st)
-
-    def render_ipv4_router_id(nbrRtrId: str) -> str:
-        nbrRtrId = int(nbrRtrId)
-        firstoctet = nbrRtrId / (256 * 256 * 256)
-        nbrRtrId = nbrRtrId - (firstoctet * 256 * 256 * 256)
-        secondocted = nbrRtrId / (256 * 256)
-        nbrRtrId = nbrRtrId - (secondocted * 256 * 256)
-        thirdorted = nbrRtrId / 256
-        nbrRtrId = nbrRtrId - (thirdorted * 256)
-        nbrRtrId = '%d.%d.%d.%d' % (firstoctet, secondocted, thirdorted, nbrRtrId)
-        return nbrRtrId
-
     try:
         OID_END, RouterId, AdminStatus, VersionNumber, AreaBdrRtrStatus, ASBdrRtrStatus, AsScopeLsaCount, \
         AsScopeLsaCksumSum, OriginateNewLsas, RxNewLsas, ExtLsaCount, ExtAreaLsdbLimit, \
@@ -151,7 +95,7 @@ def parse_ospfv3_general(string_table: StringTable) -> Optional[OspfV3General]:
         return
 
     return OspfV3General(
-        RouterId=render_ipv4_router_id(RouterId),
+        RouterId=render_ipv4_neighbor_id(RouterId),
         AdminStatus=ospf_admin_status(AdminStatus),
         VersionNumber=int(VersionNumber),
         AreaBdrRtrStatus=ospf_abr_asbr_status(AreaBdrRtrStatus),
@@ -173,23 +117,23 @@ def discovery_ospfv3_general(section: OspfV3General) -> DiscoveryResult:
 
 def check_ospfv3_general(params, section: OspfV3General) -> CheckResult:
     yield Result(state=State.OK, summary=f'Router ID: {section.RouterId}')
-    yield Result(state=State.OK, summary=f'Area border router: {section.AreaBdrRtrStatus}')
-    yield Result(state=State.OK, summary=f'Autonomous system border router: {section.ASBdrRtrStatus}')
-
-    yield Metric(name='ospf_general_lsacount', value=section.AsScopeLsaCount)
-    yield Metric(name='ospf_general_rxnewlsas', value=section.RxNewLsas)
-    yield Metric(name='ospf_general_extlsacount', value=section.ExtLsaCount)
+    yield Result(state=State.OK, summary=f'ABR: {section.AreaBdrRtrStatus}')
+    yield Result(state=State.OK, summary=f'ASBR: {section.ASBdrRtrStatus}')
 
     yield Result(state=State.OK, notice=f'Version number: {section.VersionNumber}')
     yield Result(state=State.OK, notice=f'AS scope LSA checksum: {section.AsScopeLsaCksumSum}')
     yield Result(state=State.OK, notice=f'max # of non-default AS-external-LSAs: {section.ExtAreaLsdbLimit}')
-    yield Result(state=State.OK, notice=f'Reference bandwidth: %s KBPS' % section.ReferenceBandwidth)
+    yield Result(state=State.OK, notice=f'Reference bandwidth: {section.ReferenceBandwidth} KBPS')
     yield Result(state=State.OK, notice=f'Stub router support: {section.StubRouterSupport}')
     yield Result(state=State.OK, notice=f'Stub router advertisement: {section.StubRouterAdvertisement}')
 
     if section.AdminStatus == 'disabled':
         yield Result(state=State.WARN, notice='Admin disabled')
 
+    yield Metric(name='ospf3_general_lsacount', value=section.AsScopeLsaCount)
+    yield Metric(name='ospf3_general_rxnewlsas', value=section.RxNewLsas)
+    yield Metric(name='ospf3_general_extlsacount', value=section.ExtLsaCount)
+
 
 register.snmp_section(
     name='ospfv3_general',
@@ -230,7 +174,7 @@ register.snmp_section(
 
 register.check_plugin(
     name='ospfv3_general',
-    service_name='OSPFv3',
+    service_name='OSPFv3 general',
     discovery_function=discovery_ospfv3_general,
     check_function=check_ospfv3_general,
     check_default_parameters={
diff --git a/agent_based/ospfv3_interface.py b/agent_based/ospfv3_interface.py
index 2c135ad..9e01868 100644
--- a/agent_based/ospfv3_interface.py
+++ b/agent_based/ospfv3_interface.py
@@ -60,7 +60,7 @@
 # .1.3.6.1.2.1.191.1.7.1.25.18.0 = INTEGER: 2
 # .1.3.6.1.2.1.191.1.7.1.25.23.0 = INTEGER: 2
 #
-# sample info
+# sample string_table
 # [
 #  [
 #   ['1.0', '0', '1', '1', '1', '1', '4', '120', '6', '50529055', '16843038', '6', '1', '4', '104400'],
@@ -101,7 +101,6 @@ from cmk.base.plugins.agent_based.agent_based_api.v1 import (
     exists,
     Metric,
     OIDEnd,
-    check_levels,
 )
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
     DiscoveryResult,
@@ -109,6 +108,14 @@ from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
     StringTable,
 )
 
+from cmk.base.plugins.agent_based.utils.ospfv3 import (
+    ospf_if_type,
+    ospf_if_admin_status,
+    ospf_if_state,
+    render_ipv4_neighbor_id,
+    get_short_if_name,
+)
+
 
 @dataclass
 class OspfV3Interface:
@@ -129,59 +136,6 @@ class OspfV3Interface:
 
 
 def parse_ospfv3_interface(string_table: List[StringTable]) -> Optional[Dict[str, OspfV3Interface]]:
-    def ospf_if_type(st: str) -> str:
-        names = {'1': 'broadcast',
-                 '2': 'nbma',
-                 '3': 'pointToPoint',
-                 '5': 'pointToMultipoint',
-                 }
-        return names.get(st, st)
-
-    def ospf_if_admin_status(st: str) -> str:
-        names = {'1': 'enabled',
-                 '2': 'disabled'}
-        return names.get(st, st)
-
-    def ospf_if_state(st: str) -> str:
-        names = {
-            '1': 'down',
-            '2': 'loopback',
-            '3': 'waiting',
-            '4': 'P2P',
-            '5': 'DR',
-            '6': 'BDR',
-            '7': 'other DR',
-            '8': 'standby',
-        }
-        return names.get(st, st)
-
-    def render_ipv4_neighbor_id(nbrRtrId: str) -> str:
-        nbrRtrId = int(nbrRtrId)
-        firstoctet = nbrRtrId / (256 * 256 * 256)
-        nbrRtrId = nbrRtrId - (firstoctet * 256 * 256 * 256)
-        secondocted = nbrRtrId / (256 * 256)
-        nbrRtrId = nbrRtrId - (secondocted * 256 * 256)
-        thirdorted = nbrRtrId / 256
-        nbrRtrId = nbrRtrId - (thirdorted * 256)
-        nbrRtrId = '%d.%d.%d.%d' % (firstoctet, secondocted, thirdorted, nbrRtrId)
-        return nbrRtrId
-
-    def get_short_if_name(st: str) -> str:
-        names = {'ethernet': 'Eth',
-                 'fastethernet': 'Fa',
-                 'gigabitethernet': 'Gi',
-                 'tengigabitethernet': 'Te',
-                 'fortygigabitethernet': 'Fo',
-                 'hundredgigabitethernet': 'H',
-                 'port-channel': 'Po',
-                 'tunnel': 'T',
-                 'loopback': 'Lo',
-                 }
-        for item in names.keys():
-            if st.lower().startswith(item):
-                st = st.lower().replace(item, names.get(item))
-        return st
-
     ospf_interfaces = {}
     interfaceinfo, interfaces = string_table
 
@@ -233,11 +187,8 @@ def check_ospfv3_interface(item, params, section: Dict[str, OspfV3Interface]) ->
     yield Result(state=State.OK, summary=f'DR: {interface.IfDesignatedRouter}')
     yield Result(state=State.OK, summary=f'BDR: {interface.IfBackupDesignatedRouter}')
     yield Result(state=State.OK, summary=f'State: {interface.IfState}')
-    yield Result(state=State.OK, summary=f'link scope LSA checksum: {interface.IfLinkLsaCksumSum}')
-
-    yield Metric(name='ospfv3_interface_events', value=interface.IfEvents)
-    yield Metric(name='ospfv3_interface_linkscopelsacount', value=interface.IfLinkScopeLsaCount)
 
+    yield Result(state=State.OK, notice=f'Link scope LSA checksum: {interface.IfLinkLsaCksumSum}')
     yield Result(state=State.OK, notice=f'Interface type: {interface.IfType}')
     yield Result(state=State.OK, notice=f'Interface priority: {interface.IfRtrPriority}')
     yield Result(state=State.OK, notice=f'Interface hello interval: {interface.IfHelloInterval}')
@@ -254,6 +205,9 @@ def check_ospfv3_interface(item, params, section: Dict[str, OspfV3Interface]) ->
     if interface.IfState in ['waiting', 'standby']:
         yield Result(state=State.WARN, notice=f'state is {interface.IfState}')
 
+    yield Metric(name='ospfv3_interface_events', value=interface.IfEvents)
+    yield Metric(name='ospfv3_interface_linkscopelsacount', value=interface.IfLinkScopeLsaCount)
+
 
 register.snmp_section(
     name='ospfv3_interface',
diff --git a/agent_based/ospfv3_neighbor.py b/agent_based/ospfv3_neighbor.py
index f75eda6..f074333 100644
--- a/agent_based/ospfv3_neighbor.py
+++ b/agent_based/ospfv3_neighbor.py
@@ -37,6 +37,8 @@
 # OSPFV3-MIB::ospfv3NbrRestartHelperAge.1.0.50529054 = Gauge32: 0 seconds
 # OSPFV3-MIB::ospfv3NbrRestartHelperExitReason.1.0.50529054 = INTEGER: none(1)
 #
+#
+# sample string_table
 # [
 #  [
 #   ['1.0.50529054', '2', '\xfe\x80\x00\x00\x00\x00\x00\x00\x01\x92\x01h\x00\x00\x00\x03',
@@ -67,9 +69,8 @@
 
 #
 
-import re
 from dataclasses import dataclass
-from typing import Optional, Dict, List
+from typing import Dict, List
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
     register,
@@ -80,7 +81,6 @@ from cmk.base.plugins.agent_based.agent_based_api.v1 import (
     exists,
     Metric,
     OIDEnd,
-    check_levels,
 )
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
     DiscoveryResult,
@@ -88,6 +88,17 @@ from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
     StringTable,
 )
 
+from cmk.base.plugins.agent_based.utils.ospfv3 import (
+    ospf_nbr_addresstype,
+    ospf_nbr_hellosuppressed,
+    ospf_nbr_helperstatus,
+    ospf_nbr_helperexitreason,
+    render_ipv6_address,
+    render_ipv4_neighbor_id,
+    get_short_if_name,
+    ospf_nbr_state,
+)
+
 
 @dataclass
 class OspfV3Neighbor:
@@ -107,85 +118,6 @@ class OspfV3Neighbor:
 
 
 def parse_ospfv3_neighbor(string_table: List[StringTable]) -> Dict[str, OspfV3Neighbor]:
-    def ospf_nbr_addresstype(st: str) -> str:
-        names = {'0': 'unknown',
-                 '1': 'ipv4',
-                 '2': 'ipv6',
-                 '3': 'ipv4z',
-                 '4': 'ipv6z',
-                 '16': 'dns', }
-        return names.get(st, st)
-
-    def ospf_nbr_hellosuppressed(st: str) -> str:
-        names = {'1': 'true',
-                 '2': 'false'}
-        return names.get(st, st)
-
-    def ospf_nbr_helperstatus(st: str) -> str:
-        names = {'1': 'not Helping',
-                 '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 shorten_ipv6_adress(address: str) -> str:
-        address = address.split(':')
-        span = 2
-        address = [''.join(address[i:i + span]) for i in range(0, len(address), span)]
-        for m in range(0, len(address)):
-            address[m] = re.sub(r'^0{1,3}', r'', address[m])
-        address = ':'.join(address)
-        zeros = ':0:0:0:0:0:0:'
-        while not zeros == '':
-            if zeros in address:
-                address = re.sub(r'%s' % zeros, r'::', address)
-                zeros = ''
-            else:
-                zeros = zeros[:-2]
-        if address == '0::0':
-            address = '::'
-
-        return address
-
-    def render_ipv6_address(bytestring) -> str:
-        address = ':'.join(['%02s' % hex(ord(m))[2:] for m in bytestring]).replace(' ', '0').upper()
-        address = shorten_ipv6_adress(address)
-
-        return address
-
-    def render_ipv4_neighbor_id(nbrRtrId: str) -> str:
-        nbrRtrId = int(nbrRtrId)
-        firstoctet = nbrRtrId / (256 * 256 * 256)
-        nbrRtrId = nbrRtrId - (firstoctet * 256 * 256 * 256)
-        secondocted = nbrRtrId / (256 * 256)
-        nbrRtrId = nbrRtrId - (secondocted * 256 * 256)
-        thirdorted = nbrRtrId / 256
-        nbrRtrId = nbrRtrId - (thirdorted * 256)
-        nbrRtrId = '%d.%d.%d.%d' % (firstoctet, secondocted, thirdorted, nbrRtrId)
-        return nbrRtrId
-
-    def get_short_if_name(st: str) -> str:
-        names = {'ethernet': 'Eth',
-                 'fastethernet': 'Fa',
-                 'gigabitethernet': 'Gi',
-                 'tengigabitethernet': 'Te',
-                 'fortygigabitethernet': 'Fo',
-                 'hundredgigabitethernet': 'H',
-                 'port-channel': 'Po',
-                 'tunnel': 'T',
-                 'loopback': 'Lo',
-                 }
-        for item in names.keys():
-            if st.lower().startswith(item):
-                st = st.lower().replace(item, names.get(item))
-        return st
-
     neighbors = {}
     neighborinfo, interfaces = string_table
 
@@ -228,17 +160,6 @@ def discovery_ospfv3_neighbor(section: Dict[str, OspfV3Neighbor]) -> DiscoveryRe
 
 
 def check_ospfv3_neighbor(item, params, section: Dict[str, OspfV3Neighbor]) -> CheckResult:
-    def ospf_nbr_state(st: int) -> str:
-        names = {1: 'down',
-                 2: 'attempt',
-                 3: 'init',
-                 4: 'twoWay',
-                 5: 'exchangeStart',
-                 6: 'exchange',
-                 7: 'loading',
-                 8: 'full'}
-        return names.get(st, st)
-
     try:
         neighbor = section[item]
     except KeyError:
@@ -261,9 +182,6 @@ def check_ospfv3_neighbor(item, params, section: Dict[str, OspfV3Neighbor]) -> C
     else:
         yield Result(state=State.UNKNOWN, notice='Invalid Output from Agent')
 
-    yield Metric(name='ospfv3_events', value=neighbor.nbrEvents)
-    yield Metric(name='ospfv3_lsretransqlen', value=neighbor.nbrLsRetransQLen)
-
     yield Result(state=State.OK, notice=f'Neighbor options: {neighbor.nbrOptions}')
     yield Result(state=State.OK, notice=f'Neighbor priority: {neighbor.nbrPriority}')
     yield Result(state=State.OK, notice=f'Neighbor hello suppressed: {neighbor.nbrHelloSuppressed}')
@@ -271,6 +189,9 @@ def check_ospfv3_neighbor(item, params, section: Dict[str, OspfV3Neighbor]) -> C
     yield Result(state=State.OK, notice=f'Neighbor helper age: {neighbor.nbrRestartHelperAge}')
     yield Result(state=State.OK, notice=f'Neighbor helper exit reason: {neighbor.nbrRestartHelperExitReason}')
 
+    yield Metric(name='ospfv3_events', value=neighbor.nbrEvents)
+    yield Metric(name='ospfv3_lsretransqlen', value=neighbor.nbrLsRetransQLen)
+
 
 register.snmp_section(
     name='ospfv3_neighbor',
diff --git a/agent_based/ospfv3_virtuallink.py b/agent_based/ospfv3_virtuallink.py
index df2af49..f78aeb3 100644
--- a/agent_based/ospfv3_virtuallink.py
+++ b/agent_based/ospfv3_virtuallink.py
@@ -39,10 +39,11 @@
 # OSPFV3-MIB::ospfv3VirtNbrRestartHelperAge.3.50529054 = Gauge32: 0 seconds
 # OSPFV3-MIB::ospfv3VirtNbrRestartHelperExitReason.3.50529054 = INTEGER: none(1)
 #
-#
+# sample string_table
 # [
 #  [
-#   ['3.50529054', '0', '0', '2', '0\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x03\x00\x03\x000', '51', '8', '6', '0', '1', '26', '1', '0', '1']
+#   ['3.50529054', '0', '0', '2', '0\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x03\x00\x03\x000',
+#    '51', '8', '6', '0', '1', '26', '1', '0', '1']
 #  ],
 #  [
 #   ['1', 'Vlan1'],
@@ -66,9 +67,8 @@
 # ]
 #
 
-import re
 from dataclasses import dataclass
-from typing import Optional, Dict, List
+from typing import Dict, List
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
     register,
@@ -79,7 +79,6 @@ from cmk.base.plugins.agent_based.agent_based_api.v1 import (
     exists,
     Metric,
     OIDEnd,
-    check_levels,
 )
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
     DiscoveryResult,
@@ -87,6 +86,17 @@ from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
     StringTable,
 )
 
+from cmk.base.plugins.agent_based.utils.ospfv3 import (
+    ospf_nbr_addresstype,
+    ospf_nbr_hellosuppressed,
+    ospf_nbr_helperstatus,
+    ospf_nbr_helperexitreason,
+    render_ipv6_address,
+    render_ipv4_neighbor_id,
+    get_short_if_name,
+    ospf_nbr_state,
+)
+
 
 @dataclass
 class OspfV3VirtualLink:
@@ -100,88 +110,9 @@ class OspfV3VirtualLink:
     VirtNbrRestartHelperStatus: str
     VirtNbrRestartHelperAge: int
     VirtNbrRestartHelperExitReason: str
-    
-    
-def parse_ospfv3_virtuallink(string_table: List[StringTable]) -> Dict[str, OspfV3VirtualLink]:
-    def ospf_nbr_addresstype(st):
-        names = { '0': 'unknown',
-                      '1': 'ipv4',
-                      '2': 'ipv6',
-                      '3': 'ipv4z',
-                      '4': 'ipv6z',
-                      '16':'dns',}
-        return names.get(st, st)
-
-    def ospf_nbr_hellosuppressed(st):
-        names = {'1': 'true',
-                 '2': 'false'}
-        return names.get(st, st)
-
-    def ospf_nbr_helperstatus(st):
-        names = {'1': 'not Helping',
-                 '2': 'helping'}
-        return names.get(st, st)
-
-    def ospf_nbr_helperexitreason(st):
-        names = {'1': 'none',
-                 '2': 'inProgress',
-                 '3': 'completed',
-                 '4': 'timedOut',
-                 '5': 'topologyChanged'}
-        return names.get(st, st)
-
-    def shorten_ipv6_adress(address):
-        address = address.split(':')
-        span = 2
-        address = [''.join(address[i:i + span]) for i in range(0, len(address), span)]
-        for m in range(0, len(address)):
-            address[m] = re.sub(r'^0{1,3}', r'', address[m])
-        address = ':'.join(address)
-        zeros = ':0:0:0:0:0:0:'
-        while not zeros == '':
-            if zeros in address:
-                address = re.sub(r'%s' % zeros, r'::', address)
-                zeros = ''
-            else:
-                zeros = zeros[:-2]
-        if address == '0::0':
-            address = '::'
-
-        return address
-
-    def render_ipv6_address(bytestring):
-        address = ":".join(["%02s" % hex(ord(m))[2:] for m in bytestring]).replace(' ', '0').upper()
-        address = shorten_ipv6_adress(address)
-
-        return address
-
-    def render_ipv4_neighbor_id(nbrRtrId: str)-> str:
-        nbrRtrId = int(nbrRtrId)
-        firstoctet = nbrRtrId / (256*256*256)
-        nbrRtrId = nbrRtrId - (firstoctet *256*256*256)
-        secondocted = nbrRtrId / (256*256)
-        nbrRtrId = nbrRtrId - (secondocted * 256 * 256)
-        thirdorted = nbrRtrId / 256
-        nbrRtrId = nbrRtrId - (thirdorted * 256)
-        nbrRtrId = '%d.%d.%d.%d' % (firstoctet, secondocted, thirdorted, nbrRtrId)
-        return nbrRtrId
 
-    def get_short_if_name(st):
-        names = {'ethernet': 'Eth',
-                 'fastethernet': 'Fa',
-                 'gigabitethernet': 'Gi',
-                 'tengigabitethernet': 'Te',
-                 'fortygigabitethernet': 'Fo',
-                 'hundredgigabitethernet': 'H',
-                 'port-channel': 'Po',
-                 'tunnel': 'T',
-                 'loopback': 'Lo',
-                 }
-        for item in names.keys():
-            if st.lower().startswith(item):
-                st = st.lower().replace(item, names.get(item))
-        return st
 
+def parse_ospfv3_virtuallink(string_table: List[StringTable]) -> Dict[str, OspfV3VirtualLink]:
     virtual_links = {}
     virtual_links_info, interfaces = string_table
 
@@ -202,7 +133,7 @@ def parse_ospfv3_virtuallink(string_table: List[StringTable]) -> Dict[str, OspfV
                     nbrLocalInterface = get_short_if_name(ifName)
 
             # virtual_links[f'{VirtNbrAddress} on {nbrLocalInterface}']= OspfV3VirtualLink(
-            virtual_links[VirtNbrAddress]= OspfV3VirtualLink(
+            virtual_links[VirtNbrAddress] = OspfV3VirtualLink(
                 VirtNbrId=render_ipv4_neighbor_id(VirtNbrId),
                 VirtNbrArea=int(VirtNbrArea),
                 VirtNbrOptions=VirtNbrOptions,
@@ -224,31 +155,19 @@ def discovery_ospfv3_virtuallink(section: Dict[str, OspfV3VirtualLink]) -> Disco
 
 
 def check_ospfv3_virtuallink(item, params, section: Dict[str, OspfV3VirtualLink]) -> CheckResult:
-
-    def ospf_nbr_state(st: int)-> str:
-        names = {1: 'down',
-                 2: 'attempt',
-                 3: 'init',
-                 4: 'twoWay',
-                 5: 'exchangeStart',
-                 6: 'exchange',
-                 7: 'loading',
-                 8: 'full'}
-        return names.get(st, st)
-
     try:
         virtual_link = section[item]
     except KeyError:
         yield Result(state=State.UNKNOWN, notice='Item not found in SNMP data')
         return
-    
+
     nbrstatus = ospf_nbr_state(virtual_link.VirtNbrState)
 
     yield Result(state=State.OK, summary=f'Neighbor ID: {virtual_link.VirtNbrId}')
-    yield Result(state=State.OK, summary=f', Area: {virtual_link.VirtNbrArea}')
+    yield Result(state=State.OK, summary=f'Area: {virtual_link.VirtNbrArea}')
 
     nbrstate = virtual_link.VirtNbrState
-            
+
     if nbrstate in params['critical_states']:
         yield Result(state=State.CRIT, notice='State: {nbrstatus}')
     elif nbrstate in params['warning_states']:
@@ -258,14 +177,15 @@ def check_ospfv3_virtuallink(item, params, section: Dict[str, OspfV3VirtualLink]
     else:
         yield Result(state=State.UNKNOWN, notice='Invalid Output from Agent')
 
-    yield Metric(name='ospfv3_events', value=virtual_link.VirtNbrEvents)
-    yield Metric(name='ospfv3_lsretransqlen', value=virtual_link.VirtNbrLsRetransQLen)
-
     yield Result(state=State.OK, notice=f'Virtual link options: {virtual_link.VirtNbrOptions}')
     yield Result(state=State.OK, notice=f'Virtual link hello suppressed: {virtual_link.VirtNbrHelloSuppressed}')
     yield Result(state=State.OK, notice=f'Virtual link helper status: {virtual_link.VirtNbrRestartHelperStatus}')
     yield Result(state=State.OK, notice=f'Virtual link helper age: {virtual_link.VirtNbrRestartHelperAge}')
-    yield Result(state=State.OK, notice=f'Virtual link helper exit reason: {virtual_link.VirtNbrRestartHelperExitReason}')
+    yield Result(state=State.OK,
+                 notice=f'Virtual link helper exit reason: {virtual_link.VirtNbrRestartHelperExitReason}')
+
+    yield Metric(name='ospfv3_events', value=virtual_link.VirtNbrEvents)
+    yield Metric(name='ospfv3_lsretransqlen', value=virtual_link.VirtNbrLsRetransQLen)
 
 
 register.snmp_section(
diff --git a/agent_based/utils/ospfv3.py b/agent_based/utils/ospfv3.py
new file mode 100644
index 0000000..832cf90
--- /dev/null
+++ b/agent_based/utils/ospfv3.py
@@ -0,0 +1,306 @@
+#!/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-09-17
+#
+# include file, will be used with ospfv3_area, ospfv3_general, ospfv3_interface, ospfv3_neighbor, ospfv3_virtuallink
+#
+import re
+
+
+def get_area_type(area_import_as_extern: str, area_summary: str) -> str:
+    areatype = 'UNKNOWN'
+    if int(area_import_as_extern) == 1 and int(area_summary) == 2:
+        areatype = 'normal'
+    if int(area_import_as_extern) == 2 and int(area_summary) == 1:
+        areatype = 'totaly subby'
+    if int(area_import_as_extern) == 2 and int(area_summary) == 2:
+        areatype = 'stubby'
+    if int(area_import_as_extern) == 3 and int(area_summary) == 2:
+        areatype = 'NSSA'
+    if int(area_import_as_extern) == 3 and int(area_summary) == 1:
+        areatype = 'totaly NSSA'
+    return areatype
+
+
+def render_ipv4_neighbor_id(router_id: str) -> str:
+    """
+    >>> from pprint import pprint
+    >>> pprint(render_ipv4_neighbor_id('50529054'))
+    3.3.3.30
+    """
+    router_id = int(router_id)
+    firstoctet = router_id // (256 * 256 * 256)
+    router_id = router_id - (firstoctet * 256 * 256 * 256)
+    secondocted = router_id // (256 * 256)
+    router_id = router_id - (secondocted * 256 * 256)
+    thirdorted = router_id // 256
+    router_id = router_id - (thirdorted * 256)
+    router_id = f'{firstoctet}.{secondocted}.{thirdorted}.{router_id}'
+    return router_id
+
+
+_interface_displayhints = {
+    'ethernet': 'eth',
+    'fastethernet': 'Fa',
+    'gigabitethernet': 'Gi',
+    'tengigabitethernet': 'Te',
+    'fortygigabitethernet': 'Fo',
+    'hundredgigabitethernet': 'Hu',
+    'port-channel': 'Po',
+    'tunnel': 'Tu',
+    'loopback': 'Lo',
+    'cellular': 'Cel',
+    'vlan': 'Vlan',
+    'management': 'Ma',
+}
+
+
+def get_short_if_name(ifname: str) -> str:
+    """
+    returns short interface name from long interface name
+    ifname: is the long interface name
+    :type ifname: str
+    """
+
+    for ifname_prefix in _interface_displayhints.keys():
+        if ifname.lower().startswith(ifname_prefix.lower()):
+            ifname_short = _interface_displayhints[ifname_prefix]
+            return ifname.lower().replace(ifname_prefix.lower(), ifname_short, 1)
+    return ifname
+
+
+def _shorten_ipv6_adress(address: str) -> str:
+    """
+    >>> from pprint import pprint
+    >>> pprint(_shorten_ipv6_adress('20:03:00:00:00:00:00:00:01:92:01:68:00:00:00:01'))
+    2003::192:168:0:1
+    """
+    # '20:03:00:00:00:00:00:00:01:92:01:68:00:00:00:01'
+    address = address.split(':')
+    span = 2
+    address = [''.join(address[i:i + span]) for i in range(0, len(address), span)]
+    # ['2003', '0000', '0000', '0000', '0192', '0168', '0000', '0001']
+    for m in range(0, len(address)):
+        address[m] = re.sub(r'^0{1,3}', r'', address[m])
+    # ['2003', '0', '0', '0', '192', '168', '0', '1']
+    address = ':'.join(address)
+    # '2003:0:0:0:192:168:0:1'
+    zeros = ':0:0:0:0:0:0:'
+    while not zeros == '':
+        if zeros in address:
+            address = address.replace(zeros, '::', 1)
+            zeros = ''
+        else:
+            zeros = zeros[:-2]
+    if address == '0::0':
+        address = '::'
+    # '2003::192:168:0:1'
+    return address
+
+
+def render_ipv6_address(bytestring) -> str:
+    address = ':'.join([f'{hex(ord(m))[2:]:0>2}' for m in bytestring]).replace(' ', '0').upper()
+    address = _shorten_ipv6_adress(address)
+
+    return address
+
+
+def ospf_stub_router_advertisement(st: str) -> str:
+    names = {
+        '1': 'do Not Advertise',
+        '2': 'advertise',
+    }
+    return names.get(st, st)
+
+
+def ospf_exit_reason(st: str) -> str:
+    names = {
+        '1': 'none',
+        '2': 'in Progress',
+        '3': 'completed',
+        '4': 'timed Out',
+        '5': 'topology Changed',
+    }
+    return names.get(st, st)
+
+
+def ospf_restart_status(st: str) -> str:
+    names = {
+        '1': 'not Restarting',
+        '2': 'planned Restart',
+        '3': 'unplanned Restart',
+    }
+    return names.get(st, st)
+
+
+def ospf_restart_support(st: str) -> str:
+    names = {
+        '1': 'none',
+        '2': 'planned Only',
+        '3': 'planned And Unplanned',
+    }
+    return names.get(st, st)
+
+
+def ospf_admin_status(st: str) -> str:
+    names = {
+        '1': 'enabled',
+        '2': 'disabled',
+    }
+    return names.get(st, st)
+
+
+def ospf_abr_asbr_status(st: str) -> str:
+    names = {
+        '1': 'yes',
+        '2': 'no',
+    }
+    return names.get(st, st)
+
+
+def ospf_stub_router_support(st: str) -> str:
+    names = {
+        '1': 'yes',
+        '2': 'no',
+    }
+    return names.get(st, st)
+
+
+def ospf_area_impotasextern(st: str) -> str:
+    names = {
+        '1': 'import External',
+        '2': 'import no External',
+        '3': 'import Nssa',
+    }
+    return names.get(st, st)
+
+
+def ospf_area_summary(st: str) -> str:
+    names = {
+        '1': 'no Area Summary',
+        '2': 'send Area Summary',
+    }
+    return names.get(st, st)
+
+
+def ospf_area_translatorrole(st: str) -> str:
+    names = {
+        '1': 'always',
+        '2': 'candidate',
+    }
+    return names.get(st, st)
+
+
+def ospf_area_translatorrolestate(st: str) -> str:
+    names = {
+        '1': 'enabled',
+        '2': 'elected',
+        '3': 'disabled',
+    }
+    return names.get(st, st)
+
+
+def ospf_area_submetrictype(st: str) -> str:
+    names = {
+        '1': 'ospfv3 Metric',
+        '2': 'comparable Cost',
+        '3': 'non Comparable',
+    }
+    return names.get(st, st)
+
+
+def ospf_area_teeenabled(st: str) -> str:
+    names = {
+        '1': 'enabled',
+    }
+    return names.get(st, 'disabled')
+
+
+def ospf_if_type(st: str) -> str:
+    names = {
+        '1': 'broadcast',
+        '2': 'nbma',
+        '3': 'pointToPoint',
+        '5': 'pointToMultipoint',
+    }
+    return names.get(st, st)
+
+
+def ospf_if_admin_status(st: str) -> str:
+    names = {
+        '1': 'enabled',
+        '2': 'disabled',
+    }
+    return names.get(st, st)
+
+
+def ospf_if_state(st: str) -> str:
+    names = {
+        '1': 'down',
+        '2': 'loopback',
+        '3': 'waiting',
+        '4': 'P2P',
+        '5': 'DR',
+        '6': 'BDR',
+        '7': 'other DR',
+        '8': 'standby',
+    }
+    return names.get(st, st)
+
+
+def ospf_nbr_state(st: int) -> str:
+    names = {
+        1: 'down',
+        2: 'attempt',
+        3: 'init',
+        4: 'twoWay',
+        5: 'exchangeStart',
+        6: 'exchange',
+        7: 'loading',
+        8: 'full',
+    }
+    return names.get(st, st)
+
+
+def ospf_nbr_addresstype(st: str) -> str:
+    names = {
+        '0': 'unknown',
+        '1': 'ipv4',
+        '2': 'ipv6',
+        '3': 'ipv4z',
+        '4': 'ipv6z',
+        '16': 'dns',
+    }
+    return names.get(st, st)
+
+
+def ospf_nbr_hellosuppressed(st: str) -> str:
+    names = {
+        '1': 'true',
+        '2': 'false',
+    }
+    return names.get(st, st)
+
+
+def ospf_nbr_helperstatus(st: str) -> str:
+    names = {
+        '1': 'not Helping',
+        '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)
diff --git a/ospfv3.mkp b/ospfv3.mkp
index 8fef4840fb4ed39cef766c46040cb98b66d7b03a..e46832ab2faabdf1ed8b4d742e190900e05650fa 100644
GIT binary patch
literal 9626
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

literal 9372
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

diff --git a/packages/ospfv3 b/packages/ospfv3
index 5747a4d..5ac9a2f 100644
--- a/packages/ospfv3
+++ b/packages/ospfv3
@@ -5,10 +5,11 @@
                            'ospfv3_general.py',
                            'ospfv3_interface.py',
                            'ospfv3_neighbor.py',
-                           'ospfv3_virtuallink.py'],
+                           'ospfv3_virtuallink.py',
+                           'utils/ospfv3.py'],
            'web': ['plugins/metrics/ospfv3.py']},
  'name': 'ospfv3',
- 'num_files': 6,
+ 'num_files': 7,
  'title': 'Collection of OSPFv3 checks',
  'version': '20210917.v0.2',
  'version.min_required': '2.0.0',
diff --git a/web/plugins/metrics/ospfv3.py b/web/plugins/metrics/ospfv3.py
index 75b5e6a..58e9e6c 100644
--- a/web/plugins/metrics/ospfv3.py
+++ b/web/plugins/metrics/ospfv3.py
@@ -7,7 +7,7 @@
 # URL   : https://thl-cmk.hopto.org
 # Date  : 2019-11-02
 #
-# Cisco OSPFv3 metrics plugin
+# OSPFv3 metrics plugin
 #
 from cmk.gui.i18n import _
 
-- 
GitLab