summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVikas Varma <vikas.varma@att.com>2020-03-25 13:49:53 +0000
committerGerrit Code Review <gerrit@onap.org>2020-03-25 13:49:53 +0000
commit0c9d9098ba21f79fe4e721b38e1e7c311c958964 (patch)
tree86251e73b14af3018deb70072bfa337ccac00c93
parentbb5346f671007485776b1f71ed1e1b337e787603 (diff)
parentfcb37e97e37137d3111924e993e75fdb83c2a0a0 (diff)
Merge "Add functionality to support NSI selection"
-rw-r--r--apps/placement/optimizers/conductor/remote_opt_processor.py4
-rw-r--r--apps/slice_selection/__init__.py0
-rw-r--r--apps/slice_selection/models/api/__init__.py17
-rw-r--r--apps/slice_selection/models/api/nsi_selection_request.py54
-rw-r--r--apps/slice_selection/models/api/nsi_selection_response.py73
-rw-r--r--apps/slice_selection/optimizers/__init__.py17
-rw-r--r--apps/slice_selection/optimizers/conductor/__init__.py0
-rw-r--r--apps/slice_selection/optimizers/conductor/remote_opt_processor.py104
-rw-r--r--apps/slice_selection/optimizers/conductor/response_processor.py123
-rw-r--r--config/common_config.yaml18
-rw-r--r--osdf/adapters/conductor/api_builder.py21
-rw-r--r--osdf/adapters/conductor/conductor.py30
-rwxr-xr-xosdf/adapters/conductor/templates/conductor_interface.json2
-rw-r--r--osdf/adapters/conductor/translation.py58
-rwxr-xr-xosdfapp.py12
-rw-r--r--test/apps/slice_selection/conductor_error_response.json18
-rw-r--r--test/apps/slice_selection/new_solution_conductor_response.json87
-rw-r--r--test/apps/slice_selection/new_solution_nsi_response.json53
-rw-r--r--test/apps/slice_selection/nsi_error_response.json6
-rw-r--r--test/apps/slice_selection/nsi_request.json30
-rw-r--r--test/apps/slice_selection/shared_solution_conductor_response.json87
-rw-r--r--test/apps/slice_selection/shared_solution_nsi_response.json16
-rw-r--r--test/apps/slice_selection/slice_policies.txt4
-rw-r--r--test/apps/slice_selection/test_remote_opt_processor.py102
-rw-r--r--test/conductor/test_conductor_calls.py4
-rw-r--r--test/policy-local-files/subscriber_policy_URLLC_1.json34
-rw-r--r--test/policy-local-files/thresholdPolicy_URLLC_Core_1_latency.json32
-rw-r--r--test/policy-local-files/thresholdPolicy_URLLC_Core_1_reliability.json32
-rw-r--r--test/policy-local-files/vnfPolicy_URLLC_Core_1.json37
-rw-r--r--test/test_ConductorApiBuilder.py4
30 files changed, 1028 insertions, 51 deletions
diff --git a/apps/placement/optimizers/conductor/remote_opt_processor.py b/apps/placement/optimizers/conductor/remote_opt_processor.py
index 0b5cb16..3d7a287 100644
--- a/apps/placement/optimizers/conductor/remote_opt_processor.py
+++ b/apps/placement/optimizers/conductor/remote_opt_processor.py
@@ -134,8 +134,8 @@ def process_placement_opt(request_json, policies, osdf_config):
demands = request_json['placementInfo']['placementDemands']
request_parameters = request_json['placementInfo']['requestParameters']
service_info = request_json['serviceInfo']
- resp = conductor.request(req_info, demands, request_parameters, service_info,
- osdf_config, policies)
+ resp = conductor.request(req_info, demands, request_parameters, service_info, True,
+ osdf_config, policies)
if resp["plans"][0].get("recommendations"):
placement_response = conductor_response_processor(resp, req_id, transaction_id)
else: # "solved" but no solutions found
diff --git a/apps/slice_selection/__init__.py b/apps/slice_selection/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/apps/slice_selection/__init__.py
diff --git a/apps/slice_selection/models/api/__init__.py b/apps/slice_selection/models/api/__init__.py
new file mode 100644
index 0000000..b45f74d
--- /dev/null
+++ b/apps/slice_selection/models/api/__init__.py
@@ -0,0 +1,17 @@
+# -------------------------------------------------------------------------
+# Copyright (C) 2020 Wipro Limited.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# -------------------------------------------------------------------------
+#
diff --git a/apps/slice_selection/models/api/nsi_selection_request.py b/apps/slice_selection/models/api/nsi_selection_request.py
new file mode 100644
index 0000000..b7f3fbd
--- /dev/null
+++ b/apps/slice_selection/models/api/nsi_selection_request.py
@@ -0,0 +1,54 @@
+# -------------------------------------------------------------------------
+# Copyright (C) 2020 Wipro Limited.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# -------------------------------------------------------------------------
+#
+
+from osdf.models.api.common import OSDFModel
+from schematics.types import BaseType, StringType, URLType, IntType, BooleanType
+from schematics.types.compound import ModelType, ListType, DictType
+
+
+class RequestInfo(OSDFModel):
+ """Info for northbound request from client such as SO"""
+ transactionId = StringType(required=True)
+ requestId = StringType(required=True)
+ callbackUrl = URLType(required=True)
+ callbackHeader = DictType(BaseType)
+ sourceId = StringType(required=True)
+ timeout = IntType()
+
+
+class NSTInfo(OSDFModel):
+ """Preferred candidate for a resource (sent as part of a request from client)"""
+ modelInvariantId = StringType(required=True)
+ modelVersionId = StringType(required=True)
+ modelName = StringType()
+ modelType = StringType()
+ modelVersion = StringType()
+ modelCustomizationName = StringType()
+
+
+class ServiceInfo(OSDFModel):
+ serviceInstanceId = StringType(required=True)
+ serviceName = StringType(required=True)
+
+
+class NSISelectionAPI(OSDFModel):
+ """Request for nsi selection (specific to optimization and additional metadata"""
+ requestInfo = ModelType(RequestInfo, required=True)
+ NSTInfoList = ListType(ModelType(NSTInfo), required=True)
+ serviceInfo = ModelType(ServiceInfo, required=True)
+ serviceProfile = DictType(BaseType, required=True)
diff --git a/apps/slice_selection/models/api/nsi_selection_response.py b/apps/slice_selection/models/api/nsi_selection_response.py
new file mode 100644
index 0000000..9547200
--- /dev/null
+++ b/apps/slice_selection/models/api/nsi_selection_response.py
@@ -0,0 +1,73 @@
+# -------------------------------------------------------------------------
+# Copyright (C) 2020 Wipro Limited.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# -------------------------------------------------------------------------
+#
+
+from osdf.models.api.common import OSDFModel
+from schematics.types import BaseType, StringType
+from schematics.types.compound import ModelType, ListType, DictType
+
+
+# TODO: update osdf.models
+class SharedNSISolution(OSDFModel):
+ invariantUUID = StringType(required=True)
+ UUID = StringType(required=True)
+ NSIName = StringType(required=True)
+ NSIId = StringType(required=True)
+ matchLevel = StringType(required=True)
+
+
+class NSSTInfo(OSDFModel):
+ invariantUUID = StringType(required=True)
+ UUID = StringType(required=True)
+ NSSTName = StringType(required=True)
+
+
+class NSSIInfo(OSDFModel):
+ NSSIName = StringType(required=True)
+ NSSIId = StringType(required=True)
+ matchLevel = StringType(required=True)
+
+
+class NSSISolution(OSDFModel):
+ sliceProfile = DictType(BaseType)
+ NSSTInfo = ModelType(NSSTInfo, required=True)
+ NSSISolution = ModelType(NSSIInfo, required=True)
+
+
+class NSTInfo(OSDFModel):
+ invariantUUID = StringType(required=True)
+ UUID = StringType(required=True)
+ NSTName = StringType(required=True)
+
+
+class NewNSISolution(OSDFModel):
+ matchLevel = StringType(required=True)
+ NSTInfo = ModelType(NSTInfo, required=True)
+ NSSISolutions = ListType(ModelType(NSSISolution))
+
+
+class Solution(OSDFModel):
+ sharedNSISolutions = ListType(ModelType(SharedNSISolution))
+ newNSISolutions = ListType(ModelType(NewNSISolution))
+
+
+class NSISelectionResponse(OSDFModel):
+ transactionId = StringType(required=True)
+ requestId = StringType(required=True)
+ requestStatus = StringType(required=True)
+ statusMessage = StringType()
+ solutions = ModelType(Solution, required=True)
diff --git a/apps/slice_selection/optimizers/__init__.py b/apps/slice_selection/optimizers/__init__.py
new file mode 100644
index 0000000..b45f74d
--- /dev/null
+++ b/apps/slice_selection/optimizers/__init__.py
@@ -0,0 +1,17 @@
+# -------------------------------------------------------------------------
+# Copyright (C) 2020 Wipro Limited.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# -------------------------------------------------------------------------
+#
diff --git a/apps/slice_selection/optimizers/conductor/__init__.py b/apps/slice_selection/optimizers/conductor/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/apps/slice_selection/optimizers/conductor/__init__.py
diff --git a/apps/slice_selection/optimizers/conductor/remote_opt_processor.py b/apps/slice_selection/optimizers/conductor/remote_opt_processor.py
new file mode 100644
index 0000000..a44fc4e
--- /dev/null
+++ b/apps/slice_selection/optimizers/conductor/remote_opt_processor.py
@@ -0,0 +1,104 @@
+# -------------------------------------------------------------------------
+# Copyright (C) 2020 Wipro Limited.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# -------------------------------------------------------------------------
+#
+
+"""
+Module for processing slice selection request
+"""
+
+import json
+import traceback
+from requests import RequestException
+
+from apps.slice_selection.optimizers.conductor.response_processor \
+ import conductor_response_processor, conductor_error_response_processor
+from osdf.adapters.conductor import conductor
+from osdf.adapters.policy.interface import get_policies
+from osdf.adapters.policy.utils import group_policies_gen
+from osdf.logging.osdf_logging import error_log, debug_log
+from osdf.utils.mdc_utils import mdc_from_json
+
+
+def process_nsi_selection_opt(request_json, osdf_config):
+ """Process the nsi selection request from API layer
+ :param request_json: api request
+ :param policies: flattened policies corresponding to this request
+ :param osdf_config: configuration specific to OSDF app
+ :return: response as a dictionary
+ """
+ req_info = request_json['requestInfo']
+ try:
+ mdc_from_json(request_json)
+
+ overall_recommendations = dict()
+ nst_info_map = dict()
+ for nst_info in request_json["NSTInfoList"]:
+ nst_name = nst_info["modelName"]
+ nst_info_map["nst_name"] = {"NSTName": nst_name,
+ "UUID": nst_info["modelVersionId"],
+ "invariantUUID": nst_info["modelInvariantId"]}
+
+ policy_request_json = request_json.copy()
+ policy_request_json['serviceInfo']['serviceName'] = nst_name
+
+ policies = get_policies(policy_request_json, "slice_selection")
+
+ demands = get_slice_demands(nst_name, policies, osdf_config.core)
+
+ request_parameters = {}
+ service_info = {}
+ req_info['numSolutions'] = 'all'
+ resp = conductor.request(req_info, demands, request_parameters, service_info, False,
+ osdf_config, policies)
+ debug_log.debug("Response from conductor {}".format(str(resp)))
+ overall_recommendations[nst_name] = resp["plans"][0].get("recommendations")
+
+ return conductor_response_processor(overall_recommendations, nst_info_map, req_info)
+
+ except Exception as ex:
+ error_log.error("Error for {} {}".format(req_info.get('requestId'),
+ traceback.format_exc()))
+ if isinstance(ex, RequestException):
+ try:
+ error_message = json.loads(ex.response)['plans'][0]['message']
+ except Exception:
+ error_message = "Problem connecting to conductor"
+ else:
+ error_message = str(ex)
+ return conductor_error_response_processor(req_info, error_message)
+
+
+def get_slice_demands(model_name, policies, config):
+ """
+ :param model_name: model name of the slice
+ :param policies: flattened polcies corresponding to the request
+ :param config: configuration specific to OSDF app
+ :return: list of demands for the request
+ """
+ group_policies = group_policies_gen(policies, config)
+ subscriber_policy_list = group_policies["onap.policies.optimization.SubscriberPolicy"]
+ slice_demands = list()
+ for subscriber_policy in subscriber_policy_list:
+ policy_properties = subscriber_policy[list(subscriber_policy.keys())[0]]['properties']
+ if model_name in policy_properties["services"]:
+ subnet_attributes = policy_properties["properties"]["subscriberRole"][0]
+ for subnet in policy_properties["properties"]["subscriberName"]:
+ slice_demand = dict()
+ slice_demand["resourceModuleName"] = subnet
+ slice_demand['resourceModelInfo'] = subnet_attributes[subnet]
+ slice_demands.append(slice_demand)
+ return slice_demands
diff --git a/apps/slice_selection/optimizers/conductor/response_processor.py b/apps/slice_selection/optimizers/conductor/response_processor.py
new file mode 100644
index 0000000..5b7be01
--- /dev/null
+++ b/apps/slice_selection/optimizers/conductor/response_processor.py
@@ -0,0 +1,123 @@
+# -------------------------------------------------------------------------
+# Copyright (C) 2020 Wipro Limited.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# -------------------------------------------------------------------------
+#
+
+"""
+Module for processing response from conductor for slice selection
+"""
+
+from osdf.logging.osdf_logging import debug_log
+
+
+SLICE_PROFILE_FIELDS = ["latency", "max_number_of_ues", "coverage_area_ta_list",
+ "ue_mobility_level", "resource_sharing_level", "exp_data_rate_ul",
+ "exp_data_rate_dl", "area_traffic_cap_ul", "area_traffic_cap_dl",
+ "activity_factor", "e2e_latency", "jitter", "survival_time",
+ "exp_data_rate", "payload_size", "traffic_density", "conn_density",
+ "reliability", "service_area_dimension", "cs_availability"]
+
+
+def conductor_response_processor(overall_recommendations, nst_info_map, request_info):
+ """Process conductor response to form the response for the API request
+ :param overall_recommendations: recommendations from conductor
+ :param nst_info_map: NST info from the request
+ :param request_info: request info
+ :return: response json as a dictionary
+ """
+ shared_nsi_solutions = list()
+ new_nsi_solutions = list()
+
+ for nst_name, recommendations in overall_recommendations.items():
+ for recommendation in recommendations:
+ nsi_set = set(values['candidate']['nsi_name'] for key, values in recommendation.items())
+ if len(nsi_set) == 1:
+ nsi = nsi_set.pop()
+ debug_log.debug("The NSSIs in the solution belongs to the same NSI {}".format(nsi))
+ shared_nsi_solution = dict()
+ shared_nsi_solution["NSIName"] = nsi
+ shared_nsi_solutions.append(shared_nsi_solution)
+ else:
+ nssi_solutions = get_nssi_solutions(recommendation)
+ new_nsi_solution = dict()
+ new_nsi_solution['matchLevel'] = ""
+ new_nsi_solution['NSTInfo'] = nst_info_map.get(nst_name)
+ new_nsi_solution['NSSISolutions'] = nssi_solutions
+ new_nsi_solutions.append(new_nsi_solution)
+
+ solutions = dict()
+ solutions['sharedNSISolutions'] = shared_nsi_solutions
+ solutions['newNSISolutions'] = new_nsi_solutions
+ return get_nsi_selection_response(request_info, solutions)
+
+
+def conductor_error_response_processor(request_info, error_message):
+ """Form response message from the error message
+ :param request_info: request info
+ :param error_message: error message while processing the request
+ :return: response json as dictionary
+ """
+ return {'requestId': request_info['requestId'],
+ 'transactionId': request_info['transactionId'],
+ 'requestStatus': 'error',
+ 'statusMessage': error_message}
+
+
+def get_nssi_solutions(recommendation):
+ """Get nssi solutions from recommendation
+ :param recommendation: recommendation from conductor
+ :return: new nssi solutions list
+ """
+ nssi_solutions = list()
+
+ for nsst_name, nsst_rec in recommendation.items():
+ candidate = nsst_rec['candidate']
+ nssi_info, slice_profile = get_solution_from_candidate(candidate)
+ nsst_info = {"NSSTName": nsst_name}
+ nssi_solution = {"sliceProfile": slice_profile,
+ "NSSTInfo": nsst_info,
+ "NSSISolution": nssi_info}
+ nssi_solutions.append(nssi_solution)
+ return nssi_solutions
+
+
+def get_solution_from_candidate(candidate):
+ """Get nssi info from candidate
+ :param candidate: Candidate from the recommendation
+ :return: nssi_info and slice profile derived from candidate
+ """
+ slice_profile = dict()
+ nssi_info = {"NSSIName": candidate['instance_name'],
+ "NSSIId": candidate['candidate_id']}
+
+ for field in SLICE_PROFILE_FIELDS:
+ if candidate[field]:
+ slice_profile[field] = candidate[field]
+
+ return nssi_info, slice_profile
+
+
+def get_nsi_selection_response(request_info, solutions):
+ """Get NSI selection response from final solution
+ :param request_info: request info
+ :param solutions: final solutions
+ :return: NSI selection response to send back as dictionary
+ """
+ return {'requestId': request_info['requestId'],
+ 'transactionId': request_info['transactionId'],
+ 'requestStatus': 'completed',
+ 'statusMessage': '',
+ 'solutions': solutions}
diff --git a/config/common_config.yaml b/config/common_config.yaml
index d9ecc18..04a5594 100644
--- a/config/common_config.yaml
+++ b/config/common_config.yaml
@@ -11,6 +11,7 @@ osdf_temp: # special configuration required for "workarounds" or testing
local_policies:
global_disabled: True
local_placement_policies_enabled: True
+ local_slice_selection_policies_enabled: True
placement_policy_dir_vcpe: "./test/policy-local-files/"
placement_policy_files_vcpe: # workaroud for policy platform glitches (or "work-arounds" for other components)
- Affinity_vCPE_1.json
@@ -39,6 +40,14 @@ osdf_temp: # special configuration required for "workarounds" or testing
- vnfPolicy_vPGN_TD.json
- affinity_vFW_TD.json
- QueryPolicy_vFW_TD.json
+
+ slice_selection_policy_dir_urllc_1: "./test/policy-local-files/"
+ slice_selection_policy_files_urllc_1:
+ - vnfPolicy_URLLC_Core_1.json
+ - thresholdPolicy_URLLC_Core_1_reliability.json
+ - thresholdPolicy_URLLC_Core_1_latency.json
+ - subscriber_policy_URLLC_1.json
+
service_info:
vCPE:
vcpeHostName: requestParameters.vcpeHostName
@@ -68,6 +77,15 @@ policy_info:
service_name:
- properties.services
+ slice_selection:
+ policy_fetch: by_scope
+ policy_scope:
+ -
+ scope:
+ - OSDF_FRANKFURT
+ service:
+ - get_param: service_name
+
placement:
policy_fetch: by_scope
policy_scope:
diff --git a/osdf/adapters/conductor/api_builder.py b/osdf/adapters/conductor/api_builder.py
index 17057d8..c99c5eb 100644
--- a/osdf/adapters/conductor/api_builder.py
+++ b/osdf/adapters/conductor/api_builder.py
@@ -33,7 +33,8 @@ def _build_parameters(group_policies, service_info, request_parameters):
:param request_parameters: request parameters
:return:
"""
- initial_params = tr.get_opt_query_data(request_parameters, group_policies['onap.policies.optimization.QueryPolicy'])
+ initial_params = tr.get_opt_query_data(request_parameters,
+ group_policies['onap.policies.optimization.QueryPolicy'])
params = dict()
params.update({"REQUIRED_MEM": initial_params.pop("requiredMemory", "")})
params.update({"REQUIRED_DISK": initial_params.pop("requiredDisk", "")})
@@ -49,16 +50,19 @@ def _build_parameters(group_policies, service_info, request_parameters):
return params
-def conductor_api_builder(req_info, demands, request_parameters, service_info, flat_policies: list, local_config,
+def conductor_api_builder(req_info, demands, request_parameters, service_info,
+ location_enabled, flat_policies: list, local_config,
template="osdf/adapters/conductor/templates/conductor_interface.json"):
"""Build an OSDF southbound API call for HAS-Conductor/Placement optimization
:param req_info: parameter data received from a client
:param demands: list of demands
:param request_parameters: request parameters
:param service_info: service info object
+ :param location_enabled: boolean to check location to be sent in the request
:param flat_policies: policy data received from the policy platform (flat policies)
:param template: template to generate southbound API call to conductor
- :param local_config: local configuration file with pointers for the service specific information
+ :param local_config: local configuration file with pointers for
+ the service specific information
:return: json to be sent to Conductor/placement optimization
"""
@@ -88,10 +92,14 @@ def conductor_api_builder(req_info, demands, request_parameters, service_info, f
demand_name_list, gp['onap.policies.optimization.Vim_fit'])
hpa_policy_list = tr.gen_hpa_policy(
demand_name_list, gp['onap.policies.optimization.HpaPolicy'])
+ threshold_policy_list = tr.gen_threshold_policy(demand_name_list,
+ gp['onap.policies.optimization.'
+ 'ThresholdPolicy'])
req_params_dict = _build_parameters(gp, service_info, request_parameters)
- conductor_policies = [attribute_policy_list, distance_to_location_policy_list, inventory_policy_list,
- resource_instance_policy_list, resource_region_policy_list, zone_policy_list,
- reservation_policy_list, capacity_policy_list, hpa_policy_list]
+ conductor_policies = [attribute_policy_list, distance_to_location_policy_list,
+ inventory_policy_list, resource_instance_policy_list,
+ resource_region_policy_list, zone_policy_list, reservation_policy_list,
+ capacity_policy_list, hpa_policy_list, threshold_policy_list]
filtered_policies = [x for x in conductor_policies if len(x) > 0]
policy_groups = list_flatten(filtered_policies)
request_type = req_info.get('requestType', None)
@@ -104,6 +112,7 @@ def conductor_api_builder(req_info, demands, request_parameters, service_info, f
timeout=req_info['timeout'],
limit=req_info['numSolutions'],
request_params=req_params_dict,
+ location_enabled=location_enabled,
json=json)
json_payload = json.dumps(json.loads(rendered_req)) # need this because template's JSON is ugly!
return json_payload
diff --git a/osdf/adapters/conductor/conductor.py b/osdf/adapters/conductor/conductor.py
index 00069a4..155d4d5 100644
--- a/osdf/adapters/conductor/conductor.py
+++ b/osdf/adapters/conductor/conductor.py
@@ -28,7 +28,8 @@ from osdf.utils.interfaces import RestClient
from osdf.operation.exceptions import BusinessException
-def request(req_info, demands, request_parameters, service_info, osdf_config, flat_policies):
+def request(req_info, demands, request_parameters, service_info, location_enabled,
+ osdf_config, flat_policies):
config = osdf_config.deployment
local_config = osdf_config.core
uid, passwd = config['conductorUsername'], config['conductorPassword']
@@ -43,13 +44,16 @@ def request(req_info, demands, request_parameters, service_info, osdf_config, fl
if cond_minor_version is not None:
x_minor_version = str(cond_minor_version)
headers.update({'X-MinorVersion': x_minor_version})
- debug_log.debug("Versions set in HTTP header to conductor: X-MinorVersion: {} ".format(x_minor_version))
+ debug_log.debug("Versions set in HTTP header to "
+ "conductor: X-MinorVersion: {} ".format(x_minor_version))
max_retries = config.get('conductorMaxRetries', 30)
ping_wait_time = config.get('conductorPingWaitTime', 60)
- rc = RestClient(userid=uid, passwd=passwd, method="GET", log_func=debug_log.debug, headers=headers)
- conductor_req_json_str = conductor_api_builder(req_info, demands, request_parameters, service_info, flat_policies,
+ rc = RestClient(userid=uid, passwd=passwd, method="GET", log_func=debug_log.debug,
+ headers=headers)
+ conductor_req_json_str = conductor_api_builder(req_info, demands, request_parameters,
+ service_info, location_enabled, flat_policies,
local_config)
conductor_req_json = json.loads(conductor_req_json_str)
@@ -77,14 +81,16 @@ def request(req_info, demands, request_parameters, service_info, osdf_config, fl
"this transaction is timing out".format(max_timeout))
time.sleep(ping_wait_time)
ctr += 1
- debug_log.debug("Attempt number {} url {}; prior status={}".format(ctr, new_url, resp['plans'][0]['status']))
+ debug_log.debug("Attempt number {} url {}; prior status={}"
+ .format(ctr, new_url, resp['plans'][0]['status']))
total_time += ping_wait_time
try:
raw_resp = rc.request(new_url, raw_response=True)
resp = raw_resp.json()
except RequestException as e:
- debug_log.debug("Conductor attempt {} for request_id {} has failed because {}".format(ctr, req_id, str(e)))
+ debug_log.debug("Conductor attempt {} for request_id {} has failed because {}"
+ .format(ctr, req_id, str(e)))
def initial_request_to_conductor(rc, conductor_url, conductor_req_json):
@@ -92,17 +98,21 @@ def initial_request_to_conductor(rc, conductor_url, conductor_req_json):
:param rc: REST client object for calling conductor
:param conductor_url: conductor's base URL to submit a placement request
:param conductor_req_json: request json object to send to Conductor
- :return: URL to check for follow up (similar to redirects); we keep checking these till we get a result/error
+ :return: URL to check for follow up (similar to redirects);
+ we keep checking these till we get a result/error
"""
debug_log.debug("Payload to Conductor: {}".format(json.dumps(conductor_req_json)))
- raw_resp = rc.request(url=conductor_url, raw_response=True, method="POST", json=conductor_req_json)
+ raw_resp = rc.request(url=conductor_url, raw_response=True, method="POST",
+ json=conductor_req_json)
resp = raw_resp.json()
if resp["status"] != "template":
raise RequestException(response=raw_resp, request=raw_resp.request)
time.sleep(10) # 10 seconds wait time to avoid being too quick!
plan_url = resp["links"][0][0]["href"]
- debug_log.debug("Attempting to read the plan from the conductor provided url {}".format(plan_url))
- raw_resp = rc.request(raw_response=True, url=plan_url) # TODO: check why a list of lists for links
+ debug_log.debug("Attempting to read the plan from "
+ "the conductor provided url {}".format(plan_url))
+ raw_resp = rc.request(raw_response=True,
+ url=plan_url) # TODO: check why a list of lists for links
resp = raw_resp.json()
if resp["plans"][0]["status"] in ["error"]:
diff --git a/osdf/adapters/conductor/templates/conductor_interface.json b/osdf/adapters/conductor/templates/conductor_interface.json
index 030d6a0..d4a9a0e 100755
--- a/osdf/adapters/conductor/templates/conductor_interface.json
+++ b/osdf/adapters/conductor/templates/conductor_interface.json
@@ -11,12 +11,14 @@
"{{key}}": {{ json.dumps(value) }}
{% endfor %}
},
+ {% if location_enabled %}
"locations": {
"customer_loc": {
"latitude": { "get_param": "customer_lat" },
"longitude": { "get_param": "customer_long" }
}
},
+ {% endif %}
"demands": {{ json.dumps(demand_list) }},
{% set comma_main = joiner(",") %}
"constraints": {
diff --git a/osdf/adapters/conductor/translation.py b/osdf/adapters/conductor/translation.py
index 12dfc88..002af88 100644
--- a/osdf/adapters/conductor/translation.py
+++ b/osdf/adapters/conductor/translation.py
@@ -26,6 +26,19 @@ from osdf.utils.programming_utils import dot_notation
policy_config_mapping = yaml.safe_load(open('config/has_config.yaml')).get('policy_config_mapping')
+CONSTRAINT_TYPE_MAP = {"onap.policies.optimization.AttributePolicy": "attribute",
+ "onap.policies.optimization.DistancePolicy": "distance_to_location",
+ "onap.policies.optimization.InventoryGroupPolicy": "inventory_group",
+ "onap.policies.optimization.ResourceInstancePolicy": "instance_fit",
+ "onap.policies.optimization.ResourceRegionPolicy": "region_fit",
+ "onap.policies.optimization.AffinityPolicy": "zone",
+ "onap.policies.optimization.InstanceReservationPolicy":
+ "instance_reservation",
+ "onap.policies.optimization.Vim_fit": "vim_fit",
+ "onap.policies.optimization.HpaPolicy": "hpa",
+ "onap.policies.optimization.ThresholdPolicy": "threshold"
+ }
+
def get_opt_query_data(request_parameters, policies):
"""
@@ -44,6 +57,7 @@ def get_opt_query_data(request_parameters, policies):
req_param_dict.update({queryProp['attribute']: attr_val})
return req_param_dict
+
def gen_optimization_policy(vnf_list, optimization_policy):
"""Generate optimization policy details to pass to Conductor
:param vnf_list: List of vnf's to used in placement request
@@ -105,7 +119,7 @@ def gen_policy_instance(vnf_list, resource_policy, match_type="intersection", rt
for policy in resource_policy:
pc = policy[list(policy.keys())[0]]
default, demands = get_matching_vnfs(pc['properties']['resources'], vnf_list, match_type=match_type)
- resource = {pc['properties']['identity']: {'type': map_constraint_type(pc['type']), 'demands': demands}}
+ resource = {pc['properties']['identity']: {'type': CONSTRAINT_TYPE_MAP.get(pc['type']), 'demands': demands}}
if rtype:
resource[pc['properties']['identity']]['properties'] = {'controller': pc[rtype]['controller'],
@@ -115,13 +129,13 @@ def gen_policy_instance(vnf_list, resource_policy, match_type="intersection", rt
if default:
for d in demands:
resource_repeated = True \
- if {pc['properties']['identity']: {'type': map_constraint_type(pc['type']), 'demands': d}} \
+ if {pc['properties']['identity']: {'type': CONSTRAINT_TYPE_MAP.get(pc['type']), 'demands': d}} \
in resource_policy_list else False
if resource_repeated:
continue
else:
resource_policy_list.append(
- {pc['properties']['identity']: {'type': map_constraint_type(pc['type']), 'demands': d }})
+ {pc['properties']['identity']: {'type': CONSTRAINT_TYPE_MAP.get(pc['type']), 'demands': d }})
policy[list(policy.keys())[0]]['properties']['resources'] = d
related_policies.append(policy)
# Need to override the default policies, here delete the outdated policy stored in the db
@@ -216,6 +230,14 @@ def gen_hpa_policy(vnf_list, hpa_policy):
return cur_policies
+def gen_threshold_policy(vnf_list, threshold_policy):
+ cur_policies, related_policies = gen_policy_instance(vnf_list, threshold_policy, rtype=None)
+ for p_new, p_main in zip(cur_policies, related_policies):
+ pmz = p_main[list(p_main.keys())[0]]['properties']['thresholdProperty']
+ p_new[p_main[list(p_main.keys())[0]]['properties']['identity']]['properties'] = pmz
+ return cur_policies
+
+
def get_augmented_policy_attributes(policy_property, demand):
"""Get policy attributes and augment them using policy_config_mapping and demand information"""
attributes = copy.copy(policy_property['attributes'])
@@ -260,13 +282,13 @@ def get_demand_properties(demand, policies):
policy_property['unique'] else {})
prop['filtering_attributes'] = dict()
prop['filtering_attributes'].update({'global-customer-id': policy_property['customerId']}
- if policy_property['customerId'] else {})
+ if 'customerId' in policy_property and policy_property['customerId'] else {})
prop['filtering_attributes'].update({'model-invariant-id': demand['resourceModelInfo']['modelInvariantId']}
if demand['resourceModelInfo']['modelInvariantId'] else {})
prop['filtering_attributes'].update({'model-version-id': demand['resourceModelInfo']['modelVersionId']}
if demand['resourceModelInfo']['modelVersionId'] else {})
prop['filtering_attributes'].update({'equipment-role': policy_property['equipmentRole']}
- if policy_property['equipmentRole'] else {})
+ if 'equipmentRole' in policy_property and policy_property['equipmentRole'] else {})
if policy_property.get('attributes'):
for attr_key, attr_val in policy_property['attributes'].items():
@@ -304,7 +326,8 @@ def update_converted_attribute(attr_key, attr_val, properties, attribute_type):
def gen_demands(demands, vnf_policies):
"""Generate list of demands based on request and VNF policies
:param demands: A List of demands
- :param vnf_policies: Policies associated with demand resources (e.g. from grouped_policies['vnfPolicy'])
+ :param vnf_policies: Policies associated with demand resources
+ (e.g. from grouped_policies['vnfPolicy'])
:return: list of demand parameters to populate the Conductor API call
"""
demand_dictionary = {}
@@ -315,29 +338,6 @@ def gen_demands(demands, vnf_policies):
return demand_dictionary
-def map_constraint_type(policy_type):
- if "onap.policies.optimization.AttributePolicy" == policy_type:
- return "attribute"
- if "onap.policies.optimization.DistancePolicy" == policy_type:
- return "distance_to_location"
- if "onap.policies.optimization.InventoryGroupPolicy" == policy_type:
- return "inventory_group"
- if "onap.policies.optimization.ResourceInstancePolicy" == policy_type:
- return "instance_fit"
- if "onap.policies.optimization.ResourceRegionPolicy" == policy_type:
- return "region_fit"
- if "onap.policies.optimization.AffinityPolicy" == policy_type:
- return "zone"
- if "onap.policies.optimization.InstanceReservationPolicy" == policy_type:
- return "instance_reservation"
- if "onap.policies.optimization.Vim_fit" == policy_type:
- return "vim_fit"
- if "onap.policies.optimization.HpaPolicy" == policy_type:
- return "hpa"
-
- return policy_type
-
-
def gen_cloud_region(property):
prop = {"cloud_region_attributes": dict()}
if 'cloudRegion' in property:
diff --git a/osdfapp.py b/osdfapp.py
index c0a554e..fdc2c1d 100755
--- a/osdfapp.py
+++ b/osdfapp.py
@@ -34,6 +34,8 @@ from apps.pci.optimizers.pci_opt_processor import process_pci_optimation
from apps.placement.models.api.placementRequest import PlacementAPI
from apps.placement.optimizers.conductor.remote_opt_processor import process_placement_opt
from apps.route.optimizers.simple_route_opt import RouteOpt
+from apps.slice_selection.models.api.nsi_selection_request import NSISelectionAPI
+from apps.slice_selection.optimizers.conductor.remote_opt_processor import process_nsi_selection_opt
from osdf.adapters.policy.interface import get_policies
from osdf.adapters.policy.interface import upload_policy_models
from osdf.config.base import osdf_config
@@ -137,5 +139,15 @@ def do_pci_optimization():
request_status="accepted", status_message="")
+@app.route("/api/oof/selection/nsi/v1", methods=["POST"])
+def do_nsi_selection():
+ request_json = request.get_json()
+ req_id = request_json['requestInfo']['requestId']
+ g.request_id = req_id
+ audit_log.info(MH.received_request(request.url, request.remote_addr, json.dumps(request_json)))
+ NSISelectionAPI(request_json).validate()
+ return process_nsi_selection_opt(request_json, osdf_config)
+
+
if __name__ == "__main__":
run_app()
diff --git a/test/apps/slice_selection/conductor_error_response.json b/test/apps/slice_selection/conductor_error_response.json
new file mode 100644
index 0000000..95a9750
--- /dev/null
+++ b/test/apps/slice_selection/conductor_error_response.json
@@ -0,0 +1,18 @@
+{
+ "plans": [
+ {
+ "status": "error",
+ "message": "Some error message",
+ "name": "Plan Name 1",
+ "links": [
+ [
+ {
+ "href": "http://conductor:8091/v1/plans/plan_id",
+ "rel": "self"
+ }
+ ]
+ ],
+ "id": "plan_id"
+ }
+ ]
+}
diff --git a/test/apps/slice_selection/new_solution_conductor_response.json b/test/apps/slice_selection/new_solution_conductor_response.json
new file mode 100644
index 0000000..39fef7b
--- /dev/null
+++ b/test/apps/slice_selection/new_solution_conductor_response.json
@@ -0,0 +1,87 @@
+{
+ "plans":[
+ {
+ "status":"done",
+ "id":"plan_id",
+ "name":"Plan Name 1",
+ "links":[
+ [
+ {
+ "href":"http://conductor:8091/v1/plans/plan_id",
+ "rel":"self"
+ }
+ ]
+ ],
+ "recommendations":[
+ {
+ "URLLC_Core_1":{
+ "inventory_provider":"aai",
+ "candidate":{
+ "exp_data_rate":0,
+ "conn_density":0,
+ "coverage_area_ta_list":"[{\"province\":\"??\",\"city\":\"???\",\"county\":\"???\",\"street\":\"?????\"}]",
+ "activity_factor":0,
+ "cs_availability":null,
+ "candidate_id":"1a636c4d-5e76-427e-bfd6-241a947224b0",
+ "area_traffic_cap_dl":null,
+ "latency":20,
+ "service_area_dimension":null,
+ "domain":"cn",
+ "e2e_latency":0,
+ "area_traffic_cap_ul":null,
+ "inventory_provider":"aai",
+ "exp_data_rate_ul":100,
+ "max_number_of_ues":0,
+ "ue_mobility_level":"stationary",
+ "candidate_type":"nssi",
+ "traffic_density":0,
+ "payload_size":0,
+ "exp_data_rate_dl":100,
+ "jitter":0,
+ "survival_time":0,
+ "resource_sharing_level":"0",
+ "inventory_type":"nssi",
+ "reliability":null,
+ "cost":1.0,
+ "nsi_name":"nsi_test_0211",
+ "instance_name":"nssi_test_0211"
+ }
+ },
+ "URLLC_Ran_1":{
+ "inventory_provider":"aai",
+ "candidate":{
+ "exp_data_rate":0,
+ "conn_density":0,
+ "coverage_area_ta_list":"[{\"province\":\"??\",\"city\":\"???\",\"county\":\"???\",\"street\":\"?????\"}]",
+ "activity_factor":0,
+ "cs_availability":null,
+ "candidate_id":"490c68b0-639c-11ea-bc55-0242ac130003",
+ "area_traffic_cap_dl":null,
+ "latency":15,
+ "service_area_dimension":null,
+ "domain":"cn",
+ "e2e_latency":0,
+ "area_traffic_cap_ul":null,
+ "inventory_provider":"aai",
+ "exp_data_rate_ul":100,
+ "max_number_of_ues":0,
+ "ue_mobility_level":"stationary",
+ "candidate_type":"nssi",
+ "traffic_density":0,
+ "payload_size":0,
+ "exp_data_rate_dl":100,
+ "jitter":0,
+ "survival_time":0,
+ "resource_sharing_level":"0",
+ "inventory_type":"nssi",
+ "reliability":null,
+ "cost":1.0,
+ "nsi_name":"nsi_test_0212",
+ "instance_name":"nssi_test_ran_0211"
+ }
+ }
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/test/apps/slice_selection/new_solution_nsi_response.json b/test/apps/slice_selection/new_solution_nsi_response.json
new file mode 100644
index 0000000..de248c6
--- /dev/null
+++ b/test/apps/slice_selection/new_solution_nsi_response.json
@@ -0,0 +1,53 @@
+{
+ "requestId":"d290f1ee-6c54-4b01-90e6-d701748f0851",
+ "transactionId":"d290f1ee-6c54-4b01-90e6-d701748f0851",
+ "requestStatus":"completed",
+ "statusMessage":"",
+ "solutions":{
+ "sharedNSISolutions":[
+
+ ],
+ "newNSISolutions":[
+ {
+ "matchLevel":"",
+ "NSTInfo":null,
+ "NSSISolutions":[
+ {
+ "sliceProfile":{
+ "latency":20,
+ "coverage_area_ta_list":"[{\"province\":\"??\",\"city\":\"???\",\"county\":\"???\",\"street\":\"?????\"}]",
+ "ue_mobility_level":"stationary",
+ "resource_sharing_level":"0",
+ "exp_data_rate_ul":100,
+ "exp_data_rate_dl":100
+ },
+ "NSSTInfo":{
+ "NSSTName":"URLLC_Core_1"
+ },
+ "NSSISolution":{
+ "NSSIName":"nssi_test_0211",
+ "NSSIId":"1a636c4d-5e76-427e-bfd6-241a947224b0"
+ }
+ },
+ {
+ "sliceProfile":{
+ "latency":15,
+ "coverage_area_ta_list":"[{\"province\":\"??\",\"city\":\"???\",\"county\":\"???\",\"street\":\"?????\"}]",
+ "ue_mobility_level":"stationary",
+ "resource_sharing_level":"0",
+ "exp_data_rate_ul":100,
+ "exp_data_rate_dl":100
+ },
+ "NSSTInfo":{
+ "NSSTName":"URLLC_Ran_1"
+ },
+ "NSSISolution":{
+ "NSSIName":"nssi_test_ran_0211",
+ "NSSIId":"490c68b0-639c-11ea-bc55-0242ac130003"
+ }
+ }
+ ]
+ }
+ ]
+ }
+} \ No newline at end of file
diff --git a/test/apps/slice_selection/nsi_error_response.json b/test/apps/slice_selection/nsi_error_response.json
new file mode 100644
index 0000000..c09bda8
--- /dev/null
+++ b/test/apps/slice_selection/nsi_error_response.json
@@ -0,0 +1,6 @@
+{
+ "requestId":"d290f1ee-6c54-4b01-90e6-d701748f0851",
+ "transactionId":"d290f1ee-6c54-4b01-90e6-d701748f0851",
+ "requestStatus":"error",
+ "statusMessage":"Some error message"
+} \ No newline at end of file
diff --git a/test/apps/slice_selection/nsi_request.json b/test/apps/slice_selection/nsi_request.json
new file mode 100644
index 0000000..69d6e80
--- /dev/null
+++ b/test/apps/slice_selection/nsi_request.json
@@ -0,0 +1,30 @@
+{
+ "serviceProfile": {
+ "latency": 2,
+ "security": "High",
+ "reliability": 99.9999,
+ "trafficDensity": 1,
+ "connDensity": 100000,
+ "expDataRate": 50,
+ "jitter": 1,
+ "survivalTime": 0
+ },
+ "serviceInfo":{
+ "serviceInstanceId": "209fb01e-60ca-4325-b074-c5ad4e0499f8",
+ "serviceName": ""
+ },
+ "requestInfo": {
+ "transactionId": "d290f1ee-6c54-4b01-90e6-d701748f0851",
+ "requestId": "d290f1ee-6c54-4b01-90e6-d701748f0851",
+ "callbackUrl": "http://0.0.0.0:9000/osdfCallback/",
+ "sourceId": "SO",
+ "timeout": 5
+ },
+ "NSTInfoList": [
+ {
+ "modelInvariantId": "fda3c1e8-7653-4acd-80ef-f5755c1d3859",
+ "modelVersionId": "a6906768-1cae-4e78-acd1-d753ac61f3e8",
+ "modelName": "URLLC_1"
+ }
+ ]
+} \ No newline at end of file
diff --git a/test/apps/slice_selection/shared_solution_conductor_response.json b/test/apps/slice_selection/shared_solution_conductor_response.json
new file mode 100644
index 0000000..5bccd44
--- /dev/null
+++ b/test/apps/slice_selection/shared_solution_conductor_response.json
@@ -0,0 +1,87 @@
+{
+ "plans":[
+ {
+ "status":"done",
+ "id":"plan_id",
+ "name":"Plan Name 1",
+ "links":[
+ [
+ {
+ "href":"http://conductor:8091/v1/plans/plan_id",
+ "rel":"self"
+ }
+ ]
+ ],
+ "recommendations":[
+ {
+ "URLLC_Core_1":{
+ "inventory_provider":"aai",
+ "candidate":{
+ "exp_data_rate":0,
+ "conn_density":0,
+ "coverage_area_ta_list":"[{\"province\":\"??\",\"city\":\"???\",\"county\":\"???\",\"street\":\"?????\"}]",
+ "activity_factor":0,
+ "cs_availability":null,
+ "candidate_id":"1a636c4d-5e76-427e-bfd6-241a947224b0",
+ "area_traffic_cap_dl":null,
+ "latency":20,
+ "service_area_dimension":null,
+ "domain":"cn",
+ "e2e_latency":0,
+ "area_traffic_cap_ul":null,
+ "inventory_provider":"aai",
+ "exp_data_rate_ul":100,
+ "max_number_of_ues":0,
+ "ue_mobility_level":"stationary",
+ "candidate_type":"nssi",
+ "traffic_density":0,
+ "payload_size":0,
+ "exp_data_rate_dl":100,
+ "jitter":0,
+ "survival_time":0,
+ "resource_sharing_level":"0",
+ "inventory_type":"nssi",
+ "reliability":null,
+ "cost":1.0,
+ "nsi_name":"nsi_test_0212",
+ "instance_name":"nssi_test_0211"
+ }
+ },
+ "URLLC_Ran_1":{
+ "inventory_provider":"aai",
+ "candidate":{
+ "exp_data_rate":0,
+ "conn_density":0,
+ "coverage_area_ta_list":"[{\"province\":\"??\",\"city\":\"???\",\"county\":\"???\",\"street\":\"?????\"}]",
+ "activity_factor":0,
+ "cs_availability":null,
+ "candidate_id":"490c68b0-639c-11ea-bc55-0242ac130003",
+ "area_traffic_cap_dl":null,
+ "latency":15,
+ "service_area_dimension":null,
+ "domain":"cn",
+ "e2e_latency":0,
+ "area_traffic_cap_ul":null,
+ "inventory_provider":"aai",
+ "exp_data_rate_ul":100,
+ "max_number_of_ues":0,
+ "ue_mobility_level":"stationary",
+ "candidate_type":"nssi",
+ "traffic_density":0,
+ "payload_size":0,
+ "exp_data_rate_dl":100,
+ "jitter":0,
+ "survival_time":0,
+ "resource_sharing_level":"0",
+ "inventory_type":"nssi",
+ "reliability":null,
+ "cost":1.0,
+ "nsi_name":"nsi_test_0212",
+ "instance_name":"nssi_test_ran_0211"
+ }
+ }
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/test/apps/slice_selection/shared_solution_nsi_response.json b/test/apps/slice_selection/shared_solution_nsi_response.json
new file mode 100644
index 0000000..b0c0e2a
--- /dev/null
+++ b/test/apps/slice_selection/shared_solution_nsi_response.json
@@ -0,0 +1,16 @@
+{
+ "requestId":"d290f1ee-6c54-4b01-90e6-d701748f0851",
+ "transactionId":"d290f1ee-6c54-4b01-90e6-d701748f0851",
+ "requestStatus":"completed",
+ "statusMessage":"",
+ "solutions":{
+ "sharedNSISolutions":[
+ {
+ "NSIName":"nsi_test_0212"
+ }
+ ],
+ "newNSISolutions":[
+
+ ]
+ }
+} \ No newline at end of file
diff --git a/test/apps/slice_selection/slice_policies.txt b/test/apps/slice_selection/slice_policies.txt
new file mode 100644
index 0000000..e4ccb65
--- /dev/null
+++ b/test/apps/slice_selection/slice_policies.txt
@@ -0,0 +1,4 @@
+subscriber_policy_URLLC_1.json
+thresholdPolicy_URLLC_Core_1_latency.json
+thresholdPolicy_URLLC_Core_1_reliability.json
+vnfPolicy_URLLC_Core_1.json \ No newline at end of file
diff --git a/test/apps/slice_selection/test_remote_opt_processor.py b/test/apps/slice_selection/test_remote_opt_processor.py
new file mode 100644
index 0000000..d9b4f24
--- /dev/null
+++ b/test/apps/slice_selection/test_remote_opt_processor.py
@@ -0,0 +1,102 @@
+# -------------------------------------------------------------------------
+# Copyright (C) 2020 Wipro Limited.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# -------------------------------------------------------------------------
+#
+
+import json
+import unittest
+from requests import RequestException
+
+from apps.slice_selection.optimizers.conductor.remote_opt_processor import process_nsi_selection_opt
+from osdf.adapters.local_data import local_policies
+from osdf.utils.interfaces import json_from_file, yaml_from_file
+from osdf.utils.programming_utils import DotDict
+import osdf.config.loader as config_loader
+from mock import patch, MagicMock
+import json
+from osdf.logging.osdf_logging import error_log, debug_log
+from osdf.adapters.policy.interface import get_policies
+
+
+class TestRemoteOptProcessor(unittest.TestCase):
+ def setUp(self):
+ self.config_spec = {
+ "deployment": "config/osdf_config.yaml",
+ "core": "config/common_config.yaml"
+ }
+ self.osdf_config = DotDict(config_loader.all_configs(**self.config_spec))
+
+ def tearDown(self):
+ patch.stopall()
+
+ def test_process_nsi_selection_opt(self):
+ main_dir = ""
+ request_file = main_dir + 'test/apps/slice_selection/nsi_request.json'
+ new_solution_response_file = main_dir + 'test/apps/slice_selection/new_solution_nsi_response.json'
+ shared_solution_response_file = main_dir + 'test/apps/slice_selection/shared_solution_nsi_response.json'
+ error_response_file = main_dir + 'test/apps/slice_selection/nsi_error_response.json'
+
+ request_json = json_from_file(request_file)
+ new_solution_response_json = json_from_file(new_solution_response_file)
+ shared_solution_response_json = json_from_file(shared_solution_response_file)
+ error_response_json = json_from_file(error_response_file)
+
+ policies_path = main_dir + 'test/policy-local-files'
+ slice_policies_file = main_dir + 'test/apps/slice_selection/slice_policies.txt'
+
+ valid_policies_files = local_policies.get_policy_names_from_file(slice_policies_file)
+ policies = [json_from_file(policies_path + '/' + name) for name in valid_policies_files]
+ self.patcher_get_policies = patch('osdf.adapters.policy.interface.remote_api',
+ return_value=policies)
+ self.Mock_get_policies = self.patcher_get_policies.start()
+
+ new_solution_conductor_response_file = 'test/apps/slice_selection/new_solution_conductor_response.json'
+ new_solution_conductor_response = json_from_file(new_solution_conductor_response_file)
+ self.patcher_req = patch('osdf.adapters.conductor.conductor.request',
+ return_value=new_solution_conductor_response)
+ self.Mock_req = self.patcher_req.start()
+ self.assertEquals(new_solution_response_json, process_nsi_selection_opt(request_json, self.osdf_config))
+ self.patcher_req.stop()
+
+ shared_solution_conductor_response_file = 'test/apps/slice_selection/shared_solution_conductor_response.json'
+ shared_solution_conductor_response = json_from_file(shared_solution_conductor_response_file)
+ self.patcher_req = patch('osdf.adapters.conductor.conductor.request',
+ return_value=shared_solution_conductor_response)
+ self.Mock_req = self.patcher_req.start()
+ self.assertEquals(shared_solution_response_json,
+ process_nsi_selection_opt(request_json, self.osdf_config))
+ self.patcher_req.stop()
+
+ conductor_error_response_file = 'test/apps/slice_selection/conductor_error_response.json'
+ conductor_error_response = json_from_file(conductor_error_response_file)
+
+ self.patcher_req = patch('osdf.adapters.conductor.conductor.request',
+ side_effect=RequestException(response=json.dumps(conductor_error_response)))
+ self.Mock_req = self.patcher_req.start()
+ self.assertEquals(error_response_json, process_nsi_selection_opt(request_json, self.osdf_config))
+ self.patcher_req.stop()
+
+ self.patcher_req = patch('osdf.adapters.conductor.conductor.request',
+ side_effect=Exception("test_exception"))
+ self.Mock_req = self.patcher_req.start()
+ self.assertEquals('test_exception',
+ process_nsi_selection_opt(request_json, self.osdf_config).get('statusMessage'))
+ self.patcher_req.stop()
+
+
+if __name__ == "__main__":
+ unittest.main()
+
diff --git a/test/conductor/test_conductor_calls.py b/test/conductor/test_conductor_calls.py
index 0042ecb..d342fa5 100644
--- a/test/conductor/test_conductor_calls.py
+++ b/test/conductor/test_conductor_calls.py
@@ -46,7 +46,7 @@ class TestConductorCalls(unittest.TestCase):
demands = req_json['placementInfo']['placementDemands']
request_parameters = req_json['placementInfo']['requestParameters']
service_info = req_json['serviceInfo']
- conductor.request(req_info, demands, request_parameters, service_info, self.osdf_config, policies)
+ conductor.request(req_info, demands, request_parameters, service_info, True, self.osdf_config, policies)
def test_request_vfmod(self):
req_json = json_from_file("./test/placement-tests/request_vfmod.json")
@@ -55,7 +55,7 @@ class TestConductorCalls(unittest.TestCase):
demands = req_json['placementInfo']['placementDemands']
request_parameters = req_json['placementInfo']['requestParameters']
service_info = req_json['serviceInfo']
- conductor.request(req_info, demands, request_parameters, service_info, self.osdf_config, policies)
+ conductor.request(req_info, demands, request_parameters, service_info, True, self.osdf_config, policies)
if __name__ == "__main__":
diff --git a/test/policy-local-files/subscriber_policy_URLLC_1.json b/test/policy-local-files/subscriber_policy_URLLC_1.json
new file mode 100644
index 0000000..ffa4d79
--- /dev/null
+++ b/test/policy-local-files/subscriber_policy_URLLC_1.json
@@ -0,0 +1,34 @@
+{
+ "OSDF_FRANKFURT.SubscriberPolicy_URLLC_1": {
+ "type": "onap.policies.optimization.SubscriberPolicy",
+ "version": "1.0.0",
+ "type_version": "1.0.0",
+ "metadata": {
+ "policy-id": "OSDF_FRANKFURT.SubscriberPolicy_URLLC_1",
+ "policy-version": 1
+ },
+ "properties": {
+ "scope": [
+ "OSDF_FRANKFURT",
+ "URLLC_1"
+ ],
+ "services": [
+ "URLLC_1"
+ ],
+ "identity": "subscriber_URLLC_1",
+ "properties": {
+ "subscriberName": [
+ "URLLC_Core_1"
+ ],
+ "subscriberRole": [
+ {
+ "URLLC_Core_1": {
+ "modelInvariantId": "21d57d4b-52ad-4d3c-a798-248b5bb9124a",
+ "modelVersionId": "bfba363e-e39c-4bd9-a9d5-1371c28f4d22"
+ }
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/test/policy-local-files/thresholdPolicy_URLLC_Core_1_latency.json b/test/policy-local-files/thresholdPolicy_URLLC_Core_1_latency.json
new file mode 100644
index 0000000..35106f6
--- /dev/null
+++ b/test/policy-local-files/thresholdPolicy_URLLC_Core_1_latency.json
@@ -0,0 +1,32 @@
+{
+ "OSDF_FRANKFURT.Threshold_URLLC_Core_1": {
+ "type": "onap.policies.optimization.ThresholdPolicy",
+ "version": "1.0.0",
+ "type_version": "1.0.0",
+ "metadata": {
+ "policy-id": "OSDF_FRANKFURT.Threshold_URLLC_Core_1_latency",
+ "policy-version": 1
+ },
+ "properties": {
+ "scope": [
+ "OSDF_FRANKFURT",
+ "URLLC_1",
+ "URLLC_Core_1"
+ ],
+ "resources": [
+ "URLLC_Core_1"
+ ],
+ "services": [
+ "URLLC_1"
+ ],
+ "identity": "Threshold_URLLC_Core_1_latency",
+ "applicableResources": "any",
+ "thresholdProperty": {
+ "attribute": "latency",
+ "operator": "lte",
+ "threshold": 5,
+ "unit": "ms"
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/test/policy-local-files/thresholdPolicy_URLLC_Core_1_reliability.json b/test/policy-local-files/thresholdPolicy_URLLC_Core_1_reliability.json
new file mode 100644
index 0000000..56089f0
--- /dev/null
+++ b/test/policy-local-files/thresholdPolicy_URLLC_Core_1_reliability.json
@@ -0,0 +1,32 @@
+{
+ "OSDF_FRANKFURT.Threshold_URLLC_Core_1": {
+ "type": "onap.policies.optimization.ThresholdPolicy",
+ "version": "1.0.0",
+ "type_version": "1.0.0",
+ "metadata": {
+ "policy-id": "OSDF_FRANKFURT.Threshold_URLLC_Core_1_reliability",
+ "policy-version": 1
+ },
+ "properties": {
+ "scope": [
+ "OSDF_FRANKFURT",
+ "URLLC_1",
+ "URLLC_Core_1"
+ ],
+ "resources": [
+ "URLLC_Core_1"
+ ],
+ "services": [
+ "URLLC_1"
+ ],
+ "identity": "Threshold_URLLC_Core_1_reliability",
+ "applicableResources": "any",
+ "thresholdProperty": {
+ "attribute":"reliability",
+ "operator":"gte",
+ "threshold":99.999,
+ "unit":""
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/test/policy-local-files/vnfPolicy_URLLC_Core_1.json b/test/policy-local-files/vnfPolicy_URLLC_Core_1.json
new file mode 100644
index 0000000..6582c17
--- /dev/null
+++ b/test/policy-local-files/vnfPolicy_URLLC_Core_1.json
@@ -0,0 +1,37 @@
+{
+ "OSDF_FRANKFURT.vnfPolicy_URLLC_Core_1": {
+ "type": "onap.policies.optimization.VnfPolicy",
+ "version": "1.0.0",
+ "type_version": "1.0.0",
+ "metadata": {
+ "policy-id": "OSDF_FRANKFURT.vnfPolicy_URLLC_Core_1",
+ "policy-version": 1
+ },
+ "properties": {
+ "scope": [
+ "OSDF_FRANKFURT",
+ "URLLC_1",
+ "URLLC_Core_1"
+ ],
+ "resources": [
+ "URLLC_Core_1"
+ ],
+ "services": [
+ "URLLC_1"
+ ],
+ "identity": "vnf_URLLC_Core_1",
+ "applicableResources": "any",
+ "vnfProperties": [
+ {
+ "inventoryProvider": "aai",
+ "inventoryType": "nssi",
+ "region": "RegionOne",
+ "attributes": {
+ "orchestrationStatus": "active",
+ "service-role": "nssi"
+ }
+ }
+ ]
+ }
+ }
+}
diff --git a/test/test_ConductorApiBuilder.py b/test/test_ConductorApiBuilder.py
index 44c14d8..b3dd97c 100644
--- a/test/test_ConductorApiBuilder.py
+++ b/test/test_ConductorApiBuilder.py
@@ -53,7 +53,7 @@ class TestConductorApiBuilder(unittest.TestCase):
demands = request_json['placementInfo']['placementDemands']
request_parameters = request_json['placementInfo']['requestParameters']
service_info = request_json['serviceInfo']
- templ_string = conductor_api_builder(req_info, demands, request_parameters, service_info, policies,
+ templ_string = conductor_api_builder(req_info, demands, request_parameters, service_info, True, policies,
local_config, self.conductor_api_template)
templ_json = json.loads(templ_string)
self.assertEqual(templ_json["name"], "yyy-yyy-yyyy")
@@ -66,7 +66,7 @@ class TestConductorApiBuilder(unittest.TestCase):
demands = request_json['placementInfo']['placementDemands']
request_parameters = request_json['placementInfo']['requestParameters']
service_info = request_json['serviceInfo']
- templ_string = conductor_api_builder(req_info, demands, request_parameters, service_info, policies,
+ templ_string = conductor_api_builder(req_info, demands, request_parameters, service_info, True, policies,
local_config, self.conductor_api_template)
templ_json = json.loads(templ_string)
self.assertEqual(templ_json, self.request_placement_vfmod_json)