diff --git a/inventory_provider/snmp.py b/inventory_provider/snmp.py index d53eb6265dd25dabaafb4568a8fb6cb566534b36..b3b6852f5c42096c59bea4472be1a744944c5a06 100644 --- a/inventory_provider/snmp.py +++ b/inventory_provider/snmp.py @@ -2,6 +2,7 @@ import ipaddress import logging import re import struct +from enum import StrEnum from pysnmp.hlapi import nextCmd, SnmpEngine, CommunityData, \ UdpTransportTarget, ContextData, ObjectType, ObjectIdentity @@ -13,6 +14,15 @@ from pysnmp.error import PySnmpError RFC1213_MIB_IFDESC = '1.3.6.1.2.1.2.2.1.2' # BGP4-V2-MIB-JUNIPER::jnxBgpM2PeerState JNX_BGP_M2_PEER_STATE = '1.3.6.1.4.1.2636.5.1.1.2.1.1.1.2' + +# tBgpPeerNgOperLastState +NOKIA_BGP_PEER_STATE = '1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11' + +# I think these values are a GEANT convention +class IPVersion(StrEnum): + IPV4 = '1' + IPV6 = '2' + logger = logging.getLogger(__name__) @@ -191,7 +201,10 @@ def _v4str(int_str_list): return '.'.join(int_str_list) -def _get_peer_state_info(hostname, community): + + + +def _get_peer_state_info_juniper(hostname, community): oid_prefix = f'.{JNX_BGP_M2_PEER_STATE}.' for ifc in walk(hostname, community, JNX_BGP_M2_PEER_STATE): @@ -202,28 +215,29 @@ def _get_peer_state_info(hostname, community): rest = ifc['oid'][len(oid_prefix):] splits = rest.split('.') splits.pop(0) # no idea what this integer is ... - if splits[0] == splits[5] == '1': # v4 should peer with v4 + if splits[0] == splits[5] == IPVersion.IPV4: # v4 should peer with v4 # ipv4 assert len(splits) == 10 - local = ipaddress.ip_address(_v4str(splits[1:5])) + # local not needed, leaving in for reference + # local = ipaddress.ip_address(_v4str(splits[1:5])) remote = ipaddress.ip_address(_v4str(splits[6:])) - elif splits[0] == splits[17] == '2': # v6 should peer with v6 + elif splits[0] == splits[17] == IPVersion.IPV6: # v6 should peer with v6 assert len(splits) == 34 - local = ipaddress.ip_address(_v6bytes(splits[1:17])) + # local not needed, leaving in for reference + # local = ipaddress.ip_address(_v6bytes(splits[1:17])) remote = ipaddress.ip_address(_v6bytes(splits[18:])) else: logger.error(f'expected v4 or v6 peering, got type {splits[0]}') assert False yield { - 'local': local.exploded, 'remote': remote.exploded, 'oid': ifc['oid'], 'community': community } -def get_peer_state_info(hostname, community, logical_systems): +def get_peer_state_info_juniper(hostname, community, logical_systems): """ return peering states from all logical systems @@ -236,4 +250,54 @@ def get_peer_state_info(hostname, community, logical_systems): :return: generator yielding dicts """ yield from _walk_util( - hostname, community, logical_systems, _get_peer_state_info) + hostname, community, logical_systems, _get_peer_state_info_juniper) + + +def get_peer_state_info_nokia(hostname, community): + """ + return peering info + + items are structured like: + {remote: str, oid: str, community: str} + + :param hostname: + :param community: + :return: generator yielding dicts + """ + oid_prefix = f'.{NOKIA_BGP_PEER_STATE}.' + for ifc in walk(hostname, community, NOKIA_BGP_PEER_STATE): + oid = ifc['oid'] + rest = oid[len(oid_prefix):] + splits = rest.split('.') + + # Indicator for Type of service - info provided by Rich Adam + # TIMETRA - BGP - MIB::tBgpInstanceDescription.1 = STRING: + # TIMETRA - BGP - MIB::tBgpInstanceDescription.2 = STRING: LHCONE_L3VPN + # TIMETRA - BGP - MIB::tBgpInstanceDescription.3 = STRING: IAS + # TIMETRA - BGP - MIB::tBgpInstanceDescription.5 = STRING: pop - lan + # TIMETRA - BGP - MIB::tBgpInstanceDescription.6 = STRING: COPERNICUS + # TIMETRA - BGP - MIB::tBgpInstanceDescription.7 = STRING: SNM_OPTICAL_MGMT + splits.pop(0) # Indicator for Type of service (see above) - not needed here + ip_indicator = splits.pop(0) + splits.pop(0) # Indicator for length of address - not needed here + try: + ip_version = IPVersion(ip_indicator) # Indicator for Type of address + + except ValueError: + logger.error(f'expected v4 or v6 peering, got type {ip_indicator}') + assert False + if ip_version == IPVersion.IPV4: + address_func = _v4str + else: + address_func = _v6bytes + peer_address = ipaddress.ip_address(address_func(splits)) + yield { + 'remote': peer_address.exploded, + 'oid': oid, + 'community': community + } + + + + + diff --git a/inventory_provider/tasks/worker.py b/inventory_provider/tasks/worker.py index adcf3e0a59fd2a154b3ad78d3be9ef16a552a392..3e8805690f59757dee2bfa09e5aa043c967971b1 100644 --- a/inventory_provider/tasks/worker.py +++ b/inventory_provider/tasks/worker.py @@ -1029,7 +1029,7 @@ def snmp_refresh_peerings_juniper( hostname, community, logical_systems, update_callback=lambda S: None): try: peerings = list( - snmp.get_peer_state_info(hostname, community, logical_systems)) + snmp.get_peer_state_info_juniper(hostname, community, logical_systems)) except ConnectionError: msg = f'error loading snmp peering data from {hostname}' logger.exception(msg) diff --git a/test/data/snmp-peer-info.json b/test/data/snmp-peer-info-juniper.json similarity index 100% rename from test/data/snmp-peer-info.json rename to test/data/snmp-peer-info-juniper.json diff --git a/test/data/snmp-peer-info-nokia.json b/test/data/snmp-peer-info-nokia.json new file mode 100644 index 0000000000000000000000000000000000000000..5f1034e6c64044893f2f5cc0f6176dc58e8ca95f --- /dev/null +++ b/test/data/snmp-peer-info-nokia.json @@ -0,0 +1,138 @@ +[ + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.1.1.4.62.40.119.2", + "value": 6 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.1.1.4.62.40.119.4", + "value": 6 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.1.1.4.62.40.119.5", + "value": 6 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.1.1.4.62.40.119.7", + "value": 6 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.1.1.4.62.40.119.8", + "value": 6 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.1.1.4.62.40.119.9", + "value": 6 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.1.1.4.62.40.119.10", + "value": 6 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.1.1.4.62.40.124.158", + "value": 6 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.1.1.4.62.40.124.218", + "value": 6 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.1.1.4.83.97.90.35", + "value": 3 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.1.1.4.208.76.14.223", + "value": 3 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.1.2.16.32.1.7.152.0.1.0.0.0.0.0.0.0.0.2.102", + "value": 6 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.1.2.16.32.1.7.152.0.20.16.170.0.0.0.0.0.0.0.10", + "value": 6 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.1.2.16.32.1.7.152.0.153.0.1.0.0.0.0.0.0.0.58", + "value": 3 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.1.2.16.32.1.7.153.1.171.0.0.0.0.0.0.0.0.0.2", + "value": 6 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.1.2.16.32.1.7.153.1.171.0.0.0.0.0.0.0.0.0.4", + "value": 3 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.1.2.16.32.1.7.153.1.171.0.0.0.0.0.0.0.0.0.5", + "value": 3 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.1.2.16.32.1.7.153.1.171.0.0.0.0.0.0.0.0.0.7", + "value": 6 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.1.2.16.32.1.7.153.1.171.0.0.0.0.0.0.0.0.0.8", + "value": 3 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.1.2.16.32.1.7.153.1.171.0.0.0.0.0.0.0.0.0.9", + "value": 6 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.1.2.16.32.1.7.153.1.171.0.0.0.0.0.0.0.0.0.10", + "value": 6 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.1.2.16.38.32.1.41.0.1.0.2.0.0.0.0.0.0.0.1", + "value": 3 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.2.1.4.62.40.126.77", + "value": 3 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.2.1.4.192.65.184.146", + "value": 6 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.2.2.16.32.1.7.152.1.17.0.1.0.0.0.0.0.0.0.50", + "value": 6 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.3.1.4.62.40.102.18", + "value": 6 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.3.1.4.83.97.89.74", + "value": 6 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.3.1.4.83.97.90.34", + "value": 6 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.3.1.4.212.133.7.165", + "value": 6 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.3.2.16.32.1.7.152.0.1.0.0.0.0.0.0.0.0.2.101", + "value": 3 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.3.2.16.32.1.7.153.1.171.0.21.0.0.0.0.0.0.0.2", + "value": 6 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.3.2.16.32.1.7.248.0.60.0.0.0.0.0.0.0.0.0.54", + "value": 6 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.3.2.16.32.1.25.0.0.5.0.2.0.2.0.0.0.8.90.85", + "value": 6 + }, + { + "oid": ".1.3.6.1.4.1.6527.3.1.2.14.4.8.1.11.5.1.4.172.17.255.10", + "value": 1 + } +] \ No newline at end of file diff --git a/test/per_router/test_snmp_handling.py b/test/per_router/test_snmp_handling.py index 496b32c171a4eac17196a4d159e36d1c7cf4dda9..b813448ea8b5a502925fb30590147274b3cb6850 100644 --- a/test/per_router/test_snmp_handling.py +++ b/test/per_router/test_snmp_handling.py @@ -8,6 +8,22 @@ from inventory_provider import snmp import inventory_provider + +PEERING_RESULT_SCHEMA = { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "array", + "items": { + "type": "object", + "properties": { + "remote": {"type": "string"}, + "oid": {"type": "string"}, + "community": {"type": "string"} + }, + "required": ["remote", "oid", "community"], + "additionalProperties": False + } +} + TEST_DATA_DIRNAME = os.path.realpath(os.path.join( inventory_provider.__path__[0], "..", @@ -24,7 +40,7 @@ def interfaces_walk_responses(): def peering_walk_responses(): test_data_filename = os.path.join( - TEST_DATA_DIRNAME, "snmp-peer-info.json") + TEST_DATA_DIRNAME, "snmp-peer-info-juniper.json") with open(test_data_filename) as f: for peering in json.loads(f.read()): yield {'oid': peering['oid'], 'value': 6} @@ -42,7 +58,7 @@ def _gen_mocked_nextCmd(mocked_walk_data): return _mocked_nextCmd -def test_snmp_interfaces(mocker, netconf_doc): +def test_snmp_interfaces_juniper(mocker, netconf_doc): expected_result_schema = { "$schema": "http://json-schema.org/draft-07/schema#", @@ -77,23 +93,7 @@ def test_snmp_interfaces(mocker, netconf_doc): assert len(interfaces) == expected_num_loops * len(test_data) -def test_peer_info(mocker, netconf_doc): - - expected_result_schema = { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "array", - "items": { - "type": "object", - "properties": { - "local": {"type": "string"}, - "remote": {"type": "string"}, - "oid": {"type": "string"}, - "community": {"type": "string"} - }, - "required": ["local", "remote", "oid", "community"], - "additionalProperties": False - } - } +def test_peer_info_juniper(mocker, netconf_doc): test_data = list(peering_walk_responses()) assert test_data # sanity @@ -104,10 +104,20 @@ def test_peer_info(mocker, netconf_doc): logical_systems = juniper.logical_systems(netconf_doc) peerings = list( - snmp.get_peer_state_info('ignored', 'ignored', logical_systems)) + snmp.get_peer_state_info_juniper('ignored', 'ignored', logical_systems)) - jsonschema.validate(peerings, expected_result_schema) + jsonschema.validate(peerings, PEERING_RESULT_SCHEMA) # we should loop the base community # string and once per logical_system expected_num_loops = 1 + len(logical_systems) assert len(peerings) == expected_num_loops * len(test_data) + + +def test_peer_info_nokia(mocker): + + with open(os.path.join(TEST_DATA_DIRNAME, "snmp-peer-info-nokia.json")) as f: + test_data = json.load(f) + mocker.patch('inventory_provider.snmp.walk', lambda *args: test_data) + res = list(snmp.get_peer_state_info_nokia('ignored', 'ignored')) + jsonschema.validate(res, PEERING_RESULT_SCHEMA) + assert len(res) == len(test_data)