From 8ab101d9b07764acc2ae51e7975bf78a921b52d1 Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Thu, 6 Jun 2024 13:35:29 +0200
Subject: [PATCH] 1- Removed managed_routers from config and tests. 2- Updated
 tests 3- Made gap.py function-based

---
 inventory_provider/config.py         |   3 -
 inventory_provider/gap.py            | 204 +++++++++++++--------------
 inventory_provider/juniper.py        |  15 --
 inventory_provider/routes/testing.py |   3 +-
 inventory_provider/tasks/worker.py   |   5 +-
 test/data/router-info.json           | Bin 137281293 -> 137282171 bytes
 test/test_flask_config.py            |  11 +-
 test/test_junos_devices_query.py     |  25 ----
 test/test_worker.py                  |  12 +-
 9 files changed, 120 insertions(+), 158 deletions(-)
 delete mode 100644 test/test_junos_devices_query.py

diff --git a/inventory_provider/config.py b/inventory_provider/config.py
index 950ad310..3fa03b9d 100644
--- a/inventory_provider/config.py
+++ b/inventory_provider/config.py
@@ -246,7 +246,6 @@ CONFIG_SCHEMA = {
             'minItems': 1,
             'items': {'type': 'integer'}
         },
-        'managed-routers': {'type': 'string'},
         'lab-routers': {
             'type': 'object',
             'properties': {
@@ -276,7 +275,6 @@ CONFIG_SCHEMA = {
                 'redis',
                 'redis-databases',
                 'ims',
-                'managed-routers',
                 'gws-direct',
                 'nren-asn-map',
                 'aai',
@@ -289,7 +287,6 @@ CONFIG_SCHEMA = {
                 'sentinel',
                 'redis-databases',
                 'ims',
-                'managed-routers',
                 'gws-direct',
                 'nren-asn-map',
                 'aai',
diff --git a/inventory_provider/gap.py b/inventory_provider/gap.py
index 7e2da432..2f728c31 100644
--- a/inventory_provider/gap.py
+++ b/inventory_provider/gap.py
@@ -1,124 +1,122 @@
 import logging
 
 import requests
-
-from inventory_provider.tasks.config import inventory_provider_config
+from flask import current_app
 
 logger = logging.getLogger(__name__)
 
+GRANT_TYPE = 'client_credentials'
+SCOPE = 'openid profile email aarc'
 
-class Orchestrator:
-    GRANT_TYPE = 'client_credentials'
-    SCOPE = 'openid profile email aarc'
-
-    def __init__(self):
-        self.config = inventory_provider_config['aai']['inventory_provider']
-        self.base_url = f'{inventory_provider_config["orchestrator"]["url"]}/api/graphql'
-
-    @staticmethod
-    def _get_token_endpoint() -> str:
-        response = requests.get(inventory_provider_config['aai']['discovery_endpoint_url'])
-        response.raise_for_status()
-        return response.json()['token_endpoint']
-
-    def _get_token(self) -> str:
-        response = requests.post(
-            self._get_token_endpoint(),
-            data={
-                'grant_type': self.GRANT_TYPE,
-                'scope': self.SCOPE,
-                'client_id': self.config['client_id'],
-                'client_secret': self.config['secret']
-            }
-        )
-        response.raise_for_status()
-        return response.json()['access_token']
-
-    def _get_headers(self) -> dict:
-        token = self._get_token()
-        return {
-            'Authorization': f'Bearer {token}'
-        }
 
-    def _make_request(self, url: str, body: dict) -> dict:
-        headers = self._get_headers()
-        response = requests.post(url, headers=headers, json=body)
-        response.raise_for_status()
-        return response.json()
-
-    def _extract_router_info(self, device: dict) -> dict or None:
-        tag_to_key_map = {
-            "RTR": "router",
-            "OFFICE_ROUTER": "office_router",
-            "Super_POP_SWITCH": "super_pop_switch"
-        }
+def get_token_endpoint(discovery_endpoint_url: str) -> str:
+    response = requests.get(discovery_endpoint_url)
+    response.raise_for_status()
+    return response.json()['token_endpoint']
 
-        tag = device.get("product", {}).get("tag")
-        key = tag_to_key_map.get(tag)
-        subscription_id = device.get("subscriptionId")
-
-        if key is None or subscription_id is None:
-            logger.warning(f"Skipping device with invalid tag or subscription ID: {device}")
-            return None
-
-        query = f"""
-        query {{
-                subscriptions(
-                    filterBy: {{ field: "subscriptionId", value: "{subscription_id}" }}
-                ) {{
-                    page {{
-                        subscriptionId
-                        productBlockInstances {{
-                            productBlockInstanceValues
-                        }}
+
+def get_token(aai_config: dict) -> str:
+    """Get an access token using the given configuration."""
+    response = requests.post(
+        get_token_endpoint(aai_config['discovery_endpoint_url']),
+        data={
+            'grant_type': GRANT_TYPE,
+            'scope': SCOPE,
+            'client_id': aai_config['inventory_provider']['client_id'],
+            'client_secret': aai_config['inventory_provider']['secret']
+        }
+    )
+    response.raise_for_status()
+    return response.json()['access_token']
+
+
+def make_request(body: dict) -> dict:
+    """Make a request to the orchestrator using the given body."""
+    config = current_app.config['INVENTORY_PROVIDER_CONFIG']
+    api_url = f'{config["orchestrator"]["url"]}/api/graphql'
+    headers = {'Authorization': f'Bearer {get_token(config["aai"])}'}
+    response = requests.post(api_url, headers=headers, json=body)
+    response.raise_for_status()
+    return response.json()
+
+
+def extract_router_info(device: dict) -> dict or None:
+    tag_to_key_map = {
+        "RTR": "router",
+        "OFFICE_ROUTER": "office_router",
+        "Super_POP_SWITCH": "super_pop_switch"
+    }
+
+    tag = device.get("product", {}).get("tag")
+    key = tag_to_key_map.get(tag)
+    subscription_id = device.get("subscriptionId")
+
+    if key is None or subscription_id is None:
+        logger.warning(f"Skipping device with invalid tag or subscription ID: {device}")
+        return None
+
+    query = f"""
+    query {{
+            subscriptions(
+                filterBy: {{ field: "subscriptionId", value: "{subscription_id}" }}
+            ) {{
+                page {{
+                    subscriptionId
+                    productBlockInstances {{
+                        productBlockInstanceValues
                     }}
                 }}
             }}
-            """
+        }}
+        """
 
-        response = self._make_request(self.base_url, body={'query': query})
-        page_data = response.get('data', {}).get('subscriptions', {}).get('page')
+    response = make_request(body={'query': query})
+    page_data = response.get('data', {}).get('subscriptions', {}).get('page')
 
-        if not page_data:
-            logger.warning(f"No data for subscription ID: {subscription_id}")
-            return None
+    if not page_data:
+        logger.warning(f"No data for subscription ID: {subscription_id}")
+        return None
 
-        instance_values = page_data[0].get('productBlockInstances', [{}])[0].get('productBlockInstanceValues', [])
+    instance_values = page_data[0].get('productBlockInstances', [{}])[0].get('productBlockInstanceValues', [])
 
-        fqdn = next((item.get('value') for item in instance_values if item.get('field') == f'{key}Fqdn'), None)
-        vendor = next((item.get('value') for item in instance_values if item.get('field') == 'vendor'), None)
+    fqdn = next((item.get('value') for item in instance_values if item.get('field') == f'{key}Fqdn'), None)
+    vendor = next((item.get('value') for item in instance_values if item.get('field') == 'vendor'), None)
 
-        if fqdn and vendor:
-            return {'fqdn': fqdn, 'vendor': vendor}
-        else:
-            logger.warning(f"Skipping device with missing FQDN or vendor: {device}")
-            return None
+    if fqdn and vendor:
+        return {'fqdn': fqdn, 'vendor': vendor}
+    else:
+        logger.warning(f"Skipping device with missing FQDN or vendor: {device}")
+        return None
 
-    def load_routers_from_orchestrator(self) -> dict:
-        query = """
-        {
-            subscriptions(
-                filterBy: {field: "status", value: "ACTIVE"},
-                query: "tag:(RTR|OFFICE_ROUTER|Super_POP_SWITCH)"
-                
-            ) {
-                page {
-                    subscriptionId
-                    product {
-                        tag
-                    }
+
+def load_routers_from_orchestrator() -> dict:
+    """Gets devices from the orchestrator and returns a dictionary of FQDNs and vendors."""
+
+    query = """
+    {
+        subscriptions(
+            filterBy: {field: "status", value: "ACTIVE"},
+            query: "tag:(RTR|OFFICE_ROUTER|Super_POP_SWITCH)"
+        ) {
+            page {
+                subscriptionId
+                product {
+                    tag
                 }
             }
         }
-        """
-        routers = {}
-        response = self._make_request(self.base_url, body={'query': query})
-        try:
-            devices = response['data']['subscriptions']['page']
-        except TypeError:
-            devices = []
-        for device in devices:
-            router_info = self._extract_router_info(device)
-            if router_info is not None:
-                routers[router_info['fqdn']] = router_info['vendor']
-        return routers
+    }
+    """
+    routers = {}
+    response = make_request(body={'query': query})
+
+    try:
+        devices = response['data']['subscriptions']['page']
+    except (TypeError, KeyError):
+        devices = []
+
+    for device in devices:
+        router_info = extract_router_info(device)
+        if router_info is not None:
+            routers[router_info['fqdn']] = router_info['vendor']
+    return routers
diff --git a/inventory_provider/juniper.py b/inventory_provider/juniper.py
index 47e1d1e3..3ca41b1c 100644
--- a/inventory_provider/juniper.py
+++ b/inventory_provider/juniper.py
@@ -393,21 +393,6 @@ def interface_addresses(netconf_config):
                 "interface name": ifc['name']
             }
 
-
-def load_routers_from_netdash(url):
-    """
-    Query url for a linefeed-delimitted list of managed router hostnames.
-
-    :param url: url of alldevices.txt file
-    :return: list of router hostnames
-    """
-    r = requests.get(url=url)
-    r.raise_for_status()
-    return [
-        ln.strip() for ln in r.text.splitlines() if ln.strip()
-    ]
-
-
 def local_interfaces(
         type=netifaces.AF_INET,
         omit_link_local=True,
diff --git a/inventory_provider/routes/testing.py b/inventory_provider/routes/testing.py
index 41e80603..5fcb7cce 100644
--- a/inventory_provider/routes/testing.py
+++ b/inventory_provider/routes/testing.py
@@ -55,7 +55,8 @@ def juniper_addresses():
     routers = r.get('netdash')
     assert routers  # sanity: value shouldn't be empty
     routers = json.loads(routers.decode('utf-8'))
-    return jsonify(routers)
+    juniper_routers = [k for k, v in routers.items() if v == 'juniper']
+    return jsonify(juniper_routers)
 
 
 @routes.route("bgp/<hostname>", methods=['GET'])
diff --git a/inventory_provider/tasks/worker.py b/inventory_provider/tasks/worker.py
index 813af6f0..7bd89612 100644
--- a/inventory_provider/tasks/worker.py
+++ b/inventory_provider/tasks/worker.py
@@ -20,7 +20,6 @@ from ncclient.transport import TransportError
 
 from inventory_provider.db import ims_data
 from inventory_provider.db.ims import IMS
-from inventory_provider.gap import Orchestrator
 from inventory_provider.routes.poller import load_error_report_interfaces, load_interfaces_to_poll
 from inventory_provider.tasks.app import app
 from inventory_provider.tasks.common \
@@ -28,7 +27,7 @@ from inventory_provider.tasks.common \
     latch_db, get_latch, set_latch, update_latch_status, \
     ims_sorted_service_type_key, set_single_latch
 from inventory_provider.tasks import monitor
-from inventory_provider import config, nokia
+from inventory_provider import config, nokia, gap
 from inventory_provider import environment
 from inventory_provider import snmp
 from inventory_provider import juniper
@@ -530,7 +529,7 @@ def retrieve_and_persist_neteng_managed_device_list(
     netdash_equipment = None
     try:
         info_callback('querying netdash for managed routers')
-        netdash_equipment = Orchestrator().load_routers_from_orchestrator()
+        netdash_equipment = gap.load_routers_from_orchestrator()
     except Exception as e:
         warning_callback(f'Error retrieving device list: {e}')
 
diff --git a/test/data/router-info.json b/test/data/router-info.json
index c5d6758fdbe6dd4be250a18fc25afa3c04386104..5352029a65d23d054d1e445be633de72732417e2 100644
GIT binary patch
delta 8147
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%0DmVt=n
zAYuiGSP3Flfr!;0VhxB`3nJEmi1i?11BloNA~u1D%^+e6h}a4uwt<N4AYuoI*a;$b
zfr#B8Vh@Pe3nKP`i2Weq0EjpUA`XFw!yw`ah&T!&j)92dAmRjwI0+(7fr!%};tYs5
z3nI>ei1Q%g0*JT>A})c5%OK(kh`0(Ou7QZ_AmRpyxCtU|fr#56;tq(o3nK1;i2ESo
z0f=}AA|8Q=#~|Vfh<FMjo`HzxAmRmxcnKoFCDLmU>kWu_3nJcui1#4k1BmztB0hnL
z&miIpi1-R3zJZAEAmRsz_z5C@fr#HA;tz=U3nKo3i2v-}iLW?7#UF@Z0ujt0f(1md
zf(SMc!44ugKm;d<-~ticAc6-(@PY_F5Wx>31VDr!h!6r1!XQEfM2Lb2F%TgRA|ybB
zB#4j#5z-(+21Lk$2sscT4<Zymgd&Jg0ujm}LIp&qf(SJbp$;N6K!hfU&;k+KAVP;@
Lf8r}$jx|>Reaw(j

delta 7387
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

diff --git a/test/test_flask_config.py b/test/test_flask_config.py
index 262b2a84..7ba83ec5 100644
--- a/test/test_flask_config.py
+++ b/test/test_flask_config.py
@@ -27,9 +27,18 @@ def config():
             'username': 'ims_username',
             'password': 'ims_password',
         },
-        'managed-routers': 'router_list',
         'gws-direct': [],
         'nren-asn-map': [],
+        'aai': {
+            'discovery_endpoint_url': 'https://smaple.discovery.endpoint',
+            'inventory_provider': {
+                'client_id': 'sample-client-id',
+                'secret': 'sample-secret'
+            }
+        },
+        'orchestrator': {
+            'url': 'https://orchestrator.url'
+        }
     }
 
 
diff --git a/test/test_junos_devices_query.py b/test/test_junos_devices_query.py
deleted file mode 100644
index b1ef7ee2..00000000
--- a/test/test_junos_devices_query.py
+++ /dev/null
@@ -1,25 +0,0 @@
-import os
-
-import responses
-
-from inventory_provider import juniper
-
-TEST_DATA_FILENAME = os.path.realpath(os.path.join(
-    os.path.dirname(__file__),
-    'data',
-    'netdash-alldevices.txt'))
-
-
-@responses.activate
-def test_junosspace_devices_parsing(data_config):
-    data_config['managed-routers'] = 'http://sabababa'
-    with open(TEST_DATA_FILENAME) as f:
-        responses.add(
-            method=responses.GET,
-            url=data_config['managed-routers'],
-            body=f.read())
-
-    hostnames = juniper.load_routers_from_netdash(
-        data_config['managed-routers'])
-
-    assert 'mx1.ams.nl.geant.net' in hostnames
diff --git a/test/test_worker.py b/test/test_worker.py
index dcc32853..d8628ff4 100644
--- a/test/test_worker.py
+++ b/test/test_worker.py
@@ -637,19 +637,16 @@ def test_persist_ims_data(mocker, data_config, mocked_redis):
 
 def test_retrieve_and_persist_neteng_managed_device_list(
         mocker, data_config, mocked_redis):
-    device_list = ['abc', 'def']
+    device_list = [{'abc': 'juniper'}, {'def': 'nokia'}]
     r = common._get_redis(data_config)
 
     mocker.patch(
         'inventory_provider.tasks.worker.InventoryTask.config'
     )
-    mocker.patch('inventory_provider.tasks.worker.get_next_redis',
-                 return_value=r)
+    mocker.patch('inventory_provider.tasks.worker.get_next_redis', return_value=r)
     r.delete('netdash')
-    mocked_j = mocker.patch(
-        'inventory_provider.tasks.worker.juniper.load_routers_from_netdash'
-    )
-    mocked_j.return_value = device_list
+    mocker.patch('inventory_provider.tasks.worker.get_current_redis', return_value=r)
+    mocker.patch('inventory_provider.gap.load_routers_from_orchestrator', return_value=device_list)
     result = retrieve_and_persist_neteng_managed_device_list()
     assert result == device_list
     assert json.loads(r.get('netdash')) == device_list
@@ -1020,6 +1017,7 @@ def test_populate_error_report_interfaces_cache(mocker, data_config, mocked_redi
     mocker.patch(
         'inventory_provider.tasks.worker.InventoryTask.config'
     )
+    mocker.patch('inventory_provider.routes.poller.load_error_report_interfaces.get_vendor', return_value='juniper')
     exp_router_a_interfaces = [
         {
             "router": "router_a.geant.net",
-- 
GitLab