From dee386685502c63d64959e0f2324bf52440e9655 Mon Sep 17 00:00:00 2001 From: Aleem Raja Date: Fri, 20 Jan 2023 06:06:17 +0000 Subject: new API selectNSST for slicing use case Issue-ID: OPTFRA-1122 Signed-off-by: Aleem Raja Change-Id: Ib027bfe49948180a1808b5507dc1647ba05c6e0d --- apps/nsst/__init__.py | 0 apps/nsst/models/api/nsstSelectionRequest.py | 43 +++++++ apps/nsst/optimizers/__init__.py | 0 apps/nsst/optimizers/conf/configIinputs.json | 53 +++++++++ apps/nsst/optimizers/nsst_select_processor.py | 155 ++++++++++++++++++++++++++ config/common_config.yaml | 11 ++ osdfapp.py | 15 +++ 7 files changed, 277 insertions(+) create mode 100644 apps/nsst/__init__.py create mode 100644 apps/nsst/models/api/nsstSelectionRequest.py create mode 100644 apps/nsst/optimizers/__init__.py create mode 100644 apps/nsst/optimizers/conf/configIinputs.json create mode 100644 apps/nsst/optimizers/nsst_select_processor.py diff --git a/apps/nsst/__init__.py b/apps/nsst/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/nsst/models/api/nsstSelectionRequest.py b/apps/nsst/models/api/nsstSelectionRequest.py new file mode 100644 index 0000000..3355ea2 --- /dev/null +++ b/apps/nsst/models/api/nsstSelectionRequest.py @@ -0,0 +1,43 @@ +# ------------------------------------------------------------------------- +# Copyright (c) 2020 Huawei Intellectual Property +# +# 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 +from schematics.types.compound import DictType +from schematics.types.compound import ModelType +from schematics.types import IntType +from schematics.types import StringType +from schematics.types import URLType + + +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 NSSTSelectionAPI(OSDFModel): + """Request for NST selection """ + + requestInfo = ModelType(RequestInfo, required=True) + sliceProfile = DictType(BaseType) diff --git a/apps/nsst/optimizers/__init__.py b/apps/nsst/optimizers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/nsst/optimizers/conf/configIinputs.json b/apps/nsst/optimizers/conf/configIinputs.json new file mode 100644 index 0000000..11247bb --- /dev/null +++ b/apps/nsst/optimizers/conf/configIinputs.json @@ -0,0 +1,53 @@ +{ + "NST": [{ + "NST1 ": { + "name": "EmbbNst", + "id": "EmbbNst_1", + "latency": 20, + "uplink": 5, + "downlink": 8, + "reliability": 95, + "areaTrafficCapDL": 10, + "areaTrafficCapUL": 100, + "maxNumberofUEs": 10000, + "areas": " area1|area2", + "expDataRateDL": 10, + "expDataRateUL": 1000, + "uEMobilityLevel": "stationary", + "resourceSharingLevel": "shared", + "skip_post_instantiation_configuration": "true", + "controller_actor": "SO-REF-DATA", + "sNSSAI": "01-3226E7D1", + "pLMNIdList": "39-00", + "sST": "embb", + "uEMobilityLevel": "stationary", + "activityFactor": "0", + "coverageAreaTAList": "Beijing;Beijing;HaidanDistrict;WanshouluStreet", + "modeluuid": "fe6c82b9-4e53-4322-a671-e2d8637bfbb7", + "modelinvariantuuid": "7d7df980-cb81-45f8-bad9-4e5ad2876393" + + } + }, + { + "NST2 ": { + "name": "NST_2", + "id": "NST_2_id", + "latency": 3, + "uplink": 7, + "downlink": 1, + "areaTrafficCapDL": 100, + "areaTrafficCapUL": 100, + "maxNumberofUEs": 300, + "areas": " area1|area2", + "expDataRateDL": 10, + "expDataRateUL": 30, + "uEMobilityLevel": "stationary", + "resourceSharingLevel": "shared", + "skip_post_instantiation_configuration": "true", + "controller_actor": "SO-REF-DATA", + "modeluuid": "7981375e-5e0a-4bf5-93fa-f3e3c02f2b15", + "modelinvariantuuid": "087f11b4-aca0-4341-8104-e5bb2b73285g" + } + } + ] +} diff --git a/apps/nsst/optimizers/nsst_select_processor.py b/apps/nsst/optimizers/nsst_select_processor.py new file mode 100644 index 0000000..90f40f2 --- /dev/null +++ b/apps/nsst/optimizers/nsst_select_processor.py @@ -0,0 +1,155 @@ +# ------------------------------------------------------------------------- +# Copyright (c) 2020 Huawei Intellectual Property +# +# 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. +# +# ------------------------------------------------------------------------- + +""" +This application generates NST SELECTION API calls using the information received from SO +""" +import os +from osdf.adapters.conductor import conductor +from osdf.adapters.policy.interface import get_policies +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 requests import RequestException +from threading import Thread +import traceback +BASE_DIR = os.path.dirname(__file__) + + +# This is the class for NST Selection + + +class NsstSelection(Thread): + + def __init__(self, osdf_config, request_json): + super().__init__() + self.osdf_config = osdf_config + self.request_json = request_json + self.request_info = self.request_json['requestInfo'] + self.request_info['numSolutions'] = 1 + + def run(self): + self.process_nsst_selection() + + def process_nsst_selection(self): + """Process a PCI request from a Client (build config-db, policy and API call, make the call, return result) + + :param req_object: Request parameters from the client + :param osdf_config: Configuration specific to OSDF application (core + deployment) + :return: response from NST Opt + """ + try: + rest_client = get_rest_client(self.request_json, service='so') + solution = self.get_nsst_solution() + except Exception as err: + error_log.error("Error for {} {}".format(self.request_info.get('requestId'), + traceback.format_exc())) + error_message = str(err) + solution = self.error_response(error_message) + + try: + rest_client.request(json=solution, noresponse=True) + except RequestException: + error_log.error("Error sending asynchronous notification for {} {}". + format(self.request_info['requestId'], traceback.format_exc())) + + def get_nsst_solution(self): + """the file is in the same folder for now will move it to the conf folder of the has once its + + integrated there... + """ + req_info = self.request_json['requestInfo'] + requirements = self.request_json['sliceProfile'] + model_name = "nsst" + policies = self.get_app_policies(model_name, "nsst_selection") + conductor_response = self.get_conductor(req_info, requirements, policies, model_name) + return conductor_response + + def get_nsst_selection_response(self, solutions): + """Get NST selection response from final solution + + :param solutions: final solutions + :return: NST selection response to send back as dictionary + """ + return {'requestId': self.request_info['requestId'], + 'transactionId': self.request_info['transactionId'], + 'requestStatus': 'completed', + 'statusMessage': '', + 'solutions': solutions} + + def 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_app_policies(self, model_name, app_name): + policy_request_json = self.request_json.copy() + policy_request_json['serviceInfo'] = {'serviceName': model_name} + debug_log.debug("policy_request_json {}".format(str(policy_request_json))) + return get_policies(policy_request_json, app_name) # app_name: nsst_selection + + def get_conductor(self, req_info, request_parameters, policies, model_name): + 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'] + if "Unable to find any" in error: + return self.get_nsst_selection_response([]) + error_log.error('Error from conductor {}'.format(error)) + return self.error_response(error) + debug_log.debug("Response from conductor in get_conductor method {}".format(str(resp))) + recommendations = resp["plans"][0].get("recommendations") + return self.process_response(recommendations, model_name) + + def process_response(self, recommendations, model_name): + """Process conductor response to form the response for the API request + + :param recommendations: recommendations from conductor + :return: response json as a dictionary + """ + if not recommendations: + return self.get_nsst_selection_response([]) + solutions = [self.get_solution_from_candidate(rec[model_name]['candidate']) + for rec in recommendations] + return self.get_nsst_selection_response(solutions) + + def get_solution_from_candidate(self, candidate): + if candidate['inventory_type'] == 'nsst': + return { + 'UUID': candidate['model_version_id'], + 'invariantUUID': candidate['model_invariant_id'], + 'NSSTName': candidate['model_name'], + } diff --git a/config/common_config.yaml b/config/common_config.yaml index da30a14..713e15f 100644 --- a/config/common_config.yaml +++ b/config/common_config.yaml @@ -112,6 +112,17 @@ policy_info: resources: - nst + nsst_selection: + policy_fetch: by_scope + policy_scope: + - + scope: + - OSDF_GUILIN + services: + - nsst + resources: + - nsst + subnet_selection: policy_fetch: by_scope policy_scope: diff --git a/osdfapp.py b/osdfapp.py index 28f9376..8b672f4 100755 --- a/osdfapp.py +++ b/osdfapp.py @@ -29,8 +29,10 @@ from flask import request, g from osdf.apps.baseapp import app, run_app from apps.nst.models.api.nstSelectionRequest import NSTSelectionAPI +from apps.nsst.models.api.nsstSelectionRequest import NSSTSelectionAPI from apps.pci.models.api.pciOptimizationRequest import PCIOptimizationAPI from apps.nst.optimizers.nst_select_processor import NstSelection +from apps.nsst.optimizers.nsst_select_processor import NsstSelection from apps.pci.optimizers.pci_opt_processor import process_pci_optimation from apps.placement.models.api.placementRequest import PlacementAPI from apps.placement.optimizers.conductor.remote_opt_processor import process_placement_opt @@ -136,6 +138,19 @@ def do_nst_selection(): request_status="accepted", status_message="") +@app.route("/api/oof/v1/selection/nsst", methods=["POST"]) +def do_nsst_selection(): + request_json = request.get_json() + req_id = request_json['requestInfo']['requestId'] + audit_log.info(MH.received_request(request.url, request.remote_addr, json.dumps(request_json))) + NSSTSelectionAPI(request_json).validate() + audit_log.info(MH.new_worker_thread(req_id, "[for NSST selection]")) + nsst_selection = NsstSelection(osdf_config, request_json) + nsst_selection.start() + return req_accept(request_id=req_id, + transaction_id=request_json['requestInfo']['transactionId'], + request_status="accepted", status_message="") + @app.route("/api/oof/v1/pci", methods=["POST"]) @app.route("/api/oof/pci/v1", methods=["POST"]) @auth_basic.login_required -- cgit 1.2.3-korg