Fix bug 724
[osm/NBI.git] / osm_nbi / descriptor_topics.py
index 6ecf7dc..b63e5d2 100644 (file)
@@ -1,5 +1,18 @@
 # -*- coding: utf-8 -*-
 
+# 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 tarfile
 import yaml
 import json
@@ -9,6 +22,11 @@ from osm_common.dbbase import DbException, deep_update_rfc7396
 from http import HTTPStatus
 from validation import ValidationError, pdu_new_schema, pdu_edit_schema
 from base_topic import BaseTopic, EngineException, get_iterable
+from osm_im.vnfd import vnfd as vnfd_im
+from osm_im.nsd import nsd as nsd_im
+from osm_im.nst import nst as nst_im
+from pyangbind.lib.serialise import pybindJSONDecoder
+import pyangbind.lib.pybindJSON as pybindJSON
 
 __author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
 
@@ -18,48 +36,57 @@ class DescriptorTopic(BaseTopic):
     def __init__(self, db, fs, msg):
         BaseTopic.__init__(self, db, fs, msg)
 
-    def check_conflict_on_edit(self, session, final_content, edit_content, _id, force=False):
-        # check that this id is not present
-        _filter = {"id": final_content["id"]}
-        if _id:
+    def check_conflict_on_edit(self, session, final_content, edit_content, _id):
+        super().check_conflict_on_edit(session, final_content, edit_content, _id)
+        # 1. validate again with pyangbind
+        # 1.1. remove internal keys
+        internal_keys = {}
+        for k in ("_id", "_admin"):
+            if k in final_content:
+                internal_keys[k] = final_content.pop(k)
+        storage_params = internal_keys["_admin"].get("storage")
+        serialized = self._validate_input_new(final_content, storage_params, session["force"])
+        # 1.2. modify final_content with a serialized version
+        final_content.clear()
+        final_content.update(serialized)
+        # 1.3. restore internal keys
+        for k, v in internal_keys.items():
+            final_content[k] = v
+
+        if session["force"]:
+            return
+        # 2. check that this id is not present
+        if "id" in edit_content:
+            _filter = self._get_project_filter(session)
+            _filter["id"] = final_content["id"]
             _filter["_id.neq"] = _id
-
-        _filter.update(self._get_project_filter(session, write=False, show_all=False))
-        if self.db.get_one(self.topic, _filter, fail_on_empty=False):
-            raise EngineException("{} with id '{}' already exists for this project".format(self.topic[:-1],
-                                                                                           final_content["id"]),
-                                  HTTPStatus.CONFLICT)
-        # TODO validate with pyangbind. Load and dumps to convert data types
+            if self.db.get_one(self.topic, _filter, fail_on_empty=False):
+                raise EngineException("{} with id '{}' already exists for this project".format(self.topic[:-1],
+                                                                                               final_content["id"]),
+                                      HTTPStatus.CONFLICT)
 
     @staticmethod
     def format_on_new(content, project_id=None, make_public=False):
         BaseTopic.format_on_new(content, project_id=project_id, make_public=make_public)
         content["_admin"]["onboardingState"] = "CREATED"
         content["_admin"]["operationalState"] = "DISABLED"
-        content["_admin"]["usageSate"] = "NOT_IN_USE"
+        content["_admin"]["usageState"] = "NOT_IN_USE"
 
-    def delete(self, session, _id, force=False, dry_run=False):
+    def delete_extra(self, session, _id, db_content):
         """
-        Delete item by its internal _id
-        :param session: contains the used login username, working project, and admin rights
+        Deletes file system storage associated with the descriptor
+        :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
         :param _id: server internal id
-        :param force: indicates if deletion must be forced in case of conflict
-        :param dry_run: make checking but do not delete
-        :return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ...
+        :param db_content: The database content of the descriptor
+        :return: None if ok or raises EngineException with the problem
         """
-        # TODO add admin to filter, validate rights
-        v = BaseTopic.delete(self, session, _id, force, dry_run=True)
-        if dry_run:
-            return
-        v = self.db.del_one(self.topic, {"_id": _id})
         self.fs.file_delete(_id, ignore_non_exist=True)
-        self._send_msg("delete", {"_id": _id})
-        return v
+        self.fs.file_delete(_id + "_", ignore_non_exist=True)  # remove temp folder
 
     @staticmethod
     def get_one_by_id(db, session, topic, id):
         # find owned by this project
-        _filter = BaseTopic._get_project_filter(session, write=False, show_all=False)
+        _filter = BaseTopic._get_project_filter(session)
         _filter["id"] = id
         desc_list = db.get_list(topic, _filter)
         if len(desc_list) == 1:
@@ -69,7 +96,7 @@ class DescriptorTopic(BaseTopic):
                               HTTPStatus.CONFLICT)
 
         # not found any: try to find public
-        _filter = BaseTopic._get_project_filter(session, write=False, show_all=True)
+        _filter = BaseTopic._get_project_filter(session)
         _filter["id"] = id
         desc_list = db.get_list(topic, _filter)
         if not desc_list:
@@ -80,18 +107,16 @@ class DescriptorTopic(BaseTopic):
             raise DbException("Found more than one public {} with id='{}'; and no one belonging to this project".format(
                 topic[:-1], id), HTTPStatus.CONFLICT)
 
-    def new(self, rollback, session, indata=None, kwargs=None, headers=None, force=False, make_public=False):
+    def new(self, rollback, session, indata=None, kwargs=None, headers=None):
         """
         Creates a new almost empty DISABLED  entry into database. Due to SOL005, it does not follow normal procedure.
         Creating a VNFD or NSD is done in two steps: 1. Creates an empty descriptor (this step) and 2) upload content
         (self.upload_content)
         :param rollback: list to append created items at database in case a rollback may to be done
-        :param session: contains the used login username and working project
+        :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
         :param indata: data to be inserted
         :param kwargs: used to override the indata descriptor
         :param headers: http request headers
-        :param force: If True avoid some dependence checks
-        :param make_public: Make the created descriptor public to all projects
         :return: _id: identity of the inserted data.
         """
 
@@ -105,26 +130,25 @@ class DescriptorTopic(BaseTopic):
             self._update_input_with_kwargs(indata, kwargs)
             # uncomment when this method is implemented.
             # Avoid override in this case as the target is userDefinedData, but not vnfd,nsd descriptors
-            # indata = DescriptorTopic._validate_input_new(self, indata, force=force)
+            # indata = DescriptorTopic._validate_input_new(self, indata, project_id=session["force"])
 
             content = {"_admin": {"userDefinedData": indata}}
-            self.format_on_new(content, session["project_id"], make_public=make_public)
+            self.format_on_new(content, session["project_id"], make_public=session["public"])
             _id = self.db.create(self.topic, content)
             rollback.append({"topic": self.topic, "_id": _id})
             return _id
         except ValidationError as e:
             raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
 
-    def upload_content(self, session, _id, indata, kwargs, headers, force=False):
+    def upload_content(self, session, _id, indata, kwargs, headers):
         """
         Used for receiving content by chunks (with a transaction_id header and/or gzip file. It will store and extract)
-        :param session: session
+        :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
         :param _id : the nsd,vnfd is already created, this is the id
         :param indata: http body request
         :param kwargs: user query string to override parameters. NOT USED
         :param headers:  http request headers
-        :param force: to be more tolerant with validation
-        :return: True package has is completely uploaded or False if partial content has been uplodaed.
+        :return: True if package is completely uploaded or False if partial content has been uploded
             Raise exception on error
         """
         # Check that _id exists and it is valid
@@ -153,18 +177,19 @@ class DescriptorTopic(BaseTopic):
                 total = int(content_range[3])
             else:
                 start = 0
+            temp_folder = _id + "_"  # all the content is upload here and if ok, it is rename from id_ to is folder
 
             if start:
-                if not self.fs.file_exists(_id, 'dir'):
+                if not self.fs.file_exists(temp_folder, 'dir'):
                     raise EngineException("invalid Transaction-Id header", HTTPStatus.NOT_FOUND)
             else:
-                self.fs.file_delete(_id, ignore_non_exist=True)
-                self.fs.mkdir(_id)
+                self.fs.file_delete(temp_folder, ignore_non_exist=True)
+                self.fs.mkdir(temp_folder)
 
             storage = self.fs.get_params()
             storage["folder"] = _id
 
-            file_path = (_id, filename)
+            file_path = (temp_folder, filename)
             if self.fs.file_exists(file_path, 'file'):
                 file_size = self.fs.file_size(file_path)
             else:
@@ -224,8 +249,8 @@ class DescriptorTopic(BaseTopic):
                     raise EngineException("Not found any descriptor file at package descriptor tar.gz")
                 storage["descriptor"] = descriptor_file_name
                 storage["zipfile"] = filename
-                self.fs.file_extract(tar, _id)
-                with self.fs.file_open((_id, descriptor_file_name), "r") as descriptor_file:
+                self.fs.file_extract(tar, temp_folder)
+                with self.fs.file_open((temp_folder, descriptor_file_name), "r") as descriptor_file:
                     content = descriptor_file.read()
             else:
                 content = file_pkg.read()
@@ -248,11 +273,12 @@ class DescriptorTopic(BaseTopic):
             if kwargs:
                 self._update_input_with_kwargs(indata, kwargs)
             # it will call overrides method at VnfdTopic or NsdTopic
-            indata = self._validate_input_new(indata, force=force)
+            # indata = self._validate_input_edit(indata, force=session["force"])
 
             deep_update_rfc7396(current_desc, indata)
-            self.check_conflict_on_edit(session, current_desc, indata, _id=_id, force=force)
+            self.check_conflict_on_edit(session, current_desc, indata, _id=_id)
             self.db.replace(self.topic, _id, current_desc)
+            self.fs.dir_rename(temp_folder, _id)
 
             indata["_id"] = _id
             self._send_msg("created", indata)
@@ -281,7 +307,7 @@ class DescriptorTopic(BaseTopic):
     def get_file(self, session, _id, path=None, accept_header=None):
         """
         Return the file content of a vnfd or nsd
-        :param session: contains the used login username and working project
+        :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
         :param _id: Identity of the vnfd, nsd
         :param path: artifact path or "$DESCRIPTOR" or None
         :param accept_header: Content of Accept header. Must contain applition/zip or/and text/plain
@@ -335,6 +361,34 @@ class DescriptorTopic(BaseTopic):
                                       "future versions", http_code=HTTPStatus.NOT_ACCEPTABLE)
             return self.fs.file_open((storage['folder'], storage['zipfile']), "rb"), accept_zip
 
+    def pyangbind_validation(self, item, data, force=False):
+        try:
+            if item == "vnfds":
+                myvnfd = vnfd_im()
+                pybindJSONDecoder.load_ietf_json({'vnfd:vnfd-catalog': {'vnfd': [data]}}, None, None, obj=myvnfd,
+                                                 path_helper=True, skip_unknown=force)
+                out = pybindJSON.dumps(myvnfd, mode="ietf")
+            elif item == "nsds":
+                mynsd = nsd_im()
+                pybindJSONDecoder.load_ietf_json({'nsd:nsd-catalog': {'nsd': [data]}}, None, None, obj=mynsd,
+                                                 path_helper=True, skip_unknown=force)
+                out = pybindJSON.dumps(mynsd, mode="ietf")
+            elif item == "nsts":
+                mynst = nst_im()
+                pybindJSONDecoder.load_ietf_json({'nst': [data]}, None, None, obj=mynst,
+                                                 path_helper=True, skip_unknown=force)
+                out = pybindJSON.dumps(mynst, mode="ietf")
+            else:
+                raise EngineException("Not possible to validate '{}' item".format(item),
+                                      http_code=HTTPStatus.INTERNAL_SERVER_ERROR)
+
+            desc_out = self._remove_envelop(yaml.safe_load(out))
+            return desc_out
+
+        except Exception as e:
+            raise EngineException("Error in pyangbind validation: {}".format(str(e)),
+                                  http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
+
 
 class VnfdTopic(DescriptorTopic):
     topic = "vnfds"
@@ -354,79 +408,129 @@ class VnfdTopic(DescriptorTopic):
             clean_indata = clean_indata['vnfd-catalog']
         if clean_indata.get('vnfd'):
             if not isinstance(clean_indata['vnfd'], list) or len(clean_indata['vnfd']) != 1:
-                raise EngineException("'vnfd' must be a list only one element")
+                raise EngineException("'vnfd' must be a list of only one element")
             clean_indata = clean_indata['vnfd'][0]
+        elif clean_indata.get('vnfd:vnfd'):
+            if not isinstance(clean_indata['vnfd:vnfd'], list) or len(clean_indata['vnfd:vnfd']) != 1:
+                raise EngineException("'vnfd:vnfd' must be a list of only one element")
+            clean_indata = clean_indata['vnfd:vnfd'][0]
         return clean_indata
 
-    def check_conflict_on_del(self, session, _id, force=False):
+    def check_conflict_on_edit(self, session, final_content, edit_content, _id):
+        super().check_conflict_on_edit(session, final_content, edit_content, _id)
+
+        # set type of vnfd
+        contains_pdu = False
+        contains_vdu = False
+        for vdu in get_iterable(final_content.get("vdu")):
+            if vdu.get("pdu-type"):
+                contains_pdu = True
+            else:
+                contains_vdu = True
+        if contains_pdu:
+            final_content["_admin"]["type"] = "hnfd" if contains_vdu else "pnfd"
+        elif contains_vdu:
+            final_content["_admin"]["type"] = "vnfd"
+        # if neither vud nor pdu do not fill type
+
+    def check_conflict_on_del(self, session, _id, db_content):
         """
         Check that there is not any NSD that uses this VNFD. Only NSDs belonging to this project are considered. Note
         that VNFD can be public and be used by NSD of other projects. Also check there are not deployments, or vnfr
         that uses this vnfd
-        :param session:
-        :param _id: vnfd inernal id
-        :param force: Avoid this checking
+        :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
+        :param _id: vnfd internal id
+        :param db_content: The database content of the _id.
         :return: None or raises EngineException with the conflict
         """
-        if force:
+        if session["force"]:
             return
-        descriptor = self.db.get_one("vnfds", {"_id": _id})
+        descriptor = db_content
         descriptor_id = descriptor.get("id")
         if not descriptor_id:  # empty vnfd not uploaded
             return
 
-        _filter = self._get_project_filter(session, write=False, show_all=False)
+        _filter = self._get_project_filter(session)
+
         # check vnfrs using this vnfd
         _filter["vnfd-id"] = _id
         if self.db.get_list("vnfrs", _filter):
-            raise EngineException("There is some VNFR that depends on this VNFD", http_code=HTTPStatus.CONFLICT)
+            raise EngineException("There is at least one VNF using this descriptor", http_code=HTTPStatus.CONFLICT)
+
+        # check NSD referencing this VNFD
         del _filter["vnfd-id"]
-        # check NSD using this VNFD
         _filter["constituent-vnfd.ANYINDEX.vnfd-id-ref"] = descriptor_id
         if self.db.get_list("nsds", _filter):
-            raise EngineException("There is soame NSD that depends on this VNFD", http_code=HTTPStatus.CONFLICT)
-
-    def _validate_input_new(self, indata, force=False):
-        # TODO validate with pyangbind, serialize
+            raise EngineException("There is at least one NSD referencing this descriptor",
+                                  http_code=HTTPStatus.CONFLICT)
 
+    def _validate_input_new(self, indata, storage_params, force=False):
+        indata = self.pyangbind_validation("vnfds", indata, force)
         # Cross references validation in the descriptor
-        if indata.get("mgmt-interface"):
+        if indata.get("vdu"):
+            if not indata.get("mgmt-interface"):
+                raise EngineException("'mgmt-interface' is a mandatory field and it is not defined",
+                                      http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
             if indata["mgmt-interface"].get("cp"):
                 for cp in get_iterable(indata.get("connection-point")):
                     if cp["name"] == indata["mgmt-interface"]["cp"]:
                         break
                 else:
-                    raise EngineException("mgmt-interface:cp='{} must match an existing connection-point"
-                                          .format(indata["mgmt-interface"].get("cp")),
+                    raise EngineException("mgmt-interface:cp='{}' must match an existing connection-point"
+                                          .format(indata["mgmt-interface"]["cp"]),
                                           http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
-        else:
-            raise EngineException("'mgmt-interface' is a mandatory field and it is not defined",
-                                  http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
 
         for vdu in get_iterable(indata.get("vdu")):
             for interface in get_iterable(vdu.get("interface")):
                 if interface.get("external-connection-point-ref"):
                     for cp in get_iterable(indata.get("connection-point")):
-                        if cp["name"] == interface.get("external-connection-point-ref"):
+                        if cp["name"] == interface["external-connection-point-ref"]:
                             break
                     else:
-                        raise EngineException("vdu[id='{}']:interface[name='{}']:external-connection-point-ref='{} "
+                        raise EngineException("vdu[id='{}']:interface[name='{}']:external-connection-point-ref='{}' "
                                               "must match an existing connection-point"
                                               .format(vdu["id"], interface["name"],
                                                       interface["external-connection-point-ref"]),
                                               http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
-            for interface in get_iterable(vdu.get("interface")):
-                if interface.get("internal-connection-point-ref"):
+
+                elif interface.get("internal-connection-point-ref"):
                     for internal_cp in get_iterable(vdu.get("internal-connection-point")):
-                        if interface.get("internal-connection-point-ref") == internal_cp.get("id"):
+                        if interface["internal-connection-point-ref"] == internal_cp.get("id"):
                             break
                     else:
-                        raise EngineException("vdu[id='{}']:interface[name='{}']:internal-connection-point-ref='{}' is "
-                                              "not associated to an internal-connection-point".format
-                                              (vdu["id"], interface["name"],
-                                               interface["internal-connection-point-ref"]),
+                        raise EngineException("vdu[id='{}']:interface[name='{}']:internal-connection-point-ref='{}' "
+                                              "must match an existing vdu:internal-connection-point"
+                                              .format(vdu["id"], interface["name"],
+                                                      interface["internal-connection-point-ref"]),
                                               http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
+            # Validate that if descriptor contains charms, artifacts _admin.storage."pkg-dir" is not none
+            if vdu.get("vdu-configuration"):
+                if vdu["vdu-configuration"].get("juju"):
+                    if not self._validate_package_folders(storage_params, 'charms'):
+                        raise EngineException("Charm defined in vnf[id={}]:vdu[id={}] but not present in "
+                                              "package".format(indata["id"], vdu["id"]))
+            # Validate that if descriptor contains cloud-init, artifacts _admin.storage."pkg-dir" is not none
+            if vdu.get("cloud-init-file"):
+                if not self._validate_package_folders(storage_params, 'cloud_init', vdu["cloud-init-file"]):
+                    raise EngineException("Cloud-init defined in vnf[id={}]:vdu[id={}] but not present in "
+                                          "package".format(indata["id"], vdu["id"]))
+        # Validate that if descriptor contains charms, artifacts _admin.storage."pkg-dir" is not none
+        if indata.get("vnf-configuration"):
+            if indata["vnf-configuration"].get("juju"):
+                if not self._validate_package_folders(storage_params, 'charms'):
+                    raise EngineException("Charm defined in vnf[id={}] but not present in "
+                                          "package".format(indata["id"]))
+        vld_names = []  # For detection of duplicated VLD names
         for ivld in get_iterable(indata.get("internal-vld")):
+            # BEGIN Detection of duplicated VLD names
+            ivld_name = ivld["name"]
+            if ivld_name in vld_names:
+                raise EngineException("Duplicated VLD name '{}' in vnfd[id={}]:internal-vld[id={}]"
+                                      .format(ivld["name"], indata["id"], ivld["id"]),
+                                      http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
+            else:
+                vld_names.append(ivld_name)
+            # END Detection of duplicated VLD names
             for icp in get_iterable(ivld.get("internal-connection-point")):
                 icp_mark = False
                 for vdu in get_iterable(indata.get("vdu")):
@@ -437,15 +541,15 @@ class VnfdTopic(DescriptorTopic):
                     if icp_mark:
                         break
                 else:
-                    raise EngineException("'internal-vld='{}':internal-connection-point='{}'' must match an existing "
-                                          "internal-connection-point".format(ivld["id"], icp["id-ref"]),
+                    raise EngineException("internal-vld[id='{}']:internal-connection-point='{}' must match an existing "
+                                          "vdu:internal-connection-point".format(ivld["id"], icp["id-ref"]),
                                           http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
             if ivld.get("ip-profile-ref"):
-                for ip_prof in get_iterable(indata["ip-profiles"]):
+                for ip_prof in get_iterable(indata.get("ip-profiles")):
                     if ip_prof["name"] == get_iterable(ivld.get("ip-profile-ref")):
                         break
                 else:
-                    raise EngineException("internal-vld='{}':ip-profile-ref='{}' does not exist".format(
+                    raise EngineException("internal-vld[id='{}']:ip-profile-ref='{}' does not exist".format(
                         ivld["id"], ivld["ip-profile-ref"]),
                         http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
         for mp in get_iterable(indata.get("monitoring-param")):
@@ -453,7 +557,7 @@ class VnfdTopic(DescriptorTopic):
                 mp_vmp_mark = False
                 for vdu in get_iterable(indata.get("vdu")):
                     for vmp in get_iterable(vdu.get("monitoring-param")):
-                        if vmp["id"] == mp["vdu-monitoring-param"]["vdu-monitoring-param-ref"] and vdu["id"] ==\
+                        if vmp["id"] == mp["vdu-monitoring-param"].get("vdu-monitoring-param-ref") and vdu["id"] ==\
                                 mp["vdu-monitoring-param"]["vdu-ref"]:
                             mp_vmp_mark = True
                             break
@@ -461,7 +565,7 @@ class VnfdTopic(DescriptorTopic):
                         break
                 else:
                     raise EngineException("monitoring-param:vdu-monitoring-param:vdu-monitoring-param-ref='{}' not "
-                                          "defined in VDU vdu[id='{}'] or does not exist an VDU with this id"
+                                          "defined at vdu[id='{}'] or vdu does not exist"
                                           .format(mp["vdu-monitoring-param"]["vdu-monitoring-param-ref"],
                                                   mp["vdu-monitoring-param"]["vdu-ref"]),
                                           http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
@@ -477,8 +581,8 @@ class VnfdTopic(DescriptorTopic):
                         if mp_vm_mark:
                             break
                 else:
-                    raise EngineException("monitoring-param:vdu-metric:vdu-metric-name-ref='{}' not defined in VDU "
-                                          "vdu[id='{}'] or or does not exist an VDU with this id"
+                    raise EngineException("monitoring-param:vdu-metric:vdu-metric-name-ref='{}' not defined at "
+                                          "vdu[id='{}'] or vdu does not exist"
                                           .format(mp["vdu-metric"]["vdu-metric-name-ref"],
                                                   mp["vdu-metric"]["vdu-ref"]),
                                           http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
@@ -490,9 +594,9 @@ class VnfdTopic(DescriptorTopic):
                         if mp["id"] == get_iterable(sc.get("vnf-monitoring-param-ref")):
                             break
                     else:
-                        raise EngineException("scaling-group-descriptor:name='{}':scaling-criteria:name='{}':"
-                                              "vnf-monitoring-param-ref='{}' not defined in any 'monitoring-param"
-                                              "['id']'".format(sgd["name"], sc["name"], sc["vnf-monitoring-param-ref"]),
+                        raise EngineException("scaling-group-descriptor[name='{}']:scaling-criteria[name='{}']:"
+                                              "vnf-monitoring-param-ref='{}' not defined in any monitoring-param"
+                                              .format(sgd["name"], sc["name"], sc["vnf-monitoring-param-ref"]),
                                               http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
             for sgd_vdu in get_iterable(sgd.get("vdu")):
                 sgd_vdu_mark = False
@@ -503,34 +607,46 @@ class VnfdTopic(DescriptorTopic):
                 if sgd_vdu_mark:
                     break
             else:
-                raise EngineException("scaling-group-descriptor[name='{}']:vdu not defined and"
-                                      "it is mandatory in any 'scaling-group-descriptor'"
-                                      .format(sgd["name"]),
+                raise EngineException("scaling-group-descriptor[name='{}']:vdu-id-ref={} does not match any vdu"
+                                      .format(sgd["name"], sgd_vdu["vdu-id-ref"]),
                                       http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
             for sca in get_iterable(sgd.get("scaling-config-action")):
-                sca_vcp_mark = False
-                if indata.get("vnf-configuration"):
-                    for primitive in get_iterable(indata["vnf-configuration"].get("config-primitive")):
-                        if primitive["name"] == sca["vnf-config-primitive-name-ref"]:
-                            sca_vcp_mark = True
-                            break
-                    else:
-                        raise EngineException("scaling-group-descriptor:name='{}':scaling-config-action:vnf-config-"
-                                              "primitive-name-ref='{}' not defined in "
-                                              "'vnf-configuration:config-primitive'"
-                                              .format(sgd["name"], sca["vnf-config-primitive-name-ref"]),
-                                              http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
+                if not indata.get("vnf-configuration"):
+                    raise EngineException("'vnf-configuration' not defined in the descriptor but it is referenced by "
+                                          "scaling-group-descriptor[name='{}']:scaling-config-action"
+                                          .format(sgd["name"]),
+                                          http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
+                for primitive in get_iterable(indata["vnf-configuration"].get("config-primitive")):
+                    if primitive["name"] == sca["vnf-config-primitive-name-ref"]:
+                        break
                 else:
-                    raise EngineException("'vnf-configuration' not defined in the descriptor but it is referenced",
+                    raise EngineException("scaling-group-descriptor[name='{}']:scaling-config-action:vnf-config-"
+                                          "primitive-name-ref='{}' does not match any "
+                                          "vnf-configuration:config-primitive:name"
+                                          .format(sgd["name"], sca["vnf-config-primitive-name-ref"]),
                                           http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
-                if sca_vcp_mark:
-                    break
         return indata
 
     def _validate_input_edit(self, indata, force=False):
-        # TODO validate with pyangbind, serialize
+        # not needed to validate with pyangbind becuase it will be validated at check_conflict_on_edit
         return indata
 
+    def _validate_package_folders(self, storage_params, folder, file=None):
+        if not storage_params or not storage_params.get("pkg-dir"):
+            return False
+        else:
+            if self.fs.file_exists("{}_".format(storage_params["folder"]), 'dir'):
+                f = "{}_/{}/{}".format(storage_params["folder"], storage_params["pkg-dir"], folder)
+            else:
+                f = "{}/{}/{}".format(storage_params["folder"], storage_params["pkg-dir"], folder)
+            if file:
+                return self.fs.file_exists("{}/{}".format(f, file), 'file')
+            else:
+                if self.fs.file_exists(f, 'dir'):
+                    if self.fs.dir_ls(f):
+                        return True
+            return False
+
 
 class NsdTopic(DescriptorTopic):
     topic = "nsds"
@@ -551,61 +667,197 @@ class NsdTopic(DescriptorTopic):
             clean_indata = clean_indata['nsd-catalog']
         if clean_indata.get('nsd'):
             if not isinstance(clean_indata['nsd'], list) or len(clean_indata['nsd']) != 1:
-                raise EngineException("'nsd' must be a list only one element")
+                raise EngineException("'nsd' must be a list of only one element")
             clean_indata = clean_indata['nsd'][0]
+        elif clean_indata.get('nsd:nsd'):
+            if not isinstance(clean_indata['nsd:nsd'], list) or len(clean_indata['nsd:nsd']) != 1:
+                raise EngineException("'nsd:nsd' must be a list of only one element")
+            clean_indata = clean_indata['nsd:nsd'][0]
         return clean_indata
 
-    def _validate_input_new(self, indata, force=False):
-        # transform constituent-vnfd:member-vnf-index to string
-        if indata.get("constituent-vnfd"):
-            for constituent_vnfd in indata["constituent-vnfd"]:
-                if "member-vnf-index" in constituent_vnfd:
-                    constituent_vnfd["member-vnf-index"] = str(constituent_vnfd["member-vnf-index"])
-
-        # TODO validate with pyangbind, serialize
+    def _validate_input_new(self, indata, storage_params, force=False):
+        indata = self.pyangbind_validation("nsds", indata, force)
+        # Cross references validation in the descriptor
+        # TODO validata that if contains cloud-init-file or charms, have artifacts _admin.storage."pkg-dir" is not none
+        for vld in get_iterable(indata.get("vld")):
+            if vld.get("mgmt-network") and vld.get("ip-profile-ref"):
+                raise EngineException("Error at vld[id='{}']:ip-profile-ref"
+                                      " You cannot set an ip-profile when mgmt-network is True"
+                                      .format(vld["id"]), http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
+            for vnfd_cp in get_iterable(vld.get("vnfd-connection-point-ref")):
+                for constituent_vnfd in get_iterable(indata.get("constituent-vnfd")):
+                    if vnfd_cp["member-vnf-index-ref"] == constituent_vnfd["member-vnf-index"]:
+                        if vnfd_cp.get("vnfd-id-ref") and vnfd_cp["vnfd-id-ref"] != constituent_vnfd["vnfd-id-ref"]:
+                            raise EngineException("Error at vld[id='{}']:vnfd-connection-point-ref[vnfd-id-ref='{}'] "
+                                                  "does not match constituent-vnfd[member-vnf-index='{}']:vnfd-id-ref"
+                                                  " '{}'".format(vld["id"], vnfd_cp["vnfd-id-ref"],
+                                                                 constituent_vnfd["member-vnf-index"],
+                                                                 constituent_vnfd["vnfd-id-ref"]),
+                                                  http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
+                        break
+                else:
+                    raise EngineException("Error at vld[id='{}']:vnfd-connection-point-ref[member-vnf-index-ref='{}'] "
+                                          "does not match any constituent-vnfd:member-vnf-index"
+                                          .format(vld["id"], vnfd_cp["member-vnf-index-ref"]),
+                                          http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
         return indata
 
     def _validate_input_edit(self, indata, force=False):
-        # TODO validate with pyangbind, serialize
+        # not needed to validate with pyangbind becuase it will be validated at check_conflict_on_edit
         return indata
 
     def _check_descriptor_dependencies(self, session, descriptor):
         """
-        Check that the dependent descriptors exist on a new descriptor or edition
-        :param session: client session information
+        Check that the dependent descriptors exist on a new descriptor or edition. Also checks references to vnfd
+        connection points are ok
+        :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
         :param descriptor: descriptor to be inserted or edit
         :return: None or raises exception
         """
-        if not descriptor.get("constituent-vnfd"):
+        if session["force"]:
             return
-        for vnf in descriptor["constituent-vnfd"]:
-            vnfd_id = vnf["vnfd-id-ref"]
-            filter_q = self._get_project_filter(session, write=False, show_all=True)
-            filter_q["id"] = vnfd_id
-            if not self.db.get_list("vnfds", filter_q):
-                raise EngineException("Descriptor error at 'constituent-vnfd':'vnfd-id-ref'='{}' references a non "
-                                      "existing vnfd".format(vnfd_id), http_code=HTTPStatus.CONFLICT)
+        member_vnfd_index = {}
+        if descriptor.get("constituent-vnfd") and not session["force"]:
+            for vnf in descriptor["constituent-vnfd"]:
+                vnfd_id = vnf["vnfd-id-ref"]
+                filter_q = self._get_project_filter(session)
+                filter_q["id"] = vnfd_id
+                vnf_list = self.db.get_list("vnfds", filter_q)
+                if not vnf_list:
+                    raise EngineException("Descriptor error at 'constituent-vnfd':'vnfd-id-ref'='{}' references a non "
+                                          "existing vnfd".format(vnfd_id), http_code=HTTPStatus.CONFLICT)
+                # elif len(vnf_list) > 1:
+                #     raise EngineException("More than one vnfd found for id='{}'".format(vnfd_id),
+                #                           http_code=HTTPStatus.CONFLICT)
+                member_vnfd_index[vnf["member-vnf-index"]] = vnf_list[0]
+
+        # Cross references validation in the descriptor and vnfd connection point validation
+        for vld in get_iterable(descriptor.get("vld")):
+            for referenced_vnfd_cp in get_iterable(vld.get("vnfd-connection-point-ref")):
+                # look if this vnfd contains this connection point
+                vnfd = member_vnfd_index.get(referenced_vnfd_cp["member-vnf-index-ref"])
+                if not vnfd:
+                    raise EngineException("Error at vld[id='{}']:vnfd-connection-point-ref[member-vnf-index-ref='{}'] "
+                                          "does not match any constituent-vnfd:member-vnf-index"
+                                          .format(vld["id"], referenced_vnfd_cp["member-vnf-index-ref"]),
+                                          http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
+                for vnfd_cp in get_iterable(vnfd.get("connection-point")):
+                    if referenced_vnfd_cp.get("vnfd-connection-point-ref") == vnfd_cp["name"]:
+                        break
+                else:
+                    raise EngineException(
+                        "Error at vld[id='{}']:vnfd-connection-point-ref[member-vnf-index-ref='{}']:vnfd-"
+                        "connection-point-ref='{}' references a non existing conection-point:name inside vnfd '{}'"
+                        .format(vld["id"], referenced_vnfd_cp["member-vnf-index-ref"],
+                                referenced_vnfd_cp["vnfd-connection-point-ref"], vnfd["id"]),
+                        http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
 
-    def check_conflict_on_edit(self, session, final_content, edit_content, _id, force=False):
-        super().check_conflict_on_edit(session, final_content, edit_content, _id, force=force)
+    def check_conflict_on_edit(self, session, final_content, edit_content, _id):
+        super().check_conflict_on_edit(session, final_content, edit_content, _id)
 
         self._check_descriptor_dependencies(session, final_content)
 
-    def check_conflict_on_del(self, session, _id, force=False):
+    def check_conflict_on_del(self, session, _id, db_content):
         """
         Check that there is not any NSR that uses this NSD. Only NSRs belonging to this project are considered. Note
         that NSD can be public and be used by other projects.
-        :param session:
-        :param _id: vnfd inernal id
-        :param force: Avoid this checking
+        :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
+        :param _id: nsd internal id
+        :param db_content: The database content of the _id
         :return: None or raises EngineException with the conflict
         """
-        if force:
+        if session["force"]:
+            return
+        descriptor = db_content
+        descriptor_id = descriptor.get("id")
+        if not descriptor_id:  # empty nsd not uploaded
             return
-        _filter = self._get_project_filter(session, write=False, show_all=False)
-        _filter["nsdId"] = _id
+
+        # check NSD used by NS
+        _filter = self._get_project_filter(session)
+        _filter["nsd-id"] = _id
         if self.db.get_list("nsrs", _filter):
-            raise EngineException("There is some NSR that depends on this NSD", http_code=HTTPStatus.CONFLICT)
+            raise EngineException("There is at least one NS using this descriptor", http_code=HTTPStatus.CONFLICT)
+
+        # check NSD referenced by NST
+        del _filter["nsd-id"]
+        _filter["netslice-subnet.ANYINDEX.nsd-ref"] = descriptor_id
+        if self.db.get_list("nsts", _filter):
+            raise EngineException("There is at least one NetSlice Template referencing this descriptor",
+                                  http_code=HTTPStatus.CONFLICT)
+
+
+class NstTopic(DescriptorTopic):
+    topic = "nsts"
+    topic_msg = "nst"
+
+    def __init__(self, db, fs, msg):
+        DescriptorTopic.__init__(self, db, fs, msg)
+
+    @staticmethod
+    def _remove_envelop(indata=None):
+        if not indata:
+            return {}
+        clean_indata = indata
+
+        if clean_indata.get('nst'):
+            if not isinstance(clean_indata['nst'], list) or len(clean_indata['nst']) != 1:
+                raise EngineException("'nst' must be a list only one element")
+            clean_indata = clean_indata['nst'][0]
+        elif clean_indata.get('nst:nst'):
+            if not isinstance(clean_indata['nst:nst'], list) or len(clean_indata['nst:nst']) != 1:
+                raise EngineException("'nst:nst' must be a list only one element")
+            clean_indata = clean_indata['nst:nst'][0]
+        return clean_indata
+
+    def _validate_input_edit(self, indata, force=False):
+        # TODO validate with pyangbind, serialize
+        return indata
+
+    def _validate_input_new(self, indata, storage_params, force=False):
+        indata = self.pyangbind_validation("nsts", indata, force)
+        return indata.copy()
+
+    def _check_descriptor_dependencies(self, session, descriptor):
+        """
+        Check that the dependent descriptors exist on a new descriptor or edition
+        :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
+        :param descriptor: descriptor to be inserted or edit
+        :return: None or raises exception
+        """
+        if not descriptor.get("netslice-subnet"):
+            return
+        for nsd in descriptor["netslice-subnet"]:
+            nsd_id = nsd["nsd-ref"]
+            filter_q = self._get_project_filter(session)
+            filter_q["id"] = nsd_id
+            if not self.db.get_list("nsds", filter_q):
+                raise EngineException("Descriptor error at 'netslice-subnet':'nsd-ref'='{}' references a non "
+                                      "existing nsd".format(nsd_id), http_code=HTTPStatus.CONFLICT)
+
+    def check_conflict_on_edit(self, session, final_content, edit_content, _id):
+        super().check_conflict_on_edit(session, final_content, edit_content, _id)
+
+        self._check_descriptor_dependencies(session, final_content)
+
+    def check_conflict_on_del(self, session, _id, db_content):
+        """
+        Check that there is not any NSIR that uses this NST. Only NSIRs belonging to this project are considered. Note
+        that NST can be public and be used by other projects.
+        :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
+        :param _id: nst internal id
+        :param db_content: The database content of the _id.
+        :return: None or raises EngineException with the conflict
+        """
+        # TODO: Check this method
+        if session["force"]:
+            return
+        # Get Network Slice Template from Database
+        _filter = self._get_project_filter(session)
+        _filter["nst-id"] = _id
+        if self.db.get_list("nsis", _filter):
+            raise EngineException("there is at least one Netslice Instance using this descriptor",
+                                  http_code=HTTPStatus.CONFLICT)
 
 
 class PduTopic(BaseTopic):
@@ -619,15 +871,23 @@ class PduTopic(BaseTopic):
 
     @staticmethod
     def format_on_new(content, project_id=None, make_public=False):
-        BaseTopic.format_on_new(content, project_id=None, make_public=make_public)
+        BaseTopic.format_on_new(content, project_id=project_id, make_public=make_public)
         content["_admin"]["onboardingState"] = "CREATED"
-        content["_admin"]["operationalState"] = "DISABLED"
-        content["_admin"]["usageSate"] = "NOT_IN_USE"
+        content["_admin"]["operationalState"] = "ENABLED"
+        content["_admin"]["usageState"] = "NOT_IN_USE"
 
-    def check_conflict_on_del(self, session, _id, force=False):
-        if force:
+    def check_conflict_on_del(self, session, _id, db_content):
+        """
+        Check that there is not any vnfr that uses this PDU
+        :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
+        :param _id: pdu internal id
+        :param db_content: The database content of the _id.
+        :return: None or raises EngineException with the conflict
+        """
+        if session["force"]:
             return
-        # TODO Is it needed to check descriptors _admin.project_read/project_write??
-        _filter = {"vdur.pdu-id": _id}
+
+        _filter = self._get_project_filter(session)
+        _filter["vdur.pdu-id"] = _id
         if self.db.get_list("vnfrs", _filter):
-            raise EngineException("There is some NSR that uses this PDU", http_code=HTTPStatus.CONFLICT)
+            raise EngineException("There is at least one VNF using this PDU", http_code=HTTPStatus.CONFLICT)