From 58926cce98f4c590a98e042c94c7ea0ddeff4672 Mon Sep 17 00:00:00 2001 From: dhebeha Date: Fri, 21 Aug 2020 12:57:41 +0530 Subject: [PATCH] Add NSSI candidate -Add generic flow for NxI candidate -Add default attributes Issue-ID: OPTFRA-801 Signed-off-by: dhebeha Change-Id: I869ebcd35d4c2436676868b26006ca991b34e538 --- conductor/conductor/controller/translator.py | 29 +-- .../data/plugins/inventory_provider/aai.py | 271 +++++++++------------ .../inventory_provider/candidates/candidate.py | 5 +- .../inventory_provider/candidates/nxi_candidate.py | 28 +++ .../plugins/inventory_provider/utils/aai_utils.py | 81 ++++++ .../inventory_provider/first_level_filter.json | 5 + .../plugins/inventory_provider/nssi_candidate.json | 52 ++-- .../inventory_provider/nssi_demand_list.json | 17 ++ .../plugins/inventory_provider/nssi_response.json | 3 +- .../plugins/inventory_provider/slice_profile.json | 19 ++ .../slice_profile_converted.json | 18 ++ .../data/plugins/inventory_provider/test_aai.py | 66 +++-- .../plugins/inventory_provider/test_aai_utils.py | 95 ++++++++ .../solver/optimizer/constraints/test_threshold.py | 2 +- conductor/tox.ini | 4 +- 15 files changed, 465 insertions(+), 230 deletions(-) create mode 100644 conductor/conductor/data/plugins/inventory_provider/candidates/nxi_candidate.py create mode 100644 conductor/conductor/data/plugins/inventory_provider/utils/aai_utils.py create mode 100644 conductor/conductor/tests/unit/data/plugins/inventory_provider/first_level_filter.json create mode 100644 conductor/conductor/tests/unit/data/plugins/inventory_provider/nssi_demand_list.json create mode 100644 conductor/conductor/tests/unit/data/plugins/inventory_provider/slice_profile.json create mode 100644 conductor/conductor/tests/unit/data/plugins/inventory_provider/slice_profile_converted.json create mode 100644 conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai_utils.py diff --git a/conductor/conductor/controller/translator.py b/conductor/conductor/controller/translator.py index c0d7e29..83c71ed 100644 --- a/conductor/conductor/controller/translator.py +++ b/conductor/conductor/controller/translator.py @@ -26,14 +26,14 @@ import uuid import six import yaml -from conductor import __file__ as conductor_root -from conductor import messaging -from conductor import service -from conductor.common import threshold +from conductor import __file__ as conductor_root from conductor.common.music import messaging as music_messaging -from conductor.data.plugins.triage_translator.triage_translator_data import TraigeTranslatorData +from conductor.common import threshold from conductor.data.plugins.triage_translator.triage_translator import TraigeTranslator +from conductor.data.plugins.triage_translator.triage_translator_data import TraigeTranslatorData +from conductor import messaging +from conductor import service from oslo_config import cfg from oslo_log import log @@ -48,8 +48,8 @@ INVENTORY_TYPES = ['cloud', 'service', 'transport', 'vfmodule', 'nssi'] DEFAULT_INVENTORY_PROVIDER = INVENTORY_PROVIDERS[0] CANDIDATE_KEYS = ['candidate_id', 'cost', 'inventory_type', 'location_id', 'location_type'] -DEMAND_KEYS = ['filtering_attributes', 'passthrough_attributes', 'candidates', 'complex', 'conflict_identifier', - 'customer_id', 'default_cost', 'excluded_candidates', +DEMAND_KEYS = ['filtering_attributes', 'passthrough_attributes', 'default_attributes', 'candidates', 'complex', + 'conflict_identifier', 'customer_id', 'default_cost', 'excluded_candidates', 'existing_placement', 'flavor', 'inventory_provider', 'inventory_type', 'port_key', 'region', 'required_candidates', 'service_id', 'service_resource_id', 'service_subscription', @@ -142,7 +142,7 @@ class Translator(object): self._translation = None self._valid = False self._ok = False - self.triageTranslatorData= TraigeTranslatorData() + self.triageTranslatorData = TraigeTranslatorData() self.triageTranslator = TraigeTranslator() # Set up the RPC service(s) we want to talk to. self.data_service = self.setup_rpc(self.conf, "data") @@ -504,7 +504,7 @@ class Translator(object): "demands": { name: requirements, }, - "plan_info":{ + "plan_info": { "plan_id": self._plan_id, "plan_name": self._plan_name }, @@ -517,10 +517,8 @@ class Translator(object): for requirement in requirements: required_candidates = requirement.get("required_candidates") excluded_candidates = requirement.get("excluded_candidates") - if (required_candidates and - excluded_candidates and - set(map(lambda entry: entry['candidate_id'], - required_candidates)) + if (required_candidates and excluded_candidates and set(map(lambda entry: entry['candidate_id'], + required_candidates)) & set(map(lambda entry: entry['candidate_id'], excluded_candidates))): raise TranslatorException( @@ -939,9 +937,7 @@ class Translator(object): if not self.valid: raise TranslatorException("Can't translate an invalid template.") - request_type = self._parameters.get("request_type") \ - or self._parameters.get("REQUEST_TYPE") \ - or "" + request_type = self._parameters.get("request_type") or self._parameters.get("REQUEST_TYPE") or "" self._translation = { "conductor_solver": { @@ -952,7 +948,6 @@ class Translator(object): "demands": self.parse_demands(self._demands), "objective": self.parse_optimization(self._optmization), "constraints": self.parse_constraints(self._constraints), - "objective": self.parse_optimization(self._optmization), "reservations": self.parse_reservations(self._reservations), } } diff --git a/conductor/conductor/data/plugins/inventory_provider/aai.py b/conductor/conductor/data/plugins/inventory_provider/aai.py index 821219c..9defe94 100644 --- a/conductor/conductor/data/plugins/inventory_provider/aai.py +++ b/conductor/conductor/data/plugins/inventory_provider/aai.py @@ -27,17 +27,21 @@ import uuid from oslo_config import cfg from oslo_log import log + from conductor.common import rest from conductor.data.plugins import constants from conductor.data.plugins.inventory_provider import base -from conductor.data.plugins.inventory_provider import hpa_utils from conductor.data.plugins.inventory_provider.candidates.candidate import Candidate from conductor.data.plugins.inventory_provider.candidates.cloud_candidate import Cloud +from conductor.data.plugins.inventory_provider.candidates.nxi_candidate import NxI from conductor.data.plugins.inventory_provider.candidates.service_candidate import Service from conductor.data.plugins.inventory_provider.candidates.transport_candidate import Transport from conductor.data.plugins.inventory_provider.candidates.vfmodule_candidate import VfModule +from conductor.data.plugins.inventory_provider import hpa_utils +from conductor.data.plugins.inventory_provider.utils import aai_utils from conductor.data.plugins.triage_translator.triage_translator import TraigeTranslator -from conductor.i18n import _LE, _LI +from conductor.i18n import _LE +from conductor.i18n import _LI LOG = log.getLogger(__name__) @@ -81,7 +85,7 @@ AAI_OPTS = [ help='Certificate Authority Bundle file in pem format. ' 'Must contain the appropriate trust chain for the ' 'Certificate file.'), - #TODO(larry): follow-up with ONAP people on this (AA&I basic auth username and password?) + # TODO(larry): follow-up with ONAP people on this (AA&I basic auth username and password?) cfg.StrOpt('username', default='', help='Username for AAI.'), @@ -252,8 +256,7 @@ class AAI(base.InventoryProviderBase): if len(physical_location_list) > 0: physical_location_id = physical_location_list[0].get('d_value') - if not (cloud_region_version and - cloud_region_id): + if not (cloud_region_version and cloud_region_id): continue rel_link_data_list = \ self._get_aai_rel_link_data( @@ -291,8 +294,7 @@ class AAI(base.InventoryProviderBase): country = complex_info.get('country') complex_name = complex_info.get('complex-name') - if not (latitude and longitude and city and country - and complex_name): + if not (latitude and longitude and city and country and complex_name): keys = ('latitude', 'longitude', 'city', 'country', 'complex_name') missing_keys = \ @@ -451,12 +453,11 @@ class AAI(base.InventoryProviderBase): return regions def _get_flavors(self, cloud_owner, cloud_region_id): + '''Fetch all flavors of a given cloud regions specified using {cloud-owner}/{cloud-region-id} composite key + + :return flavors_info json object which list of flavor nodes and its children - HPACapabilities: ''' - Fetch all flavors of a given cloud regions specified using - {cloud-owner}/{cloud-region-id} composite key - :return flavors_info json object which list of flavor nodes and - its children - HPACapabilities: - ''' + LOG.debug("Fetch all flavors and its child nodes HPACapabilities") flavor_path = constants.FLAVORS_URI % (cloud_owner, cloud_region_id) path = self._aai_versioned_path(flavor_path) @@ -476,9 +477,8 @@ class AAI(base.InventoryProviderBase): # Remove extraneous flavor information return flavors_info else: - LOG.error(_LE("Received Error while fetching flavors from" \ - "Cloud-region {}/{}").format(cloud_owner, - cloud_region_id)) + LOG.error(_LE("Received Error while fetching flavors from Cloud-region {}/{}").format(cloud_owner, + cloud_region_id)) return def _get_aai_path_from_link(self, link): @@ -507,7 +507,6 @@ class AAI(base.InventoryProviderBase): for vnf in generic_vnf: related_to = "service-instance" search_key = "customer.global-customer-id" - match_key = "customer.global-customer-id" rl_data_list = self._get_aai_rel_link_data( data=vnf, related_to=related_to, @@ -714,9 +713,7 @@ class AAI(base.InventoryProviderBase): Used by resolve_demands """ - if restricted_value and \ - restricted_value is not '' and \ - candidate[attribute_name] != restricted_value: + if restricted_value and restricted_value != '' and candidate[attribute_name] != restricted_value: LOG.info(_LI("Demand: {} " "Discarded {} candidate as " "it doesn't match the " @@ -745,10 +742,9 @@ class AAI(base.InventoryProviderBase): for attribute_key, attribute_values in template_attributes.items(): - if attribute_key and (attribute_key == 'service-type' or - attribute_key == 'equipment-role' or - attribute_key == 'model-invariant-id' or - attribute_key == 'model-version-id'): + if attribute_key and \ + (attribute_key == 'service-type' or attribute_key == 'equipment-role' + or attribute_key == 'model-invariant-id' or attribute_key == 'model-version-id'): continue match_type = 'any' @@ -761,12 +757,11 @@ class AAI(base.InventoryProviderBase): if match_type == 'any': if attribute_key not in inventory_attributes or \ - (len(attribute_values) > 0 and - inventory_attributes[attribute_key] not in attribute_values): + (len(attribute_values) > 0 and inventory_attributes[attribute_key] not in attribute_values): return False elif match_type == 'not': # drop the candidate when - # 1). field exists in AAI and 2). value is not null or empty 3). value is one of those in the 'not' list + # 1)field exists in AAI and 2)value is not null or empty 3)value is one of those in the 'not' list # Remember, this means if the property is not returned at all from AAI, that still can be a candidate. if attribute_key in inventory_attributes and \ inventory_attributes[attribute_key] and \ @@ -785,7 +780,8 @@ class AAI(base.InventoryProviderBase): body = response.json() return body.get("generic-vnf", []) - def resolve_v_server_for_candidate(self, candidate_id, location_id, vs_link, add_interfaces, demand_name, triage_translator_data): + def resolve_v_server_for_candidate(self, candidate_id, location_id, vs_link, add_interfaces, demand_name, + triage_translator_data): if not vs_link: LOG.error(_LE("{} VSERVER link information not " "available from A&AI").format(demand_name)) @@ -895,8 +891,8 @@ class AAI(base.InventoryProviderBase): vs_link_list.append(rl_data_list[i].get('link')) return vs_link_list - def resolve_complex_info_link_for_v_server(self, candidate_id, v_server, cloud_owner, cloud_region_id, service_type, - demand_name, triage_translator_data): + def resolve_complex_info_link_for_v_server(self, candidate_id, v_server, cloud_owner, cloud_region_id, + service_type, demand_name, triage_translator_data): related_to = "pserver" rl_data_list = self._get_aai_rel_link_data( data=v_server, @@ -1020,8 +1016,8 @@ class AAI(base.InventoryProviderBase): if "cloud-region-version" in region: return self._get_version_from_string(region["cloud-region-version"]) - def resolve_global_customer_id_for_vnf(self, candidate_id, location_id, vnf, customer_id, service_type, demand_name, - triage_translator_data): + def resolve_global_customer_id_for_vnf(self, candidate_id, location_id, vnf, customer_id, service_type, + demand_name, triage_translator_data): related_to = "service-instance" search_key = "customer.global-customer-id" match_key = "customer.global-customer-id" @@ -1042,8 +1038,8 @@ class AAI(base.InventoryProviderBase): return None return rl_data_list[0] - def resolve_service_instance_id_for_vnf(self, candidate_id, location_id, vnf, customer_id, service_type, demand_name, - triage_translator_data): + def resolve_service_instance_id_for_vnf(self, candidate_id, location_id, vnf, customer_id, service_type, + demand_name, triage_translator_data): related_to = "service-instance" search_key = "service-instance.service-instance-id" match_key = "customer.global-customer-id" @@ -1135,10 +1131,10 @@ class AAI(base.InventoryProviderBase): candidate_uniqueness = requirement.get('unique', 'true') filtering_attributes = requirement.get('filtering_attributes') passthrough_attributes = requirement.get('passthrough_attributes') - # TODO: may need to support multiple service_type and customer_id in the futrue - - # TODO: make it consistent for dash and underscore + default_attributes = requirement.get('default_attributes') + # TODO(XYZ): may need to support multiple service_type and customer_id in the futrue + # TODO(XYZ): make it consistent for dash and underscore if filtering_attributes: # catch equipment-role and service-type from template equipment_role = filtering_attributes.get('equipment-role') @@ -1194,17 +1190,18 @@ class AAI(base.InventoryProviderBase): LOG.debug("Region information is not available in cache") for region_id, region in regions.items(): # Pick only candidates from the restricted_region - info = Candidate.build_candidate_info('aai', inventory_type, self.conf.data.cloud_candidate_cost, + info = Candidate.build_candidate_info('aai', inventory_type, + self.conf.data.cloud_candidate_cost, candidate_uniqueness, region_id, service_resource_id) cloud = self.resolve_cloud_for_region(region, region_id) complex_info = self.build_complex_dict(region['complex'], inventory_type) flavors = self.resolve_flavors_for_region(region['flavors']) other = dict() other['vim-id'] = self.get_vim_id(cloud['cloud_owner'], cloud['location_id']) - other['sriov_automation'] = 'true' if self.check_sriov_automation(cloud['cloud_region_version'], - name, - info['candidate_id']) \ - else 'false' + if self.check_sriov_automation(cloud['cloud_region_version'], name, info['candidate_id']): + other['sriov_automation'] = 'true' + else: + other['sriov_automation'] = 'false' cloud_candidate = Cloud(info=info, cloud_region=cloud, complex=complex_info, flavors=flavors, additional_fields=other) candidate = cloud_candidate.convert_nested_dict_to_dict() @@ -1218,11 +1215,13 @@ class AAI(base.InventoryProviderBase): cloud_region_attr['physical-location-id'] = region['physical_location_id'] if filtering_attributes and (not self.match_inventory_attributes(filtering_attributes, - cloud_region_attr, candidate['candidate_id'])): + cloud_region_attr, + candidate['candidate_id'])): self.triage_translator.collectDroppedCandiate(candidate['candidate_id'], candidate['location_id'], name, triage_translator_data, - reason='attributes and match invetory attributes') + reason='attributes and match invetory ' + 'attributes') continue if conflict_identifier: @@ -1318,7 +1317,7 @@ class AAI(base.InventoryProviderBase): other = dict() other['vim-id'] = self.get_vim_id(cloud['cloud_owner'], cloud['location_id']) other['sriov_automation'] = 'true' if self.check_sriov_automation( - cloud['cloud_region_version'], name, info['candidate_id']) else 'false' + cloud['cloud_region_version'], name, info['candidate_id']) else 'false' # Second level query to get the pserver from vserver complex_list = list() @@ -1327,8 +1326,9 @@ class AAI(base.InventoryProviderBase): triage_translator_data, service_type): complex_list.append(complex_link[1]) - complex_info = self.build_complex_info_for_candidate(info['candidate_id'], cloud['location_id'], - vnf, complex_list, service_type, name, + complex_info = self.build_complex_info_for_candidate(info['candidate_id'], + cloud['location_id'], vnf, + complex_list, service_type, name, triage_translator_data) if "complex_name" not in complex_info: continue @@ -1410,7 +1410,8 @@ class AAI(base.InventoryProviderBase): vnf_dict[vnf_id] = vnf # INFO - info = Candidate.build_candidate_info('aai', inventory_type, self.conf.data.service_candidate_cost, + info = Candidate.build_candidate_info('aai', inventory_type, + self.conf.data.service_candidate_cost, candidate_uniqueness, "", service_resource_id) # VLAN INFO vlan_info = self.build_vlan_info(vlan_key, port_key) @@ -1437,7 +1438,8 @@ class AAI(base.InventoryProviderBase): else: # vserver is for a different customer self.triage_translator.collectDroppedCandiate('', '', name, triage_translator_data, - reason="candidate is for a different customer") + reason="candidate is for a different" + " customer") continue vf_modules_list = self.resolve_vf_modules_for_generic_vnf('', '', vnf, name, @@ -1465,16 +1467,19 @@ class AAI(base.InventoryProviderBase): vserver_info = dict() vserver_info['vservers'] = list() complex_list = list() - for v_server, complex_link in self.resolve_v_server_and_complex_link_for_vnf(info['candidate_id'], - cloud, vnf, name, - triage_translator_data, - service_type): + for v_server, complex_link in \ + self.resolve_v_server_and_complex_link_for_vnf(info['candidate_id'], + cloud, vnf, name, + triage_translator_data, + service_type): complex_list.append(complex_link) candidate_vserver = dict() candidate_vserver['vserver-id'] = v_server.get('vserver-id') candidate_vserver['vserver-name'] = v_server.get('vserver-name') - l_interfaces = self.get_l_interfaces_from_vserver(info['candidate_id'], cloud['location_id'], - v_server, name, triage_translator_data) + l_interfaces = self.get_l_interfaces_from_vserver(info['candidate_id'], + cloud['location_id'], + v_server, name, + triage_translator_data) if l_interfaces: candidate_vserver['l-interfaces'] = l_interfaces else: @@ -1483,8 +1488,8 @@ class AAI(base.InventoryProviderBase): # COMPLEX complex_info = self.build_complex_info_for_candidate(info['candidate_id'], - cloud['location_id'], vnf, complex_list, - service_type, name, + cloud['location_id'], vnf, + complex_list, service_type, name, triage_translator_data) if complex_info.get("complex_name") is None: continue @@ -1495,7 +1500,7 @@ class AAI(base.InventoryProviderBase): additional_fields=other, vlan=vlan_info) candidate = vf_module_candidate.convert_nested_dict_to_dict() - ##add vf-module parameters for filtering + # add vf-module parameters for filtering vnf_vf_module_inventory = copy.deepcopy(vnf) vnf_vf_module_inventory.update(vf_module) # add specifal parameters for comparsion @@ -1576,7 +1581,8 @@ class AAI(base.InventoryProviderBase): candidate_uniqueness, vnf_service_instance_id, service_resource_id) else: - self.triage_translator.collectDroppedCandiate('', other['location_id'], name, triage_translator_data, + self.triage_translator.collectDroppedCandiate('', other['location_id'], name, + triage_translator_data, reason="service-instance-id error ") continue @@ -1600,8 +1606,8 @@ class AAI(base.InventoryProviderBase): ) if len(rel_link_data_list) > 1: - self.triage_translator.collectDroppedCandiate(info['candidate_id'], other['location_id'], name, - triage_translator_data, + self.triage_translator.collectDroppedCandiate(info['candidate_id'], other['location_id'], + name, triage_translator_data, reason="rel_link_data_list error") continue @@ -1613,9 +1619,10 @@ class AAI(base.InventoryProviderBase): LOG.debug("{} complex information not " "available from A&AI - {}". format(name, complex_link)) - self.triage_translator.collectDroppedCandiate(info['candidate_id'], other['location_id'], name, - triage_translator_data, - reason="complex information not available from A&AI") + self.triage_translator.collectDroppedCandiate(info['candidate_id'], other['location_id'], + name, triage_translator_data, + reason="complex information not available " + "from A&AI") continue else: complex_info = self._get_complex( @@ -1626,13 +1633,16 @@ class AAI(base.InventoryProviderBase): LOG.debug("{} complex information not " "available from A&AI - {}". format(name, complex_link)) - self.triage_translator.collectDroppedCandiate(info['candidate_id'], other['location_id'], name, + self.triage_translator.collectDroppedCandiate(info['candidate_id'], + other['location_id'], name, triage_translator_data, - reason="complex information not available from A&AI") + reason="complex information not " + "available from A&AI") continue # move ahead with the next vnf complex_info = self.build_complex_dict(complex_info, inventory_type) - transport_candidate = Transport(info=info, zone=zone_info, complex=complex_info, additional_fiels=other) + transport_candidate = Transport(info=info, zone=zone_info, complex=complex_info, + additional_fiels=other) candidate = transport_candidate.convert_nested_dict_to_dict() self.add_passthrough_attributes(candidate, passthrough_attributes, name) @@ -1641,9 +1651,12 @@ class AAI(base.InventoryProviderBase): elif inventory_type == 'nssi': if filtering_attributes and model_invariant_id: - resolved_demands[name].extend(self.get_nssi_candidates(filtering_attributes, - model_invariant_id, model_version_id, - service_role, candidate_uniqueness)) + second_level_match = aai_utils.get_first_level_and_second_level_filter(filtering_attributes, + "service_instance") + aai_response = self.get_nxi_candidates(filtering_attributes) + resolved_demands[name].extend(self.filter_nxi_candidates(aai_response, second_level_match, + default_attributes, + candidate_uniqueness, inventory_type)) else: LOG.error("Unknown inventory_type " @@ -1659,7 +1672,8 @@ class AAI(base.InventoryProviderBase): if inv_type == "cloud": for valid_key in valid_keys: if '-' in valid_key: - complex_info[valid_key.replace('-', '_')] = aai_complex.get('complex_id') if valid_key == 'physical-location-id' else \ + complex_info[valid_key.replace('-', '_')] = aai_complex.get('complex_id') \ + if valid_key == 'physical-location-id' else \ aai_complex.get(valid_key.replace('-', '_')) else: complex_info[valid_key] = aai_complex.get(valid_key) @@ -1783,7 +1797,8 @@ class AAI(base.InventoryProviderBase): LOG.error("Zone information not available from A&AI for transport candidates") self.triage_translator.collectDroppedCandiate(candidate_id, location_id, name, triage_translator_data, - reason="Zone information not available from A&AI for transport candidates") + reason="Zone information not available from A&AI for " + "transport candidates") return None zone_aai_path = self._get_aai_path_from_link(zone_link) response = self._request('get', path=zone_aai_path, data=None) @@ -1795,7 +1810,8 @@ class AAI(base.InventoryProviderBase): body = response.json() return body - def match_region(self, candidate, restricted_region_id, restricted_complex_id, demand_name, triage_translator_data): + def match_region(self, candidate, restricted_region_id, restricted_complex_id, demand_name, + triage_translator_data): if self.match_candidate_attribute( candidate, "location_id", @@ -1852,92 +1868,41 @@ class AAI(base.InventoryProviderBase): directives = None return directives - def get_nssi_candidates(self, filtering_attributes, model_invariant_id, model_version_id, service_role, - candidate_uniqueness): - raw_path = ('nodes/service-instances' + - '?model-invariant-id={}'.format(model_invariant_id) + - ('&model-version-id={}'.format(model_version_id) if model_version_id else '') + - ('&service-role={}'.format(service_role) if service_role else '') + - '&depth=2') - + def get_nxi_candidates(self, filtering_attributes): + raw_path = 'nodes/service-instances' + aai_utils.add_query_params_and_depth(filtering_attributes, "2") path = self._aai_versioned_path(raw_path) aai_response = self._request('get', path, data=None) if aai_response is None or aai_response.status_code != 200: return None + if aai_response.json(): + return aai_response.json() - return self.filter_nssi_candidates(aai_response.json(), filtering_attributes, candidate_uniqueness) - - def filter_nssi_candidates(self, response_body, filtering_attributes, candidate_uniqueness): - + def filter_nxi_candidates(self, response_body, filtering_attributes, default_attributes, candidate_uniqueness, + type): candidates = list() - if filtering_attributes and response_body is not None: - nssi_instances = response_body.get("service-instance", []) - - for nssi_instance in nssi_instances: - inventory_attributes = dict() - inventory_attributes["orchestration-status"] = nssi_instance.get('orchestration-status') - inventory_attributes["service-role"] = nssi_instance.get('service-role') - - if self.match_inventory_attributes(filtering_attributes, inventory_attributes, - nssi_instance.get('service-instance-id')): - - nsi_link = self._get_aai_rel_link(nssi_instance, 'service-instance') - - nsi_info = self.get_nsi_info(nsi_link) - - slice_profiles = nssi_instance.get('slice-profiles').get('slice-profile') - slice_profile = min(slice_profiles, key=lambda x: x['latency']) - - candidate = dict() - candidate['candidate_id'] = nssi_instance.get('service-instance-id') - candidate['instance_name'] = nssi_instance.get('service-instance-name') - candidate['cost'] = self.conf.data.nssi_candidate_cost - candidate['candidate_type'] = 'nssi' - candidate['inventory_type'] = 'nssi' - candidate['inventory_provider'] = 'aai' - candidate['domain'] = nssi_instance.get('environment-context') - candidate['latency'] = slice_profile.get('latency') - candidate['max_number_of_ues'] = slice_profile.get('max-number-of-UEs') - candidate['coverage_area_ta_list'] = slice_profile.get('coverage-area-TA-list') - candidate['ue_mobility_level'] = slice_profile.get('ue-mobility-level') - candidate['resource_sharing_level'] = slice_profile.get('resource-sharing-level') - candidate['exp_data_rate_ul'] = slice_profile.get('exp-data-rate-UL') - candidate['exp_data_rate_dl'] = slice_profile.get('exp-data-rate-DL') - candidate['area_traffic_cap_ul'] = slice_profile.get('area-traffic-cap-UL') - candidate['area_traffic_cap_dl'] = slice_profile.get('area-traffic-cap-DL') - candidate['activity_factor'] = slice_profile.get('activity-factor') - candidate['e2e_latency'] = slice_profile.get('e2e-latency') - candidate['jitter'] = slice_profile.get('jitter') - candidate['survival_time'] = slice_profile.get('survival-time') - candidate['exp_data_rate'] = slice_profile.get('exp-data-rate') - candidate['payload_size'] = slice_profile.get('payload-size') - candidate['traffic_density'] = slice_profile.get('traffic-density') - candidate['conn_density'] = slice_profile.get('conn-density') - candidate['reliability'] = slice_profile.get('reliability') - candidate['service_area_dimension'] = slice_profile.get('service-area-dimension') - candidate['cs_availability'] = slice_profile.get('cs-availability') - candidate['uniqueness'] = candidate_uniqueness - if nsi_info: - candidate['nsi_name'] = nsi_info.get('instance_name') - candidate['nsi_id'] = nsi_info.get('instance_id') - candidate['nsi_model_version_id'] = nsi_info.get('model_version_id') - candidate['nsi_model_invariant_id'] = nsi_info.get('model_invariant_id') - candidates.append(candidate) - + if response_body is not None: + nxi_instances = response_body.get("service-instance", []) + + for nxi_instance in nxi_instances: + inventory_attributes = aai_utils.get_inv_values_for_second_level_filter(filtering_attributes, + nxi_instance) + nxi_info = aai_utils.get_instance_info(nxi_instance) + if not filtering_attributes or \ + self.match_inventory_attributes(filtering_attributes, inventory_attributes, + nxi_instance.get('service-instance-id')): + if type == 'nssi': + profiles = nxi_instance.get('slice-profiles').get('slice-profile') + cost = self.conf.data.nssi_candidate_cost + elif type == 'nsi': + profiles = nxi_instance.get('service-profiles').get('service-profile') + cost = self.conf.data.nsi_candidate_cost + for profile in profiles: + profile_id = profile.get('profile-id') + info = Candidate.build_candidate_info('aai', type, cost, candidate_uniqueness, profile_id) + profile_info = aai_utils.convert_hyphen_to_under_score(profile) + nxi_candidate = NxI(instance_info=nxi_info, profile_info=profile_info, info=info, + default_fields=aai_utils.convert_hyphen_to_under_score(default_attributes)) + candidate = nxi_candidate.convert_nested_dict_to_dict() + candidates.append(candidate) return candidates - - def get_nsi_info(self, nsi_link): - nsi_info = dict() - if nsi_link: - nsi_link_path = self._get_aai_path_from_link(nsi_link) - path = self._aai_versioned_path(nsi_link_path) - nsi_response = self._request('get', path, data=None) - if nsi_response and nsi_response.status_code == 200: - nsi_response_body = nsi_response.json() - nsi_info['instance_id'] = nsi_response_body.get('service-instance-id') - nsi_info['instance_name'] = nsi_response_body.get('service-instance-name') - nsi_info['model_version_id'] = nsi_response_body.get('model-version-id') - nsi_info['model_invariant_id'] = nsi_response_body.get('model-invariant-id') - - return nsi_info diff --git a/conductor/conductor/data/plugins/inventory_provider/candidates/candidate.py b/conductor/conductor/data/plugins/inventory_provider/candidates/candidate.py index 7f241c3..2b2eb4b 100644 --- a/conductor/conductor/data/plugins/inventory_provider/candidates/candidate.py +++ b/conductor/conductor/data/plugins/inventory_provider/candidates/candidate.py @@ -17,16 +17,17 @@ # ------------------------------------------------------------------------- # -class Candidate: +class Candidate(object): def __init__(self, info): self.candidate_id = info.get('candidate_id') if info.get('candidate_type'): self.candidate_type = info.get('candidate_type') + if info.get('service_resource_id'): + self.service_resource_id = info.get('service_resource_id') self.inventory_provider = info.get('inventory_provider') self.inventory_type = info.get('inventory_type') self.uniqueness = info.get('uniqueness') self.cost = info.get('cost') - self.service_resource_id = info.get('service_resource_id', None) def convert_nested_dict_to_dict(self): candidate = dict() diff --git a/conductor/conductor/data/plugins/inventory_provider/candidates/nxi_candidate.py b/conductor/conductor/data/plugins/inventory_provider/candidates/nxi_candidate.py new file mode 100644 index 0000000..5eb75a1 --- /dev/null +++ b/conductor/conductor/data/plugins/inventory_provider/candidates/nxi_candidate.py @@ -0,0 +1,28 @@ +# +# ------------------------------------------------------------------------- +# Copyright (C) 2020 Wipro Limited. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------- +# + +from conductor.data.plugins.inventory_provider.candidates.candidate import Candidate + + +class NxI(Candidate): + def __init__(self, **kwargs): + super().__init__(kwargs['info']) + self.nxi_info = kwargs['instance_info'] + self.profile_info = kwargs['profile_info'] + self.other = kwargs['default_fields'] diff --git a/conductor/conductor/data/plugins/inventory_provider/utils/aai_utils.py b/conductor/conductor/data/plugins/inventory_provider/utils/aai_utils.py new file mode 100644 index 0000000..2fbaabd --- /dev/null +++ b/conductor/conductor/data/plugins/inventory_provider/utils/aai_utils.py @@ -0,0 +1,81 @@ +# +# ------------------------------------------------------------------------- +# Copyright (C) 2020 Wipro Limited. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------- +# + +QUERY_PARAMS = {'service_instance': ["service-instance-id", "service-instance-name", "environment-context", + "workload-context", "model-invariant-id", "model-version-id", "widget-model-id", + "widget-model-version", "service-instance-location-id", "orchestration-status"] + } + + +def convert_hyphen_to_under_score(hyphened_dict): + converted_dict = dict() + if hyphened_dict: + for key in hyphened_dict: + if '-' in key: + converted_dict[key.replace('-', '_')] = hyphened_dict[key] + else: + converted_dict[key] = hyphened_dict[key] + if 'resource_version' in converted_dict: + converted_dict.pop('resource_version') + return converted_dict + + +def add_query_params(filtering_attributes): + if not filtering_attributes: + return '' + url = '?' + for key, value in filtering_attributes.items(): + url = f'{url}{key}={value}&' + return url + + +def add_query_params_and_depth(filtering_attributes, depth): + url_with_query_params = add_query_params(filtering_attributes) + if url_with_query_params: + return f"{url_with_query_params}depth={depth}" + else: + return f"?depth={depth}" + + +def get_first_level_and_second_level_filter(filtering_attributes, aai_node): + second_level_filters = dict() + valid_query_params = QUERY_PARAMS.get(aai_node) + for key in list(filtering_attributes): + if key not in valid_query_params: + second_level_filters[key] = filtering_attributes[key] + del filtering_attributes[key] + return second_level_filters + + +def get_inv_values_for_second_level_filter(second_level_filters, nssi_instance): + if not second_level_filters: + return None + inventory_attributes = dict() + for key in list(second_level_filters): + inventory_attributes[key] = nssi_instance.get(key) + return inventory_attributes + + +def get_instance_info(nxi_instance): + nxi_dict = dict() + nxi_dict['instance_id'] = nxi_instance.get('service-instance-id') + nxi_dict['instance_name'] = nxi_instance.get('service-instance-name') + if nxi_instance.get('service-function'): + nxi_dict['domain'] = nxi_instance.get('service-function') + return nxi_dict diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/first_level_filter.json b/conductor/conductor/tests/unit/data/plugins/inventory_provider/first_level_filter.json new file mode 100644 index 0000000..be1810c --- /dev/null +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/first_level_filter.json @@ -0,0 +1,5 @@ +{"orchestration-status": "active", +"model-invariant-id": "123644", +"model-version-id": "524846", +"environment-context": "shared" +} diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/nssi_candidate.json b/conductor/conductor/tests/unit/data/plugins/inventory_provider/nssi_candidate.json index 28a4f8f..b5a4a7d 100644 --- a/conductor/conductor/tests/unit/data/plugins/inventory_provider/nssi_candidate.json +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/nssi_candidate.json @@ -1,36 +1,30 @@ [ { - "exp_data_rate":0, - "conn_density":0, - "coverage_area_ta_list":"[{\"province\":\"??\",\"city\":\"???\",\"county\":\"???\",\"street\":\"?????\"}]", - "activity_factor":0, - "cs_availability":null, - "candidate_id":"1a636c4d-5e76-427e-bfd6-241a947224b0", - "area_traffic_cap_dl":null, - "latency":20, - "service_area_dimension":null, - "domain":"cn", - "e2e_latency":0, - "area_traffic_cap_ul":null, - "inventory_provider":"aai", - "exp_data_rate_ul":100, - "exp_data_rate_dl":100, - "max_number_of_ues":0, - "ue_mobility_level":"stationary", - "candidate_type":"nssi", - "traffic_density":0, - "payload_size":0, - "jitter":0, - "survival_time":0, - "resource_sharing_level":"0", + "candidate_id":"cdad9f49-4201-4e3a-aac1-b0f27902c299", "inventory_type":"nssi", - "reliability":null, + "uniqueness": "true", "cost":1.0, - "nsi_name": "nsi_test_0211", - "nsi_id": "4115d3c8-dd59-45d6-b09d-e756dee9b518", - "nsi_model_version_id": "8b664b11-6646-4776-9f59-5c3de46da2d6", - "nsi_model_invariant_id": "39b10fe6-efcc-40bc-8184-c38414b80771", + "inventory_provider": "aai", "instance_name": "nssi_test_0211", - "uniqueness": "true" + "instance_id": "1a636c4d-5e76-427e-bfd6-241a947224b0", + "domain": "cn", + "creation_cost": 1, + + "profile_id": "cdad9f49-4201-4e3a-aac1-b0f27902c299", + "latency": 20, + "max_number_of_UEs": 0, + "coverage_area_TA_list": "[{\"province\":\"??\",\"city\":\"???\",\"county\":\"???\",\"street\":\"?????\"}]", + "ue_mobility_level": "stationary", + "resource_sharing_level": "0", + "exp_data_rate_UL": 100, + "exp_data_rate_DL": 100, + "activity_factor": 0, + "e2e_latency": 0, + "jitter": 0, + "survival_time": 0, + "exp_data_rate": 0, + "payload_size": 0, + "traffic_density": 0, + "conn_density": 0 } ] diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/nssi_demand_list.json b/conductor/conductor/tests/unit/data/plugins/inventory_provider/nssi_demand_list.json new file mode 100644 index 0000000..15168be --- /dev/null +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/nssi_demand_list.json @@ -0,0 +1,17 @@ +{ + "embb_cn": [{ + "inventory_provider": "aai", + "inventory_type": "nssi", + "unique": "true", + "filtering_attributes": { + "model-version-id": "bfba363e-e39c-4bd9-a9d5-1371c28f4d22", + "model-invariant-id": "21d57d4b-52ad-4d3c-a798-248b5bb9124a", + "orchestration-status": "active", + "service-role": "nssi", + "environment-context": "shared" + }, + "default_attributes": { + "creation-cost": 1 + } + }] +} diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/nssi_response.json b/conductor/conductor/tests/unit/data/plugins/inventory_provider/nssi_response.json index e22ce39..286c8bf 100644 --- a/conductor/conductor/tests/unit/data/plugins/inventory_provider/nssi_response.json +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/nssi_response.json @@ -3,7 +3,8 @@ "service-instance-name": "nssi_test_0211", "service-type": "embb", "service-role": "nssi", - "environment-context": "cn", + "service-function": "cn", + "environment-context": "shared", "model-invariant-id": "21d57d4b-52ad-4d3c-a798-248b5bb9124a", "model-version-id": "bfba363e-e39c-4bd9-a9d5-1371c28f4d22", "resource-version": "1581418601616", diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/slice_profile.json b/conductor/conductor/tests/unit/data/plugins/inventory_provider/slice_profile.json new file mode 100644 index 0000000..4bbda28 --- /dev/null +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/slice_profile.json @@ -0,0 +1,19 @@ +{ + "profile-id": "cdad9f49-4201-4e3a-aac1-b0f27902c299", + "latency": 20, + "max-number-of-UEs": 0, + "coverage-area-TA-list": "[{\"province\":\"??\",\"city\":\"???\",\"county\":\"???\",\"street\":\"?????\"}]", + "ue-mobility-level": "stationary", + "resource-sharing-level": "0", + "exp-data-rate-UL": 100, + "exp-data-rate-DL": 100, + "activity-factor": 0, + "e2e-latency": 0, + "jitter": 0, + "survival-time": 0, + "exp-data-rate": 0, + "payload-size": 0, + "traffic-density": 0, + "conn-density": 0, + "resource-version": "1581418602494" +} diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/slice_profile_converted.json b/conductor/conductor/tests/unit/data/plugins/inventory_provider/slice_profile_converted.json new file mode 100644 index 0000000..f7581a2 --- /dev/null +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/slice_profile_converted.json @@ -0,0 +1,18 @@ +{ + "profile_id": "cdad9f49-4201-4e3a-aac1-b0f27902c299", + "latency": 20, + "max_number_of_UEs": 0, + "coverage_area_TA_list": "[{\"province\":\"??\",\"city\":\"???\",\"county\":\"???\",\"street\":\"?????\"}]", + "ue_mobility_level": "stationary", + "resource_sharing_level": "0", + "exp_data_rate_UL": 100, + "exp_data_rate_DL": 100, + "activity_factor": 0, + "e2e_latency": 0, + "jitter": 0, + "survival_time": 0, + "exp_data_rate": 0, + "payload_size": 0, + "traffic_density": 0, + "conn_density": 0 +} diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai.py b/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai.py index 55bdef9..4c38feb 100644 --- a/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai.py +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai.py @@ -725,44 +725,60 @@ tenant/3c6c471ada7747fe8ff7f28e100b61e8/vservers/vserver/00bddefc-126e-4e4f-a18d self.assertEqual(None, self.aai_ep.match_hpa(candidate_json['candidate_list'][1], feature_json[5])) - def test_get_nssi_candidates(self): + def test_filter_nssi_candidates(self): nssi_response_file = './conductor/tests/unit/data/plugins/inventory_provider/nssi_response.json' nssi_response = json.loads(open(nssi_response_file).read()) nssi_candidates_file = './conductor/tests/unit/data/plugins/inventory_provider/nssi_candidate.json' nssi_candidates = json.loads(open(nssi_candidates_file).read()) - nsi_info = {'instance_name': 'nsi_test_0211', - 'instance_id': '4115d3c8-dd59-45d6-b09d-e756dee9b518', - 'model_version_id': '8b664b11-6646-4776-9f59-5c3de46da2d6', - 'model_invariant_id': '39b10fe6-efcc-40bc-8184-c38414b80771'} + service_role = 'nssi' + second_level_filter = dict() + second_level_filter['service-role'] = service_role + default_attributes = dict() + default_attributes['creation_cost'] =1 + self.assertEqual(nssi_candidates, self.aai_ep.filter_nxi_candidates(nssi_response, second_level_filter, + default_attributes, "true", service_role)) - self.nsi_patcher = mock.patch('conductor.data.plugins.inventory_provider.aai.AAI.get_nsi_info', - return_value=nsi_info) - self.nsi_patcher.start() + nssi_response['service-instance'][0]['service-role'] = 'service' - service_role = 'nssi' - model_invariant_id = '21d57d4b-52ad-4d3c-a798-248b5bb9124a' - model_version_id = 'bfba363e-e39c-4bd9-a9d5-1371c28f4d22' - orchestration_status = 'active' - filtering_attributes = dict() - filtering_attributes['orchestration-status'] = orchestration_status - filtering_attributes['service-role'] = service_role - filtering_attributes['model-invariant-id'] = model_invariant_id - filtering_attributes['model-version-id'] = model_version_id + self.assertEqual([], self.aai_ep.filter_nxi_candidates(nssi_response, second_level_filter, default_attributes, + "true", service_role)) - self.assertEqual(nssi_candidates, self.aai_ep.filter_nssi_candidates(nssi_response, filtering_attributes, "true")) + self.assertEqual([], self.aai_ep.filter_nxi_candidates(None, second_level_filter, default_attributes, + "true", service_role)) - nssi_response['service-instance'][0]['orchestration-status'] = 'deactivated' + self.assertEqual([], self.aai_ep.filter_nxi_candidates(None, None, default_attributes, "true", service_role)) - self.assertEqual([], self.aai_ep.filter_nssi_candidates(nssi_response, filtering_attributes, "true")) + self.assertEqual(nssi_candidates, self.aai_ep.filter_nxi_candidates(nssi_response, None, default_attributes, + "true", service_role)) + del nssi_candidates[0]['creation_cost'] + self.assertEqual(nssi_candidates, self.aai_ep.filter_nxi_candidates(nssi_response, None, None, "true", + service_role)) - nssi_response['service-instance'][0]['service-role'] = 'service' + def test_resolve_demands_inventory_type_nssi(self): + self.aai_ep.conf.HPA_enabled = True + TraigeTranslator.getPlanIdNAme = mock.MagicMock(return_value=None) + TraigeTranslator.addDemandsTriageTranslator = mock.MagicMock(return_value=None) - self.assertEqual([], self.aai_ep.filter_nssi_candidates(nssi_response, filtering_attributes, "true")) + plan_info = { + 'plan_name': 'name', + 'plan_id': 'id' + } + triage_translator_data = None - self.assertEqual([], self.aai_ep.filter_nssi_candidates(None, filtering_attributes, "true")) + demands_list_file = './conductor/tests/unit/data/plugins/inventory_provider/nssi_demand_list.json' + demands_list = json.loads(open(demands_list_file).read()) - self.assertEqual([], self.aai_ep.filter_nssi_candidates(None, None, "true")) + nssi_response_file = './conductor/tests/unit/data/plugins/inventory_provider/nssi_response.json' + nssi_response = json.loads(open(nssi_response_file).read()) + nssi_candidates_file = './conductor/tests/unit/data/plugins/inventory_provider/nssi_candidate.json' + nssi_candidates = json.loads(open(nssi_candidates_file).read()) + result = dict() + result['embb_cn'] = nssi_candidates - self.assertEqual([], self.aai_ep.filter_nssi_candidates(nssi_response, None, "true")) + self.mock_get_nxi_candidates = mock.patch.object(AAI, 'get_nxi_candidates', + return_value=nssi_response) + self.mock_get_nxi_candidates.start() + self.assertEqual(result, self.aai_ep.resolve_demands(demands_list, plan_info=plan_info, + triage_translator_data=triage_translator_data)) diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai_utils.py b/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai_utils.py new file mode 100644 index 0000000..23b6640 --- /dev/null +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai_utils.py @@ -0,0 +1,95 @@ +# +# ------------------------------------------------------------------------- +# Copyright (C) 2020 Wipro Limited. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------- +# + +import unittest +import json + +from mock import patch + +from conductor.data.plugins.inventory_provider.utils import aai_utils + +class TestUtils(unittest.TestCase): + + def setUp(self): + pass + + def tearDown(self): + patch.stopall() + + def test_convert_hyphen_to_under_score(self): + slice_profile_file = './conductor/tests/unit/data/plugins/inventory_provider/slice_profile.json' + slice_profile_hyphened = json.loads(open(slice_profile_file).read()) + + converted_slice_profile_file = './conductor/tests/unit/data/plugins/inventory_provider' \ + '/slice_profile_converted.json' + converted_slice_profile = json.loads(open(converted_slice_profile_file).read()) + self.assertEqual(converted_slice_profile, aai_utils.convert_hyphen_to_under_score(slice_profile_hyphened)) + + def test_get_first_level_and_second_level_filter(self): + first_level_filter_file = './conductor/tests/unit/data/plugins/inventory_provider/first_level_filter.json' + first_level_filter = json.loads(open(first_level_filter_file).read()) + filtering_attributes = dict() + filtering_attributes['orchestration-status'] = "active" + filtering_attributes['service-role'] = "nssi" + filtering_attributes['model-invariant-id'] = "123644" + filtering_attributes['model-version-id'] = "524846" + filtering_attributes['environment-context'] = 'shared' + + second_level_filter = {'service-role': 'nssi'} + + self.assertEqual(second_level_filter, aai_utils.get_first_level_and_second_level_filter(filtering_attributes, + "service_instance")) + + self.assertEqual(first_level_filter, filtering_attributes) + + self.assertEqual({}, aai_utils.get_first_level_and_second_level_filter(filtering_attributes, + "service_instance")) + + def test_add_query_params(self): + first_level_filter_file = './conductor/tests/unit/data/plugins/inventory_provider/first_level_filter.json' + first_level_filter = json.loads(open(first_level_filter_file).read()) + + query_params = "?orchestration-status=active&model-invariant-id=123644&model-version-id=524846&" \ + "environment-context=shared&" + + self.assertEqual(query_params, aai_utils.add_query_params(first_level_filter)) + first_level_filter = {} + self.assertEqual('', aai_utils.add_query_params(first_level_filter)) + + def test_add_query_params_and_depth(self): + first_level_filter_file = './conductor/tests/unit/data/plugins/inventory_provider/first_level_filter.json' + first_level_filter = json.loads(open(first_level_filter_file).read()) + + query_params_with_depth = "?orchestration-status=active&model-invariant-id=123644&model-version-id=524846&" \ + "environment-context=shared&depth=2" + + self.assertEqual(query_params_with_depth, aai_utils.add_query_params_and_depth(first_level_filter, "2")) + + only_depth = "?depth=2" + first_level_filter = {} + self.assertEqual(only_depth, aai_utils.add_query_params_and_depth(first_level_filter, "2")) + + def test_get_inv_values_for_second_level_filter(self): + nssi_response_file = './conductor/tests/unit/data/plugins/inventory_provider/nssi_response.json' + nssi_response = json.loads(open(nssi_response_file).read()) + nssi_instance = nssi_response.get("service-instance")[0] + second_level_filter = {'service-role': 'nsi'} + inventory_attribute = {'service-role': 'nssi'} + self.assertEqual(inventory_attribute, aai_utils.get_inv_values_for_second_level_filter(second_level_filter, + nssi_instance)) diff --git a/conductor/conductor/tests/unit/solver/optimizer/constraints/test_threshold.py b/conductor/conductor/tests/unit/solver/optimizer/constraints/test_threshold.py index 311e790..f12ed9c 100644 --- a/conductor/conductor/tests/unit/solver/optimizer/constraints/test_threshold.py +++ b/conductor/conductor/tests/unit/solver/optimizer/constraints/test_threshold.py @@ -33,7 +33,7 @@ class TestThreshold(unittest.TestCase): # test 1 properties = {'evaluate': [{'attribute': 'latency', 'threshold': 30, 'operator': 'lte'}, - {'attribute': 'exp_data_rate_ul', 'threshold': 70, 'operator': 'gte'}]} + {'attribute': 'exp_data_rate_UL', 'threshold': 70, 'operator': 'gte'}]} threshold_obj = Threshold('urllc_threshold', 'threshold', ['URLLC'], _priority=0, _properties=properties) diff --git a/conductor/tox.ini b/conductor/tox.ini index 3fe867a..b65abd4 100644 --- a/conductor/tox.ini +++ b/conductor/tox.ini @@ -64,8 +64,8 @@ select = E,H,W,F max-line-length = 119 exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build,install-guide,*/tests/* show-source = True -ignore= -per-file-ignores= +ignore= W503 #conflict with W504 +per-file-ignores= conductor/data/plugins/inventory_provider/aai.py:F821 [hacking] import_exceptions = conductor.common.i18n -- 2.16.6