"Nst-Selection enhancement" 46/116946/4
authorhariharan97 <rh20085046@wipro.com>
Mon, 18 Jan 2021 11:09:39 +0000 (16:39 +0530)
committerhariharan97 <rh20085046@wipro.com>
Mon, 8 Feb 2021 13:55:52 +0000 (19:25 +0530)
Issue-ID: OPTFRA-764
Signed-off-by: hariharan97 <rh20085046@wipro.com>
Change-Id: Id5d05967e349381bc5769c79b91e4439ea38fc82

24 files changed:
conductor.conf
conductor/conductor/controller/translator_utils.py
conductor/conductor/data/plugins/inventory_provider/aai.py
conductor/conductor/data/plugins/inventory_provider/candidates/nst_candidate.py [new file with mode: 0644]
conductor/conductor/data/plugins/inventory_provider/sdc.py [new file with mode: 0644]
conductor/conductor/data/plugins/inventory_provider/utils/aai_utils.py
conductor/conductor/data/plugins/inventory_provider/utils/csar.py [new file with mode: 0644]
conductor/conductor/data/service.py
conductor/conductor/solver/service.py
conductor/conductor/tests/unit/data/plugins/inventory_provider/final_nst_candidate.json [new file with mode: 0644]
conductor/conductor/tests/unit/data/plugins/inventory_provider/model_ver.json [new file with mode: 0644]
conductor/conductor/tests/unit/data/plugins/inventory_provider/model_ver_response.json [new file with mode: 0644]
conductor/conductor/tests/unit/data/plugins/inventory_provider/newembbnst.csar [new file with mode: 0644]
conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_candidate.json [new file with mode: 0644]
conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_demand_list.json [new file with mode: 0644]
conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_prop_dict.json [new file with mode: 0644]
conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_properties.json [new file with mode: 0644]
conductor/conductor/tests/unit/data/plugins/inventory_provider/nst_response.json [new file with mode: 0644]
conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai.py
conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai_utils.py
conductor/conductor/tests/unit/data/plugins/inventory_provider/test_sdc.py [new file with mode: 0644]
conductor/requirements.txt
conductor/test-requirements.txt
conductor/tox.ini

index e2e2c66..c6beec3 100755 (executable)
@@ -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"
index 17a9f82..e4aa4e1 100644 (file)
@@ -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']
index a87cbb6..6ec15da 100644 (file)
@@ -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 (file)
index 0000000..d5ea251
--- /dev/null
@@ -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 (file)
index 0000000..6f3cb4f
--- /dev/null
@@ -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
index ee870d4..4c76645 100644 (file)
@@ -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 (file)
index 0000000..0caccac
--- /dev/null
@@ -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
index 743ea8e..69f6945 100644 (file)
@@ -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')
index d565d9f..b6ed5dc 100644 (file)
@@ -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 (file)
index 0000000..ab7aefd
--- /dev/null
@@ -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 (file)
index 0000000..1f1c640
--- /dev/null
@@ -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 (file)
index 0000000..a3f6167
--- /dev/null
@@ -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 (file)
index 0000000..d5c4270
Binary files /dev/null and b/conductor/conductor/tests/unit/data/plugins/inventory_provider/newembbnst.csar 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 (file)
index 0000000..f6e9dde
--- /dev/null
@@ -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 (file)
index 0000000..5b5d63c
--- /dev/null
@@ -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 (file)
index 0000000..1761655
--- /dev/null
@@ -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 (file)
index 0000000..05cca18
--- /dev/null
@@ -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 (file)
index 0000000..2c075e3
--- /dev/null
@@ -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
index 484806e..54789f2 100644 (file)
@@ -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))
index 23b6640..54da12e 100644 (file)
@@ -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 (file)
index 0000000..054fabe
--- /dev/null
@@ -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"))
+
+
+
+
+
index 62630cb..0627288 100644 (file)
@@ -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
index a44bdd5..993837b 100644 (file)
@@ -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
+
index bd9de98..c6cc39f 100644 (file)
@@ -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]