summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLukasz Rajewski <lukasz.rajewski@orange.com>2019-04-02 12:08:19 +0200
committerLukasz Rajewski <lukasz.rajewski@orange.com>2019-04-18 16:21:04 +0200
commit38185437b8251ecfe5220d3bbc35bfb07b414852 (patch)
treef33098df4a01adb1c89a0aafb393129f37f6e698
parent9d1da5ec7f01d0a9c1d7344786eb0be8dc24138f (diff)
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 <lukasz.rajewski@orange.com>
-rw-r--r--conductor/conductor/controller/translator.py6
-rw-r--r--conductor/conductor/data/plugins/inventory_provider/aai.py1065
-rw-r--r--conductor/conductor/data/service.py5
-rw-r--r--conductor/conductor/solver/optimizer/constraints/service.py5
-rwxr-xr-xconductor/conductor/solver/optimizer/optimizer.py30
-rw-r--r--conductor/conductor/solver/optimizer/random_pick.py14
-rw-r--r--conductor/conductor/solver/service.py21
-rw-r--r--conductor/conductor/tests/unit/controller/test_translator.py93
-rw-r--r--conductor/conductor/tests/unit/data/demands_vfmodule.json35
-rw-r--r--conductor/conductor/tests/unit/data/plugins/inventory_provider/bad_generic_vnf_list.json127
-rw-r--r--conductor/conductor/tests/unit/data/plugins/inventory_provider/service_candidates.json28
-rw-r--r--conductor/conductor/tests/unit/data/plugins/inventory_provider/service_demand_list.json15
-rw-r--r--conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai.py330
-rw-r--r--conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_candidates.json73
-rw-r--r--conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_complex.json85
-rw-r--r--conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_demand_list.json20
-rw-r--r--conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_list.json41
-rw-r--r--conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_region.json24
-rw-r--r--conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_service_generic_vnf_list.json79
-rw-r--r--conductor/conductor/tests/unit/data/plugins/inventory_provider/vfmodule_vserver.json157
-rw-r--r--conductor/conductor/tests/unit/data/test_service.py44
21 files changed, 1918 insertions, 379 deletions
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
@@ -166,8 +166,99 @@ 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
@@ -233,6 +233,48 @@ class TestDataEndpoint(unittest.TestCase):
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')
@mock.patch.object(stevedore.ExtensionManager, 'map_method')