Merge "Check cross-references in automated tests"
[osm/NBI.git] / osm_nbi / instance_topics.py
index b338202..de5bfd1 100644 (file)
@@ -1,13 +1,27 @@
 # -*- coding: utf-8 -*-
 
 # -*- 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 logging
 from uuid import uuid4
 from http import HTTPStatus
 from time import time
 from copy import copy, deepcopy
 # import logging
 from uuid import uuid4
 from http import HTTPStatus
 from time import time
 from copy import copy, deepcopy
-from validation import validate_input, ValidationError, ns_instantiate, ns_action, ns_scale
+from validation import validate_input, ValidationError, ns_instantiate, ns_action, ns_scale, nsi_instantiate
 from base_topic import BaseTopic, EngineException, get_iterable
 from descriptor_topics import DescriptorTopic
 from base_topic import BaseTopic, EngineException, get_iterable
 from descriptor_topics import DescriptorTopic
+from yaml import safe_dump
 
 __author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
 
 
 __author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
 
@@ -61,6 +75,7 @@ class NsrTopic(BaseTopic):
         if dry_run:
             return
 
         if dry_run:
             return
 
+        self.fs.file_delete(_id, ignore_non_exist=True)
         v = self.db.del_one("nsrs", {"_id": _id})
         self.db.del_list("nslcmops", {"nsInstanceId": _id})
         self.db.del_list("vnfrs", {"nsr-id-ref": _id})
         v = self.db.del_one("nsrs", {"_id": _id})
         self.db.del_list("nslcmops", {"nsInstanceId": _id})
         self.db.del_list("vnfrs", {"nsr-id-ref": _id})
@@ -70,6 +85,61 @@ class NsrTopic(BaseTopic):
         self._send_msg("deleted", {"_id": _id})
         return v
 
         self._send_msg("deleted", {"_id": _id})
         return v
 
+    @staticmethod
+    def _format_ns_request(ns_request):
+        formated_request = copy(ns_request)
+        formated_request.pop("additionalParamsForNs", None)
+        formated_request.pop("additionalParamsForVnf", None)
+        return formated_request
+
+    @staticmethod
+    def _format_addional_params(ns_request, member_vnf_index=None, descriptor=None):
+        """
+        Get and format user additional params for NS or VNF
+        :param ns_request: User instantiation additional parameters
+        :param member_vnf_index: None for extract NS params, or member_vnf_index to extract VNF params
+        :param descriptor: If not None it check that needed parameters of descriptor are supplied
+        :return: a formated copy of additional params or None if not supplied
+        """
+        additional_params = None
+        if not member_vnf_index:
+            additional_params = copy(ns_request.get("additionalParamsForNs"))
+            where_ = "additionalParamsForNs"
+        elif ns_request.get("additionalParamsForVnf"):
+            for additionalParamsForVnf in get_iterable(ns_request.get("additionalParamsForVnf")):
+                if additionalParamsForVnf["member-vnf-index"] == member_vnf_index:
+                    additional_params = copy(additionalParamsForVnf.get("additionalParams"))
+                    where_ = "additionalParamsForVnf[member-vnf-index={}]".format(
+                        additionalParamsForVnf["member-vnf-index"])
+                    break
+        if additional_params:
+            for k, v in additional_params.items():
+                if not isinstance(k, str):
+                    raise EngineException("Invalid param at {}:{}. Only string keys are allowed".format(where_, k))
+                if "." in k or "$" in k:
+                    raise EngineException("Invalid param at {}:{}. Keys must not contain dots or $".format(where_, k))
+                if isinstance(v, (dict, tuple, list)):
+                    additional_params[k] = "!!yaml " + safe_dump(v)
+
+        if descriptor:
+            # check that enough parameters are supplied for the initial-config-primitive
+            # TODO: check for cloud-init
+            if member_vnf_index:
+                if descriptor.get("vnf-configuration"):
+                    for initial_primitive in get_iterable(
+                            descriptor["vnf-configuration"].get("initial-config-primitive")):
+                        for param in get_iterable(initial_primitive.get("parameter")):
+                            if param["value"].startswith("<") and param["value"].endswith(">"):
+                                if param["value"] in ("<rw_mgmt_ip>", "<VDU_SCALE_INFO>"):
+                                    continue
+                                if not additional_params or param["value"][1:-1] not in additional_params:
+                                    raise EngineException("Parameter '{}' needed for vnfd[id={}]:vnf-configuration:"
+                                                          "initial-config-primitive[name={}] not supplied".
+                                                          format(param["value"], descriptor["id"],
+                                                                 initial_primitive["name"]))
+
+        return additional_params
+
     def new(self, rollback, session, indata=None, kwargs=None, headers=None, force=False, make_public=False):
         """
         Creates a new nsr into database. It also creates needed vnfrs
     def new(self, rollback, session, indata=None, kwargs=None, headers=None, force=False, make_public=False):
         """
         Creates a new nsr into database. It also creates needed vnfrs
@@ -97,6 +167,7 @@ class NsrTopic(BaseTopic):
             nsd = self.db.get_one("nsds", _filter)
 
             nsr_id = str(uuid4())
             nsd = self.db.get_one("nsds", _filter)
 
             nsr_id = str(uuid4())
+
             now = time()
             step = "filling nsr from input data"
             nsr_descriptor = {
             now = time()
             step = "filling nsr from input data"
             nsr_descriptor = {
@@ -121,7 +192,8 @@ class NsrTopic(BaseTopic):
                 "nsd-name-ref": nsd["name"],
                 "operational-events": [],   # "id", "timestamp", "description", "event",
                 "nsd-ref": nsd["id"],
                 "nsd-name-ref": nsd["name"],
                 "operational-events": [],   # "id", "timestamp", "description", "event",
                 "nsd-ref": nsd["id"],
-                "instantiate_params": ns_request,
+                "instantiate_params": self._format_ns_request(ns_request),
+                "additionalParamsForNs": self._format_addional_params(ns_request),
                 "ns-instance-config-ref": nsr_id,
                 "id": nsr_id,
                 "_id": nsr_id,
                 "ns-instance-config-ref": nsr_id,
                 "id": nsr_id,
                 "_id": nsr_id,
@@ -134,7 +206,7 @@ class NsrTopic(BaseTopic):
                 nsr_descriptor["vld"] = []
                 for nsd_vld in nsd.get("vld"):
                     nsr_descriptor["vld"].append(
                 nsr_descriptor["vld"] = []
                 for nsd_vld in nsd.get("vld"):
                     nsr_descriptor["vld"].append(
-                        {key: nsd_vld[key] for key in ("id", "vim-network-name") if key in nsd_vld})
+                        {key: nsd_vld[key] for key in ("id", "vim-network-name", "vim-network-id") if key in nsd_vld})
 
             # Create VNFR
             needed_vnfds = {}
 
             # Create VNFR
             needed_vnfds = {}
@@ -157,6 +229,8 @@ class NsrTopic(BaseTopic):
                     "_id": vnfr_id,
                     "nsr-id-ref": nsr_id,
                     "member-vnf-index-ref": member_vnf["member-vnf-index"],
                     "_id": vnfr_id,
                     "nsr-id-ref": nsr_id,
                     "member-vnf-index-ref": member_vnf["member-vnf-index"],
+                    "additionalParamsForVnf": self._format_addional_params(ns_request, member_vnf["member-vnf-index"],
+                                                                           vnfd),
                     "created-time": now,
                     # "vnfd": vnfd,        # at OSM model.but removed to avoid data duplication TODO: revise
                     "vnfd-ref": vnfd_id,
                     "created-time": now,
                     # "vnfd": vnfd,        # at OSM model.but removed to avoid data duplication TODO: revise
                     "vnfd-ref": vnfd_id,
@@ -172,7 +246,8 @@ class NsrTopic(BaseTopic):
                     vnfr_descriptor["vld"] = []
                     for vnfd_vld in vnfd.get("internal-vld"):
                         vnfr_descriptor["vld"].append(
                     vnfr_descriptor["vld"] = []
                     for vnfd_vld in vnfd.get("internal-vld"):
                         vnfr_descriptor["vld"].append(
-                            {key: vnfd_vld[key] for key in ("id", "vim-network-name") if key in vnfd_vld})
+                            {key: vnfd_vld[key] for key in ("id", "vim-network-name", "vim-network-id") if key in
+                             vnfd_vld})
 
                 vnfd_mgmt_cp = vnfd["mgmt-interface"].get("cp")
                 for cp in vnfd.get("connection-point", ()):
 
                 vnfd_mgmt_cp = vnfd["mgmt-interface"].get("cp")
                 for cp in vnfd.get("connection-point", ()):
@@ -263,6 +338,10 @@ class NsrTopic(BaseTopic):
             self.format_on_new(nsr_descriptor, session["project_id"], make_public=make_public)
             self.db.create("nsrs", nsr_descriptor)
             rollback.append({"topic": "nsrs", "_id": nsr_id})
             self.format_on_new(nsr_descriptor, session["project_id"], make_public=make_public)
             self.db.create("nsrs", nsr_descriptor)
             rollback.append({"topic": "nsrs", "_id": nsr_id})
+
+            step = "creating nsr temporal folder"
+            self.fs.mkdir(nsr_id)
+
             return nsr_id
         except Exception as e:
             self.logger.exception("Exception {} at NsrTopic.new()".format(e), exc_info=True)
             return nsr_id
         except Exception as e:
             self.logger.exception("Exception {} at NsrTopic.new()".format(e), exc_info=True)
@@ -305,17 +384,6 @@ class NsLcmOpTopic(BaseTopic):
     def __init__(self, db, fs, msg):
         BaseTopic.__init__(self, db, fs, msg)
 
     def __init__(self, db, fs, msg):
         BaseTopic.__init__(self, db, fs, msg)
 
-    def _validate_input_new(self, input, force=False):
-        """
-        Validates input user content for a new entry. It uses jsonschema for each type or operation.
-        :param input: user input content for the new topic
-        :param force: may be used for being more tolerant
-        :return: The same input content, or a changed version of it.
-        """
-        if self.schema_new:
-            validate_input(input, self.schema_new)
-        return input
-
     def _check_ns_operation(self, session, nsr, operation, indata):
         """
         Check that user has enter right parameters for the operation
     def _check_ns_operation(self, session, nsr, operation, indata):
         """
         Check that user has enter right parameters for the operation
@@ -514,10 +582,10 @@ class NsLcmOpTopic(BaseTopic):
             }
             self.db.set_one("pdus", {"_id": pdu["_id"]},
                             {"_admin.usageState": "IN_USE",
             }
             self.db.set_one("pdus", {"_id": pdu["_id"]},
                             {"_admin.usageState": "IN_USE",
-                             "_admin.usage.vnfr_id": vnfr["_id"],
-                             "_admin.usage.nsr_id": vnfr["nsr-id-ref"],
-                             "_admin.usage.vdur": vdur["vdu-id-ref"]}
-                            )
+                             "_admin.usage": {"vnfr_id": vnfr["_id"],
+                                              "nsr_id": vnfr["nsr-id-ref"],
+                                              "vdur": vdur["vdu-id-ref"]}
+                             })
             rollback.append({"topic": "pdus", "_id": pdu["_id"], "operation": "set", "content": rollback_pdu})
 
             # step 3. Fill vnfr info by filling vdur
             rollback.append({"topic": "pdus", "_id": pdu["_id"], "operation": "set", "content": rollback_pdu})
 
             # step 3. Fill vnfr info by filling vdur
@@ -539,13 +607,17 @@ class NsLcmOpTopic(BaseTopic):
                             if vdur_interface.get("mgmt-vnf"):
                                 vnfr_update_rollback["ip-address"] = vnfr.get("ip-address")
                                 vnfr_update["ip-address"] = pdu_interface["ip-address"]
                             if vdur_interface.get("mgmt-vnf"):
                                 vnfr_update_rollback["ip-address"] = vnfr.get("ip-address")
                                 vnfr_update["ip-address"] = pdu_interface["ip-address"]
-                        if pdu_interface.get("vim-network-name"):  # or pdu_interface.get("vim-network-id"):
+                        if pdu_interface.get("vim-network-name") or pdu_interface.get("vim-network-id"):
                             ifaces_forcing_vim_network.append({
                             ifaces_forcing_vim_network.append({
-                                # "vim-network-id": pdu_interface.get("vim-network-id"),
-                                "vim-network-name": pdu_interface.get("vim-network-name"),
                                 "name": vdur_interface.get("vnf-vld-id") or vdur_interface.get("ns-vld-id"),
                                 "vnf-vld-id": vdur_interface.get("vnf-vld-id"),
                                 "ns-vld-id": vdur_interface.get("ns-vld-id")})
                                 "name": vdur_interface.get("vnf-vld-id") or vdur_interface.get("ns-vld-id"),
                                 "vnf-vld-id": vdur_interface.get("vnf-vld-id"),
                                 "ns-vld-id": vdur_interface.get("ns-vld-id")})
+                            if pdu_interface.get("vim-network-id"):
+                                ifaces_forcing_vim_network.append({
+                                    "vim-network-id": pdu_interface.get("vim-network-id")})
+                            if pdu_interface.get("vim-network-name"):
+                                ifaces_forcing_vim_network.append({
+                                    "vim-network-name": pdu_interface.get("vim-network-name")})
                         break
 
         return ifaces_forcing_vim_network
                         break
 
         return ifaces_forcing_vim_network
@@ -630,7 +702,8 @@ class NsLcmOpTopic(BaseTopic):
         }
         return nslcmop
 
         }
         return nslcmop
 
-    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, force=False, make_public=False,
+            slice_object=False):
         """
         Performs a new operation over a ns
         :param rollback: list to append created items at database in case a rollback must to be done
         """
         Performs a new operation over a ns
         :param rollback: list to append created items at database in case a rollback must to be done
@@ -660,7 +733,7 @@ class NsLcmOpTopic(BaseTopic):
             if not nsr["_admin"].get("nsState") or nsr["_admin"]["nsState"] == "NOT_INSTANTIATED":
                 if operation == "terminate" and indata.get("autoremove"):
                     # NSR must be deleted
             if not nsr["_admin"].get("nsState") or nsr["_admin"]["nsState"] == "NOT_INSTANTIATED":
                 if operation == "terminate" and indata.get("autoremove"):
                     # NSR must be deleted
-                    return self.delete(session, nsInstanceId)
+                    return None    # a none in this case is used to indicate not instantiated. It can be removed
                 if operation != "instantiate":
                     raise EngineException("ns_instance '{}' cannot be '{}' because it is not instantiated".format(
                         nsInstanceId, operation), HTTPStatus.CONFLICT)
                 if operation != "instantiate":
                     raise EngineException("ns_instance '{}' cannot be '{}' because it is not instantiated".format(
                         nsInstanceId, operation), HTTPStatus.CONFLICT)
@@ -677,7 +750,8 @@ class NsLcmOpTopic(BaseTopic):
             self.format_on_new(nslcmop_desc, session["project_id"], make_public=make_public)
             _id = self.db.create("nslcmops", nslcmop_desc)
             rollback.append({"topic": "nslcmops", "_id": _id})
             self.format_on_new(nslcmop_desc, session["project_id"], make_public=make_public)
             _id = self.db.create("nslcmops", nslcmop_desc)
             rollback.append({"topic": "nslcmops", "_id": _id})
-            self.msg.write("ns", operation, nslcmop_desc)
+            if not slice_object:
+                self.msg.write("ns", operation, nslcmop_desc)
             return _id
         except ValidationError as e:
             raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
             return _id
         except ValidationError as e:
             raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
@@ -689,3 +763,389 @@ class NsLcmOpTopic(BaseTopic):
 
     def edit(self, session, _id, indata=None, kwargs=None, force=False, content=None):
         raise EngineException("Method edit called directly", HTTPStatus.INTERNAL_SERVER_ERROR)
 
     def edit(self, session, _id, indata=None, kwargs=None, force=False, content=None):
         raise EngineException("Method edit called directly", HTTPStatus.INTERNAL_SERVER_ERROR)
+
+
+class NsiTopic(BaseTopic):
+    topic = "nsis"
+    topic_msg = "nsi"
+
+    def __init__(self, db, fs, msg):
+        BaseTopic.__init__(self, db, fs, msg)
+
+    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 descriptor: descriptor to be inserted or edit
+        :return: None or raises exception
+        """
+        if not descriptor.get("nst-ref"):
+            return
+        nstd_id = descriptor["nst-ref"]
+        if not self.get_item_list(session, "nsts", {"id": nstd_id}):
+            raise EngineException("Descriptor error at nst-ref='{}' references a non exist nstd".format(nstd_id),
+                                  http_code=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)
+
+    def check_conflict_on_del(self, session, _id, force=False):
+        if force:
+            return
+        nsi = self.db.get_one("nsis", {"_id": _id})
+        if nsi["_admin"].get("nsiState") == "INSTANTIATED":
+            raise EngineException("nsi '{}' cannot be deleted because it is in 'INSTANTIATED' state. "
+                                  "Launch 'terminate' operation first; or force deletion".format(_id),
+                                  http_code=HTTPStatus.CONFLICT)
+
+    def delete(self, session, _id, force=False, dry_run=False):
+        """
+        Delete item by its internal _id
+        :param session: contains the used login username, working project, and admin rights
+        :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, ...
+        """
+        # TODO add admin to filter, validate rights
+        BaseTopic.delete(self, session, _id, force, dry_run=True)
+        if dry_run:
+            return
+
+        # deletes NetSlice instance object
+        v = self.db.del_one("nsis", {"_id": _id})
+
+        # makes a temporal list of nsilcmops objects related to the _id given and deletes them from db
+        _filter = {"netsliceInstanceId": _id} 
+        self.db.del_list("nsilcmops", _filter)
+
+        _filter = {"operationParams.netsliceInstanceId": _id}
+        nslcmops_list = self.db.get_list("nslcmops", _filter)
+
+        for id_item in nslcmops_list:
+            _filter = {"_id": id_item}
+            nslcmop = self.db.get_one("nslcmops", _filter)
+            nsr_id = nslcmop["operationParams"]["nsr_id"]
+            NsrTopic.delete(self, session, nsr_id, force=False, dry_run=False)
+        self._send_msg("deleted", {"_id": _id})
+        return v
+
+    def new(self, rollback, session, indata=None, kwargs=None, headers=None, force=False, make_public=False):
+        """
+        Creates a new netslice instance record into database. It also creates needed nsrs and vnfrs
+        :param rollback: list to append the created items at database in case a rollback must be done
+        :param session: contains the used login username and working project
+        :param indata: params to be used for the nsir
+        :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 item public to all projects
+        :return: the _id of nsi descriptor created at database
+        """
+
+        try:
+            slice_request = self._remove_envelop(indata)
+            # Override descriptor with query string kwargs
+            self._update_input_with_kwargs(slice_request, kwargs)
+            self._validate_input_new(slice_request, force)
+
+            step = ""
+            # look for nstd
+            step = "getting nstd id='{}' from database".format(slice_request.get("nstId"))
+            _filter = {"_id": slice_request["nstId"]}
+            _filter.update(BaseTopic._get_project_filter(session, write=False, show_all=True))
+            nstd = self.db.get_one("nsts", _filter)
+            nstd.pop("_admin", None)
+            nstd.pop("_id", None)
+            nsi_id = str(uuid4())
+            step = "filling nsi_descriptor with input data"
+
+            # Creating the NSIR
+            nsi_descriptor = {
+                "id": nsi_id,
+                "name": slice_request["nsiName"],
+                "description": slice_request.get("nsiDescription", ""),
+                "datacenter": slice_request["vimAccountId"],
+                "nst-ref": nstd["id"],
+                "instantiation_parameters": slice_request,
+                "network-slice-template": nstd,
+                "nsr-ref-list": [],
+                "vlr-list": [],
+                "_id": nsi_id,
+            }
+
+            step = "creating nsi at database"
+            self.format_on_new(nsi_descriptor, session["project_id"], make_public=make_public)
+            nsi_descriptor["_admin"]["nsiState"] = "NOT_INSTANTIATED"
+            nsi_descriptor["_admin"]["netslice-subnet"] = None
+            
+            # Creating netslice-vld for the RO.
+            step = "creating netslice-vld at database"
+            instantiation_parameters = slice_request
+
+            # Building the vlds list to be deployed
+            # From netslice descriptors, creating the initial list
+            nsi_vlds = []           
+            if nstd.get("netslice-vld"):
+                # Building VLDs from NST
+                for netslice_vlds in nstd["netslice-vld"]:
+                    nsi_vld = {}
+                    # Adding nst vld name and global vimAccountId for netslice vld creation
+                    nsi_vld["name"] = netslice_vlds["name"]
+                    nsi_vld["vimAccountId"] = slice_request["vimAccountId"]
+                    # Getting template Instantiation parameters from NST
+                    for netslice_vld in netslice_vlds["nss-connection-point-ref"]:
+                        for netslice_subnet in nstd["netslice-subnet"]:
+                            nsi_vld["nsd-ref"] = netslice_subnet["nsd-ref"]
+                            nsi_vld["nsd-connection-point-ref"] = netslice_vld["nsd-connection-point-ref"]
+                            # Obtaining the vimAccountId from template instantiation parameter            
+                            if netslice_subnet.get("instantiation-parameters"):
+                                # Taking the vimAccountId from NST netslice-subnet instantiation parameters
+                                if netslice_subnet["instantiation-parameters"].get("vimAccountId"):
+                                    netsn = netslice_subnet["instantiation-parameters"]["vimAccountId"]
+                                    nsi_vld["vimAccountId"] = netsn
+                            # Obtaining the vimAccountId from user instantiation parameter
+                            if instantiation_parameters.get("netslice-subnet"):
+                                for ins_param in instantiation_parameters["netslice-subnet"]:
+                                    if ins_param.get("id") == netslice_vld["nss-ref"]:
+                                        if ins_param.get("vimAccountId"):
+                                            nsi_vld["vimAccountId"] = ins_param["vimAccountId"]
+                    # Adding vim-network-name defined by the user to vld
+                    if instantiation_parameters.get("netslice-vld"):
+                        for ins_param in instantiation_parameters["netslice-vld"]:
+                            if ins_param["name"] == netslice_vlds["name"]:
+                                if ins_param.get("vim-network-name"):
+                                    nsi_vld["vim-network-name"] = ins_param.get("vim-network-name")
+                                if ins_param.get("vim-network-id"):
+                                    nsi_vld["vim-network-id"] = ins_param.get("vim-network-id")
+                    nsi_vlds.append(nsi_vld)
+
+            nsi_descriptor["_admin"]["netslice-vld"] = nsi_vlds
+            # Creating netslice-subnet_record. 
+            needed_nsds = {}
+            services = []
+
+            for member_ns in nstd["netslice-subnet"]:
+                nsd_id = member_ns["nsd-ref"]
+                step = "getting nstd id='{}' constituent-nsd='{}' from database".format(
+                    member_ns["nsd-ref"], member_ns["id"])
+                if nsd_id not in needed_nsds:
+                    # Obtain nsd
+                    nsd = DescriptorTopic.get_one_by_id(self.db, session, "nsds", nsd_id)
+                    nsd.pop("_admin")
+                    needed_nsds[nsd_id] = nsd
+                    member_ns["_id"] = needed_nsds[nsd_id].get("_id")
+                    services.append(member_ns)
+                else:
+                    nsd = needed_nsds[nsd_id]
+                    member_ns["_id"] = needed_nsds[nsd_id].get("_id")
+                    services.append(member_ns)
+
+                step = "filling nsir nsd-id='{}' constituent-nsd='{}' from database".format(
+                    member_ns["nsd-ref"], member_ns["id"])
+
+            # creates Network Services records (NSRs)
+            step = "creating nsrs at database using NsrTopic.new()"
+            ns_params = slice_request.get("netslice-subnet")
+
+            nsrs_list = []
+            nsi_netslice_subnet = []
+            for service in services:
+                indata_ns = {}
+                indata_ns["nsdId"] = service["_id"]
+                indata_ns["nsName"] = slice_request.get("nsiName") + "." + service["id"]
+                indata_ns["vimAccountId"] = slice_request.get("vimAccountId")
+                indata_ns["nsDescription"] = service["description"]
+                indata_ns["key-pair-ref"] = None
+
+                # NsrTopic(rollback, session, indata_ns, kwargs, headers, force)
+                # Overwriting ns_params filtering by nsName == netslice-subnet.id
+
+                if ns_params:
+                    for ns_param in ns_params:
+                        if ns_param.get("id") == service["id"]:
+                            copy_ns_param = deepcopy(ns_param)
+                            del copy_ns_param["id"]
+                            indata_ns.update(copy_ns_param)
+                # TODO: Improve network selection via networkID
+                if nsi_vlds:
+                    indata_ns_list = []
+                    for nsi_vld in nsi_vlds:
+                        nsd = self.db.get_one("nsds", {"id": nsi_vld["nsd-ref"]})
+                        if nsd["connection-point"]:
+                            for vld_id_ref in nsd["connection-point"]:
+                                name = vld_id_ref["vld-id-ref"]
+                                vnm = nsi_vld.get("vim-network-name")
+                                if type(vnm) is not dict:
+                                    vnm = {slice_request.get("vimAccountId"): vnm}
+                                if vld_id_ref["name"] == nsi_vld["nsd-connection-point-ref"]:
+                                    if list(vnm.values())[0] is None:
+                                        networkName = "default"
+                                    else:
+                                        networkName = str(list(vnm.values())[0])
+                                    # VIMNetworkName = NetSliceName-NetworkName
+                                    vimnetname = str(slice_request["nsiName"]) + "-" + networkName
+                                    indata_ns_list.append({"name": name, "vim-network-name": vimnetname})
+                    indata_ns["vld"] = indata_ns_list
+                            
+                _id_nsr = NsrTopic.new(self, rollback, session, indata_ns, kwargs, headers, force)
+                nsrs_item = {"nsrId": _id_nsr}
+                nsrs_list.append(nsrs_item)
+                nsi_netslice_subnet.append(indata_ns)
+                nsr_ref = {"nsr-ref": _id_nsr}
+                nsi_descriptor["nsr-ref-list"].append(nsr_ref)
+
+            # Adding the nsrs list to the nsi
+            nsi_descriptor["_admin"]["nsrs-detailed-list"] = nsrs_list
+            nsi_descriptor["_admin"]["netslice-subnet"] = nsi_netslice_subnet
+            # Creating the entry in the database
+            self.db.create("nsis", nsi_descriptor)
+            rollback.append({"topic": "nsis", "_id": nsi_id})
+            return nsi_id
+        except Exception as e:
+            self.logger.exception("Exception {} at NsiTopic.new()".format(e), exc_info=True)
+            raise EngineException("Error {}: {}".format(step, e))
+        except ValidationError as e:
+            raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
+
+    def edit(self, session, _id, indata=None, kwargs=None, force=False, content=None):
+        raise EngineException("Method edit called directly", HTTPStatus.INTERNAL_SERVER_ERROR)
+
+
+class NsiLcmOpTopic(BaseTopic):
+    topic = "nsilcmops"
+    topic_msg = "nsi"
+    operation_schema = {  # mapping between operation and jsonschema to validate
+        "instantiate": nsi_instantiate,
+        "terminate": None
+    }
+
+    def __init__(self, db, fs, msg):
+        BaseTopic.__init__(self, db, fs, msg)
+
+    def _check_nsi_operation(self, session, nsir, operation, indata):
+        """
+        Check that user has enter right parameters for the operation
+        :param session:
+        :param operation: it can be: instantiate, terminate, action, TODO: update, heal
+        :param indata: descriptor with the parameters of the operation
+        :return: None
+        """
+        nsds = {}
+        nstd = nsir["network-slice-template"]
+
+        def check_valid_netslice_subnet_id(nstId):
+            # TODO change to vnfR (??)
+            for netslice_subnet in nstd["netslice-subnet"]:
+                if nstId == netslice_subnet["id"]:
+                    nsd_id = netslice_subnet["nsd-ref"]
+                    if nsd_id not in nsds:
+                        nsds[nsd_id] = self.db.get_one("nsds", {"id": nsd_id})
+                    return nsds[nsd_id]
+            else:
+                raise EngineException("Invalid parameter nstId='{}' is not one of the "
+                                      "nst:netslice-subnet".format(nstId))
+        if operation == "instantiate":
+            # check the existance of netslice-subnet items
+            for in_nst in get_iterable(indata.get("netslice-subnet")):   
+                check_valid_netslice_subnet_id(in_nst["id"])
+
+    def _create_nsilcmop(self, session, netsliceInstanceId, operation, params):
+        now = time()
+        _id = str(uuid4())
+        nsilcmop = {
+            "id": _id,
+            "_id": _id,
+            "operationState": "PROCESSING",  # COMPLETED,PARTIALLY_COMPLETED,FAILED_TEMP,FAILED,ROLLING_BACK,ROLLED_BACK
+            "statusEnteredTime": now,
+            "netsliceInstanceId": netsliceInstanceId,
+            "lcmOperationType": operation,
+            "startTime": now,
+            "isAutomaticInvocation": False,
+            "operationParams": params,
+            "isCancelPending": False,
+            "links": {
+                "self": "/osm/nsilcm/v1/nsi_lcm_op_occs/" + _id,
+                "nsInstance": "/osm/nsilcm/v1/netslice_instances/" + netsliceInstanceId,
+            }
+        }
+        return nsilcmop
+
+    def new(self, rollback, session, indata=None, kwargs=None, headers=None, force=False, make_public=False):
+        """
+        Performs a new operation over a ns
+        :param rollback: list to append created items at database in case a rollback must to be done
+        :param session: contains the used login username and working project
+        :param indata: descriptor with the parameters of the operation. It must contains among others
+            nsiInstanceId: _id of the nsir to perform the operation
+            operation: it can be: instantiate, terminate, action, TODO: update, heal
+        :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 item public to all projects
+        :return: id of the nslcmops
+        """
+        try:
+            # Override descriptor with query string kwargs
+            self._update_input_with_kwargs(indata, kwargs)
+            operation = indata["lcmOperationType"]
+            nsiInstanceId = indata["nsiInstanceId"]
+            validate_input(indata, self.operation_schema[operation])
+
+            # get nsi from nsiInstanceId
+            _filter = BaseTopic._get_project_filter(session, write=True, show_all=False)
+            _filter["_id"] = nsiInstanceId
+            nsir = self.db.get_one("nsis", _filter)
+
+            # initial checking
+            if not nsir["_admin"].get("nsiState") or nsir["_admin"]["nsiState"] == "NOT_INSTANTIATED":
+                if operation == "terminate" and indata.get("autoremove"):
+                    # NSIR must be deleted
+                    return None    # a none in this case is used to indicate not instantiated. It can be removed
+                if operation != "instantiate":
+                    raise EngineException("netslice_instance '{}' cannot be '{}' because it is not instantiated".format(
+                        nsiInstanceId, operation), HTTPStatus.CONFLICT)
+            else:
+                if operation == "instantiate" and not indata.get("force"):
+                    raise EngineException("netslice_instance '{}' cannot be '{}' because it is already instantiated".
+                                          format(nsiInstanceId, operation), HTTPStatus.CONFLICT)
+            
+            # Creating all the NS_operation (nslcmop)
+            # Get service list from db
+            nsrs_list = nsir["_admin"]["nsrs-detailed-list"]
+            nslcmops = []
+            for nsr_item in nsrs_list:
+                service = self.db.get_one("nsrs", {"_id": nsr_item["nsrId"]})
+                indata_ns = {}
+                indata_ns = service["instantiate_params"]
+                indata_ns["lcmOperationType"] = operation
+                indata_ns["nsInstanceId"] = service["_id"]
+                # Including netslice_id in the ns instantiate Operation
+                indata_ns["netsliceInstanceId"] = nsiInstanceId
+                del indata_ns["key-pair-ref"]
+                nsi_NsLcmOpTopic = NsLcmOpTopic(self.db, self.fs, self.msg)
+                # Creating NS_LCM_OP with the flag slice_object=True to not trigger the service instantiation 
+                # message via kafka bus
+                nslcmop = nsi_NsLcmOpTopic.new(rollback, session, indata_ns, kwargs, headers, force, slice_object=True)
+                nslcmops.append(nslcmop)
+
+            # Creates nsilcmop
+            indata["nslcmops_ids"] = nslcmops
+            self._check_nsi_operation(session, nsir, operation, indata)
+            nsilcmop_desc = self._create_nsilcmop(session, nsiInstanceId, operation, indata)
+            self.format_on_new(nsilcmop_desc, session["project_id"], make_public=make_public)
+            _id = self.db.create("nsilcmops", nsilcmop_desc)
+            rollback.append({"topic": "nsilcmops", "_id": _id})
+            self.msg.write("nsi", operation, nsilcmop_desc)
+            return _id
+        except ValidationError as e:
+            raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
+        # except DbException as e:
+        #     raise EngineException("Cannot get nsi_instance '{}': {}".format(e), HTTPStatus.NOT_FOUND)
+
+    def delete(self, session, _id, force=False, dry_run=False):
+        raise EngineException("Method delete called directly", HTTPStatus.INTERNAL_SERVER_ERROR)
+
+    def edit(self, session, _id, indata=None, kwargs=None, force=False, content=None):
+        raise EngineException("Method edit called directly", HTTPStatus.INTERNAL_SERVER_ERROR)