diff options
author | hariharan97 <rh20085046@wipro.com> | 2021-01-18 16:39:39 +0530 |
---|---|---|
committer | hariharan97 <rh20085046@wipro.com> | 2021-02-08 19:25:52 +0530 |
commit | d9902448f190fe1052d693cbfa7a5cb385613140 (patch) | |
tree | 98a04916e4e1636034749ddc89485e916f6780b8 /conductor | |
parent | f4adf00f25425c6fce2b1256fa0de71bd9d5f077 (diff) |
"Nst-Selection enhancement"
Issue-ID: OPTFRA-764
Signed-off-by: hariharan97 <rh20085046@wipro.com>
Change-Id: Id5d05967e349381bc5769c79b91e4439ea38fc82
Diffstat (limited to 'conductor')
23 files changed, 817 insertions, 6 deletions
diff --git a/conductor/conductor/controller/translator_utils.py b/conductor/conductor/controller/translator_utils.py index 17a9f82..e4aa4e1 100644 --- a/conductor/conductor/controller/translator_utils.py +++ b/conductor/conductor/controller/translator_utils.py @@ -20,8 +20,8 @@ VERSIONS = {'BASE': ["2016-11-01", "2017-10-10", "2018-02-01"], 'GENERIC': ["2020-08-13"]} LOCATION_KEYS = ['latitude', 'longitude', 'host_name', 'clli_code'] -INVENTORY_PROVIDERS = ['aai', 'generator'] -INVENTORY_TYPES = ['cloud', 'service', 'transport', 'vfmodule', 'nssi', 'nsi', 'slice_profiles'] +INVENTORY_PROVIDERS = ['aai', 'generator', 'sdc'] +INVENTORY_TYPES = ['cloud', 'service', 'transport', 'vfmodule', 'nssi', 'nsi', 'slice_profiles', 'nst'] DEFAULT_INVENTORY_PROVIDER = INVENTORY_PROVIDERS[0] CANDIDATE_KEYS = ['candidate_id', 'cost', 'inventory_type', 'location_id', 'location_type'] diff --git a/conductor/conductor/data/plugins/inventory_provider/aai.py b/conductor/conductor/data/plugins/inventory_provider/aai.py index a87cbb6..6ec15da 100644 --- a/conductor/conductor/data/plugins/inventory_provider/aai.py +++ b/conductor/conductor/data/plugins/inventory_provider/aai.py @@ -33,11 +33,13 @@ from conductor.data.plugins import constants from conductor.data.plugins.inventory_provider import base from conductor.data.plugins.inventory_provider.candidates.candidate import Candidate from conductor.data.plugins.inventory_provider.candidates.cloud_candidate import Cloud +from conductor.data.plugins.inventory_provider.candidates.nst_candidate import NST from conductor.data.plugins.inventory_provider.candidates.nxi_candidate import NxI from conductor.data.plugins.inventory_provider.candidates.service_candidate import Service from conductor.data.plugins.inventory_provider.candidates.transport_candidate import Transport from conductor.data.plugins.inventory_provider.candidates.vfmodule_candidate import VfModule from conductor.data.plugins.inventory_provider import hpa_utils +from conductor.data.plugins.inventory_provider.sdc import SDC from conductor.data.plugins.inventory_provider.utils import aai_utils from conductor.data.plugins.triage_translator.triage_translator import TraigeTranslator from conductor.i18n import _LE @@ -1662,6 +1664,17 @@ class AAI(base.InventoryProviderBase): default_attributes, candidate_uniqueness, inventory_type)) + elif inventory_type == 'nst': + if filtering_attributes: + second_level_match = aai_utils.get_first_level_and_second_level_filter(filtering_attributes, + "nst") + aai_response = self.get_nst_response(filtering_attributes) + + sdc_candidates_list = self.get_nst_candidates(aai_response, second_level_match, + default_attributes, candidate_uniqueness, + inventory_type) + resolved_demands[name].extend(SDC().update_candidates(sdc_candidates_list)) + else: LOG.error("Unknown inventory_type " " {}".format(inventory_type)) @@ -1901,3 +1914,35 @@ class AAI(base.InventoryProviderBase): candidate = nxi_candidate.convert_nested_dict_to_dict() candidates.append(candidate) return candidates + + def get_nst_response(self, filtering_attributes): + raw_path = 'service-design-and-creation/models' + aai_utils.add_query_params_and_depth(filtering_attributes, + "2") + path = self._aai_versioned_path(raw_path) + aai_response = self._request('get', path, data=None) + + if aai_response is None or aai_response.status_code != 200: + return None + if aai_response.json(): + return aai_response.json() + + def get_nst_candidates(self, response_body, filtering_attributes, default_attributes, candidate_uniqueness, + type): + candidates = list() + if response_body is not None: + nst_metadatas = response_body.get("model", []) + + for nst_metadata in nst_metadatas: + nst_info = aai_utils.get_nst_info(nst_metadata) + model_vers = nst_metadata.get('model-vers').get('model-ver') + for model_ver in model_vers: + model_version_id = model_ver.get('model-version-id') + cost = self.conf.data.nst_candidate_cost + info = Candidate.build_candidate_info('aai', type, cost, candidate_uniqueness, model_version_id) + model_version_obj = aai_utils.get_model_ver_info(model_ver) + model_ver_info = aai_utils.convert_hyphen_to_under_score(model_version_obj) + nst_candidate = NST(model_info=nst_info, model_ver=model_ver_info, info=info, + default_fields=aai_utils.convert_hyphen_to_under_score(default_attributes), + profile_info=None) + candidates.append(nst_candidate) + return candidates diff --git a/conductor/conductor/data/plugins/inventory_provider/candidates/nst_candidate.py b/conductor/conductor/data/plugins/inventory_provider/candidates/nst_candidate.py new file mode 100644 index 0000000..d5ea251 --- /dev/null +++ b/conductor/conductor/data/plugins/inventory_provider/candidates/nst_candidate.py @@ -0,0 +1,29 @@ +# +# ------------------------------------------------------------------------- +# Copyright (C) 2020 Wipro Limited. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------- +# + +from conductor.data.plugins.inventory_provider.candidates.candidate import Candidate + + +class NST(Candidate): + def __init__(self, **kwargs): + super().__init__(kwargs['info']) + self.nst_info = kwargs['model_info'] + self.model_ver_info = kwargs['model_ver'] + self.profile_info = kwargs['profile_info'] + self.other = kwargs['default_fields'] diff --git a/conductor/conductor/data/plugins/inventory_provider/sdc.py b/conductor/conductor/data/plugins/inventory_provider/sdc.py new file mode 100644 index 0000000..6f3cb4f --- /dev/null +++ b/conductor/conductor/data/plugins/inventory_provider/sdc.py @@ -0,0 +1,188 @@ +# +# ------------------------------------------------------------------------- +# Copyright (C) 2020 Wipro Limited. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------- +# +from conductor.common import rest +from conductor.data.plugins.inventory_provider.utils import csar +from conductor.i18n import _LE +import os +from oslo_config import cfg +from oslo_log import log +import time +import uuid + + +LOG = log.getLogger(__name__) + +CONF = cfg.CONF + +SDC_OPTS = [ + cfg.StrOpt('table_prefix', + default='sdc', + help='Data Store table prefix.'), + cfg.StrOpt('server_url', + default='https://controller:8443/sdc', + help='Base URL for SDC, up to and not including ' + 'the version, and without a trailing slash.'), + cfg.StrOpt('sdc_rest_timeout', + default='30', + help='Timeout for SDC Rest Call'), + cfg.StrOpt('sdc_retries', + default='3', + help='Number of retry for SDC Rest Call'), + cfg.StrOpt('server_url_version', + default='v1', + help='The version of SDC in v# format.'), + # TODO(larry): follow-up with ONAP people on this (SDC basic auth username and password?) + cfg.StrOpt('certificate_file', + default='certificate.pem', + help='SSL/TLS certificate file in pem format. ' + 'This certificate must be registered with the A&AI ' + 'endpoint.'), + cfg.StrOpt('certificate_key_file', + default='certificate_key.pem', + help='Private Certificate Key file in pem format.'), + cfg.StrOpt('certificate_authority_bundle_file', + default='certificate_authority_bundle.pem', + help='Certificate Authority Bundle file in pem format. ' + 'Must contain the appropriate trust chain for the ' + 'Certificate file.'), + cfg.StrOpt('username', + default='', + help='Username for SDC.'), + cfg.StrOpt('password', + default='', + help='Password for SDC.'), + cfg.StrOpt('temp_path', + default=',', + help="path to store nst templates") +] + +CONF.register_opts(SDC_OPTS, group='sdc') + + +class SDC(object): + """SDC Inventory Provider""" + + def __init__(self): + """Initializer""" + + self.conf = CONF + + self.base = self.conf.sdc.server_url.rstrip('/') + self.version = self.conf.sdc.server_url_version.rstrip('/') + self.cert = self.conf.sdc.certificate_file + self.key = self.conf.sdc.certificate_key_file + self.verify = self.conf.sdc.certificate_authority_bundle_file + self.timeout = self.conf.sdc.sdc_rest_timeout + self.retries = self.conf.sdc.sdc_retries + self.username = self.conf.sdc.username + self.password = self.conf.sdc.password + self._init_python_request() + + def initialize(self): + + """Perform any late initialization.""" + # Initialize the Python requests + # self._init_python_request() + + def _sdc_versioned_path(self, path): + """Return a URL path with the SDC version prepended""" + return '/{}/{}'.format(self.version, path.lstrip('/')) + + def _request(self, method='get', path='/', data=None, + context=None, value=None): + """Performs HTTP request.""" + headers = { + 'X-FromAppId': 'CONDUCTOR', + 'X-TransactionId': str(uuid.uuid4()), + } + kwargs = { + "method": method, + "path": path, + "headers": headers, + "data": data, + } + + # TODO(jdandrea): Move timing/response logging into the rest helper? + start_time = time.time() + response = self.rest.request(**kwargs) + elapsed = time.time() - start_time + LOG.debug("Total time for SDC request " + "({0:}: {1:}): {2:.3f} sec".format(context, value, elapsed)) + + if response is None: + LOG.error(_LE("No response from SDC ({}: {})"). + format(context, value)) + elif response.status_code != 200: + LOG.error(_LE("SDC request ({}: {}) returned HTTP " + "status {} {}, link: {}{}"). + format(context, value, + response.status_code, response.reason, + self.base, path)) + return response + + def _init_python_request(self): + + kwargs = { + "server_url": self.base, + "retries": self.retries, + "username": self.username, + "password": self.password, + "read_timeout": self.timeout, + } + self.rest = rest.REST(**kwargs) + + def update_candidates(self, candidates): + absfilepath = self.conf.sdc.temp_path + candidateslist = [] + for candidate in candidates: + model_ver_obj = candidate.model_ver_info + model_name = model_ver_obj['model_name'] + self.model_version_id = candidate.candidate_id + response = self.get_nst_template(self.model_version_id) + filepath = os.path.join(absfilepath, "{}.csar".format(self.model_version_id)) + if not os.path.exists(absfilepath): + os.makedirs(absfilepath) + f = open(filepath, "wb") + file_res = response.content + f.write(file_res) + obj = csar.SDCCSAR(filepath, model_name) + nst_temp_prop = obj.validate() + nst_properties = self.get_nst_prop_dict(nst_temp_prop) + candidate.profile_info = nst_properties + finalcandidate = candidate.convert_nested_dict_to_dict() + candidateslist.append(finalcandidate) + return candidateslist + + def get_nst_prop_dict(self, nst_properties): + properties_dict = dict() + for key in list(nst_properties): + temp_dict = nst_properties[key] + for temp_key in list(temp_dict): + if "default" in temp_key: + properties_dict[key] = temp_dict[temp_key] + return properties_dict + + def get_nst_template(self, ver_id): + raw_path = "/catalog/services/{}/toscaModel".format(ver_id) + path = self._sdc_versioned_path(raw_path) + sdc_response = self._request('get', path, data=None) + if sdc_response is None or sdc_response.status_code != 200: + return None + if sdc_response: + return sdc_response diff --git a/conductor/conductor/data/plugins/inventory_provider/utils/aai_utils.py b/conductor/conductor/data/plugins/inventory_provider/utils/aai_utils.py index ee870d4..4c76645 100644 --- a/conductor/conductor/data/plugins/inventory_provider/utils/aai_utils.py +++ b/conductor/conductor/data/plugins/inventory_provider/utils/aai_utils.py @@ -19,7 +19,8 @@ QUERY_PARAMS = {'service_instance': ["service-instance-id", "service-instance-name", "environment-context", "workload-context", "model-invariant-id", "model-version-id", "widget-model-id", - "widget-model-version", "service-instance-location-id", "orchestration-status"] + "widget-model-version", "service-instance-location-id", "orchestration-status"], + 'nst': ["model-role"] } @@ -79,3 +80,18 @@ def get_instance_info(nxi_instance): if nxi_instance.get('workload-context'): nxi_dict['domain'] = nxi_instance.get('workload-context') return nxi_dict + + +def get_nst_info(nst_instance): + nst_dict = {} + nst_dict['model_invariant_id'] = nst_instance.get('model-invariant-id') + nst_dict['model_type'] = nst_instance.get('model-type') + nst_dict['model_role'] = nst_instance.get('model-role') + return nst_dict + + +def get_model_ver_info(model_version): + for key in list(model_version): + if "model-elements" in key: + del model_version["model-elements"] + return model_version diff --git a/conductor/conductor/data/plugins/inventory_provider/utils/csar.py b/conductor/conductor/data/plugins/inventory_provider/utils/csar.py new file mode 100644 index 0000000..0caccac --- /dev/null +++ b/conductor/conductor/data/plugins/inventory_provider/utils/csar.py @@ -0,0 +1,98 @@ + + +# 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 os +import os.path +import requests +from toscaparser.common.exception import ExceptionCollector +from toscaparser.common.exception import ValidationError +from toscaparser.prereq.csar import CSAR +from toscaparser.utils.gettextutils import _ +from toscaparser.utils.urlutils import UrlUtils +from toscaparser.utils import yamlparser +import zipfile + + +try: # Python 2.x + from BytesIO import BytesIO +except ImportError: # Python 3.x + from io import BytesIO + +TOSCA_META = 'TOSCA-Metadata/TOSCA.meta' +YAML_LOADER = yamlparser.load_yaml + + +class SDCCSAR(CSAR): + def __init__(self, csar_file, model_name, a_file=True): + super(SDCCSAR, self).__init__(csar_file, a_file=True) + self.model_name = model_name + + def validate(self): + """Validate the provided CSAR file.""" + self.is_validated = True + # validate that the file or URL exists + missing_err_msg = (_('"%s" does not exist.') % self.path) + if self.a_file: + if not os.path.isfile(self.path): + ExceptionCollector.appendException( + ValidationError(message=missing_err_msg)) + return False + else: + self.csar = self.path + else: # a URL + if not UrlUtils.validate_url(self.path): + ExceptionCollector.appendException( + ValidationError(message=missing_err_msg)) + return False + else: + response = requests.get(self.path) + self.csar = BytesIO(response.content) + + # validate that it is a valid zip file + if not zipfile.is_zipfile(self.csar): + err_msg = (_('"%s" is not a valid zip file.') % self.path) + ExceptionCollector.appendException( + ValidationError(message=err_msg)) + return False + + # validate that it contains the metadata file in the correct location + self.zfile = zipfile.ZipFile(self.csar, 'r') + filelist = self.zfile.namelist() + if TOSCA_META in filelist: + self.is_tosca_metadata = True + # validate that 'Entry-Definitions' property exists in TOSCA.meta + is_validated = self._validate_tosca_meta(filelist) + else: + self.is_tosca_metadata = False + is_validated = self._validate_root_level_yaml(filelist) + + if is_validated: + main_tpl = self._read_template_yaml(self.main_template_file_name) + nst_properies_res = self.get_nst_properties(main_tpl) + print("nst properties", nst_properies_res) + return nst_properies_res + + def get_nst_properties(self, main_tpl): + importsarr = main_tpl.get('imports') + for imports in importsarr: + for key in imports: + if "service-{}-interface".format(self.model_name) in key: + val = imports[key] + filename = val.get("file") + datanew = self._read_template_yaml("Definitions/" + filename) + node_types = datanew.get("node_types") + for key in list(node_types): + if "org.openecomp" in key: + nodedata = node_types[key] + nst_properties = nodedata.get("properties") + return nst_properties diff --git a/conductor/conductor/data/service.py b/conductor/conductor/data/service.py index 743ea8e..69f6945 100644 --- a/conductor/conductor/data/service.py +++ b/conductor/conductor/data/service.py @@ -66,6 +66,8 @@ DATA_OPTS = [ default=1.0), cfg.FloatOpt('nsi_candidate_cost', default=1.0), + cfg.FloatOpt('nst_candidate_cost', + default=1.0), ] CONF.register_opts(DATA_OPTS, group='data') diff --git a/conductor/conductor/solver/service.py b/conductor/conductor/solver/service.py index d565d9f..b6ed5dc 100644 --- a/conductor/conductor/solver/service.py +++ b/conductor/conductor/solver/service.py @@ -486,7 +486,7 @@ class SolverService(cotyledon.Service): 'aic_version': resource.get("cloud_region_version")}, } - if rec["candidate"]["inventory_type"] in ["nssi", "nsi", "slice_profiles"]: + if rec["candidate"]["inventory_type"] in ["nssi", "nsi", "slice_profiles", "nst"]: rec["candidate"] = resource if resource.get('vim-id'): diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/final_nst_candidate.json b/conductor/conductor/tests/unit/data/plugins/inventory_provider/final_nst_candidate.json new file mode 100644 index 0000000..ab7aefd --- /dev/null +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/final_nst_candidate.json @@ -0,0 +1,35 @@ +[{ + "candidate_id":"5d345ca8-1f8e-4f1e-aac7-6c8b33cc33e7", + "inventory_provider":"aai", + "inventory_type":"nst", + "uniqueness":"True", + "cost":1.0, + + "model_invariant_id":"f0aa2f5c-a022-4947-80bf-fc05a1502d82", + "model_type":"service", + "model_role":"NST", + + "model_version_id":"5d345ca8-1f8e-4f1e-aac7-6c8b33cc33e7", + "model_name":"EmbbNst", + "model_version":"1.0", + "distribution_status":"DISTRIBUTION_COMPLETE_OK", + "model_description":"EmbbNst", + + "creation_cost": 1, + + "ueMobilityLevel":"stationary", + "skip_post_instantiation_configuration":true, + "controller_actor":"SO-REF-DATA", + "areaTrafficCapDL":300, + "maxNumberofUEs":1000000, + "latency":30, + "expDataRateUL":300, + "availability":0.6, + "plmnIdList":"39-00|39-01", + "sST":"embb", + "areaTrafficCapUL":300, + "expDataRateDL":1000, + "activityFactor":60, + "resourceSharingLevel":"shared" + +}]
\ No newline at end of file diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/model_ver.json b/conductor/conductor/tests/unit/data/plugins/inventory_provider/model_ver.json new file mode 100644 index 0000000..1f1c640 --- /dev/null +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/model_ver.json @@ -0,0 +1,43 @@ + { + "model-version-id":"5d345ca8-1f8e-4f1e-aac7-6c8b33cc33e7", + "model-name":"EmbbNst", + "model-version":"1.0", + "distribution-status":"DISTRIBUTION_COMPLETE_OK", + "model-description":"EmbbNst", + "resource-version":"1609762782080", + "model-elements":{ + "model-element":[ + { + "model-element-uuid":"5c7f04ca-a3b6-4ef9-9b9e-f887121276b0", + "new-data-del-flag":"T", + "cardinality":"unbounded", + "resource-version":"1609762578458", + "relationship-list":{ + "relationship":[ + { + "related-to":"model-ver", + "relationship-label":"org.onap.relationships.inventory.IsA", + "related-link":"/aai/v21/service-design-and-creation/models/model/82194af1-3c2c-485a-8f44-420e22a9eaa4/model-vers/model-ver/46b92144-923a-4d20-b85a-3cbd847668a9", + "relationship-data":[ + { + "relationship-key":"model.model-invariant-id", + "relationship-value":"82194af1-3c2c-485a-8f44-420e22a9eaa4" + }, + { + "relationship-key":"model-ver.model-version-id", + "relationship-value":"46b92144-923a-4d20-b85a-3cbd847668a9" + } + ], + "related-to-property":[ + { + "property-key":"model-ver.model-name", + "property-value":"service-instance" + } + ] + } + ] + } + } + ] + } + }
\ No newline at end of file diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/model_ver_response.json b/conductor/conductor/tests/unit/data/plugins/inventory_provider/model_ver_response.json new file mode 100644 index 0000000..a3f6167 --- /dev/null +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/model_ver_response.json @@ -0,0 +1,8 @@ +{ + "model-version-id": "5d345ca8-1f8e-4f1e-aac7-6c8b33cc33e7", + "model-name": "EmbbNst", + "model-version": "1.0", + "distribution-status": "DISTRIBUTION_COMPLETE_OK", + "model-description": "EmbbNst", + "resource-version": "1609762782080" +}
\ No newline at end of file diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/newembbnst.csar b/conductor/conductor/tests/unit/data/plugins/inventory_provider/newembbnst.csar Binary files differnew file mode 100644 index 0000000..d5c4270 --- /dev/null +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/newembbnst.csar diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_candidate.json b/conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_candidate.json new file mode 100644 index 0000000..f6e9dde --- /dev/null +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_candidate.json @@ -0,0 +1,28 @@ +[ + { + + "candidate_id": "5d345ca8-1f8e-4f1e-aac7-6c8b33cc33e7", + "inventory_provider": "aai", + "inventory_type": "nst", + "uniqueness": "True", + "cost": 1.0, + "nst_info": { + "model_invariant_id": "f0aa2f5c-a022-4947-80bf-fc05a1502d82", + "model_type": "service", + "model_role": "NST" + }, + "model_ver": { + "model_version_id": "5d345ca8-1f8e-4f1e-aac7-6c8b33cc33e7", + "model_name": "EmbbNst", + "model_version": "1.0", + "distribution_status": "DISTRIBUTION_COMPLETE_OK", + "model_description": "EmbbNst" + }, + "default_fields": { + "creation_cost": 1 + }, + "profile_info": { + + } + } +] diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_demand_list.json b/conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_demand_list.json new file mode 100644 index 0000000..5b5d63c --- /dev/null +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_demand_list.json @@ -0,0 +1,13 @@ +{ + "embb_nst": [{ + "inventory_provider": "aai", + "inventory_type": "nst", + "unique": "true", + "filtering_attributes": { + "model-role": "NST" + }, + "default_attributes": { + "creation-cost": 1 + } + }] +}
\ No newline at end of file diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_prop_dict.json b/conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_prop_dict.json new file mode 100644 index 0000000..1761655 --- /dev/null +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_prop_dict.json @@ -0,0 +1,16 @@ +{ + "ueMobilityLevel":"stationary", + "skip_post_instantiation_configuration":true, + "controller_actor":"SO-REF-DATA", + "areaTrafficCapDL":300, + "maxNumberofUEs":1000000, + "latency":30, + "expDataRateUL":300, + "availability":0.6, + "plmnIdList":"39-00|39-01", + "sST":"embb", + "areaTrafficCapUL":300, + "expDataRateDL":1000, + "activityFactor":60, + "resourceSharingLevel":"shared" +}
\ No newline at end of file diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_properties.json b/conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_properties.json new file mode 100644 index 0000000..05cca18 --- /dev/null +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_properties.json @@ -0,0 +1,84 @@ +{ + "ueMobilityLevel":{ + "default":"stationary", + "type":"string", + "required":false + }, + "skip_post_instantiation_configuration":{ + "default":true, + "type":"boolean", + "required":false + }, + "controller_actor":{ + "default":"SO-REF-DATA", + "type":"string", + "required":false + }, + "areaTrafficCapDL":{ + "default":300, + "type":"integer", + "required":false + }, + "maxNumberofUEs":{ + "default":1000000, + "type":"integer", + "required":false + }, + "latency":{ + "default":30, + "type":"integer", + "required":false + }, + "expDataRateUL":{ + "default":300, + "type":"integer", + "required":false + }, + "availability":{ + "default":0.6, + "type":"float", + "required":false + }, + "additionalSystemFeature":{ + "type":"string", + "required":false + }, + "plmnIdList":{ + "default":"39-00|39-01", + "type":"string", + "required":false + }, + "sST":{ + "default":"embb", + "type":"string", + "required":false + }, + "areaTrafficCapUL":{ + "default":300, + "type":"integer", + "required":false + }, + "cds_model_version":{ + "type":"string", + "required":false + }, + "cds_model_name":{ + "type":"string", + "required":false + }, + "expDataRateDL":{ + "default":1000, + "type":"integer", + "required":false + }, + "activityFactor":{ + "default":60, + "type":"integer", + "required":false + }, + "resourceSharingLevel":{ + "default":"shared", + "type":"string", + "required":false + } +}
\ No newline at end of file diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_response.json b/conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_response.json new file mode 100644 index 0000000..2c075e3 --- /dev/null +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_response.json @@ -0,0 +1,59 @@ +{ + "model":[ + { + "model-invariant-id":"f0aa2f5c-a022-4947-80bf-fc05a1502d82", + "model-type":"service", + "model-role":"NST", + "resource-version":"1609762578458", + "model-vers":{ + "model-ver":[ + { + "model-version-id":"5d345ca8-1f8e-4f1e-aac7-6c8b33cc33e7", + "model-name":"EmbbNst", + "model-version":"1.0", + "distribution-status":"DISTRIBUTION_COMPLETE_OK", + "model-description":"EmbbNst", + "resource-version":"1609762782080", + "model-elements":{ + "model-element":[ + { + "model-element-uuid":"5c7f04ca-a3b6-4ef9-9b9e-f887121276b0", + "new-data-del-flag":"T", + "cardinality":"unbounded", + "resource-version":"1609762578458", + "relationship-list":{ + "relationship":[ + { + "related-to":"model-ver", + "relationship-label":"org.onap.relationships.inventory.IsA", + "related-link":"/aai/v21/service-design-and-creation/models/model/82194af1-3c2c-485a-8f44-420e22a9eaa4/model-vers/model-ver/46b92144-923a-4d20-b85a-3cbd847668a9", + "relationship-data":[ + { + "relationship-key":"model.model-invariant-id", + "relationship-value":"82194af1-3c2c-485a-8f44-420e22a9eaa4" + }, + { + "relationship-key":"model-ver.model-version-id", + "relationship-value":"46b92144-923a-4d20-b85a-3cbd847668a9" + } + ], + "related-to-property":[ + { + "property-key":"model-ver.model-name", + "property-value":"service-instance" + } + ] + } + ] + } + } + ] + } + } + ] + } + } + + + ] +}
\ No newline at end of file diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai.py b/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai.py index 484806e..54789f2 100644 --- a/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai.py +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai.py @@ -21,11 +21,13 @@ import copy import json import mock import unittest +from unittest.mock import patch from oslo_config import cfg import conductor.data.plugins.inventory_provider.aai as aai from conductor.data.plugins.inventory_provider.aai import AAI +from conductor.data.plugins.inventory_provider.sdc import SDC from conductor.data.plugins.inventory_provider.hpa_utils import match_hpa from conductor.data.plugins.triage_translator.triage_translator import TraigeTranslator @@ -830,3 +832,61 @@ tenant/3c6c471ada7747fe8ff7f28e100b61e8/vservers/vserver/00bddefc-126e-4e4f-a18d self.maxDiff = None self.assertEqual(result, self.aai_ep.resolve_demands(demands_list, plan_info=plan_info, triage_translator_data=triage_translator_data)) + + + def test_get_nst_candidates(self): + nst_response_file = './conductor/tests/unit/data/plugins/inventory_provider/nst_response.json' + nst_response = json.loads(open(nst_response_file).read()) + + + + second_level_filter=None + + default_attributes = dict() + default_attributes['creation_cost'] = 1 + self.assertEqual("5d345ca8-1f8e-4f1e-aac7-6c8b33cc33e7", self.aai_ep.get_nst_candidates(nst_response, second_level_filter, + default_attributes, "true", "nst").__getitem__(0).__getattribute__('candidate_id')) + + + def test_resolve_demands_inventory_type_nst(self): + self.aai_ep.conf.HPA_enabled = True + TraigeTranslator.getPlanIdNAme = mock.MagicMock(return_value=None) + TraigeTranslator.addDemandsTriageTranslator = mock.MagicMock(return_value=None) + + plan_info = { + 'plan_name': 'name', + 'plan_id': 'id' + } + triage_translator_data = None + + demands_list_file = './conductor/tests/unit/data/plugins/inventory_provider/nst_demand_list.json' + demands_list = json.loads(open(demands_list_file).read()) + + nst_response_file = './conductor/tests/unit/data/plugins/inventory_provider/nst_response.json' + nst_response = json.loads(open(nst_response_file).read()) + final_nst_candidates_file = './conductor/tests/unit/data/plugins/inventory_provider/final_nst_candidate.json' + final_nst_candidates = json.loads(open(final_nst_candidates_file).read()) + result = dict() + result['embb_nst'] = final_nst_candidates + + self.mock_get_nst_candidates = mock.patch.object(AAI, 'get_nst_response', + return_value=nst_response) + self.mock_get_final_nst_candidates = mock.patch.object(SDC, 'update_candidates', + return_value=final_nst_candidates) + self.mock_get_nst_candidates.start() + self.mock_get_final_nst_candidates.start() + self.maxDiff = None + self.assertEqual(result, self.aai_ep.resolve_demands(demands_list, plan_info=plan_info, + triage_translator_data=triage_translator_data)) + + def test_get_aai_data(self): + nst_response_file = './conductor/tests/unit/data/plugins/inventory_provider/nst_response.json' + nst_response = json.loads(open(nst_response_file).read()) + response = mock.MagicMock() + response.status_code = 200 + response.ok = True + response.json.return_value = nst_response + self.mock_get_request = mock.patch.object(AAI, '_request', return_value=response) + self.mock_get_request.start() + filtering_attr={"model-role":"NST"} + self.assertEquals(nst_response, self.aai_ep.get_nst_response(filtering_attr)) diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai_utils.py b/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai_utils.py index 23b6640..54da12e 100644 --- a/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai_utils.py +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai_utils.py @@ -93,3 +93,11 @@ class TestUtils(unittest.TestCase): inventory_attribute = {'service-role': 'nssi'} self.assertEqual(inventory_attribute, aai_utils.get_inv_values_for_second_level_filter(second_level_filter, nssi_instance)) + + def test_get_model_ver_info(self): + model_ver_file = './conductor/tests/unit/data/plugins/inventory_provider/model_ver.json' + model_ver = json.loads(open(model_ver_file).read()) + model_ver_res_file = './conductor/tests/unit/data/plugins/inventory_provider/model_ver_response.json' + model_ver_res = json.loads(open(model_ver_res_file).read()) + self.assertEqual(model_ver_res, aai_utils.get_model_ver_info(model_ver)) + diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_sdc.py b/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_sdc.py new file mode 100644 index 0000000..054fabe --- /dev/null +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_sdc.py @@ -0,0 +1,76 @@ +import json +import mock +import unittest + + + +from oslo_config import cfg +import conductor.data.plugins.inventory_provider.sdc as sdc +from conductor.data.plugins.inventory_provider.sdc import SDC +from conductor.data.plugins.inventory_provider.candidates.nst_candidate import NST + + + +class TestSDC(unittest.TestCase): + + def setUp(self): + cfg.CONF.set_override('password', '4HyU6sI+Tw0YMXgSHr5sJ5C0UTkeBaxXoxQqWuSVFugls7sQnaAXp4zMfJ8FKFrH', 'aai') + CONF = cfg.CONF + CONF.register_opts(sdc.SDC_OPTS, group='sdc') + self.conf = CONF + self.sdc_ep = SDC() + + def tearDown(self): + mock.patch.stopall() + + def test_get_sdc_response(self): + nst_candidates_file = './conductor/tests/unit/data/plugins/inventory_provider/nst_candidate.json' + nst_candidates = json.loads(open(nst_candidates_file).read()) + info={} + candidates=[] + for nst_candidate in nst_candidates: + info["candidate_id"]=nst_candidate.get("candidate_id") + info['inventory_provider']=nst_candidate.get("inventory_provider") + info['inventory_type']=nst_candidate.get("inventory_type") + info['uniqueness']=nst_candidate.get("uniqueness") + info['cost']=nst_candidate.get("cost") + candidate= NST(model_info=nst_candidate.get('nst_info'), model_ver=nst_candidate.get('model_ver'), info=info, + default_fields=nst_candidate.get('default_fields'),profile_info=nst_candidate.get('profile_info')) + candidates.append(candidate) + final_nst_candidates_file = './conductor/tests/unit/data/plugins/inventory_provider/final_nst_candidate.json' + final_nst_candidates = json.loads(open(final_nst_candidates_file).read()) + response = mock.MagicMock() + response.content = None + ff = open('./conductor/tests/unit/data/plugins/inventory_provider/newembbnst.csar',"rb") + file_res = ff.read() + response.status_code = 200 + response.ok = True + response.content=file_res + self.mock_get_request = mock.patch.object(SDC, 'get_nst_template', + return_value=response) + self.mock_get_request.start() + self.maxDiff=None + self.assertEqual(final_nst_candidates, + self.sdc_ep.update_candidates(candidates)) + + + + + def test_get_nst_prop_dict(self): + nst_properties_file = './conductor/tests/unit/data/plugins/inventory_provider/nst_properties.json' + nst_candidates = json.loads(open(nst_properties_file).read()) + nst_output_file = './conductor/tests/unit/data/plugins/inventory_provider/nst_prop_dict.json' + nst_output = json.loads(open(nst_output_file).read()) + self.assertEqual(nst_output, + self.sdc_ep.get_nst_prop_dict(nst_candidates)) + + + def test_sdc_versioned_path(self): + + self.assertEqual("/{}/catalog/services/5d345ca8-1f8e-4f1e-aac7-6c8b33cc33e7/toscaModel".format(self.conf.sdc.server_url_version), + self.sdc_ep._sdc_versioned_path("/catalog/services/5d345ca8-1f8e-4f1e-aac7-6c8b33cc33e7/toscaModel")) + + + + + diff --git a/conductor/requirements.txt b/conductor/requirements.txt index 62630cb..0627288 100644 --- a/conductor/requirements.txt +++ b/conductor/requirements.txt @@ -2,7 +2,7 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -cotyledon # Apache-2.0 +cotyledon>=1.7.3 # Apache-2.0 futurist>=0.11.0 # Apache-2.0 lxml>=4.5.0 # BSD oslo.config>=3.9.0 # Apache-2.0 @@ -28,3 +28,4 @@ Flask>=0.11.1 prometheus-client>=0.3.1 pycryptodome==3.9.7 jsonschema>=3.2.0 +tosca-parser>=2.2.0 diff --git a/conductor/test-requirements.txt b/conductor/test-requirements.txt index a44bdd5..993837b 100644 --- a/conductor/test-requirements.txt +++ b/conductor/test-requirements.txt @@ -20,3 +20,5 @@ tempest>=11.0.0 # Apache-2.0 pifpaf>=0.0.11 junitxml>=0.7 requests-mock>=1.5.2 +tosca-parser>=2.2.0 + diff --git a/conductor/tox.ini b/conductor/tox.ini index bd9de98..c6cc39f 100644 --- a/conductor/tox.ini +++ b/conductor/tox.ini @@ -64,7 +64,7 @@ select = E,H,W,F max-line-length = 119 exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build,install-guide,*/tests/*,__init__.py,conductor/data/service.py show-source = True -ignore = W503 #conflict with W504 +ignore = W503 H904 #conflict with W504 per-file-ignores = conductor/data/plugins/inventory_provider/aai.py:F821 [hacking] |