Add NSSI candidate 16/111516/6
authordhebeha <dhebeha.mj71@wipro.com>
Fri, 21 Aug 2020 07:27:41 +0000 (12:57 +0530)
committerdhebeha <dhebeha.mj71@wipro.com>
Tue, 1 Sep 2020 07:08:26 +0000 (12:38 +0530)
   -Add generic flow for NxI candidate
   -Add default attributes

Issue-ID: OPTFRA-801
Signed-off-by: dhebeha <dhebeha.mj71@wipro.com>
Change-Id: I869ebcd35d4c2436676868b26006ca991b34e538

15 files changed:
conductor/conductor/controller/translator.py
conductor/conductor/data/plugins/inventory_provider/aai.py
conductor/conductor/data/plugins/inventory_provider/candidates/candidate.py
conductor/conductor/data/plugins/inventory_provider/candidates/nxi_candidate.py [new file with mode: 0644]
conductor/conductor/data/plugins/inventory_provider/utils/aai_utils.py [new file with mode: 0644]
conductor/conductor/tests/unit/data/plugins/inventory_provider/first_level_filter.json [new file with mode: 0644]
conductor/conductor/tests/unit/data/plugins/inventory_provider/nssi_candidate.json
conductor/conductor/tests/unit/data/plugins/inventory_provider/nssi_demand_list.json [new file with mode: 0644]
conductor/conductor/tests/unit/data/plugins/inventory_provider/nssi_response.json
conductor/conductor/tests/unit/data/plugins/inventory_provider/slice_profile.json [new file with mode: 0644]
conductor/conductor/tests/unit/data/plugins/inventory_provider/slice_profile_converted.json [new file with mode: 0644]
conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai.py
conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai_utils.py [new file with mode: 0644]
conductor/conductor/tests/unit/solver/optimizer/constraints/test_threshold.py
conductor/tox.ini

index c0d7e29..83c71ed 100644 (file)
@@ -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),
             }
         }
index 821219c..9defe94 100644 (file)
@@ -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
index 7f241c3..2b2eb4b 100644 (file)
 # -------------------------------------------------------------------------
 #
 
-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 (file)
index 0000000..5eb75a1
--- /dev/null
@@ -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 (file)
index 0000000..2fbaabd
--- /dev/null
@@ -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 (file)
index 0000000..be1810c
--- /dev/null
@@ -0,0 +1,5 @@
+{"orchestration-status": "active",
+"model-invariant-id": "123644",
+"model-version-id": "524846",
+"environment-context": "shared"
+}
index 28a4f8f..b5a4a7d 100644 (file)
@@ -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 (file)
index 0000000..15168be
--- /dev/null
@@ -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
+        }
+    }]
+}
index e22ce39..286c8bf 100644 (file)
@@ -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 (file)
index 0000000..4bbda28
--- /dev/null
@@ -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 (file)
index 0000000..f7581a2
--- /dev/null
@@ -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
+}
index 55bdef9..4c38feb 100644 (file)
@@ -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 (file)
index 0000000..23b6640
--- /dev/null
@@ -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))
index 311e790..f12ed9c 100644 (file)
@@ -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)
index 3fe867a..b65abd4 100644 (file)
@@ -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