From fcb37e97e37137d3111924e993e75fdb83c2a0a0 Mon Sep 17 00:00:00 2001 From: krishnaa96 Date: Mon, 23 Mar 2020 13:11:07 +0530 Subject: Add functionality to support NSI selection Issue-ID: OPTFRA-677 Signed-off-by: krishnaa96 Change-Id: Ibc51e15fce4692a445df400053060d3a6977b4ce --- apps/slice_selection/__init__.py | 0 apps/slice_selection/models/api/__init__.py | 17 +++ .../models/api/nsi_selection_request.py | 54 +++++++++ .../models/api/nsi_selection_response.py | 73 ++++++++++++ apps/slice_selection/optimizers/__init__.py | 17 +++ .../optimizers/conductor/__init__.py | 0 .../optimizers/conductor/remote_opt_processor.py | 104 +++++++++++++++++ .../optimizers/conductor/response_processor.py | 123 +++++++++++++++++++++ 8 files changed, 388 insertions(+) create mode 100644 apps/slice_selection/__init__.py create mode 100644 apps/slice_selection/models/api/__init__.py create mode 100644 apps/slice_selection/models/api/nsi_selection_request.py create mode 100644 apps/slice_selection/models/api/nsi_selection_response.py create mode 100644 apps/slice_selection/optimizers/__init__.py create mode 100644 apps/slice_selection/optimizers/conductor/__init__.py create mode 100644 apps/slice_selection/optimizers/conductor/remote_opt_processor.py create mode 100644 apps/slice_selection/optimizers/conductor/response_processor.py (limited to 'apps/slice_selection') diff --git a/apps/slice_selection/__init__.py b/apps/slice_selection/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/slice_selection/models/api/__init__.py b/apps/slice_selection/models/api/__init__.py new file mode 100644 index 0000000..b45f74d --- /dev/null +++ b/apps/slice_selection/models/api/__init__.py @@ -0,0 +1,17 @@ +# ------------------------------------------------------------------------- +# Copyright (C) 2020 Wipro Limited. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------- +# diff --git a/apps/slice_selection/models/api/nsi_selection_request.py b/apps/slice_selection/models/api/nsi_selection_request.py new file mode 100644 index 0000000..b7f3fbd --- /dev/null +++ b/apps/slice_selection/models/api/nsi_selection_request.py @@ -0,0 +1,54 @@ +# ------------------------------------------------------------------------- +# Copyright (C) 2020 Wipro Limited. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------- +# + +from osdf.models.api.common import OSDFModel +from schematics.types import BaseType, StringType, URLType, IntType, BooleanType +from schematics.types.compound import ModelType, ListType, DictType + + +class RequestInfo(OSDFModel): + """Info for northbound request from client such as SO""" + transactionId = StringType(required=True) + requestId = StringType(required=True) + callbackUrl = URLType(required=True) + callbackHeader = DictType(BaseType) + sourceId = StringType(required=True) + timeout = IntType() + + +class NSTInfo(OSDFModel): + """Preferred candidate for a resource (sent as part of a request from client)""" + modelInvariantId = StringType(required=True) + modelVersionId = StringType(required=True) + modelName = StringType() + modelType = StringType() + modelVersion = StringType() + modelCustomizationName = StringType() + + +class ServiceInfo(OSDFModel): + serviceInstanceId = StringType(required=True) + serviceName = StringType(required=True) + + +class NSISelectionAPI(OSDFModel): + """Request for nsi selection (specific to optimization and additional metadata""" + requestInfo = ModelType(RequestInfo, required=True) + NSTInfoList = ListType(ModelType(NSTInfo), required=True) + serviceInfo = ModelType(ServiceInfo, required=True) + serviceProfile = DictType(BaseType, required=True) diff --git a/apps/slice_selection/models/api/nsi_selection_response.py b/apps/slice_selection/models/api/nsi_selection_response.py new file mode 100644 index 0000000..9547200 --- /dev/null +++ b/apps/slice_selection/models/api/nsi_selection_response.py @@ -0,0 +1,73 @@ +# ------------------------------------------------------------------------- +# Copyright (C) 2020 Wipro Limited. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------- +# + +from osdf.models.api.common import OSDFModel +from schematics.types import BaseType, StringType +from schematics.types.compound import ModelType, ListType, DictType + + +# TODO: update osdf.models +class SharedNSISolution(OSDFModel): + invariantUUID = StringType(required=True) + UUID = StringType(required=True) + NSIName = StringType(required=True) + NSIId = StringType(required=True) + matchLevel = StringType(required=True) + + +class NSSTInfo(OSDFModel): + invariantUUID = StringType(required=True) + UUID = StringType(required=True) + NSSTName = StringType(required=True) + + +class NSSIInfo(OSDFModel): + NSSIName = StringType(required=True) + NSSIId = StringType(required=True) + matchLevel = StringType(required=True) + + +class NSSISolution(OSDFModel): + sliceProfile = DictType(BaseType) + NSSTInfo = ModelType(NSSTInfo, required=True) + NSSISolution = ModelType(NSSIInfo, required=True) + + +class NSTInfo(OSDFModel): + invariantUUID = StringType(required=True) + UUID = StringType(required=True) + NSTName = StringType(required=True) + + +class NewNSISolution(OSDFModel): + matchLevel = StringType(required=True) + NSTInfo = ModelType(NSTInfo, required=True) + NSSISolutions = ListType(ModelType(NSSISolution)) + + +class Solution(OSDFModel): + sharedNSISolutions = ListType(ModelType(SharedNSISolution)) + newNSISolutions = ListType(ModelType(NewNSISolution)) + + +class NSISelectionResponse(OSDFModel): + transactionId = StringType(required=True) + requestId = StringType(required=True) + requestStatus = StringType(required=True) + statusMessage = StringType() + solutions = ModelType(Solution, required=True) diff --git a/apps/slice_selection/optimizers/__init__.py b/apps/slice_selection/optimizers/__init__.py new file mode 100644 index 0000000..b45f74d --- /dev/null +++ b/apps/slice_selection/optimizers/__init__.py @@ -0,0 +1,17 @@ +# ------------------------------------------------------------------------- +# Copyright (C) 2020 Wipro Limited. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------- +# diff --git a/apps/slice_selection/optimizers/conductor/__init__.py b/apps/slice_selection/optimizers/conductor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/slice_selection/optimizers/conductor/remote_opt_processor.py b/apps/slice_selection/optimizers/conductor/remote_opt_processor.py new file mode 100644 index 0000000..a44fc4e --- /dev/null +++ b/apps/slice_selection/optimizers/conductor/remote_opt_processor.py @@ -0,0 +1,104 @@ +# ------------------------------------------------------------------------- +# Copyright (C) 2020 Wipro Limited. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------- +# + +""" +Module for processing slice selection request +""" + +import json +import traceback +from requests import RequestException + +from apps.slice_selection.optimizers.conductor.response_processor \ + import conductor_response_processor, conductor_error_response_processor +from osdf.adapters.conductor import conductor +from osdf.adapters.policy.interface import get_policies +from osdf.adapters.policy.utils import group_policies_gen +from osdf.logging.osdf_logging import error_log, debug_log +from osdf.utils.mdc_utils import mdc_from_json + + +def process_nsi_selection_opt(request_json, osdf_config): + """Process the nsi selection request from API layer + :param request_json: api request + :param policies: flattened policies corresponding to this request + :param osdf_config: configuration specific to OSDF app + :return: response as a dictionary + """ + req_info = request_json['requestInfo'] + try: + mdc_from_json(request_json) + + overall_recommendations = dict() + nst_info_map = dict() + for nst_info in request_json["NSTInfoList"]: + nst_name = nst_info["modelName"] + nst_info_map["nst_name"] = {"NSTName": nst_name, + "UUID": nst_info["modelVersionId"], + "invariantUUID": nst_info["modelInvariantId"]} + + policy_request_json = request_json.copy() + policy_request_json['serviceInfo']['serviceName'] = nst_name + + policies = get_policies(policy_request_json, "slice_selection") + + demands = get_slice_demands(nst_name, policies, osdf_config.core) + + request_parameters = {} + service_info = {} + req_info['numSolutions'] = 'all' + resp = conductor.request(req_info, demands, request_parameters, service_info, False, + osdf_config, policies) + debug_log.debug("Response from conductor {}".format(str(resp))) + overall_recommendations[nst_name] = resp["plans"][0].get("recommendations") + + return conductor_response_processor(overall_recommendations, nst_info_map, req_info) + + except Exception as ex: + error_log.error("Error for {} {}".format(req_info.get('requestId'), + traceback.format_exc())) + if isinstance(ex, RequestException): + try: + error_message = json.loads(ex.response)['plans'][0]['message'] + except Exception: + error_message = "Problem connecting to conductor" + else: + error_message = str(ex) + return conductor_error_response_processor(req_info, error_message) + + +def get_slice_demands(model_name, policies, config): + """ + :param model_name: model name of the slice + :param policies: flattened polcies corresponding to the request + :param config: configuration specific to OSDF app + :return: list of demands for the request + """ + group_policies = group_policies_gen(policies, config) + subscriber_policy_list = group_policies["onap.policies.optimization.SubscriberPolicy"] + slice_demands = list() + for subscriber_policy in subscriber_policy_list: + policy_properties = subscriber_policy[list(subscriber_policy.keys())[0]]['properties'] + if model_name in policy_properties["services"]: + subnet_attributes = policy_properties["properties"]["subscriberRole"][0] + for subnet in policy_properties["properties"]["subscriberName"]: + slice_demand = dict() + slice_demand["resourceModuleName"] = subnet + slice_demand['resourceModelInfo'] = subnet_attributes[subnet] + slice_demands.append(slice_demand) + return slice_demands diff --git a/apps/slice_selection/optimizers/conductor/response_processor.py b/apps/slice_selection/optimizers/conductor/response_processor.py new file mode 100644 index 0000000..5b7be01 --- /dev/null +++ b/apps/slice_selection/optimizers/conductor/response_processor.py @@ -0,0 +1,123 @@ +# ------------------------------------------------------------------------- +# Copyright (C) 2020 Wipro Limited. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------- +# + +""" +Module for processing response from conductor for slice selection +""" + +from osdf.logging.osdf_logging import debug_log + + +SLICE_PROFILE_FIELDS = ["latency", "max_number_of_ues", "coverage_area_ta_list", + "ue_mobility_level", "resource_sharing_level", "exp_data_rate_ul", + "exp_data_rate_dl", "area_traffic_cap_ul", "area_traffic_cap_dl", + "activity_factor", "e2e_latency", "jitter", "survival_time", + "exp_data_rate", "payload_size", "traffic_density", "conn_density", + "reliability", "service_area_dimension", "cs_availability"] + + +def conductor_response_processor(overall_recommendations, nst_info_map, request_info): + """Process conductor response to form the response for the API request + :param overall_recommendations: recommendations from conductor + :param nst_info_map: NST info from the request + :param request_info: request info + :return: response json as a dictionary + """ + shared_nsi_solutions = list() + new_nsi_solutions = list() + + for nst_name, recommendations in overall_recommendations.items(): + for recommendation in recommendations: + nsi_set = set(values['candidate']['nsi_name'] for key, values in recommendation.items()) + if len(nsi_set) == 1: + nsi = nsi_set.pop() + debug_log.debug("The NSSIs in the solution belongs to the same NSI {}".format(nsi)) + shared_nsi_solution = dict() + shared_nsi_solution["NSIName"] = nsi + shared_nsi_solutions.append(shared_nsi_solution) + else: + nssi_solutions = get_nssi_solutions(recommendation) + new_nsi_solution = dict() + new_nsi_solution['matchLevel'] = "" + new_nsi_solution['NSTInfo'] = nst_info_map.get(nst_name) + new_nsi_solution['NSSISolutions'] = nssi_solutions + new_nsi_solutions.append(new_nsi_solution) + + solutions = dict() + solutions['sharedNSISolutions'] = shared_nsi_solutions + solutions['newNSISolutions'] = new_nsi_solutions + return get_nsi_selection_response(request_info, solutions) + + +def conductor_error_response_processor(request_info, error_message): + """Form response message from the error message + :param request_info: request info + :param error_message: error message while processing the request + :return: response json as dictionary + """ + return {'requestId': request_info['requestId'], + 'transactionId': request_info['transactionId'], + 'requestStatus': 'error', + 'statusMessage': error_message} + + +def get_nssi_solutions(recommendation): + """Get nssi solutions from recommendation + :param recommendation: recommendation from conductor + :return: new nssi solutions list + """ + nssi_solutions = list() + + for nsst_name, nsst_rec in recommendation.items(): + candidate = nsst_rec['candidate'] + nssi_info, slice_profile = get_solution_from_candidate(candidate) + nsst_info = {"NSSTName": nsst_name} + nssi_solution = {"sliceProfile": slice_profile, + "NSSTInfo": nsst_info, + "NSSISolution": nssi_info} + nssi_solutions.append(nssi_solution) + return nssi_solutions + + +def get_solution_from_candidate(candidate): + """Get nssi info from candidate + :param candidate: Candidate from the recommendation + :return: nssi_info and slice profile derived from candidate + """ + slice_profile = dict() + nssi_info = {"NSSIName": candidate['instance_name'], + "NSSIId": candidate['candidate_id']} + + for field in SLICE_PROFILE_FIELDS: + if candidate[field]: + slice_profile[field] = candidate[field] + + return nssi_info, slice_profile + + +def get_nsi_selection_response(request_info, solutions): + """Get NSI selection response from final solution + :param request_info: request info + :param solutions: final solutions + :return: NSI selection response to send back as dictionary + """ + return {'requestId': request_info['requestId'], + 'transactionId': request_info['transactionId'], + 'requestStatus': 'completed', + 'statusMessage': '', + 'solutions': solutions} -- cgit 1.2.3-korg