summaryrefslogtreecommitdiffstats
path: root/conductor
diff options
context:
space:
mode:
authorhariharan97 <rh20085046@wipro.com>2021-01-18 16:39:39 +0530
committerhariharan97 <rh20085046@wipro.com>2021-02-08 19:25:52 +0530
commitd9902448f190fe1052d693cbfa7a5cb385613140 (patch)
tree98a04916e4e1636034749ddc89485e916f6780b8 /conductor
parentf4adf00f25425c6fce2b1256fa0de71bd9d5f077 (diff)
"Nst-Selection enhancement"
Issue-ID: OPTFRA-764 Signed-off-by: hariharan97 <rh20085046@wipro.com> Change-Id: Id5d05967e349381bc5769c79b91e4439ea38fc82
Diffstat (limited to 'conductor')
-rw-r--r--conductor/conductor/controller/translator_utils.py4
-rw-r--r--conductor/conductor/data/plugins/inventory_provider/aai.py45
-rw-r--r--conductor/conductor/data/plugins/inventory_provider/candidates/nst_candidate.py29
-rw-r--r--conductor/conductor/data/plugins/inventory_provider/sdc.py188
-rw-r--r--conductor/conductor/data/plugins/inventory_provider/utils/aai_utils.py18
-rw-r--r--conductor/conductor/data/plugins/inventory_provider/utils/csar.py98
-rw-r--r--conductor/conductor/data/service.py2
-rw-r--r--conductor/conductor/solver/service.py2
-rw-r--r--conductor/conductor/tests/unit/data/plugins/inventory_provider/final_nst_candidate.json35
-rw-r--r--conductor/conductor/tests/unit/data/plugins/inventory_provider/model_ver.json43
-rw-r--r--conductor/conductor/tests/unit/data/plugins/inventory_provider/model_ver_response.json8
-rw-r--r--conductor/conductor/tests/unit/data/plugins/inventory_provider/newembbnst.csarbin0 -> 48090 bytes
-rw-r--r--conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_candidate.json28
-rw-r--r--conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_demand_list.json13
-rw-r--r--conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_prop_dict.json16
-rw-r--r--conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_properties.json84
-rw-r--r--conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_response.json59
-rw-r--r--conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai.py60
-rw-r--r--conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai_utils.py8
-rw-r--r--conductor/conductor/tests/unit/data/plugins/inventory_provider/test_sdc.py76
-rw-r--r--conductor/requirements.txt3
-rw-r--r--conductor/test-requirements.txt2
-rw-r--r--conductor/tox.ini2
23 files changed, 817 insertions, 6 deletions
diff --git a/conductor/conductor/controller/translator_utils.py b/conductor/conductor/controller/translator_utils.py
index 17a9f82..e4aa4e1 100644
--- a/conductor/conductor/controller/translator_utils.py
+++ b/conductor/conductor/controller/translator_utils.py
@@ -20,8 +20,8 @@
VERSIONS = {'BASE': ["2016-11-01", "2017-10-10", "2018-02-01"],
'GENERIC': ["2020-08-13"]}
LOCATION_KEYS = ['latitude', 'longitude', 'host_name', 'clli_code']
-INVENTORY_PROVIDERS = ['aai', 'generator']
-INVENTORY_TYPES = ['cloud', 'service', 'transport', 'vfmodule', 'nssi', 'nsi', 'slice_profiles']
+INVENTORY_PROVIDERS = ['aai', 'generator', 'sdc']
+INVENTORY_TYPES = ['cloud', 'service', 'transport', 'vfmodule', 'nssi', 'nsi', 'slice_profiles', 'nst']
DEFAULT_INVENTORY_PROVIDER = INVENTORY_PROVIDERS[0]
CANDIDATE_KEYS = ['candidate_id', 'cost', 'inventory_type', 'location_id',
'location_type']
diff --git a/conductor/conductor/data/plugins/inventory_provider/aai.py b/conductor/conductor/data/plugins/inventory_provider/aai.py
index a87cbb6..6ec15da 100644
--- a/conductor/conductor/data/plugins/inventory_provider/aai.py
+++ b/conductor/conductor/data/plugins/inventory_provider/aai.py
@@ -33,11 +33,13 @@ from conductor.data.plugins import constants
from conductor.data.plugins.inventory_provider import base
from conductor.data.plugins.inventory_provider.candidates.candidate import Candidate
from conductor.data.plugins.inventory_provider.candidates.cloud_candidate import Cloud
+from conductor.data.plugins.inventory_provider.candidates.nst_candidate import NST
from conductor.data.plugins.inventory_provider.candidates.nxi_candidate import NxI
from conductor.data.plugins.inventory_provider.candidates.service_candidate import Service
from conductor.data.plugins.inventory_provider.candidates.transport_candidate import Transport
from conductor.data.plugins.inventory_provider.candidates.vfmodule_candidate import VfModule
from conductor.data.plugins.inventory_provider import hpa_utils
+from conductor.data.plugins.inventory_provider.sdc import SDC
from conductor.data.plugins.inventory_provider.utils import aai_utils
from conductor.data.plugins.triage_translator.triage_translator import TraigeTranslator
from conductor.i18n import _LE
@@ -1662,6 +1664,17 @@ class AAI(base.InventoryProviderBase):
default_attributes,
candidate_uniqueness, inventory_type))
+ elif inventory_type == 'nst':
+ if filtering_attributes:
+ second_level_match = aai_utils.get_first_level_and_second_level_filter(filtering_attributes,
+ "nst")
+ aai_response = self.get_nst_response(filtering_attributes)
+
+ sdc_candidates_list = self.get_nst_candidates(aai_response, second_level_match,
+ default_attributes, candidate_uniqueness,
+ inventory_type)
+ resolved_demands[name].extend(SDC().update_candidates(sdc_candidates_list))
+
else:
LOG.error("Unknown inventory_type "
" {}".format(inventory_type))
@@ -1901,3 +1914,35 @@ class AAI(base.InventoryProviderBase):
candidate = nxi_candidate.convert_nested_dict_to_dict()
candidates.append(candidate)
return candidates
+
+ def get_nst_response(self, filtering_attributes):
+ raw_path = 'service-design-and-creation/models' + aai_utils.add_query_params_and_depth(filtering_attributes,
+ "2")
+ path = self._aai_versioned_path(raw_path)
+ aai_response = self._request('get', path, data=None)
+
+ if aai_response is None or aai_response.status_code != 200:
+ return None
+ if aai_response.json():
+ return aai_response.json()
+
+ def get_nst_candidates(self, response_body, filtering_attributes, default_attributes, candidate_uniqueness,
+ type):
+ candidates = list()
+ if response_body is not None:
+ nst_metadatas = response_body.get("model", [])
+
+ for nst_metadata in nst_metadatas:
+ nst_info = aai_utils.get_nst_info(nst_metadata)
+ model_vers = nst_metadata.get('model-vers').get('model-ver')
+ for model_ver in model_vers:
+ model_version_id = model_ver.get('model-version-id')
+ cost = self.conf.data.nst_candidate_cost
+ info = Candidate.build_candidate_info('aai', type, cost, candidate_uniqueness, model_version_id)
+ model_version_obj = aai_utils.get_model_ver_info(model_ver)
+ model_ver_info = aai_utils.convert_hyphen_to_under_score(model_version_obj)
+ nst_candidate = NST(model_info=nst_info, model_ver=model_ver_info, info=info,
+ default_fields=aai_utils.convert_hyphen_to_under_score(default_attributes),
+ profile_info=None)
+ candidates.append(nst_candidate)
+ return candidates
diff --git a/conductor/conductor/data/plugins/inventory_provider/candidates/nst_candidate.py b/conductor/conductor/data/plugins/inventory_provider/candidates/nst_candidate.py
new file mode 100644
index 0000000..d5ea251
--- /dev/null
+++ b/conductor/conductor/data/plugins/inventory_provider/candidates/nst_candidate.py
@@ -0,0 +1,29 @@
+#
+# -------------------------------------------------------------------------
+# Copyright (C) 2020 Wipro Limited.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# -------------------------------------------------------------------------
+#
+
+from conductor.data.plugins.inventory_provider.candidates.candidate import Candidate
+
+
+class NST(Candidate):
+ def __init__(self, **kwargs):
+ super().__init__(kwargs['info'])
+ self.nst_info = kwargs['model_info']
+ self.model_ver_info = kwargs['model_ver']
+ self.profile_info = kwargs['profile_info']
+ self.other = kwargs['default_fields']
diff --git a/conductor/conductor/data/plugins/inventory_provider/sdc.py b/conductor/conductor/data/plugins/inventory_provider/sdc.py
new file mode 100644
index 0000000..6f3cb4f
--- /dev/null
+++ b/conductor/conductor/data/plugins/inventory_provider/sdc.py
@@ -0,0 +1,188 @@
+#
+# -------------------------------------------------------------------------
+# Copyright (C) 2020 Wipro Limited.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# -------------------------------------------------------------------------
+#
+from conductor.common import rest
+from conductor.data.plugins.inventory_provider.utils import csar
+from conductor.i18n import _LE
+import os
+from oslo_config import cfg
+from oslo_log import log
+import time
+import uuid
+
+
+LOG = log.getLogger(__name__)
+
+CONF = cfg.CONF
+
+SDC_OPTS = [
+ cfg.StrOpt('table_prefix',
+ default='sdc',
+ help='Data Store table prefix.'),
+ cfg.StrOpt('server_url',
+ default='https://controller:8443/sdc',
+ help='Base URL for SDC, up to and not including '
+ 'the version, and without a trailing slash.'),
+ cfg.StrOpt('sdc_rest_timeout',
+ default='30',
+ help='Timeout for SDC Rest Call'),
+ cfg.StrOpt('sdc_retries',
+ default='3',
+ help='Number of retry for SDC Rest Call'),
+ cfg.StrOpt('server_url_version',
+ default='v1',
+ help='The version of SDC in v# format.'),
+ # TODO(larry): follow-up with ONAP people on this (SDC basic auth username and password?)
+ cfg.StrOpt('certificate_file',
+ default='certificate.pem',
+ help='SSL/TLS certificate file in pem format. '
+ 'This certificate must be registered with the A&AI '
+ 'endpoint.'),
+ cfg.StrOpt('certificate_key_file',
+ default='certificate_key.pem',
+ help='Private Certificate Key file in pem format.'),
+ cfg.StrOpt('certificate_authority_bundle_file',
+ default='certificate_authority_bundle.pem',
+ help='Certificate Authority Bundle file in pem format. '
+ 'Must contain the appropriate trust chain for the '
+ 'Certificate file.'),
+ cfg.StrOpt('username',
+ default='',
+ help='Username for SDC.'),
+ cfg.StrOpt('password',
+ default='',
+ help='Password for SDC.'),
+ cfg.StrOpt('temp_path',
+ default=',',
+ help="path to store nst templates")
+]
+
+CONF.register_opts(SDC_OPTS, group='sdc')
+
+
+class SDC(object):
+ """SDC Inventory Provider"""
+
+ def __init__(self):
+ """Initializer"""
+
+ self.conf = CONF
+
+ self.base = self.conf.sdc.server_url.rstrip('/')
+ self.version = self.conf.sdc.server_url_version.rstrip('/')
+ self.cert = self.conf.sdc.certificate_file
+ self.key = self.conf.sdc.certificate_key_file
+ self.verify = self.conf.sdc.certificate_authority_bundle_file
+ self.timeout = self.conf.sdc.sdc_rest_timeout
+ self.retries = self.conf.sdc.sdc_retries
+ self.username = self.conf.sdc.username
+ self.password = self.conf.sdc.password
+ self._init_python_request()
+
+ def initialize(self):
+
+ """Perform any late initialization."""
+ # Initialize the Python requests
+ # self._init_python_request()
+
+ def _sdc_versioned_path(self, path):
+ """Return a URL path with the SDC version prepended"""
+ return '/{}/{}'.format(self.version, path.lstrip('/'))
+
+ def _request(self, method='get', path='/', data=None,
+ context=None, value=None):
+ """Performs HTTP request."""
+ headers = {
+ 'X-FromAppId': 'CONDUCTOR',
+ 'X-TransactionId': str(uuid.uuid4()),
+ }
+ kwargs = {
+ "method": method,
+ "path": path,
+ "headers": headers,
+ "data": data,
+ }
+
+ # TODO(jdandrea): Move timing/response logging into the rest helper?
+ start_time = time.time()
+ response = self.rest.request(**kwargs)
+ elapsed = time.time() - start_time
+ LOG.debug("Total time for SDC request "
+ "({0:}: {1:}): {2:.3f} sec".format(context, value, elapsed))
+
+ if response is None:
+ LOG.error(_LE("No response from SDC ({}: {})").
+ format(context, value))
+ elif response.status_code != 200:
+ LOG.error(_LE("SDC request ({}: {}) returned HTTP "
+ "status {} {}, link: {}{}").
+ format(context, value,
+ response.status_code, response.reason,
+ self.base, path))
+ return response
+
+ def _init_python_request(self):
+
+ kwargs = {
+ "server_url": self.base,
+ "retries": self.retries,
+ "username": self.username,
+ "password": self.password,
+ "read_timeout": self.timeout,
+ }
+ self.rest = rest.REST(**kwargs)
+
+ def update_candidates(self, candidates):
+ absfilepath = self.conf.sdc.temp_path
+ candidateslist = []
+ for candidate in candidates:
+ model_ver_obj = candidate.model_ver_info
+ model_name = model_ver_obj['model_name']
+ self.model_version_id = candidate.candidate_id
+ response = self.get_nst_template(self.model_version_id)
+ filepath = os.path.join(absfilepath, "{}.csar".format(self.model_version_id))
+ if not os.path.exists(absfilepath):
+ os.makedirs(absfilepath)
+ f = open(filepath, "wb")
+ file_res = response.content
+ f.write(file_res)
+ obj = csar.SDCCSAR(filepath, model_name)
+ nst_temp_prop = obj.validate()
+ nst_properties = self.get_nst_prop_dict(nst_temp_prop)
+ candidate.profile_info = nst_properties
+ finalcandidate = candidate.convert_nested_dict_to_dict()
+ candidateslist.append(finalcandidate)
+ return candidateslist
+
+ def get_nst_prop_dict(self, nst_properties):
+ properties_dict = dict()
+ for key in list(nst_properties):
+ temp_dict = nst_properties[key]
+ for temp_key in list(temp_dict):
+ if "default" in temp_key:
+ properties_dict[key] = temp_dict[temp_key]
+ return properties_dict
+
+ def get_nst_template(self, ver_id):
+ raw_path = "/catalog/services/{}/toscaModel".format(ver_id)
+ path = self._sdc_versioned_path(raw_path)
+ sdc_response = self._request('get', path, data=None)
+ if sdc_response is None or sdc_response.status_code != 200:
+ return None
+ if sdc_response:
+ return sdc_response
diff --git a/conductor/conductor/data/plugins/inventory_provider/utils/aai_utils.py b/conductor/conductor/data/plugins/inventory_provider/utils/aai_utils.py
index ee870d4..4c76645 100644
--- a/conductor/conductor/data/plugins/inventory_provider/utils/aai_utils.py
+++ b/conductor/conductor/data/plugins/inventory_provider/utils/aai_utils.py
@@ -19,7 +19,8 @@
QUERY_PARAMS = {'service_instance': ["service-instance-id", "service-instance-name", "environment-context",
"workload-context", "model-invariant-id", "model-version-id", "widget-model-id",
- "widget-model-version", "service-instance-location-id", "orchestration-status"]
+ "widget-model-version", "service-instance-location-id", "orchestration-status"],
+ 'nst': ["model-role"]
}
@@ -79,3 +80,18 @@ def get_instance_info(nxi_instance):
if nxi_instance.get('workload-context'):
nxi_dict['domain'] = nxi_instance.get('workload-context')
return nxi_dict
+
+
+def get_nst_info(nst_instance):
+ nst_dict = {}
+ nst_dict['model_invariant_id'] = nst_instance.get('model-invariant-id')
+ nst_dict['model_type'] = nst_instance.get('model-type')
+ nst_dict['model_role'] = nst_instance.get('model-role')
+ return nst_dict
+
+
+def get_model_ver_info(model_version):
+ for key in list(model_version):
+ if "model-elements" in key:
+ del model_version["model-elements"]
+ return model_version
diff --git a/conductor/conductor/data/plugins/inventory_provider/utils/csar.py b/conductor/conductor/data/plugins/inventory_provider/utils/csar.py
new file mode 100644
index 0000000..0caccac
--- /dev/null
+++ b/conductor/conductor/data/plugins/inventory_provider/utils/csar.py
@@ -0,0 +1,98 @@
+
+
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+import os
+import os.path
+import requests
+from toscaparser.common.exception import ExceptionCollector
+from toscaparser.common.exception import ValidationError
+from toscaparser.prereq.csar import CSAR
+from toscaparser.utils.gettextutils import _
+from toscaparser.utils.urlutils import UrlUtils
+from toscaparser.utils import yamlparser
+import zipfile
+
+
+try: # Python 2.x
+ from BytesIO import BytesIO
+except ImportError: # Python 3.x
+ from io import BytesIO
+
+TOSCA_META = 'TOSCA-Metadata/TOSCA.meta'
+YAML_LOADER = yamlparser.load_yaml
+
+
+class SDCCSAR(CSAR):
+ def __init__(self, csar_file, model_name, a_file=True):
+ super(SDCCSAR, self).__init__(csar_file, a_file=True)
+ self.model_name = model_name
+
+ def validate(self):
+ """Validate the provided CSAR file."""
+ self.is_validated = True
+ # validate that the file or URL exists
+ missing_err_msg = (_('"%s" does not exist.') % self.path)
+ if self.a_file:
+ if not os.path.isfile(self.path):
+ ExceptionCollector.appendException(
+ ValidationError(message=missing_err_msg))
+ return False
+ else:
+ self.csar = self.path
+ else: # a URL
+ if not UrlUtils.validate_url(self.path):
+ ExceptionCollector.appendException(
+ ValidationError(message=missing_err_msg))
+ return False
+ else:
+ response = requests.get(self.path)
+ self.csar = BytesIO(response.content)
+
+ # validate that it is a valid zip file
+ if not zipfile.is_zipfile(self.csar):
+ err_msg = (_('"%s" is not a valid zip file.') % self.path)
+ ExceptionCollector.appendException(
+ ValidationError(message=err_msg))
+ return False
+
+ # validate that it contains the metadata file in the correct location
+ self.zfile = zipfile.ZipFile(self.csar, 'r')
+ filelist = self.zfile.namelist()
+ if TOSCA_META in filelist:
+ self.is_tosca_metadata = True
+ # validate that 'Entry-Definitions' property exists in TOSCA.meta
+ is_validated = self._validate_tosca_meta(filelist)
+ else:
+ self.is_tosca_metadata = False
+ is_validated = self._validate_root_level_yaml(filelist)
+
+ if is_validated:
+ main_tpl = self._read_template_yaml(self.main_template_file_name)
+ nst_properies_res = self.get_nst_properties(main_tpl)
+ print("nst properties", nst_properies_res)
+ return nst_properies_res
+
+ def get_nst_properties(self, main_tpl):
+ importsarr = main_tpl.get('imports')
+ for imports in importsarr:
+ for key in imports:
+ if "service-{}-interface".format(self.model_name) in key:
+ val = imports[key]
+ filename = val.get("file")
+ datanew = self._read_template_yaml("Definitions/" + filename)
+ node_types = datanew.get("node_types")
+ for key in list(node_types):
+ if "org.openecomp" in key:
+ nodedata = node_types[key]
+ nst_properties = nodedata.get("properties")
+ return nst_properties
diff --git a/conductor/conductor/data/service.py b/conductor/conductor/data/service.py
index 743ea8e..69f6945 100644
--- a/conductor/conductor/data/service.py
+++ b/conductor/conductor/data/service.py
@@ -66,6 +66,8 @@ DATA_OPTS = [
default=1.0),
cfg.FloatOpt('nsi_candidate_cost',
default=1.0),
+ cfg.FloatOpt('nst_candidate_cost',
+ default=1.0),
]
CONF.register_opts(DATA_OPTS, group='data')
diff --git a/conductor/conductor/solver/service.py b/conductor/conductor/solver/service.py
index d565d9f..b6ed5dc 100644
--- a/conductor/conductor/solver/service.py
+++ b/conductor/conductor/solver/service.py
@@ -486,7 +486,7 @@ class SolverService(cotyledon.Service):
'aic_version': resource.get("cloud_region_version")},
}
- if rec["candidate"]["inventory_type"] in ["nssi", "nsi", "slice_profiles"]:
+ if rec["candidate"]["inventory_type"] in ["nssi", "nsi", "slice_profiles", "nst"]:
rec["candidate"] = resource
if resource.get('vim-id'):
diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/final_nst_candidate.json b/conductor/conductor/tests/unit/data/plugins/inventory_provider/final_nst_candidate.json
new file mode 100644
index 0000000..ab7aefd
--- /dev/null
+++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/final_nst_candidate.json
@@ -0,0 +1,35 @@
+[{
+ "candidate_id":"5d345ca8-1f8e-4f1e-aac7-6c8b33cc33e7",
+ "inventory_provider":"aai",
+ "inventory_type":"nst",
+ "uniqueness":"True",
+ "cost":1.0,
+
+ "model_invariant_id":"f0aa2f5c-a022-4947-80bf-fc05a1502d82",
+ "model_type":"service",
+ "model_role":"NST",
+
+ "model_version_id":"5d345ca8-1f8e-4f1e-aac7-6c8b33cc33e7",
+ "model_name":"EmbbNst",
+ "model_version":"1.0",
+ "distribution_status":"DISTRIBUTION_COMPLETE_OK",
+ "model_description":"EmbbNst",
+
+ "creation_cost": 1,
+
+ "ueMobilityLevel":"stationary",
+ "skip_post_instantiation_configuration":true,
+ "controller_actor":"SO-REF-DATA",
+ "areaTrafficCapDL":300,
+ "maxNumberofUEs":1000000,
+ "latency":30,
+ "expDataRateUL":300,
+ "availability":0.6,
+ "plmnIdList":"39-00|39-01",
+ "sST":"embb",
+ "areaTrafficCapUL":300,
+ "expDataRateDL":1000,
+ "activityFactor":60,
+ "resourceSharingLevel":"shared"
+
+}] \ No newline at end of file
diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/model_ver.json b/conductor/conductor/tests/unit/data/plugins/inventory_provider/model_ver.json
new file mode 100644
index 0000000..1f1c640
--- /dev/null
+++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/model_ver.json
@@ -0,0 +1,43 @@
+ {
+ "model-version-id":"5d345ca8-1f8e-4f1e-aac7-6c8b33cc33e7",
+ "model-name":"EmbbNst",
+ "model-version":"1.0",
+ "distribution-status":"DISTRIBUTION_COMPLETE_OK",
+ "model-description":"EmbbNst",
+ "resource-version":"1609762782080",
+ "model-elements":{
+ "model-element":[
+ {
+ "model-element-uuid":"5c7f04ca-a3b6-4ef9-9b9e-f887121276b0",
+ "new-data-del-flag":"T",
+ "cardinality":"unbounded",
+ "resource-version":"1609762578458",
+ "relationship-list":{
+ "relationship":[
+ {
+ "related-to":"model-ver",
+ "relationship-label":"org.onap.relationships.inventory.IsA",
+ "related-link":"/aai/v21/service-design-and-creation/models/model/82194af1-3c2c-485a-8f44-420e22a9eaa4/model-vers/model-ver/46b92144-923a-4d20-b85a-3cbd847668a9",
+ "relationship-data":[
+ {
+ "relationship-key":"model.model-invariant-id",
+ "relationship-value":"82194af1-3c2c-485a-8f44-420e22a9eaa4"
+ },
+ {
+ "relationship-key":"model-ver.model-version-id",
+ "relationship-value":"46b92144-923a-4d20-b85a-3cbd847668a9"
+ }
+ ],
+ "related-to-property":[
+ {
+ "property-key":"model-ver.model-name",
+ "property-value":"service-instance"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ } \ No newline at end of file
diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/model_ver_response.json b/conductor/conductor/tests/unit/data/plugins/inventory_provider/model_ver_response.json
new file mode 100644
index 0000000..a3f6167
--- /dev/null
+++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/model_ver_response.json
@@ -0,0 +1,8 @@
+{
+ "model-version-id": "5d345ca8-1f8e-4f1e-aac7-6c8b33cc33e7",
+ "model-name": "EmbbNst",
+ "model-version": "1.0",
+ "distribution-status": "DISTRIBUTION_COMPLETE_OK",
+ "model-description": "EmbbNst",
+ "resource-version": "1609762782080"
+} \ No newline at end of file
diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/newembbnst.csar b/conductor/conductor/tests/unit/data/plugins/inventory_provider/newembbnst.csar
new file mode 100644
index 0000000..d5c4270
--- /dev/null
+++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/newembbnst.csar
Binary files differ
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]