New Inventory type for TD
[optf/has.git] / conductor / conductor / data / plugins / inventory_provider / aai.py
index 31064a6..d6fb724 100644 (file)
@@ -20,6 +20,7 @@
 import re
 import time
 import uuid
+import copy
 
 import json
 from oslo_config import cfg
@@ -787,6 +788,78 @@ class AAI(base.InventoryProviderBase):
         body = response.json()
         return body.get("generic-vnf", [])
 
+    def resolove_v_server_for_candidate(self, candidate, 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))
+            self.triage_translator.collectDroppedCandiate(candidate['candidate_id'],
+                                                          candidate['location_id'], demand_name,
+                                                          triage_translator_data,
+                                                          reason="VSERVER link information not")
+            return None  # move ahead with the next vnf
+
+        if add_interfaces:
+            vs_link = vs_link + '?depth=2'
+        vs_path = self._get_aai_path_from_link(vs_link)
+        if not vs_path:
+            LOG.error(_LE("{} VSERVER path information not "
+                          "available from A&AI - {}").
+                      format(demand_name, vs_path))
+            self.triage_translator.collectDroppedCandiate(candidate['candidate_id'],
+                                                          candidate['location_id'], demand_name,
+                                                          triage_translator_data,
+                                                          reason="VSERVER path information not available from A&AI")
+            return None  # move ahead with the next vnf
+        path = self._aai_versioned_path(vs_path)
+        response = self._request(
+            path=path, context="demand, VSERVER",
+            value="{}, {}".format(demand_name, vs_path))
+        if response is None or response.status_code != 200:
+            self.triage_translator.collectDroppedCandiate(candidate['candidate_id'],
+                                                          candidate['location_id'], demand_name,
+                                                          triage_translator_data,
+                                                          reason=response.status_code)
+            return None
+        return response.json()
+
+    def resolve_vf_modules_for_generic_vnf(self, candidate, vnf, demand_name, triage_translator_data):
+        raw_path = '/network/generic-vnfs/generic-vnf/{}?depth=1'.format(vnf.get("vnf-id"))
+        path = self._aai_versioned_path(raw_path)
+
+        response = self._request('get', path=path, data=None)
+        if response is None or response.status_code != 200:
+            self.triage_translator.collectDroppedCandiate(candidate['candidate_id'],
+                                                          candidate['location_id'], demand_name,
+                                                          triage_translator_data,
+                                                          reason=response)
+            return None
+        generic_vnf_details = response.json()
+
+        if generic_vnf_details is None or not generic_vnf_details.get('vf-modules') \
+                or not generic_vnf_details.get('vf-modules').get('vf-module'):
+            self.triage_translator.collectDroppedCandiate(candidate['candidate_id'],
+                                                          candidate['location_id'], demand_name,
+                                                          triage_translator_data,
+                                                          reason="Generic-VNF No detailed data for VF-modules")
+            return None
+        else:
+            return generic_vnf_details.get('vf-modules').get('vf-module')
+
+    def resolve_cloud_regions_by_cloud_region_id(self, cloud_region_id):
+        cloud_region_uri = '/cloud-infrastructure/cloud-regions' \
+                           '/?cloud-region-id=' \
+                           + cloud_region_id
+        path = self._aai_versioned_path(cloud_region_uri)
+
+        response = self._request('get',
+                                 path=path,
+                                 data=None)
+        if response is None or response.status_code != 200:
+            return None
+
+        body = response.json()
+        return body.get('cloud-region', [])
+
     def assign_candidate_existing_placement(self, candidate, existing_placement):
 
         """Assign existing_placement and cost parameters to candidate
@@ -817,6 +890,289 @@ class AAI(base.InventoryProviderBase):
 
         return ''.join(conflict_id_list)
 
+    def resolve_v_server_links_for_vnf(self, vnf):
+        related_to = "vserver"
+        search_key = "cloud-region.cloud-owner"
+        rl_data_list = self._get_aai_rel_link_data(
+            data=vnf, related_to=related_to,
+            search_key=search_key)
+        vs_link_list = list()
+        for i in range(0, len(rl_data_list)):
+            vs_link_list.append(rl_data_list[i].get('link'))
+        return vs_link_list
+
+    def resolve_complex_info_link_for_v_server(self, candidate, 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,
+            related_to=related_to,
+            search_key=None
+        )
+        if len(rl_data_list) > 1:
+            self._log_multiple_item_error(
+                demand_name, service_type, related_to, "item",
+                "VSERVER", v_server)
+            self.triage_translator.collectDroppedCandiate(candidate['candidate_id'],
+                                                          candidate['location_id'], demand_name,
+                                                          triage_translator_data, reason="item VSERVER")
+            return None
+        rl_data = rl_data_list[0]
+        ps_link = rl_data.get('link')
+
+        # Third level query to get cloud region from pserver
+        if not ps_link:
+            LOG.error(_LE("{} pserver related link "
+                          "not found in A&AI: {}").
+                      format(demand_name, rl_data))
+            # if HPA_feature is disabled
+            if not self.conf.HPA_enabled:
+                # Triage Tool Feature Changes
+                self.triage_translator.collectDroppedCandiate(candidate['candidate_id'],
+                                                              candidate['location_id'], demand_name,
+                                                              triage_translator_data,
+                                                              reason="ps link not found")
+                return None
+            else:
+                if not (cloud_owner and cloud_region_id):
+                    LOG.error("{} cloud-owner or cloud-region not "
+                              "available from A&AI".
+                              format(demand_name))
+                    # Triage Tool Feature Changes
+                    self.triage_translator.collectDroppedCandiate(candidate['candidate_id'],
+                                                                  candidate['location_id'], demand_name,
+                                                                  triage_translator_data,
+                                                                  reason="Cloud owner and cloud region "
+                                                                         "id not found")
+                    return None  # move ahead with the next vnf
+                cloud_region_uri = \
+                    '/cloud-infrastructure/cloud-regions/cloud-region' \
+                    '/?cloud-owner=' + cloud_owner \
+                    + '&cloud-region-id=' + cloud_region_id
+                path = self._aai_versioned_path(cloud_region_uri)
+                response = self._request('get',
+                                         path=path,
+                                         data=None)
+                if response is None or response.status_code != 200:
+                    # Triage Tool Feature Changes
+                    self.triage_translator.collectDroppedCandiate(candidate['candidate_id'],
+                                                                  candidate['location_id'], demand_name,
+                                                                  triage_translator_data,
+                                                                  reason=response)
+                    return None
+                body = response.json()
+        else:
+            ps_path = self._get_aai_path_from_link(ps_link)
+            if not ps_path:
+                LOG.error(_LE("{} pserver path information "
+                              "not found in A&AI: {}").
+                          format(demand_name, ps_link))
+                # Triage Tool Feature Changes
+                self.triage_translator.collectDroppedCandiate(candidate['candidate_id'],
+                                                              candidate['location_id'], demand_name,
+                                                              triage_translator_data,
+                                                              reason="ps path not found")
+                return None  # move ahead with the next vnf
+            path = self._aai_versioned_path(ps_path)
+            response = self._request(
+                path=path, context="PSERVER", value=ps_path)
+            if response is None or response.status_code != 200:
+                # Triage Tool Feature Changes
+                self.triage_translator.collectDroppedCandiate(candidate['candidate_id'],
+                                                              candidate['location_id'], demand_name,
+                                                              triage_translator_data,
+                                                              reason=response)
+                return None
+            body = response.json()
+
+        related_to = "complex"
+        search_key = "complex.physical-location-id"
+        rl_data_list = self._get_aai_rel_link_data(
+            data=body,
+            related_to=related_to,
+            search_key=search_key
+        )
+        if len(rl_data_list) > 1:
+            if not self.match_vserver_attribute(rl_data_list):
+                self._log_multiple_item_error(
+                    demand_name, service_type, related_to, search_key, "PSERVER", body)
+                self.triage_translator.collectDroppedCandiate(candidate['candidate_id'], candidate['location_id'],
+                                                              demand_name, triage_translator_data,
+                                                              reason="PSERVER error")
+                return None
+        return rl_data_list[0]
+
+    def resolve_cloud_region_id_and_version_for_vnf(self, candidate, vnf, service_type, demand_name,
+                                                    triage_translator_data):
+        related_to = "vserver"
+        search_key = "cloud-region.cloud-region-id"
+
+        rl_data_list = self._get_aai_rel_link_data(
+            data=vnf,
+            related_to=related_to,
+            search_key=search_key
+        )
+        if len(rl_data_list) > 1:
+            if not self.match_vserver_attribute(rl_data_list):
+                self._log_multiple_item_error(
+                    demand_name, service_type, related_to, search_key,
+                    "VNF", vnf)
+                self.triage_translator.collectDroppedCandiate(candidate['candidate_id'],
+                                                              candidate['location_id'], demand_name,
+                                                              triage_translator_data,
+                                                              reason="VNF error")
+                return None, None
+        cloud_region_rl_data = rl_data_list[0]
+        cloud_region_id = cloud_region_rl_data.get('d_value')
+
+        # get version for service candidate
+        cloud_region_version_rl_data = {'d_value': ''}
+        if cloud_region_id:
+            regions = self.resolve_cloud_regions_by_cloud_region_id(cloud_region_id)
+            if regions is None:
+                return cloud_region_rl_data, None
+
+            for region in regions:
+                if "cloud-region-version" in region:
+                    cloud_region_version_rl_data['d_value'] = self._get_version_from_string(region["cloud-region-version"])
+
+        return cloud_region_rl_data, cloud_region_version_rl_data
+
+    def resolve_cloud_owner_for_vnf(self, candidate, vnf, service_type, demand_name, triage_translator_data):
+        related_to = "vserver"
+        search_key = "cloud-region.cloud-owner"
+        rl_data_list = self._get_aai_rel_link_data(
+            data=vnf, related_to=related_to,
+            search_key=search_key)
+
+        if len(rl_data_list) > 1:
+            if not self.match_vserver_attribute(rl_data_list):
+                self._log_multiple_item_error(
+                    demand_name, service_type, related_to, search_key,
+                    "VNF", vnf)
+                self.triage_translator.collectDroppedCandiate(candidate['candidate_id'],
+                                                              candidate['location_id'], demand_name,
+                                                              triage_translator_data,
+                                                              reason="VNF error")
+                return None
+        return rl_data_list[0]
+
+    def resolve_global_customer_id_for_vnf(self, candidate, 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"
+        rl_data_list = self._get_aai_rel_link_data(
+            data=vnf,
+            related_to=related_to,
+            search_key=search_key,
+            match_dict={'key': match_key,
+                        'value': customer_id}
+        )
+        if len(rl_data_list) > 1:
+            if not self.match_vserver_attribute(rl_data_list):
+                self._log_multiple_item_error(
+                    demand_name, service_type, related_to, search_key, "VNF", vnf)
+                self.triage_translator.collectDroppedCandiate(candidate['candidate_id'], candidate['location_id'],
+                                                              demand_name, triage_translator_data,
+                                                              reason=" match_vserver_attribute generic-vnf")
+                return None
+        return rl_data_list[0]
+
+    def resolve_service_instance_id_for_vnf(self, candidate, 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"
+        rl_data_list = self._get_aai_rel_link_data(
+            data=vnf,
+            related_to=related_to,
+            search_key=search_key,
+            match_dict={'key': match_key,
+                        'value': customer_id}
+        )
+        if len(rl_data_list) > 1:
+            if not self.match_vserver_attribute(rl_data_list):
+                self._log_multiple_item_error(
+                    demand_name, service_type, related_to, search_key, "VNF", vnf)
+                self.triage_translator.collectDroppedCandiate(candidate['candidate_id'], candidate['location_id'],
+                                                              demand_name, triage_translator_data,
+                                                              reason="multiple_item_error generic-vnf")
+                return None
+        return rl_data_list[0]
+
+    def build_complex_info_for_candidate(self, candidate, vnf, complex_list, service_type, demand_name,
+                                         triage_translator_data):
+        if not complex_list or \
+                len(complex_list) < 1:
+            LOG.error("Complex information not "
+                      "available from A&AI")
+            self.triage_translator.collectDroppedCandiate(candidate['candidate_id'],
+                                                          candidate['location_id'], demand_name,
+                                                          triage_translator_data,
+                                                          reason="Complex information not available from A&AI")
+            return
+
+        # In the scenario where no pserver information is available
+        # assumption here is that cloud-region does not span across
+        # multiple complexes
+        if len(complex_list) > 1:
+            related_to = "complex"
+            search_key = "complex.physical-location-id"
+            if not self.match_vserver_attribute(complex_list):
+                self._log_multiple_item_error(
+                    demand_name, service_type, related_to, search_key,
+                    "VNF", vnf)
+                self.triage_translator.collectDroppedCandiate(candidate['candidate_id'],
+                                                              candidate['location_id'], demand_name,
+                                                              triage_translator_data,
+                                                              reason="Generic-vnf error")
+                return
+
+        rl_data = complex_list[0]
+        complex_link = rl_data.get('link')
+        complex_id = rl_data.get('d_value')
+
+        # Final query for the complex information
+        if not (complex_link and complex_id):
+            LOG.debug("{} complex information not "
+                      "available from A&AI - {}".
+                      format(demand_name, complex_link))
+            self.triage_translator.collectDroppedCandiate(candidate['candidate_id'],
+                                                          candidate['location_id'], demand_name,
+                                                          triage_translator_data,
+                                                          reason="Complex information not available from A&AI")
+            return  # move ahead with the next vnf
+        else:
+            complex_info = self._get_complex(
+                complex_link=complex_link,
+                complex_id=complex_id
+            )
+            if not complex_info:
+                LOG.debug("{} complex information not "
+                          "available from A&AI - {}".
+                          format(demand_name, complex_link))
+                self.triage_translator.collectDroppedCandiate(candidate['candidate_id'],
+                                                              candidate['location_id'], demand_name,
+                                                              triage_translator_data,
+                                                              reason="Complex information not available from A&AI")
+                return  # move ahead with the next vnf
+            candidate['physical_location_id'] = \
+                complex_id
+            candidate['complex_name'] = \
+                complex_info.get('complex-name')
+            candidate['latitude'] = \
+                complex_info.get('latitude')
+            candidate['longitude'] = \
+                complex_info.get('longitude')
+            candidate['state'] = \
+                complex_info.get('state')
+            candidate['country'] = \
+                complex_info.get('country')
+            candidate['city'] = \
+                complex_info.get('city')
+            candidate['region'] = \
+                complex_info.get('region')
 
     def resolve_demands(self, demands, plan_info, triage_translator_data):
         """Resolve demands into inventory candidate lists"""
@@ -958,56 +1314,28 @@ class AAI(base.InventoryProviderBase):
                         if conflict_identifier:
                             candidate['conflict_id'] = self.resovle_conflict_id(conflict_identifier, candidate)
 
-                        if self.match_candidate_attribute(
-                                candidate, "candidate_id",
-                                restricted_region_id, name,
-                                inventory_type) or \
-                           self.match_candidate_attribute(
-                               candidate, "physical_location_id",
-                               restricted_complex_id, name,
-                               inventory_type):
+                        if not self.match_region(candidate, restricted_region_id, restricted_complex_id, name,
+                                                 triage_translator_data):
                             continue
 
                         self.assign_candidate_existing_placement(candidate, existing_placement)
 
                         # Pick only candidates not in the excluded list
                         # if excluded candidate list is provided
-                        if excluded_candidates:
-                            has_excluded_candidate = False
-                            for excluded_candidate in excluded_candidates:
-                                if excluded_candidate \
-                                   and excluded_candidate.get('inventory_type') == \
-                                   candidate.get('inventory_type') \
-                                   and excluded_candidate.get('candidate_id') == \
-                                   candidate.get('candidate_id'):
-                                    has_excluded_candidate = True
-                                    break
-
-                            if has_excluded_candidate:
-                                continue
+                        if excluded_candidates and self.match_candidate_by_list(candidate, excluded_candidates, True, name, triage_translator_data):
+                            continue
 
                         # Pick only candidates in the required list
                         # if required candidate list is provided
-                        if required_candidates:
-                            has_required_candidate = False
-                            for required_candidate in required_candidates:
-                                if required_candidate \
-                                   and required_candidate.get('inventory_type') \
-                                   == candidate.get('inventory_type') \
-                                   and required_candidate.get('candidate_id') \
-                                   == candidate.get('candidate_id'):
-                                    has_required_candidate = True
-                                    break
-
-                            if not has_required_candidate:
-                                self.triage_translator.collectDroppedCandiate(candidate['candidate_id'], candidate['location_id'], name, triage_translator_data,
-                                                                             reason="has_required_candidate")
-                                continue
+                        if required_candidates and not self.match_candidate_by_list(candidate, required_candidates, False, name, triage_translator_data):
+                            continue
 
                         # add candidate to demand candidates
                         resolved_demands[name].append(candidate)
+                        LOG.debug(">>>>>>> Candidate <<<<<<<")
+                        LOG.debug(json.dumps(candidate, indent=4))
 
-                elif inventory_type == 'service' \
+                elif (inventory_type == 'service') \
                         and customer_id:
 
                     # First level query to get the list of generic vnfs
@@ -1045,7 +1373,7 @@ class AAI(base.InventoryProviderBase):
                         candidate = dict()
                         candidate['inventory_provider'] = 'aai'
                         candidate['service_resource_id'] = service_resource_id
-                        candidate['inventory_type'] = 'service'
+                        candidate['inventory_type'] = inventory_type
                         candidate['candidate_id'] = ''
                         candidate['location_id'] = ''
                         candidate['location_type'] = 'att_aic'
@@ -1060,48 +1388,25 @@ class AAI(base.InventoryProviderBase):
                         # start populating the candidate
                         candidate['host_id'] = vnf.get("vnf-name")
 
-                        related_to = "vserver"
-                        search_key = "cloud-region.cloud-owner"
-                        rl_data_list = self._get_aai_rel_link_data(
-                            data=vnf, related_to=related_to,
-                            search_key=search_key)
-
-                        if len(rl_data_list) > 1:
-                            if not self.match_vserver_attribute(rl_data_list):
-                                self._log_multiple_item_error(
-                                    name, service_type, related_to, search_key,
-                                    "GENERIC-VNF", vnf)
-                                self.triage_translator.collectDroppedCandiate(candidate['candidate_id'], candidate['location_id'], name, triage_translator_data,
-                                                                             reason="Generic -vnf error")
-                                continue
-                        rl_data = rl_data_list[0]
-
-                        vs_link_list = list()
-                        for i in range(0, len(rl_data_list)):
-                            vs_link_list.append(rl_data_list[i].get('link'))
+                        rl_data = self.resolve_cloud_owner_for_vnf(candidate, vnf, service_type, name,
+                                                                   triage_translator_data)
+                        if rl_data is None:
+                            continue
+                        else:
+                            cloud_owner = rl_data.get('d_value')
 
-                        cloud_owner = rl_data.get('d_value')
                         candidate['cloud_owner'] = cloud_owner
 
+                        cloud_region_id_rl_data, cloud_region_version_rl_data = self.resolve_cloud_region_id_and_version_for_vnf(
+                            candidate, vnf, service_type, name, triage_translator_data)
 
-                        search_key = "cloud-region.cloud-region-id"
+                        if cloud_region_id_rl_data is None or cloud_region_version_rl_data is None:
+                            continue
 
-                        rl_data_list = self._get_aai_rel_link_data(
-                            data=vnf,
-                            related_to=related_to,
-                            search_key=search_key
-                        )
-                        if len(rl_data_list) > 1:
-                            if not self.match_vserver_attribute(rl_data_list):
-                                self._log_multiple_item_error(
-                                    name, service_type, related_to, search_key,
-                                    "GENERIC-VNF", vnf)
-                                self.triage_translator.collectDroppedCandiate(candidate['candidate_id'], candidate['location_id'], name, triage_translator_data,
-                                                                             reason=" generic-vnf error")
-                                continue
-                        rl_data = rl_data_list[0]
-                        cloud_region_id = rl_data.get('d_value')
+                        cloud_region_id = cloud_region_id_rl_data.get('d_value')
+                        cloud_region_version = cloud_region_version_rl_data.get('d_value')
                         candidate['location_id'] = cloud_region_id
+                        candidate['cloud_region_version'] = cloud_region_version
 
                         # Added vim-id for short-term workaround
                         if self.conf.HPA_enabled:
@@ -1110,28 +1415,6 @@ class AAI(base.InventoryProviderBase):
                             candidate['vim-id'] = \
                                 candidate['cloud_owner'] + '_' + cloud_region_id
 
-                        # get version for service candidate
-                        if cloud_region_id:
-                            cloud_region_uri = '/cloud-infrastructure/cloud-regions' \
-                                               '/?cloud-region-id=' \
-                                               + cloud_region_id
-                            path = self._aai_versioned_path(cloud_region_uri)
-
-                            response = self._request('get',
-                                                     path=path,
-                                                     data=None)
-                            if response is None or response.status_code != 200:
-                                return None
-
-                            body = response.json()
-                            regions = body.get('cloud-region', [])
-
-                            for region in regions:
-                                if "cloud-region-version" in region:
-                                    candidate['cloud_region_version'] = \
-                                        self._get_version_from_string(
-                                            region["cloud-region-version"])
-
                         if self.check_sriov_automation(
                                 candidate['cloud_region_version'], name,
                                 candidate['host_id']):
@@ -1139,250 +1422,60 @@ class AAI(base.InventoryProviderBase):
                         else:
                             candidate['sriov_automation'] = 'false'
 
-                        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,
-                            search_key=search_key,
-                            match_dict={'key': match_key,
-                                        'value': customer_id}
-                        )
-                        if len(rl_data_list) > 1:
-                            if not self.match_vserver_attribute(rl_data_list):
-                                self._log_multiple_item_error(
-                                    name, service_type, related_to, search_key,
-                                    "GENERIC-VNF", vnf)
-                                self.triage_translator.collectDroppedCandiate(candidate['candidate_id'], candidate['location_id'], name,triage_translator_data,
-                                                                             reason=" match_vserver_attribute generic-vnf")
-                                continue
-                        rl_data = rl_data_list[0]
-                        vs_cust_id = rl_data.get('d_value')
+                        rl_data = self.resolve_global_customer_id_for_vnf(candidate, vnf, customer_id, service_type,
+                                                                          name, triage_translator_data)
+                        if rl_data is None:
+                            continue
+                        else:
+                            vs_cust_id = rl_data.get('d_value')
 
-                        search_key = "service-instance.service-instance-id"
-                        match_key = "customer.global-customer-id"
-                        rl_data_list = self._get_aai_rel_link_data(
-                            data=vnf,
-                            related_to=related_to,
-                            search_key=search_key,
-                            match_dict={'key': match_key,
-                                        'value': customer_id}
-                        )
-                        if len(rl_data_list) > 1:
-                            if not self.match_vserver_attribute(rl_data_list):
-                                self._log_multiple_item_error(
-                                    name, service_type, related_to, search_key,
-                                    "GENERIC-VNF", vnf)
-                                self.triage_translator.collectDroppedCandiate(candidate['candidate_id'], candidate['location_id'], name,triage_translator_data,
-                                                                             reason="multiple_item_error generic-vnf")
-                                continue
-                        rl_data = rl_data_list[0]
-                        vs_service_instance_id = rl_data.get('d_value')
+                        rl_data = self.resolve_service_instance_id_for_vnf(candidate, vnf, customer_id,
+                                                                           service_type, name, triage_translator_data)
+                        if rl_data is None:
+                            continue
+                        else:
+                            vs_service_instance_id = rl_data.get('d_value')
 
                         if vs_cust_id and vs_cust_id == customer_id:
                             candidate['candidate_id'] = \
                                 vs_service_instance_id
                         else:  # vserver is for a different customer
-                            self.triage_translator.collectDroppedCandiate(candidate['candidate_id'], candidate['location_id'], name, triage_translator_data, reason= "vserver is for a different customer")
+                            self.triage_translator.collectDroppedCandiate(candidate['candidate_id'],
+                                                                          candidate['location_id'], name,
+                                                                          triage_translator_data,
+                                                                          reason= "vserver is for a different customer")
                             continue
 
                         # Second level query to get the pserver from vserver
                         complex_list = list()
+                        vs_link_list = self.resolve_v_server_links_for_vnf(vnf)
 
                         for vs_link in vs_link_list:
 
-                            if not vs_link:
-                                LOG.error(_LE("{} VSERVER link information not "
-                                              "available from A&AI").format(name))
-                                LOG.debug("Related link data: {}".format(rl_data))
-                                self.triage_translator.collectDroppedCandiate(candidate['candidate_id'], candidate['location_id'], name, triage_translator_data,
-                                                                             reason="VSERVER link information not")
-                                continue  # move ahead with the next vnf
-
-                            vs_path = self._get_aai_path_from_link(vs_link)
-                            if not vs_path:
-                                LOG.error(_LE("{} VSERVER path information not "
-                                              "available from A&AI - {}").
-                                          format(name, vs_path))
-                                self.triage_translator.collectDroppedCandiate(candidate['candidate_id'], candidate['location_id'], name, triage_translator_data,
-                                                                             reason="VSERVER path information not available from A&AI")
-                                continue  # move ahead with the next vnf
-                            path = self._aai_versioned_path(vs_path)
-                            response = self._request(
-                                path=path, context="demand, VSERVER",
-                                value="{}, {}".format(name, vs_path))
-                            if response is None or response.status_code != 200:
-                                self.triage_translator.collectDroppedCandiate(candidate['candidate_id'], candidate['location_id'], name, triage_translator_data,
-                                                                             reason=response.status_code)
+                            body = self.resolove_v_server_for_candidate(candidate, vs_link, True, name,
+                                                                        triage_translator_data)
+                            if body is None:
                                 continue
-                            body = response.json()
 
-                            related_to = "pserver"
-                            rl_data_list = self._get_aai_rel_link_data(
-                                data=body,
-                                related_to=related_to,
-                                search_key=None
-                            )
-                            if len(rl_data_list) > 1:
-                                self._log_multiple_item_error(
-                                    name, service_type, related_to, "item",
-                                    "VSERVER", body)
-                                self.triage_translator.collectDroppedCandiate(candidate['candidate_id'], candidate['location_id'], name, triage_translator_data,
-                                                                             reason="item VSERVER")
+                            rl_data = self.resolve_complex_info_link_for_v_server(candidate, body, cloud_owner,
+                                                                                  cloud_region_id, service_type,
+                                                                                  name, triage_translator_data)
+                            if rl_data is None:
                                 continue
-                            rl_data = rl_data_list[0]
-                            ps_link = rl_data.get('link')
-
-                            # Third level query to get cloud region from pserver
-                            if not ps_link:
-                                LOG.error(_LE("{} pserver related link "
-                                              "not found in A&AI: {}").
-                                          format(name, rl_data))
-                                # if HPA_feature is disabled
-                                if not self.conf.HPA_enabled:
-                                    # Triage Tool Feature Changes
-                                    self.triage_translator.collectDroppedCandiate(candidate['candidate_id'],
-                                                                                  candidate['location_id'], name,
-                                                                                  triage_translator_data,
-                                                                                  reason="ps link not found")
-                                    continue
-                                else:
-                                    if not (cloud_owner and cloud_region_id):
-                                        LOG.error("{} cloud-owner or cloud-region not "
-                                                  "available from A&AI".
-                                                  format(name))
-                                        # Triage Tool Feature Changes
-                                        self.triage_translator.collectDroppedCandiate(candidate['candidate_id'],
-                                                                                      candidate['location_id'], name,
-                                                                                      triage_translator_data,
-                                                                                      reason="Cloud owner and cloud region "
-                                                                                             "id not found")
-                                        continue  # move ahead with the next vnf
-                                    cloud_region_uri = \
-                                        '/cloud-infrastructure/cloud-regions/cloud-region' \
-                                        '/?cloud-owner=' + cloud_owner \
-                                        + '&cloud-region-id=' + cloud_region_id
-                                    path = self._aai_versioned_path(cloud_region_uri)
-                                    response = self._request('get',
-                                                             path=path,
-                                                             data=None)
-                                    if response is None or response.status_code != 200:
-                                        # Triage Tool Feature Changes
-                                        self.triage_translator.collectDroppedCandiate(candidate['candidate_id'],
-                                                                                      candidate['location_id'], name,
-                                                                                      triage_translator_data,
-                                                                                      reason=response)
-                                        continue
-                                    body = response.json()
-                            else:
-                                ps_path = self._get_aai_path_from_link(ps_link)
-                                if not ps_path:
-                                    LOG.error(_LE("{} pserver path information "
-                                                  "not found in A&AI: {}").
-                                              format(name, ps_link))
-                                    # Triage Tool Feature Changes
-                                    self.triage_translator.collectDroppedCandiate(candidate['candidate_id'],
-                                                                                  candidate['location_id'], name,
-                                                                                  triage_translator_data,
-                                                                                  reason="ps path not found")
-                                    continue  # move ahead with the next vnf
-                                path = self._aai_versioned_path(ps_path)
-                                response = self._request(
-                                    path=path, context="PSERVER", value=ps_path)
-                                if response is None or response.status_code != 200:
-                                    # Triage Tool Feature Changes
-                                    self.triage_translator.collectDroppedCandiate(candidate['candidate_id'],
-                                                                                  candidate['location_id'], name,
-                                                                                  triage_translator_data,
-                                                                                  reason=response)
-                                    continue
-                                body = response.json()
-
-                            related_to = "complex"
-                            search_key = "complex.physical-location-id"
-                            rl_data_list = self._get_aai_rel_link_data(
-                                data=body,
-                                related_to=related_to,
-                                search_key=search_key
-                            )
-                            if len(rl_data_list) > 1:
-                                if not self.match_vserver_attribute(rl_data_list):
-                                    self._log_multiple_item_error(
-                                        name, service_type, related_to, search_key,
-                                        "PSERVER", body)
-                                    self.triage_translator.collectDroppedCandiate(candidate['candidate_id'], candidate['location_id'], name, triage_translator_data,
-                                                                                 reason="PSERVER error")
-                                    continue
-                            rl_data = rl_data_list[0]
-                            complex_list.append(rl_data)
-
-                        if not complex_list or \
-                            len(complex_list) < 1:
-                            LOG.error("Complex information not "
-                                          "available from A&AI")
-                            self.triage_translator.collectDroppedCandiate(candidate['candidate_id'], candidate['location_id'], name, triage_translator_data,
-                                                                         reason="Complex information not available from A&AI")
-                            continue
 
-                        # In the scenario where no pserver information is available
-                        # assumption here is that cloud-region does not span across
-                        # multiple complexes
-                        if len(complex_list) > 1:
-                            if not self.match_vserver_attribute(complex_list):
-                                self._log_multiple_item_error(
-                                    name, service_type, related_to, search_key,
-                                    "GENERIC-VNF", vnf)
-                                self.triage_translator.collectDroppedCandiate(candidate['candidate_id'], candidate['location_id'], name, triage_translator_data,
-                                                                             reason="Generic-vnf error")
-                                continue
+                            complex_list.append(rl_data)
 
-                        rl_data = complex_list[0]
-                        complex_link = rl_data.get('link')
-                        complex_id = rl_data.get('d_value')
+                        self.build_complex_info_for_candidate(candidate, vnf, complex_list, service_type, name,
+                                                              triage_translator_data)
 
-                        # Final query for the complex information
-                        if not (complex_link and complex_id):
-                            LOG.debug("{} complex information not "
-                                      "available from A&AI - {}".
-                                      format(name, complex_link))
-                            self.triage_translator.collectDroppedCandiate(candidate['candidate_id'], candidate['location_id'], name, triage_translator_data,
-                                                                         reason="Complex information not available from A&AI")
-                            continue  # move ahead with the next vnf
-                        else:
-                            complex_info = self._get_complex(
-                                complex_link=complex_link,
-                                complex_id=complex_id
-                            )
-                            if not complex_info:
-                                LOG.debug("{} complex information not "
-                                          "available from A&AI - {}".
-                                          format(name, complex_link))
-                                self.triage_translator.collectDroppedCandiate(candidate['candidate_id'], candidate['location_id'], name, triage_translator_data,
-                                                                             reason="Complex information not available from A&AI")
-                                continue  # move ahead with the next vnf
-                            candidate['physical_location_id'] = \
-                                complex_id
-                            candidate['complex_name'] = \
-                                complex_info.get('complex-name')
-                            candidate['latitude'] = \
-                                complex_info.get('latitude')
-                            candidate['longitude'] = \
-                                complex_info.get('longitude')
-                            candidate['state'] = \
-                                complex_info.get('state')
-                            candidate['country'] = \
-                                complex_info.get('country')
-                            candidate['city'] = \
-                                complex_info.get('city')
-                            candidate['region'] = \
-                                complex_info.get('region')
+                        if "complex_name" not in candidate:
+                            continue
 
                         # add specifal parameters for comparsion
                         vnf['global-customer-id'] = customer_id
                         vnf['customer-id'] = customer_id
                         vnf['cloud-region-id'] = cloud_region_id
-                        vnf['physical-location-id'] = complex_id
+                        vnf['physical-location-id'] = candidate.get('physical_location_id')
 
                         if attributes and not self.match_inventory_attributes(attributes, vnf, candidate['candidate_id']):
                             self.triage_translator.collectDroppedCandiate(candidate['candidate_id'], candidate['location_id'], name, triage_translator_data,
@@ -1392,61 +1485,268 @@ class AAI(base.InventoryProviderBase):
 
                         # Pick only candidates not in the excluded list
                         # if excluded candidate list is provided
-                        if excluded_candidates:
-                            has_excluded_candidate = False
-                            for excluded_candidate in excluded_candidates:
-                                if excluded_candidate \
-                                        and excluded_candidate.get('inventory_type') == \
-                                        candidate.get('inventory_type') \
-                                        and excluded_candidate.get('candidate_id') == \
-                                        candidate.get('candidate_id'):
-                                    has_excluded_candidate = True
-                                    break
-
-                            if has_excluded_candidate:
-                                self.triage_translator.collectDroppedCandiate(candidate['candidate_id'], candidate['location_id'], name, triage_translator_data,
-                                                                             reason="excluded candidate")
-                                continue
+                        if excluded_candidates and self.match_candidate_by_list(candidate, excluded_candidates, True,
+                                                                                name, triage_translator_data):
+                            continue
 
                         # Pick only candidates in the required list
                         # if required candidate list is provided
-                        if required_candidates:
-                            has_required_candidate = False
-                            for required_candidate in required_candidates:
-                                if required_candidate \
-                                        and required_candidate.get('inventory_type') \
-                                        == candidate.get('inventory_type') \
-                                        and required_candidate.get('candidate_id') \
-                                        == candidate.get('candidate_id'):
-                                    has_required_candidate = True
-                                    break
-
-                            if not has_required_candidate:
-                                self.triage_translator.collectDroppedCandiate(candidate['candidate_id'], candidate['location_id'], name, triage_translator_data,
-                                                                             reason="has_required_candidate candidate")
-                                continue
+                        if required_candidates and not self.match_candidate_by_list(candidate, required_candidates,
+                                                                                    False, name, triage_translator_data):
+                            continue
 
                         # add the candidate to the demand
                         # Pick only candidates from the restricted_region
                         # or restricted_complex
-                        if self.match_candidate_attribute(
-                                candidate,
-                                "location_id",
-                                restricted_region_id,
-                                name,
-                                inventory_type) or \
-                           self.match_candidate_attribute(
-                               candidate,
-                               "physical_location_id",
-                               restricted_complex_id,
-                               name,
-                               inventory_type):
-                            self.triage_translator.collectDroppedCandiate(candidate['candidate_id'], candidate['location_id'], name, triage_translator_data,
-                                                                         reason="match candidate attribute")
-
+                        if not self.match_region(candidate, restricted_region_id, restricted_complex_id, name,
+                                                 triage_translator_data):
                             continue
                         else:
                             resolved_demands[name].append(candidate)
+                            LOG.debug(">>>>>>> Candidate <<<<<<<")
+                            LOG.debug(json.dumps(candidate, indent=4))
+
+                elif (inventory_type == 'vfmodule') \
+                        and customer_id:
+
+                    # First level query to get the list of generic vnfs
+                    vnf_by_model_invariant = list()
+                    if attributes and model_invariant_id:
+
+                        raw_path = '/network/generic-vnfs/' \
+                                   '?model-invariant-id={}&depth=0'.format(model_invariant_id)
+                        if model_version_id:
+                            raw_path = '/network/generic-vnfs/' \
+                                       '?model-invariant-id={}&model-version-id={}&depth=0'.format(model_invariant_id,
+                                                                                                   model_version_id)
+                        path = self._aai_versioned_path(raw_path)
+                        vnf_by_model_invariant = self.first_level_service_call(path, name, service_type)
+
+                    vnf_by_service_type = list()
+                    if service_type or equipment_role:
+                        path = self._aai_versioned_path(
+                            '/network/generic-vnfs/'
+                            '?equipment-role={}&depth=0'.format(service_type))
+                        vnf_by_service_type = self.first_level_service_call(path, name, service_type)
+
+                    generic_vnf = vnf_by_model_invariant + vnf_by_service_type
+                    vnf_dict = dict()
+
+                    for vnf in generic_vnf:
+                        # if this vnf already appears, skip it
+                        vnf_id = vnf.get('vnf-id')
+                        if vnf_id in vnf_dict:
+                            continue
+
+                        # add vnf (with vnf_id as key) to the dictionary
+                        vnf_dict[vnf_id] = vnf
+
+                        # create a default candidate
+                        candidate = dict()
+                        candidate['inventory_provider'] = 'aai'
+                        candidate['service_resource_id'] = service_resource_id
+                        candidate['inventory_type'] = inventory_type
+                        candidate['candidate_id'] = ''
+                        candidate['location_id'] = ''
+                        candidate['location_type'] = 'att_aic'
+                        candidate['host_id'] = ''
+                        candidate['cost'] = self.conf.data.service_candidate_cost
+                        candidate['cloud_owner'] = ''
+                        candidate['cloud_region_version'] = ''
+                        candidate['vlan_key'] = vlan_key
+                        candidate['port_key'] = port_key
+                        candidate['uniqueness'] = candidate_uniqueness
+
+                        # start populating the candidate
+                        candidate['host_id'] = vnf.get("vnf-name")
+
+                        candidate['nf-name'] = vnf.get("vnf-name")
+                        candidate['nf-id'] = vnf.get("vnf-id")
+                        candidate['nf-type'] = 'vnf'
+                        candidate['vnf-type'] = vnf.get("vnf-type")
+                        candidate['ipv4-oam-address'] = ''
+                        candidate['ipv6-oam-address'] = ''
+
+                        if vnf.get("ipv4-oam-address"):
+                            candidate['ipv4-oam-address'] = vnf.get("ipv4-oam-address")
+                        if vnf.get("ipv6-oam-address"):
+                            candidate['ipv6-oam-address'] = vnf.get("ipv6-oam-address")
+
+                        rl_data = self.resolve_global_customer_id_for_vnf(candidate, vnf, customer_id, service_type,
+                                                                          name, triage_translator_data)
+                        if rl_data is None:
+                            continue
+                        else:
+                            vs_cust_id = rl_data.get('d_value')
+
+                        rl_data = self.resolve_service_instance_id_for_vnf(candidate, vnf, customer_id,
+                                                                           service_type, name, triage_translator_data)
+                        if rl_data is None:
+                            continue
+                        else:
+                            vs_service_instance_id = rl_data.get('d_value')
+
+                        if vs_cust_id and vs_cust_id == customer_id:
+                            candidate['service_instance_id'] = vs_service_instance_id
+                        else:  # vserver is for a different customer
+                            self.triage_translator.collectDroppedCandiate(candidate['candidate_id'],
+                                                                          candidate['location_id'], name,
+                                                                          triage_translator_data,
+                                                                          reason="candidate is for a different customer")
+                            continue
+
+                        vf_modules_list = self.resolve_vf_modules_for_generic_vnf(candidate, vnf, name,
+                                                                                  triage_translator_data)
+                        if vf_modules_list is None:
+                            continue
+
+                        candidate_base = candidate
+                        for vf_module in vf_modules_list:
+                            # for vfmodule demands we allow to have vfmodules from different cloud regions
+                            candidate = copy.deepcopy(candidate_base)
+                            candidate['candidate_id'] = vf_module.get("vf-module-id")
+                            candidate['vf-module-name'] = vf_module.get("vf-module-name")
+                            candidate['vf-module-id'] = vf_module.get("vf-module-id")
+
+                            rl_data = self.resolve_cloud_owner_for_vnf(candidate, vf_module, service_type, name,
+                                                                       triage_translator_data)
+                            if rl_data is None:
+                                continue
+                            else:
+                                cloud_owner = rl_data.get('d_value')
+                            candidate['cloud_owner'] = cloud_owner
+
+                            cloud_region_id_rl_data, cloud_region_version_rl_data = self.resolve_cloud_region_id_and_version_for_vnf(
+                                candidate, vf_module, service_type, name, triage_translator_data)
+
+                            if cloud_region_id_rl_data is None or cloud_region_version_rl_data is None:
+                                continue
+
+                            cloud_region_id = cloud_region_id_rl_data.get('d_value')
+                            cloud_region_version = cloud_region_version_rl_data.get('d_value')
+                            candidate['location_id'] = cloud_region_id
+                            candidate['cloud_region_version'] = cloud_region_version
+
+                            # Added vim-id for short-term workaround
+                            if self.conf.HPA_enabled:
+                                if not cloud_owner:
+                                    continue
+                                candidate['vim-id'] = \
+                                    candidate['cloud_owner'] + '_' + cloud_region_id
+
+                            if self.check_sriov_automation(
+                                    candidate['cloud_region_version'], name,
+                                    candidate['host_id']):
+                                candidate['sriov_automation'] = 'true'
+                            else:
+                                candidate['sriov_automation'] = 'false'
+
+                            # Second level query to get the pserver from vserver
+                            candidate['vservers'] = list()
+                            complex_list = list()
+                            vs_link_list = self.resolve_v_server_links_for_vnf(vf_module)
+
+                            for vs_link in vs_link_list:
+
+                                body = self.resolove_v_server_for_candidate(candidate, vs_link, True, name,
+                                                                            triage_translator_data)
+                                if body is None:
+                                    continue
+
+                                candidate_vserver = dict()
+                                candidate_vserver['vserver-id'] = body.get('vserver-id')
+                                candidate_vserver['vserver-name'] = body.get('vserver-name')
+
+                                rl_data = self.resolve_complex_info_link_for_v_server(candidate, body, cloud_owner,
+                                                                                      cloud_region_id, service_type,
+                                                                                      name, triage_translator_data)
+                                if rl_data is None:
+                                    continue
+
+                                #Interfaces info
+                                if not body.get('l-interfaces') or not body.get('l-interfaces').get('l-interface'):
+                                    self.triage_translator.collectDroppedCandiate(candidate['candidate_id'],
+                                                                                  candidate['location_id'], name,
+                                                                                  triage_translator_data,
+                                                                                  reason="VF-server interfaces error")
+                                    continue
+                                else:
+                                    l_interfaces = body.get('l-interfaces').get('l-interface')
+                                    candidate_vserver['l-interfaces'] = list()
+
+                                    for l_interface in l_interfaces:
+                                        vserver_interface = dict()
+                                        vserver_interface['interface-id'] = l_interface.get('interface-id')
+                                        vserver_interface['interface-name'] = l_interface.get('interface-name')
+                                        vserver_interface['macaddr'] = l_interface.get('macaddr')
+                                        vserver_interface['network-id'] = l_interface.get('network-name')
+                                        vserver_interface['network-name'] = ''
+                                        vserver_interface['ipv4-addresses'] = list()
+                                        vserver_interface['ipv6-addresses'] = list()
+
+                                        if l_interface.get('l3-interface-ipv4-address-list'):
+                                            for ip_address_info in l_interface.get('l3-interface-ipv4-address-list'):
+                                                vserver_interface['ipv4-addresses'].\
+                                                    append(ip_address_info.get('l3-interface-ipv4-address'))
+
+                                        if l_interface.get('l3-interface-ipv6-address-list'):
+                                            for ip_address_info in l_interface.get('l3-interface-ipv6-address-list'):
+                                                vserver_interface['ipv6-addresses'].\
+                                                    append(ip_address_info.get('l3-interface-ipv6-address'))
+
+                                        candidate_vserver['l-interfaces'].append(vserver_interface)
+
+                                complex_list.append(rl_data)
+                                candidate['vservers'].append(candidate_vserver)
+
+                            self.build_complex_info_for_candidate(candidate, vnf, complex_list, service_type, name,
+                                                                  triage_translator_data)
+
+                            if candidate.get("complex_name") is None:
+                                continue
+
+                            ##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
+                            vnf_vf_module_inventory['global-customer-id'] = customer_id
+                            vnf_vf_module_inventory['customer-id'] = customer_id
+                            vnf_vf_module_inventory['cloud-region-id'] = cloud_region_id
+                            vnf_vf_module_inventory['physical-location-id'] = candidate.get('physical_location_id')
+                            vnf_vf_module_inventory['service_instance_id'] = vs_service_instance_id
+
+                            if attributes and not self.match_inventory_attributes(attributes, vnf_vf_module_inventory,
+                                                                                  candidate['candidate_id']):
+                                self.triage_translator.collectDroppedCandiate(candidate['candidate_id'],
+                                                                              candidate['location_id'], name,
+                                                                              triage_translator_data,
+                                                                              reason="attibute check error")
+                                continue
+                            self.assign_candidate_existing_placement(candidate, existing_placement)
+
+                            # Pick only candidates not in the excluded list
+                            # if excluded candidate list is provided
+                            if excluded_candidates and self.match_candidate_by_list(candidate, excluded_candidates, True,
+                                                                                    name, triage_translator_data):
+                                continue
+
+                            # Pick only candidates in the required list
+                            # if required candidate list is provided
+                            if required_candidates and not self.match_candidate_by_list(candidate, required_candidates,
+                                                                                        False, name,
+                                                                                        triage_translator_data):
+                                continue
+
+                            # add the candidate to the demand
+                            # Pick only candidates from the restricted_region
+                            # or restricted_complex
+                            if not self.match_region(candidate, restricted_region_id, restricted_complex_id, name,
+                                                     triage_translator_data):
+                                continue
+                            else:
+                                resolved_demands[name].append(candidate)
+                                LOG.debug(">>>>>>> Candidate <<<<<<<")
+                                LOG.debug(json.dumps(candidate, indent=4))
 
                 elif inventory_type == 'transport' \
                      and customer_id and service_type and \
@@ -1578,6 +1878,49 @@ class AAI(base.InventoryProviderBase):
                               " {}".format(inventory_type))
         return resolved_demands
 
+    def match_region(self, candidate, restricted_region_id, restricted_complex_id, demand_name, triage_translator_data):
+        if self.match_candidate_attribute(
+                candidate,
+                "location_id",
+                restricted_region_id,
+                demand_name,
+                candidate.get('inventory_type')) or \
+                self.match_candidate_attribute(
+                    candidate,
+                    "physical_location_id",
+                    restricted_complex_id,
+                    demand_name,
+                    candidate.get('inventory_type')):
+            self.triage_translator.collectDroppedCandiate(candidate['candidate_id'], candidate['location_id'],
+                                                          demand_name, triage_translator_data,
+                                                          reason="candidate region does not match")
+            return False
+        else:
+            return True
+
+    def match_candidate_by_list(self, candidate, candidates_list, exclude, demand_name, triage_translator_data):
+        has_candidate = False
+        if candidates_list:
+            for list_candidate in candidates_list:
+                if list_candidate \
+                        and list_candidate.get('inventory_type') \
+                        == candidate.get('inventory_type') \
+                        and list_candidate.get('candidate_id') \
+                        == candidate.get('candidate_id'):
+                    has_candidate = True
+                    break
+
+        if not exclude:
+            if not has_candidate:
+                self.triage_translator.collectDroppedCandiate(candidate['candidate_id'], candidate['location_id'],
+                                                              demand_name, triage_translator_data,
+                                                              reason="has_required_candidate candidate")
+        elif has_candidate:
+            self.triage_translator.collectDroppedCandiate(candidate['candidate_id'], candidate['location_id'],
+                                                          demand_name, triage_translator_data,
+                                                          reason="excluded candidate")
+        return has_candidate
+
     def match_hpa(self, candidate, features):
         """Match HPA features requirement with the candidate flavors """
         hpa_provider = hpa_utils.HpaMatchProvider(candidate, features)