new API selectNSST for slicing use case 67/132967/2 3.0.8
authorAleem Raja <aleem.raja@t-systems.com>
Fri, 20 Jan 2023 06:06:17 +0000 (06:06 +0000)
committerLukasz Rajewski <lukasz.rajewski@t-mobile.pl>
Wed, 15 Feb 2023 21:46:51 +0000 (21:46 +0000)
Issue-ID: OPTFRA-1122
Signed-off-by: Aleem Raja <aleem.raja@t-systems.com>
Change-Id: Ib027bfe49948180a1808b5507dc1647ba05c6e0d

apps/nsst/__init__.py [new file with mode: 0644]
apps/nsst/models/api/nsstSelectionRequest.py [new file with mode: 0644]
apps/nsst/optimizers/__init__.py [new file with mode: 0644]
apps/nsst/optimizers/conf/configIinputs.json [new file with mode: 0644]
apps/nsst/optimizers/nsst_select_processor.py [new file with mode: 0644]
config/common_config.yaml
osdfapp.py

diff --git a/apps/nsst/__init__.py b/apps/nsst/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/nsst/models/api/nsstSelectionRequest.py b/apps/nsst/models/api/nsstSelectionRequest.py
new file mode 100644 (file)
index 0000000..3355ea2
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/apps/nsst/optimizers/conf/configIinputs.json b/apps/nsst/optimizers/conf/configIinputs.json
new file mode 100644 (file)
index 0000000..11247bb
--- /dev/null
@@ -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 (file)
index 0000000..90f40f2
--- /dev/null
@@ -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'],
+            }
index da30a14..713e15f 100644 (file)
@@ -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:
index 28f9376..8b672f4 100755 (executable)
@@ -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