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