Feature 8178 VNF Repositories
[osm/NBI.git] / osm_nbi / descriptor_topics.py
index eef2ca4..c1c1f63 100644 (file)
@@ -20,8 +20,12 @@ import json
 from hashlib import md5
 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 time import time
+from uuid import uuid4
+from re import fullmatch
+from osm_nbi.validation import ValidationError, pdu_new_schema, pdu_edit_schema, \
+    validate_input, vnfpkgop_new_schema
+from osm_nbi.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
@@ -33,10 +37,32 @@ __author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
 
 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):
+    def __init__(self, db, fs, msg, auth):
+        BaseTopic.__init__(self, db, fs, msg, auth)
+
+    def check_conflict_on_edit(self, session, final_content, edit_content, _id):
+        super().check_conflict_on_edit(session, final_content, edit_content, _id)
+
+        def _check_unique_id_name(descriptor, position=""):
+            for desc_key, desc_item in descriptor.items():
+                if isinstance(desc_item, list) and desc_item:
+                    used_ids = []
+                    desc_item_id = None
+                    for index, list_item in enumerate(desc_item):
+                        if isinstance(list_item, dict):
+                            _check_unique_id_name(list_item, "{}.{}[{}]"
+                                                  .format(position, desc_key, index))
+                            # Base case
+                            if index == 0 and (list_item.get("id") or list_item.get("name")):
+                                desc_item_id = "id" if list_item.get("id") else "name"
+                            if desc_item_id and list_item.get(desc_item_id):
+                                if list_item[desc_item_id] in used_ids:
+                                    position = "{}.{}[{}]".format(position, desc_key, index)
+                                    raise EngineException("Error: identifier {} '{}' is not unique and repeats at '{}'"
+                                                          .format(desc_item_id, list_item[desc_item_id],
+                                                                  position), HTTPStatus.UNPROCESSABLE_ENTITY)
+                                used_ids.append(list_item[desc_item_id])
+        _check_unique_id_name(final_content)
         # 1. validate again with pyangbind
         # 1.1. remove internal keys
         internal_keys = {}
@@ -44,7 +70,7 @@ class DescriptorTopic(BaseTopic):
             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, force)
+        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)
@@ -52,11 +78,11 @@ class DescriptorTopic(BaseTopic):
         for k, v in internal_keys.items():
             final_content[k] = v
 
-        if force:
+        if session["force"]:
             return
         # 2. check that this id is not present
         if "id" in edit_content:
-            _filter = self._get_project_filter(session, write=False, show_all=False)
+            _filter = self._get_project_filter(session)
             _filter["id"] = final_content["id"]
             _filter["_id.neq"] = _id
             if self.db.get_one(self.topic, _filter, fail_on_empty=False):
@@ -71,29 +97,22 @@ class DescriptorTopic(BaseTopic):
         content["_admin"]["operationalState"] = "DISABLED"
         content["_admin"]["usageState"] = "NOT_IN_USE"
 
-    def delete(self, session, _id, force=False, dry_run=False):
+    def delete_extra(self, session, _id, db_content, not_send_msg=None):
         """
-        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
+        :param not_send_msg: To not send message (False) or store content (list) instead
+        :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.fs.file_delete(_id + "_", ignore_non_exist=True)  # remove temp folder
-        self._send_msg("delete", {"_id": _id})
-        return v
 
     @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:
@@ -103,7 +122,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:
@@ -114,22 +133,23 @@ 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.
+        :return: _id, None: identity of the inserted data; and None as there is not any operation
         """
 
         try:
+            # Check Quota
+            self.check_quota(session)
+
             # _remove_envelop
             if indata:
                 if "userDefinedData" in indata:
@@ -139,25 +159,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
+            self._send_msg("created", {"_id": _id})
+            return _id, None
         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 if package is completely uploaded or False if partial content has been uploded
             Raise exception on error
         """
@@ -271,7 +291,7 @@ class DescriptorTopic(BaseTopic):
                 indata = json.load(content)
             else:
                 error_text = "Invalid yaml format "
-                indata = yaml.load(content)
+                indata = yaml.load(content, Loader=yaml.SafeLoader)
 
             current_desc["_admin"]["storage"] = storage
             current_desc["_admin"]["onboardingState"] = "ONBOARDED"
@@ -283,15 +303,16 @@ 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_edit(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)
+            current_desc["_admin"]["modified"] = time()
             self.db.replace(self.topic, _id, current_desc)
             self.fs.dir_rename(temp_folder, _id)
 
             indata["_id"] = _id
-            self._send_msg("created", indata)
+            self._send_msg("edited", indata)
 
             # TODO if descriptor has changed because kwargs update content and remove cached zip
             # TODO if zip is not present creates one
@@ -317,7 +338,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
@@ -404,8 +425,8 @@ class VnfdTopic(DescriptorTopic):
     topic = "vnfds"
     topic_msg = "vnfd"
 
-    def __init__(self, db, fs, msg):
-        DescriptorTopic.__init__(self, db, fs, msg)
+    def __init__(self, db, fs, msg, auth):
+        DescriptorTopic.__init__(self, db, fs, msg, auth)
 
     @staticmethod
     def _remove_envelop(indata=None):
@@ -426,8 +447,8 @@ class VnfdTopic(DescriptorTopic):
             clean_indata = clean_indata['vnfd:vnfd'][0]
         return clean_indata
 
-    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)
 
         # set type of vnfd
         contains_pdu = False
@@ -443,33 +464,36 @@ class VnfdTopic(DescriptorTopic):
             final_content["_admin"]["type"] = "vnfd"
         # if neither vud nor pdu do not fill type
 
-    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 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)
+            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)
@@ -488,8 +512,17 @@ class VnfdTopic(DescriptorTopic):
                                           http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
 
         for vdu in get_iterable(indata.get("vdu")):
+            icp_refs = []
+            ecp_refs = []
             for interface in get_iterable(vdu.get("interface")):
                 if interface.get("external-connection-point-ref"):
+                    if interface.get("external-connection-point-ref") in ecp_refs:
+                        raise EngineException("vdu[id='{}']:interface[name='{}']:external-connection-point-ref='{}' "
+                                              "is referenced by other interface"
+                                              .format(vdu["id"], interface["name"],
+                                                      interface["external-connection-point-ref"]),
+                                              http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
+                    ecp_refs.append(interface.get("external-connection-point-ref"))
                     for cp in get_iterable(indata.get("connection-point")):
                         if cp["name"] == interface["external-connection-point-ref"]:
                             break
@@ -499,8 +532,14 @@ class VnfdTopic(DescriptorTopic):
                                               .format(vdu["id"], interface["name"],
                                                       interface["external-connection-point-ref"]),
                                               http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
-
                 elif interface.get("internal-connection-point-ref"):
+                    if interface.get("internal-connection-point-ref") in icp_refs:
+                        raise EngineException("vdu[id='{}']:interface[name='{}']:internal-connection-point-ref='{}' "
+                                              "is referenced by other interface"
+                                              .format(vdu["id"], interface["name"],
+                                                      interface["internal-connection-point-ref"]),
+                                              http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
+                    icp_refs.append(interface.get("internal-connection-point-ref"))
                     for internal_cp in get_iterable(vdu.get("internal-connection-point")):
                         if interface["internal-connection-point-ref"] == internal_cp.get("id"):
                             break
@@ -527,7 +566,17 @@ class VnfdTopic(DescriptorTopic):
                 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")):
@@ -644,13 +693,26 @@ class VnfdTopic(DescriptorTopic):
                         return True
             return False
 
+    def delete_extra(self, session, _id, db_content, not_send_msg=None):
+        """
+        Deletes associate file system storage (via super)
+        Deletes associated vnfpkgops from database.
+        :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
+        :param _id: server internal id
+        :param db_content: The database content of the descriptor
+        :return: None
+        :raises: FsException in case of error while deleting associated storage
+        """
+        super().delete_extra(session, _id, db_content, not_send_msg)
+        self.db.del_list("vnfpkgops", {"vnfPkgId": _id})
+
 
 class NsdTopic(DescriptorTopic):
     topic = "nsds"
     topic_msg = "nsd"
 
-    def __init__(self, db, fs, msg):
-        DescriptorTopic.__init__(self, db, fs, msg)
+    def __init__(self, db, fs, msg, auth):
+        DescriptorTopic.__init__(self, db, fs, msg, auth)
 
     @staticmethod
     def _remove_envelop(indata=None):
@@ -697,28 +759,40 @@ class NsdTopic(DescriptorTopic):
                                           "does not match any constituent-vnfd:member-vnf-index"
                                           .format(vld["id"], vnfd_cp["member-vnf-index-ref"]),
                                           http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
+        # Check VNFFGD
+        for fgd in get_iterable(indata.get("vnffgd")):
+            for cls in get_iterable(fgd.get("classifier")):
+                rspref = cls.get("rsp-id-ref")
+                for rsp in get_iterable(fgd.get("rsp")):
+                    rspid = rsp.get("id")
+                    if rspid and rspref and rspid == rspref:
+                        break
+                else:
+                    raise EngineException(
+                        "Error at vnffgd[id='{}']:classifier[id='{}']:rsp-id-ref '{}' does not match any rsp:id"
+                        .format(fgd["id"], cls["id"], rspref),
+                        http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
         return indata
 
     def _validate_input_edit(self, indata, force=False):
         # 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, force=False):
+    def _check_descriptor_dependencies(self, session, descriptor):
         """
         Check that the dependent descriptors exist on a new descriptor or edition. Also checks references to vnfd
         connection points are ok
-        :param session: client session information
+        :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
         :param descriptor: descriptor to be inserted or edit
-        :param force: if true skip dependencies checking
         :return: None or raises exception
         """
-        if force:
+        if session["force"]:
             return
         member_vnfd_index = {}
-        if descriptor.get("constituent-vnfd") and not force:
+        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, write=False, show_all=True)
+                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:
@@ -734,11 +808,6 @@ class NsdTopic(DescriptorTopic):
             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
@@ -750,34 +819,47 @@ class NsdTopic(DescriptorTopic):
                                 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, force)
+        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)
+    def __init__(self, db, fs, msg, auth):
+        DescriptorTopic.__init__(self, db, fs, msg, auth)
 
     @staticmethod
     def _remove_envelop(indata=None):
@@ -806,7 +888,7 @@ class NstTopic(DescriptorTopic):
     def _check_descriptor_dependencies(self, session, descriptor):
         """
         Check that the dependent descriptors exist on a new descriptor or edition
-        :param session: client session information
+        :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
         :param descriptor: descriptor to be inserted or edit
         :return: None or raises exception
         """
@@ -814,41 +896,35 @@ class NstTopic(DescriptorTopic):
             return
         for nsd in descriptor["netslice-subnet"]:
             nsd_id = nsd["nsd-ref"]
-            filter_q = self._get_project_filter(session, write=False, show_all=True)
+            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, 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 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:
+        :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
         :param _id: nst internal id
-        :param force: Avoid this checking
+        :param db_content: The database content of the _id.
         :return: None or raises EngineException with the conflict
         """
         # TODO: Check this method
-        if force:
+        if session["force"]:
             return
         # Get Network Slice Template from Database
-        _filter = self._get_project_filter(session, write=False, show_all=False)
-        _filter["_id"] = _id
-        nst = self.db.get_one("nsts", _filter)
-        
-        # Search NSIs using NST via nst-ref
-        _filter = self._get_project_filter(session, write=False, show_all=False)
-        _filter["nst-ref"] = nst["id"]
-        nsis_list = self.db.get_list("nsis", _filter)
-        for nsi_item in nsis_list:
-            if nsi_item["_admin"].get("nsiState") != "TERMINATED":
-                raise EngineException("There is some NSIS that depends on this NST", http_code=HTTPStatus.CONFLICT)
+        _filter = self._get_project_filter(session)
+        _filter["_admin.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):
@@ -857,8 +933,8 @@ class PduTopic(BaseTopic):
     schema_new = pdu_new_schema
     schema_edit = pdu_edit_schema
 
-    def __init__(self, db, fs, msg):
-        BaseTopic.__init__(self, db, fs, msg)
+    def __init__(self, db, fs, msg, auth):
+        BaseTopic.__init__(self, db, fs, msg, auth)
 
     @staticmethod
     def format_on_new(content, project_id=None, make_public=False):
@@ -867,10 +943,112 @@ class PduTopic(BaseTopic):
         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)
+
+
+class VnfPkgOpTopic(BaseTopic):
+    topic = "vnfpkgops"
+    topic_msg = "vnfd"
+    schema_new = vnfpkgop_new_schema
+    schema_edit = None
+
+    def __init__(self, db, fs, msg, auth):
+        BaseTopic.__init__(self, db, fs, msg, auth)
+
+    def edit(self, session, _id, indata=None, kwargs=None, content=None):
+        raise EngineException("Method 'edit' not allowed for topic '{}'".format(self.topic),
+                              HTTPStatus.METHOD_NOT_ALLOWED)
+
+    def delete(self, session, _id, dry_run=False):
+        raise EngineException("Method 'delete' not allowed for topic '{}'".format(self.topic),
+                              HTTPStatus.METHOD_NOT_ALLOWED)
+
+    def delete_list(self, session, filter_q=None):
+        raise EngineException("Method 'delete_list' not allowed for topic '{}'".format(self.topic),
+                              HTTPStatus.METHOD_NOT_ALLOWED)
+
+    def new(self, rollback, session, indata=None, kwargs=None, headers=None):
+        """
+        Creates a new entry into database.
+        :param rollback: list to append created items at database in case a rollback may to be done
+        :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
+        :return: _id, op_id:
+            _id: identity of the inserted data.
+             op_id: None
+        """
+        self._update_input_with_kwargs(indata, kwargs)
+        validate_input(indata, self.schema_new)
+        vnfpkg_id = indata["vnfPkgId"]
+        filter_q = BaseTopic._get_project_filter(session)
+        filter_q["_id"] = vnfpkg_id
+        vnfd = self.db.get_one("vnfds", filter_q)
+        operation = indata["lcmOperationType"]
+        kdu_name = indata["kdu_name"]
+        for kdu in vnfd.get("kdu", []):
+            if kdu["name"] == kdu_name:
+                helm_chart = kdu.get("helm-chart")
+                juju_bundle = kdu.get("juju-bundle")
+                break
+        else:
+            raise EngineException("Not found vnfd[id='{}']:kdu[name='{}']".format(vnfpkg_id, kdu_name))
+        if helm_chart:
+            indata["helm-chart"] = helm_chart
+            match = fullmatch(r"([^/]*)/([^/]*)", helm_chart)
+            repo_name = match.group(1) if match else None
+        elif juju_bundle:
+            indata["juju-bundle"] = juju_bundle
+            match = fullmatch(r"([^/]*)/([^/]*)", juju_bundle)
+            repo_name = match.group(1) if match else None
+        else:
+            raise EngineException("Found neither 'helm-chart' nor 'juju-bundle' in vnfd[id='{}']:kdu[name='{}']"
+                                  .format(vnfpkg_id, kdu_name))
+        if repo_name:
+            del filter_q["_id"]
+            filter_q["name"] = repo_name
+            repo = self.db.get_one("k8srepos", filter_q)
+            k8srepo_id = repo.get("_id")
+            k8srepo_url = repo.get("url")
+        else:
+            k8srepo_id = None
+            k8srepo_url = None
+        indata["k8srepoId"] = k8srepo_id
+        indata["k8srepo_url"] = k8srepo_url
+        vnfpkgop_id = str(uuid4())
+        vnfpkgop_desc = {
+            "_id": vnfpkgop_id,
+            "operationState": "PROCESSING",
+            "vnfPkgId": vnfpkg_id,
+            "lcmOperationType": operation,
+            "isAutomaticInvocation": False,
+            "isCancelPending": False,
+            "operationParams": indata,
+            "links": {
+                "self": "/osm/vnfpkgm/v1/vnfpkg_op_occs/" + vnfpkgop_id,
+                "vnfpkg": "/osm/vnfpkgm/v1/vnf_packages/" + vnfpkg_id,
+            }
+        }
+        self.format_on_new(vnfpkgop_desc, session["project_id"], make_public=session["public"])
+        ctime = vnfpkgop_desc["_admin"]["created"]
+        vnfpkgop_desc["statusEnteredTime"] = ctime
+        vnfpkgop_desc["startTime"] = ctime
+        self.db.create(self.topic, vnfpkgop_desc)
+        rollback.append({"topic": self.topic, "_id": vnfpkgop_id})
+        self.msg.write(self.topic_msg, operation, vnfpkgop_desc)
+        return vnfpkgop_id, None