From 43680e1e5f9272e6e8795698e3e6538456339af1 Mon Sep 17 00:00:00 2001 From: Erik Reid <erik.reid@geant.org> Date: Sat, 23 Jan 2021 11:51:34 +0100 Subject: [PATCH] use new redis schema to construct peer-info rsp --- inventory_provider/routes/classifier.py | 159 ++++++++++++++++++------ 1 file changed, 120 insertions(+), 39 deletions(-) diff --git a/inventory_provider/routes/classifier.py b/inventory_provider/routes/classifier.py index 9e9b4845..e2e144cf 100644 --- a/inventory_provider/routes/classifier.py +++ b/inventory_provider/routes/classifier.py @@ -243,46 +243,129 @@ def get_juniper_link_info(source_equipment, interface): return Response(result, mimetype='application/json') -def ix_peering_info(peer_info): +def _vpn_rr_peering_info(redis, address): """ - TODO: this is probably the least efficient way of doing this - (if it's a problem, pre-compute these lists) - - :param peer_info: an element from ix_public_peer:address + :param redis: a redis db connection + :param address: the remote peer address :return: """ + def _is_rr(peering_info): + if peering_info.get('logical-system', '') != 'VRR': + return False + group = peering_info.get('group', '') + if group not in ('VPN-RR', 'VPN-RR-INTERNAL'): + return False + if 'description' not in peering_info: + logger.error('internal data error, looks like vpn rr peering' + f'but description is missing: {peering_info}') + return False + return True - result = { - 'peer': peer_info, - 'group': [], - 'router': [] + try: + address = ipaddress.ip_address(address).exploded + except ValueError: + raise ClassifierProcessingError( + f'unable to parse {address} as an ip address') + + all_peerings = redis.get(f'juniper-peerings:remote:{address}') + if not all_peerings: + return None + + all_peerings = json.loads(all_peerings.decode('utf-8')) + rr_peerings = list(filter(_is_rr, all_peerings)) + if not rr_peerings: + return None + + if len(rr_peerings) > 1: + logger.warning( + f'using the first of multiple vpn rr peer matches: {rr_peerings}') + + return_value = { + 'name': address, + 'description': rr_peerings[0]['description'], + 'router': rr_peerings[0]['hostname'] } + if 'remote-asn' in rr_peerings[0]: + return_value['peer-as'] = rr_peerings[0]['remote-asn'] + + return return_value + + +def _ix_peering_info(redis, address): + """ + :param redis: a redis db connection + :param address: the remote peer address + :return: + """ + + def _is_ix(peering_info): + if peering_info.get('instance', '') != 'IAS': + return False + if not peering_info.get('group', '').startswith('GEANT-IX'): + return False + + expected_keys = ('description', 'local-asn', 'remote-asn') + if any(peering_info.get(x, None) is None for x in expected_keys): + logger.error('internal data error, looks like ix peering but' + f'some expected keys are missing: {peering_info}') + return False + return True + try: - address = ipaddress.ip_address(peer_info['name']) + address = ipaddress.ip_address(address).exploded except ValueError: raise ClassifierProcessingError( - 'unable to parse %r as an ip address' % address) - - description = peer_info['description'] - assert description is not None # sanity + f'unable to parse {address} as an ip address') - keyword = description.split(' ')[0] # regex needed??? (e.g. tabs???) + all_peerings = redis.get(f'juniper-peerings:remote:{address}') + if not all_peerings: + return None + + all_peerings = json.loads(all_peerings.decode('utf-8')) + ix_peerings = list(filter(_is_ix, all_peerings)) + if not ix_peerings: + return None + + if len(ix_peerings) > 1: + logger.warning( + f'using the first of multiple ix peer matches: {ix_peerings}') + + peer_info = { + 'name': address, + 'description': ix_peerings[0]['description'], + 'as': { + 'local': ix_peerings[0]['local-asn'], + 'peer': ix_peerings[0]['remote-asn'] + }, + 'router': ix_peerings[0]['hostname'] + } - for doc in common.load_json_docs( - config_params=current_app.config['INVENTORY_PROVIDER_CONFIG'], - key_pattern='ix_public_peer:*', - num_threads=10): + return_value = { + 'peer': peer_info, + 'group': [], + 'router': [] + } - other = doc['value'] - if other['router'] == peer_info['router']: - result['router'].append(other['name']) + # load the other peers in the same group + # regex needed??? (e.g. tabs???) + peering_group_name = peer_info['description'].split(' ')[0] + peering_group = redis.get( + f'juniper-peerings:ix-groups:{peering_group_name}') + if peering_group: + peering_group = peering_group.decode('utf-8') + return_value['group'] = sorted(json.loads(peering_group)) - assert other['description'] is not None # sanity: as above... - if other['description'].startswith(keyword): - result['group'].append(other['name']) + # load the other ix peers from the same router + router_peerings = redis.get( + f'juniper-peerings:hosts:{peer_info["router"]}') + router_peerings = json.loads(router_peerings.decode('utf-8')) + router_ix_peers = list(filter(_is_ix, router_peerings)) + if router_ix_peers: + addresses = {p['address'] for p in router_ix_peers} + return_value['router'] = sorted(list(addresses)) - return result + return return_value def find_interfaces(address): @@ -362,19 +445,17 @@ def peer_info(address): 'locations': [] } - info = r.get(f'ix_public_peer:{address}') - if info: - info = info.decode('utf-8') - info = json.loads(info) - result['ix-public-peer-info'] = ix_peering_info(info) - result['locations'] += _locations_from_router(info['router']) - - info = r.get(f'vpn_rr_peer:{address}') - if info: - info = info.decode('utf-8') - info = json.loads(info) - result['vpn-rr-peer-info'] = info - result['locations'] += _locations_from_router(info['router']) + ix_peering_info = _ix_peering_info(r, address) + if ix_peering_info: + result['ix-public-peer-info'] = ix_peering_info + result['locations'] += _locations_from_router( + ix_peering_info['router']) + + vpn_rr_peering_info = _vpn_rr_peering_info(r, address) + if vpn_rr_peering_info: + result['vpn-rr-peer-info'] = vpn_rr_peering_info + result['locations'] += _locations_from_router( + vpn_rr_peering_info['router']) interfaces = list(find_interfaces_and_services(address)) if interfaces: -- GitLab