From 226a3077463be7de9dbcb358f0d0fde4aac2b05f Mon Sep 17 00:00:00 2001
From: Erik Reid <erik.reid@geant.org>
Date: Wed, 10 Aug 2022 13:08:38 +0200
Subject: [PATCH] initial raft /msr/services updated with redundant asn

---
 inventory_provider/routes/msr.py | 56 ++++++++++++++++++++++++--------
 1 file changed, 42 insertions(+), 14 deletions(-)

diff --git a/inventory_provider/routes/msr.py b/inventory_provider/routes/msr.py
index 8c052a67..cdd3cc64 100644
--- a/inventory_provider/routes/msr.py
+++ b/inventory_provider/routes/msr.py
@@ -260,7 +260,8 @@ SYSTEM_CORRELATION_SERVICES_LIST_SCHEMA = {
                 'service_type': {'type': 'string'},  # TODO: enum?
                 'project': {'type': 'string'},  # TODO: remove this?
                 'customer': {'type': 'string'},
-                'endpoints': {'$ref': '#/definitions/endpoints'}
+                'endpoints': {'$ref': '#/definitions/endpoints'},
+                'redundant_asn': {'type': 'integer'}
             },
             'required': [
                 'circuit_id', 'sid', 'name', 'speed', 'status', 'monitored',
@@ -905,39 +906,51 @@ def get_system_correlation_services():
     r = common.get_current_redis()
     response = _ignore_cache_or_retrieve(request, cache_key, r)
     if not response:
-        peering_info = defaultdict(defaultdict)
 
-        key_pattern = 'netconf-interfaces:*'
+        # dict of dicts:
+        #   peering_info[hostname][interface_name] = dict of ifc details
+        peering_info = defaultdict(dict)
 
-        host_if_extraction_re = re.compile(
-            r'^netconf-interfaces:(.+?):')
+        host_if_extraction_re = re.compile(r'^netconf-interfaces:(.+?):')
         for doc in common.load_json_docs(
                 config_params=current_app.config['INVENTORY_PROVIDER_CONFIG'],
-                key_pattern=key_pattern,
+                key_pattern='netconf-interfaces:*',
                 num_threads=20):
             matches = host_if_extraction_re.match(doc['key'])
             if matches:
                 peering_info[matches[1]][doc['value']['name']] = doc['value']
 
+        def _load_redundant_peers():
+            # REPORTING-312: limited to eGEANT group,
+            # but this can be expanded here in future
+            redundant_access_groups = ['eGEANT']
+            for g in redundant_access_groups:
+                doc = r.get(f'juniper-peerings:group:{g}')
+                for peer in json.loads(doc.decode('utf-8')):
+                    yield peer['address'], peer['remote-asn']
+
+        # dict of peers & asn's: [peer address] -> asn
+        redundant_access_group_peers = dict(_load_redundant_peers())
+
         def _ip_endpoint_extractor(endpoint_details: Dict):
-            hostname = ims_equipment_to_hostname(
-                endpoint_details['equipment'])
+            hostname = ims_equipment_to_hostname(endpoint_details['equipment'])
             interface = endpoint_details['port'].lower()
 
             ip_endpoint = {
                 'hostname': hostname,
                 'interface': interface,
             }
-            addresses = {}
-            host_info = peering_info.get(hostname, {})
+            host_info = peering_info[hostname]
             interface_info = host_info.get(interface, {})
+
+            addresses = {}
             ipv4 = interface_info.get('ipv4')
             ipv6 = interface_info.get('ipv6')
             if ipv4:
                 addresses['v4'] = ipv4[0]
             if ipv6:
                 addresses['v6'] = ipv6[0]
-            if ipv4 or ipv6:
+            if addresses:
                 ip_endpoint['addresses'] = addresses
 
             return ip_endpoint
@@ -952,12 +965,23 @@ def get_system_correlation_services():
             if not endpoint_details['geant_equipment']:
                 return
             potential_hostname = ims_equipment_to_hostname(
-                    endpoint_details['equipment'])
+                endpoint_details['equipment'])
             if potential_hostname in peering_info.keys():
                 return _ip_endpoint_extractor(endpoint_details)
             else:
                 return _optical_endpoint_extractor(endpoint_details)
 
+        def _get_redundancy_asn(service):
+            for ep in service['endpoints']:
+                addresses = ep.get('addresses', {})
+                for ifc_address in addresses.values():
+                    for p, asn in redundant_access_group_peers.items():
+                        peer = ipaddress.ip_address(p)
+                        ifc = ipaddress.ip_interface(ifc_address)
+                        if peer in ifc.network:
+                            return asn
+            return None
+
         sid_services = json.loads(r.get('ims:sid_services').decode('utf-8'))
 
         response = []
@@ -977,11 +1001,15 @@ def get_system_correlation_services():
                 endpoint = _endpoint_extractor(d)
                 if endpoint:
                     service_info['endpoints'].append(endpoint)
+
             if service_info.get('endpoints'):
+                asn = _get_redundancy_asn(service_info)
+                if asn:
+                    service_info['redundant_asn'] = asn
                 response.append(service_info)
 
-        jsonschema.validate(response,
-                            SYSTEM_CORRELATION_SERVICES_LIST_SCHEMA)
+        jsonschema.validate(
+            response, SYSTEM_CORRELATION_SERVICES_LIST_SCHEMA)
 
         if response:
             response = json.dumps(response, indent=2)
-- 
GitLab