aboutsummaryrefslogtreecommitdiffstats
path: root/apps/slice_selection/optimizers
diff options
context:
space:
mode:
Diffstat (limited to 'apps/slice_selection/optimizers')
-rw-r--r--apps/slice_selection/optimizers/__init__.py17
-rw-r--r--apps/slice_selection/optimizers/conductor/__init__.py0
-rw-r--r--apps/slice_selection/optimizers/conductor/remote_opt_processor.py134
-rw-r--r--apps/slice_selection/optimizers/conductor/response_processor.py108
4 files changed, 259 insertions, 0 deletions
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
--- /dev/null
+++ b/apps/slice_selection/optimizers/conductor/__init__.py
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..68c9409
--- /dev/null
+++ b/apps/slice_selection/optimizers/conductor/remote_opt_processor.py
@@ -0,0 +1,134 @@
+# -------------------------------------------------------------------------
+# 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
+"""
+
+from requests import RequestException
+from threading import Thread
+import traceback
+
+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.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
+
+
+class SliceSelectionOptimizer(Thread):
+ def __init__(self, osdf_config, slice_config, request_json, model_type):
+ super().__init__()
+ 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', "") \
+ in ['not-shared', 'non-shared']:
+ final_response = self.response_processor.get_slice_selection_response([])
+
+ else:
+ 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, model_info)
+
+ 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 isinstance(error, list) and "Unable to find any" in error[0]:
+ return self.response_processor.get_slice_selection_response([])
+ 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, self.model_type)
+
+ def get_request_parameters(self, requirements, model_info):
+ 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']}_"
+ capability_details = subnet_capability['capabilityDetails']
+ for key, value in capability_details.items():
+ request_params[f"{domain_type}{camel_to_snake[key]}"] = value
+ request_params.update(model_info)
+ 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 'serviceProfile' in self.request_json:
+ slice_scope = self.request_json['serviceProfile']['resourceSharingLevel']
+ if 'preferReuse' in self.request_json and slice_scope == "shared":
+ slice_scope = slice_scope + "," + ("reuse" if self.request_json['preferReuse'] else "create_new")
+ policy_request_json['slice_scope'] = slice_scope
+ debug_log.debug("policy_request_json {}".format(str(policy_request_json)))
+ 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
new file mode 100644
index 0000000..2357ab9
--- /dev/null
+++ b/apps/slice_selection/optimizers/conductor/response_processor.py
@@ -0,0 +1,108 @@
+# -------------------------------------------------------------------------
+# 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
+"""
+
+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, model_type):
+ """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
+ :param model_type: NSI or NSSI
+ :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, model_type)
+ for rec in recommendations]
+ return self.get_slice_selection_response(solutions)
+
+ def get_solution_from_candidate(self, candidate, model_info, subnets, model_type):
+ if candidate['inventory_type'] == 'slice_profiles':
+ return {
+ 'existingNSI': False,
+ 'newNSISolution': {
+ 'sliceProfiles': self.get_slice_profiles_from_candidate(candidate, subnets)
+ }
+ }
+ elif model_type == 'NSSI':
+ return {
+ 'UUID': model_info['UUID'],
+ 'invariantUUID': model_info['invariantUUID'],
+ 'NSSIName': candidate['instance_name'],
+ 'NSSIId': candidate['instance_id']
+ }
+
+ elif model_type == 'NSI':
+ return {
+ 'existingNSI': True,
+ 'sharedNSISolution': {
+ 'UUID': model_info['UUID'],
+ 'invariantUUID': model_info['invariantUUID'],
+ 'NSIName': candidate['instance_name'],
+ 'NSIId': candidate['instance_id']
+ }
+ }
+
+ 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}