From 38185437b8251ecfe5220d3bbc35bfb07b414852 Mon Sep 17 00:00:00 2001 From: Lukasz Rajewski Date: Tue, 2 Apr 2019 12:08:19 +0200 Subject: New Inventory type for TD * New inventory type 'vfmodule' for ditribute traffic and refactored 'service' type that has many functions in common with 'vfmodule' type * Improved optimizer for random pick algorithm when no optimization constraint is specified * Unit tests added * resolve_demands() refactored to make it testable and to remove code repetitions for different inventory types Change-Id: If5c7e7d2d4e7c1b449d0b37e5c33c697aa4e653f Issue-ID: OPTFRA-445 Signed-off-by: Lukasz Rajewski --- conductor/conductor/controller/translator.py | 6 +- .../data/plugins/inventory_provider/aai.py | 1065 +++++++++++++------- conductor/conductor/data/service.py | 5 + .../solver/optimizer/constraints/service.py | 5 + conductor/conductor/solver/optimizer/optimizer.py | 30 +- .../conductor/solver/optimizer/random_pick.py | 14 +- conductor/conductor/solver/service.py | 21 + .../tests/unit/controller/test_translator.py | 93 +- .../tests/unit/data/demands_vfmodule.json | 35 + .../inventory_provider/bad_generic_vnf_list.json | 127 +++ .../inventory_provider/service_candidates.json | 28 + .../inventory_provider/service_demand_list.json | 15 + .../data/plugins/inventory_provider/test_aai.py | 330 +++++- .../inventory_provider/vfmodule_candidates.json | 73 ++ .../inventory_provider/vfmodule_complex.json | 85 ++ .../inventory_provider/vfmodule_demand_list.json | 20 + .../plugins/inventory_provider/vfmodule_list.json | 41 + .../inventory_provider/vfmodule_region.json | 24 + .../vfmodule_service_generic_vnf_list.json | 79 ++ .../inventory_provider/vfmodule_vserver.json | 157 +++ .../conductor/tests/unit/data/test_service.py | 44 +- 21 files changed, 1918 insertions(+), 379 deletions(-) create mode 100644 conductor/conductor/tests/unit/data/demands_vfmodule.json create mode 100644 conductor/conductor/tests/unit/data/plugins/inventory_provider/bad_generic_vnf_list.json create mode 100644 conductor/conductor/tests/unit/data/plugins/inventory_provider/service_candidates.json create mode 100644 conductor/conductor/tests/unit/data/plugins/inventory_provider/service_demand_list.json create mode 100644 conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_candidates.json create mode 100644 conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_complex.json create mode 100644 conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_demand_list.json create mode 100644 conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_list.json create mode 100644 conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_region.json create mode 100644 conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_service_generic_vnf_list.json create mode 100644 conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_vserver.json (limited to 'conductor') diff --git a/conductor/conductor/controller/translator.py b/conductor/conductor/controller/translator.py index a13c273..85f63af 100644 --- a/conductor/conductor/controller/translator.py +++ b/conductor/conductor/controller/translator.py @@ -43,7 +43,7 @@ CONF = cfg.CONF VERSIONS = ["2016-11-01", "2017-10-10", "2018-02-01"] LOCATION_KEYS = ['latitude', 'longitude', 'host_name', 'clli_code'] INVENTORY_PROVIDERS = ['aai'] -INVENTORY_TYPES = ['cloud', 'service', 'transport'] +INVENTORY_TYPES = ['cloud', 'service', 'transport', 'vfmodule'] DEFAULT_INVENTORY_PROVIDER = INVENTORY_PROVIDERS[0] CANDIDATE_KEYS = ['candidate_id', 'cost', 'inventory_type', 'location_id', 'location_type'] @@ -445,9 +445,9 @@ class Translator(object): "demand {}".format(inventory_type, name) ) - # For service inventories, customer_id and + # For service and vfmodule inventories, customer_id and # service_type MUST be specified - if inventory_type == 'service': + if inventory_type == 'service' or inventory_type == 'vfmodule': attributes = requirement.get('attributes') if attributes: diff --git a/conductor/conductor/data/plugins/inventory_provider/aai.py b/conductor/conductor/data/plugins/inventory_provider/aai.py index 31064a6..d6fb724 100644 --- a/conductor/conductor/data/plugins/inventory_provider/aai.py +++ b/conductor/conductor/data/plugins/inventory_provider/aai.py @@ -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) diff --git a/conductor/conductor/data/service.py b/conductor/conductor/data/service.py index 832b4f8..09756d2 100644 --- a/conductor/conductor/data/service.py +++ b/conductor/conductor/data/service.py @@ -240,6 +240,7 @@ class DataEndpoint(object): cloud_requests = value.get("cloud-requests") service_requests = value.get("service-requests") + vfmodule_requests = value.get("vfmodule-requests") for candidate in candidate_list: if candidate.get("inventory_type") == "cloud" and \ @@ -250,6 +251,9 @@ class DataEndpoint(object): (candidate.get(value_attrib) not in service_requests): discard_set.add(candidate.get("candidate_id")) + elif candidate.get("inventory_type") == "vfmodule" and \ + (candidate.get(value_attrib) not in vfmodule_requests): + discard_set.add(candidate.get("candidate_id")) return discard_set @@ -671,6 +675,7 @@ class DataEndpoint(object): clli_code ) else: + results = None # unknown location response LOG.error(_LE("Unknown location type from the input template." "Expected location types are host_name" diff --git a/conductor/conductor/solver/optimizer/constraints/service.py b/conductor/conductor/solver/optimizer/constraints/service.py index ee16482..c2a5206 100644 --- a/conductor/conductor/solver/optimizer/constraints/service.py +++ b/conductor/conductor/solver/optimizer/constraints/service.py @@ -56,6 +56,11 @@ class Service(constraint.Constraint): candidates_to_check.append(candidate) else: select_list.append(candidate) + elif self.inventory_type == "vfmodule": + if candidate["inventory_type"] == "vfmodule": + candidates_to_check.append(candidate) + else: + select_list.append(candidate) # call conductor data with request parameters if len(candidates_to_check) > 0: cei = _request.cei diff --git a/conductor/conductor/solver/optimizer/optimizer.py b/conductor/conductor/solver/optimizer/optimizer.py index a36124c..7909c15 100755 --- a/conductor/conductor/solver/optimizer/optimizer.py +++ b/conductor/conductor/solver/optimizer/optimizer.py @@ -22,7 +22,6 @@ from oslo_log import log import copy import time - from conductor import service # from conductor.solver.optimizer import decision_path as dpath # from conductor.solver.optimizer import best_first @@ -74,7 +73,7 @@ class Optimizer(object): def get_solution(self, num_solutions): - LOG.debug("search start") + LOG.debug("search start for max {} solutions".format(num_solutions)) for rk in self.requests: request = self.requests[rk] LOG.debug("--- request = {}".format(rk)) @@ -88,7 +87,8 @@ class Optimizer(object): LOG.debug("2. search") - while (num_solutions == 'all' or num_solutions > 0): + rand_counter = 10 + while num_solutions == 'all' or num_solutions > 0: LOG.debug("searching for the solution {}".format(len(decision_list) + 1)) @@ -106,19 +106,28 @@ class Optimizer(object): best_path = self.search.search(demand_list, request.objective, request) + LOG.debug("search delay = {} sec".format(time.time() - st)) + + demand_list = copy.deepcopy(_copy_demand_list) + if best_path is not None: self.search.print_decisions(best_path) + rand_counter = 10 + elif not request.objective.goal and rand_counter > 0 and self._has_candidates(request): + # RandomPick gave no candidates after applying constraints. If there are any candidates left + # lets' try again several times until some solution is found. When one of the demands is not unique + # it persists in the list all the time. In order to prevent infinite loop we need to have counter + rand_counter -= 1 + LOG.debug("Incomplete random solution - repeat {}".format(rand_counter)) + continue else: LOG.debug("no solution found") break - LOG.debug("search delay = {} sec".format(time.time() - st)) - # add the current solution to decision_list decision_list.append(best_path.decisions) #remove the candidate with "uniqueness = true" - demand_list = copy.deepcopy(_copy_demand_list) self._remove_unique_candidate(request, best_path, demand_list) if num_solutions != 'all': @@ -126,6 +135,15 @@ class Optimizer(object): self.search.triageSolver.getSolution(decision_list) return decision_list + def _has_candidates(self, request): + for demand_name, demand in request.demands.items(): + LOG.debug("Req Available resources: {} {}".format(demand_name, len(request.demands[demand_name].resources))) + if len(demand.resources) == 0: + LOG.debug("No more candidates for demand {}".format(demand_name)) + return False + + return True + def _remove_unique_candidate(self, _request, current_decision, demand_list): # This method is to remove previous solved/used candidate from consideration diff --git a/conductor/conductor/solver/optimizer/random_pick.py b/conductor/conductor/solver/optimizer/random_pick.py index 79750a6..7fb2ced 100644 --- a/conductor/conductor/solver/optimizer/random_pick.py +++ b/conductor/conductor/solver/optimizer/random_pick.py @@ -45,9 +45,15 @@ class RandomPick(search.Search): _decision_path.current_demand = demand candidate_list = self._solve_constraints(_decision_path, _request) - # random pick one candidate - r_index = randint(0, len(candidate_list) - 1) - best_resource = candidate_list[r_index] - _decision_path.decisions[demand.name] = best_resource + # When you have two demands and for one there are no candidates left after filtering by constraints + # the code tries to randomly choose index randint(0, -1) what raises an exception. None is returned + # in order to prevent that and this case is considered in the Optimizer respectively + if len(candidate_list) > 0: + # random pick one candidate + r_index = randint(0, len(candidate_list) - 1) + best_resource = candidate_list[r_index] + _decision_path.decisions[demand.name] = best_resource + else: + return None return _decision_path diff --git a/conductor/conductor/solver/service.py b/conductor/conductor/solver/service.py index 8647e4b..978f735 100644 --- a/conductor/conductor/solver/service.py +++ b/conductor/conductor/solver/service.py @@ -509,6 +509,27 @@ class SolverService(cotyledon.Service): if resource.get('port_key'): rec["attributes"]['port_key'] = resource.get('port_key') + if rec["candidate"]["inventory_type"] == "vfmodule": + rec["attributes"]["host_id"] = resource.get("host_id") + rec["attributes"]["service_instance_id"] = resource.get("service_instance_id") + rec["candidate"]["host_id"] = resource.get("host_id") + + if resource.get('vlan_key'): + rec["attributes"]['vlan_key'] = resource.get('vlan_key') + if resource.get('port_key'): + rec["attributes"]['port_key'] = resource.get('port_key') + + vf_module_data = rec["attributes"] + vf_module_data['nf-name'] = resource.get("nf-name") + vf_module_data['nf-id'] = resource.get("nf-id") + vf_module_data['nf-type'] = resource.get("nf-type") + vf_module_data['vnf-type'] = resource.get("vnf-type") + vf_module_data['vf-module-id'] = resource.get("vf-module-id") + vf_module_data['vf-module-name'] = resource.get("vf-module-name") + vf_module_data['ipv4-oam-address'] = resource.get("ipv4-oam-address") + vf_module_data['ipv6-oam-address'] = resource.get("ipv6-oam-address") + vf_module_data['vservers'] = resource.get("vservers") + elif rec["candidate"]["inventory_type"] == "cloud": if resource.get("all_directives") and resource.get("flavor_map"): rec["attributes"]["directives"] = \ diff --git a/conductor/conductor/tests/unit/controller/test_translator.py b/conductor/conductor/tests/unit/controller/test_translator.py index 9682c7d..2eea9b5 100644 --- a/conductor/conductor/tests/unit/controller/test_translator.py +++ b/conductor/conductor/tests/unit/controller/test_translator.py @@ -165,9 +165,100 @@ class TestNoExceptionTranslator(unittest.TestCase): self.assertEquals(self.Translator.parse_demands(demands), rtn) + @patch('conductor.common.music.messaging.component.RPCClient.call') + def test_parse_demands_inventory_type_vfmodule(self, mock_call): + TraigeTranslator.thefinalCallTrans = mock.MagicMock(return_value=None) + demands = { + "vFW-SINK": [{ + "service_resource_id": "vFW-SINK-XX", + "inventory_provider": "aai", + "inventory_type": "vfmodule", + "vlan_key": "vlan_key", + "port_key": "vlan_port", + "excluded_candidates": [{ + "candidate_id": ["e765d576-8755-4145-8536-0bb6d9b1dc9a"], + "inventory_type": "vfmodule" + }], + "attributes": { + "prov-status": "ACTIVE", + "global-customer-id": "Demonstration", + "model-version-id": "763731df-84fd-494b-b824-01fc59a5ff2d", + "model-invariant-id": "e7227847-dea6-4374-abca-4561b070fe7d", + "orchestration-status": ["active"], + "cloud-region-id": { + "get_param": "chosen_region" + }, + "service_instance_id": { + "get_param": "service_id" + } + }, + "service_type": "vFW-SINK-XX" + }] + } + self.Translator._plan_id = "" + self.Translator._plan_name = "" + mock_call.return_value = {'resolved_demands': { + "vFW-SINK": [{ + "service_resource_id": "vFW-SINK-XX", + "inventory_provider": "aai", + "inventory_type": "vfmodule", + "vlan_key": "vlan_key", + "port_key": "vlan_port", + "excluded_candidates": [{ + "candidate_id": ["e765d576-8755-4145-8536-0bb6d9b1dc9a"], + "inventory_type": "vfmodule" + }], + "attributes": { + "prov-status": "ACTIVE", + "global-customer-id": "Demonstration", + "model-version-id": "763731df-84fd-494b-b824-01fc59a5ff2d", + "model-invariant-id": "e7227847-dea6-4374-abca-4561b070fe7d", + "orchestration-status": ["active"], + "cloud-region-id": { + "get_param": "chosen_region" + }, + "service_instance_id": { + "get_param": "service_id" + } + }, + "service_type": "vFW-SINK-XX" + }] + }} + rtn = { + "vFW-SINK": { + "candidates": [{ + "excluded_candidates": [{ + "candidate_id": ["e765d576-8755-4145-8536-0bb6d9b1dc9a"], + "inventory_type": "vfmodule" + }], + "port_key": "vlan_port", + "service_resource_id": "vFW-SINK-XX", + "vlan_key": "vlan_key", + "service_type": "vFW-SINK-XX", + "attributes": { + "cloud-region-id": { + "get_param": "chosen_region" + }, + "model-version-id": "763731df-84fd-494b-b824-01fc59a5ff2d", + "service_instance_id": { + "get_param": "service_id" + }, + "orchestration-status": ["active"], + "global-customer-id": "Demonstration", + "prov-status": "ACTIVE", + "model-invariant-id": "e7227847-dea6-4374-abca-4561b070fe7d" + }, + "inventory_provider": "aai", + "inventory_type": "vfmodule" + }] + } + } + + self.assertEquals(self.Translator.parse_demands(demands), rtn) + @patch('conductor.common.music.messaging.component.RPCClient.call') def test_parse_demands_without_candidate(self, mock_call): - TraigeTranslator.thefinalCallTrans = mock.MagicMock(return_value=None) + TraigeTranslator.thefinalCallTrans = mock.MagicMock(return_value=None) demands = { "vGMuxInfra": [{ "inventory_provider": "aai", diff --git a/conductor/conductor/tests/unit/data/demands_vfmodule.json b/conductor/conductor/tests/unit/data/demands_vfmodule.json new file mode 100644 index 0000000..78cb7d4 --- /dev/null +++ b/conductor/conductor/tests/unit/data/demands_vfmodule.json @@ -0,0 +1,35 @@ +{ + "demands": { + "vFW-SINK": [{ + "service_resource_id": "vFW-SINK-XX", + "inventory_provider": "aai", + "inventory_type": "vfmodule", + "vlan_key": "vlan_key", + "port_key": "vlan_port", + "excluded_candidates": [{ + "candidate_id": "e765d576-8755-4145-8536-0bb6d9b1dc9a", + "inventory_type": "vfmodule" + }], + "attributes": { + "prov-status": "ACTIVE", + "global-customer-id": "Demonstration", + "model-version-id": "763731df-84fd-494b-b824-01fc59a5ff2d", + "model-invariant-id": "e7227847-dea6-4374-abca-4561b070fe7d", + "orchestration-status": ["active"], + "cloud-region-id": { + "get_param": "chosen_region" + }, + "service_instance_id": { + "get_param": "service_id" + } + }, + "service_type": "vFW-SINK-XX" + }] + }, + "triage_translator_data": { + "plan_id": "plan_abc", + "plan_name": "plan_name", + "translator_triage": [], + "dropped_candidates": [] + } +} \ No newline at end of file diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/bad_generic_vnf_list.json b/conductor/conductor/tests/unit/data/plugins/inventory_provider/bad_generic_vnf_list.json new file mode 100644 index 0000000..ed86a1b --- /dev/null +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/bad_generic_vnf_list.json @@ -0,0 +1,127 @@ +[{ + "vnf-id": "fcbff633-47cc-4f38-a98d-4ba8285bd8b6", + "vnf-name": "vFW-PKG-MC", + "vnf-type": "5G_EVE_Demo/5G_EVE_PKG 0", + "service-id": "f5728144-f4a2-4bf8-9f0e-4ee924235c42", + "prov-status": "ACTIVE", + "orchestration-status": "Active", + "ipv4-oam-address": "oam_network_zb4J", + "in-maint": false, + "is-closed-loop-disabled": false, + "resource-version": "1554713856131", + "model-invariant-id": "762472ef-5284-4daa-ab32-3e7bee2ec355", + "model-version-id": "e02a7e5c-9d27-4360-ab7c-73bb83b07e3b", + "model-customization-id": "83f1f72e-26ea-4e32-8dae-a7b3a833c7f6", + "nf-type": "", + "nf-function": "", + "nf-role": "", + "nf-naming-code": "", + "relationship-list": { + "relationship": [{ + "related-to": "service-instance", + "relationship-label": "org.onap.relationships.inventory.ComposedOf", + "related-link": "/aai/v14/business/customers/customer/Demonstration/service-subscriptions/service-subscription/vFW/service-instances/service-instance/3e8d118c-10ca-4b4b-b3db-089b5e9e6a1c", + "relationship-data": [{ + "relationship-key": "customer.global-customer-id", + "relationship-value": "Demonstration" + }, + { + "relationship-key": "service-subscription.service-type", + "relationship-value": "vFW" + }, + { + "relationship-key": "service-instance.service-instance-id", + "relationship-value": "3e8d118c-10ca-4b4b-b3db-089b5e9e6a1c" + } + ], + "related-to-property": [{ + "property-key": "service-instance.service-instance-name", + "property-value": "vFW-R1-Tenant1-LR" + }] + }, + { + "related-to": "service-instance", + "relationship-label": "org.onap.relationships.inventory.ComposedOf", + "related-link": "/aai/v14/business/customers/customer/Demonstration/service-subscriptions/service-subscription/vFW/service-instances/service-instance/3e8d118c-10ca-4b4b-b3db-089b5e9e6a1b", + "relationship-data": [{ + "relationship-key": "customer.global-customer-id", + "relationship-value": "Demonstration" + }, + { + "relationship-key": "service-subscription.service-type", + "relationship-value": "vFW" + }, + { + "relationship-key": "service-instance.service-instance-id", + "relationship-value": "3e8d118c-10ca-4b4b-b3db-089b5e9e6a1b" + } + ], + "related-to-property": [{ + "property-key": "service-instance.service-instance-name", + "property-value": "vFW-R1-Tenant1-LK" + }] + }, + { + "related-to": "platform", + "relationship-label": "org.onap.relationships.inventory.Uses", + "related-link": "/aai/v14/business/platforms/platform/Platform-Demonstration", + "relationship-data": [{ + "relationship-key": "platform.platform-name", + "relationship-value": "Platform-Demonstration" + }] + }, + { + "related-to": "vserver", + "relationship-label": "tosca.relationships.HostedOn", + "related-link": "/aai/v14/cloud-infrastructure/cloud-regions/cloud-region/CloudOwner/RegionOne/tenants/tenant/3c6c471ada7747fe8ff7f28e100b61e8/vservers/vserver/00bddefc-126e-4e4f-a18d-99b94d8d9a30", + "relationship-data": [{ + "relationship-key": "cloud-region.cloud-owner", + "relationship-value": "CloudOwner" + }, + { + "relationship-key": "cloud-region.cloud-region-id", + "relationship-value": "RegionOne" + }, + { + "relationship-key": "tenant.tenant-id", + "relationship-value": "3c6c471ada7747fe8ff7f28e100b61e8" + }, + { + "relationship-key": "vserver.vserver-id", + "relationship-value": "00bddefc-126e-4e4f-a18d-99b94d8d9a30" + } + ], + "related-to-property": [{ + "property-key": "vserver.vserver-name", + "property-value": "zdfw1fwl01pgn01" + }] + }, + { + "related-to": "vserver", + "relationship-label": "tosca.relationships.HostedOn", + "related-link": "/aai/v14/cloud-infrastructure/cloud-regions/cloud-region/CloudOwner2/RegionOne2/tenants/tenant/3c6c471ada7747fe8ff7f28e100b61e8/vservers/vserver/00bddefc-126e-4e4f-a18d-99b94d8d9a31", + "relationship-data": [{ + "relationship-key": "cloud-region.cloud-owner", + "relationship-value": "CloudOwner2" + }, + { + "relationship-key": "cloud-region.cloud-region-id", + "relationship-value": "RegionOne2" + }, + { + "relationship-key": "tenant.tenant-id", + "relationship-value": "3c6c471ada7747fe8ff7f28e100b61e8" + }, + { + "relationship-key": "vserver.vserver-id", + "relationship-value": "00bddefc-126e-4e4f-a18d-99b94d8d9a31" + } + ], + "related-to-property": [{ + "property-key": "vserver.vserver-name", + "property-value": "zdfw1fwl01pgn02" + }] + } + ] + } +}] \ No newline at end of file diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/service_candidates.json b/conductor/conductor/tests/unit/data/plugins/inventory_provider/service_candidates.json new file mode 100644 index 0000000..8d39aaa --- /dev/null +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/service_candidates.json @@ -0,0 +1,28 @@ +{ + "vPGN": [{ + "existing_placement": "false", + "service_resource_id": "vPGN-XX", + "inventory_provider": "aai", + "cost": 1.0, + "cloud_owner": "CloudOwner", + "city": "example-city-val-27150", + "uniqueness": "true", + "location_id": "RegionOne", + "inventory_type": "service", + "sriov_automation": "false", + "state": "example-state-val-59487", + "candidate_id": "3e8d118c-10ca-4b4b-b3db-089b5e9e6a1c", + "latitude": "example-latitude-val-89101", + "physical_location_id": "clli1", + "port_key": "vlan_port", + "vlan_key": "vlan_key", + "cloud_region_version": "1", + "host_id": "vFW-PKG-MC", + "complex_name": "clli1", + "location_type": "att_aic", + "country": "example-country-val-94173", + "region": "example-region-val-13893", + "longitude": "32.89948", + "vim-id": "CloudOwner_RegionOne" + }] +} \ No newline at end of file diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/service_demand_list.json b/conductor/conductor/tests/unit/data/plugins/inventory_provider/service_demand_list.json new file mode 100644 index 0000000..fb1059a --- /dev/null +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/service_demand_list.json @@ -0,0 +1,15 @@ +{ + "vPGN": [{ + "service_resource_id": "vPGN-XX", + "inventory_provider": "aai", + "inventory_type": "service", + "port_key": "vlan_port", + "vlan_key": "vlan_key", + "attributes": { + "global-customer-id": "Demonstration", + "model-version-id": "e02a7e5c-9d27-4360-ab7c-73bb83b07e3b", + "model-invariant-id": "762472ef-5284-4daa-ab32-3e7bee2ec355" + }, + "service_type": "vPGN-XX" + }] +} \ No newline at end of file diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai.py b/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai.py index a586bc3..906897c 100644 --- a/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai.py +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai.py @@ -18,6 +18,7 @@ # import json import unittest +import copy import conductor.data.plugins.inventory_provider.aai as aai import mock @@ -103,7 +104,7 @@ class TestAAI(unittest.TestCase): self.assertEqual({'country': u'USA', 'latitude': u'28.543251', 'longitude': u'-81.377112'} , self.aai_ep.resolve_host_location("host_name")) - def test_resolve_demands(self): + def test_resolve_demands_inventory_type_cloud(self): self.aai_ep.conf.HPA_enabled = True TraigeTranslator.getPlanIdNAme = mock.MagicMock(return_value=None) @@ -141,8 +142,22 @@ class TestAAI(unittest.TestCase): self.mock_get_regions = mock.patch.object(AAI, '_get_regions', return_value=regions_response) self.mock_get_regions.start() - self.mock_get_regions = mock.patch.object(AAI, '_request', return_value=req_response) - self.mock_get_regions.start() + regions_list = list() + regions_list.append(regions_response.get('region-name')) + self.mock_resolve_cloud_regions_by_cloud_region_id = mock.patch.object(AAI, + 'resolve_cloud_regions_by_cloud_region_id', + return_value=regions_list) + self.mock_resolve_cloud_regions_by_cloud_region_id.start() + + self.mock_resolove_v_server_for_candidate = mock.patch.object(AAI, 'resolove_v_server_for_candidate', + return_value=demand_service_response) + self.mock_resolove_v_server_for_candidate.start() + + complex_link = {"link": "/aai/v10/complex-id", "d_value": 'test-id'} + self.mock_resolve_complex_info_link_for_v_server = mock.patch.object(AAI, + 'resolve_complex_info_link_for_v_server', + return_value=complex_link) + self.mock_resolve_complex_info_link_for_v_server.start() self.mock_get_complex = mock.patch.object(AAI, '_get_complex', return_value=complex_json) self.mock_get_complex.start() @@ -179,6 +194,154 @@ class TestAAI(unittest.TestCase): self.aai_ep.resolve_demands(demands_list, plan_info=plan_info, triage_translator_data=triage_translator_data)) + def test_resolve_demands_inventory_type_service(self): + self.aai_ep.conf.HPA_enabled = True + TraigeTranslator.getPlanIdNAme = mock.MagicMock(return_value=None) + TraigeTranslator.addDemandsTriageTranslator = mock.MagicMock(return_value=None) + + plan_info = { + 'plan_name': 'name', + 'plan_id': 'id' + } + triage_translator_data = None + + demands_list_file = './conductor/tests/unit/data/plugins/inventory_provider/service_demand_list.json' + demands_list = json.loads(open(demands_list_file).read()) + + generic_vnf_list_file = './conductor/tests/unit/data/plugins/inventory_provider/vfmodule_service_generic_vnf_list.json' + generic_vnf_list = json.loads(open(generic_vnf_list_file).read()) + + v_server_file = './conductor/tests/unit/data/plugins/inventory_provider/vfmodule_vserver.json' + v_server = json.loads(open(v_server_file).read()) + + demand_service_response_file = './conductor/tests/unit/data/plugins/inventory_provider/resolve_demand_service_response.json' + demand_service_response = json.loads(open(demand_service_response_file).read()) + + complex_file = './conductor/tests/unit/data/plugins/inventory_provider/vfmodule_complex.json' + complex_response = json.loads(open(complex_file).read()) + + region_response_file = './conductor/tests/unit/data/plugins/inventory_provider/vfmodule_region.json' + region_response = json.loads(open(region_response_file).read()) + + results_file = './conductor/tests/unit/data/plugins/inventory_provider/service_candidates.json' + results_json = json.loads(open(results_file).read()) + + req_response = mock.MagicMock() + req_response.status_code = 200 + req_response.ok = True + req_response.json.return_value = demand_service_response + + def mock_first_level_service_call_response(path, name, service_type): + if "equipment-role" in path: + return list() + else: + return generic_vnf_list + + self.mock_first_level_service_call = mock.patch.object(AAI, 'first_level_service_call', + side_effect=mock_first_level_service_call_response) + self.mock_first_level_service_call.start() + + regions = list() + regions.append(region_response) + self.mock_resolve_cloud_regions_by_cloud_region_id = mock.patch.object(AAI, + 'resolve_cloud_regions_by_cloud_region_id', + return_value=regions) + self.mock_resolve_cloud_regions_by_cloud_region_id.start() + + self.mock_resolove_v_server_for_candidate = mock.patch.object(AAI, 'resolove_v_server_for_candidate', + return_value=v_server) + self.mock_resolove_v_server_for_candidate.start() + + complex_link = {"link": "/aai/v14/cloud-infrastructure/complexes/complex/clli1", "d_value": 'clli1'} + self.mock_resolve_complex_info_link_for_v_server = mock.patch.object(AAI, + 'resolve_complex_info_link_for_v_server', + return_value=complex_link) + self.mock_resolve_complex_info_link_for_v_server.start() + + self.mock_get_complex = mock.patch.object(AAI, '_get_complex', return_value=complex_response) + self.mock_get_complex.start() + + self.maxDiff = None + self.assertEqual(results_json, self.aai_ep.resolve_demands(demands_list, plan_info=plan_info, + triage_translator_data=triage_translator_data)) + + def test_resolve_demands_inventory_type_vfmodule(self): + self.aai_ep.conf.HPA_enabled = True + TraigeTranslator.getPlanIdNAme = mock.MagicMock(return_value=None) + TraigeTranslator.addDemandsTriageTranslator = mock.MagicMock(return_value=None) + + plan_info = { + 'plan_name': 'name', + 'plan_id': 'id' + } + triage_translator_data = None + + demands_list_file = './conductor/tests/unit/data/plugins/inventory_provider/vfmodule_demand_list.json' + demands_list = json.loads(open(demands_list_file).read()) + + generic_vnf_list_file = './conductor/tests/unit/data/plugins/inventory_provider/vfmodule_service_generic_vnf_list.json' + generic_vnf_list = json.loads(open(generic_vnf_list_file).read()) + + vfmodules_list_file = './conductor/tests/unit/data/plugins/inventory_provider/vfmodule_list.json' + vfmodules_list = json.loads(open(vfmodules_list_file).read()) + + v_server_file = './conductor/tests/unit/data/plugins/inventory_provider/vfmodule_vserver.json' + v_server = json.loads(open(v_server_file).read()) + + demand_service_response_file = './conductor/tests/unit/data/plugins/inventory_provider/resolve_demand_service_response.json' + demand_service_response = json.loads(open(demand_service_response_file).read()) + + complex_file = './conductor/tests/unit/data/plugins/inventory_provider/vfmodule_complex.json' + complex_response = json.loads(open(complex_file).read()) + + region_response_file = './conductor/tests/unit/data/plugins/inventory_provider/vfmodule_region.json' + region_response = json.loads(open(region_response_file).read()) + + results_file = './conductor/tests/unit/data/plugins/inventory_provider/vfmodule_candidates.json' + results_json = json.loads(open(results_file).read()) + + req_response = mock.MagicMock() + req_response.status_code = 200 + req_response.ok = True + req_response.json.return_value = demand_service_response + + def mock_first_level_service_call_response(path, name, service_type): + if "equipment-role" in path: + return list() + else: + return generic_vnf_list + + self.mock_first_level_service_call = mock.patch.object(AAI, 'first_level_service_call', + side_effect=mock_first_level_service_call_response) + self.mock_first_level_service_call.start() + + self.mock_resolve_vf_modules_for_generic_vnf = mock.patch.object(AAI, 'resolve_vf_modules_for_generic_vnf', + return_value=vfmodules_list) + self.mock_resolve_vf_modules_for_generic_vnf.start() + + regions = list() + regions.append(region_response) + self.mock_resolve_cloud_regions_by_cloud_region_id = mock.patch.object(AAI, + 'resolve_cloud_regions_by_cloud_region_id', + return_value=regions) + self.mock_resolve_cloud_regions_by_cloud_region_id.start() + + self.mock_resolove_v_server_for_candidate = mock.patch.object(AAI, 'resolove_v_server_for_candidate', + return_value=v_server) + self.mock_resolove_v_server_for_candidate.start() + + complex_link = {"link": "/aai/v14/cloud-infrastructure/complexes/complex/clli1", "d_value": 'clli1'} + self.mock_resolve_complex_info_link_for_v_server = mock.patch.object(AAI, 'resolve_complex_info_link_for_v_server', + return_value=complex_link) + self.mock_resolve_complex_info_link_for_v_server.start() + + self.mock_get_complex = mock.patch.object(AAI, '_get_complex', return_value=complex_response) + self.mock_get_complex.start() + + self.maxDiff = None + self.assertEqual(results_json, self.aai_ep.resolve_demands(demands_list, plan_info=plan_info, + triage_translator_data=triage_translator_data)) + def test_get_complex(self): complex_json_file = './conductor/tests/unit/data/plugins/inventory_provider/_request_get_complex.json' @@ -312,6 +475,167 @@ class TestAAI(unittest.TestCase): "mock-cloud-region-id") self.assertEqual(2, len(flavors_info['flavor'])) + def test_resolve_complex_info_link_for_v_server(self): + TraigeTranslator.collectDroppedCandiate = mock.MagicMock(return_value=None) + triage_translator_data = None + demand_name = 'vPGN' + service_type = 'vFW' + cloud_owner = 'CloudOwner' + cloud_region_id = 'RegionOne' + v_server_file = './conductor/tests/unit/data/plugins/inventory_provider/vfmodule_vserver.json' + v_server = json.loads(open(v_server_file).read()) + region_response_file = './conductor/tests/unit/data/plugins/inventory_provider/vfmodule_region.json' + region_response = json.loads(open(region_response_file).read()) + + candidate = dict() + candidate['candidate_id'] = 'some_id' + candidate['location_id'] = 'some_location_id' + candidate['inventory_type'] = 'service' + + response = mock.MagicMock() + response.status_code = 200 + response.ok = True + response.json.return_value = region_response + + self.mock_get_request = mock.patch.object(AAI, '_request', return_value=response) + self.mock_get_request.start() + + link_rl_data = self.aai_ep.resolve_complex_info_link_for_v_server(candidate, v_server, None, + cloud_region_id, service_type, + demand_name, triage_translator_data) + self.assertEqual(None, link_rl_data) + + complex_link = {"link": "/aai/v14/cloud-infrastructure/complexes/complex/clli1", "d_value": 'clli1'} + link_rl_data = self.aai_ep.resolve_complex_info_link_for_v_server(candidate, v_server, cloud_owner, + cloud_region_id, service_type, + demand_name, triage_translator_data) + self.assertEqual(complex_link, link_rl_data) + + def test_build_complex_info_for_candidate(self): + TraigeTranslator.collectDroppedCandiate = mock.MagicMock(return_value=None) + triage_translator_data = None + demand_name = 'vPGN' + service_type = 'vFW' + complex_file = './conductor/tests/unit/data/plugins/inventory_provider/vfmodule_complex.json' + complex_response = json.loads(open(complex_file).read()) + + candidate = dict() + candidate['candidate_id'] = 'some_id' + candidate['location_id'] = 'some_location_id' + candidate['inventory_type'] = 'service' + initial_candidate = copy.deepcopy(candidate) + complex_list_empty = dict() + complex_list = list() + complex_list.append({"link": "/aai/v14/cloud-infrastructure/complexes/complex/clli1", "d_value": 'clli1'}) + complex_list.append({"link": "/aai/v14/cloud-infrastructure/complexes/complex/clli2", "d_value": 'clli2'}) + + self.mock_get_complex = mock.patch.object(AAI, '_get_complex', return_value=complex_response) + self.mock_get_complex.start() + + self.aai_ep.build_complex_info_for_candidate(candidate, None, complex_list_empty, service_type, demand_name, + triage_translator_data) + self.assertEqual(initial_candidate, candidate) + self.assertEqual(1, TraigeTranslator.collectDroppedCandiate.call_count) + + self.aai_ep.build_complex_info_for_candidate(candidate, None, complex_list, service_type, demand_name, + triage_translator_data) + self.assertEqual(initial_candidate, candidate) + self.assertEqual(2, TraigeTranslator.collectDroppedCandiate.call_count) + + complex_list.pop() + self.aai_ep.build_complex_info_for_candidate(candidate, None, complex_list, service_type, demand_name, + triage_translator_data) + + self.assertEqual(candidate, {'city': u'example-city-val-27150', 'country': u'example-country-val-94173', + 'region': u'example-region-val-13893', 'inventory_type': 'service', + 'longitude': u'32.89948', 'state': u'example-state-val-59487', + 'physical_location_id': 'clli1', 'latitude': u'example-latitude-val-89101', + 'complex_name': u'clli1', 'location_id': 'some_location_id', + 'candidate_id': 'some_id'}) + self.assertEqual(2, TraigeTranslator.collectDroppedCandiate.call_count) + + def test_resolve_vnf_parameters(self): + TraigeTranslator.collectDroppedCandiate = mock.MagicMock(return_value=None) + triage_translator_data = None + demand_name = 'vPGN' + service_type = 'vFW' + candidate = dict() + candidate['candidate_id'] = 'some_id' + candidate['location_id'] = 'some_location_id' + candidate['inventory_type'] = 'service' + + generic_vnf_list_file = './conductor/tests/unit/data/plugins/inventory_provider/vfmodule_service_generic_vnf_list.json' + good_vnf = json.loads(open(generic_vnf_list_file).read())[0] + bad_generic_vnf_list_file = './conductor/tests/unit/data/plugins/inventory_provider/bad_generic_vnf_list.json' + bad_vnf = json.loads(open(bad_generic_vnf_list_file).read())[0] + region_response_file = './conductor/tests/unit/data/plugins/inventory_provider/vfmodule_region.json' + region_response = json.loads(open(region_response_file).read()) + + self.assertEqual("CloudOwner", + self.aai_ep.resolve_cloud_owner_for_vnf(candidate, good_vnf, service_type, + demand_name, triage_translator_data).get('d_value')) + self.assertIsNone(self.aai_ep.resolve_cloud_owner_for_vnf(candidate, bad_vnf, service_type, + demand_name, triage_translator_data)) + + regions = list() + regions.append(region_response) + self.mock_get_regions = mock.patch.object(AAI, 'resolve_cloud_regions_by_cloud_region_id', + return_value=regions) + self.mock_get_regions.start() + + cloud_region_rl_data, cloud_region_ver_rl_data = self.aai_ep.resolve_cloud_region_id_and_version_for_vnf( + candidate, good_vnf, service_type, demand_name, triage_translator_data) + self.assertEqual("RegionOne", cloud_region_rl_data.get('d_value')) + self.assertEqual("1", cloud_region_ver_rl_data.get('d_value')) + + self.assertEqual((None, None), + self.aai_ep.resolve_cloud_region_id_and_version_for_vnf(candidate, bad_vnf, service_type, + demand_name, triage_translator_data)) + v_server_links = list() + v_server_links.append("/aai/v14/cloud-infrastructure/cloud-regions/cloud-region/CloudOwner/RegionOne/tenants/\ +tenant/3c6c471ada7747fe8ff7f28e100b61e8/vservers/vserver/00bddefc-126e-4e4f-a18d-99b94d8d9a30") + v_server_links.append("/aai/v14/cloud-infrastructure/cloud-regions/cloud-region/CloudOwner2/RegionOne2/tenants/\ +tenant/3c6c471ada7747fe8ff7f28e100b61e8/vservers/vserver/00bddefc-126e-4e4f-a18d-99b94d8d9a31") + self.assertEqual(v_server_links, self.aai_ep.resolve_v_server_links_for_vnf(bad_vnf)) + + customer_id = 'Demonstration' + self.assertEqual(customer_id, + self.aai_ep.resolve_global_customer_id_for_vnf(candidate, good_vnf, customer_id, service_type, + demand_name, + triage_translator_data).get('d_value')) + self.assertEqual("3e8d118c-10ca-4b4b-b3db-089b5e9e6a1c", + self.aai_ep.resolve_service_instance_id_for_vnf(candidate, good_vnf, customer_id, service_type, + demand_name, + triage_translator_data).get('d_value')) + self.assertIsNone(self.aai_ep.resolve_service_instance_id_for_vnf(candidate, bad_vnf, customer_id, service_type, + demand_name, triage_translator_data)) + + def test_match_candidate_by_list(self): + TraigeTranslator.collectDroppedCandiate = mock.MagicMock(return_value=None) + triage_translator_data = None + + candidate = dict() + candidate['candidate_id'] = 'some_id' + candidate['location_id'] = 'some_location_id' + candidate['inventory_type'] = 'service' + + candidate_list_empty = list() + candidate_list = list() + candidate_list.append(candidate) + + self.assertFalse(self.aai_ep.match_candidate_by_list(candidate, candidate_list_empty, True, 'demand', + triage_translator_data)), + self.assertEqual(0, TraigeTranslator.collectDroppedCandiate.call_count) + self.assertTrue(self.aai_ep.match_candidate_by_list(candidate, candidate_list, True, 'demand', + triage_translator_data)) + self.assertEqual(1, TraigeTranslator.collectDroppedCandiate.call_count) + self.assertTrue(self.aai_ep.match_candidate_by_list(candidate, candidate_list, False, 'demand', + triage_translator_data)) + self.assertEqual(1, TraigeTranslator.collectDroppedCandiate.call_count) + self.assertFalse(self.aai_ep.match_candidate_by_list(candidate, candidate_list_empty, False, 'demand', + triage_translator_data)) + self.assertEqual(2, TraigeTranslator.collectDroppedCandiate.call_count) + def test_match_hpa(self): flavor_json_file = \ './conductor/tests/unit/data/plugins/inventory_provider/hpa_flavors.json' diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_candidates.json b/conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_candidates.json new file mode 100644 index 0000000..7343f34 --- /dev/null +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_candidates.json @@ -0,0 +1,73 @@ +{"vPGN": [{ + "existing_placement": "false", + "service_resource_id": "vPGN-XX", + "vf-module-name": "vnf-pkg-r1-t2-mc", + "inventory_provider": "aai", + "cost": 1.0, + "vf-module-id": "d187d743-5932-4fb9-a42d-db0a5be5ba7e", + "nf-id": "fcbff633-47cc-4f38-a98d-4ba8285bd8b6", + "ipv4-oam-address": "oam_network_zb4J", + "location_id": "RegionOne", + "complex_name": "clli1", + "vservers": [{ + "vserver-id": "00bddefc-126e-4e4f-a18d-99b94d8d9a30", + "l-interfaces": [{ + "macaddr": "fa:16:3e:c4:07:7f", + "interface-name": "vnf-pkg-r1-t2-mc-vpg_private_2_port-mf7lu55usq7i", + "ipv4-addresses": [ + "10.100.100.2" + ], + "ipv6-addresses": [], + "interface-id": "4b333af1-90d6-42ae-8389-d440e6ff0e93", + "network-name": "", + "network-id": "59763a33-3296-4dc8-9ee6-2bdcd63322fc" + }, + { + "macaddr": "fa:16:3e:b5:86:38", + "interface-name": "vnf-pkg-r1-t2-mc-vpg_private_1_port-734xxixicw6r", + "ipv4-addresses": [ + "10.0.110.2" + ], + "ipv6-addresses": [], + "interface-id": "85dd57e9-6e3a-48d0-a784-4598d627e798", + "network-name": "", + "network-id": "cdb4bc25-2412-4b77-bbd5-791a02f8776d" + }, + { + "macaddr": "fa:16:3e:ff:d8:6f", + "interface-name": "vnf-pkg-r1-t2-mc-vpg_private_0_port-e5qdm3p5ijhe", + "ipv4-addresses": [ + "192.168.10.200" + ], + "ipv6-addresses": [], + "interface-id": "edaff25a-878e-4706-ad52-4e3d51cf6a82", + "network-name": "", + "network-id": "932ac514-639a-45b2-b1a3-4c5bb708b5c1" + } + ], + "vserver-name": "zdfw1fwl01pgn01" + }], + "city": "example-city-val-27150", + "cloud_owner": "CloudOwner", + "vnf-type": "5G_EVE_Demo/5G_EVE_PKG 0", + "nf-name": "vFW-PKG-MC", + "inventory_type": "vfmodule", + "sriov_automation": "false", + "uniqueness": "false", + "candidate_id": "d187d743-5932-4fb9-a42d-db0a5be5ba7e", + "latitude": "example-latitude-val-89101", + "physical_location_id": "clli1", + "nf-type": "vnf", + "port_key": "vlan_port", + "vlan_key": "vlan_key", + "cloud_region_version": "1", + "host_id": "vFW-PKG-MC", + "ipv6-oam-address": "", + "location_type": "att_aic", + "country": "example-country-val-94173", + "region": "example-region-val-13893", + "service_instance_id": "3e8d118c-10ca-4b4b-b3db-089b5e9e6a1c", + "state": "example-state-val-59487", + "longitude": "32.89948", + "vim-id": "CloudOwner_RegionOne"} +]} \ No newline at end of file diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_complex.json b/conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_complex.json new file mode 100644 index 0000000..bcb1052 --- /dev/null +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_complex.json @@ -0,0 +1,85 @@ +{ + "physical-location-id": "clli1", + "data-center-code": "example-data-center-code-val-5556", + "complex-name": "clli1", + "identity-url": "example-identity-url-val-56898", + "resource-version": "1554189552609", + "physical-location-type": "example-physical-location-type-val-7608", + "street1": "example-street1-val-34205", + "street2": "example-street2-val-99210", + "city": "example-city-val-27150", + "state": "example-state-val-59487", + "postal-code": "68871", + "country": "example-country-val-94173", + "region": "example-region-val-13893", + "latitude": "example-latitude-val-89101", + "longitude": "32.89948", + "elevation": "97.045443", + "lata": "example-lata-val-46073", + "relationship-list": { + "relationship": [ + { + "related-to": "cloud-region", + "relationship-label": "org.onap.relationships.inventory.LocatedIn", + "related-link": "/aai/v14/cloud-infrastructure/cloud-regions/cloud-region/CloudOwner/RegionOne", + "relationship-data": [ + { + "relationship-key": "cloud-region.cloud-owner", + "relationship-value": "CloudOwner" + }, + { + "relationship-key": "cloud-region.cloud-region-id", + "relationship-value": "RegionOne" + } + ], + "related-to-property": [ + { + "property-key": "cloud-region.owner-defined-type", + "property-value": "OwnerType" + } + ] + }, + { + "related-to": "cloud-region", + "relationship-label": "org.onap.relationships.inventory.LocatedIn", + "related-link": "/aai/v14/cloud-infrastructure/cloud-regions/cloud-region/ja/asd", + "relationship-data": [ + { + "relationship-key": "cloud-region.cloud-owner", + "relationship-value": "ja" + }, + { + "relationship-key": "cloud-region.cloud-region-id", + "relationship-value": "asd" + } + ], + "related-to-property": [ + { + "property-key": "cloud-region.owner-defined-type", + "property-value": "asd" + } + ] + }, + { + "related-to": "cloud-region", + "relationship-label": "org.onap.relationships.inventory.LocatedIn", + "related-link": "/aai/v14/cloud-infrastructure/cloud-regions/cloud-region/CloudOwner/RegionTwo", + "relationship-data": [ + { + "relationship-key": "cloud-region.cloud-owner", + "relationship-value": "CloudOwner" + }, + { + "relationship-key": "cloud-region.cloud-region-id", + "relationship-value": "RegionTwo" + } + ], + "related-to-property": [ + { + "property-key": "cloud-region.owner-defined-type" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_demand_list.json b/conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_demand_list.json new file mode 100644 index 0000000..c8ddc9e --- /dev/null +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_demand_list.json @@ -0,0 +1,20 @@ +{ + "vPGN": [{ + "service_resource_id": "vPGN-XX", + "inventory_provider": "aai", + "inventory_type": "vfmodule", + "port_key": "vlan_port", + "vlan_key": "vlan_key", + "unique": "false", + "attributes": { + "prov-status": "ACTIVE", + "global-customer-id": "Demonstration", + "model-version-id": "e02a7e5c-9d27-4360-ab7c-73bb83b07e3b", + "model-invariant-id": "762472ef-5284-4daa-ab32-3e7bee2ec355", + "orchestration-status": ["active"], + "cloud-region-id": "RegionOne", + "service_instance_id": "3e8d118c-10ca-4b4b-b3db-089b5e9e6a1c" + }, + "service_type": "vPGN-XX" + }] +} \ No newline at end of file diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_list.json b/conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_list.json new file mode 100644 index 0000000..a2ca43c --- /dev/null +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_list.json @@ -0,0 +1,41 @@ +[{ + "vf-module-id": "d187d743-5932-4fb9-a42d-db0a5be5ba7e", + "vf-module-name": "vnf-pkg-r1-t2-mc", + "heat-stack-id": "vnf-pkg-r1-t2-mc/215429f4-c2a2-4637-acfc-4d1bc9d72bc9", + "orchestration-status": "active", + "is-base-vf-module": true, + "automated-assignment": false, + "resource-version": "1554713261318", + "model-invariant-id": "d4a27723-b24e-46be-a1d1-633a80eade45", + "model-version-id": "9f6e6550-36e6-40b1-84eb-4c9f7c63d289", + "model-customization-id": "08b1a66a-90a4-45ab-b073-10b9b609e948", + "module-index": 0, + "relationship-list": { + "relationship": [{ + "related-to": "vserver", + "relationship-label": "org.onap.relationships.inventory.Uses", + "related-link": "/aai/v14/cloud-infrastructure/cloud-regions/cloud-region/CloudOwner/RegionOne/tenants/tenant/3c6c471ada7747fe8ff7f28e100b61e8/vservers/vserver/00bddefc-126e-4e4f-a18d-99b94d8d9a30", + "relationship-data": [{ + "relationship-key": "cloud-region.cloud-owner", + "relationship-value": "CloudOwner" + }, + { + "relationship-key": "cloud-region.cloud-region-id", + "relationship-value": "RegionOne" + }, + { + "relationship-key": "tenant.tenant-id", + "relationship-value": "3c6c471ada7747fe8ff7f28e100b61e8" + }, + { + "relationship-key": "vserver.vserver-id", + "relationship-value": "00bddefc-126e-4e4f-a18d-99b94d8d9a30" + } + ], + "related-to-property": [{ + "property-key": "vserver.vserver-name", + "property-value": "zdfw1fwl01pgn01" + }] + }] + } +}] \ No newline at end of file diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_region.json b/conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_region.json new file mode 100644 index 0000000..4dd1cf8 --- /dev/null +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_region.json @@ -0,0 +1,24 @@ +{ + "cloud-owner": "CloudOwner", + "cloud-region-id": "RegionOne", + "cloud-type": "SharedNode", + "owner-defined-type": "OwnerType", + "cloud-region-version": "v1", + "cloud-zone": "CloudZone", + "resource-version": "1554189551045", + "relationship-list": { + "relationship": [ + { + "related-to": "complex", + "relationship-label": "org.onap.relationships.inventory.LocatedIn", + "related-link": "/aai/v14/cloud-infrastructure/complexes/complex/clli1", + "relationship-data": [ + { + "relationship-key": "complex.physical-location-id", + "relationship-value": "clli1" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_service_generic_vnf_list.json b/conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_service_generic_vnf_list.json new file mode 100644 index 0000000..cf61e4b --- /dev/null +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_service_generic_vnf_list.json @@ -0,0 +1,79 @@ +[{ + "vnf-id": "fcbff633-47cc-4f38-a98d-4ba8285bd8b6", + "vnf-name": "vFW-PKG-MC", + "vnf-type": "5G_EVE_Demo/5G_EVE_PKG 0", + "service-id": "f5728144-f4a2-4bf8-9f0e-4ee924235c42", + "prov-status": "ACTIVE", + "orchestration-status": "Active", + "ipv4-oam-address": "oam_network_zb4J", + "in-maint": false, + "is-closed-loop-disabled": false, + "resource-version": "1554713856131", + "model-invariant-id": "762472ef-5284-4daa-ab32-3e7bee2ec355", + "model-version-id": "e02a7e5c-9d27-4360-ab7c-73bb83b07e3b", + "model-customization-id": "83f1f72e-26ea-4e32-8dae-a7b3a833c7f6", + "nf-type": "", + "nf-function": "", + "nf-role": "", + "nf-naming-code": "", + "relationship-list": { + "relationship": [{ + "related-to": "service-instance", + "relationship-label": "org.onap.relationships.inventory.ComposedOf", + "related-link": "/aai/v14/business/customers/customer/Demonstration/service-subscriptions/service-subscription/vFW/service-instances/service-instance/3e8d118c-10ca-4b4b-b3db-089b5e9e6a1c", + "relationship-data": [{ + "relationship-key": "customer.global-customer-id", + "relationship-value": "Demonstration" + }, + { + "relationship-key": "service-subscription.service-type", + "relationship-value": "vFW" + }, + { + "relationship-key": "service-instance.service-instance-id", + "relationship-value": "3e8d118c-10ca-4b4b-b3db-089b5e9e6a1c" + } + ], + "related-to-property": [{ + "property-key": "service-instance.service-instance-name", + "property-value": "vFW-R1-Tenant1-LR" + }] + }, + { + "related-to": "platform", + "relationship-label": "org.onap.relationships.inventory.Uses", + "related-link": "/aai/v14/business/platforms/platform/Platform-Demonstration", + "relationship-data": [{ + "relationship-key": "platform.platform-name", + "relationship-value": "Platform-Demonstration" + }] + }, + { + "related-to": "vserver", + "relationship-label": "tosca.relationships.HostedOn", + "related-link": "/aai/v14/cloud-infrastructure/cloud-regions/cloud-region/CloudOwner/RegionOne/tenants/tenant/3c6c471ada7747fe8ff7f28e100b61e8/vservers/vserver/00bddefc-126e-4e4f-a18d-99b94d8d9a30", + "relationship-data": [{ + "relationship-key": "cloud-region.cloud-owner", + "relationship-value": "CloudOwner" + }, + { + "relationship-key": "cloud-region.cloud-region-id", + "relationship-value": "RegionOne" + }, + { + "relationship-key": "tenant.tenant-id", + "relationship-value": "3c6c471ada7747fe8ff7f28e100b61e8" + }, + { + "relationship-key": "vserver.vserver-id", + "relationship-value": "00bddefc-126e-4e4f-a18d-99b94d8d9a30" + } + ], + "related-to-property": [{ + "property-key": "vserver.vserver-name", + "property-value": "zdfw1fwl01pgn01" + }] + } + ] + } +}] \ No newline at end of file diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_vserver.json b/conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_vserver.json new file mode 100644 index 0000000..9672734 --- /dev/null +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_vserver.json @@ -0,0 +1,157 @@ +{ + "vserver-id": "00bddefc-126e-4e4f-a18d-99b94d8d9a30", + "vserver-name": "zdfw1fwl01pgn01", + "vserver-name2": "zdfw1fwl01pgn01", + "prov-status": "ACTIVE", + "vserver-selflink": "http://192.168.186.11:8774/v2.1/3c6c471ada7747fe8ff7f28e100b61e8/servers/00bddefc-126e-4e4f-a18d-99b94d8d9a30", + "in-maint": false, + "is-closed-loop-disabled": false, + "resource-version": "1554713860350", + "relationship-list": { + "relationship": [ + { + "related-to": "generic-vnf", + "relationship-label": "tosca.relationships.HostedOn", + "related-link": "/aai/v14/network/generic-vnfs/generic-vnf/fcbff633-47cc-4f38-a98d-4ba8285bd8b6", + "relationship-data": [ + { + "relationship-key": "generic-vnf.vnf-id", + "relationship-value": "fcbff633-47cc-4f38-a98d-4ba8285bd8b6" + } + ], + "related-to-property": [ + { + "property-key": "generic-vnf.vnf-name", + "property-value": "vFW-PKG-MC" + } + ] + }, + { + "related-to": "vf-module", + "relationship-label": "org.onap.relationships.inventory.Uses", + "related-link": "/aai/v14/network/generic-vnfs/generic-vnf/fcbff633-47cc-4f38-a98d-4ba8285bd8b6/vf-modules/vf-module/d187d743-5932-4fb9-a42d-db0a5be5ba7e", + "relationship-data": [ + { + "relationship-key": "generic-vnf.vnf-id", + "relationship-value": "fcbff633-47cc-4f38-a98d-4ba8285bd8b6" + }, + { + "relationship-key": "vf-module.vf-module-id", + "relationship-value": "d187d743-5932-4fb9-a42d-db0a5be5ba7e" + } + ] + }, + { + "related-to": "flavor", + "relationship-label": "org.onap.relationships.inventory.Uses", + "related-link": "/aai/v14/cloud-infrastructure/cloud-regions/cloud-region/CloudOwner/RegionOne/flavors/flavor/3", + "relationship-data": [ + { + "relationship-key": "cloud-region.cloud-owner", + "relationship-value": "CloudOwner" + }, + { + "relationship-key": "cloud-region.cloud-region-id", + "relationship-value": "RegionOne" + }, + { + "relationship-key": "flavor.flavor-id", + "relationship-value": "3" + } + ], + "related-to-property": [ + { + "property-key": "flavor.flavor-name", + "property-value": "m1.medium" + } + ] + }, + { + "related-to": "image", + "relationship-label": "org.onap.relationships.inventory.Uses", + "related-link": "/aai/v14/cloud-infrastructure/cloud-regions/cloud-region/CloudOwner/RegionOne/images/image/de9c10fb-21c9-4b7d-b0a0-f3f9fec722f4", + "relationship-data": [ + { + "relationship-key": "cloud-region.cloud-owner", + "relationship-value": "CloudOwner" + }, + { + "relationship-key": "cloud-region.cloud-region-id", + "relationship-value": "RegionOne" + }, + { + "relationship-key": "image.image-id", + "relationship-value": "de9c10fb-21c9-4b7d-b0a0-f3f9fec722f4" + } + ], + "related-to-property": [ + { + "property-key": "image.image-name", + "property-value": "unknown" + } + ] + } + ] + }, + "l-interfaces": { + "l-interface": [ + { + "interface-name": "vnf-pkg-r1-t2-mc-vpg_private_2_port-mf7lu55usq7i", + "interface-id": "4b333af1-90d6-42ae-8389-d440e6ff0e93", + "macaddr": "fa:16:3e:c4:07:7f", + "network-name": "59763a33-3296-4dc8-9ee6-2bdcd63322fc", + "is-port-mirrored": false, + "resource-version": "1554713868970", + "in-maint": false, + "is-ip-unnumbered": false, + "l3-interface-ipv4-address-list": [ + { + "l3-interface-ipv4-address": "10.100.100.2", + "l3-interface-ipv4-prefix-length": 32, + "resource-version": "1554713868979", + "neutron-network-id": "59763a33-3296-4dc8-9ee6-2bdcd63322fc", + "neutron-subnet-id": "1bee4746-1ec1-4a67-995e-f3ac86999bc4" + } + ] + }, + { + "interface-name": "vnf-pkg-r1-t2-mc-vpg_private_1_port-734xxixicw6r", + "interface-id": "85dd57e9-6e3a-48d0-a784-4598d627e798", + "macaddr": "fa:16:3e:b5:86:38", + "network-name": "cdb4bc25-2412-4b77-bbd5-791a02f8776d", + "is-port-mirrored": false, + "resource-version": "1554713868144", + "in-maint": false, + "is-ip-unnumbered": false, + "l3-interface-ipv4-address-list": [ + { + "l3-interface-ipv4-address": "10.0.110.2", + "l3-interface-ipv4-prefix-length": 32, + "resource-version": "1554713868198", + "neutron-network-id": "cdb4bc25-2412-4b77-bbd5-791a02f8776d", + "neutron-subnet-id": "fad946f8-3894-433b-af59-3a81f59da3b0" + } + ] + }, + { + "interface-name": "vnf-pkg-r1-t2-mc-vpg_private_0_port-e5qdm3p5ijhe", + "interface-id": "edaff25a-878e-4706-ad52-4e3d51cf6a82", + "macaddr": "fa:16:3e:ff:d8:6f", + "network-name": "932ac514-639a-45b2-b1a3-4c5bb708b5c1", + "is-port-mirrored": false, + "resource-version": "1554713860611", + "in-maint": false, + "is-ip-unnumbered": false, + "l3-interface-ipv4-address-list": [ + { + "l3-interface-ipv4-address": "192.168.10.200", + "l3-interface-ipv4-prefix-length": 32, + "resource-version": "1554713862583", + "neutron-network-id": "932ac514-639a-45b2-b1a3-4c5bb708b5c1", + "neutron-subnet-id": "6f61389c-b0a8-4140-8a37-4a095966cde5" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/conductor/conductor/tests/unit/data/test_service.py b/conductor/conductor/tests/unit/data/test_service.py index c9104c4..3f010db 100644 --- a/conductor/conductor/tests/unit/data/test_service.py +++ b/conductor/conductor/tests/unit/data/test_service.py @@ -202,7 +202,7 @@ class TestDataEndpoint(unittest.TestCase): @mock.patch.object(service.LOG, 'info') @mock.patch.object(log_util, 'getTransactionId') @mock.patch.object(stevedore.ExtensionManager, 'map_method') - def test_reslove_demands(self, ext_mock, logutil_mock, info_mock, + def test_resolve_demands(self, ext_mock, logutil_mock, info_mock, debug_mock, error_mock): self.maxDiff = None @@ -232,6 +232,48 @@ class TestDataEndpoint(unittest.TestCase): self.assertEqual(expected_response, self.data_ep.resolve_demands(ctxt, req_json)) + @mock.patch.object(service.LOG, 'error') + @mock.patch.object(service.LOG, 'debug') + @mock.patch.object(service.LOG, 'info') + @mock.patch.object(log_util, 'getTransactionId') + @mock.patch.object(stevedore.ExtensionManager, 'map_method') + def test_resolve_vfmodule_demands(self, ext_mock, logutil_mock, info_mock, + debug_mock, + error_mock): + self.maxDiff = None + req_json_file = './conductor/tests/unit/data/demands_vfmodule.json' + req_json = yaml.safe_load(open(req_json_file).read()) + ctxt = { + 'plan_id': uuid.uuid4(), + 'keyspace': cfg.CONF.keyspace + } + logutil_mock.return_value = uuid.uuid4() + return_value = req_json['demands']['vFW-SINK'] + ext_mock.return_value = [return_value] + expected_response = \ + {'response': {'trans': {'translator_triage': [ [] ], 'plan_name': 'plan_name', 'plan_id': 'plan_abc'}, + 'resolved_demands': [{'service_resource_id': 'vFW-SINK-XX', 'vlan_key': 'vlan_key', + 'inventory_provider': 'aai', 'inventory_type': 'vfmodule', + 'excluded_candidates': [ + {'candidate_id': 'e765d576-8755-4145-8536-0bb6d9b1dc9a', + 'inventory_type': 'vfmodule' + }], 'port_key': 'vlan_port', 'service_type': 'vFW-SINK-XX', + 'attributes': {'global-customer-id': 'Demonstration', + 'cloud-region-id': {'get_param': 'chosen_region'}, + 'model-version-id': + '763731df-84fd-494b-b824-01fc59a5ff2d', + 'prov-status': 'ACTIVE', + 'service_instance_id': {'get_param': 'service_id'}, + 'model-invariant-id': + 'e7227847-dea6-4374-abca-4561b070fe7d', + 'orchestration-status': ['active'] + } + }] + }, 'error': False} + + self.assertEqual(expected_response, + self.data_ep.resolve_demands(ctxt, req_json)) + @mock.patch.object(service.LOG, 'error') @mock.patch.object(service.LOG, 'info') @mock.patch.object(stevedore.ExtensionManager, 'names') -- cgit 1.2.3-korg