From edf98746a52408386efab26143778198b0efd3c5 Mon Sep 17 00:00:00 2001 From: dhebeha Date: Sat, 5 Sep 2020 20:16:48 +0530 Subject: Add support to process NSI selection request Issue-ID: OPTFRA-802 Signed-off-by: dhebeha Signed-off-by: krishnaa96 Change-Id: I85d951061abc697714425bd223b89102d4f2ede9 --- .../optimizers/conductor/remote_opt_processor.py | 37 ++-- .../models/api/nsi_selection_request.py | 12 +- .../optimizers/conductor/remote_opt_processor.py | 176 ++++++++-------- .../optimizers/conductor/response_processor.py | 227 ++++++++------------- config/common_config.yaml | 30 ++- config/has_config.yaml | 3 +- config/slicing_config.yaml | 82 ++++++++ osdf/adapters/conductor/api_builder.py | 19 +- osdf/adapters/conductor/conductor.py | 12 +- .../conductor/templates/conductor_interface.json | 2 +- osdf/adapters/conductor/translation.py | 75 ++++--- osdf/config/base.py | 5 + osdfapp.py | 17 +- .../new_solution_conductor_response.json | 90 ++------ .../slice_selection/new_solution_nsi_response.json | 77 +++---- .../slice_selection/no_recomm_nsi_response.json | 32 +-- .../slice_selection/not_shared_nsi_request.json | 31 --- .../slice_selection/not_shared_nsi_response.json | 37 ---- test/apps/slice_selection/nsi_error_response.json | 35 +--- .../slice_selection/nsi_selection_request.json | 71 ++++--- .../slice_selection/nssi_conductor_response.json | 53 +++++ test/apps/slice_selection/nssi_error_response.json | 7 + .../slice_selection/nssi_selection_request.json | 7 +- .../shared_solution_conductor_response.json | 102 +++------ .../shared_solution_nsi_response.json | 57 +----- .../shared_solution_nssi_response.json | 15 ++ test/apps/slice_selection/slice_policies.txt | 9 +- test/apps/slice_selection/subnet_policies.txt | 5 + .../slice_selection/test_remote_opt_processor.py | 80 ++++++-- test/conductor/test_conductor_calls.py | 10 +- test/conductor/test_conductor_translation.py | 23 +++ .../opt_policy_nsi_reuse.json | 38 ++++ .../slice-selection-files/opt_policy_nssi.json | 37 ++++ .../slice-selection-files/query_policy_nsi.json | 44 ++++ .../slice-selection-files/query_policy_nssi.json | 32 +++ .../threshold_policy_nsi.json | 46 +++++ .../threshold_policy_nssi.json | 46 +++++ .../vnf_policy_nsi_non_shared_case.json | 76 +++++++ .../vnf_policy_nsi_shared_case.json | 90 ++++++++ .../vnf_policy_nssi_shared.json | 37 ++++ test/test_ConductorApiBuilder.py | 12 +- tox.ini | 7 +- 42 files changed, 1173 insertions(+), 730 deletions(-) create mode 100644 config/slicing_config.yaml delete mode 100644 test/apps/slice_selection/not_shared_nsi_request.json delete mode 100644 test/apps/slice_selection/not_shared_nsi_response.json create mode 100644 test/apps/slice_selection/nssi_conductor_response.json create mode 100644 test/apps/slice_selection/nssi_error_response.json create mode 100644 test/apps/slice_selection/shared_solution_nssi_response.json create mode 100644 test/apps/slice_selection/subnet_policies.txt create mode 100644 test/policy-local-files/slice-selection-files/opt_policy_nsi_reuse.json create mode 100644 test/policy-local-files/slice-selection-files/opt_policy_nssi.json create mode 100644 test/policy-local-files/slice-selection-files/query_policy_nsi.json create mode 100644 test/policy-local-files/slice-selection-files/query_policy_nssi.json create mode 100644 test/policy-local-files/slice-selection-files/threshold_policy_nsi.json create mode 100644 test/policy-local-files/slice-selection-files/threshold_policy_nssi.json create mode 100644 test/policy-local-files/slice-selection-files/vnf_policy_nsi_non_shared_case.json create mode 100644 test/policy-local-files/slice-selection-files/vnf_policy_nsi_shared_case.json create mode 100644 test/policy-local-files/slice-selection-files/vnf_policy_nssi_shared.json 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= -- cgit 1.2.3-korg