aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordhebeha <dhebeha.mj71@wipro.com>2020-09-05 20:16:48 +0530
committerVikas Varma <vikas.varma@att.com>2020-09-18 19:34:01 +0000
commitedf98746a52408386efab26143778198b0efd3c5 (patch)
treeadc101beb879a57547cc828283803bfe7c5fd89b
parentf9b3575cae2b521ba8c6b6b30b15c89bd8a1cb48 (diff)
Add support to process NSI selection request
Issue-ID: OPTFRA-802 Signed-off-by: dhebeha <dhebeha.mj71@wipro.com> Signed-off-by: krishnaa96 <krishna.moorthy6@wipro.com> Change-Id: I85d951061abc697714425bd223b89102d4f2ede9
-rw-r--r--apps/placement/optimizers/conductor/remote_opt_processor.py37
-rw-r--r--apps/slice_selection/models/api/nsi_selection_request.py12
-rw-r--r--apps/slice_selection/optimizers/conductor/remote_opt_processor.py176
-rw-r--r--apps/slice_selection/optimizers/conductor/response_processor.py227
-rw-r--r--config/common_config.yaml30
-rw-r--r--config/has_config.yaml3
-rw-r--r--config/slicing_config.yaml82
-rw-r--r--osdf/adapters/conductor/api_builder.py19
-rw-r--r--osdf/adapters/conductor/conductor.py12
-rwxr-xr-xosdf/adapters/conductor/templates/conductor_interface.json2
-rw-r--r--osdf/adapters/conductor/translation.py75
-rw-r--r--osdf/config/base.py5
-rwxr-xr-xosdfapp.py17
-rw-r--r--test/apps/slice_selection/new_solution_conductor_response.json90
-rw-r--r--test/apps/slice_selection/new_solution_nsi_response.json77
-rw-r--r--test/apps/slice_selection/no_recomm_nsi_response.json32
-rw-r--r--test/apps/slice_selection/not_shared_nsi_request.json31
-rw-r--r--test/apps/slice_selection/not_shared_nsi_response.json37
-rw-r--r--test/apps/slice_selection/nsi_error_response.json35
-rw-r--r--test/apps/slice_selection/nsi_selection_request.json71
-rw-r--r--test/apps/slice_selection/nssi_conductor_response.json53
-rw-r--r--test/apps/slice_selection/nssi_error_response.json7
-rw-r--r--test/apps/slice_selection/nssi_selection_request.json7
-rw-r--r--test/apps/slice_selection/shared_solution_conductor_response.json102
-rw-r--r--test/apps/slice_selection/shared_solution_nsi_response.json57
-rw-r--r--test/apps/slice_selection/shared_solution_nssi_response.json15
-rw-r--r--test/apps/slice_selection/slice_policies.txt9
-rw-r--r--test/apps/slice_selection/subnet_policies.txt5
-rw-r--r--test/apps/slice_selection/test_remote_opt_processor.py80
-rw-r--r--test/conductor/test_conductor_calls.py10
-rw-r--r--test/conductor/test_conductor_translation.py23
-rw-r--r--test/policy-local-files/slice-selection-files/opt_policy_nsi_reuse.json38
-rw-r--r--test/policy-local-files/slice-selection-files/opt_policy_nssi.json37
-rw-r--r--test/policy-local-files/slice-selection-files/query_policy_nsi.json44
-rw-r--r--test/policy-local-files/slice-selection-files/query_policy_nssi.json32
-rw-r--r--test/policy-local-files/slice-selection-files/threshold_policy_nsi.json46
-rw-r--r--test/policy-local-files/slice-selection-files/threshold_policy_nssi.json46
-rw-r--r--test/policy-local-files/slice-selection-files/vnf_policy_nsi_non_shared_case.json76
-rw-r--r--test/policy-local-files/slice-selection-files/vnf_policy_nsi_shared_case.json90
-rw-r--r--test/policy-local-files/slice-selection-files/vnf_policy_nssi_shared.json37
-rw-r--r--test/test_ConductorApiBuilder.py12
-rw-r--r--tox.ini7
42 files changed, 1173 insertions, 730 deletions
diff --git a/apps/placement/optimizers/conductor/remote_opt_processor.py b/apps/placement/optimizers/conductor/remote_opt_processor.py
index 3d7a287..2e681be 100644
--- a/apps/placement/optimizers/conductor/remote_opt_processor.py
+++ b/apps/placement/optimizers/conductor/remote_opt_processor.py
@@ -17,23 +17,26 @@
# -------------------------------------------------------------------------
#
-import json
from jinja2 import Template
+import json
from requests import RequestException
-
import traceback
-from osdf.operation.error_handling import build_json_error_body
-from osdf.logging.osdf_logging import metrics_log, MH, error_log, debug_log
-from osdf.adapters.conductor import conductor
+
from apps.license.optimizers.simple_license_allocation import license_optim
+from osdf.adapters.conductor import conductor
+from osdf.logging.osdf_logging import debug_log
+from osdf.logging.osdf_logging import error_log
+from osdf.logging.osdf_logging import metrics_log
+from osdf.logging.osdf_logging import MH
+from osdf.operation.error_handling import build_json_error_body
from osdf.utils.interfaces import get_rest_client
from osdf.utils.mdc_utils import mdc_from_json
def conductor_response_processor(conductor_response, req_id, transaction_id):
"""Build a response object to be sent to client's callback URL from Conductor's response
- This includes Conductor's placement optimization response, and required ASDC license artifacts
+ This includes Conductor's placement optimization response, and required ASDC license artifacts
:param conductor_response: JSON response from Conductor
:param raw_response: Raw HTTP response corresponding to above
:param req_id: Id of a request
@@ -60,13 +63,15 @@ def conductor_response_processor(conductor_response, req_id, transaction_id):
try:
solution['assignmentInfo'].append({"key": name_map.get(key, key), "value": value})
except KeyError:
- debug_log.debug("The key[{}] is not mapped and will not be returned in assignment info".format(key))
+ debug_log.debug("The key[{}] is not mapped and will not be returned in assignment info"
+ .format(key))
for key, value in reco[resource]['attributes'].items():
try:
solution['assignmentInfo'].append({"key": name_map.get(key, key), "value": value})
except KeyError:
- debug_log.debug("The key[{}] is not mapped and will not be returned in assignment info".format(key))
+ debug_log.debug("The key[{}] is not mapped and will not be returned in assignment info"
+ .format(key))
composite_solutions.append(solution)
request_status = "completed" if conductor_response['plans'][0]['status'] == "done" \
@@ -91,8 +96,8 @@ def conductor_response_processor(conductor_response, req_id, transaction_id):
def conductor_no_solution_processor(conductor_response, request_id, transaction_id,
template_placement_response="templates/plc_opt_response.jsont"):
"""Build a response object to be sent to client's callback URL from Conductor's response
- This is for case where no solution is found
+ This is for case where no solution is found
:param conductor_response: JSON response from Conductor
:param raw_response: Raw HTTP response corresponding to above
:param request_id: request Id associated with the client request (same as conductor response's "name")
@@ -108,6 +113,7 @@ def conductor_no_solution_processor(conductor_response, request_id, transaction_
def process_placement_opt(request_json, policies, osdf_config):
"""Perform the work for placement optimization (e.g. call SDC artifact and make conductor request)
+
NOTE: there is scope to make the requests to policy asynchronous to speed up overall performance
:param request_json: json content from original request
:param policies: flattened policies corresponding to this request
@@ -115,7 +121,7 @@ def process_placement_opt(request_json, policies, osdf_config):
:param prov_status: provStatus retrieved from Subscriber policy
:return: None, but make a POST to callback URL
"""
-
+
try:
mdc_from_json(request_json)
rc = get_rest_client(request_json, service="so")
@@ -134,7 +140,11 @@ 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, True,
+ template_fields = {
+ 'location_enabled': True,
+ 'version': '2017-10-10'
+ }
+ resp = conductor.request(req_info, demands, request_parameters, service_info, template_fields,
osdf_config, policies)
if resp["plans"][0].get("recommendations"):
placement_response = conductor_response_processor(resp, req_id, transaction_id)
@@ -162,8 +172,7 @@ def process_placement_opt(request_json, policies, osdf_config):
return
try:
- metrics_log.info(MH.calling_back_with_body(req_id, rc.url,placement_response))
+ metrics_log.info(MH.calling_back_with_body(req_id, rc.url, placement_response))
rc.request(json=placement_response, noresponse=True)
- except RequestException : # can't do much here but log it and move on
+ except RequestException: # can't do much here but log it and move on
error_log.error("Error sending asynchronous notification for {} {}".format(req_id, traceback.format_exc()))
-
diff --git a/apps/slice_selection/models/api/nsi_selection_request.py b/apps/slice_selection/models/api/nsi_selection_request.py
index 943fa56..b395012 100644
--- a/apps/slice_selection/models/api/nsi_selection_request.py
+++ b/apps/slice_selection/models/api/nsi_selection_request.py
@@ -17,8 +17,14 @@
#
from osdf.models.api.common import OSDFModel
-from schematics.types import BaseType, StringType, URLType, IntType, BooleanType
-from schematics.types.compound import ModelType, ListType, DictType
+from schematics.types import BaseType
+from schematics.types import BooleanType
+from schematics.types.compound import DictType
+from schematics.types.compound import ListType
+from schematics.types.compound import ModelType
+from schematics.types import IntType
+from schematics.types import StringType
+from schematics.types import URLType
class RequestInfo(OSDFModel):
@@ -50,7 +56,7 @@ class NSISelectionAPI(OSDFModel):
"""Request for nsi selection (specific to optimization and additional metadata"""
requestInfo = ModelType(RequestInfo, required=True)
NSTInfo = ModelType(NxTInfo, required=True)
- NSSTInfo = ListType(ModelType(NxTInfo), required=True)
+ NSSTInfo = ListType(ModelType(NxTInfo), required=False)
serviceProfile = DictType(BaseType, required=True)
subnetCapabilities = ListType(ModelType(SubnetCapability), required=True)
preferReuse = BooleanType()
diff --git a/apps/slice_selection/optimizers/conductor/remote_opt_processor.py b/apps/slice_selection/optimizers/conductor/remote_opt_processor.py
index 40638fc..c1c6980 100644
--- a/apps/slice_selection/optimizers/conductor/remote_opt_processor.py
+++ b/apps/slice_selection/optimizers/conductor/remote_opt_processor.py
@@ -20,92 +20,106 @@
Module for processing slice selection request
"""
-import json
-import traceback
from requests import RequestException
+from threading import Thread
+import traceback
-from apps.slice_selection.optimizers.conductor.response_processor \
- import conductor_response_processor, conductor_error_response_processor, solution_with_only_slice_profile, get_nsi_selection_response
+from apps.slice_selection.optimizers.conductor.response_processor import ResponseProcessor
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.logging.osdf_logging import debug_log
+from osdf.logging.osdf_logging import error_log
+from osdf.utils.interfaces import get_rest_client
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()
- new_nsi_solutions = list()
- 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"]}
-
- if request_json["serviceProfile"]["resourceSharingLevel"] == "non-shared":
- new_nsi_solution = solution_with_only_slice_profile(request_json['serviceProfile'], nst_info_map.get(nst_name))
- new_nsi_solutions.append(new_nsi_solution)
+class SliceSelectionOptimizer(Thread):
+ def __init__(self, osdf_config, slice_config, request_json, model_type):
+ self.osdf_config = osdf_config
+ self.slice_config = slice_config
+ self.request_json = request_json
+ self.model_type = model_type
+ self.response_processor = ResponseProcessor(request_json['requestInfo'], slice_config)
+
+ def run(self):
+ self.process_slice_selection_opt()
+
+ def process_slice_selection_opt(self):
+ """Process the slice selection request from the API layer"""
+ req_info = self.request_json['requestInfo']
+ rc = get_rest_client(self.request_json, service='so')
+
+ try:
+ if self.model_type == 'NSSI' \
+ and self.request_json['sliceProfile'].get('resourceSharingLevel', "") == 'not-shared':
+ final_response = self.response_processor.get_slice_selection_response([])
+
else:
- 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 = request_json.get('serviceProfile',{})
- service_info = {}
- req_info['numSolutions'] = 'all'
- try:
- resp = conductor.request(req_info, demands, request_parameters, service_info, False,
- osdf_config, policies)
- except RequestException as e:
- resp = e.response.json()
- error = resp['plans'][0]['message']
- error_log.error('Error from conductor {}'.format(error))
- debug_log.debug("Response from conductor {}".format(str(resp)))
- overall_recommendations[nst_name] = resp["plans"][0].get("recommendations")
-
- if request_json["serviceProfile"]["resourceSharingLevel"] == "non-shared":
- solutions = dict()
- solutions['newNSISolutions'] = new_nsi_solutions
- solutions['sharedNSISolutions'] = []
- return get_nsi_selection_response(req_info, solutions)
- else:
- return conductor_response_processor(overall_recommendations, nst_info_map, req_info, request_json["serviceProfile"])
- except Exception as ex:
- error_log.error("Error for {} {}".format(req_info.get('requestId'),
- traceback.format_exc()))
- 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.service.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"]:
- for subnet in policy_properties["properties"]["subscriberName"]:
- slice_demand = dict()
- slice_demand["resourceModuleName"] = subnet
- slice_demand['resourceModelInfo'] = {}
- slice_demands.append(slice_demand)
- return slice_demands
+ final_response = self.do_slice_selection()
+
+ except Exception as ex:
+ error_log.error("Error for {} {}".format(req_info.get('requestId'),
+ traceback.format_exc()))
+ error_message = str(ex)
+ final_response = self.response_processor.process_error_response(error_message)
+
+ try:
+ rc.request(json=final_response, noresponse=True)
+ except RequestException:
+ error_log.error("Error sending asynchronous notification for {} {}".format(req_info['request_id'],
+ traceback.format_exc()))
+
+ def do_slice_selection(self):
+ req_info = self.request_json['requestInfo']
+ app_info = self.slice_config['app_info'][self.model_type]
+ mdc_from_json(self.request_json)
+ requirements = self.request_json.get(app_info['requirements_field'], {})
+ model_info = self.request_json.get(app_info['model_info'])
+ model_name = model_info['name']
+ policies = self.get_app_policies(model_name, app_info['app_name'])
+ request_parameters = self.get_request_parameters(requirements)
+
+ demands = [
+ {
+ "resourceModuleName": model_name,
+ "resourceModelInfo": {}
+ }
+ ]
+
+ try:
+ template_fields = {
+ 'location_enabled': False,
+ 'version': '2020-08-13'
+ }
+ resp = conductor.request(req_info, demands, request_parameters, {}, template_fields,
+ self.osdf_config, policies)
+ except RequestException as e:
+ resp = e.response.json()
+ error = resp['plans'][0]['message']
+ error_log.error('Error from conductor {}'.format(error))
+ return self.response_processor.process_error_response(error)
+
+ debug_log.debug("Response from conductor {}".format(str(resp)))
+ recommendations = resp["plans"][0].get("recommendations")
+ subnets = [subnet['domainType'] for subnet in self.request_json['subnetCapabilities']] \
+ if self.request_json.get('subnetCapabilities') else []
+ return self.response_processor.process_response(recommendations, model_info, subnets)
+
+ def get_request_parameters(self, requirements):
+ camel_to_snake = self.slice_config['attribute_mapping']['camel_to_snake']
+ request_params = {camel_to_snake[key]: value for key, value in requirements.items()}
+ subnet_capabilities = self.request_json.get('subnetCapabilities')
+ if subnet_capabilities:
+ for subnet_capability in subnet_capabilities:
+ domain_type = f"{subnet_capability['domainType'].lower().replace('-', '_')}_"
+ capability_details = subnet_capability['capabilityDetails']
+ for key, value in capability_details.items():
+ request_params[f"{domain_type}{camel_to_snake[key]}"] = value
+ return request_params
+
+ def get_app_policies(self, model_name, app_name):
+ policy_request_json = self.request_json.copy()
+ policy_request_json['serviceInfo'] = {'serviceName': model_name}
+ if 'preferReuse' in self.request_json:
+ policy_request_json['preferReuse'] = "reuse" if self.request_json['preferReuse'] else "create_new"
+ return get_policies(policy_request_json, app_name)
diff --git a/apps/slice_selection/optimizers/conductor/response_processor.py b/apps/slice_selection/optimizers/conductor/response_processor.py
index a9bdad0..71a350f 100644
--- a/apps/slice_selection/optimizers/conductor/response_processor.py
+++ b/apps/slice_selection/optimizers/conductor/response_processor.py
@@ -20,144 +20,89 @@
Module for processing response from conductor for slice selection
"""
-from osdf.logging.osdf_logging import debug_log
-
-
-SLICE_PROFILE_FIELDS = {"latency":"latency", "max_number_of_ues":"maxNumberOfUEs", "coverage_area_ta_list": "coverageAreaTAList",
- "ue_mobility_level":"uEMobilityLevel", "resource_sharing_level":"resourceSharingLevel", "exp_data_rate_ul": "expDataRateUL",
- "exp_data_rate_dl":"expDataRateDL", "area_traffic_cap_ul":"areaTrafficCapUL", "area_traffic_cap_dl": "areaTrafficCapDL",
- "activity_factor":"activityFactor", "e2e_latency":"e2eLatency", "jitter":"jitter", "survival_time": "survivalTime",
- "exp_data_rate":"expDataRate", "payload_size":"payloadSize", "traffic_density":"trafficDensity", "conn_density":"connDensity",
- "reliability":"reliability", "service_area_dimension":"serviceAreaDimension", "cs_availability": "csAvailability"}
-
-
-def conductor_response_processor(overall_recommendations, nst_info_map, request_info, service_profile):
- """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():
- if not (recommendations):
- new_nsi_solution = solution_with_only_slice_profile(service_profile, nst_info_map.get(nst_name))
- new_nsi_solutions.append(new_nsi_solution)
- continue
-
- for recommendation in recommendations:
- nsi_set = set(values['candidate']['nsi_id'] for key, values in recommendation.items())
- if len(nsi_set) == 1:
- nsi_id = nsi_set.pop()
- candidate = list(recommendation.values())[0]['candidate']
- debug_log.debug("The NSSIs in the solution belongs to the same NSI {}"
- .format(nsi_id))
- shared_nsi_solution = dict()
- shared_nsi_solution["NSIId"] = nsi_id
- shared_nsi_solution["NSIName"] = candidate.get('nsi_name')
- shared_nsi_solution["UUID"] = candidate.get('nsi_model_version_id')
- shared_nsi_solution["invariantUUID"] = candidate.get('nsi_model_invariant_id')
-
- nssi_info_list = get_nssi_solutions(recommendation)
- nssis = list()
- for nssi_info in nssi_info_list:
- nssi = dict()
- nssi["NSSIId"] = nssi_info.get("NSSISolution").get("NSSIId")
- nssi["NSSIName"] = nssi_info.get("NSSISolution").get("NSSIName")
- nssi["UUID"] = ""
- nssi["invariantUUID"] = ""
- nssi_info.get("sliceProfile").update({"domainType":"cn"})
- nssi["sliceProfile"] = [nssi_info.get("sliceProfile")]
- nssis.append(nssi)
-
- shared_nsi_solution["NSSIs"] = nssis
- 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 solution_with_only_slice_profile(service_profile, nst_info):
- nssi_solutions = get_slice_profile_from_service_profile(service_profile)
- new_nsi_solution = dict()
- new_nsi_solution['matchLevel'] = ""
- new_nsi_solution['NSTInfo'] = nst_info
- new_nsi_solution['NSSISolutions'] = nssi_solutions
- return new_nsi_solution
-
-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_slice_profile_from_service_profile(service_profile):
- nssi_solutions = list()
- service_profile["domainType"] = "cn"
- nssi_solution = {"sliceProfile": service_profile}
- nssi_solutions.append(nssi_solution)
- return nssi_solutions
-
-
-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[SLICE_PROFILE_FIELDS[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}
-
+import re
+
+
+class ResponseProcessor(object):
+ def __init__(self, request_info, slice_config):
+ self.request_info = request_info
+ self.slice_config = slice_config
+
+ def process_response(self, recommendations, model_info, subnets):
+ """Process conductor response to form the response for the API request
+
+ :param recommendations: recommendations from conductor
+ :param model_info: model info from the request
+ :param subnets: list of subnets
+ :return: response json as a dictionary
+ """
+ if not recommendations:
+ return self.get_slice_selection_response([])
+ model_name = model_info['name']
+ solutions = [self.get_solution_from_candidate(rec[model_name]['candidate'], model_info, subnets)
+ for rec in recommendations]
+ return self.get_slice_selection_response(solutions)
+
+ def get_solution_from_candidate(self, candidate, model_info, subnets):
+ if candidate['inventory_type'] == 'nssi':
+ return {
+ 'UUID': model_info['UUID'],
+ 'invariantUUID': model_info['invariantUUID'],
+ 'NSSIName': candidate['instance_name'],
+ 'NSSIId': candidate['instance_id']
+ }
+
+ elif candidate['inventory_type'] == 'nsi':
+ return {
+ 'existingNSI': True,
+ 'sharedNSISolution': {
+ 'UUID': model_info['UUID'],
+ 'invariantUUID': model_info['invariantUUID'],
+ 'NSIName': candidate['instance_name'],
+ 'NSIId': candidate['instance_id']
+ }
+ }
+
+ elif candidate['inventory_type'] == 'slice_profiles':
+ return {
+ 'existingNSI': False,
+ 'newNSISolution': {
+ 'slice_profiles': self.get_slice_profiles_from_candidate(candidate, subnets)
+ }
+ }
+
+ def get_slice_profiles_from_candidate(self, candidate, subnets):
+ slice_profiles = []
+ for subnet in subnets:
+ slice_profile = {self.get_profile_attribute(k, subnet): v for k, v in candidate.items()
+ if k.startswith(subnet)}
+ slice_profile['domainType'] = subnet
+ slice_profiles.append(slice_profile)
+ return slice_profiles
+
+ def get_profile_attribute(self, attribute, subnet):
+ snake_to_camel = self.slice_config['attribute_mapping']['snake_to_camel']
+ return snake_to_camel[re.sub(f'^{subnet}_', '', attribute)]
+
+ def process_error_response(self, error_message):
+ """Form response message from the error message
+
+ :param error_message: error message while processing the request
+ :return: response json as dictionary
+ """
+ return {'requestId': self.request_info['requestId'],
+ 'transactionId': self.request_info['transactionId'],
+ 'requestStatus': 'error',
+ 'statusMessage': error_message}
+
+ def get_slice_selection_response(self, solutions):
+ """Get NSI selection response from final solution
+
+ :param solutions: final solutions
+ :return: NSI selection response to send back as dictionary
+ """
+ return {'requestId': self.request_info['requestId'],
+ 'transactionId': self.request_info['transactionId'],
+ 'requestStatus': 'completed',
+ 'statusMessage': '',
+ 'solutions': solutions}
diff --git a/config/common_config.yaml b/config/common_config.yaml
index f010e44..905aa0b 100644
--- a/config/common_config.yaml
+++ b/config/common_config.yaml
@@ -40,13 +40,11 @@ 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
- - subscriber_policy_URLLC_1.json
- - thresholdPolicy_URLLC_Core_1.json
- - aggregationPolicy_URLLC_1.json
- - queryPolicy_URLLC_.json
+ slice_selection_policy_dir_embb-nst: "./test/policy-local-files/slice-selection-files/"
+ slice_selection_policy_files_embb-nst:
+ - query_policy_nsi.json
+ - threshold_policy_nsi.json
+ - vnf_policy_nsi_shared_case.json
service_info:
vCPE:
@@ -66,6 +64,12 @@ references:
subscriber_role:
source: onap.policies.optimization.SubscriberPolicy
value: properties.properties.subscriberRole
+ resource_sharing_level:
+ source: request
+ value: serviceProfile.resourceSharingLevel
+ reuse_preference:
+ source: request
+ value: preferReuse
policy_info:
prioritization_attributes:
@@ -82,10 +86,20 @@ policy_info:
policy_scope:
-
scope:
- - OSDF_FRANKFURT
+ - OSDF_GUILIN
+ - get_param: resource_sharing_level
+ - get_param: reuse_preference
services:
- get_param: service_name
+ subnet_selection:
+ policy_fetch: by_scope
+ policy_scope:
+ - scope:
+ - OSDF_GUILIN
+ services:
+ - get_param: service_name
+
placement:
policy_fetch: by_scope
policy_scope:
diff --git a/config/has_config.yaml b/config/has_config.yaml
index 0b94391..2f4a1cd 100644
--- a/config/has_config.yaml
+++ b/config/has_config.yaml
@@ -20,10 +20,11 @@ policy_config_mapping:
orchestrationStatus: orchestration-status
provStatus: prov-status
cloudRegion: cloud-region
- cloud_region_attributes:
+ cloud_region_attributes:
serviceRequests: service-requests
cloudRequests: cloud-requests
passthrough_attributes: {}
+ default_attributes: {}
candidates:
# for (k1, v1), if k1 is in demand, set prop[k2] = _get_candidates(demand[k1])
excludedCandidates: excluded_candidates
diff --git a/config/slicing_config.yaml b/config/slicing_config.yaml
new file mode 100644
index 0000000..a7e3a48
--- /dev/null
+++ b/config/slicing_config.yaml
@@ -0,0 +1,82 @@
+app_info:
+ NSI:
+ app_name: slice_selection
+ requirements_field: serviceProfile
+ model_info: NSTInfo
+ NSSI:
+ app_name: subnet_selection
+ requirements_field: sliceProfile
+ model_info: NSSTInfo
+
+attribute_mapping:
+ camel_to_snake:
+ maxBandwidth: max_bandwidth
+ jitter: jitter
+ sST: sst
+ latency: latency
+ resourceSharingLevel: resource_sharing_level
+ uEMobilityLevel: ue_mobility_level
+ maxNumberOfUEs: max_number_of_ues
+ dLThptPerUE: dl_thpt_per_ue
+ uLThptPerUE: ul_thpt_per_ue
+ sNSSAI: s_nssai
+ pLMNIdList: plmn_id_list
+ activityFactor: activity_factor
+ coverageAreaTAList: coverage_area_ta_List
+ availability: availability
+ cSAvailabilityTarget: cs_availability_target
+ reliability: reliability
+ cSReliabilityMeanTime: cs_reliability_mean_time
+ dLThptPerSlice: dl_thpt_per_slice
+ expDataRateDL: exp_data_rate_dl
+ uLThptPerSlice: ul_thpt_per_slice
+ expDataRateUL: exp_data_rate_ul
+ MaxPktSize: max_pkt_size
+ msgSizeByte: msg_size_byte
+ maxNumberOfConns: max_number_of_conns
+ maxNumberOfPDUSessions: max_number_of_pdu_sessions
+ terminalDensity: terminal_density
+ survivalTime: survival_time
+ areaTrafficCapDL: area_traffic_cap_dl
+ areaTrafficCapUL: area_traffic_cap_ul
+ overallUserDensity: overall_user_density
+ transferIntervalTarget: transfer_interval_target
+ expDataRate: exp_data_rate
+ security: security
+ maxThroughput: max_throughput
+
+ snake_to_camel:
+ max_bandwidth: maxBandwidth
+ jitter: jitter
+ sst: sST
+ latency: latency
+ resource_sharing_level: resourceSharingLevel
+ ue_mobility_level: uEMobilityLevel
+ max_number_of_ues: maxNumberOfUEs
+ dl_thpt_per_ue: dLThptPerUE
+ ul_thpt_per_ue: uLThptPerUE
+ s_nssai: sNSSAI
+ plmn_id_list: pLMNIdList
+ activity_factor: activityFactor
+ coverage_area_ta_List: coverageAreaTAList
+ availability: availability
+ cs_availability_target: cSAvailabilityTarget
+ reliability: reliability
+ cs_reliability_mean_time: cSReliabilityMeanTime
+ dl_thpt_per_slice: dLThptPerSlice
+ exp_data_rate_dl: expDataRateDL
+ ul_thpt_per_slice: uLThptPerSlice
+ exp_data_rate_ul: expDataRateUL
+ max_pkt_size: MaxPktSize
+ msg_size_byte: msgSizeByte
+ max_number_of_conns: maxNumberOfConns
+ max_number_of_pdu_sessions: maxNumberOfPDUSessions
+ terminal_density: terminalDensity
+ survival_time: survivalTime
+ area_traffic_cap_dl: areaTrafficCapDL
+ area_traffic_cap_ul: areaTrafficCapUL
+ overall_user_density: overallUserDensity
+ transfer_interval_target: transferIntervalTarget
+ exp_data_rate: expDataRate
+ security: security
+ max_throughput: maxThroughput
diff --git a/osdf/adapters/conductor/api_builder.py b/osdf/adapters/conductor/api_builder.py
index daf8e54..f3b0798 100644
--- a/osdf/adapters/conductor/api_builder.py
+++ b/osdf/adapters/conductor/api_builder.py
@@ -20,14 +20,14 @@
from jinja2 import Template
import json
-from osdf.adapters.policy.utils import group_policies_gen
import osdf.adapters.conductor.translation as tr
+from osdf.adapters.policy.utils import group_policies_gen
from osdf.utils.programming_utils import list_flatten
def _build_parameters(group_policies, service_info, request_parameters):
- """
- Function prepares parameters section for has request
+ """Function prepares parameters section for has request
+
:param group_policies: filtered policies
:param service_info: service info
:param request_parameters: request parameters
@@ -50,14 +50,15 @@ def _build_parameters(group_policies, service_info, request_parameters):
def conductor_api_builder(req_info, demands, request_parameters, service_info,
- location_enabled, flat_policies: list, local_config,
+ template_fields, 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 template_fields: Fields that has to be passed to the template to render
: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
@@ -73,6 +74,7 @@ def conductor_api_builder(req_info, demands, request_parameters, service_info,
demand_list = tr.gen_demands(demands, gp['onap.policies.optimization.resource.VnfPolicy'])
attribute_policy_list = tr.gen_attribute_policy(
demand_name_list, gp['onap.policies.optimization.resource.AttributePolicy'])
+
distance_to_location_policy_list = tr.gen_distance_to_location_policy(
demand_name_list, gp['onap.policies.optimization.resource.DistancePolicy'])
inventory_policy_list = tr.gen_inventory_group_policy(
@@ -95,8 +97,8 @@ def conductor_api_builder(req_info, demands, request_parameters, service_info,
gp['onap.policies.optimization.resource.'
'ThresholdPolicy'])
aggregation_policy_list = tr.gen_aggregation_policy(demand_name_list,
- gp['onap.policies.optimization.resource.'
- 'AggregationPolicy'])
+ gp['onap.policies.optimization.resource.'
+ 'AggregationPolicy'])
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,
@@ -114,7 +116,8 @@ def conductor_api_builder(req_info, demands, request_parameters, service_info,
timeout=req_info['timeout'],
limit=req_info['numSolutions'],
request_params=req_params_dict,
- location_enabled=location_enabled,
+ location_enabled=template_fields.get('location_enabled'),
+ version=template_fields.get('version'),
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 155d4d5..6749c2c 100644
--- a/osdf/adapters/conductor/conductor.py
+++ b/osdf/adapters/conductor/conductor.py
@@ -18,17 +18,16 @@
#
import json
-
from requests import RequestException
import time
from osdf.adapters.conductor.api_builder import conductor_api_builder
from osdf.logging.osdf_logging import debug_log
-from osdf.utils.interfaces import RestClient
from osdf.operation.exceptions import BusinessException
+from osdf.utils.interfaces import RestClient
-def request(req_info, demands, request_parameters, service_info, location_enabled,
+def request(req_info, demands, request_parameters, service_info, template_fields,
osdf_config, flat_policies):
config = osdf_config.deployment
local_config = osdf_config.core
@@ -53,7 +52,7 @@ def request(req_info, demands, request_parameters, service_info, location_enable
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,
+ service_info, template_fields, flat_policies,
local_config)
conductor_req_json = json.loads(conductor_req_json_str)
@@ -74,7 +73,7 @@ def request(req_info, demands, request_parameters, service_info, location_enable
if resp["plans"][0].get("status") in ["done", "not found"]:
return resp
- new_url = resp['plans'][0]['links'][0][0]['href'] # TODO: check why a list of lists
+ new_url = resp['plans'][0]['links'][0][0]['href'] # TODO(krishna): check why a list of lists
if total_time >= max_timeout:
raise BusinessException("Conductor could not provide a solution within {} seconds,"
@@ -95,6 +94,7 @@ def request(req_info, demands, request_parameters, service_info, location_enable
def initial_request_to_conductor(rc, conductor_url, conductor_req_json):
"""First steps in the request-redirect chain in making a call to Conductor
+
: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
@@ -112,7 +112,7 @@ def initial_request_to_conductor(rc, conductor_url, conductor_req_json):
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
+ url=plan_url)
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 d4a9a0e..e8d47b3 100755
--- a/osdf/adapters/conductor/templates/conductor_interface.json
+++ b/osdf/adapters/conductor/templates/conductor_interface.json
@@ -4,7 +4,7 @@
"timeout": {{ timeout }},
"num_solution": "{{ limit }}",
"template": {
- "homing_template_version": "2017-10-10",
+ "homing_template_version": "{{ version }}",
"parameters": {
{% set comma=joiner(",") %}
{% for key, value in request_params.items() %} {{ comma() }}
diff --git a/osdf/adapters/conductor/translation.py b/osdf/adapters/conductor/translation.py
index 4b012f5..238428a 100644
--- a/osdf/adapters/conductor/translation.py
+++ b/osdf/adapters/conductor/translation.py
@@ -19,8 +19,8 @@
import copy
import json
import re
-
import yaml
+
from osdf.utils.programming_utils import dot_notation
policy_config_mapping = yaml.safe_load(open('config/has_config.yaml')).get('policy_config_mapping')
@@ -41,8 +41,8 @@ CONSTRAINT_TYPE_MAP = {"onap.policies.optimization.resource.AttributePolicy": "a
def get_opt_query_data(request_parameters, policies):
- """
- Fetch service and order specific details from the requestParameters field of a request.
+ """Fetch service and order specific details from the requestParameters field of a request.
+
:param request_parameters: A list of request parameters
:param policies: A set of policies
:return: A dictionary with service and order-specific attributes.
@@ -60,10 +60,20 @@ def get_opt_query_data(request_parameters, policies):
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
:param optimization_policy: optimization objective policy information provided in the incoming request
:return: List of optimization objective policies in a format required by Conductor
"""
+ if len(optimization_policy) == 1:
+ policy = optimization_policy[0]
+ policy_content = policy[list(policy.keys())[0]]
+ if policy_content['type_version'] == '2.0.0':
+ properties = policy_content['properties']
+ objective = {'goal': properties['goal'],
+ 'operation_function': properties['operation_function']}
+ return [objective]
+
optimization_policy_list = []
for policy in optimization_policy:
content = policy[list(policy.keys())[0]]['properties']
@@ -71,7 +81,7 @@ def gen_optimization_policy(vnf_list, optimization_policy):
parameters = ["cloud_version", "hpa_score"]
for attr in content['objectiveParameter']['parameterAttributes']:
- parameter = attr['parameter'] if attr['parameter'] in parameters else attr['parameter']+"_between"
+ parameter = attr['parameter'] if attr['parameter'] in parameters else attr['parameter'] + "_between"
default, vnfs = get_matching_vnfs(attr['resources'], vnf_list)
for vnf in vnfs:
value = [vnf] if attr['parameter'] in parameters else [attr['customerLocationInfo'], vnf]
@@ -80,13 +90,14 @@ def gen_optimization_policy(vnf_list, optimization_policy):
})
optimization_policy_list.append({
- content['objective']: {content['objectiveParameter']['operator']: parameter_list }
+ content['objective']: {content['objectiveParameter']['operator']: parameter_list}
})
return optimization_policy_list
def get_matching_vnfs(resources, vnf_list, match_type="intersection"):
"""Get a list of matching VNFs from the list of resources
+
:param resources:
:param vnf_list: List of vnfs to used in placement request
:param match_type: "intersection" or "all" or "any" (any => send all_vnfs if there is any intersection)
@@ -106,6 +117,7 @@ def get_matching_vnfs(resources, vnf_list, match_type="intersection"):
def gen_policy_instance(vnf_list, resource_policy, match_type="intersection", rtype=None):
"""Generate a list of policies
+
:param vnf_list: List of vnf's to used in placement request
:param resource_policy: policy for this specific resource
:param match_type: How to match the vnf_names with the vnf_list (intersection or "any")
@@ -129,13 +141,14 @@ 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': CONSTRAINT_TYPE_MAP.get(pc['type']), 'demands': d}} \
- in resource_policy_list else False
+ 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': CONSTRAINT_TYPE_MAP.get(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
@@ -194,9 +207,9 @@ def gen_attribute_policy(vnf_list, attribute_policy):
properties = p_main[list(p_main.keys())[0]]['properties']['attributeProperties']
attribute_mapping = policy_config_mapping['filtering_attributes'] # wanted attributes and mapping
p_new[p_main[list(p_main.keys())[0]]['properties']['identity']]['properties'] = {
- 'evaluate': dict((attribute_mapping[k], properties.get(k)
- if k != "cloudRegion" else gen_cloud_region(properties))
- for k in attribute_mapping.keys())
+ 'evaluate': dict((attribute_mapping[k], properties.get(k)
+ if k != "cloudRegion" else gen_cloud_region(properties))
+ for k in attribute_mapping.keys())
}
return cur_policies # cur_policies gets updated in place...
@@ -271,7 +284,7 @@ def get_policy_properties(demand, policies):
policy_demands = set([x.lower() for x in policy[list(policy.keys())[0]]['properties']['resources']])
if policy_demands and demand['resourceModuleName'].lower() not in policy_demands:
continue # no match for this policy
- elif policy_demands == set(): # Append resource name for default policy
+ elif policy_demands == set(): # Append resource name for default policy
policy[list(policy.keys())[0]]['properties'].update(resources=list(demand.get('resourceModuleName')))
for policy_property in policy[list(policy.keys())[0]]['properties']['vnfProperties']:
yield policy_property
@@ -285,35 +298,44 @@ def get_demand_properties(demand, policies):
inventory_type=policy_property['inventoryType'],
service_type=demand.get('serviceResourceId', ''),
service_resource_id=demand.get('serviceResourceId', ''))
+ policy_property_mapping = {'filtering_attributes': 'attributes',
+ 'passthrough_attributes': 'passthroughAttributes',
+ 'default_attributes': 'defaultAttributes'}
prop.update({'unique': policy_property['unique']} if 'unique' in policy_property and
policy_property['unique'] else {})
prop['filtering_attributes'] = dict()
- if policy_property.get('attributes'):
- for attr_key, attr_val in policy_property['attributes'].items():
- update_converted_attribute(attr_key, attr_val, prop, 'filtering_attributes')
- if policy_property.get('passthroughAttributes'):
- prop['passthrough_attributes'] = dict()
- for attr_key, attr_val in policy_property['passthroughAttributes'].items():
- update_converted_attribute(attr_key, attr_val, prop, 'passthrough_attributes')
+ for key, value in policy_property_mapping.items():
+ get_demand_attributes(prop, policy_property, key, value)
prop['filtering_attributes'].update({'global-customer-id': policy_property['customerId']}
- if 'customerId' in policy_property and 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 'modelInvariantId' in demand['resourceModelInfo'] and demand['resourceModelInfo']['modelInvariantId'] else {})
+ if 'modelInvariantId' in demand['resourceModelInfo']
+ and demand['resourceModelInfo']['modelInvariantId'] else {})
prop['filtering_attributes'].update({'model-version-id': demand['resourceModelInfo']['modelVersionId']}
- if 'modelVersionId' in demand['resourceModelInfo'] and demand['resourceModelInfo']['modelVersionId'] else {})
+ if 'modelVersionId' in demand['resourceModelInfo']
+ and demand['resourceModelInfo']['modelVersionId'] else {})
prop['filtering_attributes'].update({'equipment-role': policy_property['equipmentRole']}
- if 'equipmentRole' in policy_property and policy_property['equipmentRole'] else {})
+ if 'equipmentRole' in policy_property and policy_property['equipmentRole']
+ else {})
prop.update(get_candidates_demands(demand))
demand_properties.append(prop)
return demand_properties
+def get_demand_attributes(prop, policy_property, attribute_type, key):
+ if policy_property.get(key):
+ prop[attribute_type] = dict()
+ for attr_key, attr_val in policy_property[key].items():
+ update_converted_attribute(attr_key, attr_val, prop, attribute_type)
+
+
def update_converted_attribute(attr_key, attr_val, properties, attribute_type):
- """
- Updates dictonary of attributes with one specified in the arguments.
+ """Updates dictonary of attributes with one specified in the arguments.
+
Automatically translates key namr from camelCase to hyphens
:param attribute_type: attribute section name
:param attr_key: key of the attribute
@@ -333,6 +355,7 @@ 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'])
@@ -349,6 +372,6 @@ def gen_demands(demands, vnf_policies):
def gen_cloud_region(property):
prop = {"cloud_region_attributes": dict()}
if 'cloudRegion' in property:
- for k,v in property['cloudRegion'].items():
+ for k, v in property['cloudRegion'].items():
update_converted_attribute(k, v, prop, 'cloud_region_attributes')
return prop["cloud_region_attributes"]
diff --git a/osdf/config/base.py b/osdf/config/base.py
index fbe9315..2393642 100644
--- a/osdf/config/base.py
+++ b/osdf/config/base.py
@@ -1,5 +1,6 @@
# -------------------------------------------------------------------------
# Copyright (c) 2015-2017 AT&T Intellectual Property
+# 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.
@@ -27,6 +28,10 @@ config_spec = {
"core": "config/common_config.yaml"
}
+slicing_spec = "config/slicing_config.yaml"
+
+slice_config = config_loader.load_config_file(slicing_spec)
+
osdf_config = DotDict(config_loader.all_configs(**config_spec))
http_basic_auth_credentials = creds.load_credentials(osdf_config)
diff --git a/osdfapp.py b/osdfapp.py
index 9234d78..8d40273 100755
--- a/osdfapp.py
+++ b/osdfapp.py
@@ -37,16 +37,18 @@ from apps.placement.optimizers.conductor.remote_opt_processor import process_pla
from apps.route.optimizers.inter_domain_route_opt import InterDomainRouteOpt
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 apps.slice_selection.models.api.nssi_selection_request import NSSISelectionAPI
+from apps.slice_selection.optimizers.conductor.remote_opt_processor import SliceSelectionOptimizer
from osdf.adapters.policy.interface import get_policies
from osdf.adapters.policy.interface import upload_policy_models
from osdf.config.base import osdf_config
+from osdf.config.base import slice_config
from osdf.logging.osdf_logging import MH, audit_log
from osdf.operation.responses import osdf_response_for_request_accept as req_accept
from osdf.utils import api_data_utils
from osdf.webapp.appcontroller import auth_basic
from apps.nxi_termination.optimizers.remote_opt_processor import process_nxi_termination_opt
-from apps.nxi_termination.models.api.nxi_termination_request import NxiTerminationApi
+from apps.nxi_termination.models.api.nxi_termination_request import NxiTerminationApi
@app.route("/api/oof/v1/healthcheck", methods=["GET"])
@@ -128,6 +130,7 @@ def do_nst_selection():
response = process_nst_selection(request_json, osdf_config)
return response
+
@app.route("/api/oof/v1/pci", methods=["POST"])
@app.route("/api/oof/pci/v1", methods=["POST"])
@auth_basic.login_required
@@ -163,8 +166,8 @@ def do_nsi_selection():
audit_log.info(MH.received_request(request.url, request.remote_addr, json.dumps(request_json)))
NSISelectionAPI(request_json).validate()
audit_log.info(MH.new_worker_thread(req_id, "[for NSI selection]"))
- t = Thread(target=process_nsi_selection_opt, args=(request_json, osdf_config))
- t.start()
+ slice_opt = SliceSelectionOptimizer(osdf_config, slice_config, request_json, 'NSI')
+ slice_opt.start()
return req_accept(request_id=req_id,
transaction_id=request_json['requestInfo']['transactionId'],
request_status="accepted", status_message="")
@@ -178,12 +181,13 @@ def do_nssi_selection():
audit_log.info(MH.received_request(request.url, request.remote_addr, json.dumps(request_json)))
NSSISelectionAPI(request_json).validate()
audit_log.info(MH.new_worker_thread(req_id, "[for NSSI selection]"))
- t = Thread(target=process_nsi_selection_opt, args=(request_json, osdf_config))
- t.start()
+ slice_opt = SliceSelectionOptimizer(osdf_config, slice_config, request_json, 'NSSI')
+ slice_opt.start()
return req_accept(request_id=req_id,
transaction_id=request_json['requestInfo']['transactionId'],
request_status="accepted", status_message="")
+
@app.route("/api/oof/terminate/nxi/v1",methods=["POST"])
def do_nxi_terminaton():
request_json = request.get_json()
@@ -193,5 +197,6 @@ def do_nxi_terminaton():
NxiTerminationApi(request_json).validate()
return process_nxi_termination_opt(request_json,osdf_config)
+
if __name__ == "__main__":
run_app()
diff --git a/test/apps/slice_selection/new_solution_conductor_response.json b/test/apps/slice_selection/new_solution_conductor_response.json
index fea3544..897aa2b 100644
--- a/test/apps/slice_selection/new_solution_conductor_response.json
+++ b/test/apps/slice_selection/new_solution_conductor_response.json
@@ -14,78 +14,32 @@
],
"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,
+ "embb-nst":{
+ "inventory_provider":"generator",
+ "candidate": {
"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,
+ "inventory_provider":"generator",
+ "max_number_of_ues":100,
"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",
- "nsi_id": "7ecdfb7a-bc38-4abd-9cb3-6677d71e892f",
- "nsi_model_version_id": "c3d8a690-f138-4554-89af-9349aeb9b19a",
- "nsi_model_invariant_id": "590b9fcf-6927-495e-a898-a1418dd4820c",
- "instance_name":"nssi_test_0211"
+ "candidate_type":"slice_profiles",
+ "resource_sharing_level":"shared",
+ "inventory_type":"slice_profiles",
+ "reliability": 99.99,
+ "AN_latency": 10,
+ "AN_ue_mobility_level": "stationary",
+ "AN_max_number_of_ues": 100,
+ "AN_reliability": 99.99,
+ "AN_resource_sharing_level":"shared",
+ "CN_latency": 5,
+ "CN_reliability": 99.99,
+ "CN_resource_sharing_level":"shared",
+ "TN-BH_reliability": 99.99,
+ "TN-BH_latency": 5,
+ "TN-BH_resource_sharing_level":"shared",
+ "cost":1.0
}
- },
- "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",
- "nsi_id": "0cf2caed-c0e2-4a9d-8590-6a871e86f178",
- "nsi_model_version_id": "a841af61-3c77-4310-ae63-61e1cbf7fe9b",
- "nsi_model_invariant_id": "4e5351fe-0877-4c2e-bf75-ccb149f34a77",
- "instance_name":"nssi_test_ran_0211"
- }
- }
+ }
}
]
}
diff --git a/test/apps/slice_selection/new_solution_nsi_response.json b/test/apps/slice_selection/new_solution_nsi_response.json
index 02023ce..2ecb543 100644
--- a/test/apps/slice_selection/new_solution_nsi_response.json
+++ b/test/apps/slice_selection/new_solution_nsi_response.json
@@ -1,56 +1,35 @@
{
- "requestId":"d290f1ee-6c54-4b01-90e6-d701748f0851",
- "transactionId":"d290f1ee-6c54-4b01-90e6-d701748f0851",
- "requestStatus":"completed",
- "statusMessage":"",
- "solutions":{
- "sharedNSISolutions":[
-
- ],
- "newNSISolutions":[
- {
- "matchLevel":"",
- "NSTInfo":{"invariantUUID": "fda3c1e8-7653-4acd-80ef-f5755c1d3859",
- "UUID": "a6906768-1cae-4e78-acd1-d753ac61f3e8",
- "NSTName": "URLLC_1"
- },
- "NSSISolutions":[
+ "requestId": "d290f1ee-6c54-4b01-90e6-d701748f0851",
+ "transactionId": "d290f1ee-6c54-4b01-90e6-d701748f0851",
+ "requestStatus": "completed",
+ "statusMessage": "",
+ "solutions": [
+ {
+ "existingNSI": false,
+ "newNSISolution": {
+ "slice_profiles": [
{
- "sliceProfile":{
- "latency":20,
- "coverageAreaTAList":"[{\"province\":\"??\",\"city\":\"???\",\"county\":\"???\",\"street\":\"?????\"}]",
- "uEMobilityLevel":"stationary",
- "resourceSharingLevel":"0",
- "expDataRateUL":100,
- "expDataRateDL":100
- },
- "NSSTInfo":{
- "NSSTName":"URLLC_Core_1"
- },
- "NSSISolution":{
- "NSSIName":"nssi_test_0211",
- "NSSIId":"1a636c4d-5e76-427e-bfd6-241a947224b0"
- }
+ "domainType": "AN",
+ "resourceSharingLevel": "shared",
+ "latency": 10,
+ "reliability": 99.99,
+ "uEMobilityLevel": "stationary",
+ "maxNumberOfUEs": 100
},
{
- "sliceProfile":{
- "latency":15,
- "coverageAreaTAList":"[{\"province\":\"??\",\"city\":\"???\",\"county\":\"???\",\"street\":\"?????\"}]",
- "uEMobilityLevel":"stationary",
- "resourceSharingLevel":"0",
- "expDataRateUL":100,
- "expDataRateDL":100
- },
- "NSSTInfo":{
- "NSSTName":"URLLC_Ran_1"
- },
- "NSSISolution":{
- "NSSIName":"nssi_test_ran_0211",
- "NSSIId":"490c68b0-639c-11ea-bc55-0242ac130003"
- }
+ "domainType": "CN",
+ "resourceSharingLevel": "shared",
+ "latency": 5,
+ "reliability": 99.99
+ },
+ {
+ "domainType": "TN-BH",
+ "resourceSharingLevel": "shared",
+ "latency": 5,
+ "reliability": 99.99
}
]
}
- ]
- }
-}
+ }
+ ]
+} \ No newline at end of file
diff --git a/test/apps/slice_selection/no_recomm_nsi_response.json b/test/apps/slice_selection/no_recomm_nsi_response.json
index daf151d..e36a243 100644
--- a/test/apps/slice_selection/no_recomm_nsi_response.json
+++ b/test/apps/slice_selection/no_recomm_nsi_response.json
@@ -3,35 +3,5 @@
"transactionId":"d290f1ee-6c54-4b01-90e6-d701748f0851",
"requestStatus":"completed",
"statusMessage":"",
- "solutions":{
- "sharedNSISolutions":[
-
- ],
- "newNSISolutions":[
- {
- "matchLevel":"",
- "NSTInfo":{"invariantUUID": "fda3c1e8-7653-4acd-80ef-f5755c1d3859",
- "UUID": "a6906768-1cae-4e78-acd1-d753ac61f3e8",
- "NSTName": "URLLC_1"
- },
-
- "NSSISolutions":[
- {
- "sliceProfile": {
- "latency": 2,
- "security": "High",
- "reliability": 99.9999,
- "trafficDensity": 1,
- "connDensity": 100000,
- "expDataRate": 50,
- "jitter": 1,
- "survivalTime": 0,
- "domainType":"cn",
- "resourceSharingLevel":"shared"
- }
- }
- ]
- }
- ]
- }
+ "solutions": []
}
diff --git a/test/apps/slice_selection/not_shared_nsi_request.json b/test/apps/slice_selection/not_shared_nsi_request.json
deleted file mode 100644
index 1e22f41..0000000
--- a/test/apps/slice_selection/not_shared_nsi_request.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
- "serviceProfile": {
- "latency": 2,
- "security": "High",
- "reliability": 99.9999,
- "trafficDensity": 1,
- "connDensity": 100000,
- "expDataRate": 50,
- "jitter": 1,
- "survivalTime": 0,
- "resourceSharingLevel": "non-shared"
- },
- "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"
- }
- ]
-}
diff --git a/test/apps/slice_selection/not_shared_nsi_response.json b/test/apps/slice_selection/not_shared_nsi_response.json
deleted file mode 100644
index 873ad2e..0000000
--- a/test/apps/slice_selection/not_shared_nsi_response.json
+++ /dev/null
@@ -1,37 +0,0 @@
-{
- "requestId":"d290f1ee-6c54-4b01-90e6-d701748f0851",
- "transactionId":"d290f1ee-6c54-4b01-90e6-d701748f0851",
- "requestStatus":"completed",
- "statusMessage":"",
- "solutions":{
- "sharedNSISolutions":[
-
- ],
- "newNSISolutions":[
- {
- "matchLevel":"",
- "NSTInfo":{"invariantUUID": "fda3c1e8-7653-4acd-80ef-f5755c1d3859",
- "UUID": "a6906768-1cae-4e78-acd1-d753ac61f3e8",
- "NSTName": "URLLC_1"
- },
-
- "NSSISolutions":[
- {
- "sliceProfile": {
- "latency": 2,
- "security": "High",
- "reliability": 99.9999,
- "trafficDensity": 1,
- "connDensity": 100000,
- "expDataRate": 50,
- "jitter": 1,
- "survivalTime": 0,
- "domainType": "cn",
- "resourceSharingLevel": "non-shared"
- }
- }
- ]
- }
- ]
- }
-}
diff --git a/test/apps/slice_selection/nsi_error_response.json b/test/apps/slice_selection/nsi_error_response.json
index f022779..9dc5300 100644
--- a/test/apps/slice_selection/nsi_error_response.json
+++ b/test/apps/slice_selection/nsi_error_response.json
@@ -1,37 +1,6 @@
{
"requestId":"d290f1ee-6c54-4b01-90e6-d701748f0851",
"transactionId":"d290f1ee-6c54-4b01-90e6-d701748f0851",
- "requestStatus":"completed",
- "statusMessage":"",
- "solutions":{
- "sharedNSISolutions":[
-
- ],
- "newNSISolutions":[
- {
- "matchLevel":"",
- "NSTInfo":{"invariantUUID": "fda3c1e8-7653-4acd-80ef-f5755c1d3859",
- "UUID": "a6906768-1cae-4e78-acd1-d753ac61f3e8",
- "NSTName": "URLLC_1"
- },
-
- "NSSISolutions":[
- {
- "sliceProfile": {
- "latency": 2,
- "security": "High",
- "reliability": 99.9999,
- "trafficDensity": 1,
- "connDensity": 100000,
- "expDataRate": 50,
- "jitter": 1,
- "survivalTime": 0,
- "domainType":"cn",
- "resourceSharingLevel":"shared"
- }
- }
- ]
- }
- ]
- }
+ "requestStatus":"error",
+ "statusMessage":"Some error message"
}
diff --git a/test/apps/slice_selection/nsi_selection_request.json b/test/apps/slice_selection/nsi_selection_request.json
index 9c4fcda..acd23a2 100644
--- a/test/apps/slice_selection/nsi_selection_request.json
+++ b/test/apps/slice_selection/nsi_selection_request.json
@@ -1,23 +1,22 @@
{
- "serviceProfile": {
- "latency": 2,
- "security": "High",
- "reliability": 99.9999,
- "trafficDensity": 1,
- "connDensity": 100000,
- "expDataRate": 50,
- "jitter": 1,
- "survivalTime": 0,
- "resourceSharingLevel":"shared"
- },
- "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
- },
- "NSTInfo":{
+ "serviceProfile":{
+ "latency":5,
+ "security":"High",
+ "reliability":99.999,
+ "resourceSharingLevel":"shared"
+ },
+ "requestInfo":{
+ "transactionId":"d290f1ee-6c54-4b01-90e6-d701748f0851",
+ "requestId":"d290f1ee-6c54-4b01-90e6-d701748f0851",
+ "callbackUrl": "http://0.0.0.0:9000/osdfCallback/",
+ "callbackHeader":{
+ "blob":"content"
+ },
+ "sourceId":"d290f1ee-6c54-4b01-90e6-d701748f0851",
+ "timeout":5,
+ "numSolutions":1
+ },
+ "NSTInfo":{
"UUID":"3fa85f64-5717-4562-b3fc-2c963f66afa1",
"invariantUUID":"7ua85f64-5717-4562-b3fc-2c963f66afa6",
"name":"embb-nst"
@@ -49,36 +48,36 @@
"name":"embb-tn-bh"
}
],
- "preferReuse":true,
+ "preferReuse":false,
"subnetCapabilities":[
{
- "domainType":"AN-NF",
+ "domainType":"AN",
"capabilityDetails":{
- "blob":"content"
+ "latency": "4",
+ "reliability": "99.9",
+ "maxNumberOfUEs": "10",
+ "maxThroughput": "50",
+ "terminalDensity": "60"
}
},
{
"domainType":"CN",
"capabilityDetails":{
- "blob":"content"
- }
- },
- {
- "domainType":"TN-FH",
- "capabilityDetails":{
- "blob":"content"
- }
- },
- {
- "domainType":"TN-MH",
- "capabilityDetails":{
- "blob":"content"
+ "latency": "3",
+ "reliability": "99.9",
+ "maxNumberOfUEs": "10",
+ "maxThroughput": "50",
+ "terminalDensity": "60"
}
},
{
"domainType":"TN-BH",
"capabilityDetails":{
- "blob":"content"
+ "latency": "2",
+ "reliability": "99.9",
+ "maxNumberOfUEs": "10",
+ "maxThroughput": "50",
+ "terminalDensity": "60"
}
}
]
diff --git a/test/apps/slice_selection/nssi_conductor_response.json b/test/apps/slice_selection/nssi_conductor_response.json
new file mode 100644
index 0000000..d2edcf5
--- /dev/null
+++ b/test/apps/slice_selection/nssi_conductor_response.json
@@ -0,0 +1,53 @@
+{
+ "plans":[
+ {
+ "status":"done",
+ "id":"plan_id",
+ "name":"Plan Name 1",
+ "links":[
+ [
+ {
+ "href":"http://conductor:8091/v1/plans/plan_id",
+ "rel":"self"
+ }
+ ]
+ ],
+ "recommendations":[
+ {
+ "embb-cn": {
+ "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,
+ "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": "shared",
+ "inventory_type": "nssi",
+ "reliability": null,
+ "cost": 1.0,
+ "instance_id": "e1041cdc-12da-4f36-b84e-68c380e9cd47",
+ "instance_name": "nssi_test_0211"
+ }
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/test/apps/slice_selection/nssi_error_response.json b/test/apps/slice_selection/nssi_error_response.json
new file mode 100644
index 0000000..70e0596
--- /dev/null
+++ b/test/apps/slice_selection/nssi_error_response.json
@@ -0,0 +1,7 @@
+{
+ "requestId":"r450f1ee-6c54-4b01-90e6-d701748f0851",
+ "transactionId":"t670f1ee-6c54-4b01-90e6-d701748f0851",
+ "requestStatus":"completed",
+ "statusMessage":"",
+ "solutions": []
+}
diff --git a/test/apps/slice_selection/nssi_selection_request.json b/test/apps/slice_selection/nssi_selection_request.json
index 1a49a8b..61ee563 100644
--- a/test/apps/slice_selection/nssi_selection_request.json
+++ b/test/apps/slice_selection/nssi_selection_request.json
@@ -1,6 +1,9 @@
{
"sliceProfile": {
- "blob": "content"
+ "latency":5,
+ "security":"High",
+ "reliability":99.999,
+ "resourceSharingLevel":"shared"
},
"requestInfo": {
"transactionId": "t670f1ee-6c54-4b01-90e6-d701748f0851",
@@ -17,7 +20,7 @@
}
},
"NSSTInfo": {
- "UUID": "y7785f64-5717-4562-b3fc-2c963f66afa6",
+ "UUID": "a7785f64-5717-4562-b3fc-2c963f66afa6",
"invariantUUID": "9fh85f64-5717-4562-b3fc-2c963f66afa6",
"name": "embb-cn"
}
diff --git a/test/apps/slice_selection/shared_solution_conductor_response.json b/test/apps/slice_selection/shared_solution_conductor_response.json
index 13ea29c..a187fef 100644
--- a/test/apps/slice_selection/shared_solution_conductor_response.json
+++ b/test/apps/slice_selection/shared_solution_conductor_response.json
@@ -14,78 +14,38 @@
],
"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",
- "nsi_id": "7ecdfb7a-bc38-4abd-9cb3-6677d71e892f",
- "nsi_model_version_id": "c3d8a690-f138-4554-89af-9349aeb9b19a",
- "nsi_model_invariant_id": "590b9fcf-6927-495e-a898-a1418dd4820c",
- "instance_name":"nssi_test_0211"
+ "embb-nst": {
+ "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,
+ "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": "nsi",
+ "traffic_density": 0,
+ "payload_size": 0,
+ "exp_data_rate_dl": 100,
+ "jitter": 0,
+ "survival_time": 0,
+ "resource_sharing_level": "shared",
+ "inventory_type": "nsi",
+ "reliability": null,
+ "cost": 1.0,
+ "instance_id": "f1041cdc-12da-4f36-b84e-68c380e9cd47",
+ "instance_name": "nsi_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",
- "nsi_id": "7ecdfb7a-bc38-4abd-9cb3-6677d71e892f",
- "nsi_model_version_id": "c3d8a690-f138-4554-89af-9349aeb9b19a",
- "nsi_model_invariant_id": "590b9fcf-6927-495e-a898-a1418dd4820c",
- "instance_name":"nssi_test_ran_0211"
- }
- }
+ }
}
]
}
diff --git a/test/apps/slice_selection/shared_solution_nsi_response.json b/test/apps/slice_selection/shared_solution_nsi_response.json
index 3e1ad9f..ff83dfc 100644
--- a/test/apps/slice_selection/shared_solution_nsi_response.json
+++ b/test/apps/slice_selection/shared_solution_nsi_response.json
@@ -1,54 +1,17 @@
{
"requestId": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"requestStatus": "completed",
- "solutions": {
- "newNSISolutions": [],
- "sharedNSISolutions": [
- {
- "NSIId": "7ecdfb7a-bc38-4abd-9cb3-6677d71e892f",
- "NSIName": "nsi_test_0212",
- "NSSIs": [
- {
- "NSSIId": "1a636c4d-5e76-427e-bfd6-241a947224b0",
- "NSSIName": "nssi_test_0211",
- "UUID": "",
- "invariantUUID": "",
- "sliceProfile": [
- {
- "coverageAreaTAList": "[{\"province\":\"??\",\"city\":\"???\",\"county\":\"???\",\"street\":\"?????\"}]",
- "expDataRateDL": 100,
- "expDataRateUL": 100,
- "latency": 20,
- "resourceSharingLevel": "0",
- "uEMobilityLevel": "stationary",
- "domainType" : "cn"
- }
- ]
- },
- {
- "NSSIId": "490c68b0-639c-11ea-bc55-0242ac130003",
- "NSSIName": "nssi_test_ran_0211",
- "UUID": "",
- "invariantUUID": "",
- "sliceProfile": [
- {
- "coverageAreaTAList": "[{\"province\":\"??\",\"city\":\"???\",\"county\":\"???\",\"street\":\"?????\"}]",
- "expDataRateDL": 100,
- "expDataRateUL": 100,
- "latency": 15,
- "resourceSharingLevel": "0",
- "uEMobilityLevel": "stationary",
- "domainType" : "cn"
-
- }
- ]
- }
- ],
- "UUID": "c3d8a690-f138-4554-89af-9349aeb9b19a",
- "invariantUUID": "590b9fcf-6927-495e-a898-a1418dd4820c"
+ "solutions": [
+ {
+ "existingNSI": true,
+ "sharedNSISolution": {
+ "UUID": "3fa85f64-5717-4562-b3fc-2c963f66afa1",
+ "invariantUUID": "7ua85f64-5717-4562-b3fc-2c963f66afa6",
+ "NSIName": "nsi_test_0211",
+ "NSIId": "f1041cdc-12da-4f36-b84e-68c380e9cd47"
}
- ]
- },
+ }
+ ],
"statusMessage": "",
"transactionId": "d290f1ee-6c54-4b01-90e6-d701748f0851"
}
diff --git a/test/apps/slice_selection/shared_solution_nssi_response.json b/test/apps/slice_selection/shared_solution_nssi_response.json
new file mode 100644
index 0000000..f3468a4
--- /dev/null
+++ b/test/apps/slice_selection/shared_solution_nssi_response.json
@@ -0,0 +1,15 @@
+{
+ "requestId": "r450f1ee-6c54-4b01-90e6-d701748f0851",
+ "requestStatus": "completed",
+ "solutions": [
+ {
+ "UUID": "a7785f64-5717-4562-b3fc-2c963f66afa6",
+ "invariantUUID": "9fh85f64-5717-4562-b3fc-2c963f66afa6",
+ "NSSIName": "nssi_test_0211",
+ "NSSIId": "e1041cdc-12da-4f36-b84e-68c380e9cd47"
+
+ }
+ ],
+ "statusMessage": "",
+ "transactionId": "t670f1ee-6c54-4b01-90e6-d701748f0851"
+}
diff --git a/test/apps/slice_selection/slice_policies.txt b/test/apps/slice_selection/slice_policies.txt
index 55c934b..7eb55de 100644
--- a/test/apps/slice_selection/slice_policies.txt
+++ b/test/apps/slice_selection/slice_policies.txt
@@ -1,4 +1,5 @@
-subscriber_policy_URLLC_1.json
-thresholdPolicy_URLLC_Core_1.json
-vnfPolicy_URLLC_Core_1.json
-aggregationPolicy_URLLC_1.json
+query_policy_nsi.json
+threshold_policy_nsi.json
+vnf_policy_nsi_shared_case.json
+opt_policy_nsi_reuse.json
+
diff --git a/test/apps/slice_selection/subnet_policies.txt b/test/apps/slice_selection/subnet_policies.txt
new file mode 100644
index 0000000..649672d
--- /dev/null
+++ b/test/apps/slice_selection/subnet_policies.txt
@@ -0,0 +1,5 @@
+query_policy_nsi.json
+threshold_policy_nsi.json
+vnf_policy_nssi_shared.json
+opt_policy_nssi.json
+
diff --git a/test/apps/slice_selection/test_remote_opt_processor.py b/test/apps/slice_selection/test_remote_opt_processor.py
index 5321880..7c2d191 100644
--- a/test/apps/slice_selection/test_remote_opt_processor.py
+++ b/test/apps/slice_selection/test_remote_opt_processor.py
@@ -20,7 +20,7 @@ import json
import unittest
from requests import RequestException, Response
-from apps.slice_selection.optimizers.conductor.remote_opt_processor import process_nsi_selection_opt
+from apps.slice_selection.optimizers.conductor.remote_opt_processor import SliceSelectionOptimizer
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
@@ -37,31 +37,33 @@ class TestRemoteOptProcessor(unittest.TestCase):
"deployment": "config/osdf_config.yaml",
"core": "config/common_config.yaml"
}
+ slice_spec = "config/slicing_config.yaml"
+ self.slice_config = config_loader.load_config_file(slice_spec)
self.osdf_config = DotDict(config_loader.all_configs(**self.config_spec))
+ self.patcher_RestClient = patch(
+ 'osdf.utils.interfaces.RestClient.request', return_value=MagicMock())
+ self.mock_rc = self.patcher_RestClient.start()
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'
+ request_file = main_dir + 'test/apps/slice_selection/nsi_selection_request.json'
not_shared_request_file = main_dir + 'test/apps/slice_selection/not_shared_nsi_request.json'
#response files
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'
no_solution_response_file = main_dir + 'test/apps/slice_selection/no_recomm_nsi_response.json'
- not_shared_response_file = main_dir + 'test/apps/slice_selection/not_shared_nsi_response.json'
error_response_file = main_dir + 'test/apps/slice_selection/nsi_error_response.json'
- not_shared_request_json = json_from_file(not_shared_request_file)
- not_shared_response_json = json_from_file(not_shared_response_file)
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)
no_solution_response_json = json_from_file(no_solution_response_file)
error_response_json = json_from_file(error_response_file)
- policies_path = main_dir + 'test/policy-local-files'
+ policies_path = main_dir + 'test/policy-local-files/slice-selection-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)
@@ -69,36 +71,41 @@ class TestRemoteOptProcessor(unittest.TestCase):
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
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))
+ slice_select_opt = SliceSelectionOptimizer(self.osdf_config, self.slice_config, request_json, 'NSI')
+ slice_select_opt.process_slice_selection_opt()
+ self.mock_rc.assert_called_with(json=new_solution_response_json, noresponse=True)
self.patcher_req.stop()
+
# shared solution
+ request_json['preferReuse'] = True
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))
+ slice_select_opt = SliceSelectionOptimizer(self.osdf_config, self.slice_config, request_json, 'NSI')
+ slice_select_opt.process_slice_selection_opt()
+ self.mock_rc.assert_called_with(json=shared_solution_response_json, noresponse=True)
self.patcher_req.stop()
- # not-shared solution
- self.assertEquals(not_shared_response_json,
- process_nsi_selection_opt(not_shared_request_json, self.osdf_config))
+
# no recommendation
no_solution_conductor_response_file = 'test/apps/slice_selection/no_rec.json'
no_solution_conductor_response = json_from_file(no_solution_conductor_response_file)
self.patcher_req = patch('osdf.adapters.conductor.conductor.request',
return_value=no_solution_conductor_response)
self.Mock_req = self.patcher_req.start()
- self.assertEquals(no_solution_response_json,
- process_nsi_selection_opt(request_json, self.osdf_config))
+ slice_select_opt.process_slice_selection_opt()
+ self.mock_rc.assert_called_with(json=no_solution_response_json, noresponse=True)
self.patcher_req.stop()
+ # Exception
conductor_error_response_file = 'test/apps/slice_selection/conductor_error_response.json'
conductor_error_response = json_from_file(conductor_error_response_file)
@@ -107,17 +114,52 @@ class TestRemoteOptProcessor(unittest.TestCase):
self.patcher_req = patch('osdf.adapters.conductor.conductor.request',
side_effect=RequestException(response=response))
self.Mock_req = self.patcher_req.start()
- self.assertEquals(error_response_json, process_nsi_selection_opt(request_json, self.osdf_config))
+ slice_select_opt.process_slice_selection_opt()
+ self.mock_rc.assert_called_with(json=error_response_json, noresponse=True)
+ self.patcher_req.stop()
+
+ self.patcher_req = patch('osdf.adapters.conductor.conductor.request',
+ side_effect=Exception("Some error message"))
+ self.Mock_req = self.patcher_req.start()
+ slice_select_opt.process_slice_selection_opt()
+ self.mock_rc.assert_called_with(json=error_response_json, noresponse=True)
self.patcher_req.stop()
+ def test_process_nssi_selection_opt(self):
+ main_dir = ""
+ request_file = main_dir + 'test/apps/slice_selection/nssi_selection_request.json'
+ # response files
+ shared_solution_response_file = main_dir + 'test/apps/slice_selection/shared_solution_nssi_response.json'
+ error_response_file = main_dir + 'test/apps/slice_selection/nssi_error_response.json'
+
+ request_json = json_from_file(request_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-selection-files'
+ slice_policies_file = main_dir + 'test/apps/slice_selection/subnet_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()
+
+ shared_solution_conductor_response_file = 'test/apps/slice_selection/nssi_conductor_response.json'
+ shared_solution_conductor_response = json_from_file(shared_solution_conductor_response_file)
self.patcher_req = patch('osdf.adapters.conductor.conductor.request',
- side_effect=Exception("test_exception"))
+ return_value=shared_solution_conductor_response)
self.Mock_req = self.patcher_req.start()
- self.assertEquals('test_exception',
- process_nsi_selection_opt(request_json, self.osdf_config).get('statusMessage'))
+ slice_select_opt = SliceSelectionOptimizer(self.osdf_config, self.slice_config, request_json, 'NSSI')
+ slice_select_opt.process_slice_selection_opt()
+ self.mock_rc.assert_called_with(json=shared_solution_response_json, noresponse=True)
self.patcher_req.stop()
+ request_json['sliceProfile']['resourceSharingLevel'] = "not-shared"
+ slice_select_opt = SliceSelectionOptimizer(self.osdf_config, self.slice_config, request_json, 'NSSI')
+ slice_select_opt.process_slice_selection_opt()
+ self.mock_rc.assert_called_with(json=error_response_json, noresponse=True)
+
if __name__ == "__main__":
unittest.main()
-
diff --git a/test/conductor/test_conductor_calls.py b/test/conductor/test_conductor_calls.py
index d342fa5..8b4411d 100644
--- a/test/conductor/test_conductor_calls.py
+++ b/test/conductor/test_conductor_calls.py
@@ -35,6 +35,10 @@ class TestConductorCalls(unittest.TestCase):
self.osdf_config = DotDict(config_loader.all_configs(**self.config_spec))
self.lp = self.osdf_config.core.get('osdf_temp', {}).get('local_policies', {}
).get('placement_policy_files_vcpe')
+ self.template_fields = {
+ 'location_enabled': True,
+ 'version': '2017-10-10'
+ }
def tearDown(self):
pass
@@ -46,7 +50,8 @@ 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, True, self.osdf_config, policies)
+ conductor.request(req_info, demands, request_parameters, service_info, self.template_fields,
+ self.osdf_config, policies)
def test_request_vfmod(self):
req_json = json_from_file("./test/placement-tests/request_vfmod.json")
@@ -55,7 +60,8 @@ 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, True, self.osdf_config, policies)
+ conductor.request(req_info, demands, request_parameters, service_info, self.template_fields,
+ self.osdf_config, policies)
if __name__ == "__main__":
diff --git a/test/conductor/test_conductor_translation.py b/test/conductor/test_conductor_translation.py
index 8b6c0a1..2a600e4 100644
--- a/test/conductor/test_conductor_translation.py
+++ b/test/conductor/test_conductor_translation.py
@@ -40,6 +40,9 @@ class TestConductorTranslation(unittest.TestCase):
self.request_vfmod_json = json_from_file(parameter_data_file)
self.policies = [json_from_file(policy_data_path + '/' + name) for name in valid_policies_files]
+ self.optimization_policies = [json_from_file(policy_data_path + '/'
+ + "slice-selection-files/opt_policy_nsi_reuse.json")]
+
def tearDown(self):
pass
@@ -58,6 +61,26 @@ class TestConductorTranslation(unittest.TestCase):
res = tr.gen_demands(self.request_vfmod_json['placementInfo']['placementDemands'], vnf_policies)
assert res is not None
+ def test_gen_optimization_policy(self):
+ expected = [{
+ "goal": "minimize",
+ "operation_function": {
+ "operator": "sum",
+ "operands": [
+ {
+ "function": "attribute",
+ "params": {
+ "attribute": "creation_cost",
+ "demand": "embb-nst"
+ }
+ }
+ ]
+ }
+ }]
+ self.assertEqual(expected,
+ tr.gen_optimization_policy(self.request_vfmod_json['placementInfo']['placementDemands'],
+ self.optimization_policies))
+
if __name__ == "__main__":
unittest.main()
diff --git a/test/policy-local-files/slice-selection-files/opt_policy_nsi_reuse.json b/test/policy-local-files/slice-selection-files/opt_policy_nsi_reuse.json
new file mode 100644
index 0000000..33dbfee
--- /dev/null
+++ b/test/policy-local-files/slice-selection-files/opt_policy_nsi_reuse.json
@@ -0,0 +1,38 @@
+{
+ "OSDF_FRANKFURT.minimizeCost_URLLC": {
+ "metadata": {
+ "policy-id": "OSDF_FRANKFURT.minimizeCost_URLLC",
+ "policy-version": 1
+ },
+ "properties": {
+ "geography": [],
+ "identity": "optimization",
+ "goal": "minimize",
+ "operation_function": {
+ "operator": "sum",
+ "operands": [
+ {
+ "function": "attribute",
+ "params": {
+ "attribute": "creation_cost",
+ "demand": "embb-nst"
+ }
+ }
+ ]
+ },
+ "resources": [
+ "embb-nst"
+ ],
+ "scope": [
+ "REUSE",
+ "SHARED"
+ ],
+ "services": [
+ "embb-nst"
+ ]
+ },
+ "type": "onap.policies.optimization.resource.OptimizationPolicy",
+ "type_version": "2.0.0",
+ "version": "1.0.0"
+ }
+}
diff --git a/test/policy-local-files/slice-selection-files/opt_policy_nssi.json b/test/policy-local-files/slice-selection-files/opt_policy_nssi.json
new file mode 100644
index 0000000..2b7cbf8
--- /dev/null
+++ b/test/policy-local-files/slice-selection-files/opt_policy_nssi.json
@@ -0,0 +1,37 @@
+{
+ "OSDF_FRANKFURT.minimizeCost_URLLC": {
+ "metadata": {
+ "policy-id": "OSDF_FRANKFURT.minimizeCost_URLLC",
+ "policy-version": 1
+ },
+ "properties": {
+ "geography": [],
+ "identity": "optimization",
+ "goal": "minimize",
+ "operation_function": {
+ "operator": "sum",
+ "operands": [
+ {
+ "function": "attribute",
+ "params": {
+ "attribute": "latency",
+ "demand": "embb-cn"
+ }
+ }
+ ]
+ },
+ "resources": [
+ "embb-cn"
+ ],
+ "scope": [
+ "SHARED"
+ ],
+ "services": [
+ "embb-cn"
+ ]
+ },
+ "type": "onap.policies.optimization.resource.OptimizationPolicy",
+ "type_version": "2.0.0",
+ "version": "1.0.0"
+ }
+}
diff --git a/test/policy-local-files/slice-selection-files/query_policy_nsi.json b/test/policy-local-files/slice-selection-files/query_policy_nsi.json
new file mode 100644
index 0000000..49b1ca6
--- /dev/null
+++ b/test/policy-local-files/slice-selection-files/query_policy_nsi.json
@@ -0,0 +1,44 @@
+
+ {
+ "OSDF_GUILIN.queryPolicy_URLLC":{
+ "type":"onap.policies.optimization.service.QueryPolicy",
+ "version":"1.0.0",
+ "type_version":"1.0.0",
+ "metadata":{
+ "policy-id":"OSDF_GUILIN.queryPolicy_URLLC",
+ "policy-version":1
+ },
+ "properties":{
+ "scope":[
+ "OSDF_GUILIN"
+ ],
+ "services":[
+ "embb-nst"
+ ],
+ "geography":[],
+ "identity":"queryPolicy_URLLC",
+ "queryProperties":[
+ {
+ "attribute":"latency",
+ "attribute_location":"latency"
+ },
+ {
+ "attribute":"reliability",
+ "attribute_location":"reliability"
+ },
+ {
+ "attribute":"an_latency",
+ "attribute_location":"an_latency"
+ },
+ {
+ "attribute":"cn_latency",
+ "attribute_location":"cn_latency"
+ },
+ {
+ "attribute":"tn_bh_latency",
+ "attribute_location":"tn_bh_latency"
+ }
+ ]
+ }
+ }
+ }
diff --git a/test/policy-local-files/slice-selection-files/query_policy_nssi.json b/test/policy-local-files/slice-selection-files/query_policy_nssi.json
new file mode 100644
index 0000000..5e5893b
--- /dev/null
+++ b/test/policy-local-files/slice-selection-files/query_policy_nssi.json
@@ -0,0 +1,32 @@
+
+ {
+ "OSDF_GUILIN.queryPolicy_URLLC":{
+ "type":"onap.policies.optimization.service.QueryPolicy",
+ "version":"1.0.0",
+ "type_version":"1.0.0",
+ "metadata":{
+ "policy-id":"OSDF_GUILIN.queryPolicy_URLLC",
+ "policy-version":1
+ },
+ "properties":{
+ "scope":[
+ "SHARED"
+ ],
+ "services":[
+ "embb-cn"
+ ],
+ "geography":[],
+ "identity":"queryPolicy_URLLC",
+ "queryProperties":[
+ {
+ "attribute":"latency",
+ "attribute_location":"latency"
+ },
+ {
+ "attribute":"reliability",
+ "attribute_location":"reliability"
+ }
+ ]
+ }
+ }
+ }
diff --git a/test/policy-local-files/slice-selection-files/threshold_policy_nsi.json b/test/policy-local-files/slice-selection-files/threshold_policy_nsi.json
new file mode 100644
index 0000000..6e518dd
--- /dev/null
+++ b/test/policy-local-files/slice-selection-files/threshold_policy_nsi.json
@@ -0,0 +1,46 @@
+
+ {
+ "OSDF_GUILIN.Threshold_URLLC":{
+ "metadata":{
+ "policy-id":"OSDF_GUILIN.Threshold_URLLC",
+ "policy-version":1
+ },
+ "properties":{
+ "geography":[
+
+ ],
+ "identity":"Threshold_URLLC",
+ "resources":[
+ "embb-nst"
+ ],
+ "scope":[
+ "OSDF_GUILIN"
+ ],
+ "services":[
+ "embb-nst"
+ ],
+ "thresholdProperties":[
+ {
+ "attribute":"latency",
+ "operator":"lte",
+ "threshold":{
+ "get_param":"latency"
+ },
+ "unit":"ms"
+ },
+ {
+ "attribute":"reliability",
+ "operator":"gte",
+ "threshold":{
+ "get_param":"reliability"
+ },
+ "unit":""
+ }
+ ]
+ },
+ "type":"onap.policies.optimization.resource.ThresholdPolicy",
+ "type_version":"1.0.0",
+ "version":"1.0.0"
+ }
+ }
+
diff --git a/test/policy-local-files/slice-selection-files/threshold_policy_nssi.json b/test/policy-local-files/slice-selection-files/threshold_policy_nssi.json
new file mode 100644
index 0000000..72a24da
--- /dev/null
+++ b/test/policy-local-files/slice-selection-files/threshold_policy_nssi.json
@@ -0,0 +1,46 @@
+
+ {
+ "OSDF_GUILIN.Threshold_URLLC":{
+ "metadata":{
+ "policy-id":"OSDF_GUILIN.Threshold_URLLC",
+ "policy-version":1
+ },
+ "properties":{
+ "geography":[
+
+ ],
+ "identity":"Threshold_URLLC",
+ "resources":[
+ "embb-cn"
+ ],
+ "scope":[
+ "SHARED"
+ ],
+ "services":[
+ "embb-cn"
+ ],
+ "thresholdProperties":[
+ {
+ "attribute":"latency",
+ "operator":"lte",
+ "threshold":{
+ "get_param":"latency"
+ },
+ "unit":"ms"
+ },
+ {
+ "attribute":"reliability",
+ "operator":"gte",
+ "threshold":{
+ "get_param":"reliability"
+ },
+ "unit":""
+ }
+ ]
+ },
+ "type":"onap.policies.optimization.resource.ThresholdPolicy",
+ "type_version":"1.0.0",
+ "version":"1.0.0"
+ }
+ }
+
diff --git a/test/policy-local-files/slice-selection-files/vnf_policy_nsi_non_shared_case.json b/test/policy-local-files/slice-selection-files/vnf_policy_nsi_non_shared_case.json
new file mode 100644
index 0000000..1774780
--- /dev/null
+++ b/test/policy-local-files/slice-selection-files/vnf_policy_nsi_non_shared_case.json
@@ -0,0 +1,76 @@
+
+ {
+ "OSDF_GUILIN.vnfPolicy_URLLC":{
+ "metadata":{
+ "policy-id":"OSDF_GUILIN.vnfPolicy_URLLC",
+ "policy-version":1
+ },
+ "properties":{
+ "identity":"vnf_URLLC",
+ "resources":["embb-nst"],
+ "scope":[
+ "OSDF_GUILIN",
+ "non-shared"
+ ],
+ "services":[
+ "embb-nst"
+ ],
+ "geography":[],
+ "vnfProperties":[
+ {
+ "attributes":{
+ "core":{
+ "latency":{
+ "max":{"get_param":"latency"},
+ "min":{"get_param":"cn_latency"},
+ "steps":1
+ },
+ "reliability":{
+ "values":[
+ 99.9,
+ 99.999
+ ]
+ }
+ },
+ "ran":{
+ "latency":{
+ "max":{"get_param":"latency"},
+ "min":{"get_param":"an_latency"},
+ "steps":1
+ },
+ "reliability":{
+ "values":[
+ 99.9,
+ 99.9
+ ]
+ }
+ },
+ "transport":{
+ "latency":{
+ "max":{"get_param":"latency"},
+ "min":{"get_param":"tn_bh_latency"},
+ "steps":1
+ },
+ "reliability":{
+ "values":[
+ 99.9,
+ 99.99
+ ]
+ }
+ }
+ },
+ "inventoryProvider":"generator",
+ "inventoryType":"slice_profiles",
+ "unique":"true",
+ "defaultAttributes":{
+ "creation-cost" : 0.9
+ }
+ }
+ ]
+ },
+ "type":"onap.policies.optimization.resource.VnfPolicy",
+ "type_version":"1.0.0",
+ "version":"1.0.0"
+ }
+ }
+
diff --git a/test/policy-local-files/slice-selection-files/vnf_policy_nsi_shared_case.json b/test/policy-local-files/slice-selection-files/vnf_policy_nsi_shared_case.json
new file mode 100644
index 0000000..9932cc1
--- /dev/null
+++ b/test/policy-local-files/slice-selection-files/vnf_policy_nsi_shared_case.json
@@ -0,0 +1,90 @@
+
+ {
+ "OSDF_GUILIN.vnfPolicy_URLLC":{
+ "metadata":{
+ "policy-id":"OSDF_GUILIN.vnfPolicy_URLLC",
+ "policy-version":1
+ },
+ "properties":{
+ "identity":"vnf_URLLC",
+ "resources":["embb-nst"],
+ "scope":[
+ "OSDF_GUILIN",
+ "shared"
+ ],
+ "services":[
+ "embb-nst"
+ ],
+ "geography":[],
+ "vnfProperties":[
+ {
+ "attributes":{
+ "modelInvariantId":"bfbg3636-e39c-iidd-0987-27c28f4oo3",
+ "modelVersionId":"bfbg3636-e39c-iidd-0987-27c28f4d33",
+ "environment-context":"shared",
+ "service-role":"nsi"
+ },
+ "inventoryProvider":"aai",
+ "inventoryType":"nsi",
+ "unique":"true",
+ "defaultAttributes":{
+ "creation-cost" : 0.1
+ }
+ },
+ {
+ "attributes":{
+ "core":{
+ "latency":{
+ "max":{"get_param":"latency"},
+ "min":{"get_param":"cn_latency"},
+ "steps":1
+ },
+ "reliability":{
+ "values":[
+ 99.9,
+ 99.999
+ ]
+ }
+ },
+ "ran":{
+ "latency":{
+ "max":{"get_param":"latency"},
+ "min":{"get_param":"an_latency"},
+ "steps":1
+ },
+ "reliability":{
+ "values":[
+ 99.9,
+ 99.9
+ ]
+ }
+ },
+ "transport":{
+ "latency":{
+ "max":{"get_param":"latency"},
+ "min":{"get_param":"tn_bh_latency"},
+ "steps":1
+ },
+ "reliability":{
+ "values":[
+ 99.9,
+ 99.99
+ ]
+ }
+ }
+ },
+ "inventoryProvider":"generator",
+ "inventoryType":"slice_profiles",
+ "unique":"true",
+ "defaultAttributes":{
+ "creation-cost" : 0.9
+ }
+ }
+ ]
+ },
+ "type":"onap.policies.optimization.resource.VnfPolicy",
+ "type_version":"1.0.0",
+ "version":"1.0.0"
+ }
+ }
+
diff --git a/test/policy-local-files/slice-selection-files/vnf_policy_nssi_shared.json b/test/policy-local-files/slice-selection-files/vnf_policy_nssi_shared.json
new file mode 100644
index 0000000..bd12f84
--- /dev/null
+++ b/test/policy-local-files/slice-selection-files/vnf_policy_nssi_shared.json
@@ -0,0 +1,37 @@
+
+ {
+ "OSDF_GUILIN.vnfPolicy_URLLC":{
+ "metadata":{
+ "policy-id":"OSDF_GUILIN.vnfPolicy_URLLC",
+ "policy-version":1
+ },
+ "properties":{
+ "identity":"vnf_URLLC",
+ "resources":["embb-cn"],
+ "scope":[
+ "SHARED"
+ ],
+ "services":[
+ "embb-cn"
+ ],
+ "geography":[],
+ "vnfProperties":[
+ {
+ "attributes":{
+ "modelInvariantId":"bfbg3636-e39c-iidd-0987-27c28f4oo3",
+ "modelVersionId":"bfbg3636-e39c-iidd-0987-27c28f4d33",
+ "environment-context":"shared",
+ "service-role":"nssi"
+ },
+ "inventoryProvider":"aai",
+ "inventoryType":"nssi",
+ "unique":"true"
+ }
+ ]
+ },
+ "type":"onap.policies.optimization.resource.VnfPolicy",
+ "type_version":"1.0.0",
+ "version":"1.0.0"
+ }
+ }
+
diff --git a/test/test_ConductorApiBuilder.py b/test/test_ConductorApiBuilder.py
index b3dd97c..34f6989 100644
--- a/test/test_ConductorApiBuilder.py
+++ b/test/test_ConductorApiBuilder.py
@@ -43,6 +43,10 @@ class TestConductorApiBuilder(unittest.TestCase):
parameter_data_file = self.main_dir + "test/placement-tests/request_placement_vfmod.json"
self.request_placement_vfmod_json = json_from_file(parameter_data_file)
self.policies = [json_from_file(policy_data_path + '/' + name) for name in valid_policies_files]
+ self.template_fields = {
+ 'location_enabled': True,
+ 'version': '2017-10-10'
+ }
def test_conductor_api_call_builder(self):
main_dir = self.main_dir
@@ -53,8 +57,8 @@ 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, True, policies,
- local_config, self.conductor_api_template)
+ templ_string = conductor_api_builder(req_info, demands, request_parameters, service_info, self.template_fields,
+ policies, local_config, self.conductor_api_template)
templ_json = json.loads(templ_string)
self.assertEqual(templ_json["name"], "yyy-yyy-yyyy")
@@ -66,8 +70,8 @@ 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, True, policies,
- local_config, self.conductor_api_template)
+ templ_string = conductor_api_builder(req_info, demands, request_parameters, service_info, self.template_fields,
+ policies, local_config, self.conductor_api_template)
templ_json = json.loads(templ_string)
self.assertEqual(templ_json, self.request_placement_vfmod_json)
diff --git a/tox.ini b/tox.ini
index 1606b9b..3b15855 100644
--- a/tox.ini
+++ b/tox.ini
@@ -9,11 +9,15 @@ basepython=python3
setenv =
OSDF_CONFIG_FILE={toxinidir}/test/config/osdf_config.yaml
commands =
+ /bin/cp config/slicing_config.yaml test/config/
+ /bin/cp config/slicing_config.yaml test/functest/simulators/simulated-config/
/bin/bash test/functest/scripts/start-simulators.sh
coverage run --module pytest --junitxml xunit-results.xml
coverage xml --omit=".tox/py3/*","test/*"
coverage report -m --omit=".tox/py3/*","test/*"
/bin/bash test/functest/scripts/stop-simulators.sh
+ /bin/rm test/config/slicing_config.yaml
+ /bin/rm test/functest/simulators/simulated-config/slicing_config.yaml
# TODO: need to update the above "omit" when we package osdf as pip-installable
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test/test-requirements.txt
@@ -31,6 +35,7 @@ commands = bash -c "pylint --reports=y osdf apps runtime| tee pylint.out"
basepython=python3.6
[testenv:flake8diff]
+basepython=python3.6
whitelist_externals=bash
deps = hacking>=2.0.0
commands =
@@ -39,5 +44,5 @@ commands =
[flake8]
select = E,H,W,F
max-line-length = 119
-ignore =
+ignore = W503 #conflict with W504
per-file-ignores=