diff --git a/Changelog.md b/Changelog.md index 629a0d2d34555744c3f4b93d6a519553b791460f..06f0b391900a03aa5f75abf8a0ee03d6df4f6ebb 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,9 @@ All notable changes to this project will be documented in this file. +## [0.121] - 2024-06-17 +- DBOARD3-956: Include all Related Services contact info in TTS notification + ## [0.120] - 2024-05-21 - FIX: /poller/interfaces - Nokia snmp should be integers diff --git a/inventory_provider/tasks/worker.py b/inventory_provider/tasks/worker.py index 11900cb3a8f5b405269e4e7b38589f67d8f1ce41..82c0699cd5b44e5453e3cec646a776e5f92fcb1f 100644 --- a/inventory_provider/tasks/worker.py +++ b/inventory_provider/tasks/worker.py @@ -6,14 +6,13 @@ import json import logging import os import re -from typing import List import ncclient.transport.errors from celery import Task, states, chord from celery.result import AsyncResult from celery import signals -from collections import defaultdict +from collections import defaultdict, namedtuple from kombu.exceptions import KombuError from lxml import etree @@ -1271,171 +1270,211 @@ def _extract_ims_data(ims_api_url, ims_username, ims_password, verify_ssl): } +def _convert_to_bits(value, unit): + unit = unit.lower() + conversions = { + 'm': 1 << 20, + 'mb': 1 << 20, + 'g': 1 << 30, + 'gbe': 1 << 30, + } + return int(value) * conversions[unit] + + +def _get_speed(circuit_id, hierarchy): + c = hierarchy.get(circuit_id) + if c is None: + return 0 + if c['status'] != 'operational': + return 0 + pattern = re.compile(r'^(\d+)([a-zA-z]+)$') + m = pattern.match(c['speed']) + if m: + try: + return _convert_to_bits(m[1], m[2]) + except KeyError as e: + logger.debug(f'Could not find key: {e} ' + f'for circuit: {circuit_id}') + return 0 + else: + if c['circuit-type'] == 'service' \ + or c['product'].lower() == 'ethernet': + return sum( + (_get_speed(x, hierarchy) for x in c['carrier-circuits']) + ) + else: + return 0 + + def transform_ims_data(data): locations = data['locations'] customer_contacts = data['customer_contacts'] planned_work_contacts = data['planned_work_contacts'] circuit_ids_to_monitor = data['circuit_ids_to_monitor'] additional_circuit_customers = data['additional_circuit_customers'] - hierarchy = data['hierarchy'] - port_id_details = data['port_id_details'] - port_id_services = data['port_id_services'] + + # This is only used within this function and the populate_mic_with_third_party_data + # The following data gets added to this + # contacts + # planned_work_contacts + # sid + # third_party_id - only if it exists for the circuit + hierarchy = data['hierarchy'] # data in this gets modified + + # These two just gets the flex ILS data added to them. + # They are also used for building interface_services + port_id_details = data['port_id_details'] # data in this gets modified + port_id_services = data['port_id_services'] # data in this gets modified + circuit_ids_and_sids = data['circuit_ids_sids'] circuit_ids_and_third_party_ids = data['circuit_ids_third_party_ids'] geant_nodes = data['geant_nodes'] flexils_data = data['flexils_data'] customers = data['customers'] - sid_services = defaultdict(list) + # this is the data that most classification data will be built from + # the keys are in the format hosts:interfaces + # e.g. + # MX1.FRA.DE:ET-1/0/2 + # MX1.LON.UK:AE12 + # MX1.LON.UK:AE12.123 + interface_services = {} - def _get_circuit_contacts(c): - customer_ids = {c['customerid']} - customer_ids.update( - [ac['id'] for ac in additional_circuit_customers.get(c['id'], [])] - ) - tts_contacts = set().union( - *[customer_contacts.get(cid, []) for cid in customer_ids]) - pw_contacts = set().union( - *[planned_work_contacts.get(cid, []) for cid in customer_ids]) - return tts_contacts, pw_contacts - - for d in hierarchy.values(): - c, ttc = _get_circuit_contacts(d) - d['contacts'] = sorted(list(c)) - d['planned_work_contacts'] = sorted(list(ttc)) - d['sid'] = circuit_ids_and_sids.get(d['id'], '') - - if d['id'] in circuit_ids_and_third_party_ids: - d['third_party_id'] = circuit_ids_and_third_party_ids[d['id']] - - # add flexils data to port_id_details and port_id_services - all_ils_details = flexils_data.get(d['id']) - if all_ils_details: - for ils_details in all_ils_details: - pid = port_id_details.get(ils_details['key'], []) - pid.append({ - 'port_id': ils_details['key'], - 'equipment_name': ils_details['node_name'], - 'interface_name': ils_details['full_port_name'] - }) - port_id_details[ils_details['key']] = pid - - pis = port_id_services.get(ils_details['key'], []) - pis.append({ - 'id': d['id'], - 'name': d['name'], - 'project': d['project'], - 'port_a_id': ils_details['key'], - 'circuit_type': d['circuit-type'], - 'status': d['status'], - 'service_type': d['product'], - 'customerid': d['customerid'], - 'customer': customers.get(d['customerid'], ''), - 'contacts': d['contacts'], - 'planned_work_contacts': d['planned_work_contacts'] - }) - port_id_services[ils_details['key']] = pis - - def _convert_to_bits(value, unit): - unit = unit.lower() - conversions = { - 'm': 1 << 20, - 'mb': 1 << 20, - 'g': 1 << 30, - 'gbe': 1 << 30, - } - return int(value) * conversions[unit] - - def _get_speed(circuit_id): - c = hierarchy.get(circuit_id) - if c is None: - return 0 - if c['status'] != 'operational': - return 0 - pattern = re.compile(r'^(\d+)([a-zA-z]+)$') - m = pattern.match(c['speed']) - if m: - try: - return _convert_to_bits(m[1], m[2]) - except KeyError as e: - logger.debug(f'Could not find key: {e} ' - f'for circuit: {circuit_id}') - return 0 - else: - if c['circuit-type'] == 'service' \ - or c['product'].lower() == 'ethernet': - return sum( - (_get_speed(x) for x in c['carrier-circuits']) - ) - else: - return 0 - - def _get_fibre_routes(c_id): - _circ = hierarchy.get(c_id, None) - if _circ is None: - return - if _circ['speed'].lower() == 'fibre_route': - yield _circ['id'] - else: - for cc in _circ['carrier-circuits']: - yield from _get_fibre_routes(cc) - - def _get_related_services(circuit_id: str) -> List[dict]: - rs = {} - c = hierarchy.get(circuit_id, None) - if c: - - if c['circuit-type'] == 'service': - rs[c['id']] = { - 'id': c['id'], - 'name': c['name'], - 'circuit_type': c['circuit-type'], - 'service_type': c['product'], - 'project': c['project'], - 'contacts': c['contacts'], - 'planned_work_contacts': c['planned_work_contacts'] + services_by_type = {} + node_pair_services = {} + sid_services = {} + pop_nodes = {} + + # populate pop_nodes + for location in locations.values(): + pop_nodes.setdefault(location['pop']['name'], []).append(location['equipment-name']) + + def _get_circuit_contacts(_circuit): + _customer_ids = {_circuit['customerid']} + _customer_ids.update(ac['id'] for ac in additional_circuit_customers.get(_circuit['id'], ())) + + _trouble_ticket_contacts = set() + _planned_work_contacts = set() + for cid in _customer_ids: + _trouble_ticket_contacts.update(customer_contacts.get(cid, ())) + _planned_work_contacts.update(planned_work_contacts.get(cid, ())) + + return _trouble_ticket_contacts, _planned_work_contacts + + def _add_details_to_hierarchy(): + # this updates the values in the hierarchy dict of the outer scope + # it is an internal function just to clearly group the logic + for circuit_id, hierarchy_entry in hierarchy.items(): + # add contacts + tt_contacts, pw_contacts = _get_circuit_contacts(hierarchy_entry) + hierarchy_entry['contacts'] = sorted(tt_contacts) + hierarchy_entry['planned_work_contacts'] = sorted(pw_contacts) + # add SIDs + hierarchy_entry['sid'] = circuit_ids_and_sids.get(circuit_id, '') + # add third party ids to hierarchy - iff one exists + if circuit_id in circuit_ids_and_third_party_ids: + hierarchy_entry['third_party_id'] = circuit_ids_and_third_party_ids[circuit_id] + + _add_details_to_hierarchy() + + def _add_flex_ils_details_to_port_id_details(_flex_ils_details): + port_id_details[flex_ils_details['key']] = [{ + 'port_id': flex_ils_details['key'], + 'equipment_name': flex_ils_details['node_name'], + 'interface_name': flex_ils_details['full_port_name'] + }] + + def _add_flex_ils_details_to_port_id_services(_flex_ils_details, _circuit_id): + circuit = hierarchy[circuit_id] + port_id_services[flex_ils_details['key']] = [{ + 'id': circuit['id'], + 'name': circuit['name'], + 'project': circuit['project'], + 'port_a_id': flex_ils_details['key'], + 'circuit_type': circuit['circuit-type'], + 'status': circuit['status'], + 'service_type': circuit['product'], + 'customerid': circuit['customerid'], + 'customer': customers.get(circuit['customerid'], ''), + 'contacts': circuit['contacts'], + 'planned_work_contacts': circuit['planned_work_contacts'] + }] + + # add flex ils details to port_id_details and port_id_services + # flex_ils_data is a dict of circuit_ids to list of flex_ils_details + for circuit_id, flex_ils_details_per_circuit_id in flexils_data.items(): + if circuit_id: # add details iff there is a circuit (there can be a None key) + # an example of flex_ils_details: + # {'node_name': 'PAR01-MTC6-1', 'full_port_name': '5-A-1-L1-1', 'key': 'PAR01-MTC6-1:5-A-1-L1-1'} + for flex_ils_details in flex_ils_details_per_circuit_id: + _add_flex_ils_details_to_port_id_details(flex_ils_details) + _add_flex_ils_details_to_port_id_services(flex_ils_details, circuit_id) + + def _get_related_services(_circuit_id): + # this may want to go outside the transform function so that it can be used + # by other functions, in which case we would need to also pass + # - hierarchy + # - circuit_ids_to_monitor + # - circuit_ids_and_sids + + # using a dict as an easy way to ensure unique services + related_services = {} + + if _circuit_id and _circuit_id in hierarchy: + circuit = hierarchy.get(_circuit_id, None) + + if circuit['circuit-type'] == 'service': + related_services[circuit['id']] = { + 'id': circuit['id'], + 'name': circuit['name'], + 'circuit_type': circuit['circuit-type'], + 'service_type': circuit['product'], + 'project': circuit['project'], + 'contacts': circuit['contacts'], + 'planned_work_contacts': circuit['planned_work_contacts'] } - if c['id'] in circuit_ids_to_monitor: - rs[c['id']]['status'] = c['status'] + if circuit['id'] in circuit_ids_to_monitor: + related_services[circuit['id']]['status'] = circuit['status'] else: - rs[c['id']]['status'] = 'non-monitored' - - if c['id'] in circuit_ids_and_sids: - rs[c['id']]['sid'] = circuit_ids_and_sids[c['id']] - - if c['sub-circuits']: - for sub in c['sub-circuits']: - temp_parents = \ - _get_related_services(sub) - rs.update({t['id']: t for t in temp_parents}) - return list(rs.values()) - - def _format_service(s): - s['additional_customers'] = \ - additional_circuit_customers.get(s['id'], []) - s['original_status'] = s['status'] - s['monitored'] = True - if s['circuit_type'] == 'service' \ - and s['id'] not in circuit_ids_to_monitor: - s['monitored'] = False - s['status'] = 'non-monitored' - pd_a = port_id_details[s['port_a_id']][0] - location_a = locations.get(pd_a['equipment_name'], None) + related_services[circuit['id']]['status'] = 'non-monitored' + + if circuit['id'] in circuit_ids_and_sids: + related_services[circuit['id']]['sid'] = circuit_ids_and_sids[circuit['id']] + + if circuit['sub-circuits']: + for sub_circuit_id in circuit['sub-circuits']: + temp_parents = _get_related_services(sub_circuit_id) + related_services.update({t['id']: t for t in temp_parents}) + return related_services.values() + + def _format_circuit(circuit): + circuit['additional_customers'] = additional_circuit_customers.get(circuit['id'], []) + circuit['original_status'] = circuit['status'] + circuit['monitored'] = True + if circuit['circuit_type'] == 'service' and circuit['id'] not in circuit_ids_to_monitor: + circuit['monitored'] = False + circuit['status'] = 'non-monitored' + + # there is only ever 1 item in the list, next refactor should remove the list + port_details_a = port_id_details[circuit['port_a_id']][0] + location_a = locations.get(port_details_a['equipment_name'], None) if location_a: loc_a = location_a['pop'] else: loc_a = locations['UNKNOWN_LOC']['pop'] logger.warning( - f'Unable to find location for {pd_a["equipment_name"]} - ' - f'Service ID {s["id"]}') - s['pop_name'] = loc_a['name'] - s['pop_abbreviation'] = loc_a['abbreviation'] - s['equipment'] = pd_a['equipment_name'] - s['card_id'] = '' # this is redundant I believe - s['port'] = pd_a['interface_name'] - s['logical_unit'] = '' # this is redundant I believe - if 'port_b_id' in s: - pd_b = port_id_details[s['port_b_id']][0] + f'Unable to find location for {port_details_a["equipment_name"]} - ' + f'Service ID {circuit["id"]}') + circuit['pop_name'] = loc_a['name'] + circuit['pop_abbreviation'] = loc_a['abbreviation'] + circuit['equipment'] = port_details_a['equipment_name'] + circuit['card_id'] = '' # this is redundant I believe + circuit['port'] = port_details_a['interface_name'] + circuit['logical_unit'] = '' # this is redundant I believe + if 'port_b_id' in circuit: + + # there is only ever 1 item in the list, next refactor should remove the list + pd_b = port_id_details[circuit['port_b_id']][0] location_b = locations.get(pd_b['equipment_name'], None) if location_b: loc_b = location_b['pop'] @@ -1443,76 +1482,68 @@ def transform_ims_data(data): loc_b = locations['UNKNOWN_LOC']['pop'] logger.warning( f'Unable to find location for {pd_b["equipment_name"]} - ' - f'Service ID {s["id"]}') + f'Service ID {circuit["id"]}') - s['other_end_pop_name'] = loc_b['name'] - s['other_end_pop_abbreviation'] = loc_b['abbreviation'] - s['other_end_equipment'] = pd_b['equipment_name'] - s['other_end_port'] = pd_b['interface_name'] + circuit['other_end_pop_name'] = loc_b['name'] + circuit['other_end_pop_abbreviation'] = loc_b['abbreviation'] + circuit['other_end_equipment'] = pd_b['equipment_name'] + circuit['other_end_port'] = pd_b['interface_name'] else: - s['other_end_pop_name'] = '' - s['other_end_pop_abbreviation'] = '' - s['other_end_equipment'] = '' - s['other_end_port'] = '' - - s.pop('port_a_id', None) - s.pop('port_b_id', None) - - services_by_type = {} - interface_services = defaultdict(list) - # using a dict to ensure no duplicates - node_pair_services = defaultdict(dict) - pop_nodes = defaultdict(list) - - for value in locations.values(): - pop_nodes[value['pop']['name']].append(value['equipment-name']) - - for key, value in port_id_details.items(): - for details in value: - k = f"{details['equipment_name']}:" \ - f"{details['interface_name']}" + circuit['other_end_pop_name'] = '' + circuit['other_end_pop_abbreviation'] = '' + circuit['other_end_equipment'] = '' + circuit['other_end_port'] = '' + + circuit.pop('port_a_id', None) + circuit.pop('port_b_id', None) + + # recursive function which iterates the circuit tree to find all the fibre routes + # the given circuit is carried by + def _get_fibre_routes(_circuit_id): + FibreRoute = namedtuple('FibreRoute', 'id name status') + _circuit = hierarchy.get(_circuit_id, None) + if _circuit is None: + return + if _circuit['speed'].lower() == 'fibre_route': + yield FibreRoute(_circuit['id'], _circuit['name'], _circuit['status']) + else: + for carrier_circuit_id in _circuit['carrier-circuits']: + yield from _get_fibre_routes(carrier_circuit_id) + + def _build_interface_services(): + for port_id, port_details in port_id_details.items(): + _contacts = set() + _planned_work_contacts = set() + details = port_details[0] # there is only ever 1 item in the list, next refactor should remove the list circuits = port_id_services.get(details['port_id'], []) - - for circ in circuits: - # contacts, pw_contacts = _get_circuit_contacts(circ) - # we only want to include the Related Services contacts - contacts = set() - pw_contacts = set() - circ['fibre-routes'] = [] - for x in set(_get_fibre_routes(circ['id'])): - c = { - 'id': hierarchy[x]['id'], - 'name': hierarchy[x]['name'], - 'status': hierarchy[x]['status'] - } - circ['fibre-routes'].append(c) - - circ['related-services'] = \ - _get_related_services(circ['id']) - - for tlc in circ['related-services']: - # why were these removed? - # contacts.update(tlc.pop('contacts')) - if circ['status'] == 'operational' \ - and circ['id'] in circuit_ids_to_monitor \ - and tlc['status'] == 'operational' \ - and tlc['id'] in circuit_ids_to_monitor: - contacts.update(tlc.get('contacts')) - pw_contacts.update( - tlc.get('planned_work_contacts', [])) - circ['contacts'] = sorted(list(contacts)) - circ['planned_work_contacts'] = sorted(list(pw_contacts)) - - circ['calculated-speed'] = _get_speed(circ['id']) - _format_service(circ) - - type_services = services_by_type.setdefault( - ims_sorted_service_type_key(circ['service_type']), dict()) - type_services[circ['id']] = circ - if circ['other_end_equipment']: - node_pair_services[ - f"{circ['equipment']}/{circ['other_end_equipment']}" - ][circ['id']] = circ + for _circuit in circuits: + # add fibre routes + _circuit['fibre-routes'] = [fr._asdict() for fr in set(_get_fibre_routes(_circuit['id']))] + + # add related services + _circuit['related-services'] = list(_get_related_services(_circuit['id'])) + + # update contact list bases on related services + if _circuit['status'] == 'operational': + for related_service in _circuit['related-services']: + if related_service['status'] == 'operational' \ + and related_service['id'] in circuit_ids_to_monitor: + _contacts.update(related_service.get('contacts', [])) + _planned_work_contacts.update(related_service.get('planned_work_contacts', [])) + _circuit['contacts'] = sorted(_contacts) + _circuit['planned_work_contacts'] = sorted(_planned_work_contacts) + + # add speed + _circuit['calculated-speed'] = _get_speed(_circuit['id'], hierarchy) + + # add third party ids + if _circuit['id'] in circuit_ids_and_third_party_ids: + _circuit['third_party_id'] = circuit_ids_and_third_party_ids[_circuit['id']] + + # need to do this before the sid processing as that can skip records + _format_circuit(_circuit) + + # add sid info try: # get the physical port circuit, if it exists # https://jira.software.geant.org/browse/POL1-687 @@ -1521,15 +1552,12 @@ def transform_ims_data(data): except StopIteration: port_circuit = None - if circ['id'] in circuit_ids_and_third_party_ids: - circ['third_party_id'] = circuit_ids_and_third_party_ids[circ['id']] - sid = None - if circ['id'] in circuit_ids_and_sids: - sid = circuit_ids_and_sids[circ['id']] + if _circuit['id'] in circuit_ids_and_sids: + sid = circuit_ids_and_sids[_circuit['id']] elif 'sid' in details: if len(circuits) > 1: - if port_circuit != circ: + if port_circuit != _circuit: # if this is not the physical port circuit # related to this port, then we don't want to # assign the SID to this circuit, so skip. @@ -1541,26 +1569,48 @@ def transform_ims_data(data): if sid is None: continue - circ['sid'] = sid - - sid_info = { - 'circuit_id': circ['id'], - 'sid': sid, - 'status': circ['original_status'], - 'monitored': circ['monitored'], - 'name': circ['name'], - 'speed': circ['calculated-speed'], - 'service_type': circ['service_type'], - 'project': circ['project'], - 'customer': circ['customer'], - 'equipment': circ['equipment'], - 'port': circ['port'], - 'geant_equipment': circ['equipment'] in geant_nodes - } - if sid_info not in sid_services[sid]: - sid_services[sid].append(sid_info) + _circuit['sid'] = sid + + key = f"{details['equipment_name']}:{details['interface_name']}" + interface_services.setdefault(key, []).extend(circuits) + # end of build interface_services + + _build_interface_services() + + def _add_to_sid_services(_circuit): + if 'sid' in _circuit: + sid = _circuit['sid'] + sid_info = { + 'circuit_id': _circuit['id'], + 'sid': sid, + 'status': _circuit['original_status'], + 'monitored': _circuit['monitored'], + 'name': _circuit['name'], + 'speed': _circuit['calculated-speed'], + 'service_type': _circuit['service_type'], + 'project': _circuit['project'], + 'customer': _circuit['customer'], + 'equipment': _circuit['equipment'], + 'port': _circuit['port'], + 'geant_equipment': _circuit['equipment'] in geant_nodes + } + if sid_info not in sid_services.setdefault(sid, []): + sid_services[sid].append(sid_info) + + def _add_to_services_by_type(_circuit): + type_key = ims_sorted_service_type_key(_circuit['service_type']) + services_by_type.setdefault(type_key, {})[_circuit['id']] = _circuit + + def _add_to_node_pair_services(_circuit): + if _circuit['other_end_equipment']: + node_pair_key = f"{_circuit['equipment']}/{_circuit['other_end_equipment']}" + node_pair_services.setdefault(node_pair_key, {})[_circuit['id']] = _circuit - interface_services[k].extend(circuits) + for circuits in interface_services.values(): + for circuit in circuits: + _add_to_sid_services(circuit) + _add_to_services_by_type(circuit) + _add_to_node_pair_services(circuit) return { 'hierarchy': hierarchy, @@ -1569,10 +1619,10 @@ def transform_ims_data(data): 'node_pair_services': node_pair_services, 'sid_services': sid_services, 'pop_nodes': pop_nodes, - 'locations': data['locations'], - 'site_locations': data['site_locations'], - 'lg_routers': data['lg_routers'], - 'circuit_ids_to_monitor': data['circuit_ids_to_monitor'], + 'locations': data['locations'], # unchanged + 'site_locations': data['site_locations'], # unchanged and unused + 'lg_routers': data['lg_routers'], # unchanged and unused + 'circuit_ids_to_monitor': data['circuit_ids_to_monitor'], # unchanged } diff --git a/setup.py b/setup.py index 05bd083f214494897a9d14e41af5d67c0719116c..06743d87ca4e1857e235e8ec7dd2cfa3cb865cc6 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup( name='inventory-provider', - version="0.120", + version="0.121", author='GEANT', author_email='swd@geant.org', description='Dashboard inventory provider', diff --git a/test/test_worker.py b/test/test_worker.py index afa1508a839621b9608fd6d385dbdc7f37b0943a..dcc32853304b2b88fefa0f7a7c47396fdd1173ff 100644 --- a/test/test_worker.py +++ b/test/test_worker.py @@ -211,12 +211,16 @@ def test_transform_ims_data(): customer_contacts = { "cu_1": ["customer_1@a.org"], - "cu_1_1": ["customer_1_1@a.org"] + "cu_1_1": ["customer_1_1@a.org"], + "cu_sub_1": ["customer_sub_1@a.org"], + "cu_sub_2": ["customer_sub_2@a.org"] } planned_work_contacts = { "cu_1": ["customer_1_PW@a.org"], - "cu_1_1": ["customer_1_1_PW@a.org"] + "cu_1_1": ["customer_1_1_PW@a.org"], + "cu_sub_1": ["customer_sub_1_PW@a.org"], + "cu_sub_2": ["customer_sub_2_PW@a.org"] } port_id_details = { @@ -315,7 +319,7 @@ def test_transform_ims_data(): "project": "customer_1", "carrier-circuits": ["carrier_id_1"], "sub-circuits": ["sub_circuit_1"], - "customerid": "cu_1", + "customerid": "cu_1" }, "carrier_id_1": { "id": "carrier_id_1", @@ -363,7 +367,7 @@ def test_transform_ims_data(): "project": "customer_1", "carrier-circuits": ["circ_id_1"], "sub-circuits": ["sub_circuit_2"], - "customerid": "cu_1", + "customerid": "cu_Sub_1", }, "sub_circuit_2": { "id": "sub_circuit_2", @@ -375,7 +379,7 @@ def test_transform_ims_data(): "project": "customer_1", "carrier-circuits": ["sub_circuit_1"], "sub-circuits": [], - "customerid": "cu_1", + "customerid": "cu_sub_2", }, 702203: { "id": 702203, @@ -458,12 +462,16 @@ def test_transform_ims_data(): "CAM01-MTC6-3:1-A-1-S1-1", "CAM01-MTC6-3:1-A-1-L1-1" ] + assert hierarchy["circ_id_1"]["contacts"] == ["customer_1@a.org", "customer_1_1@a.org"] + for v in ifs.values(): assert len(v) == 1 assert len(v[0]["related-services"]) == 1 assert v[0]["related-services"][0]["id"] == "sub_circuit_2" assert len(v[0]["fibre-routes"]) == 1 assert v[0]["fibre-routes"][0]["id"] == "carrier_id_3" + assert v[0]["contacts"] == ["customer_sub_2@a.org"] + assert v[0]["planned_work_contacts"] == ["customer_sub_2_PW@a.org"] nps = res["node_pair_services"] assert list(nps.keys()) == ["eq_a/eq_b", "eq_b/eq_a"]