diff options
Diffstat (limited to 'conductor/conductor/data/plugins/inventory_provider')
5 files changed, 377 insertions, 1 deletions
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 |