From d9902448f190fe1052d693cbfa7a5cb385613140 Mon Sep 17 00:00:00 2001 From: hariharan97 Date: Mon, 18 Jan 2021 16:39:39 +0530 Subject: [PATCH] "Nst-Selection enhancement" Issue-ID: OPTFRA-764 Signed-off-by: hariharan97 Change-Id: Id5d05967e349381bc5769c79b91e4439ea38fc82 --- conductor.conf | 45 +++++ conductor/conductor/controller/translator_utils.py | 4 +- .../data/plugins/inventory_provider/aai.py | 45 +++++ .../inventory_provider/candidates/nst_candidate.py | 29 ++++ .../data/plugins/inventory_provider/sdc.py | 188 +++++++++++++++++++++ .../plugins/inventory_provider/utils/aai_utils.py | 18 +- .../data/plugins/inventory_provider/utils/csar.py | 98 +++++++++++ conductor/conductor/data/service.py | 2 + conductor/conductor/solver/service.py | 2 +- .../inventory_provider/final_nst_candidate.json | 35 ++++ .../data/plugins/inventory_provider/model_ver.json | 43 +++++ .../inventory_provider/model_ver_response.json | 8 + .../plugins/inventory_provider/newembbnst.csar | Bin 0 -> 48090 bytes .../plugins/inventory_provider/nst_candidate.json | 28 +++ .../inventory_provider/nst_demand_list.json | 13 ++ .../plugins/inventory_provider/nst_prop_dict.json | 16 ++ .../plugins/inventory_provider/nst_properties.json | 84 +++++++++ .../plugins/inventory_provider/nst_response.json | 59 +++++++ .../data/plugins/inventory_provider/test_aai.py | 60 +++++++ .../plugins/inventory_provider/test_aai_utils.py | 8 + .../data/plugins/inventory_provider/test_sdc.py | 76 +++++++++ conductor/requirements.txt | 3 +- conductor/test-requirements.txt | 2 + conductor/tox.ini | 2 +- 24 files changed, 862 insertions(+), 6 deletions(-) create mode 100644 conductor/conductor/data/plugins/inventory_provider/candidates/nst_candidate.py create mode 100644 conductor/conductor/data/plugins/inventory_provider/sdc.py create mode 100644 conductor/conductor/data/plugins/inventory_provider/utils/csar.py create mode 100644 conductor/conductor/tests/unit/data/plugins/inventory_provider/final_nst_candidate.json create mode 100644 conductor/conductor/tests/unit/data/plugins/inventory_provider/model_ver.json create mode 100644 conductor/conductor/tests/unit/data/plugins/inventory_provider/model_ver_response.json create mode 100644 conductor/conductor/tests/unit/data/plugins/inventory_provider/newembbnst.csar create mode 100644 conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_candidate.json create mode 100644 conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_demand_list.json create mode 100644 conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_prop_dict.json create mode 100644 conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_properties.json create mode 100644 conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_response.json create mode 100644 conductor/conductor/tests/unit/data/plugins/inventory_provider/test_sdc.py diff --git a/conductor.conf b/conductor.conf index e2e2c66..c6beec3 100755 --- a/conductor.conf +++ b/conductor.conf @@ -629,3 +629,48 @@ concurrent = true # Extensions list to use (list value) #extensions = multicloud +[sdc] + +# +# From conductor +# + + +# Data Store table prefix. (string value) +#table_prefix = sdc + +# Base URL for SDC, up to and not including the version, and without a +# trailing slash. (string value) +#server_url = https://controller:8443/sdc +server_url = https://sdc.api.simpledemo.onap.org:8443/sdc + +# Timeout for SDC Rest Call (string value) +#sdc_rest_timeout = 30 + +# Number of retry for SDC Rest Call (string value) +#sdc_retries = 3 + +# The version of A&AI in v# format. (string value) +server_url_version = v1 + +# SSL/TLS certificate file in pem format. This certificate must be registered +# with the SDC endpoint. (string value) +#certificate_file = certificate.pem +certificate_file = + +# Private Certificate Key file in pem format. (string value) +#certificate_key_file = certificate_key.pem +certificate_key_file = + +# Certificate Authority Bundle file in pem format. Must contain the appropriate +# trust chain for the Certificate file. (string value) +#certificate_authority_bundle_file = certificate_authority_bundle.pem +certificate_authority_bundle_file = /usr/local/bin/AAF_RootCA.cer + +# Username for SDC. (string value) +#username = + +# Password for SDC. (string value) +#password = + +temp_path = "/tmp/nsttemplates" 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 new file mode 100644 index 0000000000000000000000000000000000000000..d5c4270b31ebc6673962f03360437f2a248ddf96 GIT binary patch literal 48090 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%0DieUmt~jqY*M6E0~+ zu$ITSq@|+pKTTrnWul}ZoEvPxL{6E|W^`D;%OROGD6OjI#GmZ-A~d>SKPQbThU{fe zEy6y1VeEzUoI3hGx;hY@AW7r!$y%~ERYj+X#qVpu;;%C=QUkhgprs^jiu~NfCp>w; zVzn<35^oTF-(8Q9OV|2i zgX|9=fd!#cKJ%8zEws|nzGr~$RY+KkkefG-e^3n@nX>jq$GNJjK55WkHRVg)A zpf}d9P*&;pk5s_yIv}h*DH)5*83@jSq=?!Bh2B`dO;AjY)ZP~qXO@b1yOMH;|NvW z@!}B)+ogcE^nrfLhVhXC8pZPdq{C)?+-!TwY-w4~?BF_DAKLIU?|$=I2>9E;Y_f)$ zNuRu8-h+IRgVjNu$KU6-ARE)(t`QrfATDjEtM>lz{nY_Sc!C5unphPz+PF53+8PYi zbhL87u6LjON>qAbw{*&xK!c>B2K{tkp>Fk;!!E-jI}Rb!jY9aoy`)bOfyyUB>I1Up z=+bbc0%0)buX}YmZR`E9(v0y0ELbK@tS4=(5){o@^R0^JMdw7idf&Q_7o`n8KZ_44 zGbv9!mkE{6+WkJqe7uQ$xLFmKtLm$Ypqq>X1?IFo@`0b|dlJRiol`;cYL&@t2OS_) zT~IK8lOi5#I@THO6VkO7t%Jo=zt4WEeqX-ZY8m$mFDH#$-00(g4sH0_y;%_&Sfe$Y zkEIrOL<2zERI!0Fxrgugg5HN%Ca==s9Ui)-YHbXYhTFxw$RI8ZJ2kG$y|vaWdSP~- z0csVQp6>Ocwo(JO66>qE24Z=E{fnoC z4mtHyN2rzR0N!^}9eF2qA4Z*H0W?@2U6VV)bi%r#jkAFQi1R%+Tm#q=sXB} zuB6Y#E24HTA&)#CSkHLdO%jN7qhKn!qK&>N9xbAeWRt$t{X^|Z>(vU|kE<0kK`o0y zuGj@sEa2l!&ti6VdS$LQtZdgJrDHRLk%J5gQU z;zrUF3KWj&b14FdTUP*EPnv5fqd#zOOeyVbr{Yv?isg~%KTBRBB^JX-Um_R_crh+S zCov#e%lQge`vTbF1_z`^3v5VEPwjwl!u_%ZphSK6b^WK?syn9JPXiad%S);N(7T5% zAi~oLjk0ncF{CzMn{*Pe2e;X8eusp&h5-U*t&|RE&V4_a^Y8`-(Z--RAv7BU1KdtP z8L+tEfaZRTwS5dWM-UH z`BWFt4*$)5Gs(U>`0a=RN-ffteX>B_{f)fIia1K{_-4Gf6lnWdIj((dfHDVoO)UKU zHYBkPdX=HHb#j960peO7w-+M^dRpWN^jTsY-cE+Zg=ry(0CQD3;2YH=lkEA*8QXZ{ z&kgn4A(Cel9S0tzo+um}N3qeE4#iE5I(I@BcX;z}6|j)1@92Zve>t>-08iOYr1v72 z9nKynGNWi@T5(SyqZm&W=jA18_E?PagT%Bd<*s>FL8JkVtt%+I71MqsC{8kMl<;O{ zw|=fiwx@SMBrNV*ruCp#I^71ZM_uFr!4j6q{9L(yWct(8bGBU8^Jn#4YHl8N@XF4{ zmz-VR6P@i)e!(+F%+gL*G7|LD$I05)m+(}K`lI1**RYgu@Lbr!aVMi&d{v0v`vzk( zPLU#xp^4m0#kV}9yD|k=5^yEwbt2KfAx3B(vf?-+z9ag4&Ut75ZH}{FsTr`cK=Gfq zhReLU;eTARE@=^pJeDl?GS-1#tULF(=t!>;(8S)2r6go@uXJ6KHOc6=;eTtEt}U&sj|p(BZ?gkv!k3L>0>aUAwUNMAdKOi1Enj?@yv=8A zq3f#v0Qza%y52am{t3&$6Knz~;C>WoqJ)@I=@25{#(8T^Z4)Q`Vt1-M@{(lKWZ$C^|Zm@zsco`o^l8DB8Rpv_VNSDya2@4`(EZy20} zHh8P^vKj)4*ggIya!)rGF8;T$B}4KAYu0|7@FnCpM(psN`?zPs0&73Dp24}ehqOVL z4kJ$azOp*2XMKb*i|2qM&z;Woy}^&0BbVe%$@#`1dckjf1mOeIKn1_jM?OL}-guFs zxsFi#JVwnM$iSh8s4!bJdmhTf4NNd6Nd1 zHF3vkQ9aRoE*nOdNU*WC`P)O<)AGr}^Zs&@bmUpRe8=k5K4FU8oCXU^wUUm)f{o9H z4g-Y0J?pVBJmy;XSNrs@%6zMp_7OM*Q-Z}*KdG#jsS#SU0$M{QY`FKI)u4s6aOTs% z+4Aki*0tm_3pS7WUEn_s#vaZd<5YJ2MAKlP@RCX2sVKhYfFzKZJ&kf=w_S!m_!HF! z@btlbxVc~acsOHy#TV#;=XU(*PtTn<_Fw!>dw-6wyTS7Hcuu0a^rIN)E-HGN2iMw4 zPq)5|FnTinNv9RsVk(}EHE{FMDL4v=g)|7CjG01}1893>xt8X{~8 z?QoGqSmb9`sGa(6Bxq&ZeERlFk3x=Do)ti!r{l0)=@`zeLYZjeCMh_bq-UJZYSDcX zH|S~Wkc^zZDkQ)s{4d)bMsRwu8@OJ~)EEsh=O4-VU*YdQLuqMt98OHKezmyVXC6qSo)#Rmp>QifqOwHY-z_dg-+*XT(z%3YdQ z5hSZz!#>C(cy6mcs1jGZS#@n1wyL+@)~D-{)ZXhXpq42#kM%4#*Mt%i{FP@Lrw_sY z6SBnWPlo@zEjo?NXcJtOGXCkzo=~4~xe1(+qK^oEYs7;VVfZW=<{awx(x{^pNb3S5LjuHe8g9CYgCxN(ka0K$k5d}Cv zmadDqQ1OD@8&*e6tukR*)JIajb#KYCDZv97SV$@8J>&V(Hz-I;FIi|9#E}kt{Xw&E-&v2phGq?8sc~aOmN3mXTpcisSAkf&mHBxTbALOSa{+M z(!g8g>IgeUuz{n@rgosTUMn9quC2qU3upnqIPmQRP$3`yl2z8MfdpB_CmZRk=KeDL zX~4CzBu;6vaq=%Hq7g$VnI=Yw7~knPSOAhh1JoX*T>@jr6-V;DG&*;Q?+^4{DwdB{ zr6aIW0s{9v8%mvz=Vza`*o5i_SCAkHi36>wKP|*B!O&#+V+8g7YL?r#Dv8>>`>s6e z*%f_+g@HTzz0f&XdP|YxQ|A3qTKTVLzn5j7v>xYr|E_bQ^t2Ohv$=8|Xj`iNF^JgF z*_VAp$iB}wuzCujKmI6aqu~;r^E^y>yj@Mm7N?64a%DUUI6)|}tHh{JxH*O0IigS4 z^}8uoGG$LRFe&pC+1Y1CRcTdr^PZ-**9(mIxrg)JCU(D{^SZx-&A?=zE!B`L)Bq}0 z@_nwu^2M)mpCLwteu zH7}{%_2mHj*?V@~wKa1l@XT!hGr^63j|XPn<~J$~p4otdr1epnkG)(nM<$TmF*3#;9M!)@0{+skCLt6?47Qit_eu*bJ_19Aqlt=WaZe zI}=9yJ2aE>!h;`{C5%y#^Y`{as0SEx{h_HJ;bP9AE)kojN(VOR@gxH-;RT`Y?jL>w z*B@z$&_fvf;wn}R4uJ_vfF-mK@L7)=OJfOk-DAQ&Mxli*hjbLd0K8`mk_o6GR@@!O zUeER^%^A|&iBL!Hj2C+^LNn`B~|N)YeJ8B?R)vB_>s-xpGNBxxavN zSVz*(w{k~gUgFROc?lI(8$C7)YXbA-j~HupMYHU($HW;wn9|0k`-#%RLZ4;XqPqTX z+dXi=_d(x#TMwz_2ILaqYVWSThAvBa`W#^BwCt@J8OH6|^}%v|%trGnkz)zmoZ;d; z{wvQHSJDD|cmTf7&79o*=rxY%`)kZxY3aw@-kbXYXl`IV)miqM-jS#4^YV-if#58> zzio?Pb7_J3uOm=)e8Q}M|~zw_Ta zUFKSY%xvxYv14hDkVSu3lxh#-rT24i1LB0uilH*Eccu#pF%a**cZII1Bw?fwIpn@D zPdteIz6_4sZz&H^N%l24hQ;Pf@R&@zpl&L*%7sCO)CSMlr|9V~ZIju&O2y zBm7xIE4y_AnT5kTSL<)!H@8H9f?JNV*ha&PSlay#M$0nGIKB4KT1Cr&`cOI#RcJ39 zFIPIsRkSMAkctTvPi1s6Pzh(skmW)JZYnFv^z2t81m>IG z>#01S0eQD!bm>*6m{Wk!m{tEkvJQDKxiHtIR);QpYAO-)t!-$WIiaBka-GHf5ZF&coxF?sGr@}B_`hq6;x^>iY>|E;gycBSvl4_FlWT{nr5%#m;i(p zM8x=G%1sROcBQJF@0qM(2|V(Mh~vUj4YcDw32S{Lg)x=q(9?^9w2R4Y-cp z9v=5_Cc*!XAF7_8zwivu>fHP4)YN$&CU=acmpoX2uRwUE#wC>GEVyG79CPvmgo)#s z$Oj0*SBvoIF1}7)f=X`u3i?$Ce6@16-tjhBTnYW+~gX@>U$4k z8vN~~qffG-AigqFDyPuz98Lj3l{*X=0~)lKQyPCPH+LcDa14$T@;!m?o~_;}C>1%@ z#!c;(t&+1&1EuNxa=xz89&IG07Iww$x+ef8_e)y*MT6ERvIGuerA7?TCggaaf?p9G ziyMDw*k-Gn=3utwhgd=5c>hk05?K0rtTh0lL#(!!sQiv8=_n^I7BuU39TiBgiw%4w zN@v&M>h(M1@=R50#Jz>g^kQyHoH?AqMAKEZeS9hmV$8#(^$jFADp+dHjYUEGrF|?T za~Yc?8`nk#2S0Eo2L*#m8S0wj(?F_4DTy5DyD9}|TqBWDV^TdMTeHv@0sieiH;}0+ zoIJ;b=-S`-pmq>tC7e3Y1S}>r5(uqncK5KdSUw~^83L5ks#3UO=bG}Qd!6Kl$j5px zx}#TXHL*FR@Y|xTGk!kD0+5}|Uu+kH6>Dq!AWP{U*NW0* z4miFP4`3rzXvZa9jg-8QrjQ|zf>9ug;ZSN6(ZF`l8DYajr+JfS(6lI*AMEx%NSI_Y zTtVV=%?qB$8rTs92?_T}6O9PBO!mFQftI7So0sJVdvOTBO4jdu*0QZ^dy zIhn17;QJzNL(rPzg|?K(<8#CT@{uBf6aHyosW<;S#KQ1VY*162XQi>wQrIVsjg9oO z*_<>=$;)OYRa2#j!vDqXPIl@$i;-0U|1#fN*Gg^88B%K`s2AWoKa$k-qH_c&YfN*8 z!xQ=Lcsv+bR8#=dleiR{c(aawkJnQJt_`9~u`%-Xipvcst*aIc;$3@atR1~>o@()H z1Z}yB8UmIV#k5W zYB=v0#3$7-XfeBtn)>eqvxRmYkF^S3P#Tm*K_))&+1&7B0^SbnaNblH+Rss`X;Jg% zg%7>68vMD^y|V)db({TBJYHRa2n~LTlh~eOnyjOIH@_T;kh!R;#x%^iVA2_KCECAG z*lp8Ou~y=dm@7PpOCnOl!t04A!;;3R36QbA6Pkv?*g*ara~dlBOsJjnD?7J&k7kLB zG17)83f}k&?iOcbVY0SQE}x;71X#2qEk#d3adA}WOcnR75DgvH9XntfuT*7C#_MpW zruv)tHk1DZHVwh&kAyZ(I%dsciX=mDDQj}DLz*E$l#jpoSSG-+&?`dOVLzZ?)ze`y zIyg`n6%YI_MYXc?JAcL4(t)0=HS}_X@qOM84_|byNFYX8uBhyJGR71<>mUay!U0m- zv@jFMPF9W!Tp+KE*4ykBdk4Nk5|dp}Hd=sU4be?cZ2ZnD63V~M$=IeJp&t;57=UFT0VWrd}?7D9Feg+uGshJC#_??X+PrzSz9Tg@N!nT{Q&ERq#woQrgk$Q$D$2HKOGZ(ah(a@y8F0-b`euP(TBB@Iao+z zSuI#O=GI4+iW0tRJ6@pT<_0x=$C;qegZBDyLLY9B=!h|3knz}Ji%>luC#aS8TjCh5)Tngo0%?XKd6FO#WTF*?f5 z*y};19e48%^$}`%1$#nKA!5<{ z(2Vg4dY?oDKC*H)d!$bRYvOrW>#0^$=UhW=OCS-OKBaGeB!(KU#idfdKD#UP+hI&z zq>;oOlPPVA6=dzK)tMQscAj(!dFufe+Oh6es1KeT=Icivfl02yLA%1+@1&>W)2SCi z;MYw~-$QK^V2>aC@cwh83v=kg>&Yhn4!gbK%`g%o55^IPUo&$jGQ!3ekjGCTOn} z?oop6{TY?T3N?rYVrA5|KNOk413rN;L_BKySF+9v@M%K(E5p7`INX4@Jp}{!8#|o=aFOL8atUir9tuMlGYlX3 z69k%IbHw3->#L~Vhtja`o9XWKqn+y53iPcG$-~}=LVaPz`b=`<9w2CjKTam5k9hZys>XdDEYnVD(Y)eEi3O-^?$`AN?hb3)!E|**iIH( z9>$tVdq=2B4&uq?Ldv*tF3jY>sfj7V>L^V`Mtd#}X(2P5&~XX{aJ%H#nv>wvEO^(? zXlc*;P7f2BA^$wqIQQp+@!jFLWmHpo6Sy%Bcdi}BXL}RvADMenXj5vc*(~~+|1NL* z59X6 zolnC*qXarF3J!I~LKd7}h`bBDyT8d*bFK-U0_9?emBYG5*G?JINNmR!eSj6qOO$kt zel8bF$8w%nOJw$UxwI#id}iz9^vC3Yn8C>5zuuWQ^;IcF-747YB0pClZuG{}6i)MF zT1Df4MV)9hZ3qSquB1otKJ4&R!Oz7yI#RRDb$t?#y3$7viJX`720|=&3M~j?{R^;t z!E=1`ymT}lhvfXYI~sKJ9-$e##?KKUW6KkHqwc4+Z zLr%Kzqnt5Vgn@OYGRXo;;ZW(TlkM|SJ?2bi#Fg}bo9UwUlb^MR*sXdwPp~LqvO+h} zJzBf;W^GDUyq5YTGAn>4@{>^_kc4D1syB66EF=kp2R45yV6&;Two&{g^(ZZx3xhmY z%3)*t+;3=f(AXL{8gZusky{;Z5c=!Pzbwk-ovq=1ppSKV$1!mI_+~gZVU4_}puvjR zEx1`tOD|v#_X)uK#d67zXO z09`GFQG@TO(Mj{*m-Od5b)hrGbn;z=y&72&($O|N=J-;1`S4uX_`L9mm8t!K{IQs0 ztwJ-osL%J)+J>tN98=!Kd2yhGDNj#ZXy^Wi?@MA^UclF*56;o&T+HPO@m!<+rzi9I)XPvzjEP~=&8jGETiFMd;-Ao3%#?5QBgI$ z8AmAPIqiW|O-@x+;(kBX07D{L6X@Vy z_2F+_vm2j$pw0wm)?FoM+G=Xkj>iC#43(HQEkE*j6EtIS^_WwAh=eV*1o5^iyP})T zc+lhzfvZ+=&BcqF^-?9of$gpl5 z{Q^xUIV+Q(O$btO9i!NM9lRm)*}=}PaJ@)~f(wc#Yq^;6{?d&&9QP=QTfgIk&8BET zyrBg^Yq$_|P|%~ARyk~aC6(SaQEhQWE|Su2hLE}0y>_mqft06C!)4vDo^G z&D|hUx$b;P4NC=@h7Nuq1;QQX$$qF#Vv8yopsUkA3V-t|odbiTP3eI*x7^^Jm(uLO zRmwBTVh<=h6^CG~vAIE+&d&s*Wow9GSEFHIb@le&J^BNIP4b}jv+d7%Wr&%clyqQE zW}aF)R$lcfxB?6p^g|>x_$t_(90OTOpzQ==E4APi#hiO|dRCu583XT2T7pP;Ik`Qz ziyatKhiGepq8e{adQgc-cLU&tnR8#8dKv_Iqd@?N7XxJ($ZKnaVhVU_7v_%)FfF@Y z+&6@Gr#Y2SY$Ed}p0>Yz`~U_7NwSuQhZZgKRrA1C|7FwLf9ZfdGJj60NZ>}9(aUhc_;hEVPC6!J z|9VtYm($hj_weUE-{0fH=at^CD$#uZ?aCS;Yed86c;Eq$O2!|Z(%5s=t zVV7bCJI&*S^u#2%Rno8}^C^*gagSZm1kt_k*vIA2cc#b5ZIjH=JUB3z=7_bneYo>iA zt`giv{MdY1ZCjf^Gw#8;>!MZuEjG;ZNG>ajEoViC!CE8jPU&N7|KfesG{{%Mu z*#sM{o`6BX6;04^uShQ9%{`Ldq;}YdZCi7x`FP9(cp4&m1Y)ljk&#`}*mKeRc?8|u z5%orkc9;AN5RBm-1#3F8>6~&30-q}L6&m)c=mHVEC~KAB4<~!67fg-#A14&Ch8YTMmkh$V?T=($U3ZNSIcrehSZ@B0-YNCDM1Wkp@| z?>Qo_YXPn>Ne7*0$qq&k!Z9{5qrZ^^X_G&5^T15M*7~2xE8K+bpHKfS zTE$8|N{ZyswabE^A}d_Hr{UExO@~1>v;@#=1f?FM3H0#kUnQQu|MF0U*_{*ki}r(J zif64~4c;bSr)RG{t0X2FxGa0r@`%mWY@4rCoz_4U6}@S zRf|ztSgy-NAdQqLrZldCoj4dyxU-C)9(1QGmI{6otgI_`gs;n><_|T_=J8F+E`7TT z8m??Sxa<%`Hjtj(2U*V9({V$sl|tSQE5gSmOpB+sN~N4wuolt?m|+c>my2jl-x}(} z=y!I2`P%7v^CRbtLnhBw)CCvjRZ)7Mwf!4X;H2;0mPO@K%cX1&N{@WsXw*uFU%*z}O@rwewq}N|ksp>)&urCl$3?|T^^-02$d*moWOSM=Nj{OLc71@39y9*VWnOE z(RxU}5+MuDVRSa`Q%FT&5Q%^9xk#mg85ZL&vG0N=?`_tehQH#aX<5DM$#wdl9q}8T z@f#+h73pouuF5#1aWYrNDsoCY{%TScyc&d0Jai8Wx!tuY%NxcP4HBEoT0QC1pH>i07o{?|COtD_sj&m5(#j@LT8&>KlS0#w zZ`u((K~HT`@?0s(RpI)&745zsEA8tFP#jY1A8@`Z!tS+CZ6C;xk_AiZ1OTnYo0;Z8%x0igz8la?5RNcnvx;@QT1{#0wXfuwro zGIohvxr;7HCWBe6R^wKH#p8!Tv{$hVI16#oa9$vZ%~Ek1;#3DJWpTn{`wqb?AkJ-ty+krf?R{q=d5`Fz zE+)0JDWWT5hhFa`rR4YSTKosr3JSG85CyHja9=KNaQVDMigx}~8see$f}ne!IG(ez zVn4)uQQzG?U(QQ>ja)egwonb<^;eeu*pfW?ho)|=oJl)mqQ2sF(w-65GgG9H(V7-k zGqe-5;Z+~eC^PK(WJjMBFa?p{3>Go>aP~Eb#g8b#X(i#yLz|QPCo(&~>8rO^cT_?@ z8L4scF4$U^5Qckm7d$WRYs@Ze)<(Zt^Af^_RGv#aIl8l&bk|F?25((!QD|LSeNTiX z2xbk5W=RVfZ=5+n22BPtVK1_#mUPMEep%R2d7A0nzK#!}f;^AB1Am)rIf>Q_rrn5l z(a<62JaCi2-~H-4?h;~=0;%@I4MRp7v%>mpOioi);!JdMvYGWwZ4{%7E{6HIX!}H zaUvtdAc>}(Y|pB&;xr2fCySiVZ+Eg?sjcD+l!Hs#y}QWrDksm18Cs~F^0g9T_-9ZJ zTbj#7Bgn(;AS>D@Pa?#rIVG&Jvs^gCWaGFH@nwD91pyscRxci4*{AP}C~UY)P>TF+ zX{yFpth+Ap5t-s2QC$5fZSB18v5ks{^WRDLaI~|xb;g6RtV0y6E(ZNz*=J4lF=}By zJSJ0*SL-KkdvOmnZ~Vp`-939?ja|4Uky<*UHE6VdI^LM2QI4JV$WGy2`Nt6qV0s)l zRt}r2?;WjN$3rWf-z5$0a6HLqY3~+TBIRNDhvZvu%hcanLI>J{jS2(lRP^04k5DSY z@SfWGT8QyC_4WBR#ubabJcc9_P4-pb`<2$@4F-5fYj{^&ju(;loKuDoC3=xXUI}KL zm(R^4#B0`(tRv6bOA%OZo9o}y(fto#)afzJUzp%=5ag zwWr0Y%oV0LYCWAw3{wOWg-&v^^AX1tCW}50&g!4cn3y6+D@&zJLH4!yJ*PUt6}~51 z#OM9h@*&=SSh2(h7b0Ky=`%i z_7=YtFTt!mq%KS|kYoLVDAch*E^TV)%wDNsN+-Rzpa}q{3B0nfbt=>eW=J)`D2ORf zO{=CvuZd;k_Q;m7luC09|H^rc^#X=ip}fuZ{D*fYwmS>b{ITk_nV$xL5yNANVDXL% z+%1>63&LmvvL_E7N6N9O^lA_XtWGy?9uz0;l(^hjx#ff!k zV>Mg!Zb0JsmNt_MXLmW_{9UV~6dMmM@1MAUq+A6e6`nnQ{8X_z$Ec~6GLqE^ zpd@f+PF-NlqU-Iup#anQ19@u(9>L~!J5WO_Pi6^qGxg^$IpMJok=eWmqvUH0`6@-PM8 zW=21^RcW?@pDrgYkyb7z0B%|CVbrW~Efe;~N+gl9LQ(C6LzHn->wQK__mwxsu=V%i z_iY2#^dN6~TZpnG-ryzR7@1KShH<sd z*_G>c9x!{z9tqDAK0E0&Ho=N%@((w*0G>O1Vig>(dVCukuXghY#H_}a;)I=I@|e`4+YKcRBT^6lep!C7uuck(h6_a+F{ z8*m5DcL-hafrW{|k=ZnJ+fbB&1%^khO*7&~Ozp`(Gk#>9B@2am&Pq%`pLv_G}k+)N@X3+ zOS6nj*_?qG`--OlKOaF=AjeoBMe{L~F{z=*T&IkNY4)#jI_fKbTE{&)vT z^1Dy-4`ZbtidiBwl(;c#qO0Y;&9R+OXkF=JvldFQ#$|9cohp!EsxW|xza zW;|`8tP1kk;wGa1M8M9UA-AYqEwhN>UKQY+ zo4!xynD+BHga^{c&r4v8M7Oe*T!36il=p2)hchcQ*XrmYTd~{QY&)UKHv?P8A4r;}=+iu8rIRr5 z1A2CAc0YZtsDU@k_i=@oXIrTMxj~Dxq!F8HkFHbV(0e1KY9dGuMGX&0(wOUuSE?h* zWz(7aX2^TB&2~XqK|2}dd&{jpY7=I6f`hux8@-%K1hbNe;bx1NmzXb zBRQ{b;=EtL(p-P*xtjS;Q#-702X|8=dw-`Cd_a5mw)B~Al%8yQGnUwY8;@RQJpEP( z`;)%}J$y&>fEz*%xhU82@S4dL{81@FsH}h)=dK%8_4KWZbKxu<3P6XAFIEtZR*cXA zpR{Lz+{ro{RJS9!s6qDxjkf{KCg-DEY?rt0WG=uh5$ONo>m8Ux0h+AM#%&w7ZQHhO z+qQk%wr$(CZQHhu?RRElW50=*?>|&k*2&ECoLy>IewU;YMQDvf4*SaEPX?K<7X3=$ zFU}46HW0~cR5XmM*77nx5wpM4>moxp_rVC%z<-@so53Ku+=h*v2&$;pd!H-5@0OIa z!g89)fLZ%2N7e{+O1fHF-jk-bKod|I6+d7BON^bFT>84zX%-@%w}TZR{M}MnHISfy z6CV1|*Ed!rTD$-)3sP@oPnQFgd)Lzwlx_dSP)T#sVd11blxfY+v2pzjR>I+igMxw@ zf=0l4a8;E)SQH1$VP1b;Uyn=Fj<(eS%5l@|YdQcD>cCr(^2?STsg6BJ?m}c1`86b? zI?ff*Bu4tiktf<$`dZn(=ill?n=B#z8E1pciLKf62)YN?<^HIi{EJa3iHya0m3B4* zg{Q(EIK9#5cj%c_m)cicCXj9O7-`oPt^zF`OzIVM{tfUYw%zp;arct5la@uljqa1y z^R?#np!u-WpL0BcJE{9)^!;{X3l$qt8(x#LodQ>H?PC!DT2bI!(qZy#9|j|%KbB@) z$mGR`dC#x5(Fv=#Od_TYn^UJV+Wi48^Me8ZoWWKg!a6QrQr8Tb&N9*?&A4K{v2U8> zDgCRyd8`opd*RA(Hwl|NgI7M_ANSus&V(-y-!0sI)WwRv-bOAW8je2qC*9t^z8vh6 zPEJ+U2C5R7c`pmKG<@>>OhB55xBx{rGsWa0R6Gii?p#eiC0PGW z7yjBU>q|EW2bUI2N*K5qlA&n`g+%*6#NOSRnb$h$;NB!^)hv7Mt1LD38;7=EDYzU- z@gK!7Vl*fNlep(j{a#7RuEhpmyW(3({kX=%d3VZ^rkzj~sF=)aNV(3y;B7tV0ed(e z*x`XYz_VA5PM)4m2N+#2Rn>sGr&}lJ6jeJfb`EqMI9k!i3uw>++uIR4CkFtrIs!&F zP4!_}JmL`qN7!{Vdtg3&0z6fH5~#E|T@RHA^!`bzSqld+ipC#w1CTN2*g8M!2{bWO_)M}x!dDpWJ?sxM_S5eP@qd}-+d7#m#@fk{#2g{KQ5lp~+E*Mes*Z0_w*R zX)cbkI;dhD@V8%5)XPmThG5VxMJl(4%Nlekpr%!@2Un>uWFF{QLyi5CI zK7Jw8HS=ne4Vs9}atqti8x^#$Az|ja@`5U|L|mN5%)3mPv$F0|+%i()iQJb-A{K+i zVetYple=N55s^qJ>w45#sBjERtcR#KB1z~scKfIG3raQ!i)>d;|CmsgBi|AQgqq*9 zz;^2pT*B&cE|scoDn|`md`peuO!dsQ;Er_er$)m}mL>HxAX@r0!-`{;$pi`FptF50 z=j9;CT}YfH+K5Z^;b|j|jTc)>ai%0J0qDnNYJ%$bBeUaLh`i(xWT3BFR!|~9-VVKC zx6f@E{}xj4f=`Fb;Sgi@85W0r92gMn7EL^ETJHfynC z{o{Od1BU1H3>VsjhGRth0+KH05|g=BOMWWr3UgU>mY#{-@!AdhU@Qzw8W%dkP-}9M ziq5v<8*2T?oUl}WzBxF8U_0U^l+G}p7}xEu9qu5mqK=tyJxH+GgRhiVzK%NeBdzUi zK9&}ZbcsTxD!X#Ckb|6G9fnQM0l*=Se#* z{gwa?rhvS6F;3wXoDY@Xqf=YDKa$o~*}hY`9u_N0TX;ko`-wv|5aD-B*%+NBRA7{V zz1zb7N|!W2AT%^^co@N{S5ej73m|Q{ceqbJ;!GblOk3E!7c$xH*-nEQNZkMt;`M0jsL5JU1>@>VzMH3U#R9L3SL4AuT67+Ss(R^IrK+SR}ih0 zEiNx3hxWyiO##sP=8b;7Uk!7y0pJn{Q`k4NfQb$k%)Q>`bq^v6pbX5OM7 ztRvteK?bD@@Fevl+j4MgL}Q$Ju%17r7Jc z+T-=^LLf%FeSTLPJB@G$O z?l8`fUdV-{@M<#FxIchiR!((3n7~ebf9QPw4(?5HaJe=IAzoJS_&15-G|WasL_rm(5NNEm(6B(lx~?Df zDdnl#m?>oGW1*>esa1CVEg^X5#HW3{2=`eWL)Q^lvy_kU>7*>nAvwUgFKP{XJC(dJ z;;G~PuFUu8f^v#jRIo#?zDU~#FKhJghK>08zk04dxChvj3hgW9MlHpIDR6Z88<|Fd z2HLsY0(pL{VkX+Qlrk7I%goYBz;9CXzKotQTW^us^l^L5)TR>Xjzrs3V;J&GE>xp96U1uX)dQMhsrdv3xA-2 zkz572KraBFMi}AgC4BIE%9C_C%fCp-UwT99s0gOIF6sh|J>18-jJz1wuJU?exZH~tP7;G2!jdco#t9S!hBrouV(A6NF> znRT6w1|E2Yocpjr%rQdWS#vmlI6Zhv!M3uz0Ww+DqH8b@ISej)kQn8j?Ts!*U0`f< zB_Q=BS5866^3+cl$Fbs!>4CtYi*P+Xwk-Wx9Y!lU`7)D%kjVzg)rz@>2n1IuX z^n-MA=EVs}2-;ndXFR@sJ;G3;ME`hIn3!6`MaEAKYACA*cfByIK;p?%`?ngQU(MSz zwm5Oh+kd7#K)W~vcMD{gQpfO+k~cw$!d#LJj_bxM0iiBE6mXZS5CXyD@z)^ugogph zzgg57d2K^gk=Q(>8W&j&-uRiF71k?pdG}OaB!3^U^Gsd3jKfP^q0x}wrWk19&tI`T zn+TVG$pS&Z+9dfo6+BBGN$nTK#~ICUp(|giU#gaBaDqfjLi<5fr8*7a5r&&RHoQ7b zQF@f=?gndtsEwA?l=eExQeLk{qORpoe^~(I8OCLu?13!$kh8%eRX)W@99AOhL5Nmx z#g^ifu<56i?JPnV6uX{l^fkWzov3f69_qKV`qyo5?3zu;bNVGfq~?9L$z4S^Lglw) zc^{@o4CLC`Zd#F9F`5Kxg4qiUD`h)^GS37g>sw6)PYRPBgZH+x^YWfXYn|FPCc?(A zHS#_#I`@;ZY#Sn*zDU4S#j2($)4ed&HUt}vqaFt}Bg4?N1I%6N2q%4u!BBTO-ty0s zZoqDWc^Kv>%C=1}ZIas5XUgP-xQ+`5NagI)In%XSpxp$vl4FE4gSYbNc5%WL(8&3s zUyl^~p>}k3V8`d!jxBY}kPf&4-=9BO-rubJoY{t_UoB&v_5YwP<&7p{x4LIXHeCcb*g_J6-p#RmY=(M#<+>sg#Cxs%ZE?$*qWbeTkn+^dZS}!iDyoO+r zEr%dixeS{zcR#hR(>T;^XUraU&1Bt6lpbZG{y0(2FY4K1af{ZzbGd?EBU9;Qd7U>ykVkaDMk@VY5uJ<`Vo-eTyQEF>!J|_;4Qa(qrdUPv9YoAQ^DGs z_4Fyu27F*EE12f&QDb6|wTHGVKXWzGDYwWX^>8x$ju^AN#kG41Oac%5<(Go$UNVJ~ znYTrL&4*z|k0h+_M(wsDPQGZZ?}15Ob-+Os*E3n;_Qy<4E+UDYxk)1AM5-3ia?5D; z-2Wu{`iev~bTb)DTQ}B0R5&^n$osFD+;Ne0uILT?=PGf0oB6SxDjhoNhd#k-Ap`u= zCMC)0lw`KcvVrNv@=)De>2P)ZEDZWT8MtVHhtSs0cLwpCfqL!)(W1~u#`|_0QgKx9OTADW z!hehrK;O6Wey;^n1%h&|aSS1>4k4PCQTcHC%l=0`G9Aqy@3yN6KE&9~5IgLYQFaeG zcVFYuX*VxSR^sdF&NVkdbop6&UYDzX&;k0fprtlSvgJ;3gB8-U^Y^N}0sGT>loU#M z@cmxs+%$|NB^_d|wLqU0pa|TXn~QMlIN+zYaEJw)Q>h1j;;4MWA;dS3$HP zg{@Sjz49y1PI14Mx1Eb1y^|bv@ZiORBQ#pdBCp9p3{tgYK^=e%d&aOr&WeWY2ntTAM17(USc^ zK>e&Wn2s=ss-L0X+H>ZXcZiDVz;-B~ZP?$ zJX|kJ+YghTtri8AtE z5f*+-myvNM5RxmLsHJqvV^}8vi5Q&ptC};bCLx<(pUOAsCbKdoINP-^0IEPN85ja)5^)C?i@OtA2M4MRqd`y* zN432En~rljFT|0WONc!Luhh%@3`qVaer;FMBySb1HRL&FG%@D{<9^8!h%SUDFL6t-i z$Jn9;OF~%4F_HgNw@B<&eD+Vy!}fT+EHn6HMry$2YW)mCfr_#%Geo2vJOLVe^4kQi8hX5K7RV$c3Z?lG!>bko&crNXV4IL zROqYKh-p3f?vx;+sy;Ce3IRWK5ge1eV8EXiVeYgn@;GJJaac~wh^-FrE8c8cdeIeB z#3hW01V}wbu;_tR(u8>t0cck&@70w_;7lOZiYreJu3?Z5%Z+#!5Dm?DmVgShd3BRsKBmLvJIL=?D>1knB)% zd8c+4aMmY!wJi{aXU`u=pOnPg0f75C23jg#3JBZWTSUj%;tqI4W(m%A$NR^{-C3tf z4U@z3e0}l7xJ^ zpLVOpSfl>E!g6x$i3_THv#5tRH}b7>DG>ErJbfmXs(VIfT9Y65r^vm9(%XLv_B=^D zsjU)|sH6K7r#Lle>UO9>wrSbHf6O`|ezQ043GA?JvN!a@x@1xTrh5tbp#J^+zI1LY zphMcB`KV8L$@J;7rUFLXEd3o$oqk4&-gZmdAC^X|*t?3wrgY??0VajjI^ZvtZTH9; zgMP=}2aeREbJ0@M)RNlA836|U2C;QQzBR9Z>2Z5-Dw^Wupy*DXcxv;Rcv#zpzCYU2 zy6%d_zhe_#9iYX2IF!Wiyh8{6ed{j=+p5Q8k{UJ>0L$98lDz-AUC3r*w7^l!}yJrC$w${0~t)v8epx3%0`afVsRi zv;)mern_7A7m38Zz%NlW2kx6;3qDaW9z#a#xb#mk!wIm{#MR zr%`jokpo&VGNHAFR)ZC3k?yt;s%pmPxm4&uhUQlM%kHUbP3EEp8ekPbk{kRa3K$)1 ztvwMv3!s}_c#;juuJjZz2V#nZyUY3PgLsS1ZQ5;lYL(;YJ-V0fiF!AD%!k#F zp=U?P$CFLV_GLo_Rx@YostrxUzvivcGmPa7u-7ze8dSb{N^!xNWe{&arMwuWpyDGT zQh=s3-_9b1rSimfrv*d7*;G;ojd4K9YDFHY1t=Y2nyPbT!jX)g@OUN&OkD$c`Kre7 z`hD~DkiSy+xT&RRb(Rqi`&@Aa_Lr(-=t`rh(b^fg$ycH3kT}%h@P+9l%*jkaM`An3 zJX6v)5|9|a;a+EnN2zoRNt2OdqGAV1ga!BV*v-Ni)s0u|JdHAw*cIgJuO#|^+}eOb zNx>Z#rta>`XgCy~6ci)CEO;$6)9x>pAhx4~fJU~MKf^)2-x-C-&}MPt=&8i6q+Top z)3?;z)DK~I?!OYI^9QPDSHF5f;mi~zOGXA6j`U(b|LeE~S7i#4{IU`OfBByO`*Fkm zAIHtX_!oQpA0)Dw`F}U7o7AL#o7M1LAFA-v{Peq(={Q>n7NnuGd^A+d!m>b3objub zqcCGh`QPu|_%D)Ss&&*u;JKF$Y+es+nXH?ts5OJ4N%1a;VG5>3LAnWOe;I~knH{Kk z;+)6L?kw*k+@?TNmS&HZY;S8>i&jUwW``r@5wyrilPO5~brmT({>Uj5YcBzhO2|!8=VGX4mMm9 z&N^86u#P{A935oT-W4b!)KER~p#UU~fao0CkcJ`K>I3O*(Qcu+&sv=aivEVEMH$exjyWER-`_;J>1ZwO%7ZY?SJrW zZb)4Z)?U5+OI{3^-b6p?vp2P&fK=6jyHS$wfC~+9$PMRS(U&gspu&x)9-T=nO(rhO$si#_No7 zp_15bGXiHwsaW?lGiMO##dfbdq0%d-o={Ze^z^X<&7DhzC60-CTKz&89Jb;omwd!< zi?ZNGK!EQt(f)K7m4veO+XKQiJLv+h4BOCw@TH9v0`|+Y`Y$Nez|LB<<>7Stt&m9Q zX&ooNk21+RHMqi?+lXw}Y4m$e_ejiyNn)io!WoAT;k)13l8rt(g z7BI@(M}}`+>Z@hl)pq&vlOazzFsGgp4ygx$O`~+&!8JdmseM(vXW ze4fweMYd5o<;v!qYWW>*K%5~>Pz2`1PnpV*mea{;WwKN!mMkY;U1!`|+#wl&e!$hi zxqrpUnclMMa^e8>(YMclqfyXEt&l5Y6>ibS)(Mj8+frPFyQ`Q5I?Ld(k*i%fD53Y^ zP*!!3<-K(u5(VU6u_iw4k6ZVHh~qbg>**_r&qSzP%hlUS_t$K zR1?ZOOI`|j?(Xl<5!ew+ut~h>(NSe#-_Kz{Bhu58<>hJkTOwc2zhQ*PWpiSG^T(|e zUm$xrj)BuA^xYwbpZ5!AH*_1{M6Hr>&xqH2zKe8rm9iTvb1KX@Hu#eqCFw7A^y)A4 zG7l<|)Ev}0Snm(xBu5##3^JWdtSe8Q>VdNkIFszadgCGQtmKcRXA}U!wj;QB22dcY z?9~L>%Tv4OBGk2pWo@BDL`ccq${*eHxNw2536<)otDT1t ziE*$1r(sE7;G1F_(peBM!EQTyq{H*I4+`T^!9{v@` zqC#IpI1LCroJE-rrhlA`16l*)-pJ5yFWa0@D#sz-@VfWttI29ajT*8gMYU4LQ z#Lex*a%TlsUuQ`HkEqV+T~hFG@eu}s$Xt`Jqm#O#&@P-P9=;Xjduh?WnkaB|(m-|J zDY^)WM_Uh*CZu3;bQ8;NOfF<}&lWG0MZ|Z)ta`Xm!9MFgx=#$dHvJK?JocQk_&kI5 z)LceXwLI&j$_Wjl-TNFHQF5Q}Q^$8Q6?*;0xVLPw0{j?_r|sT)O?xl`-91EWN4S^K zPHrtR6XHS@HM@CJ_)o^!YO%IQ37f`EuD<-qpAA>q_GD3l7k8OHXQoST`T3+vCfF9z zs+WOJw<2V`U3N5i<1nGVJMrzU@p8`{@|&D8L8A$cBilA2+PjOd8@7OhW3qBX#=wFiz_PSJHYQ_s3-B= zB9NEUWq7*)K!uPrZteY4nw(1GF0G5HNJjXD_df&M_a|Sp-rs(A495Sh6#ggU;QyZq zpzq*h{tL5q`mcs}mx{FQ2HStiAav%yFdu>KODcDpcsO&jJxX0dK50O*2ns^DlK6P| z`-N+8;%6M&h7&>~+3YwL>`tfqgFOSA-wcEB6{yUftYna{EV&9!Ven#NQoKM3c+{h?Td`w&Y(z~K zRKvZ0CCHlUW75rr-)#U=J&X_JO-00At9iQ6pT@Adi@NY+n@@KSckT?`9Hf1_6CFw? zSSs&|q9Qd_>dKlqQmt0PWy!e7xS)@}(^}L3 z$I(Dkam{kSTAgEpC&?WZF;Vy%lCK+@HpbzH8vyk7#qseFQWPqZ_laEafYPK8MagDD zwasZ-Qo1v)UVp9uSduMxoZks9P{^DIacF4(Wg>Hw|<)7dahnq2x5J`U0R55z7{~okWn0KH1w5K2wksRB=F|F~wb#2!kx{ zP`ab!5kWhhDapVNTRjeFCJV^#Di!3Az`EFlWuD040z7Hi91l?KsI8(onXq#HD34_s zL1qcA*h(P=UgL=qe#%99#hDUGYGz-o6xVSRMzjZIG_lfRxPPTH8Km1{ao!_%hY0R= zQM{;US8mU=@YfYR&?$%GMqIbyQxc zXPt?XX-z+gE@ADlfvFEjD_Vx&M*(dmJUDqsJHe<#1;7$GRzmP_DEH0cP};=t) z!ESm*@L;QcwLOiqptYw)cz!9_8a`}J0k~Y3&tPAFwmQtS&?Gfe?Xb}sWNB4I>`9&b zpjvc8G=!dll9`skv92G}08i=3k;E}zZl+-(4(X&_^Yk0_OO0(nKgzNo>l|kFEm}DJ>Ddk{ zNQ~4t_Iuq9?-+0c?2<(x?s{V(u=rl~;2T5g)76@!NqTF=pb@e!A#pEN2G3k3^#1C4 z?k&vxbQXe#xmr}FuV%7FU?#0!N>u`J6|Z-jMgZ*J>HGn;sLBtBYyiwAO5NVF0V+(I2X{c1&X~RKKMO5@w6R00sCZA5zo-Vb!TGV%C7qutwu;2ehV25Tf z*8c+n0I={YWdC{Yc9v?1gh&yhyB6feMIuWN7O6Zd~K4uz? zi7OdS(EM6AGS%QA#N9vvVF4sm&c9w)08rx)QLWS54+IGe&c0_)kAO#CRB!g8L6poe z1PBDmd#T7D4H0*J)MepxO6R2@Uza{Wcvb;#>Z@IGeFPye)A)o~@CyBe6p{OwCecgy zc7QN2^?-oThcXcm7}cg_3{4877*MgZwmZ|o~SG%=8LeZD(& zYPk1jJrt;U9-Ioly;w0eqe)wG;OjCTsMwY;kiRJPAy+sy0KyRD_)NeP5}3t6jPl5y z(aiyC{xV8YdE_2#R)*!15gV(KRC)L^7hko$Olz^Qr|*UhvOhgBLJk0MFj~1+nOCORE3?;_hdesa zkO+nURJDrGbl7`hF0cDg?u7p9&X^!#CNVWnbi*nVDFbS3ts*lZvoaq zUWg7}=~SmOulwL>r>TT^5c;0ij(}0=5NJ8BWHQ){OPMke3t=m?To653AeRREfc4|b z$Z!?7W5MQRNNK^ zmW7N6HWz8B5q>Z5Iajd(z7;^9TuIA3TNf?RLM4Zsi?4*Yb+0||wlItAA2 z@(5u@B@83wCL|pH(RoZB;_?5HO%hS3KQPA=Vi(^CJ8(-hH+P8 zhJQgzaO!~o<|@1k_@p&e(4pk zo;gmpv94%Bx%6|gZ_^y8`N?SNqTH~ZA715*Zl=K#SJLOtj|4hlseeTtL*TZUyrL@ zTY?=B2~bvGgo&)MLC+JeenKd5-Q9kt3`sX5rRC_Z5LoBBY%nJpg@#CKi}NFX``ER~fy~%vM(3(-~%+t2#Ft zk$ivMX3gzOtb?G(Z4?od1?`k?dN1dai^~YA!qrY#V=s=8WBKe0zvjs>693GW`=i74 zn&6s3oXcg%s^`z>UZm@!L^h&+EWM4k*9gJ|a?7BN1-LCAddh1@_q6ly_YT5%teK|{ zIAo!8-M`+w_H)dj!Oa5f^UQlw0IpX(*+13J5q&!9oLacDcgNTN(n86+6Ej)07{7R} zBSlKxIvv$AnnmhwQ?uMtzre=0Bm1X~T=5lU|AzhibzuR2)HOb=QK$-ojOhxqjeB)i z!F_2mfwKSn2*yS_^z@y}MpTevux~+<(hCX|YujBn40Uh?GPgG)cz?)c8-ImLMEwY# zF3Triqu_jMSw+^+$6tlhc*az+bmp&y%B1N~g z?XEEj)^Q2G1hskVPZS5wDFpeTY7tvorh-ye=-lwX*J>nlMOA6#r`6v#4(9}fRVcRK zWN-Sbhg7ayPItnxnuJ5@6ma6u)+;smPrlP{!^7gOyBTC$n*xaHa`UM2=+WMEGoM*C zN5kX;XmFiz!B}~1qr9TZt9%aVStO;X#m#$7fV9$zvJA^N`gdGA8w`l31427+l2bcb z;>G_3H1zysH66Kt{XBufdo)Ip1z5S0dVS5)+w*gr_e7WoIQYCQnj;s^O z%7P7`D_!LCj$>hBb^md*(9~9Sc6sfSL7}26l73fu;3Q=i>&eI_Xm6mDB%!58j-q~NgFMWM@YdW`pP0^I-pfBDsk|A!R(|ALMFd-*9@HesC& ze(1(4ygE&?*g^Qwg!vzC5xy^Thzw{%DL zrYZ95VvLy!Dtg=i2s0@cutg;GIC4R=!2!6&ta29e8=9vtqZN z+SJ~^Os+p=NcbP`CV!U^r7T(sRl?oL>q8(-ll`;JJp$sfnw*IPL84#+Lv$UbV7Gm^#2&HwOusSH(Tjo@@8Q zp5qC7Ynim2i;?1z>*R!{KdqUsdv|T&ZtMDi3z6!2Ms6$rU9lh5KAO7yPFlDl6~`dE z=m|CLZic!+WSMi3dM0pix+*w>Fi_sOz;?{E>CqjlkR3=N_r`aiepYk6P_H#x6|30V zZcc|Y3qN023esJ?;iO}ZcicLsEY)x5XLo~H^AMZ{2lAVeER7rv{d|Uw6E~hIr|oW( zpZHlOpL7I&1RRVu@XMLF7qXc)n`mtzLf&{(*s&$4?5tMcAk4Pis{u&!FINc}ubfNd z2o;*yzs&hbS26w)yo*Ywny+r|w(ZPU!#}SVx3|TVk(Hur6d5$p572Y=(Vuy}$jEuw z=j1-@0c7D`ASJSO;yOF0*gI%=DwiFu84Gnpnx!CIG2~;5FttLPr{G^P6k;p=MBrcP z{^vGQ1&%U_{098U{O0)l@3--HOY#4>jW)JM|L+#!Z_3YsNc^tr4`d+ewUw<{qb4J( z{a(aJZ!u9uhlia|)4K^R4LlxreCn^7z)3va`npL`hs%W|`vviqp)8%kF`}r&R904c zDVr?kbbll`A9Vc2{5VoVo#|c=V#v|WAR>aB5FCK*HvKUH|NJmxL7{`S}(Mx0Y6{; zoX-+$-FCMoXm#3Q{^t9S^;nN`x{deO+i%RziJxFDw$R=EiEp!%cQ<-W^>wl82>|`s z137djQJuj}P8#ljhAHR78USB4BQPEC$y&Ia~hcPm#zyjdI%XB}9< zi%wD{t&1yEooynu1wY4qvyqe9Y=O{A6m{xOwW#wM5s(%t1w& z^N(KC^$pEP%)tle>%}_8_Se>X%iY#d+7V#29>$F^65%I6VT{}3acE=Ni8ufi0i<00 zotDpDM{TuT8oT6-hKS~G&=33aaKwtYe@7aCnm zX@bdf^87#z$`?6*DwpR;CGbbU*B<99WB9`1%FT3DTLMSG7KiVT)Bd6^n@xIckM?Ry z8g6ea&d*^;8LD8$r{EYydmn>;m2TA=SSm>I$5MY5Ov;<6j6{zH6scI} zrzcSBWW28Zgx-4-YOxHTH}cBFh>JeuKqD7YkW&AQGTV-jZ+0J12H?^$Ho?l&{%Qa- z=@8k;GHt()Py2`4&)N0;Il~Vqnw_Cf!aQ~0R5vUESlHIPJ=a?p;FF*~a%7LqXrjt# z(Tw~j$g*DvJ2W>dh*%GjonM5pUgpDrFv*zd^h%?pR*~oo7I@%;$K1n z<=Vfn*QY->{6gO(yo5zv!ic&a_LS&-_Kx*6Ky*V4VJ!!25 zpGUOwx;{rtCo=~!jWCJWB`j7#DbqS^H|x%4h1(4c7wcJ9<=7mSwl#pC?yQaOlKmjD zCLPgz{CL{!(6#DUh-^`Q*x`-VY@=RBHdeGkaq8;BRN+7~5Km_uQH(Xow4mNz&>>sfY~nyjE*pX%J5i%PoPg+C`voTs zSbJdiVJ&zOZ#@2bSJMg-3kP~hVSL5=H?UJnIY2ufZBBbdkE@hO2VXr@@Qs6dhq-QpT_2bY{$pAiG%wb8;B)=}uHNp^lQfskA&9U+16)mFnF z^6%1ext@S-`@T>jpo|Yj-o%2kRw+z?zW3r-)+t$-m@yll4mtZrn}FY$mn1 z2sNnV!o8|cWo<|n!mN*U~*xxNLFH=Z$3Hs>(0G0n??y>)ejIu3CDh+|$A;ctHC_|OzEO{nbExLMR~LY1LW zVrJow?~R&tjG?xBLvywfd2RzyK7`+(@e0ZFS_L_}hdeS)Z25c#izYAGn$BdKHMM1- z`Y7kLfwX%_S&Vk{loEHBfZVWZ$9h~DV8JtooELF0a2jF7KDS#3RWS48jmiL#c-7!0 z`gikJNjVKmvb7`UYrt{gOWm!*+#4C^C*le7ok$ql<5$57>7kfzL)|i!DeO8nWz)r<#00Rn?yu>U? zZiFE`KL*~s4PV=LyJTg%Bg)TWn{a}!v9?CBLRQGQ^b)=nE-|m{)EHU9w|*Di{V8&_ zr76k>P3CG)&*Z*_N&Y#7bNSFOB37@%z|a#LFehqXgSMF~E6^IG*ZTD}Hz+1^t~I0Y z*#H1?DFL^ncik57s&{I5GlD(6(XfqMa&{s0MxG8UNWK_*jo}|h&IhR*HqgfXK>Tl{#uG|C-F| zON14&`xY~By=ygh2S8X>Rnkh{cb3r~4MLTRJ@%x4OPR@z9 zfPWlB%v2CZL?1NedwE(N!^>@P?iMixpqn^{`hlcJZEt@sJ?Fsf44M9LJOsR3_t{=F zI+0IAF$y+VooH6WtL~rve+Wzc_J8~NkD(vhX0EAhLJD$5kkZbME75v~IAMIz6Pu4H zdM?hOT{FlmjBY+lAuc-x6@aal2By#%6bWW;m7^0){o}%Ue5P_<70H{_^(N0lcl?4=`Ht{kFg2m)|3C=ZC zb3%;2MtrbUsP!}PJz%G*A#VQoO=Q8)f$PX!OHX?6mouwQK{6lTUH9-j4) zzL{Y+2N=!PI(!t#iskOlZ6wf+*Wu6yve-VD%SWUCqCwW>hVSR&q7rsutCSM_X*wRR zp1csvh((VFm|Fyph?e+W77yfTxwe?+wcwengpddEZd7XjK^=@`3!0 z_ehnqNlfcSJ3YX${ztsS!o1xkm4B9$zpwC}NO>+1qbR$pv_QK=Z@yQ^yOmEExJLu_ zRMcDP}`pO734(ArRXBtnT4l z;m}eLlBZX&;DJ2?+s5eVb|E*1;kd5hX2_XMRGsP;iuqY6dmq}nj01;Xvi8Zf-V?Rn zt0^gXVT{b_pAOzYHpEY{w{!Q?QJw@mhhxU%AYB1|`zpT(hC|g&9jej<@UiPs`p!0a zgL;UR#TjIvFR^uyf%q0OpI;7W`Daw2g>o&pCD5hIF_)Kb*J%6d1b54wn@V@`2%{>E zgYj{FXyyr-t|6K@m{iSBWZglIwiy-)rrvs<7tz;kXQlK+PLN<`B^H4!OY!-iGt;+> zl_g?W%ddAwn}4_1SPc#HG&7Tg1<0r=;%A}V*&O$yOL5iUbWPqzl#9!I{K{b1JKNP! zlj9v$+z$rlCWRO7?b54iK`%pIRUh^H`!W>=-6;g1k+BSa<@&y8j>+g)?n>g_Z5s`^ z6QW_-WC4D-@H=_Tc4qU1epFT4Tm(I+q}M>~&F`YWLzspX&{1_P;xFVS^wc#! zN^F=J$aqaHi&fJE*hR*2(uon~Lx@GwVkGWPp7;;!)VwnHRGSN2Kb7`GD{)dHh=)wv zEBI>VK{8Wo&urmeCd5%nJc{B>xVM;E!L?{B?H9O@ z>&9!yR8acH;(^Fk?Oa@NB(!kR)%B!g7 zUC%xIdiHy?c{Sr6hFzD7Vn2Fn*G=H*AJh_LpJ}##dK4L#i)s~Cknz$Fa5~oVT{0yd z)-*G8HJ(dyqk8jB2DpSydjZO%uS7wm8-&9xX2cS*+87i+Z%L|6@O|sN7aL=u*8}>} zR;LqfHz5R4hH1aiffshLa|Hjn>5y6aSoh)t1y*B4&5dl;74sEseHlj3o|MUQ zGe8#_y-1MaN^tpC#-J5Pkt_!w!W%ejhS#M>zn=b)2@LH_f(+IRYTb^ z_R`ha8Ss2Z9-nmE0>=E8W~o4!h5l8^t+c2xn2#1t5jEa`5f0|Guk@>_>>cKh%h~(* zvz+=sP*u@C{tgvx#C3qLf=}0tLtZ9p<>ZIG@v3u|$LPjsOkIl3rKQ%KrU!!rAe|%i z@}Rj@yCEBNO^F^cpMSC6?Rq}?PXAA9?-(S@_O1Q4ZM%E5Z5yj?+uf^e+qR9>wr#st z+qP}r-si+Q|GoEp@B8VFidj(=Rr%zs8JRPq<{0^V7`qcnGVw6U1GSqeIDavmx}B`> zBYoCsuc)N{8Rd3#x*`i~(m+R9&g7fUWpMY&`3<-dcgdtdg!!Z=mh`qCgkP}Gktu) zFiIi9d56J~-HHo6bbpgMxsQm4uo8F)yKaA6hp&4W(f#hlTsuNfZad?dW8&u$tAQ*M z9d0e^s;pFaz5MOgIp(5z-}J+sS85@0uI%P_SFkBa^cC}gR2ukFSqC`)IW zkE~fQnf%dyXIuZ4#qa2{+q;9Q>c|5`-lyg=HcZb6j@!ha3S1k?MZV|X4gPM1SwtfZo(ri#aQ&_sOQa8 zM>kp2qi3)0%K_s{e5`vsJx)pf;F)%aATg1oWPaT$;fse|xAey)2=qFVU9Ve)6k)YP zi>D+GeStarRdqCnnSRMxNTwLSPUcS)8eITbm3s+)ztBq z+8-vCOa~H)AYoRASo=ZhJDMxmN4Vj3?(9ofSg3QuDu<~HnSZ7xxMC^ViVT(oUopjR zP!iO$s%Xxsr;ay9>|&&3Wa~&tEXyq#s$*2UZ$fPhj2t22ewL%e=Df8_B|LYe` zHuo4)7Y(&C>Yj@D;|PGU{YhFzdyTaZrag;VpAe=dtZ!Lab$|@nFDak;sbuqmq|l^a z^MzY6iAJ+2zDw1iwzHtyZ?{*TlQwPdZ|#(yn$7YLk&fsN0$gM z%-Tl>^jR#7*YggbMCCGWdW5n>vjvBB+K*x}mo{XeJ18=+_KcDa=ERsM6E8VH@P;g$ zz$S;F^Ngv`$zY>$l-%4n?EH{zP6>WNX83=wKtM-C^n^mi7LO-ZgH2b5kGvZ`z(6Kl7zS zMwbHSoA19XiB0ENww*`sxBlr03m?urpo1_O7>Uaohzz9~rYDQUhAIbkm6He`dsNj& z9-3^mWa5FF?}@ZH<>A)0Lbt0938yXM*9B_Ipsng0<%Rtay_ckSoY<_k+$lPxHF3sflgYvWVEHruvvSpy zu>&-bmb=VFP4K2cwF^n5q!b5qUwn|E;<{H80^qi>o@*Yi+ywM9=wWK>{B^-%>Z$WC z8Q-eWo>juB`f<5v$?v68M6JWRZ^umQ@d?t;OHfaqdz@d#g+>yOR;zQqH>q1|h5A>{ z^D``*S*wkSN`v;=M!ceR7D%MmkNUyY@Bh_9%|xpczF=ym*^`<8V9oMu7WE*oj*T_& zmBl}ZQ5v{#vEu@m)=Cv?mDBZBs?11|(h^+v?dYI_S;G5hR)^)EK(c{^a3>itr-P{S zM+mm+rC`0qb~F39mGES|b@=B91RqmWa2z3|=*46z!-npGM!Y;Rl+aWL&g`Mj>W)yOZi`srp)IqNJk=2i_&;rRY)mnmNAp64@91$) z|1619Jl6qCEvdwR(ukkX*+rpIBf|pFIaciGtE(H}5=vzwRtXKHnWd#Qg}OwiYBvf|L5hKIbPCrunH3S@+ZCK{D~b6vmmLmll}FzDT|gb26F;p@ni^#G5rkU#fs(8ied6hr`0Sv*4&I}P z06jZAAnkb~lsr+;J%f65mp3GKw<033Xv(3`Fl=!{;|vY6hwV$A{* z2cGFc=VU7%g{Gg2812@>BtYayW|W!OLrK*)gmL#}ij+ych}%h)Yn%(x2e%l*eVSq< z{2b==c2msQbvjD}C-MSSx%PUo=i*6iVlAp)Q?9;_I{J(Fs-Z4&DDa+Rm^XQ^4a2<8 z0+MpYLS2$gTUAJDBzDY45GoaKn+&5o{Y5-LZ{z7Z-?Fl-GwPZQ7-7PD{ z9&}_1C8`;egz{Sb*X0;A>;7CQdElT@A_+klm#(_HSaw#_hCK?z&RsKp?g!>+Y%_k0 z@S_9WwF11@G8h&(_LkW+q^lXUREOSZ4TmO`g8oUULwZ)PP4^5o2oz36Uf%tsFiwIPh@p_`WhmUs}y75J|QP5_y8Qd?)|c1~wEHRFnFa5iD@JHczg zvwl;5Fj2_J0oP{nPEL-uxI*T)L{{;`Xb8f}*1jQrRvZztVQs5WC#KOw=04PVM5|)j zw}AOyk4psHSq~xuK$^S4t!NYgWdIJ6;@4_E)+TX>7nAZqj_Ab znnyoGv2^GcNYIKr<-fYORrBq7!t+L9WlF(0WA}52I+eSct+oRN=j9lrLzZG1H-3e- z5&jX2r8FQEU#TV`O`QHyri2u3iZ;L+mY~2)c{+XU|_!-Dvjk0p@9tbUW{$# zx|pBXSjOt2WBSrwi%{v>((0DKtPCNFiBB#Mm&8mVO515^63b-$n%GegXI zy(EA?90ZxPI=?N^8PJDdii|qW`@mAZPqscy8TLvQ+94lHfO<1fGzBZrji*Kol{6DM z{&Y?!-lyB@SHdmFxO@Yp-&FhSZ%IN=@Cu_ZfS=C7q|9Q9(^Q6AxRKm^p<)Z^Sk=JN z=cJo%>EM5MCr1%mNCF^$GRy6_@t0*=KbxX^sAK3uxg^Cj+R3!~h3h`L-Bnj!mcXE(VmfSa5&UI!(T9#qp=zF>B!~o~?$#K;94i~ba zsCQ2YQ$I#sy`qQTzB$B+f9cd3I@|Rw5cazbk#AFGxn8=w2<{ z)={s3!V+CXGRJN4)SSoDSjHCBx2N;aQgmWDO%`ornutjl_yO&~hcd+5EUFP=P8q{p zvPzO3l2kRci4?E?gu;JRi0$v_TqGB=S~@n~n#`GVEt8&GWi)-?%wyPj=p_MzeO_)H zSR8>uf6DQ|!2Td=)yF4~MuR*9d3%`0OsyMk+?1%eKROu7W5^3*Sd|%~16}naSO)r5m9_&dmCI=p?AG9k^K?S%1oXI&pGdJF*OoHqLWk=VP1i8WXJHVmXvDn(vhB zZQ6=qI~6vg8GRDMvooq+6ET2y_m({6VRit-1&r!Hz-s)zB663Je|_Sx)b zaVBlL)hroeI~BZ&YEo!v&;NX1DiyTRgx!zfdhH2qDi@UBPE#dp66&YCZjDG=d-NoZ zKoKx&@?5r{EONRYcbL@=)nvE#A4y90e(7l*&SpN9OA3 zVEc#KXB)O6E~h2#A?534oQ{rD&WvM?xi`;{1!xguR+Fkk+b6ym{h^9`@2=YMPMhJa zx_3*}!WMC-{;-^_C56hcGQuhU49J@GPk8(m(dm>0b}~!(Amk=6Ob6_UC>>amFzv;% zt{UED-)*xCws|qZkBh>tERQjYa*cGlpA``sU9pNC`R~bnd|H0+P1Q|8&*0svC#^>e zEo8%&9tXdN&OEpx?@mFb?6@hJhx%=CdA8n%UJh3^B}YzL4|iG)FWMJ4C>AmanhRpM zcgW%UHFphN#D4;}+g!ewOz}-Vlht;;o6Zem;s*t_$)k1cadL7zWD-yvgkiUUjhn*q z%z3U!&Gp>PCK=8ije|YM&-iw3%sWiy+sXxRxJ(Kt&pZ0#G>$CvB&iKiaR_(4PLi18 zq_nnWz{m&@bcKEwGxamfU2mA@aqMIgY~e`r#yyoB3kBAABmIP6{4s_Ey*J)gH@n(!*>Ey^`r_g>pML70-6=M_!NEUMv8|F#dx*k*YSWvq>SJ@MiYDOu z>B4-dJ7u-;YAf%m^;DqMJQ*OA2l-;sH6REz(7_)ES-$o=AF2?GsHp7sXj*8)Th~3L z7;&J5!#7|`uEA%AiaAj8koll_SaC^OUq4?x-*EhL`s4W&{_7&-X-xgEq0v1^Cl40f zL3-yC>&?tIJp7W$Fzkx9NW}a_O(T2pnBj$;{liX#bpKeZm#;qeUa}{lPtjJC^OIHQcpQ@0-|oWGAyC;V6`veC3*hf zgrfE1wa}#6c>3*)>JFpKWl-6}TH}4uLD9-Z0AEfSv=hFB(H^a%x`nb=b=qP6eap;Y zK3`Y;;k;gUnN1xtb^@|al0x}Qn`xNWz1yzQ<219*ru-<&>!76bUN$XLCbk-_Fu&`^ z*D>3$fiB&j?kPs)IevU#y+0~%MswLI$tkbW*Rx%1W*d)BoMnJa7vt%V=l7HKb4#_K zd*l*tqPJtYPci#uj0!S5WFtRc-dtOPKg~L_9WHH_o&e>(yA4qC=gBoBbMKom1OO z+Imd#Sp8QM+KNi{nyD#MP(Sz5$qZXbU~s$vV+U2 z;r=d@4j#r2VyfyRPg>jVJZl#$Dgi;IV#gq@Hg}p-fp8=$ktk`doj#4v7Y*tcb|2+Z zEMc7kIUf3}^~*Z-Eb8iEo-(nW=H$)ikCgCf7dADU=pkN{&C0L#s!-E!_NrMqm7D_6 zYfALayiofD%6?!cvd6WmnL{{g%q}K1dkl6thzo!ptv8@e%DZQy z);tkFxhj3)w$O8^^oYC|TP)Dvc8X1bnvOkKp}pm*=nY_|3)rq;$!n; zxtb3T8sxK5{8>X=DMVI&yaMrEuTbGL&FQT*?Z6-)C#fQ4yH5+#-Uuf%>SaQ9Ii|@) zC|X7xcyW!zNpJgyg97CPiYG9KP5c0}h~jwJVkWQWjyy-&J9ya*O^lj=!D)1CD{(6S z89Qf45-*=svuyQ%k?eYrcBVy9>&4BP@>ewF{RzNZ`~ zNgV|=8@cRcm(w=6q<^}LFF>p3iws!PQ_mVP2z4Uk2IsV&k$WO#{)VNipbnP(Pw-wo zG|X2dl1G4L#jcJu5aH69@0q;VCz>=3C!E35k3?ZAZ-BrzLZ~bGY(JUI7n~6w0O$AR z?F#rK2mCK+%cZrQA?wk^Gwdecpl^82}~L z9vG_>+i&09du`1{u!kl%E=EZT!2}aD#jph5p_3`AJQsK9mYk+O`mB5nycP7t1X6HMT_bb&Do%o`x@T5&2 zOdtmYG~qO%BREqqpF45E?W9nzz5l-wc>(P2IMXq+WhfDU+m%EW_ zl-L8d*y*wrSoAZA2X`E;!kWSSQ|}kHXFZMkk1s7nABgU`jVVvL{;Aen@(qQfQIWcr zXzpzo+#4`gdgH@COmm<`3CR0@G^1b|UBs1qvOp*`{Iqel`zaP6qt0|^=3Aa$a;(~QM5_U!;{{4~ZEb!H^v&GtG^54hhK4CCUssB-$$CLlh<53`fue znk!$)!4TdG4oVehx0uPYz%_}YZ8{8@DJ5DB82SVFxuB;$r|L0aq)27qhZL(xJ*4wD z5Cx3)=?D#1Q#Zl0<@MRj(_sb5jSM*o#!42isz?=HR`qdV|EYYfJY*+oEMC#+Kn19T z&{Qc={vn3A?0a!K4ZdcG>rA)!$>jwgl(AzP$#IQmG$#TVd-w;f6dD!Km;Rx3C-7r$ z?r*K|Q&-D%La?BXMbQiTBae#aM3|yFM9GliDT8ykdkV$j9c_r&<{KNAC;0a{Lfa;J zPY}mxKdx;9&d(>J--1}J;aQ%*Q81+-{fc%=C!&Xj4?5gzl4h<+*gsd^arP7ZB5p$P zHRSmbJNV8_um|1vaGaBxWyZ0-b}u4rP+F0ZU-NvjUroMtS}|t(%|ow7yZPb$08@d? zjnd&NWpe=ZgRpwvjg`!&xS3a;RHSOQ(@n%hmz-3QN8N+vRVB(D$2<{ij^pXKpL;z@(>%m( zCNQ-Bc{oJcHXp##*2NV}T#}LZzILVBKYSV!quPx8HWyH_{qc5f(+~dkXwHfIsS-!V z>O||*>-}4)1~wJ36HY{u$MB7Gn1L2X_0!eIhlvL)xcF%)CMN`5_3`-g()~Sy+A$=j zuv@uD&liowS~`|hP?!a(gtTrqY3^ndeL~6-h}=WRZPsoO1J6FU{q73xInFxry8q(( zQqh_zHC)a-R#QPQx)wn&!~zYVv2x$I;bwRp3J}m+rW*8^hpQ6@0gAZQ4_*pJ{rG!z zNNLUX@!$k3vi7vtMQcei{>JDHU6IB7A}|d(H!p+u6A5x0G88y;-P5uTbCZ6%=LHvT zu1R9Nvb*3%XBmSGtUr_`X&xMaRx3Ime#r7Ye+ff7pzuP_z?A7g*o6xV7%M)>w0&eh zyVlQxsOPg<*4=7cHo|77D4TR%S#dRT0RK}d>oxPBDJSJTShPo$ONm%n|3ecKJH7o~ zV6%+SbT;f_j(FLF)vjoRY3bN!GMTnjB0N^7-NKaBTs7rRoA^F!Q-KA17RVoayPMG$ zoaDc{LKJ*KbZTN`U6zt_2}hLADQb@6bPxA|prwh}prlwso)F_1cB!RWG`?>?Je^eq z#xAo60^Q(ekP(?P_MY^B>N$QLRp06lkzpOOz038dV7hvY+EF1DKE`>MU<|ahbxz&@ zZ|_Li;Q%I?a#1Z=4U*STBb#SRh|9PVFv;Z-!Ar4p-TBJQvq0%*g}`ZuzV4@z!E|)%<@lhP*Jk&^MFXBnK%qG;2@RIxEre90J=ft`{I1W zDAp1^q)QW)?2j4+8$Y`4!+4lS_oN&iw;CL2Dkay+h;$E>meCGdET4!lHq|dM;if^G*_(lEe)xob97ud3`2k@qnUaf2n9d=0C$yjO5L>s@psOD@Q65dxg1OsR z%t=7J-~wXJmy|Kvqa=)nxE-%rs98^Up$lq)iFi2N9{>#&VdeYy^|yw_CxTKJ@OA<_ zmP;rw2qtBe^uAAbH#q+|DCCKNS8{-U^7I}=C`P18z;MKX0wLJEQr!RvZMRp2jrH_-4`y8h`(Jo)+ zo*d)si&V>yj+pS5(rhrA0&)Shi_VW%i5*lt^px*Vn7NRu8EeN^hfKKd%Ao?pp(#pY z0l_At7bS7Q7Y9c1=o!Kz`AvKmV?B31RkWR>P0snQD~%o5ArO*B5A%n`PAE#kWY+z* zviq`690aOcap$5_hU1;#sHr16qsmsb_e%Pe!5`s)+|6$GvB1A5r$+Upx#W}J-zdRq z>qe4(uKBochH5~Mp?lC+USQI|ercY-e829JvG<)!gn;3W{ejv7_Nn5=QEB;l z9hf;T7ZR@`I=~PVaJQ7iX*v^j(x0Zfe^m=H0u+$pD{8JfW&hmIY(rAC8`&g1@<9ek^o3&uNlynFVW>$aTJVw)6QFYJGf(2yUbi^O*R{wv7&1*LOs|7N{-jjReww*Zexn-&7mgS! zyToa0*0Z9C=v+w(Y77A!3DcH9w9||3#Kj?kNdYHSICphGFY(j%u+u}{V(KLBBT4ar z4e|h8vT5inAxIay&_O_uX0RNm+`JT9yNCE_R;znIE71(fAQV%F3IdII^G+>?J*+%g ziMO6w-hdp|IJs+d!+?=2h*Ecr22wl!0&_y_unZhF?%3jNjav;6f6Yoyf9Q#&E%mrW znUsmKlP7ELc(cq!yFqFgI%@wfG1uztOJh`+v1QX*>NU$wuy~VTb?baS2>PX4=4guy zBBX$#d#geN=XIh{t=Ss4{Y~L)rl|#QCjJF%uLN=s#BzsdU(x#ff)cwIAT8A8BHWYt zOSv%?7i@Mcagnhu`CPl=nQ8P1EKQ=({8}F%|Cx^N@PaLChFhKa0@6LzFjd;QT-RmA8D;ba`5KmL zm$_ve=3uY+%;P>#70Y6!hh-$Hh2R~O*VT(?5u03Rc{tasfjZ)zm!fO%g@oid)|57h z3YC9qH7U0_s71)5IwA6os`7OVw`N_#up8Gw?~;vd_XVK3MR5UCpPz6~6tE_l(fnSa z>{SiAj2*Mf#O+-kPHuLxO>7uM)wR&o=7W16&wPs4ujU<8^+6<(<*gkr!f$6*RR)iA zg@-(DJrb|P6I5{MdCT+Bn3R;&>h%i~z}iRW4HQ5Qf$yCgO6DFV*Niz}NoNum7@8=zR#FZL~JpeOaBU>@UaK z2|d*84HIxZ7Q;8;Ju@qW*TR+Lvt*l#CIvGQSOb@Pca>rYQ_4y^GU+5*|x-Bk2&b2Z+= zdL-@sAGray@HL2zBf*0b)btO$7|EuwVLm&<;^sSn3q+!_I6URFQ-x?{LVF$#Ggt`Z z1BPaR@9Fb|#(Ueyto)SBl_bAEc~xfYiks0WkbJ3wAz#{gkPsYL$eyNivB8uMM;^j7 z<6BZ!V+25Fh$lGlt_1KMK`!C)Fxp=ng6I-p@^Z=beXk`GD?=oGqQbZO_rQ^T-8z6E z(@j2|*=;R~gczNJ=_bJ}Iox}4->&$3rhh*I{u=LO0mWD-2WMzI{?hAMeO9or)dSl6 zF6yfiOfo3X51Cg))c1nB#k|6-rtL)Uk?W%hv%w)g7pAeIG8{L~efoWA0f)-x=88&s zClDwH9cCokT8X7Z2#6JM32Mnx&F$Sv6$=&fK$Qw~0J)rpfUBR_RD6PpnlSQ&A1JJK zP9lLcjpB#jQAXYBDqFX|3p*Ykft)~sZX=$o)j^_eZzuKL!oNR=8jX*bOD2I$!DbV2 z!FJkY`l$JNqq679%ai00Eu7|b7d^7{_AZ#hlO`I@6!-q~?BrH4uJwvOfVE!^;8N&d zskQG~OK#$>K=(Eh-~HS35Yc3FY~@vc;#aF->2SCvq>#XaM-x~nrUzW>Mckev9UqJ2 z?`Z!n=0YIEN+!b%60Ud)(+2GkK3f+kYZzDw{YyfXFhDWCi!32+xP}g7kLHcskz_w~ zq|mwMBxrWlKk@PEU?@>M-@~ATx!)1*;rpZ%d@f+cED=7#)EX9ZqiRN}kp?7mu7J;Lo(U?1 zMs;EdO_2(I;=PD_3O=v7Kf1fC)QS@~Qv^P+~6uqJ6b zPDf;rYWJa%__!%MEzut2I!?Yr+HHRHU$KxSZ|Rvr8JDi}pNIw~_Cq`X2}Zvd9C@W~ zUf?sL#;LBf4d6U52L`po zjjO0xnb;#BAQEwN1^cQkASHNsjs2qL%${kVdOI;q4Y_k*K~-*k|COA92rz04tAAH0 zT>BgOrkNl;&OK56AgD5rJZJ1Fw+2?vzLRT#Ga_wS(-Guj#f>;>G?Uw1gqmAyf<8d} zC{0El92Qw>dIOOjr&&0TYN3xF(Dx{b;<}>QVE#eJZbbp85~A-0M#0LpX~#g?*NoZ4 z5lJGD31DsuslT#hF#RoLK$>wnFLZD8E8tK9Or~XmzX^NUbT8aQ2WMtX)UxQ?xrWri zVP(uX_56bbR}8jun;j)m(gzSmQyzi-2I%ysYRmz&)4qlrVv7aW(&a$s~FD<}P3-<^~#XS(9MwA+4W`id(0AS4xZ@P3vSZ;ldlEqMc@x7iHO( z`jVrV{U)?fgD{dEd-M=I9X4qH+EN6I6oR?#fjYmixl@bpUMX*ou!T&_w9@Zfce<19 zn0f3HNfvXFFxs0@G%ksAr}Rc`yajey{!X{YqmIx9vnN8KGKZEdH|?JOmup*Olq8jg zh@AzFg0icy6J?;VeDtl1{vBR0p#3}p=q0dK7gljJa^rBco(KZMQ8Q9_@fGoFDc3cTV-z#rfN`dM8a?kTR3Ds52xADRue2yia zIz)hF%2TjBV*c+O1upqqD8EvwU;RIDnAs$_5;#8LV+PLQoQ%QUd4jkquseS@yAz!T51Bg)HyEL zMgFl^Y<|Q}vWnu)GFO1Dxh)>mJ_yQQByZfF;+J-=6X$3b&yTW;E`tEP(#5fy%ghr- z139vwQy142$EnB?dBoa&JOIL(y;{gY0^q(TO}Z+#Ot#FU%f8L;e89#2w6*ZgejDYZ zB!Z)(yBH673;Vcw<&x8}Zf4dugDxxWJ#1WH6Cm^o%^!fK@%PNdI4Qo6+(z-s3d}3m z;z854B5`n&t(^|y%>#*5OOM+L0*|T8hty#gS@SDEJjN~7@BQMu3zT_U*nS>V^eTu9 zNKwjL3(FO$Xbh+u4v*q*H3c*_%p5*S7#H+@GIhHveA=0~Rg6!i-|J0c=-T(K4ma`E zB;Ofg{}KZ18`L3Sw`QYee<>GEJ{z_^EJmUftCWR{B*lQtR?Vk(J2oc4D$b;?yCoOh zBOLV@o4%R|@AEx81A4mI<;!>fsWOJwe(+n{f=u3cDy4VXRc9R)4Vp_cpNeV&2aEck zo>7{A=E&%oCw?iA2Die|buK0;h^F(R7&zqR4LGKMxc0rS=flyS#tTDs_|^~v-zP)N z2R+9Bc&9I$hONo)-U!;XP#&W)pPEC~8IrtQCAHFIKA*p)@4AnS4B}QV8z-#t+R0E& z+1joazq#djfQFX2bx?R1+1+m0(5)7}xrH6;yCF2o8A^gvg&d&K*mi(g%X1eK#%z2u zGaTX~fNc?@ntjITYYP^F>L2yZ&*g-pyH`F7?M^o}l?E2Uz{4sx>hbh2UOg6o2R=7W z0X32peD4K%i)y-q1pQ$b+6KzrwF83THTF{zR~W18q}-io?5KD7oBL{Eip0J4ki|I7 zqwhEyfhPNEhj`YB_D4{olN5fIv&wQu#I!e|V?8+;6l0(Wr}f~?1i3$f_)P*`JeG(T zbAMO)yjln@{7wWaOe}>~^JrTMa7VHU(~227b?(629VF(`jemwL>B6f7TrBO%F?Nb> z&Io}7#IX@y&C`WPIBfjR>`|9gy~xzD(nOA zt#h1Mp88YYH~GYP4F1{AE+DvBcpDkaz@+KA{E7OLnUUCUIe#`rV?p; z=eXVc$-p0i^w>M0!MY45$KMBT9KrUl3O*miZKwrK>%uepHsTDVECWG ziBg%Zd|B!_HwWm+3ZUFbt3El0xLw7qy^iF4BPZhaS?rsL$zxM6k*Th^W!wF1Z2j#< zdax#t6vR^GP;E?AZT+0)fIR8XV*r1gB5Dw0L^Rmb$xYzp3UR4YT@*_{3hGtzkUoevrfI9D znT36#!}T{6{pfc$7VLn_bzCSt^|>j9;tQFc#dLV%B~jw|uQ-hP8~ou%`xor0MUjfrNJZ1d0}LysU;DG9mQ9$vGIF>ryhVs#r(z5f!O)akeob&tS8&F zgHJ#0lUq1jtEZMJ*J#0q>J9rzbS1V;LX6kKpTvf(_A__b=NcDBmNB9m1=}n27x_i( zyei~N5?7l*wt?GV>Gv}#c8x>;r&g`RLNrH%nPd9YaZE!U=wRSE&bKpmRJP-t6?bQO zw8Hrw0nVFqf^b$QG9N@sY>XssSShOPF%}t3@+0N5DLkVqtOl@s3MXT$u0JH#2gN*r z1exM_Furi`;V2YoMm8T30;rwsBoz&7>NvbyrS0jWEDs#zPfRV3Q_Dw;oaqI|U74Wb z73(VllzTn{X`4HgI9wG+Ufh9p&${sH79SKtWuv4NP^qUEu&VKFh&ahjMvM;#x*b`t zn(J1ddz-xnpbmC0*lwd#n=&z@^R2MIXA8?E;)C(Dm6JG?TXTP|7tU9#lC5AOyv`Of z5Qn5?9yST3WIYbJ81$Y6%s&#yTAxDtQCO@J2(bu{R)UAD9aPxXvQYO#{_Mel3P5aj z2kT$fexZoVYh7D=8&@}7X9ik&x}V0jmNsry#@3E>vPxpY3UtQO zf`T;U3>38PX137RzkVU=>*;OlE$<;8b^9!wZ$R?vAR*iV*x8Gi+dJEtRq4psL7oX{ zcKrgzYl8L&c%?I2aP3O#ZI`mKRt8?On{r1(FLHT(D#&@+s^Zf5WbFFV&lq;P8tWQv zmeqED^10H`%#e#|6`<(KT9u-;;GR$`YGzeteAP&qVqCH9oKo$v%TiM>@BvsS*&=lW z9gu>5J0BeBk(=u4>48L%esHp%9jE*=wZ=Kh_7w{#yY>RV3V5U&fg{MkiA;Y`L#xa-m0dVi;Gg$_IdFXHu}(UZs%nnh>f@W_*wHBYcIy5+a%rg ziQ2p)dNT=ys!K%lXA#Rg_JsB{0UT; z7R}N#M8Vtkb(_ofzUbn(yLRI-Pq z=Cf*nqM*C(t~bEVNU2Ad7DC-1Kp>P{W_yKg|8Pb|G5SoCXY;M4UJn+dG>X8q)}WpJmn#jx>(OR<_?@cH_S+0RG*%xm!32x$oa< z|BW?b{F^2Kfay2$_U}a7|1VXV|K%I~pY)fDIo-aUP62SgK`pfZ(8u^5!oTa=8#~xI z*?)Ii@xQzOUz5ZCweNqoQvGJY{4Ic?|Ec}g(Egu#_jfP5zbse(6WK!lRQQhp>%T|v z@6HT=x!(OJbS3_&@plirf2;f*xc`@n!+&By^`9#L@^tvO(BE;zfARVM6F)WnDfIuM z_5WMy@3_u?9=Njl|CIU%?fBm^f2U^s#rFJ9r2qP-jN!jXpZ`|+JMrf)%GQ5E!{(n# z|9edS7jx_1I)A^6{ly3RPaNC-Q|EtaLI0Nc`#ka2_3l5R+3^4K)lW_m^!ro<0D%8~ OgnUl`Jk5W-`+oopkjA(G literal 0 HcmV?d00001 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] -- 2.16.6