From 430153e1a9489a86cf0ff7942eceb0bf5f1c170c Mon Sep 17 00:00:00 2001 From: vrvarma Date: Fri, 13 Dec 2019 10:24:55 -0500 Subject: osdf rearchitecture into apps and libs Change-Id: I2db2f8e252bd5e60807b2ffabf5a5b193a789f7a Signed-off-by: vrvarma Issue-ID: OPTFRA-637 --- apps/__init__.py | 0 apps/license/__init__.py | 0 apps/license/optimizers/__init__.py | 0 .../optimizers/simple_license_allocation.py | 43 ++++ apps/pci/__init__.py | 0 apps/pci/models/__init__.py | 0 apps/pci/models/api/__init__.py | 0 apps/pci/models/api/pciOptimizationRequest.py | 54 ++++ apps/pci/models/api/pciOptimizationResponse.py | 46 ++++ apps/pci/optimizers/__init__.py | 0 apps/pci/optimizers/configdb.py | 69 +++++ apps/pci/optimizers/pci_opt_processor.py | 123 +++++++++ apps/pci/optimizers/solver/__init__.py | 0 apps/pci/optimizers/solver/min_confusion.mzn | 98 +++++++ apps/pci/optimizers/solver/min_confusion_inl.mzn | 140 ++++++++++ .../solver/no_conflicts_no_confusion.mzn | 86 +++++++ apps/pci/optimizers/solver/optimizer.py | 137 ++++++++++ apps/pci/optimizers/solver/pci_utils.py | 40 +++ apps/placement/__init__.py | 0 apps/placement/models/__init__.py | 0 apps/placement/models/api/__init__.py | 0 apps/placement/models/api/placementRequest.py | 105 ++++++++ apps/placement/models/api/placementResponse.py | 64 +++++ apps/placement/optimizers/__init__.py | 0 apps/placement/optimizers/conductor/__init__.py | 17 ++ apps/placement/optimizers/conductor/api_builder.py | 99 +++++++ apps/placement/optimizers/conductor/conductor.py | 202 +++++++++++++++ .../optimizers/conductor/remote_opt_processor.py | 81 ++++++ apps/placement/optimizers/conductor/translation.py | 283 +++++++++++++++++++++ apps/placement/templates/conductor_interface.json | 39 +++ apps/placement/templates/plc_opt_request.jsont | 142 +++++++++++ apps/placement/templates/plc_opt_response.jsont | 10 + apps/placement/templates/policy_request.jsont | 3 + apps/route/__init__.py | 0 apps/route/optimizers/__init__.py | 0 apps/route/optimizers/simple_route_opt.py | 153 +++++++++++ apps/templates/cms_opt_request.jsont | 35 +++ apps/templates/cms_opt_request.jsont_1707_v1 | 67 +++++ apps/templates/cms_opt_request_1702.jsont | 63 +++++ apps/templates/cms_opt_response.jsont | 8 + apps/templates/license_opt_request.jsont | 6 + apps/templates/test_cms_nb_req_from_client.jsont | 19 ++ apps/templates/test_plc_nb_req_from_client.jsont | 52 ++++ assembly.xml | 1 + docker/Dockerfile | 27 +- docker/assembly/osdf-files.xml | 66 +++++ osdf/apps/__init__.py | 2 + osdf/apps/baseapp.py | 166 ++++++++++++ osdf/config/__init__.py | 2 +- osdf/logging/osdf_logging.py | 1 + osdf/models/api/pciOptimizationRequest.py | 54 ---- osdf/models/api/pciOptimizationResponse.py | 46 ---- osdf/models/api/placementRequest.py | 105 -------- osdf/models/api/placementResponse.py | 64 ----- .../licenseopt/simple_license_allocation.py | 43 ---- osdf/optimizers/pciopt/configdb.py | 69 ----- osdf/optimizers/pciopt/pci_opt_processor.py | 122 --------- osdf/optimizers/pciopt/solver/__init__.py | 0 osdf/optimizers/pciopt/solver/min_confusion.mzn | 98 ------- .../optimizers/pciopt/solver/min_confusion_inl.mzn | 140 ---------- .../pciopt/solver/no_conflicts_no_confusion.mzn | 86 ------- osdf/optimizers/pciopt/solver/optimizer.py | 137 ---------- osdf/optimizers/pciopt/solver/pci_utils.py | 40 --- osdf/optimizers/placementopt/conductor/__init__.py | 17 -- .../placementopt/conductor/api_builder.py | 99 ------- .../optimizers/placementopt/conductor/conductor.py | 202 --------------- .../placementopt/conductor/remote_opt_processor.py | 81 ------ .../placementopt/conductor/translation.py | 283 --------------------- osdf/optimizers/routeopt/simple_route_opt.py | 153 ----------- osdf/templates/cms_opt_request.jsont | 35 --- osdf/templates/cms_opt_request.jsont_1707_v1 | 67 ----- osdf/templates/cms_opt_request_1702.jsont | 63 ----- osdf/templates/cms_opt_response.jsont | 8 - osdf/templates/conductor_interface.json | 39 --- osdf/templates/license_opt_request.jsont | 6 - osdf/templates/plc_opt_request.jsont | 142 ----------- osdf/templates/plc_opt_response.jsont | 10 - osdf/templates/policy_request.jsont | 3 - osdf/templates/test_cms_nb_req_from_client.jsont | 19 -- osdf/templates/test_plc_nb_req_from_client.jsont | 52 ---- osdf/utils/mdc_utils.py | 8 +- osdfapp.py | 147 +---------- pom.xml | 79 ++++++ requirements.txt | 3 +- script/TagVersion.groovy | 45 ++++ test/conductor/test_conductor_calls.py | 2 +- test/conductor/test_conductor_translation.py | 7 +- test/config/log.yml | 95 +++++++ test/configdb/test_configdb_calls.py | 2 +- .../pdp-has-vcpe-good/Affinity_vCPE_1.json | 6 +- .../pdp-has-vcpe-good/Capacity_vGMuxInfra.json | 2 +- .../pdp-has-vcpe-good/Capacity_vG_1.json | 2 +- .../pdp-has-vcpe-good/Distance_vGMuxInfra_1.json | 8 +- .../pdp-has-vcpe-good/Distance_vG_1.json | 8 +- .../Placement_Optimization_1.json | 19 +- .../pdp-has-vcpe-good/QueryPolicy_vCPE.json | 8 +- .../pdp-has-vcpe-good/QueryPolicy_vCPE_2.json | 17 +- .../pdp-has-vcpe-good/hpa_policy_vGMuxInfra_1.json | 8 +- .../pdp-has-vcpe-good/hpa_policy_vG_1.json | 8 +- .../pdp-has-vcpe-good/vnfPolicy_vG.json | 2 +- .../pdp-has-vcpe-good/vnfPolicy_vGMuxInfra.json | 2 +- test/functest/simulators/simulated-config/log.yml | 95 +++++++ test/mainapp/test_osdfapp.py | 15 +- test/policy/test_policy_interface.py | 8 +- test/test_ConductorApiBuilder.py | 4 +- test/test_PolicyCalls.py | 2 +- test/test_api_validation.py | 11 +- test/test_get_opt_query_data.py | 2 +- test/test_process_pci_anr_opt.py | 18 +- test/test_process_pci_opt.py | 8 +- test/test_process_placement_opt.py | 7 +- test/test_so_response_gen.py | 5 +- tox.ini | 1 - 113 files changed, 2965 insertions(+), 2521 deletions(-) create mode 100644 apps/__init__.py create mode 100644 apps/license/__init__.py create mode 100644 apps/license/optimizers/__init__.py create mode 100644 apps/license/optimizers/simple_license_allocation.py create mode 100644 apps/pci/__init__.py create mode 100644 apps/pci/models/__init__.py create mode 100644 apps/pci/models/api/__init__.py create mode 100644 apps/pci/models/api/pciOptimizationRequest.py create mode 100644 apps/pci/models/api/pciOptimizationResponse.py create mode 100644 apps/pci/optimizers/__init__.py create mode 100644 apps/pci/optimizers/configdb.py create mode 100644 apps/pci/optimizers/pci_opt_processor.py create mode 100644 apps/pci/optimizers/solver/__init__.py create mode 100644 apps/pci/optimizers/solver/min_confusion.mzn create mode 100644 apps/pci/optimizers/solver/min_confusion_inl.mzn create mode 100644 apps/pci/optimizers/solver/no_conflicts_no_confusion.mzn create mode 100644 apps/pci/optimizers/solver/optimizer.py create mode 100644 apps/pci/optimizers/solver/pci_utils.py create mode 100644 apps/placement/__init__.py create mode 100644 apps/placement/models/__init__.py create mode 100644 apps/placement/models/api/__init__.py create mode 100644 apps/placement/models/api/placementRequest.py create mode 100644 apps/placement/models/api/placementResponse.py create mode 100644 apps/placement/optimizers/__init__.py create mode 100644 apps/placement/optimizers/conductor/__init__.py create mode 100644 apps/placement/optimizers/conductor/api_builder.py create mode 100755 apps/placement/optimizers/conductor/conductor.py create mode 100644 apps/placement/optimizers/conductor/remote_opt_processor.py create mode 100644 apps/placement/optimizers/conductor/translation.py create mode 100755 apps/placement/templates/conductor_interface.json create mode 100755 apps/placement/templates/plc_opt_request.jsont create mode 100755 apps/placement/templates/plc_opt_response.jsont create mode 100755 apps/placement/templates/policy_request.jsont create mode 100644 apps/route/__init__.py create mode 100644 apps/route/optimizers/__init__.py create mode 100644 apps/route/optimizers/simple_route_opt.py create mode 100755 apps/templates/cms_opt_request.jsont create mode 100755 apps/templates/cms_opt_request.jsont_1707_v1 create mode 100755 apps/templates/cms_opt_request_1702.jsont create mode 100644 apps/templates/cms_opt_response.jsont create mode 100644 apps/templates/license_opt_request.jsont create mode 100755 apps/templates/test_cms_nb_req_from_client.jsont create mode 100755 apps/templates/test_plc_nb_req_from_client.jsont create mode 100644 docker/assembly/osdf-files.xml create mode 100644 osdf/apps/__init__.py create mode 100644 osdf/apps/baseapp.py delete mode 100644 osdf/models/api/pciOptimizationRequest.py delete mode 100644 osdf/models/api/pciOptimizationResponse.py delete mode 100644 osdf/models/api/placementRequest.py delete mode 100644 osdf/models/api/placementResponse.py delete mode 100644 osdf/optimizers/licenseopt/simple_license_allocation.py delete mode 100644 osdf/optimizers/pciopt/configdb.py delete mode 100644 osdf/optimizers/pciopt/pci_opt_processor.py delete mode 100644 osdf/optimizers/pciopt/solver/__init__.py delete mode 100644 osdf/optimizers/pciopt/solver/min_confusion.mzn delete mode 100644 osdf/optimizers/pciopt/solver/min_confusion_inl.mzn delete mode 100644 osdf/optimizers/pciopt/solver/no_conflicts_no_confusion.mzn delete mode 100644 osdf/optimizers/pciopt/solver/optimizer.py delete mode 100644 osdf/optimizers/pciopt/solver/pci_utils.py delete mode 100644 osdf/optimizers/placementopt/conductor/__init__.py delete mode 100644 osdf/optimizers/placementopt/conductor/api_builder.py delete mode 100755 osdf/optimizers/placementopt/conductor/conductor.py delete mode 100644 osdf/optimizers/placementopt/conductor/remote_opt_processor.py delete mode 100644 osdf/optimizers/placementopt/conductor/translation.py delete mode 100644 osdf/optimizers/routeopt/simple_route_opt.py delete mode 100755 osdf/templates/cms_opt_request.jsont delete mode 100755 osdf/templates/cms_opt_request.jsont_1707_v1 delete mode 100755 osdf/templates/cms_opt_request_1702.jsont delete mode 100644 osdf/templates/cms_opt_response.jsont delete mode 100755 osdf/templates/conductor_interface.json delete mode 100644 osdf/templates/license_opt_request.jsont delete mode 100755 osdf/templates/plc_opt_request.jsont delete mode 100755 osdf/templates/plc_opt_response.jsont delete mode 100755 osdf/templates/policy_request.jsont delete mode 100755 osdf/templates/test_cms_nb_req_from_client.jsont delete mode 100755 osdf/templates/test_plc_nb_req_from_client.jsont create mode 100644 script/TagVersion.groovy create mode 100644 test/config/log.yml create mode 100644 test/functest/simulators/simulated-config/log.yml diff --git a/apps/__init__.py b/apps/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/license/__init__.py b/apps/license/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/license/optimizers/__init__.py b/apps/license/optimizers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/license/optimizers/simple_license_allocation.py b/apps/license/optimizers/simple_license_allocation.py new file mode 100644 index 0000000..b2aaba4 --- /dev/null +++ b/apps/license/optimizers/simple_license_allocation.py @@ -0,0 +1,43 @@ +# ------------------------------------------------------------------------- +# Copyright (c) 2015-2017 AT&T 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.utils.mdc_utils import mdc_from_json + + +def license_optim(request_json): + """ + Fetch license artifacts associated with the service model and search licensekey-group-UUID and entitlement-pool-uuid + associated with the given att part number and nominal throughput in a request + :param request_json: Request in a JSON format + :return: A tuple of licensekey-group-uuid-list and entitlement-group-uuid-list + """ + mdc_from_json(request_json) + req_id = request_json["requestInfo"]["requestId"] + + model_name = request_json.get('placementInfo', {}).get('serviceInfo', {}).get('modelInfo', {}).get('modelName') + service_name = model_name + + license_info = [] + + for demand in request_json.get('placementInfo', {}).get('demandInfo', {}).get('licenseDemands', []): + license_info.append( + {'serviceResourceId': demand['serviceResourceId'], + 'resourceModuleName': demand['resourceModuleName'], + 'entitlementPoolList': "NOT SUPPORTED", + 'licenseKeyGroupList': "NOT SUPPORTED" + }) + return license_info diff --git a/apps/pci/__init__.py b/apps/pci/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/pci/models/__init__.py b/apps/pci/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/pci/models/api/__init__.py b/apps/pci/models/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/pci/models/api/pciOptimizationRequest.py b/apps/pci/models/api/pciOptimizationRequest.py new file mode 100644 index 0000000..02b67a2 --- /dev/null +++ b/apps/pci/models/api/pciOptimizationRequest.py @@ -0,0 +1,54 @@ +# ------------------------------------------------------------------------- +# Copyright (c) 2018 AT&T 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 schematics.types import BaseType, StringType, URLType, IntType +from schematics.types.compound import ModelType, ListType, DictType + +from osdf.models.api.common import OSDFModel + + +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) + requestType = StringType(required=True) + numSolutions = IntType() + optimizers = ListType(StringType(required=True)) + timeout = IntType() + + +class ANRInfo(OSDFModel): + cellId = StringType(required=True) + removeableNeighbors = ListType(StringType()) + + +class CellInfo(OSDFModel): + """Information specific to CellInfo """ + networkId = StringType(required=True) + cellIdList = ListType(StringType(required=True)) + anrInputList = ListType(ModelType(ANRInfo)) + trigger = StringType() + + +class PCIOptimizationAPI(OSDFModel): + """Request for PCI optimization """ + requestInfo = ModelType(RequestInfo, required=True) + cellInfo = ModelType(CellInfo, required=True) diff --git a/apps/pci/models/api/pciOptimizationResponse.py b/apps/pci/models/api/pciOptimizationResponse.py new file mode 100644 index 0000000..019a43a --- /dev/null +++ b/apps/pci/models/api/pciOptimizationResponse.py @@ -0,0 +1,46 @@ +# ------------------------------------------------------------------------- +# Copyright (c) 2018 AT&T 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 schematics.types import StringType, IntType +from schematics.types.compound import ModelType, ListType + +from osdf.models.api.common import OSDFModel + + +class PCISolution(OSDFModel): + cellId = StringType(required=True) + pci = IntType(required=True) + + +class ANRSolution(OSDFModel): + cellId = StringType(required=True) + removeableNeighbors = ListType(StringType()) + + +class Solution(OSDFModel): + networkId = StringType(required=True) + pciSolutions = ListType(ListType(ModelType(PCISolution), min_size=1)) + anrSolutions = ListType(ListType(ModelType(ANRSolution), min_size=1)) + + +class PCIOptimizationResponse(OSDFModel): + transactionId = StringType(required=True) + requestId = StringType(required=True) + requestStatus = StringType(required=True) + statusMessage = StringType() + solutions = ModelType(Solution, required=True) diff --git a/apps/pci/optimizers/__init__.py b/apps/pci/optimizers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/pci/optimizers/configdb.py b/apps/pci/optimizers/configdb.py new file mode 100644 index 0000000..8f003c2 --- /dev/null +++ b/apps/pci/optimizers/configdb.py @@ -0,0 +1,69 @@ +# ------------------------------------------------------------------------- +# Copyright (c) 2018 AT&T 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 datetime import datetime as dt + +from osdf.logging.osdf_logging import debug_log +from osdf.utils.interfaces import RestClient + + +def request(req_object, osdf_config, flat_policies): + """ + Process a configdb request from a Client (build Conductor 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) + :param flat_policies: policies related to PCI Opt (fetched based on request) + :return: response from ConfigDB (accounting for redirects from Conductor service + """ + cell_list_response = {} + config = osdf_config.deployment + local_config = osdf_config.core + uid, passwd = config['configDbUserName'], config['configDbPassword'] + req_id = req_object['requestInfo']['requestId'] + transaction_id = req_object['requestInfo']['transactionId'] + headers = dict(transaction_id=transaction_id) + + network_id = req_object['cellInfo']['networkId'] + + cell_list_response['network_id'] = network_id + + ts = dt.strftime(dt.now(), '%Y-%m-%d %H:%M:%S%z') + + rc = RestClient(userid=uid, passwd=passwd, method="GET", log_func=debug_log.debug, headers=headers) + + cell_list_url = '{}/{}/{}/{}'.format(config['configDbUrl'], config['configDbGetCellListUrl'], network_id, ts) + + cell_list_resp = rc.request(raw_response=True, url=cell_list_url) + cell_resp = cell_list_resp.json() + + cell_list = [] + count = 0 + for cell_id in cell_resp: + cell_info = {'cell_id': cell_id, 'id': count} + nbr_list_url = '{}/{}/{}/{}'.format(config['configDbUrl'], config['configDbGetNbrListUrl'], cell_id, ts) + nbr_list_raw = rc.request(url=nbr_list_url, raw_response=True) + cell_info['nbr_list'] = get_neighbor_list(nbr_list_raw.json()) + cell_list.append(cell_info) + count += 1 + + cell_list_response['cell_list'] = cell_list + return cell_resp, cell_list_response + + +def get_neighbor_list(nbr_list_response): + return nbr_list_response.get('nbrList', []) diff --git a/apps/pci/optimizers/pci_opt_processor.py b/apps/pci/optimizers/pci_opt_processor.py new file mode 100644 index 0000000..9948d55 --- /dev/null +++ b/apps/pci/optimizers/pci_opt_processor.py @@ -0,0 +1,123 @@ +# ------------------------------------------------------------------------- +# Copyright (c) 2018 AT&T 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. +# +# ------------------------------------------------------------------------- +# + +import traceback + +from onaplogging.mdcContext import MDC +from requests import RequestException + +from osdf.logging.osdf_logging import metrics_log, MH, error_log +from osdf.operation.error_handling import build_json_error_body +from osdf.utils.interfaces import get_rest_client +from .configdb import request as config_request +from .solver.optimizer import pci_optimize as optimize +from .solver.pci_utils import get_cell_id, get_pci_value +from osdf.utils.mdc_utils import mdc_from_json + +""" +This application generates PCI Optimization API calls using the information received from PCI-Handler-MS, SDN-C +and Policy. +""" + + +def process_pci_optimation(request_json, osdf_config, flat_policies): + """ + 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) + :param flat_policies: policies related to pci (fetched based on request) + :return: response from PCI Opt + """ + try: + mdc_from_json(request_json) + rc = get_rest_client(request_json, service="pcih") + req_id = request_json["requestInfo"]["requestId"] + cell_info_list, network_cell_info = config_request(request_json, osdf_config, flat_policies) + pci_response = get_solutions(cell_info_list, network_cell_info, request_json) + + metrics_log.info(MH.inside_worker_thread(req_id)) + except Exception as err: + error_log.error("Error for {} {}".format(req_id, traceback.format_exc())) + + try: + body = build_json_error_body(err) + metrics_log.info(MH.sending_response(req_id, "ERROR")) + rc.request(json=body, noresponse=True) + except RequestException as err: + MDC.put('requestID',req_id) + error_log.error("Error sending asynchronous notification for {} {}".format(req_id, traceback.format_exc())) + raise err + + + try: + metrics_log.info(MH.calling_back_with_body(req_id, rc.url, pci_response)) + rc.request(json=pci_response, noresponse=True) + 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())) + + +def get_solutions(cell_info_list, network_cell_info, request_json): + status, pci_solutions, anr_solutions = build_solution_list(cell_info_list, network_cell_info, request_json) + return { + "transactionId": request_json['requestInfo']['transactionId'], + "requestId": request_json["requestInfo"]["requestId"], + "requestStatus": "completed", + "statusMessage": status, + "solutions": { + 'networkId': request_json['cellInfo']['networkId'], + 'pciSolutions': pci_solutions, + 'anrSolutions': anr_solutions + } + } + + +def build_solution_list(cell_info_list, network_cell_info, request_json): + status = "success" + req_id = request_json["requestInfo"]["requestId"] + try: + opt_solution = optimize(network_cell_info, cell_info_list, request_json) + pci_solutions = build_pci_solution(network_cell_info, opt_solution['pci']) + anr_solutions = build_anr_solution(network_cell_info, opt_solution.get('removables', {})) + except RuntimeError: + error_log.error("Failed finding solution for {} {}".format(req_id, traceback.format_exc())) + status = "failed" + return status, pci_solutions, anr_solutions + + +def build_pci_solution(network_cell_info, pci_solution): + pci_solutions = [] + for k, v in pci_solution.items(): + old_pci = get_pci_value(network_cell_info, k) + if old_pci != v: + response = { + 'cellId': get_cell_id(network_cell_info, k), + 'pci': v + } + pci_solutions.append(response) + return pci_solutions + + +def build_anr_solution(network_cell_info, removables): + anr_solutions = [] + for k, v in removables.items(): + response = { + 'cellId': get_cell_id(network_cell_info, k), + 'removeableNeighbors': list(map(lambda x: get_cell_id(network_cell_info, x), v)) + } + anr_solutions.append(response) + return anr_solutions diff --git a/apps/pci/optimizers/solver/__init__.py b/apps/pci/optimizers/solver/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/pci/optimizers/solver/min_confusion.mzn b/apps/pci/optimizers/solver/min_confusion.mzn new file mode 100644 index 0000000..ff56c18 --- /dev/null +++ b/apps/pci/optimizers/solver/min_confusion.mzn @@ -0,0 +1,98 @@ +% ------------------------------------------------------------------------- +% Copyright (c) 2018 AT&T 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. +% +% ------------------------------------------------------------------------- +% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Parameters and its assertions +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% Number of cells/radios. +int: NUM_NODES; + +% Maximum number of Physical Cell Identifiers to be assigned to the nodes. +int: NUM_PCIS; + +% Number of edges between neighbor nodes. There is a edge (i,j) if and only +% if nodes i and j are neighbors, i.e., an user equipment (UE) can make +% handoff between i and j. Such edges are used to avoid **COLLISION**, i.e., +% to guarantee that nodes i and j have different PCIs. +int: NUM_NEIGHBORS; + +% Each line represents an edge between direct neighbors as defined before. +array[1..NUM_NEIGHBORS, 1..2] of int: NEIGHBORS; + +% Number of undirect neighbor pairs (j, k) such that both j and k are direct +% neighbors of node i, i.e., (j, k) exits if and only if exists (i, j) and +% (i, k). Nodes (i, k) can generate "confunsions" in the network if they have +% the same PCI. Such edges are used to avoid/minimize **CONFUSIONS**. +int: NUM_SECOND_LEVEL_NEIGHBORS; + +% Each line represents an edge between undirect neighbors as defined before. +array[1..NUM_SECOND_LEVEL_NEIGHBORS, 1..2] of int: SECOND_LEVEL_NEIGHBORS; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Decision variables +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% Defines the PCI for each node. +array[0..NUM_NODES-1] of var 0..NUM_PCIS-1: pci; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Constraints +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% Direct neighbors must have different PCIs for avoid **COLLISION**. +constraint +forall(i in 1..NUM_NEIGHBORS)( + pci[NEIGHBORS[i, 1]] != pci[NEIGHBORS[i, 2]] +); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Objective function +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% Total number of confusions. +var int: total_confusions = + sum([bool2int(pci[SECOND_LEVEL_NEIGHBORS[i, 1]] == + pci[SECOND_LEVEL_NEIGHBORS[i, 2]]) + | i in 1..NUM_SECOND_LEVEL_NEIGHBORS]); + +% Minimize the total number of confusions. +solve :: int_search(pci, smallest, indomain_min, complete) +minimize total_confusions; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Output +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +output +["PCI assigment"] ++ +["\nnode,pci"] ++ +[ + "\n" ++ show(node) ++ "," ++ show(pci[node]) +| node in 0..NUM_NODES-1 +] ++ + +["\n\nConfusions"] ++ +["\nTotal confusions: " ++ show(total_confusions)] ++ +["\nConfusion pairs"] ++ +[ + "\n" ++ show(SECOND_LEVEL_NEIGHBORS[i, 1]) ++ "," ++ + show(SECOND_LEVEL_NEIGHBORS[i, 2]) +| i in 1..NUM_SECOND_LEVEL_NEIGHBORS where + fix(pci[SECOND_LEVEL_NEIGHBORS[i, 1]] == pci[SECOND_LEVEL_NEIGHBORS[i, 2]]) +] diff --git a/apps/pci/optimizers/solver/min_confusion_inl.mzn b/apps/pci/optimizers/solver/min_confusion_inl.mzn new file mode 100644 index 0000000..0f0fc91 --- /dev/null +++ b/apps/pci/optimizers/solver/min_confusion_inl.mzn @@ -0,0 +1,140 @@ +% ------------------------------------------------------------------------- +% Copyright (c) 2018 AT&T 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. +% +% ------------------------------------------------------------------------- +% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Parameters and its assertions +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% Number of cells/radios. +int: NUM_NODES; + +% Maximum number of Physical Cell Identifiers to be assigned to the nodes. +int: NUM_PCIS; + +% Number of edges between neighbor nodes. There is a edge (i,j) if and only +% if nodes i and j are neighbors, i.e., an user equipment (UE) can make +% handoff between i and j. Such edges are used to avoid **COLLISION**, i.e., +% to guarantee that nodes i and j have different PCIs. +int: NUM_NEIGHBORS; + +% Each line represents an edge between direct neighbors as defined before. +array[1..NUM_NEIGHBORS, 1..2] of int: NEIGHBORS; + +% Number of undirect neighbor pairs (j, k) such that both j and k are direct +% neighbors of node i, i.e., (j, k) exits if and only if exists (i, j) and +% (i, k). Nodes (i, k) can generate "confunsions" in the network if they have +% the same PCI. Such edges are used to avoid/minimize **CONFUSIONS**. +int: NUM_SECOND_LEVEL_NEIGHBORS; + +% Each line represents an edge between undirect neighbors as defined before. +array[1..NUM_SECOND_LEVEL_NEIGHBORS, 1..2] of int: SECOND_LEVEL_NEIGHBORS; + +% Number of ignorable neighbor links. Such links can be ignored during +% optimization if needed. +int: NUM_IGNORABLE_NEIGHBOR_LINKS; + +% The links that can be ignored if needed. Each line represents the two ends +% of the links, like the previous structures. +array[1..NUM_IGNORABLE_NEIGHBOR_LINKS, 1..2] of int: IGNORABLE_NEIGHBOR_LINKS; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Decision variables +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% Defines the PCI for each node. +array[0..NUM_NODES-1] of var 0..NUM_PCIS-1: pci; + +array[1..NUM_IGNORABLE_NEIGHBOR_LINKS] of var 0..1: used_ignorables; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Constraints +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% Direct neighbors must have different PCIs for avoid **COLLISION**. +% Forced links. +constraint +forall(i in 1..NUM_NEIGHBORS, j in 1..NUM_IGNORABLE_NEIGHBOR_LINKS + where + NEIGHBORS[i, 1] != IGNORABLE_NEIGHBOR_LINKS[j, 1] \/ + NEIGHBORS[i, 2] != IGNORABLE_NEIGHBOR_LINKS[j, 2] +)( + pci[NEIGHBORS[i, 1]] != pci[NEIGHBORS[i, 2]] +); + + +% Ignorable links. +constraint +forall(i in 1..NUM_NEIGHBORS, j in 1..NUM_IGNORABLE_NEIGHBOR_LINKS + where + NEIGHBORS[i, 1] == IGNORABLE_NEIGHBOR_LINKS[j, 1] /\ + NEIGHBORS[i, 2] == IGNORABLE_NEIGHBOR_LINKS[j, 2] +)( + used_ignorables[j] >= bool2int(pci[NEIGHBORS[i, 1]] == pci[NEIGHBORS[i, 2]]) +); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Objective function +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% Total number of confusions. +var int: total_confusions = + sum([bool2int(pci[SECOND_LEVEL_NEIGHBORS[i, 1]] == + pci[SECOND_LEVEL_NEIGHBORS[i, 2]]) + | i in 1..NUM_SECOND_LEVEL_NEIGHBORS]); + +% Total number of used ignorables links. +var int: total_used_ignorables = sum(used_ignorables); + +solve :: int_search(pci, smallest, indomain_min, complete) + +% Minimize the total number of confusions. +%minimize total_confusions; + +% Minimize the total number of confusions first, +% then the number of used ignorables links. +minimize (2 * NUM_IGNORABLE_NEIGHBOR_LINKS * total_confusions) + + total_used_ignorables; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Output +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +output +["PCI assigment"] ++ +["\nnode,pci"] ++ +[ + "\n" ++ show(node) ++ "," ++ show(pci[node]) +| node in 0..NUM_NODES-1 +] ++ +["\n\nTotal used ignorables links: " ++ show(total_used_ignorables)] ++ +["\nUsed ignorables links: "] ++ +[ + "\n" ++ show(IGNORABLE_NEIGHBOR_LINKS[i, 1]) ++ + "," ++ show(IGNORABLE_NEIGHBOR_LINKS[i, 2]) + | i in 1..NUM_IGNORABLE_NEIGHBOR_LINKS where fix(used_ignorables[i] > 0) +] ++ +["\n\nConfusions"] ++ +["\nTotal confusions: " ++ show(total_confusions)] ++ +["\nConfusion pairs"] ++ +[ + "\n" ++ show(SECOND_LEVEL_NEIGHBORS[i, 1]) ++ "," ++ + show(SECOND_LEVEL_NEIGHBORS[i, 2]) + | i in 1..NUM_SECOND_LEVEL_NEIGHBORS where + fix(pci[SECOND_LEVEL_NEIGHBORS[i, 1]] == pci[SECOND_LEVEL_NEIGHBORS[i, 2]]) +] + diff --git a/apps/pci/optimizers/solver/no_conflicts_no_confusion.mzn b/apps/pci/optimizers/solver/no_conflicts_no_confusion.mzn new file mode 100644 index 0000000..0a9b3e3 --- /dev/null +++ b/apps/pci/optimizers/solver/no_conflicts_no_confusion.mzn @@ -0,0 +1,86 @@ +% ------------------------------------------------------------------------- +% Copyright (c) 2018 AT&T 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. +% +% ------------------------------------------------------------------------- +% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Parameters and its assertions +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% Number of cells/radios. +int: NUM_NODES; + +% Maximum number of Physical Cell Identifiers to be assigned to the nodes. +int: NUM_PCIS; + +% Number of edges between neighbor nodes. There is a edge (i,j) if and only +% if nodes i and j are neighbors, i.e., an user equipment (UE) can make +% handoff between i and j. Such edges are used to avoid **COLLISIONS**, i.e., +% to guarantee that nodes i and j have different PCIs. +int: NUM_NEIGHBORS; + +% Each line represents an edge between direct neighbors as defined before. +array[1..NUM_NEIGHBORS, 1..2] of int: NEIGHBORS; + +% Number of undirect neighbor pairs (j, k) such that both j and k are direct +% neighbors of node i, i.e., (j, k) exits if and only if exists (i, j) and +% (i, k). Nodes (i, k) can generate "confunsions" in the network if they have +% the same PCI. Such edges are used to avoid/minimize **CONFUSIONS**. +int: NUM_SECOND_LEVEL_NEIGHBORS; + +% Each line represents an edge between undirect neighbors as defined before. +array[1..NUM_SECOND_LEVEL_NEIGHBORS, 1..2] of int: SECOND_LEVEL_NEIGHBORS; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Decision variables +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% Defines the PCI for each node. +array[0..NUM_NODES-1] of var 0..NUM_PCIS-1: pci; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Constraints +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% Direct neighbors must have different PCIs for avoid **COLLISION**. +constraint +forall(i in 1..NUM_NEIGHBORS)( + pci[NEIGHBORS[i, 1]] != pci[NEIGHBORS[i, 2]] +); + +% Undirect neighbors must have different PCIs for avoid **CONFUSIONS**. +constraint +forall(i in 1..NUM_SECOND_LEVEL_NEIGHBORS)( + pci[SECOND_LEVEL_NEIGHBORS[i, 1]] != pci[SECOND_LEVEL_NEIGHBORS[i, 2]] +); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Objective function +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% Just satisfy the problem. +solve :: int_search(pci, smallest, indomain_min, complete) satisfy; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Output +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +output +["node,pci\n"] ++ +[ + show(node) ++ "," ++ show(pci[node]) ++ "\n" +| node in 0..NUM_NODES-1 +] diff --git a/apps/pci/optimizers/solver/optimizer.py b/apps/pci/optimizers/solver/optimizer.py new file mode 100644 index 0000000..0a6d5a4 --- /dev/null +++ b/apps/pci/optimizers/solver/optimizer.py @@ -0,0 +1,137 @@ +# ------------------------------------------------------------------------- +# Copyright (c) 2018 AT&T 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. +# +# ------------------------------------------------------------------------- +# + +import itertools +import os +from collections import defaultdict + +import pymzn + +from .pci_utils import get_id + +BASE_DIR = os.path.dirname(__file__) + + +def pci_optimize(network_cell_info, cell_info_list, request_json): + neighbor_edges = get_neighbor_list(network_cell_info) + second_level_edges = get_second_level_neighbor(network_cell_info) + ignorable_links = get_ignorable_links(network_cell_info, request_json) + anr_flag = is_anr(request_json) + + dzn_data = build_dzn_data(cell_info_list, ignorable_links, neighbor_edges, second_level_edges, anr_flag) + + return build_pci_solution(dzn_data, ignorable_links, anr_flag) + + +def build_pci_solution(dzn_data, ignorable_links, anr_flag): + mzn_solution = solve(get_mzn_model(anr_flag), dzn_data) + + solution = {'pci': mzn_solution[0]['pci']} + + if anr_flag: + removables = defaultdict(list) + used_ignorables = mzn_solution[0]['used_ignorables'] + index = 0 + for i in ignorable_links: + if used_ignorables[index] > 0: + removables[i[0]].append(i[1]) + index += 1 + solution['removables'] = removables + return solution + + +def build_dzn_data(cell_info_list, ignorable_links, neighbor_edges, second_level_edges, anr_flag): + dzn_data = { + 'NUM_NODES': len(cell_info_list), + 'NUM_PCIS': len(cell_info_list), + 'NUM_NEIGHBORS': len(neighbor_edges), + 'NEIGHBORS': get_list(neighbor_edges), + 'NUM_SECOND_LEVEL_NEIGHBORS': len(second_level_edges), + 'SECOND_LEVEL_NEIGHBORS': get_list(second_level_edges) + } + if anr_flag: + dzn_data['NUM_IGNORABLE_NEIGHBOR_LINKS'] = len(ignorable_links) + dzn_data['IGNORABLE_NEIGHBOR_LINKS'] = get_list(ignorable_links) + return dzn_data + + +def get_mzn_model(anr_flag): + if anr_flag: + mzn_model = os.path.join(BASE_DIR, 'min_confusion_inl.mzn') + else: + mzn_model = os.path.join(BASE_DIR, 'no_conflicts_no_confusion.mzn') + return mzn_model + + +def is_anr(request_json): + return 'pci-anr' in request_json["requestInfo"]["optimizers"] + + +def get_list(edge_list): + array_list = [] + for s in edge_list: + array_list.append([s[0], s[1]]) + return sorted(array_list) + + +def solve(mzn_model, dzn_data): + return pymzn.minizinc(mzn=mzn_model, data=dzn_data) + + +def get_neighbor_list(network_cell_info): + neighbor_list = set() + for cell in network_cell_info['cell_list']: + add_to_neighbor_list(network_cell_info, cell, neighbor_list) + return neighbor_list + + +def add_to_neighbor_list(network_cell_info, cell, neighbor_list): + for nbr in cell.get('nbr_list', []): + host_id = cell['id'] + nbr_id = get_id(network_cell_info, nbr['targetCellId']) + if nbr_id and host_id != nbr_id: + neighbor_list.add((host_id, nbr_id)) + + +def get_second_level_neighbor(network_cell_info): + second_neighbor_list = set() + for cell in network_cell_info['cell_list']: + comb_list = build_second_level_list(network_cell_info, cell) + for comb in comb_list: + if comb[0] and comb[1]: + second_neighbor_list.add((comb[0], comb[1])) + return sorted(second_neighbor_list) + + +def build_second_level_list(network_cell_info, cell): + second_nbr_list = [] + for nbr in cell.get('nbr_list', []): + second_nbr_list.append(get_id(network_cell_info, nbr['targetCellId'])) + return [list(elem) for elem in list(itertools.combinations(second_nbr_list, 2))] + + +def get_ignorable_links(network_cell_info, request_json): + ignorable_list = set() + anr_input_list = request_json["cellInfo"].get('anrInputList', []) + if anr_input_list: + for anr_info in anr_input_list: + cell_id = get_id(network_cell_info, anr_info['cellId']) + anr_removable = anr_info.get('removeableNeighbors', []) + for anr in anr_removable: + ignorable_list.add((cell_id, get_id(network_cell_info, anr))) + return ignorable_list diff --git a/apps/pci/optimizers/solver/pci_utils.py b/apps/pci/optimizers/solver/pci_utils.py new file mode 100644 index 0000000..04829cf --- /dev/null +++ b/apps/pci/optimizers/solver/pci_utils.py @@ -0,0 +1,40 @@ +# ------------------------------------------------------------------------- +# Copyright (c) 2018 AT&T 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. +# +# ------------------------------------------------------------------------- +# + + +def get_id(network_cell_info, cell_id): + for i in network_cell_info['cell_list']: + if i['cell_id'] == cell_id: + return i['id'] + return None + + +def get_cell_id(network_cell_info, id): + for i in network_cell_info['cell_list']: + if i['id'] == id: + return i['cell_id'] + return None + + +def get_pci_value(network_cell_info, id): + cell_id = get_cell_id(network_cell_info, id) + for i in network_cell_info['cell_list']: + for j in i['nbr_list']: + if cell_id == j['targetCellId']: + return j['pciValue'] + return None diff --git a/apps/placement/__init__.py b/apps/placement/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/placement/models/__init__.py b/apps/placement/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/placement/models/api/__init__.py b/apps/placement/models/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/placement/models/api/placementRequest.py b/apps/placement/models/api/placementRequest.py new file mode 100644 index 0000000..a0941cf --- /dev/null +++ b/apps/placement/models/api/placementRequest.py @@ -0,0 +1,105 @@ +# ------------------------------------------------------------------------- +# Copyright (c) 2015-2017 AT&T 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, 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) + requestType = StringType(required=True) + numSolutions = IntType() + optimizers = ListType(StringType(required=True)) + timeout = IntType() + + +class Candidates(OSDFModel): + """Preferred candidate for a resource (sent as part of a request from client)""" + identifierType = StringType(required=True) + identifiers = ListType(StringType(required=True)) + cloudOwner = StringType() + + +class ModelMetaData(OSDFModel): + """Model information for a specific resource""" + modelInvariantId = StringType(required=True) + modelVersionId = StringType(required=True) + modelName = StringType() + modelType = StringType() + modelVersion = StringType() + modelCustomizationName = StringType() + + +class LicenseModel(OSDFModel): + entitlementPoolUUID = ListType(StringType(required=True)) + licenseKeyGroupUUID = ListType(StringType(required=True)) + + +class LicenseDemands(OSDFModel): + resourceModuleName = StringType(required=True) + serviceResourceId = StringType(required=True) + resourceModelInfo = ModelType(ModelMetaData, required=True) + existingLicenses = ModelType(LicenseModel) + + +class LicenseInfo(OSDFModel): + licenseDemands = ListType(ModelType(LicenseDemands)) + + +class PlacementDemand(OSDFModel): + resourceModuleName = StringType(required=True) + serviceResourceId = StringType(required=True) + tenantId = StringType() + resourceModelInfo = ModelType(ModelMetaData, required=True) + existingCandidates = ListType(ModelType(Candidates)) + excludedCandidates = ListType(ModelType(Candidates)) + requiredCandidates = ListType(ModelType(Candidates)) + + +class ServiceInfo(OSDFModel): + serviceInstanceId = StringType(required=True) + modelInfo = ModelType(ModelMetaData, required=True) + serviceName = StringType(required=True) + + +class SubscriberInfo(OSDFModel): + """Details on the customer that subscribes to the VNFs""" + globalSubscriberId = StringType(required=True) + subscriberName = StringType() + subscriberCommonSiteId = StringType() + + +class PlacementInfo(OSDFModel): + """Information specific to placement optimization""" + requestParameters = DictType(BaseType) + placementDemands = ListType(ModelType(PlacementDemand), min_size=1) + subscriberInfo = ModelType(SubscriberInfo) + + +class PlacementAPI(OSDFModel): + """Request for placement optimization (specific to optimization and additional metadata""" + requestInfo = ModelType(RequestInfo, required=True) + placementInfo = ModelType(PlacementInfo, required=True) + licenseInfo = ModelType(LicenseInfo) + serviceInfo = ModelType(ServiceInfo, required=True) diff --git a/apps/placement/models/api/placementResponse.py b/apps/placement/models/api/placementResponse.py new file mode 100644 index 0000000..13b8d7a --- /dev/null +++ b/apps/placement/models/api/placementResponse.py @@ -0,0 +1,64 @@ +# ------------------------------------------------------------------------- +# Copyright (c) 2015-2017 AT&T 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, StringType +from schematics.types.compound import ModelType, ListType, DictType + + +# TODO: update osdf.models + +class LicenseSolution(OSDFModel): + serviceResourceId = StringType(required=True) + resourceModuleName = StringType(required=True) + entitlementPoolUUID = ListType(StringType(required=True)) + licenseKeyGroupUUID = ListType(StringType(required=True)) + entitlementPoolInvariantUUID = ListType(StringType(required=True)) + licenseKeyGroupInvariantUUID = ListType(StringType(required=True)) + + +class Candidates(OSDFModel): + """Preferred candidate for a resource (sent as part of a request from client)""" + identifierType = StringType(required=True) + identifiers = ListType(StringType(required=True)) + cloudOwner = StringType() + + +class AssignmentInfo(OSDFModel): + key = StringType(required=True) + value = BaseType(required=True) + + +class PlacementSolution(OSDFModel): + serviceResourceId = StringType(required=True) + resourceModuleName = StringType(required=True) + solution = ModelType(Candidates, required=True) + assignmentInfo = ListType(ModelType(AssignmentInfo)) + + +class Solution(OSDFModel): + placementSolutions = ListType(ListType(ModelType(PlacementSolution), min_size=1)) + licenseSolutions = ListType(ModelType(LicenseSolution), min_size=1) + + +class PlacementResponse(OSDFModel): + transactionId = StringType(required=True) + requestId = StringType(required=True) + requestStatus = StringType(required=True) + statusMessage = StringType() + solutions = ModelType(Solution, required=True) diff --git a/apps/placement/optimizers/__init__.py b/apps/placement/optimizers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/placement/optimizers/conductor/__init__.py b/apps/placement/optimizers/conductor/__init__.py new file mode 100644 index 0000000..4b25e5b --- /dev/null +++ b/apps/placement/optimizers/conductor/__init__.py @@ -0,0 +1,17 @@ +# ------------------------------------------------------------------------- +# Copyright (c) 2017-2018 AT&T 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. +# +# ------------------------------------------------------------------------- +# diff --git a/apps/placement/optimizers/conductor/api_builder.py b/apps/placement/optimizers/conductor/api_builder.py new file mode 100644 index 0000000..7b61580 --- /dev/null +++ b/apps/placement/optimizers/conductor/api_builder.py @@ -0,0 +1,99 @@ +# ------------------------------------------------------------------------- +# Copyright (c) 2015-2017 AT&T 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. +# +# ------------------------------------------------------------------------- +# + +import json + +from jinja2 import Template + +import apps.placement.optimizers.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, request_json): + """ + Function prepares parameters section for has request + :param group_policies: filtered policies + :param request_json: parameter data received from a client + :return: + """ + initial_params = tr.get_opt_query_data(request_json, group_policies['request_param_query']) + params = dict() + params.update({"REQUIRED_MEM": initial_params.pop("requiredMemory", "")}) + params.update({"REQUIRED_DISK": initial_params.pop("requiredDisk", "")}) + params.update({"customer_lat": initial_params.pop("customerLatitude", 0.0)}) + params.update({"customer_long": initial_params.pop("customerLongitude", 0.0)}) + params.update({"service_name": request_json['serviceInfo']['serviceName']}) + params.update({"service_id": request_json['serviceInfo']['serviceInstanceId']}) + + for key, val in initial_params.items(): + if val and val != "": + params.update({key: val}) + + return params + + +def conductor_api_builder(request_json, flat_policies: list, local_config, + template="apps/placement/templates/conductor_interface.json"): + """Build an OSDF southbound API call for HAS-Conductor/Placement optimization + :param request_json: parameter data received from a client + :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 the service specific information + :param prov_status: provStatus retrieved from Subscriber policy + :return: json to be sent to Conductor/placement optimization + """ + templ = Template(open(template).read()) + gp = group_policies_gen(flat_policies, local_config) + demand_vnf_name_list = [] + + for placementDemand in request_json['placementInfo']['placementDemands']: + demand_vnf_name_list.append(placementDemand['resourceModuleName'].lower()) + demand_list = tr.gen_demands(request_json, gp['vnfPolicy']) + attribute_policy_list = tr.gen_attribute_policy(demand_vnf_name_list, gp['attribute']) + distance_to_location_policy_list = tr.gen_distance_to_location_policy( + demand_vnf_name_list, gp['distance_to_location']) + inventory_policy_list = tr.gen_inventory_group_policy(demand_vnf_name_list, gp['inventory_group']) + resource_instance_policy_list = tr.gen_resource_instance_policy( + demand_vnf_name_list, gp['instance_fit']) + resource_region_policy_list = tr.gen_resource_region_policy(demand_vnf_name_list, gp['region_fit']) + zone_policy_list = tr.gen_zone_policy(demand_vnf_name_list, gp['zone']) + optimization_policy_list = tr.gen_optimization_policy(demand_vnf_name_list, gp['placement_optimization']) + reservation_policy_list = tr.gen_reservation_policy(demand_vnf_name_list, gp['instance_reservation']) + capacity_policy_list = tr.gen_capacity_policy(demand_vnf_name_list, gp['vim_fit']) + hpa_policy_list = tr.gen_hpa_policy(demand_vnf_name_list, gp['hpa']) + req_params_dict = _build_parameters(gp, request_json) + conductor_policies = [attribute_policy_list, distance_to_location_policy_list, inventory_policy_list, + resource_instance_policy_list, resource_region_policy_list, zone_policy_list, + reservation_policy_list, capacity_policy_list, hpa_policy_list] + filtered_policies = [x for x in conductor_policies if len(x) > 0] + policy_groups = list_flatten(filtered_policies) + req_info = request_json['requestInfo'] + request_type = req_info.get('requestType', None) + rendered_req = templ.render( + requestType=request_type, + demand_list=demand_list, + policy_groups=policy_groups, + optimization_policies=optimization_policy_list, + name=req_info['requestId'], + timeout=req_info['timeout'], + limit=req_info['numSolutions'], + request_params=req_params_dict, + json=json) + json_payload = json.dumps(json.loads(rendered_req)) # need this because template's JSON is ugly! + return json_payload diff --git a/apps/placement/optimizers/conductor/conductor.py b/apps/placement/optimizers/conductor/conductor.py new file mode 100755 index 0000000..05d1641 --- /dev/null +++ b/apps/placement/optimizers/conductor/conductor.py @@ -0,0 +1,202 @@ +# ------------------------------------------------------------------------- +# Copyright (c) 2015-2017 AT&T 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 conductor API calls using the information received from SO and Policy platform. +""" + +import json +import time + +from jinja2 import Template +from requests import RequestException + +from apps.placement.optimizers.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 + + +def request(req_object, osdf_config, flat_policies): + """ + Process a placement request from a Client (build Conductor API call, make the call, return result) + :param req_object: Request parameters from the client + :param osdf_config: Configuration specific to SNIRO application (core + deployment) + :param flat_policies: policies related to placement (fetched based on request) + :param prov_status: provStatus retrieved from Subscriber policy + :return: response from Conductor (accounting for redirects from Conductor service + """ + config = osdf_config.deployment + local_config = osdf_config.core + uid, passwd = config['conductorUsername'], config['conductorPassword'] + conductor_url = config['conductorUrl'] + req_id = req_object['requestInfo']['requestId'] + transaction_id = req_object['requestInfo']['transactionId'] + headers = dict(transaction_id=transaction_id) + placement_ver_enabled = config.get('placementVersioningEnabled', False) + + if placement_ver_enabled: + cond_minor_version = config.get('conductorMinorVersion', None) + if cond_minor_version is not None: + x_minor_version = str(cond_minor_version) + headers.update({'X-MinorVersion': x_minor_version}) + debug_log.debug("Versions set in HTTP header to conductor: X-MinorVersion: {} ".format(x_minor_version)) + + max_retries = config.get('conductorMaxRetries', 30) + ping_wait_time = config.get('conductorPingWaitTime', 60) + + rc = RestClient(userid=uid, passwd=passwd, method="GET", log_func=debug_log.debug, headers=headers) + conductor_req_json_str = conductor_api_builder(req_object, flat_policies, local_config) + conductor_req_json = json.loads(conductor_req_json_str) + + debug_log.debug("Sending first Conductor request for request_id {}".format(req_id)) + resp, raw_resp = initial_request_to_conductor(rc, conductor_url, conductor_req_json) + # Very crude way of keeping track of time. + # We are not counting initial request time, first call back, or time for HTTP request + total_time, ctr = 0, 2 + client_timeout = req_object['requestInfo']['timeout'] + configured_timeout = max_retries * ping_wait_time + max_timeout = min(client_timeout, configured_timeout) + + while True: # keep requesting conductor till we get a result or we run out of time + if resp is not None: + if resp["plans"][0].get("status") in ["error"]: + raise RequestException(response=raw_resp, request=raw_resp.request) + + if resp["plans"][0].get("status") in ["done", "not found"]: + if resp["plans"][0].get("recommendations"): + return conductor_response_processor(resp, raw_resp, req_id) + else: # "solved" but no solutions found + return conductor_no_solution_processor(resp, raw_resp, req_id) + new_url = resp['plans'][0]['links'][0][0]['href'] # TODO: check why a list of lists + + if total_time >= max_timeout: + raise BusinessException("Conductor could not provide a solution within {} seconds," + "this transaction is timing out".format(max_timeout)) + time.sleep(ping_wait_time) + ctr += 1 + debug_log.debug("Attempt number {} url {}; prior status={}".format(ctr, new_url, resp['plans'][0]['status'])) + total_time += ping_wait_time + + try: + raw_resp = rc.request(new_url, raw_response=True) + resp = raw_resp.json() + except RequestException as e: + debug_log.debug("Conductor attempt {} for request_id {} has failed because {}".format(ctr, req_id, str(e))) + + +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 + :return: URL to check for follow up (similar to redirects); we keep checking these till we get a result/error + """ + debug_log.debug("Payload to Conductor: {}".format(json.dumps(conductor_req_json))) + raw_resp = rc.request(url=conductor_url, raw_response=True, method="POST", json=conductor_req_json) + resp = raw_resp.json() + if resp["status"] != "template": + raise RequestException(response=raw_resp, request=raw_resp.request) + time.sleep(10) # 10 seconds wait time to avoid being too quick! + plan_url = resp["links"][0][0]["href"] + 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 + resp = raw_resp.json() + + if resp["plans"][0]["status"] in ["error"]: + raise RequestException(response=raw_resp, request=raw_resp.request) + return resp, raw_resp # now the caller of this will handle further follow-ups + + +def conductor_response_processor(conductor_response, raw_response, req_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 + + :param conductor_response: JSON response from Conductor + :param raw_response: Raw HTTP response corresponding to above + :param req_id: Id of a request + :return: JSON object that can be sent to the client's callback URL + """ + composite_solutions = [] + name_map = {"physical-location-id": "cloudClli", "host_id": "vnfHostName", + "cloud_version": "cloudVersion", "cloud_owner": "cloudOwner", + "cloud": "cloudRegionId", "service": "serviceInstanceId", "is_rehome": "isRehome", + "location_id": "locationId", "location_type": "locationType", "directives": "oof_directives"} + for reco in conductor_response['plans'][0]['recommendations']: + for resource in reco.keys(): + c = reco[resource]['candidate'] + solution = { + 'resourceModuleName': resource, + 'serviceResourceId': reco[resource].get('service_resource_id', ""), + 'solution': {"identifierType": name_map.get(c['inventory_type'], c['inventory_type']), + 'identifiers': [c['candidate_id']], + 'cloudOwner': c.get('cloud_owner', "")}, + 'assignmentInfo': [] + } + for key, value in c.items(): + if key in ["location_id", "location_type", "is_rehome", "host_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)) + + 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)) + composite_solutions.append(solution) + + request_status = "completed" if conductor_response['plans'][0]['status'] == "done" \ + else conductor_response['plans'][0]['status'] + transaction_id = raw_response.headers.get('transaction_id', "") + status_message = conductor_response.get('plans')[0].get('message', "") + + solution_info = {} + if composite_solutions: + solution_info.setdefault('placementSolutions', []) + solution_info['placementSolutions'].append(composite_solutions) + + resp = { + "transactionId": transaction_id, + "requestId": req_id, + "requestStatus": request_status, + "statusMessage": status_message, + "solutions": solution_info + } + return resp + + +def conductor_no_solution_processor(conductor_response, raw_response, request_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 + + :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") + :param template_placement_response: the template for generating response to client (plc_opt_response.jsont) + :return: JSON object that can be sent to the client's callback URL + """ + status_message = conductor_response["plans"][0].get("message") + templ = Template(open(template_placement_response).read()) + return json.loads(templ.render(composite_solutions=[], requestId=request_id, license_solutions=[], + transactionId=raw_response.headers.get('transaction_id', ""), + requestStatus="completed", statusMessage=status_message, json=json)) + + diff --git a/apps/placement/optimizers/conductor/remote_opt_processor.py b/apps/placement/optimizers/conductor/remote_opt_processor.py new file mode 100644 index 0000000..a08f3a4 --- /dev/null +++ b/apps/placement/optimizers/conductor/remote_opt_processor.py @@ -0,0 +1,81 @@ +# ------------------------------------------------------------------------- +# Copyright (c) 2015-2017 AT&T 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 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 +from apps.placement.optimizers.conductor import conductor +from apps.license.optimizers.simple_license_allocation import license_optim +from osdf.utils.interfaces import get_rest_client +from osdf.utils.mdc_utils import mdc_from_json + + +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 + :param osdf_config: configuration specific to OSDF app + :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") + req_id = request_json["requestInfo"]["requestId"] + transaction_id = request_json['requestInfo']['transactionId'] + + metrics_log.info(MH.inside_worker_thread(req_id)) + license_info = None + if request_json.get('licenseInfo', {}).get('licenseDemands'): + license_info = license_optim(request_json) + + # Conductor only handles placement, only call Conductor if placementDemands exist + if request_json.get('placementInfo', {}).get('placementDemands'): + metrics_log.info(MH.requesting("placement/conductor", req_id)) + placement_response = conductor.request(request_json, osdf_config, policies) + if license_info: # Attach license solution if it exists + placement_response['solutionInfo']['licenseInfo'] = license_info + else: # License selection only scenario + placement_response = { + "transactionId": transaction_id, + "requestId": req_id, + "requestStatus": "completed", + "statusMessage": "License selection completed successfully", + "solutionInfo": {"licenseInfo": license_info} + } + except Exception as err: + error_log.error("Error for {} {}".format(req_id, traceback.format_exc())) + + try: + body = build_json_error_body(err) + metrics_log.info(MH.sending_response(req_id, "ERROR")) + rc.request(json=body, noresponse=True) + except RequestException: + error_log.error("Error sending asynchronous notification for {} {}".format(req_id, traceback.format_exc())) + return + + try: + 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 + error_log.error("Error sending asynchronous notification for {} {}".format(req_id, traceback.format_exc())) + diff --git a/apps/placement/optimizers/conductor/translation.py b/apps/placement/optimizers/conductor/translation.py new file mode 100644 index 0000000..d361755 --- /dev/null +++ b/apps/placement/optimizers/conductor/translation.py @@ -0,0 +1,283 @@ +# ------------------------------------------------------------------------- +# Copyright (c) 2015-2017 AT&T 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. +# +# ------------------------------------------------------------------------- +# +import copy +import json +import yaml +import re + +from osdf.utils.data_conversion import text_to_symbol +from osdf.utils.programming_utils import dot_notation + +policy_config_mapping = yaml.safe_load(open('config/has_config.yaml')).get('policy_config_mapping') + + +def get_opt_query_data(req_json, policies): + """ + Fetch service and order specific details from the requestParameters field of a request. + :param req_json: a request file + :param policies: A set of policies + :return: A dictionary with service and order-specific attributes. + """ + req_param_dict = {} + if 'requestParameters' in req_json["placementInfo"]: + req_params = req_json["placementInfo"]["requestParameters"] + for policy in policies: + for queryProp in policy['content']['queryProperties']: + attr_val = queryProp['value'] if 'value' in queryProp and queryProp['value'] != "" \ + else dot_notation(req_params, queryProp['attribute_location']) + if attr_val is not None: + req_param_dict.update({queryProp['attribute']: attr_val}) + return req_param_dict + + +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 + """ + optimization_policy_list = [] + for policy in optimization_policy: + content = policy['content'] + parameter_list = [] + parameters = ["cloud_version", "hpa_score"] + + for attr in content['objectiveParameter']['parameterAttributes']: + parameter = attr['parameter'] if attr['parameter'] in parameters else attr['parameter']+"_between" + vnfs = get_matching_vnfs(attr['resources'], vnf_list) + for vnf in vnfs: + value = [vnf] if attr['parameter'] in parameters else [attr['customerLocationInfo'], vnf] + parameter_list.append({ + attr['operator']: [attr['weight'], {parameter: value}] + }) + + optimization_policy_list.append({ + 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) + :return: List of matching VNFs + """ + resources_lcase = [x.lower() for x in resources] + if match_type == "all": # don't bother with any comparisons + return resources if set(resources_lcase) <= set(vnf_list) else None + common_vnfs = set(vnf_list) & set(resources_lcase) + common_resources = [x for x in resources if x.lower() in common_vnfs] + if match_type == "intersection": # specifically requested intersection + return list(common_resources) + return resources if common_vnfs else None # "any" match => all resources to be returned + + +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") + intersection => return intersection; "any" implies return all vnf_names if intersection is not null + :param rtype: resource type (e.g. resourceRegionProperty or resourceInstanceProperty) + None => no controller information added to the policy specification to Conductor + :return: resource policy list in a format required by Conductor + """ + resource_policy_list = [] + related_policies = [] + for policy in resource_policy: + pc = policy['content'] + demands = get_matching_vnfs(pc['resources'], vnf_list, match_type=match_type) + resource = {pc['identity']: {'type': pc['policyType'], 'demands': demands}} + + if rtype: + resource[pc['identity']]['properties'] = {'controller': pc[rtype]['controller'], + 'request': json.loads(pc[rtype]['request'])} + if demands and len(demands) != 0: + resource_policy_list.append(resource) + related_policies.append(policy) + return resource_policy_list, related_policies + + +def gen_resource_instance_policy(vnf_list, resource_instance_policy): + """Get policies governing resource instances in order to populate the Conductor API call""" + cur_policies, _ = gen_policy_instance(vnf_list, resource_instance_policy, rtype='resourceInstanceProperty') + return cur_policies + + +def gen_resource_region_policy(vnf_list, resource_region_policy): + """Get policies governing resource region in order to populate the Conductor API call""" + cur_policies, _ = gen_policy_instance(vnf_list, resource_region_policy, rtype='resourceRegionProperty') + return cur_policies + + +def gen_inventory_group_policy(vnf_list, inventory_group_policy): + """Get policies governing inventory group in order to populate the Conductor API call""" + cur_policies, _ = gen_policy_instance(vnf_list, inventory_group_policy, rtype=None) + return cur_policies + + +def gen_reservation_policy(vnf_list, reservation_policy): + """Get policies governing resource instances in order to populate the Conductor API call""" + cur_policies, _ = gen_policy_instance(vnf_list, reservation_policy, rtype='instanceReservationProperty') + return cur_policies + + +def gen_distance_to_location_policy(vnf_list, distance_to_location_policy): + """Get policies governing distance-to-location for VNFs in order to populate the Conductor API call""" + cur_policies, related_policies = gen_policy_instance(vnf_list, distance_to_location_policy, rtype=None) + for p_new, p_main in zip(cur_policies, related_policies): # add additional fields to each policy + properties = p_main['content']['distanceProperties'] + pcp_d = properties['distance'] + p_new[p_main['content']['identity']]['properties'] = { + 'distance': pcp_d['operator'] + " " + pcp_d['value'].lower() + " " + pcp_d['unit'].lower(), + 'location': properties['locationInfo'] + } + return cur_policies + + +def gen_attribute_policy(vnf_list, attribute_policy): + """Get policies governing attributes of VNFs in order to populate the Conductor API call""" + cur_policies, related_policies = gen_policy_instance(vnf_list, attribute_policy, rtype=None) + for p_new, p_main in zip(cur_policies, related_policies): # add additional fields to each policy + properties = p_main['content']['cloudAttributeProperty'] + attribute_mapping = policy_config_mapping['attributes'] # wanted attributes and mapping + p_new[p_main['content']['identity']]['properties'] = { + 'evaluate': dict((k, properties.get(attribute_mapping.get(k))) for k in attribute_mapping.keys()) + } + return cur_policies # cur_policies gets updated in place... + + +def gen_zone_policy(vnf_list, zone_policy): + """Get zone policies in order to populate the Conductor API call""" + cur_policies, related_policies = gen_policy_instance(vnf_list, zone_policy, match_type="all", rtype=None) + for p_new, p_main in zip(cur_policies, related_policies): # add additional fields to each policy + pmz = p_main['content']['affinityProperty'] + p_new[p_main['content']['identity']]['properties'] = {'category': pmz['category'], 'qualifier': pmz['qualifier']} + return cur_policies + + +def gen_capacity_policy(vnf_list, capacity_policy): + """Get zone policies in order to populate the Conductor API call""" + cur_policies, related_policies = gen_policy_instance(vnf_list, capacity_policy, rtype=None) + for p_new, p_main in zip(cur_policies, related_policies): # add additional fields to each policy + pmz = p_main['content']['capacityProperty'] + p_new[p_main['content']['identity']]['properties'] = \ + {"controller": pmz['controller'], 'request': json.loads(pmz['request'])} + return cur_policies + + +def gen_hpa_policy(vnf_list, hpa_policy): + """Get zone policies in order to populate the Conductor API call""" + cur_policies, related_policies = gen_policy_instance(vnf_list, hpa_policy, rtype=None) + for p_new, p_main in zip(cur_policies, related_policies): # add additional fields to each policy + p_new[p_main['content']['identity']]['properties'] = {'evaluate': p_main['content']['flavorFeatures']} + return cur_policies + + +def get_augmented_policy_attributes(policy_property, demand): + """Get policy attributes and augment them using policy_config_mapping and demand information""" + attributes = copy.copy(policy_property['attributes']) + remapping = policy_config_mapping['remapping'] + extra = dict((x, demand['resourceModelInfo'][remapping[x]]) for x in attributes if x in remapping) + attributes.update(extra) + return attributes + + +def get_candidates_demands(demand): + """Get demands related to candidates; e.g. excluded/required""" + res = {} + for k, v in policy_config_mapping['candidates'].items(): + if k not in demand: + continue + res[v] = [{'inventory_type': x['identifierType'], 'candidate_id': x['identifiers']} for x in demand[k]] + return res + + +def get_policy_properties(demand, policies): + """Get policy_properties for cases where there is a match with the demand""" + for policy in policies: + policy_demands = set([x.lower() for x in policy['content'].get('resources', [])]) + if demand['resourceModuleName'].lower() not in policy_demands: + continue # no match for this policy + for policy_property in policy['content']['vnfProperties']: + yield policy_property + + +def get_demand_properties(demand, policies): + """Get list demand properties objects (named tuples) from policy""" + demand_properties = [] + for policy_property in get_policy_properties(demand, policies): + prop = dict(inventory_provider=policy_property['inventoryProvider'], + inventory_type=policy_property['inventoryType'], + service_type=demand['serviceResourceId'], + service_resource_id=demand['serviceResourceId']) + + prop.update({'unique': policy_property['unique']} if 'unique' in policy_property and + policy_property['unique'] else {}) + prop['attributes'] = dict() + prop['attributes'].update({'global-customer-id': policy_property['customerId']} + if policy_property['customerId'] else {}) + prop['attributes'].update({'model-invariant-id': demand['resourceModelInfo']['modelInvariantId']} + if demand['resourceModelInfo']['modelInvariantId'] else {}) + prop['attributes'].update({'model-version-id': demand['resourceModelInfo']['modelVersionId']} + if demand['resourceModelInfo']['modelVersionId'] else {}) + prop['attributes'].update({'equipment-role': policy_property['equipmentRole']} + if policy_property['equipmentRole'] else {}) + + if policy_property.get('attributes'): + for attr_key, attr_val in policy_property['attributes'].items(): + update_converted_attribute(attr_key, attr_val, prop) + + prop.update(get_candidates_demands(demand)) + demand_properties.append(prop) + return demand_properties + + +def update_converted_attribute(attr_key, attr_val, properties): + """ + Updates dictonary of attributes with one specified in the arguments. + Automatically translates key namr from camelCase to hyphens + :param attr_key: key of the attribute + :param attr_val: value of the attribute + :param properties: dictionary with attributes to update + :return: + """ + if attr_val: + remapping = policy_config_mapping['attributes'] + if remapping.get(attr_key): + key_value = remapping.get(attr_key) + else: + key_value = re.sub('(.)([A-Z][a-z]+)', r'\1-\2', attr_key) + key_value = re.sub('([a-z0-9])([A-Z])', r'\1-\2', key_value).lower() + properties['attributes'].update({key_value: attr_val}) + + +def gen_demands(req_json, vnf_policies): + """Generate list of demands based on request and VNF policies + :param req_json: Request object from the client (e.g. MSO) + :param vnf_policies: Policies associated with demand resources (e.g. from grouped_policies['vnfPolicy']) + :return: list of demand parameters to populate the Conductor API call + """ + demand_dictionary = {} + for demand in req_json['placementInfo']['placementDemands']: + prop = get_demand_properties(demand, vnf_policies) + if len(prop) > 0: + demand_dictionary.update({demand['resourceModuleName']: prop}) + return demand_dictionary diff --git a/apps/placement/templates/conductor_interface.json b/apps/placement/templates/conductor_interface.json new file mode 100755 index 0000000..030d6a0 --- /dev/null +++ b/apps/placement/templates/conductor_interface.json @@ -0,0 +1,39 @@ +{ + "name": "{{ name }}", + "files": {}, + "timeout": {{ timeout }}, + "num_solution": "{{ limit }}", + "template": { + "homing_template_version": "2017-10-10", + "parameters": { + {% set comma=joiner(",") %} + {% for key, value in request_params.items() %} {{ comma() }} + "{{key}}": {{ json.dumps(value) }} + {% endfor %} + }, + "locations": { + "customer_loc": { + "latitude": { "get_param": "customer_lat" }, + "longitude": { "get_param": "customer_long" } + } + }, + "demands": {{ json.dumps(demand_list) }}, + {% set comma_main = joiner(",") %} + "constraints": { + {% set comma=joiner(",") %} + {% for elem in policy_groups %} {{ comma() }} + {% for key, value in elem.items() %} + "{{key}}": {{ json.dumps(value) }} + {% endfor %} + {% endfor %} + }, + "optimization": { + {% set comma=joiner(",") %} + {% for elem in optimization_policies %} {{ comma() }} + {% for key, value in elem.items() %} + "{{key}}": {{ json.dumps(value) }} + {% endfor %} + {% endfor %} + } + } +} diff --git a/apps/placement/templates/plc_opt_request.jsont b/apps/placement/templates/plc_opt_request.jsont new file mode 100755 index 0000000..a218b8a --- /dev/null +++ b/apps/placement/templates/plc_opt_request.jsont @@ -0,0 +1,142 @@ +{ + "name": "{{ name }}", + "files": "{{ files }}", + "timeout": "{{ timeout }}", + "num_solution": "{{ limit }}", + "template": { + "CUST_ID": "{{ cust_id }}", + "E2EVPNKEY": "{{ e2evpnkey }}", + "UCPEHOST": "{{ ucpehost }}", + "WAN_PORT1_UP": "{{ wan_port1_up }}", + "WAN_PORT1_DOWN": "{{ wan_port1_down }}", + "EFFECTIVE_BANDWIDTH": "{{ effective_bandwidth }}", + "SERVICE_INST": "{{ service_inst }}", + "locations": { + "customer_loc": { + "host_name": "{{ ucpehost }}" + } + }, + "demands": [ + {% set comma=joiner(",") %} + {% for demand in demand_list %} {{ comma() }} + { + "{{ demand.vnf_name }}": [ + {% set comma2=joiner(",") %} + {% for property in demand.property %} + "inventory_provider": {{ property.inventory_provider }}, + "inventory_type": {{ property.inventory_type }}, + "service_type": {{ property.service_type }}, + "customer_id": {{ property.customer_id }}, + "candidate_id": {{ property.candidate_id }} + {% endfor %} + ] + } + {% endfor %} + ], + "constraints": { + {% set comma_main=joiner(",") %} + + {% if attribute_policy_list %} {{ comma_main() }} {% endif %} + {% set comma=joiner(",") %} + {% for attribute in attribute_policy_list %} {{ comma() }} + attribute['identity'] : { + "type": {{ attribute['type'] }}, + "demands": {{ attribute['demands'] }}, + "properties": { + "evaluate": { + "hypervisor": {{ attribute['property']['hypervisor'] }}, + "aic_version": {{ attribute['property']['aicVersion'] }}, + "aic_type": {{ attribute['property']['aicType'] }}, + "dataplane": {{ attribute['property']['datatype'] }}, + "network_roles": {{ attribute['property']['networkRoles'] }}, + "complex": {{ attribute['property']['complex'] }} + } + } + } + {% endfor %} + + {% if distance_to_location_policy_list %} {{ comma_main() }} {% endif %} + {% set comma=joiner(",") %} + {% for distance_location in distance_to_location_policy_list %} {{ comma() }} + distance_location['identity'] : { + "type": {{ distance_location['type'] }}, + "demands": {{ distance_location['demands'] }}, + "properties": { + "distance": {{ distance_location['property']['distance'] }}, + "location": {{ distance_location['property']['location'] }} + } + } + {% endfor %} + + {% if inventory_policy_list %} {{ comma_main() }} {% endif %} + {% set comma=joiner(",") %} + {% for inventory in inventory_policy_list %} {{ comma() }} + inventory['identity'] : { + "type": {{ inventory['type'] }}, + "demands": {{ inventory['demands'] }} + } + {% endfor %} + + {% if resource_instance_policy_list %} {{ comma_main() }} {% endif %} + {% set comma=joiner(",") %} + {% for resource_instance in resource_instance_policy_list %} {{ comma() }} + resource_instance['identity'] : { + "type": {{ resource_instance['type'] }}, + "demands": {{ resource_instance['demands'] }}, + "properties": { + "controller": {{ resource_instance['property']['controller'] }}, + "request": {{ resource_instance['property']['request'] }} + } + } + {% endfor %} + + {% if resource_region_policy_list %} {{ comma_main() }} {% endif %} + {% set comma=joiner(",") %} + {% for resource_region in resource_region_policy_list %} {{ comma() }} + resource_region['identity'] : { + "type": {{ resource_region['type'] }}, + "demands": {{ resource_region['demands'] }}, + "properties": { + "controller": {{ resource_region['property']['controller'] }}, + "request": {{ resource_region['property']['request'] }} + } + } + {% endfor %} + + {% if zone_policy_list %} {{ comma_main() }} {% endif %} + {% set comma=joiner(",") %} + {% for zone in zone_policy_list %} {{ comma() }} + zone['identity'] : { + "type": {{ zone['type'] }}, + "demands": {{ zone['demands'] }}, + "properties": { + "qualifier": {{ resource_region['property']['qualifier'] }}, + "category": {{ resource_region['property']['category'] }} + } + } + {% endfor %} + + {% if optmization_policy_list %} {{ comma_main() }} {% endif %} + {% set comma=joiner(",") %} + {% for optimization in optimization_policy_list %} {{ comma() }} + "optimization" : { + {{ optimization['objective'] }}: { + "sum": [ + {% set comma2=joiner(",") %} + {% for parameter in optimization['parameter'] %} {{ comma() }} + { + "product": [ + {{ parameter['weight'] }}, + { + "distance_between": [{{ parameter['customerLocation'] }},{{ parameter['demand'] }}] + } + ] + } + {% endfor %} + ] + } + } + {% endfor %} + } + } +} diff --git a/apps/placement/templates/plc_opt_response.jsont b/apps/placement/templates/plc_opt_response.jsont new file mode 100755 index 0000000..e5709e7 --- /dev/null +++ b/apps/placement/templates/plc_opt_response.jsont @@ -0,0 +1,10 @@ +{ + "requestId": "{{requestId}}", + "transactionId": "{{transacationId}}", + "requestStatus": "{{requestStatus}}", + "statusMessage": "{{statusMessage}}" + "solutions": { + "placementSolutions": {{ json.dumps(composite_solutions) }}, + "licenseSolutions":{{ json.dumps(license_solutions) }} + } +} diff --git a/apps/placement/templates/policy_request.jsont b/apps/placement/templates/policy_request.jsont new file mode 100755 index 0000000..3a9e201 --- /dev/null +++ b/apps/placement/templates/policy_request.jsont @@ -0,0 +1,3 @@ +{ + "policyName": "{{policy_name}}" {# we currently only support query by policy name only -- policyName #} +} diff --git a/apps/route/__init__.py b/apps/route/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/route/optimizers/__init__.py b/apps/route/optimizers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/route/optimizers/simple_route_opt.py b/apps/route/optimizers/simple_route_opt.py new file mode 100644 index 0000000..b00180d --- /dev/null +++ b/apps/route/optimizers/simple_route_opt.py @@ -0,0 +1,153 @@ +# ------------------------------------------------------------------------- +# Copyright (c) 2018 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. +# +# ------------------------------------------------------------------------- +# + +import requests +from requests.auth import HTTPBasicAuth + +from osdf.utils.mdc_utils import mdc_from_json + + +class RouteOpt: + + """ + This values will need to deleted.. + only added for the debug purpose + """ + # DNS server and standard port of AAI.. + # TODO: read the port from the configuration and add to DNS + aai_host = "https://aai.api.simpledemo.onap.org:8443" + aai_headers = { + "X-TransactionId": "9999", + "X-FromAppId": "OOF", + "Accept": "application/json", + "Content-Type": "application/json", + "Real-Time": "true" + } + + def isCrossONAPLink(self, logical_link): + """ + This method checks if cross link is cross onap + :param logical_link: + :return: + """ + for relationship in logical_link["relationship-list"]["relationship"]: + if relationship["related-to"] == "ext-aai-network": + return True + return False + + def getRoute(self, request): + """ + This method checks + :param logical_link: + :return: + """ + mdc_from_json(request) + + src_access_node_id = request["srcPort"]["src-access-node-id"] + dst_access_node_id = request["dstPort"]["dst-access-node-id"] + + + ingress_p_interface = None + egress_p_interface = None + + # for the case of request_json for same domain, return the same node with destination update + if src_access_node_id == dst_access_node_id: + data = '{'\ + '"vpns":['\ + '{'\ + '"access-topology-id": "' + request["srcPort"]["src-access-topology-id"] + '",'\ + '"access-client-id": "' + request["srcPort"]["src-access-client-id"] + '",'\ + '"access-provider-id": "' + request["srcPort"]["src-access-provider-id"]+ '",'\ + '"access-node-id": "' + request["srcPort"]["src-access-node-id"]+ '",'\ + '"src-access-ltp-id": "' + request["srcPort"]["src-access-ltp-id"]+ '",'\ + '"dst-access-ltp-id": "' + request["dstPort"]["dst-access-ltp-id"] +'"'\ + '}'\ + ']'\ + '}' + return data + else: + logical_links = self.get_logical_links() + + # take the logical link where both the p-interface in same onap + if logical_links != None: + for logical_link in logical_links.get("logical-link"): + if not self.isCrossONAPLink(logical_link): + # link is in local ONAP + for relationship in logical_link["relationship-list"]["relationship"]: + if relationship["related-to"] == "p-interface": + if src_access_node_id in relationship["related-link"]: + i_interface = relationship["related-link"].split("/")[-1] + ingress_p_interface = i_interface.split("-")[-1] + if dst_access_node_id in relationship["related-link"]: + e_interface = relationship["related-link"].split("/")[-1] + egress_p_interface = e_interface.split("-")[-1] + data = '{'\ + '"vpns":['\ + '{'\ + '"access-topology-id": "' + request["srcPort"]["src-access-topology-id"] + '",'\ + '"access-client-id": "' + request["srcPort"]["src-access-client-id"] + '",'\ + '"access-provider-id": "' + request["srcPort"]["src-access-provider-id"]+ '",'\ + '"access-node-id": "' + request["srcPort"]["src-access-node-id"]+ '",'\ + '"src-access-ltp-id": "' + request["srcPort"]["src-access-ltp-id"]+ '",'\ + '"dst-access-ltp-id": "' + ingress_p_interface +'"'\ + '},'\ + '{' \ + '"access-topology-id": "' + request["dstPort"]["dst-access-topology-id"] + '",' \ + '"access-topology-id": "' + request["dstPort"]["dst-access-topology-id"]+ '",' \ + '"access-provider-id": "' + request["dstPort"]["dst-access-provider-id"]+ '",' \ + '"access-node-id": "' + request["dstPort"]["dst-access-node-id"]+ '",' \ + '"src-access-ltp-id": "' + egress_p_interface + '",' \ + '"dst-access-ltp-id": "' + request["dstPort"]["dst-access-ltp-id"] + '"' \ + '}'\ + ']'\ + '}' + return data + + + def get_pinterface(self, url): + """ + This method returns details for p interface + :return: details of p interface + """ + aai_req_url = self.aai_host + url + response = requests.get(aai_req_url, + headers=self.aai_headers, + auth=HTTPBasicAuth("AAI", "AAI"), + verify=False) + + if response.status_code == 200: + return response.json() + + + def get_logical_links(self): + """ + This method returns list of all cross ONAP links + from /aai/v14/network/logical-links?operation-status="Up" + :return: logical-links[] + """ + logical_link_url = "/aai/v13/network/logical-links?operational-status=up" + aai_req_url = self.aai_host + logical_link_url + + response = requests.get(aai_req_url, + headers=self.aai_headers, + auth=HTTPBasicAuth("AAI", "AAI"), + verify=False) + + logical_links = None + if response.status_code == 200: + return response.json() \ No newline at end of file diff --git a/apps/templates/cms_opt_request.jsont b/apps/templates/cms_opt_request.jsont new file mode 100755 index 0000000..006562b --- /dev/null +++ b/apps/templates/cms_opt_request.jsont @@ -0,0 +1,35 @@ +{ + "transaction_id": "{{ transaction_id }}", + "request_id": "{{ request_id }}", + "start_date" : "{{ start_time }}", + "end_date" : "{{ end_time }}", + "change_elements" : {{ json.dumps(change_elements) }}, + "constraints" : [ + { + "type" : "general_concurrency_limit", + "parameters": [{{ concurrency_limit }}] + }, + + { + "type" : "allowed_forbidden_periods", + "parameters" : {{ json.dumps(allowed_periods) }} + } + + {% if spatial_conflicts is defined and spatial_conflicts|length > 0 %} + , + { + "type" : "spatial_conflict", + "parameters": {{ json.dumps(spatial_conflicts) }} + } + {% endif %} + + + {% if critical_periods is defined and spatial_conflicts|length > 0 %} + , + { + "type" : "critical_periods", + "parameters": {{ json.dumps(critical_periods) }} + } + {% endif %} + ] +} diff --git a/apps/templates/cms_opt_request.jsont_1707_v1 b/apps/templates/cms_opt_request.jsont_1707_v1 new file mode 100755 index 0000000..75ecbe5 --- /dev/null +++ b/apps/templates/cms_opt_request.jsont_1707_v1 @@ -0,0 +1,67 @@ +{ + "transaction_id": "{{ transaction_id }}", + "request_id": "{{ request_id }}", + "start_date" : "{{ start_time }}", + "end_date" : "{{ end_time }}", + + "change_elements" : [ + {% set comma = joiner(",") -%} + {% for element in all_upgrades -%} {{ comma() }} + { + "id" : "{{ element.id }}", + "failback_duration": {{ element.failback_duration }}, + {% if element.group_id -%} + "group_id": "{{ element.group_id }}", + {% endif %} + {% if element.scheduled_on -%} + "scheduled_on": "{{ element.scheduled_on }}", + {% endif %} + "duration": {{ element.duration }} + } + {% endfor -%} + ], + + "constraints" : [ + { + "type" : "general_concurrency_limit", + "parameters": [{{ concurrency_limit }}] + }, + + { + "type" : "allowed_forbidden_periods", + "parameters" : [ + {% set comma = joiner(",") -%} + {% for idx in all_pending -%} {{ comma() }} + { "id" : "{{ idx.id }}", + "allowed_periods": [ {{ allowed_periods }}] + } + {% endfor -%} + ] + }, + { + "type" : "spatial_conflict", + "parameters": [ + {% set comma = joiner(",") -%} + {% for pserver, vce_list in vce_pserver_mapping.items() -%} {{ comma() }} + { + "spatial_entity": "{{ pserver }}", + "affected_entities": {{ vce_list }} + } + {% endfor -%} + ] + }, + + { + "type" : "critical_periods", + "parameters": [ + {% set comma = joiner(",") -%} + {% for element, conflict_period in conflict_interval.items() -%} {{ comma() }} + { + "id" : "{{ element }}", + "periods": [{{ conflict_period }}] + } + {% endfor -%} + ] + } + ] +} diff --git a/apps/templates/cms_opt_request_1702.jsont b/apps/templates/cms_opt_request_1702.jsont new file mode 100755 index 0000000..bcafa45 --- /dev/null +++ b/apps/templates/cms_opt_request_1702.jsont @@ -0,0 +1,63 @@ +{ + "request_id": "{{ request_id }}", + "startdate" : "{{ start_time }}", + "enddate" : "{{ end_time }}", + + "change_elements" : [ +{% set comma = joiner(",") -%} +{% for element in all_upgrades -%} {{ comma() }} + { "id" : "{{ element.id }}", + {% if element.scheduled -%} "scheduled_on": "{{ element.scheduled }}", {% endif -%} + "duration": {{ element.duration }}, {# duration in seconds #} + "failback_duration": {{ element.failback_duration }}, {# duration in seconds #} + "group_id": {{ element.group_id }}, {# duration in seconds #} + }{% endfor -%} + ], + + "constraints" : [ + { + "type" : "general_concurrency_limit", + "parameters" : [ {{ general_concurrency_limit }} ] + }, + + { + "type" : "allowed_forbidden_periods", + "parameters" : [ +{% set comma = joiner(",") -%} +{% for idx in all_pending -%} {{ comma() }} + { "id" : "{{ idx.id }}", + "allowed_periods": [ {% set comma2 = joiner(",") -%} + {% for period in allowed_periods -%} {{ comma2() }} [{{ json.dumps(period[0]) }}, {{ json.dumps(period[1]) }}] + {% endfor -%} ] }{% endfor -%} + ] + } + +{% if p_v_conflict is defined and p_v_conflict|length > 0 %} + , + { + "type" : "critical_periods", + "description" : "Simultaneous upgrades", + "parameters" : [ +{% set comma2 = joiner(",") -%} +{% for element in p_v_conflict -%} {{ comma2() }} + { + "id" : "{{ element[0] }}", + "periods" : [{{ json.dumps(element[0]) }}, {{ json.dumps(element[1]) }}] + } +{% endfor -%} +{% endif %} + +{% for pserver, vce_group in grouped_vces.items() -%} {{ comma() }} + , + { + "id" : "{{ pserver }}", + "name" : "VCE's on pserver {{ pserver }}", + "description": "Only some VCEs on a pserver can be upgraded at a time", + "max_num_upgrades" : {{ max_num_upgrades(vce_group) }}, + "upgrades" : {{ json.dumps(vce_group) }} + } +{% endfor -%} + ] + } + ] +} diff --git a/apps/templates/cms_opt_response.jsont b/apps/templates/cms_opt_response.jsont new file mode 100644 index 0000000..a8817df --- /dev/null +++ b/apps/templates/cms_opt_response.jsont @@ -0,0 +1,8 @@ +{ + "transactionId": "{{transaction_id}}", + "scheduleId":"{{schedule_id}}", + "requestState": "{{request_state}}", + "status": "{{status}}", + "description": "{{description}}", + "schedule": {{schedule}} +} \ No newline at end of file diff --git a/apps/templates/license_opt_request.jsont b/apps/templates/license_opt_request.jsont new file mode 100644 index 0000000..7baa759 --- /dev/null +++ b/apps/templates/license_opt_request.jsont @@ -0,0 +1,6 @@ +{ + "transactionId": "{{transaction_id}}", + "requestId": "{{request_id}}", + "partNumber": "{{part_number}}", + "licenseModel" : "{{artifact}}" +} \ No newline at end of file diff --git a/apps/templates/test_cms_nb_req_from_client.jsont b/apps/templates/test_cms_nb_req_from_client.jsont new file mode 100755 index 0000000..a60c8ff --- /dev/null +++ b/apps/templates/test_cms_nb_req_from_client.jsont @@ -0,0 +1,19 @@ +{ + "schedulingInfo": { + "change_management_id": "{{ change_management_id }}", + "start_time": "{{ start_time }}", + "end_time": "{{ end_time }}", + "policy_id": {{ json.dumps(policy_id) }}, {# a list of policy Ids #} + "service_type": "{{ service_type }}", + "workflow_type": "{{ workflow_type }}", + "upgrades": {{ json.dumps(upgrades) }} {# a list of node Ids #} + }, + "requestInfo": { + "requestId": "{{ requestId }}", + "sourceId": "{{ sourceId }}", + "optimizer": "{{ optimizer }}", + "numSolutions": "{{ numSolutions }}", + "callbackUrl" : "{{ callbackUrl }}" + } +} + diff --git a/apps/templates/test_plc_nb_req_from_client.jsont b/apps/templates/test_plc_nb_req_from_client.jsont new file mode 100755 index 0000000..998ffb3 --- /dev/null +++ b/apps/templates/test_plc_nb_req_from_client.jsont @@ -0,0 +1,52 @@ +{ + "requestInfo": { + "requestId": "{{requestId}}", + "sourceId": "{{sourceId}}", + "optimizer": "{{optimizer}}", + "numSolutions": {{numSolutions}}, + "timeout": {{timeout}}, + "callbackUrl" : "{{callbackUrl}}" + }, + "placementInfo": { + "modelInfo": { + "modelType": "{{modelType}}", + "modelInvariant": "{{modelInvariantId}}", + "modelVersionId": "{{modelVersionId}}", + "modelName": "{{modelName}}", + "modelVersion": "{{modelVersion}}", + "modelCustomizationId": "{{modelCustomizationId}}" + }, + "subscriberInfo": { + "globalSubscriberId": "{{globalSubscriberId}}", + "subscriberName": "{{subscriberName}}", + "subscriberCommonSiteId": "{{subscriberCommonSiteId}}", + "ucpeHostName": "{{ucpeHostName}}" + }, + "policyId": {{json.dumps(policyId)}}, + "vnfInfo": { + "vnfType": "{{vnfType}}", + "vnfPartNumber": "{{vnfPartNumber}}", + "nominalThroughput": "{{nominalThroughput}}", + "vnfSoftwareVersion": "{{vnfSoftwareVersion}}", + "vnfManagementOption": "{{vnfManagementOption}}" + }, + "vpnInfo": { + "vpnId": "{{vpnId}}", + "pvcId": "{{pvcId}}" + }, + "serviceInfo": { + "dhvServiceInfo": { + "serviceInstanceId": "{{serviceInstanceId}}", + "serviceType": "{{serviceType}}", + "e2evpnkey": "{{e2evpnkey}}", + "dhvSiteEffectiveTransportBandwidth": {{dhvSiteEffectiveTransportBandwidth}}, + "dhvIPSecTransportBandwidthUp": {{dhvIPSecTransportBandwidthUp}}, + "dhvIPSecTransportBandwidthDown": {{dhvIPSecTransportBandwidthDown}}, + "dhvIPSec2TransportBandwidthUp": {{dhvIPSec2TransportBandwidthUp}}, + "dhvIPSec2TransportBandwidthDown": {{dhvIPSec2TransportBandwidthDown}}, + "dhvVendorName": "{{dhvVendorName}}" + } + }, + "demandInfo": {{json.dumps(demandInfo)}} + } +} diff --git a/assembly.xml b/assembly.xml index 24379d4..c6e724a 100644 --- a/assembly.xml +++ b/assembly.xml @@ -30,6 +30,7 @@ *.md + **/__pycache__/** **/*.pyc config/preload_secrets.yaml diff --git a/docker/Dockerfile b/docker/Dockerfile index 5a2c460..0f271c8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -30,35 +30,40 @@ ENV https_proxy $HTTPS_PROXY ENV OSDF_PORT "8699" EXPOSE ${OSDF_PORT} -ENV MZN 2.1.6 -ENV MZN_BASENAME MiniZincIDE-${MZN}-bundle-linux-x86_64 +ENV MZN 2.3.2 +ENV MZN_BASENAME MiniZincIDE-${MZN}-bundle-linux ENV MZN_GH_BASE https://github.com/MiniZinc/MiniZincIDE -ENV MZN_DL_URL ${MZN_GH_BASE}/releases/download/${MZN}/${MZN_BASENAME}.tgz +ENV MZN_DL_URL ${MZN_GH_BASE}/releases/download/${MZN}/${MZN_BASENAME}-x86_64.tgz RUN apt-get update -y \ && apt-get install -y vim unzip wget libmpfr-dev \ && apt-get install -y git libqt5printsupport5 build-essential \ && apt-get install -y python3 python3-setuptools python3-dev \ && easy_install3 pip \ - && pip install --upgrade virtualenv pip wheel + && pip install --upgrade virtualenv pip wheel setuptools RUN ln -s /usr/bin/python3.5 /usr/bin/python # Minizinc -RUN wget -q $MZN_DL_URL -O mz.tgz \ - && tar xzf mz.tgz \ +RUN wget -q $MZN_DL_URL -O /tmp/mz.tgz \ + && tar xzf /tmp/mz.tgz \ && mv $MZN_BASENAME /mz-dist \ - && rm mz.tgz \ - && echo PATH=/mz-dist:$PATH >> ~/.bashrc + && rm /tmp/mz.tgz \ + && echo PATH=/mz-dist/bin:$PATH >> ~/.bashrc \ + && echo 'export LD_LIBRARY_PATH=/mz-dist/lib:LD_LIBRARY_PATH' >> ~/.bashrc ENV SHELL /bin/bash ENV PATH /mz-dist:$PATH # OSDF WORKDIR /opt/osdf -RUN wget -O /opt/osdf.zip "https://nexus.onap.org/service/local/artifact/maven/redirect?r=${REPO}&g=org.onap.optf.osdf&a=optf-osdf&e=zip&v=${MVN_ARTIFACT_VERSION}" && \ - unzip -q -o -B /opt/osdf.zip -d /opt/ && \ - rm -f /opt/osdf.zip +#RUN wget -O /opt/osdf.zip "https://nexus.onap.org/service/local/artifact/maven/redirect?r=releases&g=org.onap.optf.osdf&a=optf-osdf&e=zip&v=1.3.4" && \ +# unzip -q -o -B /opt/osdf.zip -d /opt/ && \ +# rm -f /opt/osdf.zip + +COPY onap-osdf-tm/optf-osdf-${MVN_ARTIFACT_VERSION}.zip /tmp/optf-osdf.zip +COPY onap-osdf-tm/apps /opt/osdf/apps +RUN unzip -q -o -B /tmp/optf-osdf.zip -d /opt/ && rm -f /tmp/optf-osdf.zip RUN mkdir -p /var/log/onap/optf/osdf/ RUN pip install --no-cache-dir -r requirements.txt diff --git a/docker/assembly/osdf-files.xml b/docker/assembly/osdf-files.xml new file mode 100644 index 0000000..fc8a864 --- /dev/null +++ b/docker/assembly/osdf-files.xml @@ -0,0 +1,66 @@ + + + + + + osdf-files + + + tar.gz + + false + + + + + + ${project.build.finalName}.zip + + ${project.build.directory} + / + + + + apps/** + + + **/*.pyc + **/__pycache__/** + + / + + + + diff --git a/osdf/apps/__init__.py b/osdf/apps/__init__.py new file mode 100644 index 0000000..370bc06 --- /dev/null +++ b/osdf/apps/__init__.py @@ -0,0 +1,2 @@ +import yaml +yaml.warnings({'YAMLLoadWarning': False}) \ No newline at end of file diff --git a/osdf/apps/baseapp.py b/osdf/apps/baseapp.py new file mode 100644 index 0000000..cfa7e5d --- /dev/null +++ b/osdf/apps/baseapp.py @@ -0,0 +1,166 @@ +# ------------------------------------------------------------------------- +# Copyright (c) 2015-2017 AT&T 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. +# +# ------------------------------------------------------------------------- +# + +""" +OSDF Manager Main Flask Application +""" + +import json +import ssl +import sys +import time +import traceback +from optparse import OptionParser + +import pydevd +from flask import Flask, request, Response, g +from requests import RequestException +from schematics.exceptions import DataError + +import osdf.adapters.aaf.sms as sms +import osdf.operation.responses +from osdf.config.base import osdf_config +from osdf.logging.osdf_logging import error_log, debug_log +from osdf.operation.error_handling import request_exception_to_json_body, internal_error_message +from osdf.operation.exceptions import BusinessException +from osdf.utils.mdc_utils import clear_mdc, mdc_from_json, default_mdc + +ERROR_TEMPLATE = osdf.ERROR_TEMPLATE + +app = Flask(__name__) + +BAD_CLIENT_REQUEST_MESSAGE = 'Client sent an invalid request' + + +@app.errorhandler(BusinessException) +def handle_business_exception(e): + """An exception explicitly raised due to some business rule""" + error_log.error("Synchronous error for request id {} {}".format(g.request_id, traceback.format_exc())) + err_msg = ERROR_TEMPLATE.render(description=str(e)) + response = Response(err_msg, content_type='application/json; charset=utf-8') + response.status_code = 400 + return response + + +@app.errorhandler(RequestException) +def handle_request_exception(e): + """Returns a detailed synchronous message to the calling client + when osdf fails due to a remote call to another system""" + error_log.error("Synchronous error for request id {} {}".format(g.request_id, traceback.format_exc())) + err_msg = request_exception_to_json_body(e) + response = Response(err_msg, content_type='application/json; charset=utf-8') + response.status_code = 400 + return response + + +@app.errorhandler(DataError) +def handle_data_error(e): + """Returns a detailed message to the calling client when the initial synchronous message is invalid""" + error_log.error("Synchronous error for request id {} {}".format(g.request_id, traceback.format_exc())) + + body_dictionary = { + "serviceException": { + "text": BAD_CLIENT_REQUEST_MESSAGE, + "exceptionMessage": str(e.errors), + "errorType": "InvalidClientRequest" + } + } + + body_as_json = json.dumps(body_dictionary) + response = Response(body_as_json, content_type='application/json; charset=utf-8') + response.status_code = 400 + return response + + +@app.before_request +def log_request(): + g.request_start = time.clock() + if request.get_json(): + + request_json = request.get_json() + g.request_id = request_json['requestInfo']['requestId'] + mdc_from_json(request_json) + else: + g.request_id = "N/A" + default_mdc() + + + +@app.after_request +def log_response(response): + clear_mdc() + return response + + +@app.errorhandler(500) +def internal_failure(error): + """Returned when unexpected coding errors occur during initial synchronous processing""" + error_log.error("Synchronous error for request id {} {}".format(g.request_id, traceback.format_exc())) + response = Response(internal_error_message, content_type='application/json; charset=utf-8') + response.status_code = 500 + return response + + +def get_options(argv): + program_version_string = '%%prog %s' % "v1.0" + program_longdesc = "" + program_license = "" + + parser = OptionParser(version=program_version_string, epilog=program_longdesc, description=program_license) + parser.add_option("-l", "--local", dest="local", help="run locally", action="store_true", default=False) + parser.add_option("-t", "--devtest", dest="devtest", help="run in dev/test environment", action="store_true", + default=False) + parser.add_option("-d", "--debughost", dest="debughost", help="IP Address of host running debug server", default='') + parser.add_option("-p", "--debugport", dest="debugport", help="Port number of debug server", type=int, default=5678) + opts, args = parser.parse_args(argv) + + if opts.debughost: + debug_log.debug('pydevd.settrace({}, port={})'.format(opts.debughost, opts.debugport)) + pydevd.settrace(opts.debughost, port=opts.debugport) + return opts + + +def build_ssl_context(): + ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) + ssl_context.set_ciphers('ECDHE-RSA-AES128-SHA256:EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH') + ssl_context.load_cert_chain(sys_conf['ssl_context'][0], sys_conf['ssl_context'][1]) + return ssl_context + + +def run_app(): + global sys_conf + sys_conf = osdf_config['core']['osdf_system'] + ports = sys_conf['osdf_ports'] + internal_port, external_port = ports['internal'], ports['external'] + local_host = sys_conf['osdf_ip_default'] + common_app_opts = dict(host=local_host, threaded=True, use_reloader=False) + ssl_opts = sys_conf.get('ssl_context') + if ssl_opts: + common_app_opts.update({'ssl_context': build_ssl_context()}) + opts = get_options(sys.argv) + # Load secrets from SMS + sms.load_secrets() + if not opts.local and not opts.devtest: # normal deployment + app.run(port=internal_port, debug=False, **common_app_opts) + else: + port = internal_port if opts.local else external_port + app.run(port=port, debug=True, **common_app_opts) + + +if __name__ == "__main__": + run_app() diff --git a/osdf/config/__init__.py b/osdf/config/__init__.py index 86156a1..e32d44e 100644 --- a/osdf/config/__init__.py +++ b/osdf/config/__init__.py @@ -27,6 +27,6 @@ class CoreConfig(metaclass=MetaSingleton): def get_core_config(self, config_file=None): if self.core_config is None: - self.core_config = yaml.load(open(config_file)) + self.core_config = yaml.safe_load(open(config_file)) return self.core_config diff --git a/osdf/logging/osdf_logging.py b/osdf/logging/osdf_logging.py index e457f18..a5a9739 100755 --- a/osdf/logging/osdf_logging.py +++ b/osdf/logging/osdf_logging.py @@ -191,6 +191,7 @@ class OOF_OSDFLogMessageFormatter(object): @staticmethod def received_http_response(resp): + """ """ return "Received response [code: {}, headers: {}, data: {}]".format( resp.status_code, resp.headers, resp.__dict__) diff --git a/osdf/models/api/pciOptimizationRequest.py b/osdf/models/api/pciOptimizationRequest.py deleted file mode 100644 index f5ec6b7..0000000 --- a/osdf/models/api/pciOptimizationRequest.py +++ /dev/null @@ -1,54 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) 2018 AT&T 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 schematics.types import BaseType, StringType, URLType, IntType -from schematics.types.compound import ModelType, ListType, DictType - -from .common import OSDFModel - - -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) - requestType = StringType(required=True) - numSolutions = IntType() - optimizers = ListType(StringType(required=True)) - timeout = IntType() - - -class ANRInfo(OSDFModel): - cellId = StringType(required=True) - removeableNeighbors = ListType(StringType()) - - -class CellInfo(OSDFModel): - """Information specific to CellInfo """ - networkId = StringType(required=True) - cellIdList = ListType(StringType(required=True)) - anrInputList = ListType(ModelType(ANRInfo)) - trigger = StringType() - - -class PCIOptimizationAPI(OSDFModel): - """Request for PCI optimization """ - requestInfo = ModelType(RequestInfo, required=True) - cellInfo = ModelType(CellInfo, required=True) diff --git a/osdf/models/api/pciOptimizationResponse.py b/osdf/models/api/pciOptimizationResponse.py deleted file mode 100644 index 71d0986..0000000 --- a/osdf/models/api/pciOptimizationResponse.py +++ /dev/null @@ -1,46 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) 2018 AT&T 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 schematics.types import StringType, IntType -from schematics.types.compound import ModelType, ListType - -from .common import OSDFModel - - -class PCISolution(OSDFModel): - cellId = StringType(required=True) - pci = IntType(required=True) - - -class ANRSolution(OSDFModel): - cellId = StringType(required=True) - removeableNeighbors = ListType(StringType()) - - -class Solution(OSDFModel): - networkId = StringType(required=True) - pciSolutions = ListType(ListType(ModelType(PCISolution), min_size=1)) - anrSolutions = ListType(ListType(ModelType(ANRSolution), min_size=1)) - - -class PCIOptimizationResponse(OSDFModel): - transactionId = StringType(required=True) - requestId = StringType(required=True) - requestStatus = StringType(required=True) - statusMessage = StringType() - solutions = ModelType(Solution, required=True) diff --git a/osdf/models/api/placementRequest.py b/osdf/models/api/placementRequest.py deleted file mode 100644 index a10ddc3..0000000 --- a/osdf/models/api/placementRequest.py +++ /dev/null @@ -1,105 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) 2015-2017 AT&T 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 .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) - requestType = StringType(required=True) - numSolutions = IntType() - optimizers = ListType(StringType(required=True)) - timeout = IntType() - - -class Candidates(OSDFModel): - """Preferred candidate for a resource (sent as part of a request from client)""" - identifierType = StringType(required=True) - identifiers = ListType(StringType(required=True)) - cloudOwner = StringType() - - -class ModelMetaData(OSDFModel): - """Model information for a specific resource""" - modelInvariantId = StringType(required=True) - modelVersionId = StringType(required=True) - modelName = StringType() - modelType = StringType() - modelVersion = StringType() - modelCustomizationName = StringType() - - -class LicenseModel(OSDFModel): - entitlementPoolUUID = ListType(StringType(required=True)) - licenseKeyGroupUUID = ListType(StringType(required=True)) - - -class LicenseDemands(OSDFModel): - resourceModuleName = StringType(required=True) - serviceResourceId = StringType(required=True) - resourceModelInfo = ModelType(ModelMetaData, required=True) - existingLicenses = ModelType(LicenseModel) - - -class LicenseInfo(OSDFModel): - licenseDemands = ListType(ModelType(LicenseDemands)) - - -class PlacementDemand(OSDFModel): - resourceModuleName = StringType(required=True) - serviceResourceId = StringType(required=True) - tenantId = StringType() - resourceModelInfo = ModelType(ModelMetaData, required=True) - existingCandidates = ListType(ModelType(Candidates)) - excludedCandidates = ListType(ModelType(Candidates)) - requiredCandidates = ListType(ModelType(Candidates)) - - -class ServiceInfo(OSDFModel): - serviceInstanceId = StringType(required=True) - modelInfo = ModelType(ModelMetaData, required=True) - serviceName = StringType(required=True) - - -class SubscriberInfo(OSDFModel): - """Details on the customer that subscribes to the VNFs""" - globalSubscriberId = StringType(required=True) - subscriberName = StringType() - subscriberCommonSiteId = StringType() - - -class PlacementInfo(OSDFModel): - """Information specific to placement optimization""" - requestParameters = DictType(BaseType) - placementDemands = ListType(ModelType(PlacementDemand), min_size=1) - subscriberInfo = ModelType(SubscriberInfo) - - -class PlacementAPI(OSDFModel): - """Request for placement optimization (specific to optimization and additional metadata""" - requestInfo = ModelType(RequestInfo, required=True) - placementInfo = ModelType(PlacementInfo, required=True) - licenseInfo = ModelType(LicenseInfo) - serviceInfo = ModelType(ServiceInfo, required=True) diff --git a/osdf/models/api/placementResponse.py b/osdf/models/api/placementResponse.py deleted file mode 100644 index 063a9a8..0000000 --- a/osdf/models/api/placementResponse.py +++ /dev/null @@ -1,64 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) 2015-2017 AT&T 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 .common import OSDFModel -from schematics.types import BaseType, StringType -from schematics.types.compound import ModelType, ListType, DictType - - -# TODO: update osdf.models - -class LicenseSolution(OSDFModel): - serviceResourceId = StringType(required=True) - resourceModuleName = StringType(required=True) - entitlementPoolUUID = ListType(StringType(required=True)) - licenseKeyGroupUUID = ListType(StringType(required=True)) - entitlementPoolInvariantUUID = ListType(StringType(required=True)) - licenseKeyGroupInvariantUUID = ListType(StringType(required=True)) - - -class Candidates(OSDFModel): - """Preferred candidate for a resource (sent as part of a request from client)""" - identifierType = StringType(required=True) - identifiers = ListType(StringType(required=True)) - cloudOwner = StringType() - - -class AssignmentInfo(OSDFModel): - key = StringType(required=True) - value = BaseType(required=True) - - -class PlacementSolution(OSDFModel): - serviceResourceId = StringType(required=True) - resourceModuleName = StringType(required=True) - solution = ModelType(Candidates, required=True) - assignmentInfo = ListType(ModelType(AssignmentInfo)) - - -class Solution(OSDFModel): - placementSolutions = ListType(ListType(ModelType(PlacementSolution), min_size=1)) - licenseSolutions = ListType(ModelType(LicenseSolution), min_size=1) - - -class PlacementResponse(OSDFModel): - transactionId = StringType(required=True) - requestId = StringType(required=True) - requestStatus = StringType(required=True) - statusMessage = StringType() - solutions = ModelType(Solution, required=True) diff --git a/osdf/optimizers/licenseopt/simple_license_allocation.py b/osdf/optimizers/licenseopt/simple_license_allocation.py deleted file mode 100644 index b2aaba4..0000000 --- a/osdf/optimizers/licenseopt/simple_license_allocation.py +++ /dev/null @@ -1,43 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) 2015-2017 AT&T 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.utils.mdc_utils import mdc_from_json - - -def license_optim(request_json): - """ - Fetch license artifacts associated with the service model and search licensekey-group-UUID and entitlement-pool-uuid - associated with the given att part number and nominal throughput in a request - :param request_json: Request in a JSON format - :return: A tuple of licensekey-group-uuid-list and entitlement-group-uuid-list - """ - mdc_from_json(request_json) - req_id = request_json["requestInfo"]["requestId"] - - model_name = request_json.get('placementInfo', {}).get('serviceInfo', {}).get('modelInfo', {}).get('modelName') - service_name = model_name - - license_info = [] - - for demand in request_json.get('placementInfo', {}).get('demandInfo', {}).get('licenseDemands', []): - license_info.append( - {'serviceResourceId': demand['serviceResourceId'], - 'resourceModuleName': demand['resourceModuleName'], - 'entitlementPoolList': "NOT SUPPORTED", - 'licenseKeyGroupList': "NOT SUPPORTED" - }) - return license_info diff --git a/osdf/optimizers/pciopt/configdb.py b/osdf/optimizers/pciopt/configdb.py deleted file mode 100644 index 8f003c2..0000000 --- a/osdf/optimizers/pciopt/configdb.py +++ /dev/null @@ -1,69 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) 2018 AT&T 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 datetime import datetime as dt - -from osdf.logging.osdf_logging import debug_log -from osdf.utils.interfaces import RestClient - - -def request(req_object, osdf_config, flat_policies): - """ - Process a configdb request from a Client (build Conductor 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) - :param flat_policies: policies related to PCI Opt (fetched based on request) - :return: response from ConfigDB (accounting for redirects from Conductor service - """ - cell_list_response = {} - config = osdf_config.deployment - local_config = osdf_config.core - uid, passwd = config['configDbUserName'], config['configDbPassword'] - req_id = req_object['requestInfo']['requestId'] - transaction_id = req_object['requestInfo']['transactionId'] - headers = dict(transaction_id=transaction_id) - - network_id = req_object['cellInfo']['networkId'] - - cell_list_response['network_id'] = network_id - - ts = dt.strftime(dt.now(), '%Y-%m-%d %H:%M:%S%z') - - rc = RestClient(userid=uid, passwd=passwd, method="GET", log_func=debug_log.debug, headers=headers) - - cell_list_url = '{}/{}/{}/{}'.format(config['configDbUrl'], config['configDbGetCellListUrl'], network_id, ts) - - cell_list_resp = rc.request(raw_response=True, url=cell_list_url) - cell_resp = cell_list_resp.json() - - cell_list = [] - count = 0 - for cell_id in cell_resp: - cell_info = {'cell_id': cell_id, 'id': count} - nbr_list_url = '{}/{}/{}/{}'.format(config['configDbUrl'], config['configDbGetNbrListUrl'], cell_id, ts) - nbr_list_raw = rc.request(url=nbr_list_url, raw_response=True) - cell_info['nbr_list'] = get_neighbor_list(nbr_list_raw.json()) - cell_list.append(cell_info) - count += 1 - - cell_list_response['cell_list'] = cell_list - return cell_resp, cell_list_response - - -def get_neighbor_list(nbr_list_response): - return nbr_list_response.get('nbrList', []) diff --git a/osdf/optimizers/pciopt/pci_opt_processor.py b/osdf/optimizers/pciopt/pci_opt_processor.py deleted file mode 100644 index 47c4288..0000000 --- a/osdf/optimizers/pciopt/pci_opt_processor.py +++ /dev/null @@ -1,122 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) 2018 AT&T 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. -# -# ------------------------------------------------------------------------- -# - -import traceback - -from requests import RequestException - -from osdf.logging.osdf_logging import metrics_log, MH, error_log -from osdf.operation.error_handling import build_json_error_body -from osdf.utils.interfaces import get_rest_client -from .configdb import request as config_request -from .solver.optimizer import pci_optimize as optimize -from .solver.pci_utils import get_cell_id, get_pci_value -from osdf.utils.mdc_utils import mdc_from_json - -""" -This application generates PCI Optimization API calls using the information received from PCI-Handler-MS, SDN-C -and Policy. -""" - - -def process_pci_optimation(request_json, osdf_config, flat_policies): - """ - 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) - :param flat_policies: policies related to pci (fetched based on request) - :return: response from PCI Opt - """ - try: - mdc_from_json(request_json) - rc = get_rest_client(request_json, service="pcih") - req_id = request_json["requestInfo"]["requestId"] - cell_info_list, network_cell_info = config_request(request_json, osdf_config, flat_policies) - pci_response = get_solutions(cell_info_list, network_cell_info, request_json) - - metrics_log.info(MH.inside_worker_thread(req_id)) - except Exception as err: - error_log.error("Error for {} {}".format(req_id, traceback.format_exc())) - - try: - body = build_json_error_body(err) - metrics_log.info(MH.sending_response(req_id, "ERROR")) - rc.request(json=body, noresponse=True) - except RequestException as err: - MDC.put('requestID',req_id) - error_log.error("Error sending asynchronous notification for {} {}".format(req_id, traceback.format_exc())) - raise err - - - try: - metrics_log.info(MH.calling_back_with_body(req_id, rc.url, pci_response)) - rc.request(json=pci_response, noresponse=True) - 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())) - - -def get_solutions(cell_info_list, network_cell_info, request_json): - status, pci_solutions, anr_solutions = build_solution_list(cell_info_list, network_cell_info, request_json) - return { - "transactionId": request_json['requestInfo']['transactionId'], - "requestId": request_json["requestInfo"]["requestId"], - "requestStatus": "completed", - "statusMessage": status, - "solutions": { - 'networkId': request_json['cellInfo']['networkId'], - 'pciSolutions': pci_solutions, - 'anrSolutions': anr_solutions - } - } - - -def build_solution_list(cell_info_list, network_cell_info, request_json): - status = "success" - req_id = request_json["requestInfo"]["requestId"] - try: - opt_solution = optimize(network_cell_info, cell_info_list, request_json) - pci_solutions = build_pci_solution(network_cell_info, opt_solution['pci']) - anr_solutions = build_anr_solution(network_cell_info, opt_solution.get('removables', {})) - except RuntimeError: - error_log.error("Failed finding solution for {} {}".format(req_id, traceback.format_exc())) - status = "failed" - return status, pci_solutions, anr_solutions - - -def build_pci_solution(network_cell_info, pci_solution): - pci_solutions = [] - for k, v in pci_solution.items(): - old_pci = get_pci_value(network_cell_info, k) - if old_pci != v: - response = { - 'cellId': get_cell_id(network_cell_info, k), - 'pci': v - } - pci_solutions.append(response) - return pci_solutions - - -def build_anr_solution(network_cell_info, removables): - anr_solutions = [] - for k, v in removables.items(): - response = { - 'cellId': get_cell_id(network_cell_info, k), - 'removeableNeighbors': list(map(lambda x: get_cell_id(network_cell_info, x), v)) - } - anr_solutions.append(response) - return anr_solutions diff --git a/osdf/optimizers/pciopt/solver/__init__.py b/osdf/optimizers/pciopt/solver/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/osdf/optimizers/pciopt/solver/min_confusion.mzn b/osdf/optimizers/pciopt/solver/min_confusion.mzn deleted file mode 100644 index ff56c18..0000000 --- a/osdf/optimizers/pciopt/solver/min_confusion.mzn +++ /dev/null @@ -1,98 +0,0 @@ -% ------------------------------------------------------------------------- -% Copyright (c) 2018 AT&T 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. -% -% ------------------------------------------------------------------------- -% - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Parameters and its assertions -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -% Number of cells/radios. -int: NUM_NODES; - -% Maximum number of Physical Cell Identifiers to be assigned to the nodes. -int: NUM_PCIS; - -% Number of edges between neighbor nodes. There is a edge (i,j) if and only -% if nodes i and j are neighbors, i.e., an user equipment (UE) can make -% handoff between i and j. Such edges are used to avoid **COLLISION**, i.e., -% to guarantee that nodes i and j have different PCIs. -int: NUM_NEIGHBORS; - -% Each line represents an edge between direct neighbors as defined before. -array[1..NUM_NEIGHBORS, 1..2] of int: NEIGHBORS; - -% Number of undirect neighbor pairs (j, k) such that both j and k are direct -% neighbors of node i, i.e., (j, k) exits if and only if exists (i, j) and -% (i, k). Nodes (i, k) can generate "confunsions" in the network if they have -% the same PCI. Such edges are used to avoid/minimize **CONFUSIONS**. -int: NUM_SECOND_LEVEL_NEIGHBORS; - -% Each line represents an edge between undirect neighbors as defined before. -array[1..NUM_SECOND_LEVEL_NEIGHBORS, 1..2] of int: SECOND_LEVEL_NEIGHBORS; - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Decision variables -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -% Defines the PCI for each node. -array[0..NUM_NODES-1] of var 0..NUM_PCIS-1: pci; - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Constraints -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -% Direct neighbors must have different PCIs for avoid **COLLISION**. -constraint -forall(i in 1..NUM_NEIGHBORS)( - pci[NEIGHBORS[i, 1]] != pci[NEIGHBORS[i, 2]] -); - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Objective function -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -% Total number of confusions. -var int: total_confusions = - sum([bool2int(pci[SECOND_LEVEL_NEIGHBORS[i, 1]] == - pci[SECOND_LEVEL_NEIGHBORS[i, 2]]) - | i in 1..NUM_SECOND_LEVEL_NEIGHBORS]); - -% Minimize the total number of confusions. -solve :: int_search(pci, smallest, indomain_min, complete) -minimize total_confusions; - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Output -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -output -["PCI assigment"] ++ -["\nnode,pci"] ++ -[ - "\n" ++ show(node) ++ "," ++ show(pci[node]) -| node in 0..NUM_NODES-1 -] ++ - -["\n\nConfusions"] ++ -["\nTotal confusions: " ++ show(total_confusions)] ++ -["\nConfusion pairs"] ++ -[ - "\n" ++ show(SECOND_LEVEL_NEIGHBORS[i, 1]) ++ "," ++ - show(SECOND_LEVEL_NEIGHBORS[i, 2]) -| i in 1..NUM_SECOND_LEVEL_NEIGHBORS where - fix(pci[SECOND_LEVEL_NEIGHBORS[i, 1]] == pci[SECOND_LEVEL_NEIGHBORS[i, 2]]) -] diff --git a/osdf/optimizers/pciopt/solver/min_confusion_inl.mzn b/osdf/optimizers/pciopt/solver/min_confusion_inl.mzn deleted file mode 100644 index 0f0fc91..0000000 --- a/osdf/optimizers/pciopt/solver/min_confusion_inl.mzn +++ /dev/null @@ -1,140 +0,0 @@ -% ------------------------------------------------------------------------- -% Copyright (c) 2018 AT&T 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. -% -% ------------------------------------------------------------------------- -% - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Parameters and its assertions -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -% Number of cells/radios. -int: NUM_NODES; - -% Maximum number of Physical Cell Identifiers to be assigned to the nodes. -int: NUM_PCIS; - -% Number of edges between neighbor nodes. There is a edge (i,j) if and only -% if nodes i and j are neighbors, i.e., an user equipment (UE) can make -% handoff between i and j. Such edges are used to avoid **COLLISION**, i.e., -% to guarantee that nodes i and j have different PCIs. -int: NUM_NEIGHBORS; - -% Each line represents an edge between direct neighbors as defined before. -array[1..NUM_NEIGHBORS, 1..2] of int: NEIGHBORS; - -% Number of undirect neighbor pairs (j, k) such that both j and k are direct -% neighbors of node i, i.e., (j, k) exits if and only if exists (i, j) and -% (i, k). Nodes (i, k) can generate "confunsions" in the network if they have -% the same PCI. Such edges are used to avoid/minimize **CONFUSIONS**. -int: NUM_SECOND_LEVEL_NEIGHBORS; - -% Each line represents an edge between undirect neighbors as defined before. -array[1..NUM_SECOND_LEVEL_NEIGHBORS, 1..2] of int: SECOND_LEVEL_NEIGHBORS; - -% Number of ignorable neighbor links. Such links can be ignored during -% optimization if needed. -int: NUM_IGNORABLE_NEIGHBOR_LINKS; - -% The links that can be ignored if needed. Each line represents the two ends -% of the links, like the previous structures. -array[1..NUM_IGNORABLE_NEIGHBOR_LINKS, 1..2] of int: IGNORABLE_NEIGHBOR_LINKS; - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Decision variables -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -% Defines the PCI for each node. -array[0..NUM_NODES-1] of var 0..NUM_PCIS-1: pci; - -array[1..NUM_IGNORABLE_NEIGHBOR_LINKS] of var 0..1: used_ignorables; - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Constraints -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -% Direct neighbors must have different PCIs for avoid **COLLISION**. -% Forced links. -constraint -forall(i in 1..NUM_NEIGHBORS, j in 1..NUM_IGNORABLE_NEIGHBOR_LINKS - where - NEIGHBORS[i, 1] != IGNORABLE_NEIGHBOR_LINKS[j, 1] \/ - NEIGHBORS[i, 2] != IGNORABLE_NEIGHBOR_LINKS[j, 2] -)( - pci[NEIGHBORS[i, 1]] != pci[NEIGHBORS[i, 2]] -); - - -% Ignorable links. -constraint -forall(i in 1..NUM_NEIGHBORS, j in 1..NUM_IGNORABLE_NEIGHBOR_LINKS - where - NEIGHBORS[i, 1] == IGNORABLE_NEIGHBOR_LINKS[j, 1] /\ - NEIGHBORS[i, 2] == IGNORABLE_NEIGHBOR_LINKS[j, 2] -)( - used_ignorables[j] >= bool2int(pci[NEIGHBORS[i, 1]] == pci[NEIGHBORS[i, 2]]) -); - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Objective function -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -% Total number of confusions. -var int: total_confusions = - sum([bool2int(pci[SECOND_LEVEL_NEIGHBORS[i, 1]] == - pci[SECOND_LEVEL_NEIGHBORS[i, 2]]) - | i in 1..NUM_SECOND_LEVEL_NEIGHBORS]); - -% Total number of used ignorables links. -var int: total_used_ignorables = sum(used_ignorables); - -solve :: int_search(pci, smallest, indomain_min, complete) - -% Minimize the total number of confusions. -%minimize total_confusions; - -% Minimize the total number of confusions first, -% then the number of used ignorables links. -minimize (2 * NUM_IGNORABLE_NEIGHBOR_LINKS * total_confusions) + - total_used_ignorables; - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Output -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -output -["PCI assigment"] ++ -["\nnode,pci"] ++ -[ - "\n" ++ show(node) ++ "," ++ show(pci[node]) -| node in 0..NUM_NODES-1 -] ++ -["\n\nTotal used ignorables links: " ++ show(total_used_ignorables)] ++ -["\nUsed ignorables links: "] ++ -[ - "\n" ++ show(IGNORABLE_NEIGHBOR_LINKS[i, 1]) ++ - "," ++ show(IGNORABLE_NEIGHBOR_LINKS[i, 2]) - | i in 1..NUM_IGNORABLE_NEIGHBOR_LINKS where fix(used_ignorables[i] > 0) -] ++ -["\n\nConfusions"] ++ -["\nTotal confusions: " ++ show(total_confusions)] ++ -["\nConfusion pairs"] ++ -[ - "\n" ++ show(SECOND_LEVEL_NEIGHBORS[i, 1]) ++ "," ++ - show(SECOND_LEVEL_NEIGHBORS[i, 2]) - | i in 1..NUM_SECOND_LEVEL_NEIGHBORS where - fix(pci[SECOND_LEVEL_NEIGHBORS[i, 1]] == pci[SECOND_LEVEL_NEIGHBORS[i, 2]]) -] - diff --git a/osdf/optimizers/pciopt/solver/no_conflicts_no_confusion.mzn b/osdf/optimizers/pciopt/solver/no_conflicts_no_confusion.mzn deleted file mode 100644 index 0a9b3e3..0000000 --- a/osdf/optimizers/pciopt/solver/no_conflicts_no_confusion.mzn +++ /dev/null @@ -1,86 +0,0 @@ -% ------------------------------------------------------------------------- -% Copyright (c) 2018 AT&T 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. -% -% ------------------------------------------------------------------------- -% - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Parameters and its assertions -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -% Number of cells/radios. -int: NUM_NODES; - -% Maximum number of Physical Cell Identifiers to be assigned to the nodes. -int: NUM_PCIS; - -% Number of edges between neighbor nodes. There is a edge (i,j) if and only -% if nodes i and j are neighbors, i.e., an user equipment (UE) can make -% handoff between i and j. Such edges are used to avoid **COLLISIONS**, i.e., -% to guarantee that nodes i and j have different PCIs. -int: NUM_NEIGHBORS; - -% Each line represents an edge between direct neighbors as defined before. -array[1..NUM_NEIGHBORS, 1..2] of int: NEIGHBORS; - -% Number of undirect neighbor pairs (j, k) such that both j and k are direct -% neighbors of node i, i.e., (j, k) exits if and only if exists (i, j) and -% (i, k). Nodes (i, k) can generate "confunsions" in the network if they have -% the same PCI. Such edges are used to avoid/minimize **CONFUSIONS**. -int: NUM_SECOND_LEVEL_NEIGHBORS; - -% Each line represents an edge between undirect neighbors as defined before. -array[1..NUM_SECOND_LEVEL_NEIGHBORS, 1..2] of int: SECOND_LEVEL_NEIGHBORS; - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Decision variables -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -% Defines the PCI for each node. -array[0..NUM_NODES-1] of var 0..NUM_PCIS-1: pci; - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Constraints -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -% Direct neighbors must have different PCIs for avoid **COLLISION**. -constraint -forall(i in 1..NUM_NEIGHBORS)( - pci[NEIGHBORS[i, 1]] != pci[NEIGHBORS[i, 2]] -); - -% Undirect neighbors must have different PCIs for avoid **CONFUSIONS**. -constraint -forall(i in 1..NUM_SECOND_LEVEL_NEIGHBORS)( - pci[SECOND_LEVEL_NEIGHBORS[i, 1]] != pci[SECOND_LEVEL_NEIGHBORS[i, 2]] -); - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Objective function -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -% Just satisfy the problem. -solve :: int_search(pci, smallest, indomain_min, complete) satisfy; - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Output -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -output -["node,pci\n"] ++ -[ - show(node) ++ "," ++ show(pci[node]) ++ "\n" -| node in 0..NUM_NODES-1 -] diff --git a/osdf/optimizers/pciopt/solver/optimizer.py b/osdf/optimizers/pciopt/solver/optimizer.py deleted file mode 100644 index 0a6d5a4..0000000 --- a/osdf/optimizers/pciopt/solver/optimizer.py +++ /dev/null @@ -1,137 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) 2018 AT&T 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. -# -# ------------------------------------------------------------------------- -# - -import itertools -import os -from collections import defaultdict - -import pymzn - -from .pci_utils import get_id - -BASE_DIR = os.path.dirname(__file__) - - -def pci_optimize(network_cell_info, cell_info_list, request_json): - neighbor_edges = get_neighbor_list(network_cell_info) - second_level_edges = get_second_level_neighbor(network_cell_info) - ignorable_links = get_ignorable_links(network_cell_info, request_json) - anr_flag = is_anr(request_json) - - dzn_data = build_dzn_data(cell_info_list, ignorable_links, neighbor_edges, second_level_edges, anr_flag) - - return build_pci_solution(dzn_data, ignorable_links, anr_flag) - - -def build_pci_solution(dzn_data, ignorable_links, anr_flag): - mzn_solution = solve(get_mzn_model(anr_flag), dzn_data) - - solution = {'pci': mzn_solution[0]['pci']} - - if anr_flag: - removables = defaultdict(list) - used_ignorables = mzn_solution[0]['used_ignorables'] - index = 0 - for i in ignorable_links: - if used_ignorables[index] > 0: - removables[i[0]].append(i[1]) - index += 1 - solution['removables'] = removables - return solution - - -def build_dzn_data(cell_info_list, ignorable_links, neighbor_edges, second_level_edges, anr_flag): - dzn_data = { - 'NUM_NODES': len(cell_info_list), - 'NUM_PCIS': len(cell_info_list), - 'NUM_NEIGHBORS': len(neighbor_edges), - 'NEIGHBORS': get_list(neighbor_edges), - 'NUM_SECOND_LEVEL_NEIGHBORS': len(second_level_edges), - 'SECOND_LEVEL_NEIGHBORS': get_list(second_level_edges) - } - if anr_flag: - dzn_data['NUM_IGNORABLE_NEIGHBOR_LINKS'] = len(ignorable_links) - dzn_data['IGNORABLE_NEIGHBOR_LINKS'] = get_list(ignorable_links) - return dzn_data - - -def get_mzn_model(anr_flag): - if anr_flag: - mzn_model = os.path.join(BASE_DIR, 'min_confusion_inl.mzn') - else: - mzn_model = os.path.join(BASE_DIR, 'no_conflicts_no_confusion.mzn') - return mzn_model - - -def is_anr(request_json): - return 'pci-anr' in request_json["requestInfo"]["optimizers"] - - -def get_list(edge_list): - array_list = [] - for s in edge_list: - array_list.append([s[0], s[1]]) - return sorted(array_list) - - -def solve(mzn_model, dzn_data): - return pymzn.minizinc(mzn=mzn_model, data=dzn_data) - - -def get_neighbor_list(network_cell_info): - neighbor_list = set() - for cell in network_cell_info['cell_list']: - add_to_neighbor_list(network_cell_info, cell, neighbor_list) - return neighbor_list - - -def add_to_neighbor_list(network_cell_info, cell, neighbor_list): - for nbr in cell.get('nbr_list', []): - host_id = cell['id'] - nbr_id = get_id(network_cell_info, nbr['targetCellId']) - if nbr_id and host_id != nbr_id: - neighbor_list.add((host_id, nbr_id)) - - -def get_second_level_neighbor(network_cell_info): - second_neighbor_list = set() - for cell in network_cell_info['cell_list']: - comb_list = build_second_level_list(network_cell_info, cell) - for comb in comb_list: - if comb[0] and comb[1]: - second_neighbor_list.add((comb[0], comb[1])) - return sorted(second_neighbor_list) - - -def build_second_level_list(network_cell_info, cell): - second_nbr_list = [] - for nbr in cell.get('nbr_list', []): - second_nbr_list.append(get_id(network_cell_info, nbr['targetCellId'])) - return [list(elem) for elem in list(itertools.combinations(second_nbr_list, 2))] - - -def get_ignorable_links(network_cell_info, request_json): - ignorable_list = set() - anr_input_list = request_json["cellInfo"].get('anrInputList', []) - if anr_input_list: - for anr_info in anr_input_list: - cell_id = get_id(network_cell_info, anr_info['cellId']) - anr_removable = anr_info.get('removeableNeighbors', []) - for anr in anr_removable: - ignorable_list.add((cell_id, get_id(network_cell_info, anr))) - return ignorable_list diff --git a/osdf/optimizers/pciopt/solver/pci_utils.py b/osdf/optimizers/pciopt/solver/pci_utils.py deleted file mode 100644 index 04829cf..0000000 --- a/osdf/optimizers/pciopt/solver/pci_utils.py +++ /dev/null @@ -1,40 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) 2018 AT&T 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. -# -# ------------------------------------------------------------------------- -# - - -def get_id(network_cell_info, cell_id): - for i in network_cell_info['cell_list']: - if i['cell_id'] == cell_id: - return i['id'] - return None - - -def get_cell_id(network_cell_info, id): - for i in network_cell_info['cell_list']: - if i['id'] == id: - return i['cell_id'] - return None - - -def get_pci_value(network_cell_info, id): - cell_id = get_cell_id(network_cell_info, id) - for i in network_cell_info['cell_list']: - for j in i['nbr_list']: - if cell_id == j['targetCellId']: - return j['pciValue'] - return None diff --git a/osdf/optimizers/placementopt/conductor/__init__.py b/osdf/optimizers/placementopt/conductor/__init__.py deleted file mode 100644 index 4b25e5b..0000000 --- a/osdf/optimizers/placementopt/conductor/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) 2017-2018 AT&T 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. -# -# ------------------------------------------------------------------------- -# diff --git a/osdf/optimizers/placementopt/conductor/api_builder.py b/osdf/optimizers/placementopt/conductor/api_builder.py deleted file mode 100644 index 08a7460..0000000 --- a/osdf/optimizers/placementopt/conductor/api_builder.py +++ /dev/null @@ -1,99 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) 2015-2017 AT&T 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. -# -# ------------------------------------------------------------------------- -# - -import json - -from jinja2 import Template - -import osdf.optimizers.placementopt.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, request_json): - """ - Function prepares parameters section for has request - :param group_policies: filtered policies - :param request_json: parameter data received from a client - :return: - """ - initial_params = tr.get_opt_query_data(request_json, group_policies['request_param_query']) - params = dict() - params.update({"REQUIRED_MEM": initial_params.pop("requiredMemory", "")}) - params.update({"REQUIRED_DISK": initial_params.pop("requiredDisk", "")}) - params.update({"customer_lat": initial_params.pop("customerLatitude", 0.0)}) - params.update({"customer_long": initial_params.pop("customerLongitude", 0.0)}) - params.update({"service_name": request_json['serviceInfo']['serviceName']}) - params.update({"service_id": request_json['serviceInfo']['serviceInstanceId']}) - - for key, val in initial_params.items(): - if val and val != "": - params.update({key: val}) - - return params - - -def conductor_api_builder(request_json, flat_policies: list, local_config, - template="osdf/templates/conductor_interface.json"): - """Build an OSDF southbound API call for HAS-Conductor/Placement optimization - :param request_json: parameter data received from a client - :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 the service specific information - :param prov_status: provStatus retrieved from Subscriber policy - :return: json to be sent to Conductor/placement optimization - """ - templ = Template(open(template).read()) - gp = group_policies_gen(flat_policies, local_config) - demand_vnf_name_list = [] - - for placementDemand in request_json['placementInfo']['placementDemands']: - demand_vnf_name_list.append(placementDemand['resourceModuleName'].lower()) - demand_list = tr.gen_demands(request_json, gp['vnfPolicy']) - attribute_policy_list = tr.gen_attribute_policy(demand_vnf_name_list, gp['attribute']) - distance_to_location_policy_list = tr.gen_distance_to_location_policy( - demand_vnf_name_list, gp['distance_to_location']) - inventory_policy_list = tr.gen_inventory_group_policy(demand_vnf_name_list, gp['inventory_group']) - resource_instance_policy_list = tr.gen_resource_instance_policy( - demand_vnf_name_list, gp['instance_fit']) - resource_region_policy_list = tr.gen_resource_region_policy(demand_vnf_name_list, gp['region_fit']) - zone_policy_list = tr.gen_zone_policy(demand_vnf_name_list, gp['zone']) - optimization_policy_list = tr.gen_optimization_policy(demand_vnf_name_list, gp['placement_optimization']) - reservation_policy_list = tr.gen_reservation_policy(demand_vnf_name_list, gp['instance_reservation']) - capacity_policy_list = tr.gen_capacity_policy(demand_vnf_name_list, gp['vim_fit']) - hpa_policy_list = tr.gen_hpa_policy(demand_vnf_name_list, gp['hpa']) - req_params_dict = _build_parameters(gp, request_json) - conductor_policies = [attribute_policy_list, distance_to_location_policy_list, inventory_policy_list, - resource_instance_policy_list, resource_region_policy_list, zone_policy_list, - reservation_policy_list, capacity_policy_list, hpa_policy_list] - filtered_policies = [x for x in conductor_policies if len(x) > 0] - policy_groups = list_flatten(filtered_policies) - req_info = request_json['requestInfo'] - request_type = req_info.get('requestType', None) - rendered_req = templ.render( - requestType=request_type, - demand_list=demand_list, - policy_groups=policy_groups, - optimization_policies=optimization_policy_list, - name=req_info['requestId'], - timeout=req_info['timeout'], - limit=req_info['numSolutions'], - request_params=req_params_dict, - 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/optimizers/placementopt/conductor/conductor.py b/osdf/optimizers/placementopt/conductor/conductor.py deleted file mode 100755 index 357efd1..0000000 --- a/osdf/optimizers/placementopt/conductor/conductor.py +++ /dev/null @@ -1,202 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) 2015-2017 AT&T 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 conductor API calls using the information received from SO and Policy platform. -""" - -import json -import time - -from jinja2 import Template -from requests import RequestException - -from osdf.logging.osdf_logging import debug_log -from osdf.optimizers.placementopt.conductor.api_builder import conductor_api_builder -from osdf.utils.interfaces import RestClient -from osdf.operation.exceptions import BusinessException - - -def request(req_object, osdf_config, flat_policies): - """ - Process a placement request from a Client (build Conductor API call, make the call, return result) - :param req_object: Request parameters from the client - :param osdf_config: Configuration specific to SNIRO application (core + deployment) - :param flat_policies: policies related to placement (fetched based on request) - :param prov_status: provStatus retrieved from Subscriber policy - :return: response from Conductor (accounting for redirects from Conductor service - """ - config = osdf_config.deployment - local_config = osdf_config.core - uid, passwd = config['conductorUsername'], config['conductorPassword'] - conductor_url = config['conductorUrl'] - req_id = req_object['requestInfo']['requestId'] - transaction_id = req_object['requestInfo']['transactionId'] - headers = dict(transaction_id=transaction_id) - placement_ver_enabled = config.get('placementVersioningEnabled', False) - - if placement_ver_enabled: - cond_minor_version = config.get('conductorMinorVersion', None) - if cond_minor_version is not None: - x_minor_version = str(cond_minor_version) - headers.update({'X-MinorVersion': x_minor_version}) - debug_log.debug("Versions set in HTTP header to conductor: X-MinorVersion: {} ".format(x_minor_version)) - - max_retries = config.get('conductorMaxRetries', 30) - ping_wait_time = config.get('conductorPingWaitTime', 60) - - rc = RestClient(userid=uid, passwd=passwd, method="GET", log_func=debug_log.debug, headers=headers) - conductor_req_json_str = conductor_api_builder(req_object, flat_policies, local_config) - conductor_req_json = json.loads(conductor_req_json_str) - - debug_log.debug("Sending first Conductor request for request_id {}".format(req_id)) - resp, raw_resp = initial_request_to_conductor(rc, conductor_url, conductor_req_json) - # Very crude way of keeping track of time. - # We are not counting initial request time, first call back, or time for HTTP request - total_time, ctr = 0, 2 - client_timeout = req_object['requestInfo']['timeout'] - configured_timeout = max_retries * ping_wait_time - max_timeout = min(client_timeout, configured_timeout) - - while True: # keep requesting conductor till we get a result or we run out of time - if resp is not None: - if resp["plans"][0].get("status") in ["error"]: - raise RequestException(response=raw_resp, request=raw_resp.request) - - if resp["plans"][0].get("status") in ["done", "not found"]: - if resp["plans"][0].get("recommendations"): - return conductor_response_processor(resp, raw_resp, req_id) - else: # "solved" but no solutions found - return conductor_no_solution_processor(resp, raw_resp, req_id) - new_url = resp['plans'][0]['links'][0][0]['href'] # TODO: check why a list of lists - - if total_time >= max_timeout: - raise BusinessException("Conductor could not provide a solution within {} seconds," - "this transaction is timing out".format(max_timeout)) - time.sleep(ping_wait_time) - ctr += 1 - debug_log.debug("Attempt number {} url {}; prior status={}".format(ctr, new_url, resp['plans'][0]['status'])) - total_time += ping_wait_time - - try: - raw_resp = rc.request(new_url, raw_response=True) - resp = raw_resp.json() - except RequestException as e: - debug_log.debug("Conductor attempt {} for request_id {} has failed because {}".format(ctr, req_id, str(e))) - - -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 - :return: URL to check for follow up (similar to redirects); we keep checking these till we get a result/error - """ - debug_log.debug("Payload to Conductor: {}".format(json.dumps(conductor_req_json))) - raw_resp = rc.request(url=conductor_url, raw_response=True, method="POST", json=conductor_req_json) - resp = raw_resp.json() - if resp["status"] != "template": - raise RequestException(response=raw_resp, request=raw_resp.request) - time.sleep(10) # 10 seconds wait time to avoid being too quick! - plan_url = resp["links"][0][0]["href"] - 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 - resp = raw_resp.json() - - if resp["plans"][0]["status"] in ["error"]: - raise RequestException(response=raw_resp, request=raw_resp.request) - return resp, raw_resp # now the caller of this will handle further follow-ups - - -def conductor_response_processor(conductor_response, raw_response, req_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 - - :param conductor_response: JSON response from Conductor - :param raw_response: Raw HTTP response corresponding to above - :param req_id: Id of a request - :return: JSON object that can be sent to the client's callback URL - """ - composite_solutions = [] - name_map = {"physical-location-id": "cloudClli", "host_id": "vnfHostName", - "cloud_version": "cloudVersion", "cloud_owner": "cloudOwner", - "cloud": "cloudRegionId", "service": "serviceInstanceId", "is_rehome": "isRehome", - "location_id": "locationId", "location_type": "locationType", "directives": "oof_directives"} - for reco in conductor_response['plans'][0]['recommendations']: - for resource in reco.keys(): - c = reco[resource]['candidate'] - solution = { - 'resourceModuleName': resource, - 'serviceResourceId': reco[resource].get('service_resource_id', ""), - 'solution': {"identifierType": name_map.get(c['inventory_type'], c['inventory_type']), - 'identifiers': [c['candidate_id']], - 'cloudOwner': c.get('cloud_owner', "")}, - 'assignmentInfo': [] - } - for key, value in c.items(): - if key in ["location_id", "location_type", "is_rehome", "host_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)) - - 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)) - composite_solutions.append(solution) - - request_status = "completed" if conductor_response['plans'][0]['status'] == "done" \ - else conductor_response['plans'][0]['status'] - transaction_id = raw_response.headers.get('transaction_id', "") - status_message = conductor_response.get('plans')[0].get('message', "") - - solution_info = {} - if composite_solutions: - solution_info.setdefault('placementSolutions', []) - solution_info['placementSolutions'].append(composite_solutions) - - resp = { - "transactionId": transaction_id, - "requestId": req_id, - "requestStatus": request_status, - "statusMessage": status_message, - "solutions": solution_info - } - return resp - - -def conductor_no_solution_processor(conductor_response, raw_response, request_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 - - :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") - :param template_placement_response: the template for generating response to client (plc_opt_response.jsont) - :return: JSON object that can be sent to the client's callback URL - """ - status_message = conductor_response["plans"][0].get("message") - templ = Template(open(template_placement_response).read()) - return json.loads(templ.render(composite_solutions=[], requestId=request_id, license_solutions=[], - transactionId=raw_response.headers.get('transaction_id', ""), - requestStatus="completed", statusMessage=status_message, json=json)) - - diff --git a/osdf/optimizers/placementopt/conductor/remote_opt_processor.py b/osdf/optimizers/placementopt/conductor/remote_opt_processor.py deleted file mode 100644 index 22a0307..0000000 --- a/osdf/optimizers/placementopt/conductor/remote_opt_processor.py +++ /dev/null @@ -1,81 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) 2015-2017 AT&T 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 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 -from osdf.optimizers.placementopt.conductor import conductor -from osdf.optimizers.licenseopt.simple_license_allocation import license_optim -from osdf.utils.interfaces import get_rest_client -from osdf.utils.mdc_utils import mdc_from_json - - -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 - :param osdf_config: configuration specific to OSDF app - :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") - req_id = request_json["requestInfo"]["requestId"] - transaction_id = request_json['requestInfo']['transactionId'] - - metrics_log.info(MH.inside_worker_thread(req_id)) - license_info = None - if request_json.get('licenseInfo', {}).get('licenseDemands'): - license_info = license_optim(request_json) - - # Conductor only handles placement, only call Conductor if placementDemands exist - if request_json.get('placementInfo', {}).get('placementDemands'): - metrics_log.info(MH.requesting("placement/conductor", req_id)) - placement_response = conductor.request(request_json, osdf_config, policies) - if license_info: # Attach license solution if it exists - placement_response['solutionInfo']['licenseInfo'] = license_info - else: # License selection only scenario - placement_response = { - "transactionId": transaction_id, - "requestId": req_id, - "requestStatus": "completed", - "statusMessage": "License selection completed successfully", - "solutionInfo": {"licenseInfo": license_info} - } - except Exception as err: - error_log.error("Error for {} {}".format(req_id, traceback.format_exc())) - - try: - body = build_json_error_body(err) - metrics_log.info(MH.sending_response(req_id, "ERROR")) - rc.request(json=body, noresponse=True) - except RequestException: - error_log.error("Error sending asynchronous notification for {} {}".format(req_id, traceback.format_exc())) - return - - try: - 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 - error_log.error("Error sending asynchronous notification for {} {}".format(req_id, traceback.format_exc())) - diff --git a/osdf/optimizers/placementopt/conductor/translation.py b/osdf/optimizers/placementopt/conductor/translation.py deleted file mode 100644 index 046e6e8..0000000 --- a/osdf/optimizers/placementopt/conductor/translation.py +++ /dev/null @@ -1,283 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) 2015-2017 AT&T 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. -# -# ------------------------------------------------------------------------- -# -import copy -import json -import yaml -import re - -from osdf.utils.data_conversion import text_to_symbol -from osdf.utils.programming_utils import dot_notation - -policy_config_mapping = yaml.load(open('config/has_config.yaml')).get('policy_config_mapping') - - -def get_opt_query_data(req_json, policies): - """ - Fetch service and order specific details from the requestParameters field of a request. - :param req_json: a request file - :param policies: A set of policies - :return: A dictionary with service and order-specific attributes. - """ - req_param_dict = {} - if 'requestParameters' in req_json["placementInfo"]: - req_params = req_json["placementInfo"]["requestParameters"] - for policy in policies: - for queryProp in policy['content']['queryProperties']: - attr_val = queryProp['value'] if 'value' in queryProp and queryProp['value'] != "" \ - else dot_notation(req_params, queryProp['attribute_location']) - if attr_val is not None: - req_param_dict.update({queryProp['attribute']: attr_val}) - return req_param_dict - - -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 - """ - optimization_policy_list = [] - for policy in optimization_policy: - content = policy['content'] - parameter_list = [] - parameters = ["cloud_version", "hpa_score"] - - for attr in content['objectiveParameter']['parameterAttributes']: - parameter = attr['parameter'] if attr['parameter'] in parameters else attr['parameter']+"_between" - vnfs = get_matching_vnfs(attr['resources'], vnf_list) - for vnf in vnfs: - value = [vnf] if attr['parameter'] in parameters else [attr['customerLocationInfo'], vnf] - parameter_list.append({ - attr['operator']: [attr['weight'], {parameter: value}] - }) - - optimization_policy_list.append({ - 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) - :return: List of matching VNFs - """ - resources_lcase = [x.lower() for x in resources] - if match_type == "all": # don't bother with any comparisons - return resources if set(resources_lcase) <= set(vnf_list) else None - common_vnfs = set(vnf_list) & set(resources_lcase) - common_resources = [x for x in resources if x.lower() in common_vnfs] - if match_type == "intersection": # specifically requested intersection - return list(common_resources) - return resources if common_vnfs else None # "any" match => all resources to be returned - - -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") - intersection => return intersection; "any" implies return all vnf_names if intersection is not null - :param rtype: resource type (e.g. resourceRegionProperty or resourceInstanceProperty) - None => no controller information added to the policy specification to Conductor - :return: resource policy list in a format required by Conductor - """ - resource_policy_list = [] - related_policies = [] - for policy in resource_policy: - pc = policy['content'] - demands = get_matching_vnfs(pc['resources'], vnf_list, match_type=match_type) - resource = {pc['identity']: {'type': pc['policyType'], 'demands': demands}} - - if rtype: - resource[pc['identity']]['properties'] = {'controller': pc[rtype]['controller'], - 'request': json.loads(pc[rtype]['request'])} - if demands and len(demands) != 0: - resource_policy_list.append(resource) - related_policies.append(policy) - return resource_policy_list, related_policies - - -def gen_resource_instance_policy(vnf_list, resource_instance_policy): - """Get policies governing resource instances in order to populate the Conductor API call""" - cur_policies, _ = gen_policy_instance(vnf_list, resource_instance_policy, rtype='resourceInstanceProperty') - return cur_policies - - -def gen_resource_region_policy(vnf_list, resource_region_policy): - """Get policies governing resource region in order to populate the Conductor API call""" - cur_policies, _ = gen_policy_instance(vnf_list, resource_region_policy, rtype='resourceRegionProperty') - return cur_policies - - -def gen_inventory_group_policy(vnf_list, inventory_group_policy): - """Get policies governing inventory group in order to populate the Conductor API call""" - cur_policies, _ = gen_policy_instance(vnf_list, inventory_group_policy, rtype=None) - return cur_policies - - -def gen_reservation_policy(vnf_list, reservation_policy): - """Get policies governing resource instances in order to populate the Conductor API call""" - cur_policies, _ = gen_policy_instance(vnf_list, reservation_policy, rtype='instanceReservationProperty') - return cur_policies - - -def gen_distance_to_location_policy(vnf_list, distance_to_location_policy): - """Get policies governing distance-to-location for VNFs in order to populate the Conductor API call""" - cur_policies, related_policies = gen_policy_instance(vnf_list, distance_to_location_policy, rtype=None) - for p_new, p_main in zip(cur_policies, related_policies): # add additional fields to each policy - properties = p_main['content']['distanceProperties'] - pcp_d = properties['distance'] - p_new[p_main['content']['identity']]['properties'] = { - 'distance': pcp_d['operator'] + " " + pcp_d['value'].lower() + " " + pcp_d['unit'].lower(), - 'location': properties['locationInfo'] - } - return cur_policies - - -def gen_attribute_policy(vnf_list, attribute_policy): - """Get policies governing attributes of VNFs in order to populate the Conductor API call""" - cur_policies, related_policies = gen_policy_instance(vnf_list, attribute_policy, rtype=None) - for p_new, p_main in zip(cur_policies, related_policies): # add additional fields to each policy - properties = p_main['content']['cloudAttributeProperty'] - attribute_mapping = policy_config_mapping['attributes'] # wanted attributes and mapping - p_new[p_main['content']['identity']]['properties'] = { - 'evaluate': dict((k, properties.get(attribute_mapping.get(k))) for k in attribute_mapping.keys()) - } - return cur_policies # cur_policies gets updated in place... - - -def gen_zone_policy(vnf_list, zone_policy): - """Get zone policies in order to populate the Conductor API call""" - cur_policies, related_policies = gen_policy_instance(vnf_list, zone_policy, match_type="all", rtype=None) - for p_new, p_main in zip(cur_policies, related_policies): # add additional fields to each policy - pmz = p_main['content']['affinityProperty'] - p_new[p_main['content']['identity']]['properties'] = {'category': pmz['category'], 'qualifier': pmz['qualifier']} - return cur_policies - - -def gen_capacity_policy(vnf_list, capacity_policy): - """Get zone policies in order to populate the Conductor API call""" - cur_policies, related_policies = gen_policy_instance(vnf_list, capacity_policy, rtype=None) - for p_new, p_main in zip(cur_policies, related_policies): # add additional fields to each policy - pmz = p_main['content']['capacityProperty'] - p_new[p_main['content']['identity']]['properties'] = \ - {"controller": pmz['controller'], 'request': json.loads(pmz['request'])} - return cur_policies - - -def gen_hpa_policy(vnf_list, hpa_policy): - """Get zone policies in order to populate the Conductor API call""" - cur_policies, related_policies = gen_policy_instance(vnf_list, hpa_policy, rtype=None) - for p_new, p_main in zip(cur_policies, related_policies): # add additional fields to each policy - p_new[p_main['content']['identity']]['properties'] = {'evaluate': p_main['content']['flavorFeatures']} - return cur_policies - - -def get_augmented_policy_attributes(policy_property, demand): - """Get policy attributes and augment them using policy_config_mapping and demand information""" - attributes = copy.copy(policy_property['attributes']) - remapping = policy_config_mapping['remapping'] - extra = dict((x, demand['resourceModelInfo'][remapping[x]]) for x in attributes if x in remapping) - attributes.update(extra) - return attributes - - -def get_candidates_demands(demand): - """Get demands related to candidates; e.g. excluded/required""" - res = {} - for k, v in policy_config_mapping['candidates'].items(): - if k not in demand: - continue - res[v] = [{'inventory_type': x['identifierType'], 'candidate_id': x['identifiers']} for x in demand[k]] - return res - - -def get_policy_properties(demand, policies): - """Get policy_properties for cases where there is a match with the demand""" - for policy in policies: - policy_demands = set([x.lower() for x in policy['content'].get('resources', [])]) - if demand['resourceModuleName'].lower() not in policy_demands: - continue # no match for this policy - for policy_property in policy['content']['vnfProperties']: - yield policy_property - - -def get_demand_properties(demand, policies): - """Get list demand properties objects (named tuples) from policy""" - demand_properties = [] - for policy_property in get_policy_properties(demand, policies): - prop = dict(inventory_provider=policy_property['inventoryProvider'], - inventory_type=policy_property['inventoryType'], - service_type=demand['serviceResourceId'], - service_resource_id=demand['serviceResourceId']) - - prop.update({'unique': policy_property['unique']} if 'unique' in policy_property and - policy_property['unique'] else {}) - prop['attributes'] = dict() - prop['attributes'].update({'global-customer-id': policy_property['customerId']} - if policy_property['customerId'] else {}) - prop['attributes'].update({'model-invariant-id': demand['resourceModelInfo']['modelInvariantId']} - if demand['resourceModelInfo']['modelInvariantId'] else {}) - prop['attributes'].update({'model-version-id': demand['resourceModelInfo']['modelVersionId']} - if demand['resourceModelInfo']['modelVersionId'] else {}) - prop['attributes'].update({'equipment-role': policy_property['equipmentRole']} - if policy_property['equipmentRole'] else {}) - - if policy_property.get('attributes'): - for attr_key, attr_val in policy_property['attributes'].items(): - update_converted_attribute(attr_key, attr_val, prop) - - prop.update(get_candidates_demands(demand)) - demand_properties.append(prop) - return demand_properties - - -def update_converted_attribute(attr_key, attr_val, properties): - """ - Updates dictonary of attributes with one specified in the arguments. - Automatically translates key namr from camelCase to hyphens - :param attr_key: key of the attribute - :param attr_val: value of the attribute - :param properties: dictionary with attributes to update - :return: - """ - if attr_val: - remapping = policy_config_mapping['attributes'] - if remapping.get(attr_key): - key_value = remapping.get(attr_key) - else: - key_value = re.sub('(.)([A-Z][a-z]+)', r'\1-\2', attr_key) - key_value = re.sub('([a-z0-9])([A-Z])', r'\1-\2', key_value).lower() - properties['attributes'].update({key_value: attr_val}) - - -def gen_demands(req_json, vnf_policies): - """Generate list of demands based on request and VNF policies - :param req_json: Request object from the client (e.g. MSO) - :param vnf_policies: Policies associated with demand resources (e.g. from grouped_policies['vnfPolicy']) - :return: list of demand parameters to populate the Conductor API call - """ - demand_dictionary = {} - for demand in req_json['placementInfo']['placementDemands']: - prop = get_demand_properties(demand, vnf_policies) - if len(prop) > 0: - demand_dictionary.update({demand['resourceModuleName']: prop}) - return demand_dictionary diff --git a/osdf/optimizers/routeopt/simple_route_opt.py b/osdf/optimizers/routeopt/simple_route_opt.py deleted file mode 100644 index b00180d..0000000 --- a/osdf/optimizers/routeopt/simple_route_opt.py +++ /dev/null @@ -1,153 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) 2018 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. -# -# ------------------------------------------------------------------------- -# - -import requests -from requests.auth import HTTPBasicAuth - -from osdf.utils.mdc_utils import mdc_from_json - - -class RouteOpt: - - """ - This values will need to deleted.. - only added for the debug purpose - """ - # DNS server and standard port of AAI.. - # TODO: read the port from the configuration and add to DNS - aai_host = "https://aai.api.simpledemo.onap.org:8443" - aai_headers = { - "X-TransactionId": "9999", - "X-FromAppId": "OOF", - "Accept": "application/json", - "Content-Type": "application/json", - "Real-Time": "true" - } - - def isCrossONAPLink(self, logical_link): - """ - This method checks if cross link is cross onap - :param logical_link: - :return: - """ - for relationship in logical_link["relationship-list"]["relationship"]: - if relationship["related-to"] == "ext-aai-network": - return True - return False - - def getRoute(self, request): - """ - This method checks - :param logical_link: - :return: - """ - mdc_from_json(request) - - src_access_node_id = request["srcPort"]["src-access-node-id"] - dst_access_node_id = request["dstPort"]["dst-access-node-id"] - - - ingress_p_interface = None - egress_p_interface = None - - # for the case of request_json for same domain, return the same node with destination update - if src_access_node_id == dst_access_node_id: - data = '{'\ - '"vpns":['\ - '{'\ - '"access-topology-id": "' + request["srcPort"]["src-access-topology-id"] + '",'\ - '"access-client-id": "' + request["srcPort"]["src-access-client-id"] + '",'\ - '"access-provider-id": "' + request["srcPort"]["src-access-provider-id"]+ '",'\ - '"access-node-id": "' + request["srcPort"]["src-access-node-id"]+ '",'\ - '"src-access-ltp-id": "' + request["srcPort"]["src-access-ltp-id"]+ '",'\ - '"dst-access-ltp-id": "' + request["dstPort"]["dst-access-ltp-id"] +'"'\ - '}'\ - ']'\ - '}' - return data - else: - logical_links = self.get_logical_links() - - # take the logical link where both the p-interface in same onap - if logical_links != None: - for logical_link in logical_links.get("logical-link"): - if not self.isCrossONAPLink(logical_link): - # link is in local ONAP - for relationship in logical_link["relationship-list"]["relationship"]: - if relationship["related-to"] == "p-interface": - if src_access_node_id in relationship["related-link"]: - i_interface = relationship["related-link"].split("/")[-1] - ingress_p_interface = i_interface.split("-")[-1] - if dst_access_node_id in relationship["related-link"]: - e_interface = relationship["related-link"].split("/")[-1] - egress_p_interface = e_interface.split("-")[-1] - data = '{'\ - '"vpns":['\ - '{'\ - '"access-topology-id": "' + request["srcPort"]["src-access-topology-id"] + '",'\ - '"access-client-id": "' + request["srcPort"]["src-access-client-id"] + '",'\ - '"access-provider-id": "' + request["srcPort"]["src-access-provider-id"]+ '",'\ - '"access-node-id": "' + request["srcPort"]["src-access-node-id"]+ '",'\ - '"src-access-ltp-id": "' + request["srcPort"]["src-access-ltp-id"]+ '",'\ - '"dst-access-ltp-id": "' + ingress_p_interface +'"'\ - '},'\ - '{' \ - '"access-topology-id": "' + request["dstPort"]["dst-access-topology-id"] + '",' \ - '"access-topology-id": "' + request["dstPort"]["dst-access-topology-id"]+ '",' \ - '"access-provider-id": "' + request["dstPort"]["dst-access-provider-id"]+ '",' \ - '"access-node-id": "' + request["dstPort"]["dst-access-node-id"]+ '",' \ - '"src-access-ltp-id": "' + egress_p_interface + '",' \ - '"dst-access-ltp-id": "' + request["dstPort"]["dst-access-ltp-id"] + '"' \ - '}'\ - ']'\ - '}' - return data - - - def get_pinterface(self, url): - """ - This method returns details for p interface - :return: details of p interface - """ - aai_req_url = self.aai_host + url - response = requests.get(aai_req_url, - headers=self.aai_headers, - auth=HTTPBasicAuth("AAI", "AAI"), - verify=False) - - if response.status_code == 200: - return response.json() - - - def get_logical_links(self): - """ - This method returns list of all cross ONAP links - from /aai/v14/network/logical-links?operation-status="Up" - :return: logical-links[] - """ - logical_link_url = "/aai/v13/network/logical-links?operational-status=up" - aai_req_url = self.aai_host + logical_link_url - - response = requests.get(aai_req_url, - headers=self.aai_headers, - auth=HTTPBasicAuth("AAI", "AAI"), - verify=False) - - logical_links = None - if response.status_code == 200: - return response.json() \ No newline at end of file diff --git a/osdf/templates/cms_opt_request.jsont b/osdf/templates/cms_opt_request.jsont deleted file mode 100755 index 006562b..0000000 --- a/osdf/templates/cms_opt_request.jsont +++ /dev/null @@ -1,35 +0,0 @@ -{ - "transaction_id": "{{ transaction_id }}", - "request_id": "{{ request_id }}", - "start_date" : "{{ start_time }}", - "end_date" : "{{ end_time }}", - "change_elements" : {{ json.dumps(change_elements) }}, - "constraints" : [ - { - "type" : "general_concurrency_limit", - "parameters": [{{ concurrency_limit }}] - }, - - { - "type" : "allowed_forbidden_periods", - "parameters" : {{ json.dumps(allowed_periods) }} - } - - {% if spatial_conflicts is defined and spatial_conflicts|length > 0 %} - , - { - "type" : "spatial_conflict", - "parameters": {{ json.dumps(spatial_conflicts) }} - } - {% endif %} - - - {% if critical_periods is defined and spatial_conflicts|length > 0 %} - , - { - "type" : "critical_periods", - "parameters": {{ json.dumps(critical_periods) }} - } - {% endif %} - ] -} diff --git a/osdf/templates/cms_opt_request.jsont_1707_v1 b/osdf/templates/cms_opt_request.jsont_1707_v1 deleted file mode 100755 index 75ecbe5..0000000 --- a/osdf/templates/cms_opt_request.jsont_1707_v1 +++ /dev/null @@ -1,67 +0,0 @@ -{ - "transaction_id": "{{ transaction_id }}", - "request_id": "{{ request_id }}", - "start_date" : "{{ start_time }}", - "end_date" : "{{ end_time }}", - - "change_elements" : [ - {% set comma = joiner(",") -%} - {% for element in all_upgrades -%} {{ comma() }} - { - "id" : "{{ element.id }}", - "failback_duration": {{ element.failback_duration }}, - {% if element.group_id -%} - "group_id": "{{ element.group_id }}", - {% endif %} - {% if element.scheduled_on -%} - "scheduled_on": "{{ element.scheduled_on }}", - {% endif %} - "duration": {{ element.duration }} - } - {% endfor -%} - ], - - "constraints" : [ - { - "type" : "general_concurrency_limit", - "parameters": [{{ concurrency_limit }}] - }, - - { - "type" : "allowed_forbidden_periods", - "parameters" : [ - {% set comma = joiner(",") -%} - {% for idx in all_pending -%} {{ comma() }} - { "id" : "{{ idx.id }}", - "allowed_periods": [ {{ allowed_periods }}] - } - {% endfor -%} - ] - }, - { - "type" : "spatial_conflict", - "parameters": [ - {% set comma = joiner(",") -%} - {% for pserver, vce_list in vce_pserver_mapping.items() -%} {{ comma() }} - { - "spatial_entity": "{{ pserver }}", - "affected_entities": {{ vce_list }} - } - {% endfor -%} - ] - }, - - { - "type" : "critical_periods", - "parameters": [ - {% set comma = joiner(",") -%} - {% for element, conflict_period in conflict_interval.items() -%} {{ comma() }} - { - "id" : "{{ element }}", - "periods": [{{ conflict_period }}] - } - {% endfor -%} - ] - } - ] -} diff --git a/osdf/templates/cms_opt_request_1702.jsont b/osdf/templates/cms_opt_request_1702.jsont deleted file mode 100755 index bcafa45..0000000 --- a/osdf/templates/cms_opt_request_1702.jsont +++ /dev/null @@ -1,63 +0,0 @@ -{ - "request_id": "{{ request_id }}", - "startdate" : "{{ start_time }}", - "enddate" : "{{ end_time }}", - - "change_elements" : [ -{% set comma = joiner(",") -%} -{% for element in all_upgrades -%} {{ comma() }} - { "id" : "{{ element.id }}", - {% if element.scheduled -%} "scheduled_on": "{{ element.scheduled }}", {% endif -%} - "duration": {{ element.duration }}, {# duration in seconds #} - "failback_duration": {{ element.failback_duration }}, {# duration in seconds #} - "group_id": {{ element.group_id }}, {# duration in seconds #} - }{% endfor -%} - ], - - "constraints" : [ - { - "type" : "general_concurrency_limit", - "parameters" : [ {{ general_concurrency_limit }} ] - }, - - { - "type" : "allowed_forbidden_periods", - "parameters" : [ -{% set comma = joiner(",") -%} -{% for idx in all_pending -%} {{ comma() }} - { "id" : "{{ idx.id }}", - "allowed_periods": [ {% set comma2 = joiner(",") -%} - {% for period in allowed_periods -%} {{ comma2() }} [{{ json.dumps(period[0]) }}, {{ json.dumps(period[1]) }}] - {% endfor -%} ] }{% endfor -%} - ] - } - -{% if p_v_conflict is defined and p_v_conflict|length > 0 %} - , - { - "type" : "critical_periods", - "description" : "Simultaneous upgrades", - "parameters" : [ -{% set comma2 = joiner(",") -%} -{% for element in p_v_conflict -%} {{ comma2() }} - { - "id" : "{{ element[0] }}", - "periods" : [{{ json.dumps(element[0]) }}, {{ json.dumps(element[1]) }}] - } -{% endfor -%} -{% endif %} - -{% for pserver, vce_group in grouped_vces.items() -%} {{ comma() }} - , - { - "id" : "{{ pserver }}", - "name" : "VCE's on pserver {{ pserver }}", - "description": "Only some VCEs on a pserver can be upgraded at a time", - "max_num_upgrades" : {{ max_num_upgrades(vce_group) }}, - "upgrades" : {{ json.dumps(vce_group) }} - } -{% endfor -%} - ] - } - ] -} diff --git a/osdf/templates/cms_opt_response.jsont b/osdf/templates/cms_opt_response.jsont deleted file mode 100644 index a8817df..0000000 --- a/osdf/templates/cms_opt_response.jsont +++ /dev/null @@ -1,8 +0,0 @@ -{ - "transactionId": "{{transaction_id}}", - "scheduleId":"{{schedule_id}}", - "requestState": "{{request_state}}", - "status": "{{status}}", - "description": "{{description}}", - "schedule": {{schedule}} -} \ No newline at end of file diff --git a/osdf/templates/conductor_interface.json b/osdf/templates/conductor_interface.json deleted file mode 100755 index 030d6a0..0000000 --- a/osdf/templates/conductor_interface.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "{{ name }}", - "files": {}, - "timeout": {{ timeout }}, - "num_solution": "{{ limit }}", - "template": { - "homing_template_version": "2017-10-10", - "parameters": { - {% set comma=joiner(",") %} - {% for key, value in request_params.items() %} {{ comma() }} - "{{key}}": {{ json.dumps(value) }} - {% endfor %} - }, - "locations": { - "customer_loc": { - "latitude": { "get_param": "customer_lat" }, - "longitude": { "get_param": "customer_long" } - } - }, - "demands": {{ json.dumps(demand_list) }}, - {% set comma_main = joiner(",") %} - "constraints": { - {% set comma=joiner(",") %} - {% for elem in policy_groups %} {{ comma() }} - {% for key, value in elem.items() %} - "{{key}}": {{ json.dumps(value) }} - {% endfor %} - {% endfor %} - }, - "optimization": { - {% set comma=joiner(",") %} - {% for elem in optimization_policies %} {{ comma() }} - {% for key, value in elem.items() %} - "{{key}}": {{ json.dumps(value) }} - {% endfor %} - {% endfor %} - } - } -} diff --git a/osdf/templates/license_opt_request.jsont b/osdf/templates/license_opt_request.jsont deleted file mode 100644 index 7baa759..0000000 --- a/osdf/templates/license_opt_request.jsont +++ /dev/null @@ -1,6 +0,0 @@ -{ - "transactionId": "{{transaction_id}}", - "requestId": "{{request_id}}", - "partNumber": "{{part_number}}", - "licenseModel" : "{{artifact}}" -} \ No newline at end of file diff --git a/osdf/templates/plc_opt_request.jsont b/osdf/templates/plc_opt_request.jsont deleted file mode 100755 index a218b8a..0000000 --- a/osdf/templates/plc_opt_request.jsont +++ /dev/null @@ -1,142 +0,0 @@ -{ - "name": "{{ name }}", - "files": "{{ files }}", - "timeout": "{{ timeout }}", - "num_solution": "{{ limit }}", - "template": { - "CUST_ID": "{{ cust_id }}", - "E2EVPNKEY": "{{ e2evpnkey }}", - "UCPEHOST": "{{ ucpehost }}", - "WAN_PORT1_UP": "{{ wan_port1_up }}", - "WAN_PORT1_DOWN": "{{ wan_port1_down }}", - "EFFECTIVE_BANDWIDTH": "{{ effective_bandwidth }}", - "SERVICE_INST": "{{ service_inst }}", - "locations": { - "customer_loc": { - "host_name": "{{ ucpehost }}" - } - }, - "demands": [ - {% set comma=joiner(",") %} - {% for demand in demand_list %} {{ comma() }} - { - "{{ demand.vnf_name }}": [ - {% set comma2=joiner(",") %} - {% for property in demand.property %} - "inventory_provider": {{ property.inventory_provider }}, - "inventory_type": {{ property.inventory_type }}, - "service_type": {{ property.service_type }}, - "customer_id": {{ property.customer_id }}, - "candidate_id": {{ property.candidate_id }} - {% endfor %} - ] - } - {% endfor %} - ], - "constraints": { - {% set comma_main=joiner(",") %} - - {% if attribute_policy_list %} {{ comma_main() }} {% endif %} - {% set comma=joiner(",") %} - {% for attribute in attribute_policy_list %} {{ comma() }} - attribute['identity'] : { - "type": {{ attribute['type'] }}, - "demands": {{ attribute['demands'] }}, - "properties": { - "evaluate": { - "hypervisor": {{ attribute['property']['hypervisor'] }}, - "aic_version": {{ attribute['property']['aicVersion'] }}, - "aic_type": {{ attribute['property']['aicType'] }}, - "dataplane": {{ attribute['property']['datatype'] }}, - "network_roles": {{ attribute['property']['networkRoles'] }}, - "complex": {{ attribute['property']['complex'] }} - } - } - } - {% endfor %} - - {% if distance_to_location_policy_list %} {{ comma_main() }} {% endif %} - {% set comma=joiner(",") %} - {% for distance_location in distance_to_location_policy_list %} {{ comma() }} - distance_location['identity'] : { - "type": {{ distance_location['type'] }}, - "demands": {{ distance_location['demands'] }}, - "properties": { - "distance": {{ distance_location['property']['distance'] }}, - "location": {{ distance_location['property']['location'] }} - } - } - {% endfor %} - - {% if inventory_policy_list %} {{ comma_main() }} {% endif %} - {% set comma=joiner(",") %} - {% for inventory in inventory_policy_list %} {{ comma() }} - inventory['identity'] : { - "type": {{ inventory['type'] }}, - "demands": {{ inventory['demands'] }} - } - {% endfor %} - - {% if resource_instance_policy_list %} {{ comma_main() }} {% endif %} - {% set comma=joiner(",") %} - {% for resource_instance in resource_instance_policy_list %} {{ comma() }} - resource_instance['identity'] : { - "type": {{ resource_instance['type'] }}, - "demands": {{ resource_instance['demands'] }}, - "properties": { - "controller": {{ resource_instance['property']['controller'] }}, - "request": {{ resource_instance['property']['request'] }} - } - } - {% endfor %} - - {% if resource_region_policy_list %} {{ comma_main() }} {% endif %} - {% set comma=joiner(",") %} - {% for resource_region in resource_region_policy_list %} {{ comma() }} - resource_region['identity'] : { - "type": {{ resource_region['type'] }}, - "demands": {{ resource_region['demands'] }}, - "properties": { - "controller": {{ resource_region['property']['controller'] }}, - "request": {{ resource_region['property']['request'] }} - } - } - {% endfor %} - - {% if zone_policy_list %} {{ comma_main() }} {% endif %} - {% set comma=joiner(",") %} - {% for zone in zone_policy_list %} {{ comma() }} - zone['identity'] : { - "type": {{ zone['type'] }}, - "demands": {{ zone['demands'] }}, - "properties": { - "qualifier": {{ resource_region['property']['qualifier'] }}, - "category": {{ resource_region['property']['category'] }} - } - } - {% endfor %} - - {% if optmization_policy_list %} {{ comma_main() }} {% endif %} - {% set comma=joiner(",") %} - {% for optimization in optimization_policy_list %} {{ comma() }} - "optimization" : { - {{ optimization['objective'] }}: { - "sum": [ - {% set comma2=joiner(",") %} - {% for parameter in optimization['parameter'] %} {{ comma() }} - { - "product": [ - {{ parameter['weight'] }}, - { - "distance_between": [{{ parameter['customerLocation'] }},{{ parameter['demand'] }}] - } - ] - } - {% endfor %} - ] - } - } - {% endfor %} - } - } -} diff --git a/osdf/templates/plc_opt_response.jsont b/osdf/templates/plc_opt_response.jsont deleted file mode 100755 index e5709e7..0000000 --- a/osdf/templates/plc_opt_response.jsont +++ /dev/null @@ -1,10 +0,0 @@ -{ - "requestId": "{{requestId}}", - "transactionId": "{{transacationId}}", - "requestStatus": "{{requestStatus}}", - "statusMessage": "{{statusMessage}}" - "solutions": { - "placementSolutions": {{ json.dumps(composite_solutions) }}, - "licenseSolutions":{{ json.dumps(license_solutions) }} - } -} diff --git a/osdf/templates/policy_request.jsont b/osdf/templates/policy_request.jsont deleted file mode 100755 index 3a9e201..0000000 --- a/osdf/templates/policy_request.jsont +++ /dev/null @@ -1,3 +0,0 @@ -{ - "policyName": "{{policy_name}}" {# we currently only support query by policy name only -- policyName #} -} diff --git a/osdf/templates/test_cms_nb_req_from_client.jsont b/osdf/templates/test_cms_nb_req_from_client.jsont deleted file mode 100755 index a60c8ff..0000000 --- a/osdf/templates/test_cms_nb_req_from_client.jsont +++ /dev/null @@ -1,19 +0,0 @@ -{ - "schedulingInfo": { - "change_management_id": "{{ change_management_id }}", - "start_time": "{{ start_time }}", - "end_time": "{{ end_time }}", - "policy_id": {{ json.dumps(policy_id) }}, {# a list of policy Ids #} - "service_type": "{{ service_type }}", - "workflow_type": "{{ workflow_type }}", - "upgrades": {{ json.dumps(upgrades) }} {# a list of node Ids #} - }, - "requestInfo": { - "requestId": "{{ requestId }}", - "sourceId": "{{ sourceId }}", - "optimizer": "{{ optimizer }}", - "numSolutions": "{{ numSolutions }}", - "callbackUrl" : "{{ callbackUrl }}" - } -} - diff --git a/osdf/templates/test_plc_nb_req_from_client.jsont b/osdf/templates/test_plc_nb_req_from_client.jsont deleted file mode 100755 index 998ffb3..0000000 --- a/osdf/templates/test_plc_nb_req_from_client.jsont +++ /dev/null @@ -1,52 +0,0 @@ -{ - "requestInfo": { - "requestId": "{{requestId}}", - "sourceId": "{{sourceId}}", - "optimizer": "{{optimizer}}", - "numSolutions": {{numSolutions}}, - "timeout": {{timeout}}, - "callbackUrl" : "{{callbackUrl}}" - }, - "placementInfo": { - "modelInfo": { - "modelType": "{{modelType}}", - "modelInvariant": "{{modelInvariantId}}", - "modelVersionId": "{{modelVersionId}}", - "modelName": "{{modelName}}", - "modelVersion": "{{modelVersion}}", - "modelCustomizationId": "{{modelCustomizationId}}" - }, - "subscriberInfo": { - "globalSubscriberId": "{{globalSubscriberId}}", - "subscriberName": "{{subscriberName}}", - "subscriberCommonSiteId": "{{subscriberCommonSiteId}}", - "ucpeHostName": "{{ucpeHostName}}" - }, - "policyId": {{json.dumps(policyId)}}, - "vnfInfo": { - "vnfType": "{{vnfType}}", - "vnfPartNumber": "{{vnfPartNumber}}", - "nominalThroughput": "{{nominalThroughput}}", - "vnfSoftwareVersion": "{{vnfSoftwareVersion}}", - "vnfManagementOption": "{{vnfManagementOption}}" - }, - "vpnInfo": { - "vpnId": "{{vpnId}}", - "pvcId": "{{pvcId}}" - }, - "serviceInfo": { - "dhvServiceInfo": { - "serviceInstanceId": "{{serviceInstanceId}}", - "serviceType": "{{serviceType}}", - "e2evpnkey": "{{e2evpnkey}}", - "dhvSiteEffectiveTransportBandwidth": {{dhvSiteEffectiveTransportBandwidth}}, - "dhvIPSecTransportBandwidthUp": {{dhvIPSecTransportBandwidthUp}}, - "dhvIPSecTransportBandwidthDown": {{dhvIPSecTransportBandwidthDown}}, - "dhvIPSec2TransportBandwidthUp": {{dhvIPSec2TransportBandwidthUp}}, - "dhvIPSec2TransportBandwidthDown": {{dhvIPSec2TransportBandwidthDown}}, - "dhvVendorName": "{{dhvVendorName}}" - } - }, - "demandInfo": {{json.dumps(demandInfo)}} - } -} diff --git a/osdf/utils/mdc_utils.py b/osdf/utils/mdc_utils.py index 6da67bd..b98cbf0 100644 --- a/osdf/utils/mdc_utils.py +++ b/osdf/utils/mdc_utils.py @@ -42,11 +42,17 @@ def default_server_info(): MDC.put('serverIPAddress', server_ip_address) -def mdc_from_json(request_json): +def default_mdc(): MDC.put('instanceUUID', uuid.uuid1()) MDC.put('serviceName', 'OOF_OSDF') MDC.put('threadID', threading.currentThread().getName()) default_server_info() + MDC.put('requestID', 'N/A') + MDC.put('partnerName', 'N/A') + + +def mdc_from_json(request_json): + default_mdc() MDC.put('requestID', request_json['requestInfo']['requestId']) MDC.put('partnerName', request_json['requestInfo']['sourceId']) diff --git a/osdfapp.py b/osdfapp.py index 1cfb8a3..71106fd 100755 --- a/osdfapp.py +++ b/osdfapp.py @@ -21,99 +21,24 @@ OSDF Manager Main Flask Application """ import json -import ssl -import sys -import time -import traceback -from optparse import OptionParser from threading import Thread # for scaling up, may need celery with RabbitMQ or redis -import pydevd -import yaml -from flask import Flask, request, Response, g -from requests import RequestException -from schematics.exceptions import DataError +from flask import request, g -yaml.warnings({'YAMLLoadWarning': False}) - -import osdf.adapters.aaf.sms as sms -import osdf.operation.responses +from osdf.apps.baseapp import app, run_app +from apps.pci.models.api.pciOptimizationRequest import PCIOptimizationAPI +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 +from apps.route.optimizers.simple_route_opt import RouteOpt 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.logging.osdf_logging import MH, audit_log, error_log, debug_log -from osdf.models.api.pciOptimizationRequest import PCIOptimizationAPI -from osdf.models.api.placementRequest import PlacementAPI -from osdf.operation.error_handling import request_exception_to_json_body, internal_error_message -from osdf.operation.exceptions import BusinessException +from osdf.logging.osdf_logging import MH, audit_log from osdf.operation.responses import osdf_response_for_request_accept as req_accept -from osdf.optimizers.pciopt.pci_opt_processor import process_pci_optimation -from osdf.optimizers.placementopt.conductor.remote_opt_processor import process_placement_opt -from osdf.optimizers.routeopt.simple_route_opt import RouteOpt from osdf.utils import api_data_utils -from osdf.utils.mdc_utils import clear_mdc, mdc_from_json from osdf.webapp.appcontroller import auth_basic -ERROR_TEMPLATE = osdf.ERROR_TEMPLATE - -app = Flask(__name__) - -BAD_CLIENT_REQUEST_MESSAGE = 'Client sent an invalid request' - - -@app.errorhandler(BusinessException) -def handle_business_exception(e): - """An exception explicitly raised due to some business rule""" - error_log.error("Synchronous error for request id {} {}".format(g.request_id, traceback.format_exc())) - err_msg = ERROR_TEMPLATE.render(description=str(e)) - response = Response(err_msg, content_type='application/json; charset=utf-8') - response.status_code = 400 - return response - - -@app.errorhandler(RequestException) -def handle_request_exception(e): - """Returns a detailed synchronous message to the calling client - when osdf fails due to a remote call to another system""" - error_log.error("Synchronous error for request id {} {}".format(g.request_id, traceback.format_exc())) - err_msg = request_exception_to_json_body(e) - response = Response(err_msg, content_type='application/json; charset=utf-8') - response.status_code = 400 - return response - - -@app.errorhandler(DataError) -def handle_data_error(e): - """Returns a detailed message to the calling client when the initial synchronous message is invalid""" - error_log.error("Synchronous error for request id {} {}".format(g.request_id, traceback.format_exc())) - - body_dictionary = { - "serviceException": { - "text": BAD_CLIENT_REQUEST_MESSAGE, - "exceptionMessage": str(e.errors), - "errorType": "InvalidClientRequest" - } - } - - body_as_json = json.dumps(body_dictionary) - response = Response(body_as_json, content_type='application/json; charset=utf-8') - response.status_code = 400 - return response - - -@app.before_request -def log_request(): - g.request_start = time.clock() - request_json = request.get_json() - g.request_id = request_json['requestInfo']['requestId'] - mdc_from_json(request_json) - - -@app.after_request -def log_response(response): - clear_mdc() - return response - @app.route("/api/oof/v1/healthcheck", methods=["GET"]) def do_osdf_health_check(): @@ -194,59 +119,5 @@ def do_pci_optimization(): request_status="accepted", status_message="") -@app.errorhandler(500) -def internal_failure(error): - """Returned when unexpected coding errors occur during initial synchronous processing""" - error_log.error("Synchronous error for request id {} {}".format(g.request_id, traceback.format_exc())) - response = Response(internal_error_message, content_type='application/json; charset=utf-8') - response.status_code = 500 - return response - - -def get_options(argv): - program_version_string = '%%prog %s' % "v1.0" - program_longdesc = "" - program_license = "" - - parser = OptionParser(version=program_version_string, epilog=program_longdesc, description=program_license) - parser.add_option("-l", "--local", dest="local", help="run locally", action="store_true", default=False) - parser.add_option("-t", "--devtest", dest="devtest", help="run in dev/test environment", action="store_true", - default=False) - parser.add_option("-d", "--debughost", dest="debughost", help="IP Address of host running debug server", default='') - parser.add_option("-p", "--debugport", dest="debugport", help="Port number of debug server", type=int, default=5678) - opts, args = parser.parse_args(argv) - - if opts.debughost: - debug_log.debug('pydevd.settrace({}, port={})'.format(opts.debughost, opts.debugport)) - pydevd.settrace(opts.debughost, port=opts.debugport) - return opts - - -def build_ssl_context(): - ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) - ssl_context.set_ciphers('ECDHE-RSA-AES128-SHA256:EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH') - ssl_context.load_cert_chain(sys_conf['ssl_context'][0], sys_conf['ssl_context'][1]) - return ssl_context - - if __name__ == "__main__": - - sys_conf = osdf_config['core']['osdf_system'] - ports = sys_conf['osdf_ports'] - internal_port, external_port = ports['internal'], ports['external'] - - local_host = sys_conf['osdf_ip_default'] - common_app_opts = dict(host=local_host, threaded=True, use_reloader=False) - - ssl_opts = sys_conf.get('ssl_context') - if ssl_opts: - common_app_opts.update({'ssl_context': build_ssl_context()}) - - opts = get_options(sys.argv) - # Load secrets from SMS - sms.load_secrets() - if not opts.local and not opts.devtest: # normal deployment - app.run(port=internal_port, debug=False, **common_app_opts) - else: - port = internal_port if opts.local else external_port - app.run(port=port, debug=True, **common_app_opts) + run_app() diff --git a/pom.xml b/pom.xml index 4b513ea..5ac2477 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,11 @@ python **/**.py,osdfapp.py test/**.py + yyyyMMdd'T'HHmmss'Z' + ${maven.build.timestamp} + ${project.version} + nexus3.onap.org:10003 + ${osdf.docker.repository}/onap/optf-osdf @@ -79,6 +84,80 @@ 2 + + org.codehaus.groovy.maven + gmaven-plugin + 1.0 + + + validate + + execute + + + ${project.basedir}/script/TagVersion.groovy + + + + + + io.fabric8 + docker-maven-plugin + 0.26.0 + + true + 1.23 + + + ${image.namespace} + optf-osdf + + true + + latest + ${project.docker.latesttagtimestamp.version} + ${project.docker.latesttag.version} + + + ${project.basedir}/docker/Dockerfile + + ${project.basedir}/docker/assembly/osdf-files.xml + onap-osdf-tm + + + ${project.version} + ${project.repo} + + + + + + + + + + generate-images + install + + build + + + + push-images + deploy + + build + push + + + ${image.namespace}:%l + + + + diff --git a/requirements.txt b/requirements.txt index 0ea452e..8001016 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,8 +9,7 @@ python-dateutil>=2.5.3 PyYAML>=3.12 requests>=2.14.2 schematics>=2.0.0 -docopt>=0.6.2 pydevd==1.4.0 onapsmsclient>=0.0.4 -pymzn==0.17.1 +pymzn>=0.18.3 onappylog>=1.0.9 diff --git a/script/TagVersion.groovy b/script/TagVersion.groovy new file mode 100644 index 0000000..6ed6558 --- /dev/null +++ b/script/TagVersion.groovy @@ -0,0 +1,45 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP OSDF + * ================================================================================ + * Copyright (C) 2018 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * 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. + * ============LICENSE_END============================================ + * =================================================================== + * + */ + +package org.onap.osdf.maven.scripts + +println project.properties['osdf.project.version'] + +def versionTag +if ( project.properties['osdf.project.version'] != null ) { + versionArray = project.properties['osdf.project.version'].split('\\.|-'); + versionTag = versionArray[0] + '.' + versionArray[1] + '.' + versionArray[2] + timestamp = project.properties['osdf.build.timestamp'] +} + +if ( project.properties['osdf.project.version'].endsWith("-SNAPSHOT") ) { + project.properties['project.docker.latesttag.version']=versionTag + "-SNAPSHOT-latest"; + project.properties['project.docker.latesttagtimestamp.version']=versionTag + "-SNAPSHOT-"+timestamp; + project.properties['project.repo'] = 'snapshots' +} else { + project.properties['project.docker.latesttag.version']=baseTag + "-STAGING-latest"; + project.properties['project.docker.latesttagtimestamp.version']=versionTag + "-STAGING-"+timestamp; + project.properties['project.repo'] = 'releases' +} + +println 'New Tag for docker: ' + project.properties['project.docker.latesttag.version']; \ No newline at end of file diff --git a/test/conductor/test_conductor_calls.py b/test/conductor/test_conductor_calls.py index 77d9a72..52e0367 100644 --- a/test/conductor/test_conductor_calls.py +++ b/test/conductor/test_conductor_calls.py @@ -17,7 +17,7 @@ # import unittest -from osdf.optimizers.placementopt.conductor import conductor +from apps.placement.optimizers.conductor import conductor import osdf.config.loader as config_loader from osdf.utils.interfaces import json_from_file from osdf.utils.programming_utils import DotDict diff --git a/test/conductor/test_conductor_translation.py b/test/conductor/test_conductor_translation.py index 27711f5..ad70157 100644 --- a/test/conductor/test_conductor_translation.py +++ b/test/conductor/test_conductor_translation.py @@ -15,14 +15,11 @@ # # ------------------------------------------------------------------------- # -import mock import unittest -from flask import Response -from mock import patch from osdf.adapters.local_data import local_policies -from osdf.optimizers.placementopt.conductor import translation as tr -from osdf.utils.interfaces import json_from_file, yaml_from_file +from apps.placement.optimizers.conductor import translation as tr +from osdf.utils.interfaces import json_from_file class TestConductorTranslation(unittest.TestCase): diff --git a/test/config/log.yml b/test/config/log.yml new file mode 100644 index 0000000..0b8815f --- /dev/null +++ b/test/config/log.yml @@ -0,0 +1,95 @@ +version: 1 +disable_existing_loggers: True + +loggers: + error: + handlers: [error_handler] + level: "WARN" + propagate: True + debug: + handlers: [debug_handler] + level: "DEBUG" + propagate: True + metrics: + handlers: [metrics_handler] + level: "INFO" + propagate: True + audit: + handlers: [audit_handler] + level: "INFO" + propagate: True +handlers: + debug_handler: + level: "DEBUG" + class: "logging.handlers.TimedRotatingFileHandler" + filename: "logs/debug.log" + formatter: "debugFormat" + when: midnight + interval: 1 + utc: True + delay: False + backupCount: 10 + error_handler: + level: "WARN" + class: "logging.handlers.TimedRotatingFileHandler" + filename: "logs/error.log" + formatter: "errorFormat" + when: midnight + interval: 1 + utc: True + delay: False + backupCount: 10 + metrics_handler: + level: "INFO" + class: "logging.handlers.TimedRotatingFileHandler" + filename: "logs/metrics.log" + formatter: "metricsFormat" + when: midnight + interval: 1 + utc: True + delay: False + backupCount: 10 + audit_handler: + level: "INFO" + class: "logging.handlers.TimedRotatingFileHandler" + filename: "logs/audit.log" + formatter: "auditFormat" + when: midnight + interval: 1 + utc: True + delay: False + backupCount: 10 +formatters: + standard: + format: "%(asctime)s|||||%(name)s||%(thread)||%(funcName)s||%(levelname)s||%(message)s" + debugFormat: + format: "%(asctime)s|||||%(name)s||%(thread)s||%(funcName)s||%(levelname)s||%(message)s||||%(mdc)s" + mdcfmt: "{requestID} {threadID} {serverName} {serviceName} {instanceUUID} {upperLogLevel} {severity} {serverIPAddress} {server} {IPAddress} {className} {timer} {detailMessage}" + datefmt: "%Y-%m-%d %H:%M:%S" + (): onaplogging.mdcformatter.MDCFormatter + errorFormat: + format: "%(asctime)s|||||%(name)s||%(thread)s||%(funcName)s||%(levelname)s||%(message)s||||%(mdc)s" + mdcfmt: "{requestID} {threadID} {serviceName} {partnerName} {targetEntity} {targetServiceName} {errorCode} {errorDescription} {detailMessage}" + datefmt: "%Y-%m-%d %H:%M:%S" + (): onaplogging.mdcformatter.MDCFormatter + auditFormat: + format: "%(asctime)s|||||%(name)s||%(thread)s||%(funcName)s||%(levelname)s||%(message)s||||%(mdc)s" + mdcfmt: "{requestID} {serviceInstanceID} {threadID} {serverName} {serviceName} {partnerName} {statusCode} {responseCode} {responseDescription} {instanceUUID} {upperLogLevel} {severity} \ + {serverIPAddress} {timer} {server} {IPAddress} {className} {unused} {processKey} {customField1} {customField2} {customField3} {customField4} {detailMessage}" + datefmt: "%Y-%m-%d %H:%M:%S" + (): onaplogging.mdcformatter.MDCFormatter + metricsFormat: + format: "%(asctime)s|||||%(name)s||%(thread)s||%(funcName)s||%(levelname)s||%(message)s||||%(mdc)s" + mdcfmt: "{requestID} {serviceInstanceID} {threadID} {serverName} {serviceName} {partnerName} \ + {targetEntity} {targetServiceName} {statusCode} {responseCode} {responseDescription} \ + {instanceUUID} {upperLogLevel} {severity} {serverIPAddress} {timer} {server} {IPAddress} \ + {className} {unused} {processKey} {targetVirtualEntity} {customField1} {customField2} \ + {customField3} {customField4} {detailMessage}" + datefmt: "%Y-%m-%d %H:%M:%S" + (): onaplogging.mdcformatter.MDCFormatter + + mdcFormat: + format: "%(asctime)s|||||%(name)s||%(thread)s||%(funcName)s||%(levelname)s||%(message)s||||%(mdc)s" + mdcfmt: "{requestID} {invocationID} {serviceName} {serviceIP}" + datefmt: "%Y-%m-%d %H:%M:%S" + (): onaplogging.mdcformatter.MDCFormatter diff --git a/test/configdb/test_configdb_calls.py b/test/configdb/test_configdb_calls.py index eb799e7..a6f1c97 100644 --- a/test/configdb/test_configdb_calls.py +++ b/test/configdb/test_configdb_calls.py @@ -16,7 +16,7 @@ # ------------------------------------------------------------------------- # -from osdf.optimizers.pciopt.configdb import request +from apps.pci.optimizers.configdb import request import osdf.config.loader as config_loader from osdf.utils.interfaces import json_from_file from osdf.utils.programming_utils import DotDict diff --git a/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/Affinity_vCPE_1.json b/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/Affinity_vCPE_1.json index 6f0ecb3..2953589 100644 --- a/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/Affinity_vCPE_1.json +++ b/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/Affinity_vCPE_1.json @@ -1,7 +1,7 @@ { - "service": "zone", - "policyName": "OSDF_R2.Affinity_vCPE_1", - "description": "Optimization query policy for vCPE", + "service": "affinityPolicy", + "policyName": "OSDF_DUBLIN.Affinity_vCPE_1", + "description": "Zone policy for vCPE", "templateVersion": "OpenSource.version.1", "version": "test1", "priority": "3", diff --git a/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/Capacity_vGMuxInfra.json b/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/Capacity_vGMuxInfra.json index 2578544..010cf3f 100644 --- a/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/Capacity_vGMuxInfra.json +++ b/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/Capacity_vGMuxInfra.json @@ -1,6 +1,6 @@ { "service": "vim_fit", - "policyName": "OSDF_R2.Capacity_vGMuxInfra", + "policyName": "OSDF_DUBLIN.Capacity_vGMuxInfra", "description": "Capacity policy for vGMuxInfra", "templateVersion": "OpenSource.version.1", "version": "test1", diff --git a/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/Capacity_vG_1.json b/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/Capacity_vG_1.json index c1682fa..fedcc4f 100644 --- a/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/Capacity_vG_1.json +++ b/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/Capacity_vG_1.json @@ -1,6 +1,6 @@ { "service": "vim_fit", - "policyName": "OSDF_R2.Capacity_vG_1", + "policyName": "OSDF_DUBLIN.Capacity_vG_1", "description": "Capacity policy for vG", "templateVersion": "OpenSource.version.1", "version": "test1", diff --git a/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/Distance_vGMuxInfra_1.json b/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/Distance_vGMuxInfra_1.json index 61ec500..e3ba83c 100644 --- a/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/Distance_vGMuxInfra_1.json +++ b/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/Distance_vGMuxInfra_1.json @@ -1,6 +1,6 @@ { - "service": "distance_to_location", - "policyName": "OSDF_R2.Distance_vGMuxInfra", + "service": "distancePolicy", + "policyName": "OSDF_DUBLIN.Distance_vGMuxInfra", "description": "Distance Policy for vGMuxInfra", "templateVersion": "OpenSource.version.1", "version": "test1", @@ -10,13 +10,13 @@ "guard": "False", "content": { "distanceProperties": { - "locationInfo": "customer_location", + "locationInfo": "customer_loc", "distance": { "value": "500", "operator": "<", "unit": "km" } }, "identity": "distance-vGMuxInfra", "resources": ["vGMuxInfra"], "policyScope": ["vCPE", "US", "INTERNATIONAL", "ip", "vGMuxInfra"], - "policyType": "distancePolicy", + "policyType": "distance_to_location", "applicableResources": "any" } } diff --git a/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/Distance_vG_1.json b/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/Distance_vG_1.json index 06c3ada..c498c7a 100644 --- a/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/Distance_vG_1.json +++ b/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/Distance_vG_1.json @@ -1,6 +1,6 @@ { - "service": "distance_to_location", - "policyName": "OSDF_R2.Distance_vG_1", + "service": "distancePolicy", + "policyName": "OSDF_DUBLIN.Distance_vG_1", "description": "Distance Policy for vG", "templateVersion": "OpenSource.version.1", "version": "test1", @@ -10,13 +10,13 @@ "guard": "False", "content": { "distanceProperties": { - "locationInfo": "customer_location", + "locationInfo": "customer_loc", "distance": { "value": "1500", "operator": "<", "unit": "km" } }, "identity": "distance-vG", "resources": ["vG"], "policyScope": ["vCPE", "US", "INTERNATIONAL", "ip", "vG"], - "policyType": "distancePolicy", + "policyType": "distance_to_location", "applicableResources": "any" } } diff --git a/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/Placement_Optimization_1.json b/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/Placement_Optimization_1.json index ab3c586..9b062b0 100644 --- a/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/Placement_Optimization_1.json +++ b/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/Placement_Optimization_1.json @@ -1,6 +1,6 @@ { - "service": "placementOptimization", - "policyName": "OSDF_R2.Placement_Optimization_1", + "service": "optimizationPolicy", + "policyName": "OSDF_DUBLIN.Placement_Optimization_1", "description": "Placement Optimization Policy for vGMuxInfra", "templateVersion": "OpenSource.version.1", "version": "test1", @@ -30,13 +30,26 @@ "parameter": "hpa_score", "weight": "200", "operator": "product" + }, + { + "resources": ["vFW"], + "customerLocationInfo": "customer_loc", + "parameter": "distance", + "weight": "100", + "operator": "product" + }, + { + "resources": ["vFW"], + "parameter": "hpa_score", + "weight": "200", + "operator": "product" } ], "operator": "sum" }, "identity": "optimization", "policyScope": ["vCPE", "US", "INTERNATIONAL", "ip", "vGMuxInfra", "vG"], - "policyType": "placementOptimization", + "policyType": "placement_optimization", "objective": "minimize" } } diff --git a/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/QueryPolicy_vCPE.json b/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/QueryPolicy_vCPE.json index 27f49d2..5097964 100644 --- a/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/QueryPolicy_vCPE.json +++ b/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/QueryPolicy_vCPE.json @@ -1,7 +1,7 @@ { - "service": "optimizationQueryPolicy", - "policyName": "OSDF_R2.QueryPolicy_vCPE", - "description": "Optimization query policy for vCPE", + "service": "queryPolicy", + "policyName": "OSDF_DUBLIN.QueryPolicy_vCPE", + "description": "Query policy for vCPE", "templateVersion": "OpenSource.version.1", "version": "test1", "priority": "3", @@ -14,7 +14,7 @@ {"attribute":"customerLongitude", "attribute_location": "customerLongitude"} ], "policyScope": ["vCPE", "US", "INTERNATIONAL", "ip", "vGMuxInfra", "vG"], - "policyType": "optimizationQueryPolicy", + "policyType": "request_param_query", "serviceName": "vCPE", "identity": "vCPE_Query_Policy" } diff --git a/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/QueryPolicy_vCPE_2.json b/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/QueryPolicy_vCPE_2.json index 7f1db83..e398f39 100644 --- a/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/QueryPolicy_vCPE_2.json +++ b/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/QueryPolicy_vCPE_2.json @@ -1,10 +1,10 @@ { - "service": "optimizationQueryPolicy", - "policyName": "oofBeijing.queryPolicy_vCPE", - "description": "Optimization query policy for vCPE", - "templateVersion": "0.0.1", - "version": "oofBeijing", - "priority": "5", + "service": "queryPolicy", + "policyName": "OSDF_DUBLIN.queryPolicy_vCPE", + "description": "Query policy for vCPE", + "templateVersion": "OpenSource.version.1", + "version": "test1", + "priority": "3", "riskType": "test", "riskLevel": "2", "guard": "False", @@ -17,7 +17,8 @@ {"attribute":"customerLongitude", "attribute_location": "customerLongitude", "value": 2.2} ], "serviceName": "vCPE", - "policyScope": ["vCPE", "US", "INTERNATIONAL", "ip", "vGMuxInfra", "vG", "optimizationQueryPolicy"], - "policyType": "optimizationQueryPolicy" + "policyScope": ["vCPE", "US", "INTERNATIONAL", "ip", "vGMuxInfra", "vG"], + "policyType": "request_param_query", + "identity": "vCPE_Query_Policy" } } diff --git a/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/hpa_policy_vGMuxInfra_1.json b/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/hpa_policy_vGMuxInfra_1.json index ce0b7e3..690f5dc 100644 --- a/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/hpa_policy_vGMuxInfra_1.json +++ b/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/hpa_policy_vGMuxInfra_1.json @@ -1,6 +1,6 @@ { "service": "hpaPolicy", - "policyName": "OSDF_R2.hpa_policy_vGMuxInfra_1", + "policyName": "OSDF_DUBLIN.hpa_policy_vGMuxInfra_1", "description": "HPA policy for vGMuxInfra", "templateVersion": "OpenSource.version.1", "version": "test1", @@ -9,10 +9,10 @@ "riskLevel": "2", "guard": "False", "content": { - "resources": "vGMuxInfra", - "identity": "hpaPolicy_vGMuxInfra", + "resources": ["vGMuxInfra"], + "identity": "hpa-vGMuxInfra", "policyScope": ["vCPE", "US", "INTERNATIONAL", "ip", "vGMuxInfra"], - "policyType": "hpaPolicy", + "policyType": "hpa", "flavorFeatures": [ { "id": "vgmux_1", diff --git a/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/hpa_policy_vG_1.json b/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/hpa_policy_vG_1.json index 5d2499f..b29c67d 100644 --- a/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/hpa_policy_vG_1.json +++ b/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/hpa_policy_vG_1.json @@ -1,6 +1,6 @@ { "service": "hpaPolicy", - "policyName": "OSDF_R2.hpa_policy_vG_1", + "policyName": "OSDF_DUBLIN.hpa_policy_vG_1", "description": "HPA policy for vG", "templateVersion": "OpenSource.version.1", "version": "test1", @@ -9,10 +9,10 @@ "riskLevel": "2", "guard": "False", "content": { - "resources": "vG", - "identity": "hpaPolicy_vG", + "resources": ["vG"], + "identity": "hpa-vG", "policyScope": ["vCPE", "US", "INTERNATIONAL", "ip", "vG"], - "policyType": "hpaPolicy", + "policyType": "hpa", "flavorFeatures": [ { "id": "vg_1", diff --git a/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/vnfPolicy_vG.json b/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/vnfPolicy_vG.json index d215078..b047686 100644 --- a/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/vnfPolicy_vG.json +++ b/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/vnfPolicy_vG.json @@ -1,6 +1,6 @@ { "service": "vnfPolicy", - "policyName": "OSDF_R2.vnfPolicy_vG", + "policyName": "OSDF_DUBLIN.vnfPolicy_vG", "description": "vnfPolicy", "templateVersion": "OpenSource.version.1", "version": "test1", diff --git a/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/vnfPolicy_vGMuxInfra.json b/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/vnfPolicy_vGMuxInfra.json index 6849105..45d67f6 100644 --- a/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/vnfPolicy_vGMuxInfra.json +++ b/test/functest/simulators/policy/response-payloads/pdp-has-vcpe-good/vnfPolicy_vGMuxInfra.json @@ -1,6 +1,6 @@ { "service": "vnfPolicy", - "policyName": "OSDF_R2.vnfPolicy_vGMuxInfra", + "policyName": "OSDF_DUBLIN.vnfPolicy_vGMuxInfra", "description": "vnfPolicy", "templateVersion": "OpenSource.version.1", "version": "test1", diff --git a/test/functest/simulators/simulated-config/log.yml b/test/functest/simulators/simulated-config/log.yml new file mode 100644 index 0000000..0b8815f --- /dev/null +++ b/test/functest/simulators/simulated-config/log.yml @@ -0,0 +1,95 @@ +version: 1 +disable_existing_loggers: True + +loggers: + error: + handlers: [error_handler] + level: "WARN" + propagate: True + debug: + handlers: [debug_handler] + level: "DEBUG" + propagate: True + metrics: + handlers: [metrics_handler] + level: "INFO" + propagate: True + audit: + handlers: [audit_handler] + level: "INFO" + propagate: True +handlers: + debug_handler: + level: "DEBUG" + class: "logging.handlers.TimedRotatingFileHandler" + filename: "logs/debug.log" + formatter: "debugFormat" + when: midnight + interval: 1 + utc: True + delay: False + backupCount: 10 + error_handler: + level: "WARN" + class: "logging.handlers.TimedRotatingFileHandler" + filename: "logs/error.log" + formatter: "errorFormat" + when: midnight + interval: 1 + utc: True + delay: False + backupCount: 10 + metrics_handler: + level: "INFO" + class: "logging.handlers.TimedRotatingFileHandler" + filename: "logs/metrics.log" + formatter: "metricsFormat" + when: midnight + interval: 1 + utc: True + delay: False + backupCount: 10 + audit_handler: + level: "INFO" + class: "logging.handlers.TimedRotatingFileHandler" + filename: "logs/audit.log" + formatter: "auditFormat" + when: midnight + interval: 1 + utc: True + delay: False + backupCount: 10 +formatters: + standard: + format: "%(asctime)s|||||%(name)s||%(thread)||%(funcName)s||%(levelname)s||%(message)s" + debugFormat: + format: "%(asctime)s|||||%(name)s||%(thread)s||%(funcName)s||%(levelname)s||%(message)s||||%(mdc)s" + mdcfmt: "{requestID} {threadID} {serverName} {serviceName} {instanceUUID} {upperLogLevel} {severity} {serverIPAddress} {server} {IPAddress} {className} {timer} {detailMessage}" + datefmt: "%Y-%m-%d %H:%M:%S" + (): onaplogging.mdcformatter.MDCFormatter + errorFormat: + format: "%(asctime)s|||||%(name)s||%(thread)s||%(funcName)s||%(levelname)s||%(message)s||||%(mdc)s" + mdcfmt: "{requestID} {threadID} {serviceName} {partnerName} {targetEntity} {targetServiceName} {errorCode} {errorDescription} {detailMessage}" + datefmt: "%Y-%m-%d %H:%M:%S" + (): onaplogging.mdcformatter.MDCFormatter + auditFormat: + format: "%(asctime)s|||||%(name)s||%(thread)s||%(funcName)s||%(levelname)s||%(message)s||||%(mdc)s" + mdcfmt: "{requestID} {serviceInstanceID} {threadID} {serverName} {serviceName} {partnerName} {statusCode} {responseCode} {responseDescription} {instanceUUID} {upperLogLevel} {severity} \ + {serverIPAddress} {timer} {server} {IPAddress} {className} {unused} {processKey} {customField1} {customField2} {customField3} {customField4} {detailMessage}" + datefmt: "%Y-%m-%d %H:%M:%S" + (): onaplogging.mdcformatter.MDCFormatter + metricsFormat: + format: "%(asctime)s|||||%(name)s||%(thread)s||%(funcName)s||%(levelname)s||%(message)s||||%(mdc)s" + mdcfmt: "{requestID} {serviceInstanceID} {threadID} {serverName} {serviceName} {partnerName} \ + {targetEntity} {targetServiceName} {statusCode} {responseCode} {responseDescription} \ + {instanceUUID} {upperLogLevel} {severity} {serverIPAddress} {timer} {server} {IPAddress} \ + {className} {unused} {processKey} {targetVirtualEntity} {customField1} {customField2} \ + {customField3} {customField4} {detailMessage}" + datefmt: "%Y-%m-%d %H:%M:%S" + (): onaplogging.mdcformatter.MDCFormatter + + mdcFormat: + format: "%(asctime)s|||||%(name)s||%(thread)s||%(funcName)s||%(levelname)s||%(message)s||||%(mdc)s" + mdcfmt: "{requestID} {invocationID} {serviceName} {serviceIP}" + datefmt: "%Y-%m-%d %H:%M:%S" + (): onaplogging.mdcformatter.MDCFormatter diff --git a/test/mainapp/test_osdfapp.py b/test/mainapp/test_osdfapp.py index 7fbe707..d1f6d7e 100644 --- a/test/mainapp/test_osdfapp.py +++ b/test/mainapp/test_osdfapp.py @@ -15,9 +15,10 @@ # # ------------------------------------------------------------------------- # -import osdfapp + import unittest +from osdf.apps import baseapp from osdf.operation.exceptions import BusinessException from requests import Request, RequestException from schematics.exceptions import DataError @@ -28,7 +29,7 @@ from unittest.mock import patch class TestOSDFApp(TestCase): def setUp(self): - self.patcher_g = patch('osdfapp.g', return_value={'request_id':'DUMMY-REQ'}) + self.patcher_g = patch('osdf.apps.baseapp.g', return_value={'request_id':'DUMMY-REQ'}) self.Mock_g = self.patcher_g.start() # self.patcher2 = patch('package.module.Class2') # self.MockClass2 = self.patcher2.start() @@ -47,26 +48,26 @@ class TestOSDFApp(TestCase): def test_handle_business_exception(self): e = BusinessException("Business Exception Description") - resp = osdfapp.handle_business_exception(e) + resp = baseapp.handle_business_exception(e) assert resp.status_code == 400 def test_handle_request_exception(self): e = self.dummy_request_exception() - resp = osdfapp.handle_request_exception(e) + resp = baseapp.handle_request_exception(e) assert resp.status_code == 400 def test_handle_data_error(self): e = DataError({"A1": "A1 Data Error"}) - resp = osdfapp.handle_data_error(e) + resp = baseapp.handle_data_error(e) assert resp.status_code == 400 def test_internal_failure(self): e = Exception("An Internal Error") - resp = osdfapp.internal_failure(e) + resp = baseapp.internal_failure(e) assert resp.status_code == 500 def test_get_options_default(self): - opts = osdfapp.get_options(["PROG"]) # ensure nothing breaks + opts = baseapp.get_options(["PROG"]) # ensure nothing breaks if __name__ == "__main__": diff --git a/test/policy/test_policy_interface.py b/test/policy/test_policy_interface.py index 4f1efcf..082b7f9 100644 --- a/test/policy/test_policy_interface.py +++ b/test/policy/test_policy_interface.py @@ -15,16 +15,16 @@ # # ------------------------------------------------------------------------- # -import mock import os import unittest -from osdf.adapters.local_data import local_policies +import mock + import osdf.config.loader as config_loader +from osdf.adapters.local_data import local_policies +from osdf.adapters.policy import interface as pol from osdf.utils.interfaces import json_from_file from osdf.utils.programming_utils import DotDict -from osdf.optimizers.placementopt.conductor import translation as tr -from osdf.adapters.policy import interface as pol class TestPolicyInterface(unittest.TestCase): diff --git a/test/test_ConductorApiBuilder.py b/test/test_ConductorApiBuilder.py index 7e38f4d..e69e954 100644 --- a/test/test_ConductorApiBuilder.py +++ b/test/test_ConductorApiBuilder.py @@ -19,8 +19,8 @@ import unittest import json import yaml +from apps.placement.optimizers.conductor.api_builder import conductor_api_builder from osdf.adapters.local_data import local_policies -from osdf.optimizers.placementopt.conductor.api_builder import conductor_api_builder from osdf.utils.interfaces import json_from_file @@ -28,7 +28,7 @@ class TestConductorApiBuilder(unittest.TestCase): def setUp(self): self.main_dir = "" - self.conductor_api_template = self.main_dir + "osdf/templates/conductor_interface.json" + self.conductor_api_template = self.main_dir + "apps/placement/templates/conductor_interface.json" self.local_config_file = self.main_dir + "config/common_config.yaml" policy_data_path = self.main_dir + "test/policy-local-files" # "test/policy-local-files" diff --git a/test/test_PolicyCalls.py b/test/test_PolicyCalls.py index 4c9366a..e7e8eab 100644 --- a/test/test_PolicyCalls.py +++ b/test/test_PolicyCalls.py @@ -24,7 +24,7 @@ from osdf.adapters.policy import interface from osdf.utils.interfaces import RestClient, json_from_file import yaml from mock import patch -from osdf.optimizers.placementopt.conductor import translation +from apps.placement.optimizers.conductor import translation from osdf.operation.exceptions import BusinessException diff --git a/test/test_api_validation.py b/test/test_api_validation.py index 389ff62..73d03cd 100644 --- a/test/test_api_validation.py +++ b/test/test_api_validation.py @@ -18,9 +18,10 @@ import json import unittest -from osdf.models.api.placementRequest import PlacementAPI -from osdf.models.api.placementResponse import PlacementResponse -from schematics.exceptions import ModelValidationError +from schematics.exceptions import DataError + +from apps.placement.models.api.placementRequest import PlacementAPI +from apps.placement.models.api.placementResponse import PlacementResponse class TestReqValidation(unittest.TestCase): @@ -37,7 +38,7 @@ class TestReqValidation(unittest.TestCase): def test_req_failure(self): req_json = {} - self.assertRaises(ModelValidationError, lambda: PlacementAPI(req_json).validate()) + self.assertRaises(DataError, lambda: PlacementAPI(req_json).validate()) class TestResponseValidation(unittest.TestCase): @@ -54,7 +55,7 @@ class TestResponseValidation(unittest.TestCase): def test_invalid_response(self): resp_json = {} - self.assertRaises(ModelValidationError, lambda: PlacementResponse(resp_json).validate()) + self.assertRaises(DataError, lambda: PlacementResponse(resp_json).validate()) if __name__ == "__main__": diff --git a/test/test_get_opt_query_data.py b/test/test_get_opt_query_data.py index 1e2db17..a7a4d88 100644 --- a/test/test_get_opt_query_data.py +++ b/test/test_get_opt_query_data.py @@ -17,7 +17,7 @@ # import unittest import json -from osdf.optimizers.placementopt.conductor.translation import get_opt_query_data +from apps.placement.optimizers.conductor.translation import get_opt_query_data class TestGetOptQueryData(unittest.TestCase): diff --git a/test/test_process_pci_anr_opt.py b/test/test_process_pci_anr_opt.py index 8c6a34c..b717d32 100644 --- a/test/test_process_pci_anr_opt.py +++ b/test/test_process_pci_anr_opt.py @@ -15,14 +15,15 @@ # # ------------------------------------------------------------------------- # -import mock import unittest +import mock from flask import Response from mock import patch -from osdf.adapters.local_data import local_policies -from osdf.optimizers.pciopt.pci_opt_processor import process_pci_optimation + import osdf.config.loader as config_loader +from apps.pci.optimizers.pci_opt_processor import process_pci_optimation +from osdf.adapters.local_data import local_policies from osdf.utils.interfaces import json_from_file from osdf.utils.programming_utils import DotDict @@ -31,19 +32,19 @@ class TestProcessPlacementOpt(unittest.TestCase): def setUp(self): mock_req_accept_message = Response("Accepted Request", content_type='application/json; charset=utf-8') - self.patcher_req = patch('osdf.optimizers.pciopt.configdb.request', + self.patcher_req = patch('apps.pci.optimizers.configdb.request', return_value={"solutionInfo": {"placementInfo": "dummy"}}) self.patcher_req_accept = patch('osdf.operation.responses.osdf_response_for_request_accept', return_value=mock_req_accept_message) self.patcher_callback = patch( - 'osdf.optimizers.pciopt.pci_opt_processor.process_pci_optimation', + 'apps.pci.optimizers.pci_opt_processor.process_pci_optimation', return_value=mock_req_accept_message) mock_mzn_response = [{'pci': {0: 0, 1: 1, 2: 2, 3: 3, 4: 0}, 'used_ignorables': [0]}] self.patcher_minizinc_callback = patch( - 'osdf.optimizers.pciopt.solver.optimizer.solve', - return_value=mock_mzn_response ) + 'apps.pci.optimizers.solver.optimizer.solve', + return_value=mock_mzn_response) self.patcher_RestClient = patch( 'osdf.utils.interfaces.RestClient', return_value=mock.MagicMock()) self.Mock_req = self.patcher_req.start() @@ -71,9 +72,8 @@ class TestProcessPlacementOpt(unittest.TestCase): request_json = json_from_file(parameter_data_file) policies = [json_from_file(policy_data_path + '/' + name) for name in valid_policies_files] - templ_string = process_pci_optimation(request_json, self.osdf_config,policies) + templ_string = process_pci_optimation(request_json, self.osdf_config, policies) if __name__ == "__main__": unittest.main() - diff --git a/test/test_process_pci_opt.py b/test/test_process_pci_opt.py index 31aa5fb..ffbc718 100644 --- a/test/test_process_pci_opt.py +++ b/test/test_process_pci_opt.py @@ -21,7 +21,7 @@ import unittest from flask import Response from mock import patch from osdf.adapters.local_data import local_policies -from osdf.optimizers.pciopt.pci_opt_processor import process_pci_optimation +from apps.pci.optimizers.pci_opt_processor import process_pci_optimation import osdf.config.loader as config_loader from osdf.utils.interfaces import json_from_file from osdf.utils.programming_utils import DotDict @@ -31,18 +31,18 @@ class TestProcessPlacementOpt(unittest.TestCase): def setUp(self): mock_req_accept_message = Response("Accepted Request", content_type='application/json; charset=utf-8') - self.patcher_req = patch('osdf.optimizers.pciopt.configdb.request', + self.patcher_req = patch('apps.pci.optimizers.configdb.request', return_value={"solutionInfo": {"placementInfo": "dummy"}}) self.patcher_req_accept = patch('osdf.operation.responses.osdf_response_for_request_accept', return_value=mock_req_accept_message) self.patcher_callback = patch( - 'osdf.optimizers.pciopt.pci_opt_processor.process_pci_optimation', + 'apps.pci.optimizers.pci_opt_processor.process_pci_optimation', return_value=mock_req_accept_message) mock_mzn_response = [{'pci': {0: 0, 1: 1, 2: 2}}] self.patcher_minizinc_callback = patch( - 'osdf.optimizers.pciopt.solver.optimizer.solve', + 'apps.pci.optimizers.solver.optimizer.solve', return_value=mock_mzn_response ) self.patcher_RestClient = patch( 'osdf.utils.interfaces.RestClient', return_value=mock.MagicMock()) diff --git a/test/test_process_placement_opt.py b/test/test_process_placement_opt.py index 62a1ce6..64b69a8 100644 --- a/test/test_process_placement_opt.py +++ b/test/test_process_placement_opt.py @@ -20,8 +20,9 @@ import unittest from flask import Response from mock import patch + +from apps.placement.optimizers.conductor.remote_opt_processor import process_placement_opt from osdf.adapters.local_data import local_policies -from osdf.optimizers.placementopt.conductor.remote_opt_processor import process_placement_opt from osdf.utils.interfaces import json_from_file, yaml_from_file @@ -29,12 +30,12 @@ class TestProcessPlacementOpt(unittest.TestCase): def setUp(self): mock_req_accept_message = Response("Accepted Request", content_type='application/json; charset=utf-8') - self.patcher_req = patch('osdf.optimizers.placementopt.conductor.conductor.request', + self.patcher_req = patch('apps.placement.optimizers.conductor.conductor.request', return_value={"solutionInfo": {"placementInfo": "dummy"}}) self.patcher_req_accept = patch('osdf.operation.responses.osdf_response_for_request_accept', return_value=mock_req_accept_message) self.patcher_callback = patch( - 'osdf.optimizers.placementopt.conductor.remote_opt_processor.process_placement_opt', + 'apps.placement.optimizers.conductor.remote_opt_processor.process_placement_opt', return_value=mock_req_accept_message) self.patcher_RestClient = patch( 'osdf.utils.interfaces.RestClient', return_value=mock.MagicMock()) diff --git a/test/test_so_response_gen.py b/test/test_so_response_gen.py index ab73ef6..6705cc8 100644 --- a/test/test_so_response_gen.py +++ b/test/test_so_response_gen.py @@ -16,10 +16,9 @@ # ------------------------------------------------------------------------- # import unittest -import json -import yaml + +from apps.placement.optimizers.conductor.conductor import conductor_response_processor from osdf.utils.interfaces import json_from_file -from osdf.optimizers.placementopt.conductor.conductor import conductor_response_processor from osdf.utils.interfaces import RestClient diff --git a/tox.ini b/tox.ini index 4723f46..58ed633 100644 --- a/tox.ini +++ b/tox.ini @@ -8,7 +8,6 @@ distribute = False setenv = OSDF_CONFIG_FILE={toxinidir}/test/config/osdf_config.yaml commands = - - cat /etc/hosts /bin/bash test/functest/scripts/start-simulators.sh coverage run --module pytest --junitxml xunit-results.xml coverage xml --omit=".tox/py3/*","test/*" -- cgit 1.2.3-korg