diff --git a/inventory_provider/db/ims_data.py b/inventory_provider/db/ims_data.py index 43f65cc3199bad1ee24b24feec911fb165afd766..fca38c90bfeb46a3d54fc207835810624a52790d 100644 --- a/inventory_provider/db/ims_data.py +++ b/inventory_provider/db/ims_data.py @@ -647,3 +647,15 @@ def get_router_vendors(ds: IMS): ed_nav_properties): for r in ed.get('nodes', []): yield r['name'], ed.get('vendor', {}).get('name', None) + + +@log_entry_and_exit +def get_customer_regions(ds: IMS): + customer_id_map = {_c['id']: _c['name'] for _c in ds.get_all_entities('Customer', step_count=500)} + for _d in ds.get_filtered_entities( + 'ExtraFieldValue', + 'extrafield.id == 3329', # 3329: region field, will not change + EXTRA_FIELD_VALUE_PROPERTIES['ExtraFieldValueObjectInfo']): + _obj_id = _d['objectid'] + if _obj_id in customer_id_map: + yield {'id': _obj_id, 'name': customer_id_map[_obj_id], 'region': _d['value']} diff --git a/inventory_provider/routes/poller.py b/inventory_provider/routes/poller.py index 230d603679427fca13577ffa274762aeaea1796f..b6293ec7c1dbee594ccaf96c5d1659203cdb4d25 100644 --- a/inventory_provider/routes/poller.py +++ b/inventory_provider/routes/poller.py @@ -60,6 +60,12 @@ These endpoints are intended for use by BRIAN. .. autofunction:: inventory_provider.routes.poller.error_report_interfaces +/poller/regions +--------------- + +.. autofunction:: inventory_provider.routes.poller.get_nren_regions + + support method: _get_dashboards --------------------------------- @@ -132,6 +138,9 @@ class BRIAN_DASHBOARDS(Enum): # NREN customer NREN = auto() + # EAP NRENs + EAP = auto() + class PORT_TYPES(Enum): ACCESS = auto() @@ -438,6 +447,22 @@ STRING_LIST_SCHEMA = { 'items': {'type': 'string'} } +NREN_REGION_LIST_SCHEMA = { + '$schema': 'https://json-schema.org/draft-07/schema#', + 'definitions': { + 'nren_region': { + 'type': 'object', + 'properties': { + 'name': {'type': 'string'}, + 'region': {'type': 'string'} + } + } + }, + + 'type': 'array', + 'items': {'$ref': '#/definitions/nren_region'} +} + @routes.after_request def after_request(resp): @@ -520,7 +545,10 @@ def _get_dashboards(interface): yield BRIAN_DASHBOARDS.ANA -def _get_dashboard_data(ifc, customers): +def _get_dashboard_data(ifc, customers, regions=None): + if regions is None: + regions = [] + def _get_interface_type(description): if re.match(r'^PHY', description): return INTERFACE_TYPES.PHYSICAL @@ -577,6 +605,15 @@ def _get_dashboard_data(ifc, customers): if not names: return ifc + # add region-based dashboard names + region_names = [] + for name in names: + if name in regions: + region = regions[name] + if region == 'EAP': + region_names.append(BRIAN_DASHBOARDS.EAP.name) + names.extend(region_names) + # to maintain compatability with current brian dashboard manager we will # continue to return dashboard_info with the first customer name. We will # also return dashboards_info (note the plural of dashboards) with up to @@ -809,6 +846,20 @@ def _get_port_type(description): return PORT_TYPES.UNKNOWN.name +def _load_nren_regions(config, use_next_redis=False): + result = defaultdict(dict) + + key_pattern = 'ims:cache:customer_regions' + if use_next_redis: + r = tasks_common.get_next_redis(config) + else: + r = tasks_common.get_current_redis(config) + for id, nren in json.loads(r.get(key_pattern).decode('utf-8')).items(): + result[nren['name']] = nren['region'] + + return result + + def load_interfaces_to_poll(config, hostname=None, no_lab=False, use_next_redis=False): def is_relevant(ifc): return not re.match(r"^(lt-|so-|dsc\.|fxp\d|lo\d).*", ifc["name"]) @@ -820,6 +871,7 @@ def load_interfaces_to_poll(config, hostname=None, no_lab=False, use_next_redis= services_and_customers = \ _get_services_and_customers(config, hostname, use_next_redis) snmp_indexes = common.load_snmp_indexes(config, hostname, use_next_redis) + nren_regions = _load_nren_regions(config, use_next_redis) def _get_populated_interfaces(all_interfaces): if use_next_redis: @@ -851,7 +903,7 @@ def load_interfaces_to_poll(config, hostname=None, no_lab=False, use_next_redis= ifc['dashboards'] = sorted([d.name for d in dashboards]) ifc = _get_dashboard_data( - ifc, ifc_services_and_customers.get('customers', [])) + ifc, ifc_services_and_customers.get('customers', []), nren_regions) port_type = _get_port_type(ifc['description']) ifc['port_type'] = port_type yield ifc @@ -1703,3 +1755,19 @@ def gws_direct_config(): response=page, status=200, mimetype="text/html") + + +@routes.route('/regions', methods=['GET']) +@common.require_accepts_json +def get_nren_regions(): + """ + Handler for fetching regions of NRENs, as cached by the Inventory Provider update process. + :return: + """ + config_params = current_app.config['INVENTORY_PROVIDER_CONFIG'] + nren_region_dict = _load_nren_regions(config_params) + nren_regions = [{ + 'nren': nren, + 'region': region + } for nren, region in nren_region_dict.items()] + return jsonify(nren_regions) diff --git a/inventory_provider/tasks/worker.py b/inventory_provider/tasks/worker.py index 3f96af19f601d682cc5dafbb95513622c238d1d2..8ddfed3240810484e3de25ac760d9ede231bfaa8 100644 --- a/inventory_provider/tasks/worker.py +++ b/inventory_provider/tasks/worker.py @@ -1099,6 +1099,7 @@ def _extract_ims_data(ims_api_url, ims_username, ims_password, verify_ssl): additional_circuit_customers = {} flexils_data = {} customers = {} + customer_regions = {} hierarchy = {} port_id_details = defaultdict(list) @@ -1191,6 +1192,11 @@ def _extract_ims_data(ims_api_url, ims_username, ims_password, verify_ssl): nonlocal customers customers = {c['id']: c for c in _ds().get_all_entities('customer')} + @log_task_entry_and_exit + def _populate_customer_regions(): + nonlocal customer_regions + customer_regions = {c['id']: c for c in ims_data.get_customer_regions(ds=_ds())} + @log_task_entry_and_exit def _populate_flexils_data(): nonlocal flexils_data @@ -1223,7 +1229,8 @@ def _extract_ims_data(ims_api_url, ims_username, ims_password, verify_ssl): executor.submit(_populate_port_id_details): 'port_id_details', executor.submit(_populate_circuit_info): 'circuit_info', executor.submit(_populate_flexils_data): 'flexils_data', - executor.submit(_populate_customers): 'customers' + executor.submit(_populate_customers): 'customers', + executor.submit(_populate_customer_regions): 'customer_regions' } for future in concurrent.futures.as_completed(futures): @@ -1248,7 +1255,8 @@ def _extract_ims_data(ims_api_url, ims_username, ims_password, verify_ssl): 'port_id_services': port_id_services, 'geant_nodes': geant_nodes, 'flexils_data': flexils_data, - 'customers': customers + 'customers': customers, + 'customer_regions': customer_regions } diff --git a/test/data/router-info.json b/test/data/router-info.json index 5352029a65d23d054d1e445be633de72732417e2..71f098ab2cfa331b4b89ec4accc60640faac0a75 100644 Binary files a/test/data/router-info.json and b/test/data/router-info.json differ diff --git a/test/test_general_poller_routes.py b/test/test_general_poller_routes.py index 6c7b7e0d53392334e46ae52c544f6fe803e19cf8..1b9a67410416f3c203094c9fd83439fce026c371 100644 --- a/test/test_general_poller_routes.py +++ b/test/test_general_poller_routes.py @@ -527,3 +527,12 @@ def test_get_single_router_error_report_interfaces(client): response_data = json.loads(rv.data) jsonschema.validate(response_data, poller.ERROR_REPORT_INTERFACE_LIST_SCHEMA) assert {ifc['router'] for ifc in response_data} == {"mx1.ams.nl.geant.net"} + + +def test_get_nren_regions(client): + rv = client.get("/poller/regions", headers=DEFAULT_REQUEST_HEADERS) + assert rv.status_code == 200 + assert rv.is_json + response_data = json.loads(rv.data) + jsonschema.validate(response_data, poller.NREN_REGION_LIST_SCHEMA) + assert {nren_region['region'] for nren_region in response_data} # no values should be empty strings diff --git a/test/test_worker.py b/test/test_worker.py index 8dbd8767fb36a6d661ca38e287f956c43c021135..9118ee1e74044f3ec1c62aa7c913cfe00da5baf1 100644 --- a/test/test_worker.py +++ b/test/test_worker.py @@ -122,6 +122,16 @@ def test_extract_ims_data(mocker): } ] ) + mocker.patch( + 'inventory_provider.tasks.worker.ims_data.get_customer_regions', + return_value=[ + { + 'id': 1, + 'name': 'Cust 1', + 'region': 'REGION' + } + ] + ) res = extract_ims_data() assert res['locations'] == {'loc_a': 'LOC A', 'loc_b': 'LOC B'} assert res['site_locations'] == { @@ -129,6 +139,7 @@ def test_extract_ims_data(mocker): 'name': 'JEN-SPL'}} assert res['lg_routers'] == ['lg router 1', 'lg router 2'] assert res['customer_contacts'] == {'123': 'CON A', '456': 'CON B'} + assert res['customer_regions'] == {1: {'id': 1, 'name': 'Cust 1', 'region': 'REGION'}} assert res['planned_work_contacts'] == \ {'223': 'CON PW A', '556': 'CON PW B'} assert res['circuit_ids_to_monitor'] == [123, 456, 789] @@ -820,6 +831,9 @@ def test_populate_poller_interfaces_cache( "port_type": "SERVICE" } ] + nren_regions = { + 'NREN A': 'REGION A' + } for k in r.keys("lab:netconf-interfaces-hosts:*"): r.delete(k) @@ -835,6 +849,9 @@ def test_populate_poller_interfaces_cache( mocker.patch( 'inventory_provider.routes.poller._get_services_and_customers', return_value=services_and_customers) + mocker.patch( + 'inventory_provider.routes.poller._load_nren_regions', + return_value=nren_regions) mocker.patch( 'inventory_provider.tasks.worker.InventoryTask.config' )