From 5cc8b760abece886413d3ca38538ab9c23e76a2f Mon Sep 17 00:00:00 2001 From: "th.l" <thl-cmk@outlook.com> Date: Tue, 3 Aug 2021 09:51:35 +0200 Subject: [PATCH] update project --- agent_based/cisco_eigrp_as_info.py | 324 ++++++++++++ agent_based/cisco_eigrp_interface.py | 423 +++++++++++++++ agent_based/cisco_eigrp_peers.py | 361 +++++++++++++ agent_based/cisco_eigrp_topology_table.py | 488 ++++++++++++++++++ cisco_eigrp.mkp | Bin 15338 -> 14416 bytes packages/cisco_eigrp | 29 +- web/plugins/metrics/cisco_eigrp.py | 358 +++++-------- web/plugins/wato/cisco_eigrp_interface.py | 62 ++- web/plugins/wato/cisco_eigrp_peers.py | 80 ++- .../wato/cisco_eigrp_topology_table.py | 57 +- 10 files changed, 1887 insertions(+), 295 deletions(-) create mode 100644 agent_based/cisco_eigrp_as_info.py create mode 100644 agent_based/cisco_eigrp_interface.py create mode 100644 agent_based/cisco_eigrp_peers.py create mode 100644 agent_based/cisco_eigrp_topology_table.py diff --git a/agent_based/cisco_eigrp_as_info.py b/agent_based/cisco_eigrp_as_info.py new file mode 100644 index 0000000..dbb2b15 --- /dev/null +++ b/agent_based/cisco_eigrp_as_info.py @@ -0,0 +1,324 @@ +#!/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 : 2017-17-20 +# +# Monitor status of Cisco EIGRP AS info +# +# 2018-02-11: removed unnecessary OIDs +# 2018-08-06: modified scan function +# 2019-10-16: added support for IPv6 and VRFs, added parser function +# +# +# snmpwalk sample +# +# thl@surfbox-ii:~$ snmpwalk -v2c -c router-01 -ObentU simulant .1.3.6.1.4.1.9.9.449.1.2.1.1 +# .1.3.6.1.4.1.9.9.449.1.2.1.1.2.65536.10 = Gauge32: 3 +# .1.3.6.1.4.1.9.9.449.1.2.1.1.3.65536.10 = Counter32: 37436 +# .1.3.6.1.4.1.9.9.449.1.2.1.1.4.65536.10 = Counter32: 32170 +# .1.3.6.1.4.1.9.9.449.1.2.1.1.5.65536.10 = Counter32: 161 +# .1.3.6.1.4.1.9.9.449.1.2.1.1.6.65536.10 = Counter32: 143 +# .1.3.6.1.4.1.9.9.449.1.2.1.1.7.65536.10 = Counter32: 14 +# .1.3.6.1.4.1.9.9.449.1.2.1.1.8.65536.10 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.2.1.1.9.65536.10 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.2.1.1.10.65536.10 = Counter32: 14 +# .1.3.6.1.4.1.9.9.449.1.2.1.1.11.65536.10 = Counter32: 143 +# .1.3.6.1.4.1.9.9.449.1.2.1.1.12.65536.10 = Counter32: 132 +# .1.3.6.1.4.1.9.9.449.1.2.1.1.13.65536.10 = Gauge32: 2 +# .1.3.6.1.4.1.9.9.449.1.2.1.1.14.65536.10 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.2.1.1.15.65536.10 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.2.1.1.16.65536.10 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.2.1.1.17.65536.10 = INTEGER: 1 +# .1.3.6.1.4.1.9.9.449.1.2.1.1.18.65536.10 = Hex-STRING: 0A A7 DC FF # can be also in the form of "10.10.10.10" :-( +# .1.3.6.1.4.1.9.9.449.1.2.1.1.19.65536.10 = Counter32: 10 +# .1.3.6.1.4.1.9.9.449.1.2.1.1.20.65536.10 = Counter64: 1 +# .1.3.6.1.4.1.9.9.449.1.2.1.1.21.65536.10 = Counter64: 135 +# .1.3.6.1.4.1.9.9.449.1.2.1.1.22.65536.10 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.2.1.1.23.65536.10 = Gauge32: 0 + +# +# sample info +# [ +# [u'65536.10', u'1', u'7081', u'6637', u'37', u'45', u'0', u'0', u'0', u'0', u'26', u'32', u'2', u'0', u'0', u'0', u'1', u'\n\xa7\xdc\xfd', u'10', u'0'] +# ] +# + +# +from time import time +from dataclasses import dataclass +from typing import Optional, List + +from cmk.base.plugins.agent_based.agent_based_api.v1 import ( + register, + Service, + Result, + check_levels, + State, + SNMPTree, + all_of, + contains, + exists, + OIDEnd, + Metric, + get_rate, + get_value_store, + GetRateError, + IgnoreResultsError, +) +from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( + DiscoveryResult, + CheckResult, + StringTable, +) + +_InetAddressType = {0: 'unknown', + 1: 'ipv4', + 2: 'ipv6', + 3: 'ipv4z', + 4: 'ipv6z', + 16: 'dns', + } + + +def _render_ip_address(bytestring): + return ".".join(["%s" % ord(m) for m in bytestring]) + +########################################################################### +# +# DATA Parser function +# +########################################################################### + + +def parse_cisco_eigrp_as_info(string_table: List[StringTable]): + ASInfoTable = [] + ASTable = {} + ASInfo, VrfInfo = string_table + + for OID_END, cEigrpNbrCount, cEigrpHellosSent, cEigrpHellosRcvd, cEigrpUpdatesSent, cEigrpUpdatesRcvd, cEigrpQueriesSent, \ + cEigrpQueriesRcvd, cEigrpRepliesSent, cEigrpRepliesRcvd, cEigrpAcksSent, cEigrpAcksRcvd, cEigrpInputQHighMark, cEigrpInputQDrops, \ + cEigrpSiaQueriesSent, cEigrpSiaQueriesRcvd, cEigrpAsRouterIdType, cEigrpAsRouterId, cEigrpTopoRoutes, cEigrpXmitPendReplies in ASInfo: + + cEigrpVrfId = int(OID_END.split('.')[0]) + cEigrpAS = OID_END.split('.')[1] + + cEigrpVrfName = '' + for VrfId, VrfName in VrfInfo: + if int(VrfId) == cEigrpVrfId: + cEigrpVrfName = VrfName + if cEigrpVrfId < 100000: + cEigrpAddrFammily = 'IPv4' + else: + cEigrpAddrFammily = 'IPv6' + + if int(cEigrpAsRouterIdType) == 1: + if len(cEigrpAsRouterId) == 4: + cEigrpAsRouterId = _render_ip_address(cEigrpAsRouterId) + + ASInfoTable.append({ + 'cEigrpAS': cEigrpAS, + 'cEigrpVrfName': cEigrpVrfName, + 'cEigrpVrfId': cEigrpVrfId, + 'cEigrpAddrFammily': cEigrpAddrFammily, + 'cEigrpNbrCount': int(cEigrpNbrCount), + 'cEigrpHellosSent': int(cEigrpHellosSent), + 'cEigrpHellosRcvd': int(cEigrpHellosRcvd), + 'cEigrpUpdatesSent': int(cEigrpUpdatesSent), + 'cEigrpUpdatesRcvd': int(cEigrpUpdatesRcvd), + 'cEigrpQueriesSent': int(cEigrpQueriesSent), + 'cEigrpQueriesRcvd': int(cEigrpQueriesRcvd), + 'cEigrpRepliesSent': int(cEigrpRepliesSent), + 'cEigrpRepliesRcvd': int(cEigrpRepliesRcvd), + 'cEigrpAcksSent': int(cEigrpAcksSent), + 'cEigrpAcksRcvd': int(cEigrpAcksRcvd), + 'cEigrpInputQHighMark': int(cEigrpInputQHighMark), + 'cEigrpInputQDrops': int(cEigrpInputQDrops), + 'cEigrpSiaQueriesSent': int(cEigrpSiaQueriesSent), + 'cEigrpSiaQueriesRcvd': int(cEigrpSiaQueriesRcvd), + 'cEigrpAsRouterId': cEigrpAsRouterId, + 'cEigrpTopoRoutes': int(cEigrpTopoRoutes), + 'cEigrpXmitPendReplies': int(cEigrpXmitPendReplies), + }) + + if cEigrpVrfName == 'default' and cEigrpAddrFammily == 'IPv4': + ASTable.update({cEigrpVrfId: {'ServiceText': cEigrpAS, 'cEigrpVrfId': cEigrpVrfId}}) + elif cEigrpVrfName == 'default' and cEigrpAddrFammily == 'IPv6': + ASTable.update({cEigrpVrfId:{'ServiceText': '%s IPv6' % cEigrpAS, 'cEigrpVrfId': cEigrpVrfId}}) + elif cEigrpVrfName != 'default' and cEigrpAddrFammily == 'IPv4': + ASTable.update({cEigrpVrfId:{'ServiceText': '%s VRF %s' % (cEigrpAS, cEigrpVrfName), 'cEigrpVrfId': cEigrpVrfId}}) + elif cEigrpVrfName != 'default' and cEigrpAddrFammily == 'IPv6': + ASTable.update({cEigrpVrfId:{'ServiceText': '%s IPv6 VRF %s' % (cEigrpAS, cEigrpVrfName), 'cEigrpVrfId': cEigrpVrfId}}) + return [ASTable, ASInfoTable] + +########################################################################### +# +# INVENTORY function +# +########################################################################### + + +def discovery_cisco_eigrp_as_info(section) -> DiscoveryResult: + ASTable, ASInfoTable = section + ASTable = dict(ASTable) + for Key in ASTable.keys(): + ServiceText = ASTable.get(Key).get('ServiceText') + yield Service(item=ServiceText) + +########################################################################### +# +# CHECK function +# +########################################################################### + + +def check_cisco_eigrp_as_info(item, params, section) -> CheckResult: + ASTable, ASInfoTable = section + ASTable = dict(ASTable) + + cEigrpVrfId = '' + for Key in ASTable.keys(): + ServiceText = ASTable.get(Key).get('ServiceText') + if ServiceText == item: + cEigrpVrfId = ASTable.get(Key).get('cEigrpVrfId') + + if cEigrpVrfId != '': + for AS in ASInfoTable: + if AS.get('cEigrpVrfId') == cEigrpVrfId: + cEigrpNbrCount = AS.get('cEigrpNbrCount') + cEigrpHellosSent = AS.get('cEigrpHellosSent') + cEigrpHellosRcvd = AS.get('cEigrpHellosRcvd') + cEigrpUpdatesSent = AS.get('cEigrpUpdatesSent') + cEigrpUpdatesRcvd = AS.get('cEigrpUpdatesRcvd') + cEigrpQueriesSent = AS.get('cEigrpQueriesSent') + cEigrpQueriesRcvd = AS.get('cEigrpQueriesRcvd') + cEigrpRepliesSent = AS.get('cEigrpRepliesSent') + cEigrpRepliesRcvd = AS.get('cEigrpRepliesRcvd') + cEigrpAcksSent = AS.get('cEigrpAcksSent') + cEigrpAcksRcvd = AS.get('cEigrpAcksRcvd') + cEigrpInputQHighMark = AS.get('cEigrpInputQHighMark') + cEigrpInputQDrops = AS.get('cEigrpInputQDrops') + cEigrpSiaQueriesSent = AS.get('cEigrpSiaQueriesSent') + cEigrpSiaQueriesRcvd = AS.get('cEigrpSiaQueriesRcvd') + cEigrpAsRouterId = AS.get('cEigrpAsRouterId') + cEigrpTopoRoutes = AS.get('cEigrpTopoRoutes') + cEigrpXmitPendReplies = AS.get('cEigrpXmitPendReplies') + + yield Result( + state=State.OK, + summary=f'Router-ID: {cEigrpAsRouterId}, neighbors: {cEigrpNbrCount}, Routes in topologie table: {cEigrpTopoRoutes}') + + for key, value in [ + ('cEigrpNbrCount', cEigrpNbrCount), + ('cEigrpUpdatesSent', cEigrpUpdatesSent), + ('cEigrpUpdatesRcvd', cEigrpUpdatesRcvd), + ('cEigrpQueriesSent', cEigrpQueriesSent), + ('cEigrpQueriesRcvd', cEigrpQueriesRcvd), + ('cEigrpRepliesSent', cEigrpRepliesSent), + ('cEigrpRepliesRcvd', cEigrpRepliesRcvd), + ('cEigrpAcksSent', cEigrpAcksSent), + ('cEigrpAcksRcvd', cEigrpAcksRcvd), + ('cEigrpInputQHighMark', cEigrpInputQHighMark), + ('cEigrpInputQDrops', cEigrpInputQDrops), + ('cEigrpSiaQueriesSent', cEigrpSiaQueriesSent), + ('cEigrpSiaQueriesRcvd', cEigrpSiaQueriesRcvd), + ('cEigrpTopoRoutes', cEigrpTopoRoutes), + ('cEigrpXmitPendReplies', cEigrpXmitPendReplies), + ]: + yield Metric(name=f'cisco_eigrp_as_info_{key}', value=value) + + now_time = time() + rate_item=item.replace(' ', '_').replace(':', '_') + value_store = get_value_store() + raise_ingore_res = False + + for key, value in [ + ('cEigrpHellosSent', cEigrpHellosSent), + ('cEigrpHellosRcvd', cEigrpHellosRcvd), + + ]: + try: + try: + value = get_rate( + value_store, + f'cisco_eigrp_as_info_{key}.{rate_item}', + now_time, + value, + raise_overflow=False + ) + except GetRateError: + raise_ingore_res = True + value = 0 + yield Metric(name=f'cisco_eigrp_as_info_{key}', value=value, boundaries=(0, None)) + except KeyError: + pass + + if raise_ingore_res: + raise IgnoreResultsError('Initializing counters') + + +########################################################################### +# +# CHECK info +# +########################################################################### + + +register.snmp_section( + name='cisco_eigrp_as_info', + parse_function=parse_cisco_eigrp_as_info, + fetch=[ + SNMPTree( + base='.1.3.6.1.4.1.9.9.449.1.2.1.1', # CISCO-TRUSTSEC-MIB::ctsEnvironmentDataObjects + oids=[ + OIDEnd(), # AS number + '2', # cEigrpNbrCount [1] + '3', # cEigrpHellosSent [2] + '4', # cEigrpHellosRcvd [3] + '5', # cEigrpUpdatesSent [4] + '6', # cEigrpUpdatesRcvd [5] + '7', # cEigrpQueriesSent [6] + '8', # cEigrpQueriesRcvd [7] + '9', # cEigrpRepliesSent [8] + '10', # cEigrpRepliesRcvd [9] + '11', # cEigrpAcksSent [10] + '12', # cEigrpAcksRcvd [11] + '13', # cEigrpInputQHighMark [12] + '14', # cEigrpInputQDrops [13] + '15', # cEigrpSiaQueriesSent [14] + '16', # cEigrpSiaQueriesRcvd [15] + '17', # cEigrpAsRouterIdType [16] + '18', # cEigrpAsRouterId [17] + '19', # cEigrpTopoRoutes [18] + '22', # cEigrpXmitPendReplies [19] + # '20', # cEigrpHeadSerial [] + # '21', # cEigrpNextSerial [] + # '23', # cEigrpXmitDummies [] + ] + ), + SNMPTree( + base='.1.3.6.1.4.1.9.9.449.1.1.1.1', # CISCO-TRUSTSEC-MIB::ctsEnvironmentDataObjects + oids=[ + OIDEnd(), # VRF ID + '2', # cEigrpVpnName + ] + ), + + ], + detect=all_of( + contains('.1.3.6.1.2.1.1.1.0', 'cisco'), # sysDescr + exists('.1.3.6.1.4.1.9.9.449.1.2.1.1.2.*'), # CISCO-EIGRP-MIB::cEigrpNbrCount + )) + +register.check_plugin( + name='cisco_eigrp_as_info', + service_name='EIGRP AS number %s', + discovery_function=discovery_cisco_eigrp_as_info, + check_function=check_cisco_eigrp_as_info, + check_default_parameters={ + }, + check_ruleset_name='cisco_eigrp_as_info', +) diff --git a/agent_based/cisco_eigrp_interface.py b/agent_based/cisco_eigrp_interface.py new file mode 100644 index 0000000..fcf25df --- /dev/null +++ b/agent_based/cisco_eigrp_interface.py @@ -0,0 +1,423 @@ +#!/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 : 2017-17-21 +# +# Monitor status of Cisco EIGRP interface info +# +# 2018-01-23: Th.L.: inventory skip Loopback interface +# 2018-02-11: Th.L.: removed unnecessary OIDs +# 2018-08-06: modified scan function +# 2019-10-14: added support for hamc-sha-256 authentication, md5 moved to warn, none moved to crit +# 2019-10-16: added support for IPv6 and VRFs, added data parser function +# 2019-10-31: changed item from interface long-name to interface short name +# +# snmpwalk sample +# +# OMD[mysite]:~$ snmpwalk -ObentU -v2c -c router-01 simulant .1.3.6.1.4.1.9.9.449.1.5.1.1 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.3.65536.10.1 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.3.65536.10.7 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.3.65536.10.8 = Gauge32: 1 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.3.65536.10.9 = Gauge32: 1 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.4.65536.10.1 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.4.65536.10.7 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.4.65536.10.8 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.4.65536.10.9 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.5.65536.10.1 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.5.65536.10.7 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.5.65536.10.8 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.5.65536.10.9 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.6.65536.10.1 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.6.65536.10.7 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.6.65536.10.8 = Gauge32: 1 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.6.65536.10.9 = Gauge32: 19 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.7.65536.10.1 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.7.65536.10.7 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.7.65536.10.8 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.7.65536.10.9 = Gauge32: 23 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.8.65536.10.1 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.8.65536.10.7 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.8.65536.10.8 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.8.65536.10.9 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.9.65536.10.1 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.9.65536.10.7 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.9.65536.10.8 = Gauge32: 50 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.9.65536.10.9 = Gauge32: 84 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.10.65536.10.1 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.10.65536.10.7 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.10.65536.10.8 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.10.65536.10.9 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.11.65536.10.1 = Gauge32: 5 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.11.65536.10.7 = Gauge32: 5 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.11.65536.10.8 = Gauge32: 1 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.11.65536.10.9 = Gauge32: 1 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.12.65536.10.1 = Counter64: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.12.65536.10.7 = Counter64: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.12.65536.10.8 = Counter64: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.12.65536.10.9 = Counter64: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.13.65536.10.1 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.13.65536.10.7 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.13.65536.10.8 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.13.65536.10.9 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.14.65536.10.1 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.14.65536.10.7 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.14.65536.10.8 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.14.65536.10.9 = Counter32: 18 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.15.65536.10.1 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.15.65536.10.7 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.15.65536.10.8 = Counter32: 64 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.15.65536.10.9 = Counter32: 40 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.16.65536.10.1 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.16.65536.10.7 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.16.65536.10.8 = Counter32: 66 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.16.65536.10.9 = Counter32: 27 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.17.65536.10.1 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.17.65536.10.7 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.17.65536.10.8 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.17.65536.10.9 = Counter32: 1 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.18.65536.10.1 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.18.65536.10.7 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.18.65536.10.8 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.18.65536.10.9 = Counter32: 2 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.19.65536.10.1 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.19.65536.10.7 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.19.65536.10.8 = Counter32: 1 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.19.65536.10.9 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.20.65536.10.1 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.20.65536.10.7 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.20.65536.10.8 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.20.65536.10.9 = Counter32: 5 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.21.65536.10.1 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.21.65536.10.7 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.21.65536.10.8 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.21.65536.10.9 = Counter32: 3 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.22.65536.10.1 = INTEGER: 1 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.22.65536.10.7 = INTEGER: 1 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.22.65536.10.8 = INTEGER: 2 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.22.65536.10.9 = INTEGER: 2 +# .1.3.6.1.4.1.9.9.449.1.5.1.1.23.65536.10.1 = "" +# .1.3.6.1.4.1.9.9.449.1.5.1.1.23.65536.10.7 = "" +# .1.3.6.1.4.1.9.9.449.1.5.1.1.23.65536.10.8 = STRING: "KEY-EIGRP-10" +# .1.3.6.1.4.1.9.9.449.1.5.1.1.23.65536.10.9 = STRING: "KEY-EIGRP-10" +# # +# sample info +# [ +# [ +# [u'65536.10.1', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'5', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'1', u''], +# [u'65536.10.7', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'5', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'1', u''], +# [u'65536.10.8', u'1', u'0', u'0', u'1', u'0', u'0', u'50', u'0', u'1', u'0', u'0', u'0', u'64', u'66', u'0', u'0', u'1', u'0', u'0', u'2', u'KEY-EIGRP-10'], +# [u'65536.10.9', u'1', u'0', u'0', u'19', u'23', u'0', u'84', u'0', u'1', u'0', u'0', u'18', u'40', u'27', u'1', u'2', u'0', u'5', u'3', u'2', u'KEY-EIGRP-10'] +# ], +# [ +# [u'1', u'GigabitEthernet0/0/0'], +# [u'2', u'GigabitEthernet0/0/1'], +# [u'3', u'GigabitEthernet0/0/2'], +# [u'4', u'GigabitEthernet0/0/3'], +# [u'5', u'GigabitEthernet0'], +# [u'6', u'Null0'], +# [u'7', u'Loopback0'], +# [u'8', u'Tunnel10'], +# [u'9', u'Tunnel101'] +# ] +# ] +# + +from time import mktime, gmtime +from dataclasses import dataclass +from typing import Optional, List, Dict + +from cmk.base.plugins.agent_based.agent_based_api.v1 import ( + register, + Service, + Result, + check_levels, + State, + SNMPTree, + all_of, + contains, + exists, + OIDEnd, + Metric, +) +from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( + DiscoveryResult, + CheckResult, + StringTable, +) + + +@dataclass +class EigrpInterface: + cEigrpVrfId: int + cEigrpAS: str + cEigrpVrfName: str + cEigrpIfName: str + cEigrpPeerCount: int + cEigrpXmitReliableQ: int + cEigrpXmitUnreliableQ: int + cEigrpMeanSrtt: int + cEigrpPendingRoutes: int + cEigrpHelloInterval: int + cEigrpUMcasts: int + cEigrpUUcasts: int + cEigrpRUcasts: int + cEigrpMcastExcepts: int + cEigrpCRpkts: int + cEigrpAcksSuppressed: int + cEigrpRetransSent: int + cEigrpOOSrvcd: int + cEigrpAuthMode: str + cEigrpAuthKeyChain: str + + +_InetAddressType = { + 0: 'unknown', + 1: 'ipv4', + 2: 'ipv6', + 3: 'ipv4z', + 4: 'ipv6z', + 16: 'dns', +} + + +def _eigrp_authmode(mode: int) -> str: + name = { + 0: 'unknown', + 1: 'none', + 2: 'md5', + 3: 'hmac-sha-256' + } + return name.get(mode, f'unknown {mode}') + + +def _get_short_if_name(st: str) -> str: + names = { + 'ethernet': 'Eth', + 'fastethernet': 'Fa', + 'gigabitethernet': 'Gi', + 'tengigabitethernet': 'Te', + 'fortygigabitethernet': 'Fo', + 'hundredgigabitethernet': 'Hu', + 'port-channel': 'Po', + 'tunnel': 'Tu', + 'loopback': 'Lo', + } + for item in names.keys(): + if st.lower().startswith(item): + st = st.lower().replace(item, names.get(item)) + return st + +########################################################################### +# +# DATA Parser function +# +########################################################################### + + +def parse_cisco_eigrp_interface(string_table: List[StringTable]) -> Dict[str, EigrpInterface]: + EigrpInterfaces, InterfaceNames, VrfInfo = string_table + EigrpIfTable = {} + + for OID_END, cEigrpPeerCount, cEigrpXmitReliableQ, cEigrpXmitUnreliableQ, cEigrpMeanSrtt, cEigrpPendingRoutes, \ + cEigrpHelloInterval, cEigrpUMcasts, cEigrpRMcasts, cEigrpUUcasts, cEigrpRUcasts, cEigrpMcastExcepts, \ + cEigrpCRpkts, cEigrpAcksSuppressed, cEigrpRetransSent, cEigrpOOSrvcd, cEigrpAuthMode, \ + cEigrpAuthKeyChain in EigrpInterfaces: + + cEigrpVrfId = int(OID_END.split('.')[0]) + cEigrpAS = OID_END.split('.')[1] + cEigrpIfIndex = int(OID_END.split('.')[2]) + cEigrpIfName = '' + for ifindex, ifname, iftype in InterfaceNames: + if int(ifindex) == cEigrpIfIndex: # compare interface index + if not int(iftype) == 24: # skip Loopback interfaces + cEigrpIfName = _get_short_if_name(ifname) + + if cEigrpIfName != '': + cEigrpVrfName = '' + for VrfId, VrfName in VrfInfo: + if int(VrfId) == cEigrpVrfId: + cEigrpVrfName = VrfName + + if cEigrpVrfId < 100000: + cEigrpAddrFammily = 'IPv4' + else: + cEigrpAddrFammily = 'IPv6' + + ServiceText = '' + if cEigrpVrfName == 'default' and cEigrpAddrFammily == 'IPv4': + ServiceText = f'{cEigrpIfName} on AS {cEigrpAS}' + elif cEigrpVrfName == 'default' and cEigrpAddrFammily == 'IPv6': + ServiceText = f'{cEigrpIfName} on AS {cEigrpAS} IPv6' + elif cEigrpVrfName != 'default' and cEigrpAddrFammily == 'IPv4': + ServiceText = f'{cEigrpIfName} on AS {cEigrpAS} VRF {cEigrpVrfName}' + elif cEigrpVrfName != 'default' and cEigrpAddrFammily == 'IPv6': + ServiceText = f'{cEigrpIfName} on AS {cEigrpAS} IPv6 VRF {cEigrpVrfName}' + + EigrpIfTable[ServiceText] = EigrpInterface( + cEigrpVrfId=cEigrpVrfId, + cEigrpAS=cEigrpAS, + cEigrpVrfName=cEigrpVrfName, + cEigrpIfName=cEigrpIfName, + cEigrpPeerCount=int(cEigrpPeerCount), + cEigrpXmitReliableQ=int(cEigrpXmitReliableQ), + cEigrpXmitUnreliableQ=int(cEigrpXmitUnreliableQ), + cEigrpMeanSrtt=int(cEigrpMeanSrtt), + cEigrpPendingRoutes=int(cEigrpPendingRoutes), + cEigrpHelloInterval=int(cEigrpHelloInterval), + cEigrpUMcasts=int(cEigrpUMcasts), + cEigrpUUcasts=int(cEigrpUUcasts), + cEigrpRUcasts=int(cEigrpRUcasts), + cEigrpMcastExcepts=int(cEigrpMcastExcepts), + cEigrpCRpkts=int(cEigrpCRpkts), + cEigrpAcksSuppressed=int(cEigrpAcksSuppressed), + cEigrpRetransSent=int(cEigrpRetransSent), + cEigrpOOSrvcd=int(cEigrpOOSrvcd), + cEigrpAuthMode=_eigrp_authmode(int(cEigrpAuthMode)), + cEigrpAuthKeyChain=cEigrpAuthKeyChain, + ) + + return EigrpIfTable + +########################################################################### +# +# INVENTORY function +# +########################################################################### + + +def discovery_cisco_eigrp_interface(section: Dict[str, EigrpInterface]) -> DiscoveryResult: + for key in section.keys(): + yield Service(item=key) + +########################################################################### +# +# CHECK function +# +########################################################################### + + +def check_cisco_eigrp_interface(item, params, section: Dict[str, EigrpInterface]) -> CheckResult: + try: + eigrp_if = section[item] + except KeyError: + return + + interface_type = 0 + + yield Result(state=State.OK, summary=f'Peer count: {eigrp_if.cEigrpPeerCount}, Hello interval: {eigrp_if.cEigrpHelloInterval}, Auth Mode: {eigrp_if.cEigrpAuthMode}') + + md5_auth_state = params.get('md5_auth_state') + no_auth_state = params.get('no_auth_state') + if interface_type not in params.get('ignore_interfaces_auth'): + if eigrp_if.cEigrpAuthMode in ['md5'] and md5_auth_state != 0: + yield Result(state=State(md5_auth_state), notice='weak authentication set') + # yield Result(state=State.OK, notice=f'you can change the authentication mode to hmac-sha-256 by removing the eigrp authentication') + # yield Result(state=State.OK, notice=f'configuration from the interface and move it to the "router eigrp configuration"') + # yield Result(state=State.OK, notice=f'you need to change to "named mode configuration"') + # yield Result(state=State.OK, notice=f'router eigrp YOUR-EIGRP-NAME') + # yield Result(state=State.OK, notice=f' address-family ipv4 unicast autonomous-system {eigrp_if.cEigrpAS}') + # yield Result(state=State.OK, notice=f' af-interface {eigrp_if.cEigrpIfName}') + # yield Result(state=State.OK, notice=f' authentication mode hmac-sha-256 YOUR-EIGRPKEY') + # yield Result(state=State.OK, notice=f' exit-af-interface') + # yield Result(state=State.OK, notice=f' eigrp YOUR-ROUTER-ID') + # yield Result(state=State.OK, notice=f' network ....') + # yield Result(state=State.OK, notice=f'end') + elif eigrp_if.cEigrpAuthMode == 'none' and no_auth_state != 0: + yield Result(state=State(no_auth_state), notice='no authentication set') + if eigrp_if.cEigrpAuthKeyChain != '': + yield Result(state=State.OK, notice=f'Key chain: {eigrp_if.cEigrpAuthKeyChain}') + + for key, value in [ + ('cEigrpPeerCount', eigrp_if.cEigrpPeerCount), + ('cEigrpXmitReliableQ', eigrp_if.cEigrpXmitReliableQ), + ('cEigrpXmitUnreliableQ', eigrp_if.cEigrpXmitUnreliableQ), + ('cEigrpMeanSrtt', eigrp_if.cEigrpMeanSrtt), + ('cEigrpPendingRoutes', eigrp_if.cEigrpPendingRoutes), + # ('cEigrpRMcasts', eigrp_if.cEigrpRMcasts), + ('cEigrpUMcasts', eigrp_if.cEigrpUMcasts), + ('cEigrpUUcasts', eigrp_if.cEigrpUUcasts), + ('cEigrpRUcasts', eigrp_if.cEigrpRUcasts), + ('cEigrpMcastExcepts', eigrp_if.cEigrpMcastExcepts), + ('cEigrpCRpkts', eigrp_if.cEigrpCRpkts), + ('cEigrpAcksSuppressed', eigrp_if.cEigrpAcksSuppressed), + ('cEigrpRetransSent', eigrp_if.cEigrpRetransSent), + ('cEigrpOOSrvcd', eigrp_if.cEigrpOOSrvcd), + ]: + yield Metric(name=f'cisco_eigrp_interface_{key}', value=value) + + +########################################################################### +# +# CHECK info +# +########################################################################### + + +register.snmp_section( + name='cisco_eigrp_interface', + parse_function=parse_cisco_eigrp_interface, + fetch=[ + SNMPTree( + base='.1.3.6.1.4.1.9.9.449.1.5.1.1', # CISCO-TRUSTSEC-MIB::ctsEnvironmentDataObjects + oids=[ + OIDEnd(), # AS number and interface index [0] (u'65536.10.1') + '3', # cEigrpPeerCount [1] + '4', # cEigrpXmitReliableQ [2] + '5', # cEigrpXmitUnreliableQ [3] + '6', # cEigrpMeanSrtt [4] + '10', # cEigrpPendingRoutes [5] + '11', # cEigrpHelloInterval [6] + '13', # cEigrpUMcasts [7] + '14', # cEigrpRMcasts [8] + '15', # cEigrpUUcasts [9] + '16', # cEigrpRUcasts [10] + '17', # cEigrpMcastExcepts [11] + '18', # cEigrpCRpkts [12] + '19', # cEigrpAcksSuppressed [13] + '20', # cEigrpRetransSent [14] + '21', # cEigrpOOSrvcd [15] + '22', # cEigrpAuthMode [16] + '23', # cEigrpAuthKeyChain [17] + # '7', # cEigrpPacingReliable [] + # '8', # cEigrpPacingUnreliable [] + # '9', # cEigrpMFlowTimer [] + # '12', # cEigrpXmitNextSerial [] + ] + ), + SNMPTree( + base='.1.3.6.1.2.1.2.2.1', # + oids=[ + '1', # ifIndex + '2', # ifDescr + '3', # ifType + ] + ), + SNMPTree( + base='.1.3.6.1.4.1.9.9.449.1.1.1.1', # + oids=[ + OIDEnd(), # VRF ID + '2', # cEigrpVpnName [0] + ] + ), + ], + detect=all_of( + contains('.1.3.6.1.2.1.1.1.0', 'cisco'), # sysDescr + exists('.1.3.6.1.4.1.9.9.449.1.5.1.1.3.*'), # CISCO-EIGRP-MIB::cEigrpPeerCount + )) + +register.check_plugin( + name='cisco_eigrp_interface', + service_name='EIGRP interface %s', + discovery_function=discovery_cisco_eigrp_interface, + check_function=check_cisco_eigrp_interface, + check_default_parameters={ + 'ignore_interfaces_auth': [24, ], # Loopback + 'no_auth_state': 2, + 'md5_auth_state': 1, + }, + check_ruleset_name='cisco_eigrp_interface', +) diff --git a/agent_based/cisco_eigrp_peers.py b/agent_based/cisco_eigrp_peers.py new file mode 100644 index 0000000..4183fb4 --- /dev/null +++ b/agent_based/cisco_eigrp_peers.py @@ -0,0 +1,361 @@ +#!/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 : 2017-17-20 +# +# Monitor status of Cisco EIGRP peers +# +# 2018-02-11: removed unnecessary OIDs +# 2018-07-26: code cleanup, removed hold time from infotext (it is the actual time, not the configured value) +# 2018-08-06: modified scan function +# 2019-07-01: fixed uptime for peers up for more then 1 year... +# 2019-10-15: added initial support for IPv6, code cleanup (parser function) +# 2019-10-16: added VRF support, fixed ipv4 address rendering +# 2021-08-02: rewritten for CMK 2.0 +# +# ToDo: state_peer_not_found, alias, min_uptime_levels in WATO +# +# snmpwalk sample +# +# thl@surfbox-ii:~$ snmpwalk -v2c -c router-01 -ObentU simulant .1.3.6.1.4.1.9.9.449.1.4.1.1 +# .1.3.6.1.4.1.9.9.449.1.4.1.1.2.65536.10.1 = INTEGER: 1 +# .1.3.6.1.4.1.9.9.449.1.4.1.1.2.65536.10.2 = INTEGER: 1 +# .1.3.6.1.4.1.9.9.449.1.4.1.1.2.65536.10.3 = INTEGER: 1 +# .1.3.6.1.4.1.9.9.449.1.4.1.1.3.65536.10.1 = Hex-STRING: 0A A7 DD 3A # can be also in the form of "10.10.10.10" :-( +# .1.3.6.1.4.1.9.9.449.1.4.1.1.3.65536.10.2 = Hex-STRING: 0A A7 DD 39 +# .1.3.6.1.4.1.9.9.449.1.4.1.1.3.65536.10.3 = Hex-STRING: 0A A7 DD 3C +# .1.3.6.1.4.1.9.9.449.1.4.1.1.4.65536.10.1 = INTEGER: 9 +# .1.3.6.1.4.1.9.9.449.1.4.1.1.4.65536.10.2 = INTEGER: 9 +# .1.3.6.1.4.1.9.9.449.1.4.1.1.4.65536.10.3 = INTEGER: 9 +# .1.3.6.1.4.1.9.9.449.1.4.1.1.5.65536.10.1 = Gauge32: 2 +# .1.3.6.1.4.1.9.9.449.1.4.1.1.5.65536.10.2 = Gauge32: 2 +# .1.3.6.1.4.1.9.9.449.1.4.1.1.5.65536.10.3 = Gauge32: 2 +# .1.3.6.1.4.1.9.9.449.1.4.1.1.6.65536.10.1 = STRING: "01:05:33" +# .1.3.6.1.4.1.9.9.449.1.4.1.1.6.65536.10.2 = STRING: "01:06:06" +# .1.3.6.1.4.1.9.9.449.1.4.1.1.6.65536.10.3 = STRING: "01:05:57" +# .1.3.6.1.4.1.9.9.449.1.4.1.1.7.65536.10.1 = Gauge32: 469 +# .1.3.6.1.4.1.9.9.449.1.4.1.1.7.65536.10.2 = Gauge32: 1 +# .1.3.6.1.4.1.9.9.449.1.4.1.1.7.65536.10.3 = Gauge32: 1 +# .1.3.6.1.4.1.9.9.449.1.4.1.1.8.65536.10.1 = Gauge32: 2814 +# .1.3.6.1.4.1.9.9.449.1.4.1.1.8.65536.10.2 = Gauge32: 100 +# .1.3.6.1.4.1.9.9.449.1.4.1.1.8.65536.10.3 = Gauge32: 100 +# .1.3.6.1.4.1.9.9.449.1.4.1.1.9.65536.10.1 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.4.1.1.9.65536.10.2 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.4.1.1.9.65536.10.3 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.4.1.1.10.65536.10.1 = Gauge32: 30 +# .1.3.6.1.4.1.9.9.449.1.4.1.1.10.65536.10.2 = Gauge32: 33 +# .1.3.6.1.4.1.9.9.449.1.4.1.1.10.65536.10.3 = Gauge32: 24 +# .1.3.6.1.4.1.9.9.449.1.4.1.1.11.65536.10.1 = STRING: "20.0/2.0" +# .1.3.6.1.4.1.9.9.449.1.4.1.1.11.65536.10.2 = STRING: "20.0/2.0" +# .1.3.6.1.4.1.9.9.449.1.4.1.1.11.65536.10.3 = STRING: "20.0/2.0" +# .1.3.6.1.4.1.9.9.449.1.4.1.1.12.65536.10.1 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.4.1.1.12.65536.10.2 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.4.1.1.12.65536.10.3 = Counter32: 0 +# .1.3.6.1.4.1.9.9.449.1.4.1.1.13.65536.10.1 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.4.1.1.13.65536.10.2 = Gauge32: 0 +# .1.3.6.1.4.1.9.9.449.1.4.1.1.13.65536.10.3 = Gauge32: 0 +# +# sample info +# [ +# [ +# [u'65536.10.0', u'1', u'\n\xa7\xdd7', u'10', u'2', u'01:05:34', u'1', u'100', u'0', u'147', u'23.0/2.0', u'0', u'0'] +# ], +# [ +# [u'1', u'FastEthernet0', u'6'], +# [u'2', u'FastEthernet1', u'6'], +# [u'3', u'FastEthernet2', u'6'], +# [u'4', u'FastEthernet3', u'6'], +# [u'5', u'FastEthernet4', u'6'], +# [u'6', u'VoIP-Null0', u'1'], +# [u'7', u'Null0', u'1'], +# [u'8', u'Vlan1', u'53'], +# [u'9', u'Loopback0', u'24'], +# [u'10', u'Tunnel100', u'131'] +# ] +# ] +# + +import re +from dataclasses import dataclass +from typing import List, Dict + +from cmk.base.plugins.agent_based.agent_based_api.v1 import ( + register, + Service, + Result, + check_levels, + State, + SNMPTree, + all_of, + contains, + exists, + OIDEnd, + Metric, + render, +) +from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( + DiscoveryResult, + CheckResult, + StringTable, +) + + +@dataclass +class EigrpPeer: + cEigrpPeerAS: str + cEigrpPeerAddr: str + cEigrpUpTimeSeconds: int + cEigrpSrtt: int + cEigrpRto: int + cEigrpPktsEnqueued: int + cEigrpRetrans: int + cEigrpRetries: int + cEigrpHoldTime: int + cEigrpVrfName: str + cEigrpVrfId: int + cEigrpAddrFammily: str + PeerInterface: str + + +_InetAddressType = { + 0: 'unknown', + 1: 'ipv4', + 2: 'ipv6', + 3: 'ipv4z', + 4: 'ipv6z', + 16: 'dns', +} + + +def _render_ip4_address(bytestring: str) -> str: + if len(bytestring) == 4: # byte sting length = 4 bytes + return ".".join(["%s" % ord(m) for m in bytestring]) + elif len(bytestring) >= 7: # ipv4 address > 4 octets + 3 dots + return bytestring + + +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) -> str: + address = ":".join(["%02s" % hex(ord(m))[2:] for m in bytestring]).replace(' ', '0').upper() + address = _shorten_ipv6_adress(address) + + return address + + +def _get_uptime(uptimestr: str) -> int: + uptime_in_sec = 0 + if 'y' in uptimestr and 'w' in uptimestr: + uptimestr = uptimestr.split('y') + uptime_in_sec = int(uptimestr[0]) * 31536000 # 365 * 24 * 3600, one year + uptime_in_sec += int(uptimestr[1][:-1]) * 604800 # 7 * 24 * 3600, one week + if 'w' in uptimestr and 'd' in uptimestr: + uptimestr = uptimestr.split('w') + uptime_in_sec = int(uptimestr[0]) * 604800 # 7 * 24 * 3600, one week + uptime_in_sec += int(uptimestr[1][:-1]) * 86400 # 24 * 3600, on day + elif 'd' in uptimestr and 'h' in uptimestr: + uptimestr = uptimestr.split('d') + uptime_in_sec = int(uptimestr[0]) * 86400 # 24 * 3600, one day + uptime_in_sec += int(uptimestr[1][:-1]) * 3600 + elif ':' in uptimestr: + uptime_in_sec = uptimestr.split(':') + if len(uptime_in_sec) == 3: # [19:20:00] hours:minutes:seconds + uptime_in_sec = (int(uptime_in_sec[0]) * 3600) + (int(uptime_in_sec[1]) * 60) + int(uptime_in_sec[2]) + elif len(uptime_in_sec) == 4: # [10:19:20:00] days(?):hours:minutes:seconds + uptime_in_sec = (int(uptime_in_sec[0]) * 86400) + \ + (int(uptime_in_sec[1]) * 3600) + \ + (int(uptime_in_sec[2]) * 60) + \ + int(uptime_in_sec[3]) + + return uptime_in_sec + +########################################################################### +# +# DATA Parser function +# +########################################################################### + + +def parse_cisco_eigrp_peers(string_table: List[StringTable]) -> Dict[str, EigrpPeer]: + peers, peer_interfaces, VrfInfo = string_table + PeerTable = {} + + for peer in peers: + OID_END, cEigrpPeerAddrType, cEigrpPeerAddr, cEigrpPeerIfIndex, cEigrpHoldTime, cEigrpUpTime, cEigrpSrtt, \ + cEigrpRto, cEigrpPktsEnqueued, cEigrpRetrans, cEigrpRetries = peer + + cEigrpVrfId = int(OID_END.split('.')[0]) + cEigrpPeerAS = OID_END.split('.')[1] + cEigrpAddrFammily = int(OID_END.split('.')[2]) + if int(cEigrpPeerAddrType) == 1: # IPv4 address + cEigrpPeerAddr = _render_ip4_address(cEigrpPeerAddr) + elif int(cEigrpPeerAddrType) == 2: # IPv4 address + cEigrpPeerAddr = _render_ipv6_address(cEigrpPeerAddr) + + PeerInterface = '' + for ifIndex, ifDescr, ifType in peer_interfaces: + if int(cEigrpPeerIfIndex) == int(ifIndex): + PeerInterface = ifDescr + + cEigrpVrfName = '' + for VrfId, VrfName in VrfInfo: + if int(VrfId) == cEigrpVrfId: + cEigrpVrfName = VrfName + + if cEigrpAddrFammily == 2: + cEigrpAddrFammily = 'IPv6' + else: + cEigrpAddrFammily = 'IPv4' + + if cEigrpVrfName == 'default': + ServiceText = f'{cEigrpPeerAddr} on AS {cEigrpPeerAS}' + else: + ServiceText = f'{cEigrpPeerAddr} on AS {cEigrpPeerAS}, VRF {cEigrpVrfName}' + + PeerTable[ServiceText] = EigrpPeer( + cEigrpPeerAS=cEigrpPeerAS, + cEigrpPeerAddr=cEigrpPeerAddr, + cEigrpUpTimeSeconds=_get_uptime(cEigrpUpTime), + cEigrpSrtt=int(cEigrpSrtt), + cEigrpRto=int(cEigrpRto), + cEigrpPktsEnqueued=int(cEigrpPktsEnqueued), + cEigrpRetrans=int(cEigrpRetrans), + cEigrpRetries=int(cEigrpRetries), + cEigrpHoldTime=int(cEigrpHoldTime), + cEigrpVrfName=cEigrpVrfName, + cEigrpVrfId=cEigrpVrfId, + cEigrpAddrFammily=cEigrpAddrFammily, + PeerInterface=PeerInterface, + ) + + return PeerTable + + +########################################################################### +# +# INVENTORY function +# +########################################################################### + + +def discovery_cisco_eigrp_peers(section: Dict[str, EigrpPeer]) -> DiscoveryResult: + for key in section: + yield Service(item=key) + + +########################################################################### +# +# CHECK function +# +########################################################################### + + +def check_cisco_eigrp_peers(item, params, section: Dict[str, EigrpPeer]) -> CheckResult: + try: + peer = section[item] + except KeyError: + yield Result(state=State.CRIT, summary='Peer not found in SNMP data') + return + + yield from check_levels( + label='Uptime', + value=peer.cEigrpUpTimeSeconds, + metric_name='uptime', + render_func=render.timespan, + levels_lower=params['minuptime'] + ) + + yield Result(state=State.OK, notice=f'On interface: {peer.PeerInterface}') + yield Result(state=State.OK, notice=f'Hold time: {peer.cEigrpHoldTime}') + + for key, value in [ + ('cEigrpPktsEnqueued', peer.cEigrpPktsEnqueued), + ('cEigrpRetrans', peer.cEigrpRetrans), + ('cEigrpRetries', peer.cEigrpRetries), + ('cEigrpSrtt', peer.cEigrpSrtt), + ('cEigrpRto', peer.cEigrpRto), + ]: + yield Metric(name=f'cisco_eigrp_peers_{key}', value=value) + + +########################################################################### +# +# CHECK info +# +########################################################################### + +register.snmp_section( + name='cisco_eigrp_peers', + parse_function=parse_cisco_eigrp_peers, + fetch=[ + SNMPTree( + base='.1.3.6.1.4.1.9.9.449.1.4.1.1', # + oids=[ + OIDEnd(), # u'65536.10.0' + '2', # cEigrpPeerAddrType + '3', # cEigrpPeerAddr + '4', # cEigrpPeerIfIndex + '5', # cEigrpHoldTime + '6', # cEigrpUpTime + '7', # cEigrpSrtt + '8', # cEigrpRto + '9', # cEigrpPktsEnqueued + '12', # cEigrpRetrans + '13', # cEigrpRetries + # '10', # cEigrpLastSeq + # '11', # cEigrpVersion + ] + ), + SNMPTree( + base='.1.3.6.1.2.1.2.2.1', # + oids=[ + '1', # ifIndex + '2', # ifDescr + '3', # ifType + ] + ), + SNMPTree( + base='.1.3.6.1.4.1.9.9.449.1.1.1.1', # + oids=[ + OIDEnd(), # VRF ID + '2', # cEigrpVpnName + ] + ), + ], + detect=all_of( + contains('.1.3.6.1.2.1.1.1.0', 'cisco'), # sysDescr + exists('.1.3.6.1.4.1.9.9.449.1.4.1.1.*'), # CISCO-EIGRP-MIB::cEigrpPeerEntry + )) + +register.check_plugin( + name='cisco_eigrp_peers', + service_name='EIGRP peer %s', + discovery_function=discovery_cisco_eigrp_peers, + check_function=check_cisco_eigrp_peers, + check_default_parameters={ + 'minuptime': (3600, 7200) + }, + check_ruleset_name='cisco_eigrp_peers', +) diff --git a/agent_based/cisco_eigrp_topology_table.py b/agent_based/cisco_eigrp_topology_table.py new file mode 100644 index 0000000..7321176 --- /dev/null +++ b/agent_based/cisco_eigrp_topology_table.py @@ -0,0 +1,488 @@ +#!/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 : 2017-17-20 +# +# Monitor status of Cisco EIGRP Topology Table +# +# 2018-02-11: removed unnecessary OIDs +# 2018-08-06: modified scan function +# 2019-10-16: added initial IPv6 support, added parser function +# +# +# snmpwalk sample +# +# OMD[mysite]:~$ snmpwalk -ObentU -v2c -c router-01 simulant .1.3.6.1.4.1.9.9.449.1.3.1.1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.5.65536.10.1.4.0.0.0.0.0 = INTEGER: 2 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.5.65536.10.1.4.10.167.220.0.28 = INTEGER: 2 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.5.65536.10.1.4.10.167.220.16.28 = INTEGER: 2 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.5.65536.10.1.4.10.167.220.48.30 = INTEGER: 2 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.5.65536.10.1.4.10.167.220.249.32 = INTEGER: 2 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.5.65536.10.1.4.10.167.220.254.32 = INTEGER: 2 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.5.65536.10.1.4.10.167.220.255.32 = INTEGER: 2 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.5.65536.10.1.4.10.167.221.0.24 = INTEGER: 2 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.5.65536.10.1.4.10.167.222.0.24 = INTEGER: 2 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.5.65536.10.1.4.192.168.50.0.24 = INTEGER: 2 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.5.65536.10.1.4.192.168.55.0.24 = INTEGER: 2 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.6.65536.10.1.4.0.0.0.0.0 = INTEGER: 2 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.6.65536.10.1.4.10.167.220.0.28 = INTEGER: 2 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.6.65536.10.1.4.10.167.220.16.28 = INTEGER: 2 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.6.65536.10.1.4.10.167.220.48.30 = INTEGER: 2 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.6.65536.10.1.4.10.167.220.249.32 = INTEGER: 2 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.6.65536.10.1.4.10.167.220.254.32 = INTEGER: 2 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.6.65536.10.1.4.10.167.220.255.32 = INTEGER: 2 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.6.65536.10.1.4.10.167.221.0.24 = INTEGER: 2 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.6.65536.10.1.4.10.167.222.0.24 = INTEGER: 2 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.6.65536.10.1.4.192.168.50.0.24 = INTEGER: 2 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.6.65536.10.1.4.192.168.55.0.24 = INTEGER: 2 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.7.65536.10.1.4.0.0.0.0.0 = Gauge32: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.7.65536.10.1.4.10.167.220.0.28 = Gauge32: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.7.65536.10.1.4.10.167.220.16.28 = Gauge32: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.7.65536.10.1.4.10.167.220.48.30 = Gauge32: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.7.65536.10.1.4.10.167.220.249.32 = Gauge32: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.7.65536.10.1.4.10.167.220.254.32 = Gauge32: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.7.65536.10.1.4.10.167.220.255.32 = Gauge32: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.7.65536.10.1.4.10.167.221.0.24 = Gauge32: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.7.65536.10.1.4.10.167.222.0.24 = Gauge32: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.7.65536.10.1.4.192.168.50.0.24 = Gauge32: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.7.65536.10.1.4.192.168.55.0.24 = Gauge32: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.8.65536.10.1.4.0.0.0.0.0 = Gauge32: 28416 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.8.65536.10.1.4.10.167.220.0.28 = Gauge32: 28416 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.8.65536.10.1.4.10.167.220.16.28 = Gauge32: 2816 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.8.65536.10.1.4.10.167.220.48.30 = Gauge32: 27904 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.8.65536.10.1.4.10.167.220.249.32 = Gauge32: 1915392 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.8.65536.10.1.4.10.167.220.254.32 = Gauge32: 128256 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.8.65536.10.1.4.10.167.220.255.32 = Gauge32: 155904 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.8.65536.10.1.4.10.167.221.0.24 = Gauge32: 1787392 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.8.65536.10.1.4.10.167.222.0.24 = Gauge32: 3523840 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.8.65536.10.1.4.192.168.50.0.24 = Gauge32: 2816 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.8.65536.10.1.4.192.168.55.0.24 = Gauge32: 28416 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.9.65536.10.1.4.0.0.0.0.0 = STRING: "Summary" +# .1.3.6.1.4.1.9.9.449.1.3.1.1.9.65536.10.1.4.10.167.220.0.28 = STRING: "Internal" +# .1.3.6.1.4.1.9.9.449.1.3.1.1.9.65536.10.1.4.10.167.220.16.28 = STRING: "Connected" +# .1.3.6.1.4.1.9.9.449.1.3.1.1.9.65536.10.1.4.10.167.220.48.30 = STRING: "Connected" +# .1.3.6.1.4.1.9.9.449.1.3.1.1.9.65536.10.1.4.10.167.220.249.32 = STRING: "Internal" +# .1.3.6.1.4.1.9.9.449.1.3.1.1.9.65536.10.1.4.10.167.220.254.32 = STRING: "Connected" +# .1.3.6.1.4.1.9.9.449.1.3.1.1.9.65536.10.1.4.10.167.220.255.32 = STRING: "Internal" +# .1.3.6.1.4.1.9.9.449.1.3.1.1.9.65536.10.1.4.10.167.221.0.24 = STRING: "Internal" +# .1.3.6.1.4.1.9.9.449.1.3.1.1.9.65536.10.1.4.10.167.222.0.24 = STRING: "Connected" +# .1.3.6.1.4.1.9.9.449.1.3.1.1.9.65536.10.1.4.192.168.50.0.24 = STRING: "Rstatic" +# .1.3.6.1.4.1.9.9.449.1.3.1.1.9.65536.10.1.4.192.168.55.0.24 = STRING: "External" +# .1.3.6.1.4.1.9.9.449.1.3.1.1.10.65536.10.1.4.0.0.0.0.0 = INTEGER: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.10.65536.10.1.4.10.167.220.0.28 = INTEGER: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.10.65536.10.1.4.10.167.220.16.28 = INTEGER: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.10.65536.10.1.4.10.167.220.48.30 = INTEGER: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.10.65536.10.1.4.10.167.220.249.32 = INTEGER: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.10.65536.10.1.4.10.167.220.254.32 = INTEGER: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.10.65536.10.1.4.10.167.220.255.32 = INTEGER: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.10.65536.10.1.4.10.167.221.0.24 = INTEGER: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.10.65536.10.1.4.10.167.222.0.24 = INTEGER: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.10.65536.10.1.4.192.168.50.0.24 = INTEGER: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.10.65536.10.1.4.192.168.55.0.24 = INTEGER: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.11.65536.10.1.4.0.0.0.0.0 = Hex-STRING: 00 00 00 00 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.11.65536.10.1.4.10.167.220.0.28 = Hex-STRING: 0A A7 DC 31 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.11.65536.10.1.4.10.167.220.16.28 = Hex-STRING: 00 00 00 00 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.11.65536.10.1.4.10.167.220.48.30 = Hex-STRING: 00 00 00 00 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.11.65536.10.1.4.10.167.220.249.32 = Hex-STRING: 0A A7 DE 3A +# .1.3.6.1.4.1.9.9.449.1.3.1.1.11.65536.10.1.4.10.167.220.254.32 = Hex-STRING: 00 00 00 00 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.11.65536.10.1.4.10.167.220.255.32 = Hex-STRING: 0A A7 DC 31 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.11.65536.10.1.4.10.167.221.0.24 = Hex-STRING: 0A A7 DE 3A +# .1.3.6.1.4.1.9.9.449.1.3.1.1.11.65536.10.1.4.10.167.222.0.24 = Hex-STRING: 00 00 00 00 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.11.65536.10.1.4.192.168.50.0.24 = Hex-STRING: 00 00 00 00 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.11.65536.10.1.4.192.168.55.0.24 = Hex-STRING: 0A A7 DC 31 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.12.65536.10.1.4.0.0.0.0.0 = INTEGER: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.12.65536.10.1.4.10.167.220.0.28 = INTEGER: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.12.65536.10.1.4.10.167.220.16.28 = INTEGER: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.12.65536.10.1.4.10.167.220.48.30 = INTEGER: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.12.65536.10.1.4.10.167.220.249.32 = INTEGER: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.12.65536.10.1.4.10.167.220.254.32 = INTEGER: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.12.65536.10.1.4.10.167.220.255.32 = INTEGER: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.12.65536.10.1.4.10.167.221.0.24 = INTEGER: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.12.65536.10.1.4.10.167.222.0.24 = INTEGER: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.12.65536.10.1.4.192.168.50.0.24 = INTEGER: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.12.65536.10.1.4.192.168.55.0.24 = INTEGER: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.13.65536.10.1.4.0.0.0.0.0 = Hex-STRING: 00 00 00 00 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.13.65536.10.1.4.10.167.220.0.28 = Hex-STRING: 0A A7 DC 31 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.13.65536.10.1.4.10.167.220.16.28 = Hex-STRING: 00 00 00 00 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.13.65536.10.1.4.10.167.220.48.30 = Hex-STRING: 00 00 00 00 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.13.65536.10.1.4.10.167.220.249.32 = Hex-STRING: 0A A7 DE 3A +# .1.3.6.1.4.1.9.9.449.1.3.1.1.13.65536.10.1.4.10.167.220.254.32 = Hex-STRING: 00 00 00 00 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.13.65536.10.1.4.10.167.220.255.32 = Hex-STRING: 0A A7 DC 31 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.13.65536.10.1.4.10.167.221.0.24 = Hex-STRING: 0A A7 DE 3A +# .1.3.6.1.4.1.9.9.449.1.3.1.1.13.65536.10.1.4.10.167.222.0.24 = Hex-STRING: 00 00 00 00 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.13.65536.10.1.4.192.168.50.0.24 = Hex-STRING: 0A A7 DC 11 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.13.65536.10.1.4.192.168.55.0.24 = Hex-STRING: 0A A7 DC 31 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.14.65536.10.1.4.0.0.0.0.0 = STRING: "Null0" +# .1.3.6.1.4.1.9.9.449.1.3.1.1.14.65536.10.1.4.10.167.220.0.28 = STRING: "Tunnel10" +# .1.3.6.1.4.1.9.9.449.1.3.1.1.14.65536.10.1.4.10.167.220.16.28 = STRING: "GigabitEthernet0/0/0" +# .1.3.6.1.4.1.9.9.449.1.3.1.1.14.65536.10.1.4.10.167.220.48.30 = STRING: "Tunnel10" +# .1.3.6.1.4.1.9.9.449.1.3.1.1.14.65536.10.1.4.10.167.220.249.32 = STRING: "Tunnel101" +# .1.3.6.1.4.1.9.9.449.1.3.1.1.14.65536.10.1.4.10.167.220.254.32 = STRING: "Loopback0" +# .1.3.6.1.4.1.9.9.449.1.3.1.1.14.65536.10.1.4.10.167.220.255.32 = STRING: "Tunnel10" +# .1.3.6.1.4.1.9.9.449.1.3.1.1.14.65536.10.1.4.10.167.221.0.24 = STRING: "Tunnel101" +# .1.3.6.1.4.1.9.9.449.1.3.1.1.14.65536.10.1.4.10.167.222.0.24 = STRING: "Tunnel101" +# .1.3.6.1.4.1.9.9.449.1.3.1.1.14.65536.10.1.4.192.168.50.0.24 = "" +# .1.3.6.1.4.1.9.9.449.1.3.1.1.14.65536.10.1.4.192.168.55.0.24 = STRING: "Tunnel10" +# .1.3.6.1.4.1.9.9.449.1.3.1.1.15.65536.10.1.4.0.0.0.0.0 = Gauge32: 2816 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.15.65536.10.1.4.10.167.220.0.28 = Gauge32: 28416 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.15.65536.10.1.4.10.167.220.16.28 = Gauge32: 2816 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.15.65536.10.1.4.10.167.220.48.30 = Gauge32: 27904 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.15.65536.10.1.4.10.167.220.249.32 = Gauge32: 3651840 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.15.65536.10.1.4.10.167.220.254.32 = Gauge32: 128256 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.15.65536.10.1.4.10.167.220.255.32 = Gauge32: 155904 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.15.65536.10.1.4.10.167.221.0.24 = Gauge32: 4035840 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.15.65536.10.1.4.10.167.222.0.24 = Gauge32: 3523840 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.15.65536.10.1.4.192.168.50.0.24 = Gauge32: 2816 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.15.65536.10.1.4.192.168.55.0.24 = Gauge32: 28416 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.16.65536.10.1.4.0.0.0.0.0 = Gauge32: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.16.65536.10.1.4.10.167.220.0.28 = Gauge32: 2816 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.16.65536.10.1.4.10.167.220.16.28 = Gauge32: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.16.65536.10.1.4.10.167.220.48.30 = Gauge32: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.16.65536.10.1.4.10.167.220.249.32 = Gauge32: 128256 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.16.65536.10.1.4.10.167.220.254.32 = Gauge32: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.16.65536.10.1.4.10.167.220.255.32 = Gauge32: 128256 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.16.65536.10.1.4.10.167.221.0.24 = Gauge32: 1761792 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.16.65536.10.1.4.10.167.222.0.24 = Gauge32: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.16.65536.10.1.4.192.168.50.0.24 = Gauge32: 1 +# .1.3.6.1.4.1.9.9.449.1.3.1.1.16.65536.10.1.4.192.168.55.0.24 = Gauge32: 2816 +# +# sample info +# [ +# [u'65536.10.1.4.0.0.0.0.0', u'2', u'2', u'External', u'1', u'\n\xa7\xdd7', u'1', u'\n\xa7\xdd7', u'Tunnel100'], +# [u'65536.10.1.4.10.167.220.0.28', u'2', u'2', u'Internal', u'1', u'\n\xa7\xdd7', u'1', u'\n\xa7\xdd7', u'Tunnel100'], +# [u'65536.10.1.4.10.167.220.249.32', u'2', u'2', u'Internal', u'1', u'\n\xa7\xdd7', u'1', u'\n\xa7\xdd:', u'Tunnel100'], +# [u'65536.10.1.4.10.167.220.252.32', u'2', u'2', u'Internal', u'1', u'\n\xa7\xdd7', u'1', u'\n\xa7\xdd<', u'Tunnel100'], +# [u'65536.10.1.4.10.167.220.253.32', u'2', u'2', u'Connected', u'1', u'\x00\x00\x00\x00', u'1', u'\x00\x00\x00\x00', u'Loopback0'], +# [u'65536.10.1.4.10.167.220.255.32', u'2', u'2', u'Internal', u'1', u'\n\xa7\xdd7', u'1', u'\n\xa7\xdd7', u'Tunnel100'], +# [u'65536.10.1.4.10.167.221.0.24', u'2', u'2', u'Connected', u'1', u'\x00\x00\x00\x00', u'1', u'\x00\x00\x00\x00', u'Tunnel100'], +# [u'65536.10.1.4.192.168.57.0.24', u'2', u'2', u'Connected', u'1', u'\x00\x00\x00\x00', u'1', u'\x00\x00\x00\x00', u'Vlan1'], +# [u'65536.10.1.4.192.168.58.0.24', u'2', u'2', u'Internal', u'1', u'\n\xa7\xdd7', u'1', u'\n\xa7\xdd:', u'Tunnel100'], +# [u'65536.10.1.4.192.168.60.0.24', u'2', u'2', u'Internal', u'1', u'\n\xa7\xdd7', u'1', u'\n\xa7\xdd<', u'Tunnel100'] +# ] +# + + +import re +from dataclasses import dataclass +from typing import Optional, List + +from cmk.base.plugins.agent_based.agent_based_api.v1 import ( + register, + Service, + Result, + State, + SNMPTree, + all_of, + contains, + exists, + OIDEnd, + Metric, +) +from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( + DiscoveryResult, + CheckResult, + StringTable, +) + +_InetAddressType = { + 0: 'unknown', + 1: 'ipv4', + 2: 'ipv6', + 3: 'ipv4z', + 4: 'ipv6z', + 16: 'dns', +} + + +def _render_ip_route(route): # sample: '65536.10.1.4.192.168.55.0.24' + route = route.split('.') + if int(route[2]) == 1: # ipv4 route + masklength + route = '.'.join(route[4:8]) + '/' + route[8] + # 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 + elif int(route[2]) == 2: # IPv6 131072.10.2.16.48.0.0.0.0.0.0.0.0.3.0.3.0.3.0.32.128 + for i in range(4, 20, 1): + route[i] = '%02s' % hex(int(route[i]))[2:] + address = ':'.join(route[4:20]).replace(' ', '0').upper() + route = _shorten_ipv6_adress(address) + '/' + route[20] + else: + route = 'unknown address type' + return route # sample: 192.168.55.0/24 + + +def _render_ip4_address(bytestring): + if len(bytestring) == 4: # byte sting length = 4 bytes + return '.'.join(['%s' % ord(m) for m in bytestring]) + elif len(bytestring) >= 7: # ipv4 address > 4 octets + 3 dots + return bytestring + + +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 + + +########################################################################### +# +# DATA Parser function +# +########################################################################### + + +def parse_cisco_eigrp_topology_table(string_table: List[StringTable]): + TopologieTable = [] + ASTable = {} + Topologie, VrfInfo = string_table + + # 65536.<AS number>.1.4.<network>.<cidr> --> 65536.10.1.4.10.167.220.0.28 + for OID_END, cEigrpActive, cEigrpStuckInActive, cEigrpRouteOriginType, cEigrpRouteOriginAddrType, \ + cEigrpRouteOriginAddr, cEigrpNextHopAddressType, cEigrpNextHopAddress, cEigrpNextHopInterface in Topologie: + + cEigrpVrfId = int(OID_END.split('.')[0]) + cEigrpAS = OID_END.split('.')[1] + cEigrpAddrFammily = int(OID_END.split('.')[2]) + cEigrpRoute = _render_ip_route(OID_END) + + if int(cEigrpRouteOriginAddrType) == 1: # IPv4 + cEigrpRouteOriginAddr = _render_ip4_address(cEigrpRouteOriginAddr) + elif int(cEigrpRouteOriginAddrType) == 2: # IPv6 + cEigrpRouteOriginAddr = _render_ipv6_address(cEigrpRouteOriginAddr) + + if int(cEigrpNextHopAddressType) == 1: # IPv4 + cEigrpNextHopAddress = _render_ip4_address(cEigrpNextHopAddress) + if int(cEigrpNextHopAddressType) == 2: # IPv6 + cEigrpNextHopAddress = _render_ipv6_address(cEigrpNextHopAddress) + + cEigrpVrfName = '' + for VrfId, VrfName in VrfInfo: + if int(VrfId) == cEigrpVrfId: + cEigrpVrfName = VrfName + + if cEigrpAddrFammily == 2: + cEigrpAddrFammily = 'IPv6' + else: + cEigrpAddrFammily = 'IPv4' + + TopologieTable.append({ + 'cEigrpActive': int(cEigrpActive), + 'cEigrpStuckInActive': int(cEigrpStuckInActive), + 'cEigrpRouteOriginType': cEigrpRouteOriginType, + 'cEigrpRouteOriginAddr': cEigrpRouteOriginAddr, + 'cEigrpNextHopAddress': cEigrpNextHopAddress, + 'cEigrpNextHopInterface': cEigrpNextHopInterface, + 'cEigrpVrfName': cEigrpVrfName, + 'cEigrpAddrFammily': cEigrpAddrFammily, + 'cEigrpVrfId': cEigrpVrfId, + 'cEigrpAS': cEigrpAS, + 'cEigrpRoute': cEigrpRoute, + }) + + if cEigrpVrfName == 'default' and cEigrpAddrFammily == 'IPv4': + ASTable.update({cEigrpVrfId: {'ServiceText': cEigrpAS, 'cEigrpVrfId': cEigrpVrfId}}) + elif cEigrpVrfName == 'default' and cEigrpAddrFammily == 'IPv6': + ASTable.update({cEigrpVrfId: {'ServiceText': f'{cEigrpAS} IPv6', 'cEigrpVrfId': cEigrpVrfId}}) + elif cEigrpVrfName != 'default' and cEigrpAddrFammily == 'IPv4': + ASTable.update( + {cEigrpVrfId: {'ServiceText': f'{cEigrpAS} VRF {cEigrpVrfName}', 'cEigrpVrfId': cEigrpVrfId}}) + elif cEigrpVrfName != 'default' and cEigrpAddrFammily == 'IPv6': + ASTable.update({cEigrpVrfId: {'ServiceText': f'{cEigrpAS} IPv6 VRF {cEigrpVrfName}', + 'cEigrpVrfId': cEigrpVrfId}}) + + return [ASTable, TopologieTable] + + +########################################################################### +# +# INVENTORY function +# +########################################################################### + + +def discovery_cisco_eigrp_topology_table(section) -> DiscoveryResult: + ASTable, TopologieTable = section + ASTable = dict(ASTable) + for Key in ASTable.keys(): + ServiceText = ASTable.get(Key).get('ServiceText') + yield Service(item=ServiceText) + + +########################################################################### +# +# CHECK function +# +########################################################################### + + +def check_cisco_eigrp_topology_table(item, params, section) -> CheckResult: + ASTable, TopologieTable = section + ASTable = dict(ASTable) + cEigrpVrfId = '' + for Key in ASTable.keys(): + ServiceText = ASTable.get(Key).get('ServiceText') + if ServiceText == item: + cEigrpVrfId = ASTable.get(Key).get('cEigrpVrfId') + + if cEigrpVrfId != '': + routes = [] + activeroutes = [] + siaroutes = [] + origintypes = { + 'Static_redistributed': 0, + 'Internal': 0, + 'Connected': 0, + 'External': 0, + 'Summary': 0, + 'InetAddress': 0, + } + + for route in TopologieTable: + if route.get('cEigrpVrfId') == cEigrpVrfId: + cEigrpActive = route.get('cEigrpActive') + cEigrpStuckInActive = route.get('cEigrpStuckInActive') + cEigrpRouteOriginType = route.get('cEigrpRouteOriginType') + + # count routes + routes.append(route) + + # count active routes + if cEigrpActive == 1: + activeroutes.append(route) + + # count sia routes + if cEigrpStuckInActive == 1: + siaroutes.append(route) + + # count origin types (Rstatic, Internal, Connected, External, Summary, and so on...) + if cEigrpRouteOriginType == 'Rstatic': + cEigrpRouteOriginType = 'Static_redistributed' + origintype = 0 + if origintypes.get(cEigrpRouteOriginType): + origintype = origintypes.get(cEigrpRouteOriginType) + origintype += 1 + origintypes.update({cEigrpRouteOriginType: origintype}) + + if not len(routes) == 0: + yield Result(state=State.OK, summary=f'Routes in topology table: {len(routes)}') + + if not len(activeroutes) == 0: + if 'activeroutes' not in params.get('nowarnon'): + yield Result(state=State.WARN, notice='Active route found') + for route in activeroutes: + cEigrpRoute = route.get('cEigrpRoute') + cEigrpRouteOriginAddr = route.get('cEigrpRouteOriginAddr') + cEigrpNextHopAddress = route.get('cEigrpNextHopAddress') + cEigrpNextHopInterface = route.get('cEigrpNextHopInterface') + + yield Result( + state=State.OK, + notice=f'Active Route: {cEigrpRoute}, ' + f'originated by {cEigrpRouteOriginAddr} ' + f'on interface: {cEigrpNextHopInterface}, ' + f'next hop: {cEigrpNextHopAddress}' + ) + + if not len(siaroutes) == 0: + if 'siaroutes' not in params.get('nowarnon'): + yield Result(state=State.WARN, notice='Stuck in active (SIA) route found') + + for route in siaroutes: + cEigrpRoute = route.get('cEigrpRoute') + cEigrpRouteOriginAddr = route.get('cEigrpRouteOriginAddr') + cEigrpNextHopAddress = route.get('cEigrpNextHopAddress') + cEigrpNextHopInterface = route.get('cEigrpNextHopInterface') + + yield Result( + state=State.OK, + notice=f'SIA Route: {cEigrpRoute}, ' + f'originated by {cEigrpRouteOriginAddr} ' + f'on interface: {cEigrpNextHopInterface}, ' + f'next hop: {cEigrpNextHopAddress}' + ) + + for key, value in [ + ('activeroutes', len(activeroutes)), + ('siaroutes', len(siaroutes)), + ('routes', len(routes)), + ]: + yield Metric(name=f'cisco_eigrp_topology_table_{key}', value=value) + + # append perfdata for routes by origin type + for origintype in origintypes: + yield Metric(name=f'cisco_eigrp_topology_table_{origintype}', value=origintypes.get(origintype)) + + +########################################################################### +# +# CHECK info +# +########################################################################### + + +register.snmp_section( + name='cisco_eigrp_topology_table', + parse_function=parse_cisco_eigrp_topology_table, + fetch=[ + SNMPTree( + base='.1.3.6.1.4.1.9.9.449.1.3.1.1', # + oids=[ + OIDEnd(), # 65536.<AS number>.1.4.<network>.<cidr> --> 65536.10.1.4.10.167.220.0.28 + '5', # cEigrpActive [1] + '6', # cEigrpStuckInActive [2] + '9', # cEigrpRouteOriginType [3] + '10', # cEigrpRouteOriginAddrType [4] + '11', # cEigrpRouteOriginAddr [5] + '12', # cEigrpNextHopAddressType [6] + '13', # cEigrpNextHopAddress [7] + '14', # cEigrpNextHopInterface [8] + # '7', # cEigrpDestSuccessors [] + # '8', # cEigrpFdistance [] + # '16', # cEigrpReportDistance [] + # '15', # cEigrpDistance [] + ] + ), + SNMPTree( + base='.1.3.6.1.4.1.9.9.449.1.1.1.1', # + oids=[ + OIDEnd(), # VRF ID + '2', # cEigrpVpnName [0] + ] + ) + ], + detect=all_of( + contains('.1.3.6.1.2.1.1.1.0', 'cisco'), # sysDescr + exists('.1.3.6.1.4.1.9.9.449.1.3.1.1.*'), # CISCO-EIGRP-MIB::cEigrpTopoEntry + )) + +register.check_plugin( + name='cisco_eigrp_topology_table', + service_name='EIGRP topology table AS %s', + discovery_function=discovery_cisco_eigrp_topology_table, + check_function=check_cisco_eigrp_topology_table, + check_default_parameters={ + 'nowarnon': []}, + check_ruleset_name='cisco_eigrp_topology_table', +) diff --git a/cisco_eigrp.mkp b/cisco_eigrp.mkp index 659dae5c9257e331f6d973a414774dfae0955734..dfcb2748b97ed2902a71a4be6a6fca09644671a3 100644 GIT binary patch literal 14416 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 PK=4rP+B%S3P>}xuH5exj literal 15338 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 W;Q#)QV3qfSNHEf>53~ym^nU;XsM*;7 diff --git a/packages/cisco_eigrp b/packages/cisco_eigrp index 627b2d4..76361d4 100644 --- a/packages/cisco_eigrp +++ b/packages/cisco_eigrp @@ -1,17 +1,26 @@ -{'author': u'Th.L. (thl-cmk[at]outlook[dot]com)', - 'description': u'Monitor Cisco EIGRP. Checks:\n - cisco_eigrp_peers: creates one service for the every IPv4/IPV6 peer, AS and VRF\n - cisco_eigrp_as_info: creates one service per autonomous system (AS)\n - cisco_eigrp_interface: creates one service per EIGRP enabled interface and AS\n - cisco_eigrp_topology_table: creates one service per topology table, AS and VRF\n', +{'author': 'Th.L. (thl-cmk[at]outlook[dot]com)', + 'description': 'Monitor Cisco EIGRP. Checks:\n' + ' - cisco_eigrp_peers: creates one service for the every ' + 'IPv4/IPV6 peer, AS and VRF\n' + ' - cisco_eigrp_as_info: creates one service per autonomous ' + 'system (AS)\n' + ' - cisco_eigrp_interface: creates one service per EIGRP ' + 'enabled interface and AS\n' + ' - cisco_eigrp_topology_table: creates one service per ' + 'topology table, AS and VRF\n', 'download_url': 'https://thl-cmk.hopto.org', - 'files': {'checks': ['cisco_eigrp_peers', - 'cisco_eigrp_as_info', - 'cisco_eigrp_interface', - 'cisco_eigrp_topology_table'], + 'files': {'agent_based': ['cisco_eigrp_as_info.py', + 'cisco_eigrp_interface.py', + 'cisco_eigrp_peers.py', + 'cisco_eigrp_topology_table.py'], 'web': ['plugins/wato/cisco_eigrp_peers.py', 'plugins/wato/cisco_eigrp_interface.py', 'plugins/wato/cisco_eigrp_topology_table.py', 'plugins/metrics/cisco_eigrp.py']}, 'name': 'cisco_eigrp', 'num_files': 8, - 'title': u'Cisco EIGRP checks', - 'version': '20191021v.0.2a', - 'version.min_required': '1.2.8b8', - 'version.packaged': '1.4.0p35'} \ No newline at end of file + 'title': 'Cisco EIGRP checks', + 'version': '20210803v.0.3', + '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/cisco_eigrp.py b/web/plugins/metrics/cisco_eigrp.py index a572e40..f2b52d0 100644 --- a/web/plugins/metrics/cisco_eigrp.py +++ b/web/plugins/metrics/cisco_eigrp.py @@ -1,49 +1,25 @@ -#!/usr/bin/python -# -*- encoding: utf-8; py-indent-offset: 4 -*- +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- # -# Cisco EIGRP Peer metrics plugin +# License: GNU General Public License v2 # -# Author: Th.L. +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org # Date : 2017-12-26 # -# replaces : -# local/share/check_mk/pnp-templates/check_mk-cisco_eigrp_peers.php -# local/share/check_mk/web/plugins/perfometer/cisco_eigrp_peers.py +# Cisco EIGRP Peer metrics plugin # # missing a way to include hostname and service description # -# key a green 11/a 21/a 31/a 41/a 12/a 22/a 32/a 42/a -colors_a = ['#80F000', '#a500ff', '#ffc600', '#00ffb2', '#0075ff', '#cc00ff', '#ffd600', '#00ffff', '#0047ff', - # 13/a 23/a 33/a 43/a 14/a 24/a 34/a 44/a 15/a - '#f900ff', '#ffed00', '#00e8ff', '#000aff', '#ff4c00', '#e2ff00', '#00d1ff', '#4200ff', '#ff7a00', - # 25/a 35/a 45/a 16/a 26/a 36/a 46/a 51/a 52/a - '#bcff00', '#00b2ff', '#6000ff', '#ffa000', '#7fff00', '#0093ff', '#7f00ff', '#7f7f7f', '#7f4a26', - # 53/a - '#8c531c'] -# key b green 11/b 21/b 31/b 41/b 12/b 22/b 32/b 42/b -colors_b = ['#80F000', '#c966ff', '#cc9f00', '#00cc8e', '#66acff', '#e066ff', '#ccab00', '#00cccc', '#6690ff', - # 13/b 23/b 33/b 43/b 14/b 24/b 34/b 44/b 15/b - '#fb66ff', '#ccbd00', '#00b9cc', '#666cff', '#ff9366', '#b5cc00', '#00a7cc', '#8d66ff', '#ffaf66', - # 25/b 35/b 45/b 16/b 26/b 36/b 46/b 51/b 52/b - '#96cc00', '#008ecc', '#a066ff', '#ffc666', '#66cc00', '#0076cc', '#b266ff', '#7f7f7f', '#7f5f49', - # 53/b - '#8c6a48'] - - -# def eigrp_render_uptime(uptime): # expects time in seconds -# m, s = divmod(uptime, 60) # break in seconds / minutes -# h, m = divmod(m, 60) # break in mintes / hours -# if h >= 24: # more then one day -# d, h = divmod(h, 24) # break in hours / days -# else: -# return '%02d:%02d:%02d' % (h, m, s) -# if d >= 365: # more the one year -# y, d = divmod(d, 365) # break in days / years -# return '%dy %dd %02d:%02d:%02d' % (y, d, h, m, s) -# else: -# return '%dd %02d:%02d:%02d' % (d, h, m, s) +from cmk.gui.i18n import _ +from cmk.gui.plugins.metrics import ( + metric_info, + graph_info, + perfometer_info, + unit_info, +) ##################################################################################################################### # @@ -52,12 +28,6 @@ colors_b = ['#80F000', '#c966ff', '#cc9f00', '#00cc8e', '#66acff', '#e066ff', '# ##################################################################################################################### -# unit_info['uptime'] = { -# 'title': _('Uptime'), -# 'description': _('Timespan or Duration in seconds'), -# 'symbol': _('s'), -# 'render': lambda v: eigrp_render_uptime(v), -# } unit_info['milliseconds'] = { 'title': _('Milliseconds'), 'symbol': 'ms', @@ -65,72 +35,6 @@ unit_info['milliseconds'] = { 'stepping': 'integer', # for vertical graph labels } - -###################################################################################################################### -# -# map eigrp perfdata to metric, not really necessary but makes sure to use the right metrics -# -###################################################################################################################### - - -check_metrics['check_mk-cisco_eigrp_peers'] = { - 'cEigrpUpTime' : {'name': 'cisco_eigrp_peers_cEigrpUpTime', }, - 'cEigrpRetrans' : {'name': 'cisco_eigrp_peers_cEigrpRetrans', }, - 'cEigrpRetries' : {'name': 'cisco_eigrp_peers_cEigrpRetries', }, - 'cEigrpSrtt' : {'name': 'cisco_eigrp_peers_cEigrpSrtt', }, - 'cEigrpRto' : {'name': 'cisco_eigrp_peers_cEigrpRto', }, - 'cEigrpPktsEnqueued': {'name': 'cisco_eigrp_peers_cEigrpPktsEnqueued'} -} - -check_metrics['check_mk-cisco_eigrp_interface'] = { - 'cEigrpPeerCount' : {'name': 'cisco_eigrp_interface_cEigrpPeerCount', }, - 'cEigrpXmitReliableQ' : {'name': 'cisco_eigrp_interface_cEigrpXmitReliableQ', }, - 'cEigrpXmitUnreliableQ': {'name': 'cisco_eigrp_interface_cEigrpXmitUnreliableQ', }, - 'cEigrpMeanSrtt' : {'name': 'cisco_eigrp_interface_cEigrpMeanSrtt', }, - 'cEigrpPendingRoutes' : {'name': 'cisco_eigrp_interface_cEigrpPendingRoutes', }, - 'cEigrpRMcasts' : {'name': 'cisco_eigrp_interface_cEigrpRMcasts', }, - 'cEigrpUMcasts' : {'name': 'cisco_eigrp_interface_cEigrpUMcasts', }, - 'cEigrpUUcasts' : {'name': 'cisco_eigrp_interface_cEigrpUUcasts', }, - 'cEigrpRUcasts' : {'name': 'cisco_eigrp_interface_cEigrpRUcasts', }, - 'cEigrpMcastExcepts' : {'name': 'cisco_eigrp_interface_cEigrpMcastExcepts', }, - 'cEigrpCRpkts' : {'name': 'cisco_eigrp_interface_cEigrpCRpkts', }, - 'cEigrpAcksSuppressed' : {'name': 'cisco_eigrp_interface_cEigrpAcksSuppressed', }, - 'cEigrpRetransSent' : {'name': 'cisco_eigrp_interface_cEigrpRetransSent', }, - 'cEigrpOOSrvcd' : {'name': 'cisco_eigrp_interface_cEigrpOOSrvcd', }, -} - -check_metrics['check_mk-cisco_eigrp_topology_table'] = { - 'Internal' : {'name': 'cisco_eigrp_topology_table_Internal', }, - 'Connected' : {'name': 'cisco_eigrp_topology_table_Connected', }, - 'External' : {'name': 'cisco_eigrp_topology_table_External', }, - 'Summary' : {'name': 'cisco_eigrp_topology_table_Summary', }, - 'Static_redistributed': {'name': 'cisco_eigrp_topology_table_Static_redistributed', }, - 'InetAddress' : {'name': 'cisco_eigrp_topology_table_Inetaddress', }, - 'activeroutes' : {'name': 'cisco_eigrp_topology_table_activeroutes', }, - 'siaroutes' : {'name': 'cisco_eigrp_topology_table_siaroutes', }, - 'routes' : {'name': 'cisco_eigrp_topology_table_routes', }, -} - -check_metrics['check_mk-cisco_eigrp_as_info'] = { - 'cEigrpNbrCount' : {'name': 'cisco_eigrp_as_info_cEigrpNbrCount', }, - 'cEigrpHellosSent' : {'name': 'cisco_eigrp_as_info_cEigrpHellosSent', }, - 'cEigrpHellosRcvd' : {'name': 'cisco_eigrp_as_info_cEigrpHellosRcvd', }, - 'cEigrpUpdatesSent' : {'name': 'cisco_eigrp_as_info_cEigrpUpdatesSent', }, - 'cEigrpUpdatesRcvd' : {'name': 'cisco_eigrp_as_info_cEigrpUpdatesRcvd', }, - 'cEigrpQueriesSent' : {'name': 'cisco_eigrp_as_info_cEigrpQueriesSent', }, - 'cEigrpQueriesRcvd' : {'name': 'cisco_eigrp_as_info_cEigrpQueriesRcvd', }, - 'cEigrpRepliesSent' : {'name': 'cisco_eigrp_as_info_cEigrpRepliesSent', }, - 'cEigrpRepliesRcvd' : {'name': 'cisco_eigrp_as_info_cEigrpRepliesRcvd', }, - 'cEigrpAcksSent' : {'name': 'cisco_eigrp_as_info_cEigrpAcksSent', }, - 'cEigrpAcksRcvd' : {'name': 'cisco_eigrp_as_info_cEigrpAcksRcvd', }, - 'cEigrpInputQHighMark' : {'name': 'cisco_eigrp_as_info_cEigrpInputQHighMark', }, - 'cEigrpInputQDrops' : {'name': 'cisco_eigrp_as_info_cEigrpInputQDrops', }, - 'cEigrpSiaQueriesSent' : {'name': 'cisco_eigrp_as_info_cEigrpSiaQueriesSent', }, - 'cEigrpSiaQueriesRcvd' : {'name': 'cisco_eigrp_as_info_cEigrpSiaQueriesRcvd', }, - 'cEigrpTopoRoutes' : {'name': 'cisco_eigrp_as_info_cEigrpTopoRoutes', }, - 'cEigrpXmitPendReplies': {'name': 'cisco_eigrp_as_info_cEigrpXmitPendReplies', }, -} - ##################################################################################################################### # # define metrics for eigrp peer perfdata @@ -140,34 +44,34 @@ check_metrics['check_mk-cisco_eigrp_as_info'] = { # define metrics for cisco_eigrp_peer perfdata metric_info['cisco_eigrp_peers_cEigrpUpTime'] = { - 'title' : _('Peer uptime'), - 'unit' : 's', - 'color' : colors_a[0], + 'title': _('Peer uptime'), + 'unit': 's', + 'color': '26/a', } metric_info['cisco_eigrp_peers_cEigrpPktsEnqueued'] = { - 'title' : _('Packets enqueued'), - 'unit' : 'count', - 'color' : colors_a[1], + 'title': _('Packets enqueued'), + 'unit': 'count', + 'color': '11/a', } metric_info['cisco_eigrp_peers_cEigrpRetrans'] = { - 'title' : _('Retransmissions'), - 'unit' : 'count', - 'color' : colors_a[2], + 'title': _('Retransmissions'), + 'unit': 'count', + 'color': '21/a', } metric_info['cisco_eigrp_peers_cEigrpRetries'] = { - 'title' : _('Retries'), - 'unit' : 'count', - 'color' : colors_a[3], + 'title': _('Retries'), + 'unit': 'count', + 'color': '31/a', } metric_info['cisco_eigrp_peers_cEigrpSrtt'] = { 'title': _('Smooth round trip time'), - 'unit' : 'milliseconds', - 'color': colors_a[4], + 'unit': 'milliseconds', + 'color': '41/a', } metric_info['cisco_eigrp_peers_cEigrpRto'] = { - 'title' : _('Retransmission timeout'), - 'unit' : 'milliseconds', - 'color' : colors_b[5], + 'title': _('Retransmission timeout'), + 'unit': 'milliseconds', + 'color': '12/a', } # define metrics for cisco_eigrp_interface perfdata @@ -175,72 +79,72 @@ metric_info['cisco_eigrp_peers_cEigrpRto'] = { metric_info['cisco_eigrp_interface_cEigrpPeerCount'] = { 'title': _('Peer count'), 'unit': 'count', - 'color': colors_a[0], + 'color': '26/a', } metric_info['cisco_eigrp_interface_cEigrpXmitReliableQ'] = { 'title': _('Reliable queue length'), 'unit': 'count', - 'color': colors_a[1], + 'color': '11/a', } metric_info['cisco_eigrp_interface_cEigrpXmitUnreliableQ'] = { 'title': _('Unreliable queue length'), 'unit': 'count', - 'color': colors_a[2], + 'color': '21/a', } metric_info['cisco_eigrp_interface_cEigrpMeanSrtt'] = { 'title': _('Average smooth round trip time'), 'unit': 'milliseconds', - 'color': colors_a[3], + 'color': '31/a', } metric_info['cisco_eigrp_interface_cEigrpPendingRoutes'] = { 'title': _('Routing updates awaiting transmission'), 'unit': 'count', - 'color': colors_a[4], + 'color': '41/a', } metric_info['cisco_eigrp_interface_cEigrpRMcasts'] = { 'title': _('Reliable multicasts send'), 'unit': 'count', - 'color': colors_a[5], + 'color': '12/a', } metric_info['cisco_eigrp_interface_cEigrpUMcasts'] = { 'title': _('Unreliable multicasts send'), 'unit': 'count', - 'color': colors_a[6], + 'color': '22/a', } metric_info['cisco_eigrp_interface_cEigrpUUcasts'] = { 'title': _('Unreliable unicasts send'), 'unit': 'count', - 'color': colors_a[7], + 'color': '32/a', } metric_info['cisco_eigrp_interface_cEigrpRUcasts'] = { 'title': _('Reliable unicasts send'), 'unit': 'count', - 'color': colors_a[8], + 'color': '42/a', } metric_info['cisco_eigrp_interface_cEigrpMcastExcepts'] = { 'title': _('Multicast exceptions received'), 'unit': 'count', - 'color': colors_a[9], + 'color': '25/a', } metric_info['cisco_eigrp_interface_cEigrpCRpkts'] = { 'title': _('Conditional-Receive packets'), 'unit': 'count', - 'color': colors_a[10], + 'color': '35/a', } metric_info['cisco_eigrp_interface_cEigrpAcksSuppressed'] = { 'title': _('Suppressed acknowledgements'), 'unit': 'count', - 'color': colors_a[11], + 'color': '45/a', } metric_info['cisco_eigrp_interface_cEigrpRetransSent'] = { 'title': _('Retransmissions send'), 'unit': 'count', - 'color': colors_a[12], + 'color': '16/a', } metric_info['cisco_eigrp_interface_cEigrpOOSrvcd'] = { 'title': _('Out-of-sequence packets received'), 'unit': 'count', - 'color': colors_a[13], + 'color': '26/b', } # define metrics for cisco_eigrp_topology_table perfdata @@ -248,47 +152,47 @@ metric_info['cisco_eigrp_interface_cEigrpOOSrvcd'] = { metric_info['cisco_eigrp_topology_table_routes'] = { 'title': _('All routes'), 'unit': 'count', - 'color': colors_a[0], + 'color': '26/a', } metric_info['cisco_eigrp_topology_table_activeroutes'] = { 'title': _('Active routes'), 'unit': 'count', - 'color': colors_a[1], + 'color': '11/a', } metric_info['cisco_eigrp_topology_table_siaroutes'] = { 'title': _('Stuck in active (SIA) routes'), 'unit': 'count', - 'color': colors_a[2], + 'color': '21/a', } metric_info['cisco_eigrp_topology_table_Connected'] = { 'title': _('Connected'), 'unit': 'count', - 'color': colors_a[3], + 'color': '31/a', } metric_info['cisco_eigrp_topology_table_Internal'] = { - 'title' : _('Internal'), - 'unit' : 'count', - 'color' : colors_a[4], + 'title': _('Internal'), + 'unit': 'count', + 'color': '41/a', } metric_info['cisco_eigrp_topology_table_External'] = { 'title': _('External'), 'unit': 'count', - 'color': colors_a[5], + 'color': '12/a', } metric_info['cisco_eigrp_topology_table_Summary'] = { 'title': _('Summary'), 'unit': 'count', - 'color': colors_a[6], + 'color': '22/a', } metric_info['cisco_eigrp_topology_table_Static_redistributed'] = { - 'title' : _('Static redistributed'), - 'unit' : 'count', - 'color' : colors_a[14], + 'title': _('Static redistributed'), + 'unit': 'count', + 'color': '24/a', } metric_info['cisco_eigrp_topology_table_Inetaddress'] = { 'title': _('Inet address'), 'unit': 'count', - 'color': colors_a[17], + 'color': '15/a', } # define metrics for cisco_eigrp_as_info perfdata @@ -296,87 +200,87 @@ metric_info['cisco_eigrp_topology_table_Inetaddress'] = { metric_info['cisco_eigrp_as_info_cEigrpNbrCount'] = { 'title': _('Neighbour count'), 'unit': 'count', - 'color': colors_a[0], + 'color': '26/a', } metric_info['cisco_eigrp_as_info_cEigrpHellosSent'] = { 'title': _('Hellos send'), 'unit': '1/s', - 'color': colors_a[1], + 'color': '11/a', } metric_info['cisco_eigrp_as_info_cEigrpHellosRcvd'] = { 'title': _('Hellos received'), 'unit': '1/s', - 'color': colors_a[2], + 'color': '21/a', } metric_info['cisco_eigrp_as_info_cEigrpUpdatesSent'] = { 'title': _('Updates send'), 'unit': 'count', - 'color': colors_a[3], + 'color': '31/a', } metric_info['cisco_eigrp_as_info_cEigrpUpdatesRcvd'] = { 'title': _('Updates received'), 'unit': 'count', - 'color': colors_a[4], + 'color': '41/a', } metric_info['cisco_eigrp_as_info_cEigrpQueriesSent'] = { 'title': _('Alternate route queries send'), 'unit': 'count', - 'color': colors_a[5], + 'color': '12/a', } metric_info['cisco_eigrp_as_info_cEigrpQueriesRcvd'] = { 'title': _('Alternate route queries received'), 'unit': 'count', - 'color': colors_a[6], + 'color': '22/a', } metric_info['cisco_eigrp_as_info_cEigrpRepliesSent'] = { 'title': _('Reply packets send'), 'unit': 'count', - 'color': colors_a[7], + 'color': '32/a', } metric_info['cisco_eigrp_as_info_cEigrpRepliesRcvd'] = { 'title': _('Reply packets received'), 'unit': 'count', - 'color': colors_a[8], + 'color': '42/a', } metric_info['cisco_eigrp_as_info_cEigrpAcksSent'] = { 'title': _('Acknowledgements send'), 'unit': 'count', - 'color': colors_a[9], + 'color': '13/a', } metric_info['cisco_eigrp_as_info_cEigrpAcksRcvd'] = { 'title': _('Acknowledgements received'), 'unit': 'count', - 'color': colors_a[10], + 'color': '23/a', } metric_info['cisco_eigrp_as_info_cEigrpInputQHighMark'] = { 'title': _('Highest number of packets in the input queue'), 'unit': 'count', - 'color': colors_a[11], + 'color': '33/a', } metric_info['cisco_eigrp_as_info_cEigrpInputQDrops'] = { 'title': _('Packets dropped from the input queue'), 'unit': 'count', - 'color': colors_a[12], + 'color': '43/a', } metric_info['cisco_eigrp_as_info_cEigrpSiaQueriesSent'] = { 'title': _('Stuck in active (SIA) queries sent'), 'unit': 'count', - 'color': colors_a[13], + 'color': '14/a', } metric_info['cisco_eigrp_as_info_cEigrpSiaQueriesRcvd'] = { 'title': _('Stuck in active (SIA) queries received'), 'unit': 'count', - 'color': colors_a[14], + 'color': '24/a', } metric_info['cisco_eigrp_as_info_cEigrpTopoRoutes'] = { 'title': _('Routes in the topology table'), 'unit': 'count', - 'color': colors_a[15], + 'color': '34/a', } metric_info['cisco_eigrp_as_info_cEigrpXmitPendReplies'] = { 'title': _('Outstanding replies expected to queries send'), 'unit': 'count', - 'color': colors_a[16], + 'color': '44/a', } ###################################################################################################################### @@ -387,55 +291,60 @@ metric_info['cisco_eigrp_as_info_cEigrpXmitPendReplies'] = { # graphs for cisco_eigrp_peer -graph_info.append({ +graph_info['cisco_eigrp_peer_uptime'] = { 'title': _('Uptime'), 'metrics': [ ('cisco_eigrp_peers_cEigrpUpTime', 'area'), ], -}) -graph_info.append({ +} + +graph_info['cisco_eigrp_peer_enqued'] = { 'title': _('Packets enqueued'), 'metrics': [ ('cisco_eigrp_peers_cEigrpPktsEnqueued', 'line'), ], -}) -graph_info.append({ +} + +graph_info['cisco_eigrp_peer_retransmission'] = { 'title': _('Smooth round trip time / retransmission timeout'), 'metrics': [ ('cisco_eigrp_peers_cEigrpSrtt', 'line'), ('cisco_eigrp_peers_cEigrpRto', 'line'), ], -}) -graph_info.append({ +} +graph_info['cisco_eigrp_peer_error'] = { 'title': _('Retransmissions / Retries'), 'metrics': [ ('cisco_eigrp_peers_cEigrpRetrans', 'line'), ('cisco_eigrp_peers_cEigrpRetries', 'line'), ], -}) +} # graphs for cisco_eigrp_as_info -graph_info.append({ +graph_info['cisco_eigrp_as_info_neighbour_count'] = { 'title': _('Neighbour count'), 'metrics': [ ('cisco_eigrp_as_info_cEigrpNbrCount', 'line'), ], -}) -graph_info.append({ +} + +graph_info['cisco_eigrp_as_info_route_count'] = { 'title': _('Routes in the topology table'), 'metrics': [ ('cisco_eigrp_as_info_cEigrpTopoRoutes', 'line'), ], -}) -graph_info.append({ +} + +graph_info['cisco_eigrp_as_info_hellos'] = { 'title': _('Hellos send/received'), 'metrics': [ ('cisco_eigrp_as_info_cEigrpHellosSent', 'line'), ('cisco_eigrp_as_info_cEigrpHellosRcvd', '-line'), ], -}) -graph_info.append({ +} + +graph_info['cisco_eigrp_as_info_packets'] = { 'title': _('Packets send/received'), 'metrics': [ ('cisco_eigrp_as_info_cEigrpQueriesSent', 'line'), @@ -449,51 +358,57 @@ graph_info.append({ ('cisco_eigrp_as_info_cEigrpAcksRcvd', '-line'), ('cisco_eigrp_as_info_cEigrpSiaQueriesRcvd', '-line'), ], -}) -graph_info.append({ +} + +graph_info['cisco_eigrp_as_info_drops'] = { 'title': _('Input queue drops / Queue hi mark'), 'metrics': [ ('cisco_eigrp_as_info_cEigrpInputQDrops', 'line'), ('cisco_eigrp_as_info_cEigrpInputQHighMark', 'line'), ], -}) +} # graphs for cisco_eigrp_interface -graph_info.append({ +graph_info['cisco_eigrp_interface_peer_count'] = { 'title': _('Peer count'), 'metrics': [ ('cisco_eigrp_interface_cEigrpPeerCount', 'line'), ], -}) -graph_info.append({ +} + +graph_info['cisco_eigrp_interface_queue_length'] = { 'title': _('Queue length'), 'metrics': [ ('cisco_eigrp_interface_cEigrpXmitReliableQ', 'line'), ('cisco_eigrp_interface_cEigrpXmitUnreliableQ', 'line'), ], -}) -graph_info.append({ +} + +graph_info['cisco_eigrp_interface_multicasts'] = { 'title': _('Multicasts send'), 'metrics': [ ('cisco_eigrp_interface_cEigrpRMcasts', 'line'), ('cisco_eigrp_interface_cEigrpUMcasts', 'line'), ], -}) -graph_info.append({ +} + +graph_info['cisco_eigrp_interface_unicasts'] = { 'title': _('Unicasts send'), 'metrics': [ ('cisco_eigrp_interface_cEigrpUUcasts', 'line'), ('cisco_eigrp_interface_cEigrpRUcasts', 'line'), ], -}) -graph_info.append({ +} + +graph_info['cisco_eigrp_interface_pendinf_routes'] = { 'title': _('Pending Routes'), 'metrics': [ ('cisco_eigrp_interface_cEigrpPendingRoutes', 'line'), ], -}) -graph_info.append({ +} + +graph_info['cisco_eigrp_interface_packets'] = { 'title': _('Various packets'), 'metrics': [ ('cisco_eigrp_interface_cEigrpMcastExcepts', 'line'), @@ -501,23 +416,25 @@ graph_info.append({ ('cisco_eigrp_interface_cEigrpRetransSent', 'line'), ('cisco_eigrp_interface_cEigrpOOSrvcd', 'line'), ], -}) -graph_info.append({ +} + +graph_info['cisco_eigrp_interface_rtt'] = { 'title': _('Average smooth round trip time'), 'metrics': [ ('cisco_eigrp_interface_cEigrpMeanSrtt', 'line'), ], -}) -graph_info.append({ +} + +graph_info['cisco_eigrp_interface_ack_suppressed'] = { 'title': _('Suppressed acknowledgements'), 'metrics': [ ('cisco_eigrp_interface_cEigrpAcksSuppressed', 'line'), ], -}) +} # define metrics for cisco_eigrp_topology_table perfdata -graph_info.append({ +graph_info['cisco_eigrp_topology_table_routes'] = { 'title': _('Routes by origin type'), 'metrics': [ ('cisco_eigrp_topology_table_Inetaddress', 'line'), @@ -528,17 +445,24 @@ graph_info.append({ ('cisco_eigrp_topology_table_Connected', 'line'), ('cisco_eigrp_topology_table_routes', 'line'), ], + 'optional_metrics': [ + 'cisco_eigrp_topology_table_Inetaddress', + 'cisco_eigrp_topology_table_Static_redistributed', + 'cisco_eigrp_topology_table_Summary', + 'cisco_eigrp_topology_table_External', + 'cisco_eigrp_topology_table_Internal', + 'cisco_eigrp_topology_table_Connected', + ] # 'range': (0, 'cisco_eigrp_topology_table_routes:max') -}) +} -graph_info.append({ +graph_info['cisco_eigrp_topology_table_active_sia'] = { 'title': _('Stuck in active (SIA) / active routes'), 'metrics': [ ('cisco_eigrp_topology_table_siaroutes', 'line'), ('cisco_eigrp_topology_table_activeroutes', 'line'), ], -}) - +} ###################################################################################################################### # @@ -556,17 +480,17 @@ perfometer_info.append({ # cisco_eigrp_interface send packets perfometer_info.append({ - 'type': 'linear', - 'segments': ['cisco_eigrp_interface_cEigrpPeerCount'], - 'total': 50, - }) + 'type': 'linear', + 'segments': ['cisco_eigrp_interface_cEigrpPeerCount'], + 'total': 50, +}) # cisco_eigrp_topology_table perfometer_info.append({ - 'type': 'linear', - 'segments': ['cisco_eigrp_topology_table_routes'], - 'total': 1000, - }) + 'type': 'linear', + 'segments': ['cisco_eigrp_topology_table_routes'], + 'total': 1000, +}) # cisco_eigrp_as_info perfometer_info.append({ diff --git a/web/plugins/wato/cisco_eigrp_interface.py b/web/plugins/wato/cisco_eigrp_interface.py index 00f8e0b..b15a414 100644 --- a/web/plugins/wato/cisco_eigrp_interface.py +++ b/web/plugins/wato/cisco_eigrp_interface.py @@ -1,17 +1,31 @@ -#!/usr/bin/python -# -*- encoding: utf-8; py-indent-offset: 4 -*- +#!/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 : 2017-12-27 # # Check_MK cisco_eigrp_peer WATO plugin # -# Author: Th.L. -# Date: 2017-12-27 -# -# -# -# +from cmk.gui.i18n import _ +from cmk.gui.valuespec import ( + Dictionary, + Integer, + TextAscii, + Tuple, + MonitoringState, + ListChoice, +) -ignore_interfaces_auth = [ +from cmk.gui.plugins.wato import ( + CheckParameterRulespecWithItem, + rulespec_registry, + RulespecGroupCheckParametersNetworking, +) + +_ignore_interfaces_auth = [ (6, 'Ethernet CSMAD'), (24, 'Loopback'), (53, 'Proprietary Virtual (Cisco VLAN L3)'), @@ -20,19 +34,16 @@ ignore_interfaces_auth = [ (136, 'Layer 3 IP VLAN'), ] -register_check_parameters( - subgroup_networking, - 'cisco_eigrp_interface', - _('Cisco EIGRP interface'), - Dictionary( + +def _parameter_valuespec_cisco_eigrp_interface(): + return Dictionary( help=_(''), elements=[ ('ignore_interfaces_auth', ListChoice( title=_('no warning if EIRGP authentication not configured on selected interface types'), - label=_('no warning if EIRGP authentication not configured on selected interface types'), help=_('no warning if EIRGP authentication not configured on selected interface types'), - choices=ignore_interfaces_auth, + choices=_ignore_interfaces_auth, default_value=[], )), ('no_auth_state', @@ -47,12 +58,21 @@ register_check_parameters( ('md5_auth_state', MonitoringState( title=_('State to report when interface uses MD5 authentication'), - help=_('State if an EIGRP enabled interface uses an MD5 hash as authentication method. Default is warning'), + help=_( + 'State if an EIGRP enabled interface uses an MD5 hash as authentication method. Default is warning'), default_value=1, ), ), ], - ), - TextAscii(title=_('Cisco EIGRP interface')), - match_type='dict', -) + ) + + +rulespec_registry.register( + CheckParameterRulespecWithItem( + check_group_name='cisco_eigrp_interface', + group=RulespecGroupCheckParametersNetworking, + item_spec=lambda: TextAscii(title=_('Cisco EIGRP interface'), ), + match_type='dict', + parameter_valuespec=_parameter_valuespec_cisco_eigrp_interface, + title=lambda: _('Cisco EIGRP interface'), + )) diff --git a/web/plugins/wato/cisco_eigrp_peers.py b/web/plugins/wato/cisco_eigrp_peers.py index 7d44d87..5ed6f97 100644 --- a/web/plugins/wato/cisco_eigrp_peers.py +++ b/web/plugins/wato/cisco_eigrp_peers.py @@ -1,35 +1,61 @@ -#!/usr/bin/python -# -*- encoding: utf-8; py-indent-offset: 4 -*- +#!/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 : 2017-12-25 # # Check_MK cisco_eigrp_peer WATO plugin # -# Author: Th.L. -# Date: 2017-12-25 -# -# -# -# -register_check_parameters( - subgroup_networking, - 'cisco_eigrp_peers', - _('Cisco EIGRP peer'), - Dictionary( +from cmk.gui.i18n import _ +from cmk.gui.valuespec import ( + Dictionary, + Integer, + TextAscii, + Tuple, +) + +from cmk.gui.plugins.wato import ( + CheckParameterRulespecWithItem, + rulespec_registry, + RulespecGroupCheckParametersNetworking, +) + + +def _parameter_valuespec_cisco_eigrp_peers(): + return Dictionary( help=_(''), elements=[ ('minuptime', - Integer( - help=_('Set the time in seconds, a peer must be up before the peer is considered sable.' - 'If the peer uptime less then X, the check outcome is set to warning.'), - title=_('Minimum uptime for peer. If uptime less, set check outcome to warning. Default is 1 day.'), - default_value=86400, - allow_empty=False, - unit='seconds', - minvalue=1, - ), - ) + Tuple( + title=_('Minimum uptime for peer. Default is 1/2 hours.'), + help=_('Set the time in seconds, a peer must be up before the peer is considered stable.' + 'If the peer uptime less then X, the check outcome is set to warning/critical.'), + elements=[ + Integer(title=_('Warning below'), + unit='seconds', + default_value=7200, + help=_('The uptime in seconds below which a warning state is triggered. Default is 7200s'), + ), + Integer(title=_('Critical below'), + unit='seconds', + default_value=3600, + help=_('The uptime in seconds below which a critical state is triggered. default is 3600s'), + ) + ], + )), ], - ), - TextAscii(title=_('Cisco EIGRP peer')), - match_type='dict', -) \ No newline at end of file + ) + + +rulespec_registry.register( + CheckParameterRulespecWithItem( + check_group_name='cisco_eigrp_peers', + group=RulespecGroupCheckParametersNetworking, + item_spec=lambda: TextAscii(title=_('Cisco EIGRP peer'), ), + match_type='dict', + parameter_valuespec=_parameter_valuespec_cisco_eigrp_peers, + title=lambda: _('Cisco EIGRP peer'), + )) diff --git a/web/plugins/wato/cisco_eigrp_topology_table.py b/web/plugins/wato/cisco_eigrp_topology_table.py index 72e8da2..51644c2 100644 --- a/web/plugins/wato/cisco_eigrp_topology_table.py +++ b/web/plugins/wato/cisco_eigrp_topology_table.py @@ -1,37 +1,54 @@ -#!/usr/bin/python -# -*- encoding: utf-8; py-indent-offset: 4 -*- - -# -# Check_MK cisco_eigrp_topology_table WATO plugin -# -# Author: Th.L. -# Date: 2017-12-27 +#!/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 : 2017-12-27 # # +# Check_MK cisco_eigrp_topology_table WATO plugin # +from cmk.gui.i18n import _ +from cmk.gui.valuespec import ( + Dictionary, + ListChoice, + TextAscii, +) -nowarnon = [('siaroutes', 'Stuck in active (SIA) routes'), +from cmk.gui.plugins.wato import ( + CheckParameterRulespecWithItem, + rulespec_registry, + RulespecGroupCheckParametersNetworking, +) + +_nowarnon = [('siaroutes', 'Stuck in active (SIA) routes'), ('activeroutes', 'Active routes')] -register_check_parameters( - subgroup_networking, - 'cisco_eigrp_topology_table', - _('Cisco EIGRP topology table'), - Dictionary( + +def _parameter_valuespec_cisco_eigrp_topology_table(): + return Dictionary( help=_(''), elements=[ ('nowarnon', ListChoice( title=_('no warning if EIRGP SIA/active route found'), - label=_('no warning if EIRGP SIA/active route found'), help=_('no warning if EIRGP Stuck in active (SIA) / active route found'), - choices=nowarnon, + choices=_nowarnon, default_value=[], ), ) ], - ), - TextAscii(title=_('Cisco EIGRP topology table')), - match_type='dict', -) + ) + + +rulespec_registry.register( + CheckParameterRulespecWithItem( + check_group_name='cisco_eigrp_topology_table', + group=RulespecGroupCheckParametersNetworking, + item_spec=lambda: TextAscii(title=_('Cisco EIGRP topology table'), ), + match_type='dict', + parameter_valuespec=_parameter_valuespec_cisco_eigrp_topology_table, + title=lambda: _('Cisco EIGRP topology table'), + )) \ No newline at end of file -- GitLab