From 711cd409ce17b36fda604984952982ddf970a77d Mon Sep 17 00:00:00 2001
From: Robert Latta <robert.latta@geant.org>
Date: Mon, 2 Nov 2020 14:44:47 +0000
Subject: [PATCH] temporary file for parallel development of IMS based
 classification

---
 inventory_provider/routes/ims_classifier.py | 219 ++++++++++++++++++++
 1 file changed, 219 insertions(+)
 create mode 100644 inventory_provider/routes/ims_classifier.py

diff --git a/inventory_provider/routes/ims_classifier.py b/inventory_provider/routes/ims_classifier.py
new file mode 100644
index 00000000..7f6084a9
--- /dev/null
+++ b/inventory_provider/routes/ims_classifier.py
@@ -0,0 +1,219 @@
+import ipaddress
+import json
+import logging
+
+from flask import Blueprint, Response, current_app
+
+from inventory_provider.routes import common
+
+routes = Blueprint("ims-inventory-data-classifier-support-routes", __name__)
+
+logger = logging.getLogger(__name__)
+
+
+class ClassifierRequestError(Exception):
+    status_code = 500
+
+    def __init__(self):
+        super().__init__()
+        self.message = "Unclassified Internal Error"
+
+
+class ClassifierProcessingError(ClassifierRequestError):
+    status_code = 422
+
+    def __init__(self, message, status_code=None):
+        super().__init__()
+        self.message = str(message)
+        if status_code is not None:
+            self.status_code = status_code
+
+
+@routes.errorhandler(ClassifierRequestError)
+def handle_request_error(error):
+    return Response(
+        response=error.message,
+        status=error.status_code)
+
+
+@routes.after_request
+def after_request(resp):
+    return common.after_request(resp)
+
+
+def _LOCATION(equipment, name, abbreviation):
+    return {
+        'equipment': equipment,
+        'name': name,
+        'abbreviation': abbreviation
+    }
+
+
+def _remove_duplicates_from_list(all):
+    """
+    removes duplicates from the input list
+    the list items must be encodable as json
+    :param l:
+    :return: a new list with unique elements
+    """
+    tmp_dict = dict([(json.dumps(item, sort_keys=True), item) for item in all])
+    return list(tmp_dict.values())
+
+
+def _locations_from_router(router_name):
+    r = common.get_current_redis()
+    result = r.get(f'ims:location:{router_name}')
+    if not result:
+        logger.error(f'error looking up location for {router_name}')
+        return []
+
+    result = json.loads(result.decode('utf-8'))
+    if not result:
+        logger.error(f'sanity failure: empty list for location {router_name}')
+        return []
+
+    return [{
+        'a': _LOCATION(
+            equipment=result[0]['equipment-name'],
+            name=result[0]['pop']['name'],
+            abbreviation=result[0]['pop']['abbreviation'])
+    }]
+
+
+def _location_from_service_dict(s):
+    location = {
+        'a': _LOCATION(
+            equipment=s['equipment'],
+            name=s['pop_name'],
+            abbreviation=s['pop_abbreviation'])
+    }
+
+    if all(s[n] for n in (
+            'other_end_equipment',
+            'other_end_pop_name',
+            'other_end_pop_abbreviation')):
+        location['b'] = _LOCATION(
+            equipment=s['other_end_equipment'],
+            name=s['other_end_pop_name'],
+            abbreviation=s['other_end_pop_abbreviation'])
+
+    return location
+
+
+def related_interfaces(hostname, interface):
+    r = common.get_current_redis()
+    prefix = 'netconf-interfaces:%s:' % hostname
+    for k in r.keys(prefix + interface + '.*'):
+        k = k.decode('utf-8')
+        assert k.startswith(prefix)  # sanity
+        assert len(k) > len(prefix)  # sanity (contains at least an interface)
+        yield k[len(prefix):]
+
+
+def get_top_level_services(circuit_id, r):
+    tls = []
+    results = r.get("ims:services:children:{}".format(circuit_id))
+    if results:
+        results = json.loads(results.decode('utf-8'))
+
+        for c in results:
+            temp_parents = \
+                get_top_level_services(c['parent_circuit_id'], r)
+            if not temp_parents:
+                tls.append(
+                    {'name': c['parent_circuit'],
+                     'status': c['parent_circuit_status'],
+                     'circuit_type': c['parent_circuit_type'].lower(),
+                     'project': c['parent_project']
+                     })
+            tls.extend(temp_parents)
+    return tls
+
+
+@routes.route("/juniper-link-info/<source_equipment>/<path:interface>",
+              methods=['GET', 'POST'])
+@common.require_accepts_json
+def get_juniper_link_info(source_equipment: str, interface: str):
+    ims_source_equipment = source_equipment.upper()
+    ims_interface = interface.upper()
+    if ims_source_equipment.startswith('MX'):
+        ims_source_equipment = ims_source_equipment.split('.GEANT.NET')[0]
+
+    r = common.get_current_redis()
+
+    cache_key = f'ims-classifier-cache:juniper:{ims_source_equipment}:{ims_interface}'
+
+    # result = r.get(cache_key)
+    result = False
+
+    if result:
+        result = result.decode('utf-8')
+    else:
+
+        result = {
+            'locations': []
+        }
+
+        top_level_services = []
+
+        services = r.get(
+            f'ims:interface_services:{ims_source_equipment}:{ims_interface}')
+        if services:
+            result['services'] = json.loads(services.decode('utf=8'))
+            for s in result['services']:
+                top_level_services.extend(get_top_level_services(s['id'], r))
+            result['locations'] += [
+                _location_from_service_dict(s) for s in result['services']]
+
+        ifc_info = r.get(f'netconf-interfaces:{source_equipment}:{interface}')
+        if ifc_info:
+            result['interface'] = json.loads(ifc_info.decode('utf-8'))
+        else:
+            # warning: this should match the structure returned by
+            #          juniper:list_interfaces:_ifc_info
+            result['interface'] = {
+                'name': interface,
+                'description': '',
+                'bundle': [],
+                'ipv4': [],
+                'ipv6': []
+            }
+        bundle_members = r.get(
+            f'netconf-interface-bundles:{source_equipment}:{interface}')
+        if bundle_members:
+            result['interface']['bundle_members'] = \
+                json.loads(bundle_members.decode('utf-8'))
+        else:
+            result['interface']['bundle_members'] = []
+
+        def _related_services():
+            for related in related_interfaces(source_equipment, interface):
+                logger.debug(f'Related Interface: {related}')
+                rs = r.get(f'ims:interface_services:{ims_source_equipment}:'
+                           f'{related.upper()}')
+                if rs:
+                    for s in json.loads(rs.decode('utf-8')):
+                        top_level_services.extend(
+                            get_top_level_services(s['id'], r))
+                        yield {
+                            'name': s['name'],
+                            'status': s['status'],
+                            'circuit_type': s['circuit_type'],
+                            'project': s['project']
+                        }
+
+        related_services = list(_related_services())
+        if related_services:
+            top_level_services.extend(related_services)
+        if top_level_services:
+            result['related-services'] = top_level_services
+
+        if not result['locations']:
+            result['locations'] = _locations_from_router(ims_source_equipment)
+
+        result['locations'] = _remove_duplicates_from_list(result['locations'])
+        result = json.dumps(result)
+        # cache this data for the next call
+        r.set(cache_key, result.encode('utf-8'))
+
+    return Response(result, mimetype="application/json")
-- 
GitLab