Merge branch 'netslice' 04/6904/1
authortierno <alfonso.tiernosepulveda@telefonica.com>
Fri, 16 Nov 2018 15:34:49 +0000 (16:34 +0100)
committertierno <alfonso.tiernosepulveda@telefonica.com>
Fri, 16 Nov 2018 15:34:49 +0000 (16:34 +0100)
Change-Id: Ic9e13d09ca7aeb77afdae6e5ccfb25952acd993f
Signed-off-by: tierno <alfonso.tiernosepulveda@telefonica.com>
1  2 
Dockerfile.local
osm_nbi/descriptor_topics.py
osm_nbi/html_out.py
osm_nbi/html_public/version
osm_nbi/instance_topics.py
osm_nbi/nbi.py
osm_nbi/tests/clear-all.sh
osm_nbi/tests/test.py
osm_nbi/validation.py

diff --combined Dockerfile.local
@@@ -6,7 -6,7 +6,7 @@@ FROM ubuntu:16.0
  WORKDIR /app/NBI/osm_nbi
  
  # Copy the current directory contents into the container at /app
- ADD . /app/NBI
+ #ADD . /app
  
  RUN apt-get update && apt-get install -y git python3 python3-jsonschema \
      python3-pymongo python3-yaml python3-pip python3-keystoneclient \
@@@ -56,7 -56,7 +56,7 @@@ ENV OSMNBI_DATABASE_HOS
  ENV OSMNBI_DATABASE_PORT                        27017
  # ENV OSMNBI_DATABASE_USER                      xxx
  # ENV OSMNBI_DATABASE_PASSWORD                  xxx
 -# ENV OSMNBI_DATABASE_MASTERPASSWORD            xxx
 +# ENV OSMNBI_DATABASE_COMMONKEY                 xxx
  # web
  ENV OSMNBI_STATIC_DIR                           /app/osm_nbi/html_public
  # logs
@@@ -67,7 -67,7 +67,7 @@@ ENV OSMNBI_MESSAGE_DRIVE
  ENV OSMNBI_MESSAGE_HOST                         kafka
  ENV OSMNBI_MESSAGE_PORT                         9092
  # logs
 -ENV OSMNBI_LOG_FILE                             /app/log/nbi.log
 +ENV OSMNBI_LOG_FILE                             /app/log/nbi.log
  ENV OSMNBI_LOG_LEVEL                            DEBUG
  # authentication
  ENV OSMNBI_AUTHENTICATION_BACKEND               internal
@@@ -80,6 -80,8 +80,8 @@@
  #ENV OSMNBI_AUTHENTICATION_SERVICE_PASSWORD      nbi
  #ENV OSMNBI_AUTHENTICATION_SERVICE_PROJECT       service
  
+ ADD . /app/NBI
  # Run app.py when the container launches
  CMD ["python3", "nbi.py"]
  
@@@ -23,29 -23,17 +23,29 @@@ class DescriptorTopic(BaseTopic)
          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:
 +        # 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)
 +        serialized = self._validate_input_new(final_content, 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
 +
 +        # 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["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):
@@@ -401,23 -389,6 +401,23 @@@ 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)
 +
 +        # 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, force=False):
          """
          Check that there is not any NSD that uses this VNFD. Only NSDs belonging to this project are considered. Note
              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
          indata = self.pyangbind_validation("vnfds", indata, force)
          # Cross references validation in the descriptor
 -        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"]["cp"]),
 +        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"]["cp"]),
 +                                          http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
  
          for vdu in get_iterable(indata.get("vdu")):
              for interface in get_iterable(vdu.get("interface")):
                                            "vnf-configuration:config-primitive:name"
                                            .format(sgd["name"], sca["vnf-config-primitive-name-ref"]),
                                            http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
 +        # TODO validata that if contains cloud-init-file or charms, have artifacts _admin.storage."pkg-dir" is not none
          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
  
  
@@@ -616,12 -586,13 +616,12 @@@ class NsdTopic(DescriptorTopic)
          return clean_indata
  
      def _validate_input_new(self, indata, force=False):
 -
 -        # TODO validate with pyangbind, serialize
          indata = self.pyangbind_validation("nsds", indata, force)
 +        # TODO validata that if contains cloud-init-file or charms, have artifacts _admin.storage."pkg-dir" is not none
          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):
      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)
  
 -        self._check_descriptor_dependencies(session, final_content)
 +        if not force:
 +            self._check_descriptor_dependencies(session, final_content)
  
      def check_conflict_on_del(self, session, _id, force=False):
          """
              raise EngineException("There is some NSR that depends on this NSD", 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:nst'):
+             clean_indata = clean_indata['nst:nst']
+         elif clean_indata.get('nst'):
+             clean_indata = clean_indata['nst']
+         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]
+         return clean_indata
+     def _validate_input_edit(self, indata, force=False):
+         # TODO validate with pyangbind, serialize
+         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
+         :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, write=False, show_all=True)
+             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)
+         self._check_descriptor_dependencies(session, final_content)
+     def check_conflict_on_del(self, session, _id, force=False):
+         """
+         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 _id: nst internal id
+         :param force: Avoid this checking
+         :return: None or raises EngineException with the conflict
+         """
+         # TODO: Check this method
+         if 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("nst", _filter)
+         
+         # Search NSIs using NST via nst-ref
+         _filter = self._get_project_filter(session, write=False, show_all=False)
+         _filter["nst-ref"] = nst["id"]
+         if self.db.get_list("nsis", _filter):
+             raise EngineException("There is some NSIS that depends on this NST", http_code=HTTPStatus.CONFLICT)
  class PduTopic(BaseTopic):
      topic = "pdus"
      topic_msg = "pdu"
  
      @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"]["operationalState"] = "ENABLED"
          content["_admin"]["usageState"] = "NOT_IN_USE"
  
      def check_conflict_on_del(self, session, _id, force=False):
diff --combined osm_nbi/html_out.py
@@@ -21,10 -21,11 +21,12 @@@ html_start = ""
        <a href="https://osm.etsi.org"> <img src="/osm/static/OSM-logo.png" height="42" width="100"
          style="vertical-align:middle"> </a>
        <a>( {} )</a>
 +      <a href="/osm/pdu/v1/pdu_descriptors">PDUs </a>
        <a href="/osm/vnfpkgm/v1/vnf_packages">VNFDs </a>
        <a href="/osm/nsd/v1/ns_descriptors">NSDs </a>
        <a href="/osm/nslcm/v1/ns_instances">NSs </a>
+       <a href="/osm/nst/v1/netslice_templates">NSTDs </a>
+       <a href="/osm/nsilcm/v1/netslice_instances">NSIs </a>
        <a href="/osm/admin/v1/users">USERs </a>
        <a href="/osm/admin/v1/projects">PROJECTs </a>
        <a href="/osm/admin/v1/tokens">TOKENs </a>
@@@ -102,6 -103,15 +104,15 @@@ html_nslcmop_body = ""
  </form>
  """
  
+ html_nsilcmop_body = """
+ <a href="/osm/nsilcm/v1/nsi_lcm_op_occs?nsiInstanceId={id}">nsilcm operations </a>
+ <form action="/osm/nsilcm/v1/netslice_instances/{id}/terminate" method="post" enctype="multipart/form-data">
+     <h3> <table style="border: 0;"> <tr>
+         <td> <input type="submit" value="Terminate"/> </td>
+     </tr> </table> </h3>
+ </form>
+ """
  
  def format(data, request, response, session):
      """
              body += html_upload_body.format(request.path_info, "VNFD")
          elif request.path_info == "/nsd/v1/ns_descriptors":
              body += html_upload_body.format(request.path_info + "_content", "NSD")
+         elif request.path_info == "/nst/v1/nst_templates":
+             body += html_upload_body.format(request.path_info + "_content", "NSTD")
          for k in data:
              if isinstance(k, dict):
                  data_id = k.pop("_id", None)
                      request.path_info.startswith("/nslcm/v1/ns_instances/"):
                  _id = request.path_info[request.path_info.rfind("/")+1:]
                  body += html_nslcmop_body.format(id=_id)
+             elif request.path_info.startswith("/nsilcm/v1/netslice_instances_content/") or \
+                     request.path_info.startswith("/nsilcm/v1/netslice_instances/"):
+                 _id = request.path_info[request.path_info.rfind("/")+1:]
+                 body += html_nsilcmop_body.format(id=_id)
          body += "<pre>" + html_escape(yaml.safe_dump(data, explicit_start=True, indent=4, default_flow_style=False)) + \
                  "</pre>"
      elif data is None:
@@@ -1,2 -1,2 +1,2 @@@
- 0.1.27
 -0.1.24
 -2018-11-02
++0.1.28
 +2018-11-16
@@@ -5,7 -5,7 +5,7 @@@ from uuid import uuid
  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
  
@@@ -126,19 -126,13 +126,19 @@@ class NsrTopic(BaseTopic)
                  "id": nsr_id,
                  "_id": nsr_id,
                  # "input-parameter": xpath, value,
 -                "ssh-authorized-key": ns_request.get("key-pair-ref"),
 +                "ssh-authorized-key": ns_request.get("key-pair-ref"),  # TODO remove
              }
              ns_request["nsr_id"] = nsr_id
 +            # Create vld
 +            if nsd.get("vld"):
 +                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})
  
              # Create VNFR
              needed_vnfds = {}
 -            for member_vnf in nsd["constituent-vnfd"]:
 +            for member_vnf in nsd.get("constituent-vnfd", ()):
                  vnfd_id = member_vnf["vnfd-id-ref"]
                  step = "getting vnfd id='{}' constituent-vnfd='{}' from database".format(
                      member_vnf["vnfd-id-ref"], member_vnf["member-vnf-index"])
                      "connection-point": [],
                      "ip-address": None,  # mgmt-interface filled by LCM
                  }
 +
 +                # Create vld
 +                if vnfd.get("internal-vld"):
 +                    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})
 +
 +                vnfd_mgmt_cp = vnfd["mgmt-interface"].get("cp")
                  for cp in vnfd.get("connection-point", ()):
                      vnf_cp = {
                          "name": cp["name"],
                          # vim-id  # TODO it would be nice having a vim port id
                      }
                      vnfr_descriptor["connection-point"].append(vnf_cp)
 -                for vdu in vnfd["vdu"]:
 +                for vdu in vnfd.get("vdu", ()):
                      vdur = {
                          "vdu-id-ref": vdu["id"],
                          # TODO      "name": ""     Name of the VDU in the VIM
                              # "ip-address", "mac-address" # filled by LCM
                              # vim-id  # TODO it would be nice having a vim port id
                          }
 +                        if vnfd_mgmt_cp and iface.get("external-connection-point-ref") == vnfd_mgmt_cp:
 +                            vdu_iface["mgmt-vnf"] = True
                          if iface.get("mgmt-interface"):
 -                            vdu_iface["mgmt-interface"] = True
 +                            vdu_iface["mgmt-interface"] = True  # TODO change to mgmt-vdu
 +
 +                        # look for network where this interface is connected
 +                        if iface.get("external-connection-point-ref"):
 +                            for nsd_vld in get_iterable(nsd.get("vld")):
 +                                for nsd_vld_cp in get_iterable(nsd_vld.get("vnfd-connection-point-ref")):
 +                                    if nsd_vld_cp.get("vnfd-connection-point-ref") == \
 +                                            iface["external-connection-point-ref"] and \
 +                                            nsd_vld_cp.get("member-vnf-index-ref") == member_vnf["member-vnf-index"]:
 +                                        vdu_iface["ns-vld-id"] = nsd_vld["id"]
 +                                        break
 +                                else:
 +                                    continue
 +                                break
 +                        elif iface.get("internal-connection-point-ref"):
 +                            for vnfd_ivld in get_iterable(vnfd.get("internal-vld")):
 +                                for vnfd_ivld_icp in get_iterable(vnfd_ivld.get("internal-connection-point")):
 +                                    if vnfd_ivld_icp.get("id-ref") == iface["internal-connection-point-ref"]:
 +                                        vdu_iface["vnf-vld-id"] = vnfd_ivld["id"]
 +                                        break
 +                                else:
 +                                    continue
 +                                break
  
                          vdur["interfaces"].append(vdu_iface)
                      count = vdu.get("count", 1)
@@@ -305,17 -266,6 +305,6 @@@ class NsLcmOpTopic(BaseTopic)
      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
                      raise EngineException("Invalid parameter vld:name='{}' is not present at nsd:vld".format(
                          in_vld["name"]))
  
 -    def _look_for_pdu(self, session, rollback, vnfr, vim_account):
 +    def _look_for_pdu(self, session, rollback, vnfr, vim_account, vnfr_update, vnfr_update_rollback):
          """
 -        Look for a free PDU in the catalog matching vdur type and interfaces. Fills vdur with ip_address information
 -        :param vdur: vnfr:vdur descriptor. It is modified with pdu interface info if pdu is found
 -        :param member_vnf_index: used just for logging. Target vnfd of nsd
 -        :param vim_account:
 -        :return: vder_update: dictionary to update vnfr:vdur with pdu info. In addition it modified choosen pdu to set
 -        at status IN_USE
 +        Look for a free PDU in the catalog matching vdur type and interfaces. Fills vnfr.vdur with the interface
 +        (ip_address, ...) information.
 +        Modifies PDU _admin.usageState to 'IN_USE'
 +        
 +        :param session: client session information
 +        :param rollback: list with the database modifications to rollback if needed
 +        :param vnfr: vnfr to be updated. It is modified with pdu interface info if pdu is found
 +        :param vim_account: vim_account where this vnfr should be deployed
 +        :param vnfr_update: dictionary filled by this method with changes to be done at database vnfr
 +        :param vnfr_update_rollback: dictionary filled by this method with original content of vnfr in case a rollback
 +                                     of the changed vnfr is needed
 +
 +        :return: List of PDU interfaces that are connected to an existing VIM network. Each item contains:
 +                 "vim-network-name": used at VIM
 +                  "name": interface name
 +                  "vnf-vld-id": internal VNFD vld where this interface is connected, or
 +                  "ns-vld-id": NSD vld where this interface is connected.
 +                  NOTE: One, and only one between 'vnf-vld-id' and 'ns-vld-id' contains a value. The other will be None
          """
 -        vnfr_update = {}
 -        rollback_vnfr = {}
 +
 +        ifaces_forcing_vim_network = []
          for vdur_index, vdur in enumerate(get_iterable(vnfr.get("vdur"))):
              if not vdur.get("pdu-type"):
                  continue
              pdu_type = vdur.get("pdu-type")
              pdu_filter = self._get_project_filter(session, write=True, show_all=True)
 -            pdu_filter["vim.vim_accounts"] = vim_account
 +            pdu_filter["vim_accounts"] = vim_account
              pdu_filter["type"] = pdu_type
              pdu_filter["_admin.operationalState"] = "ENABLED"
 -            pdu_filter["_admin.usageState"] = "NOT_IN_USE",
 +            pdu_filter["_admin.usageState"] = "NOT_IN_USE"
              # TODO feature 1417: "shared": True,
  
              available_pdus = self.db.get_list("pdus", pdu_filter)
                      break
              else:
                  raise EngineException(
 -                    "No PDU of type={} found for member_vnf_index={} at vim_account={} matching interface "
 -                    "names".format(vdur["vdu-id-ref"], vnfr["member-vnf-index-ref"], pdu_type))
 +                    "No PDU of type={} at vim_account={} found for member_vnf_index={}, vdu={} matching interface "
 +                    "names".format(pdu_type, vim_account, vnfr["member-vnf-index-ref"], vdur["vdu-id-ref"]))
  
              # step 2. Update pdu
              rollback_pdu = {
  
              # step 3. Fill vnfr info by filling vdur
              vdu_text = "vdur.{}".format(vdur_index)
 -            rollback_vnfr[vdu_text + ".pdu-id"] = None
 +            vnfr_update_rollback[vdu_text + ".pdu-id"] = None
              vnfr_update[vdu_text + ".pdu-id"] = pdu["_id"]
              for iface_index, vdur_interface in enumerate(vdur["interfaces"]):
                  for pdu_interface in pdu["interfaces"]:
                      if pdu_interface["name"] == vdur_interface["name"]:
                          iface_text = vdu_text + ".interfaces.{}".format(iface_index)
                          for k, v in pdu_interface.items():
 -                            vnfr_update[iface_text + ".{}".format(k)] = v
 -                            rollback_vnfr[iface_text + ".{}".format(k)] = vdur_interface.get(v)
 +                            if k in ("ip-address", "mac-address"):  # TODO: switch-xxxxx must be inserted
 +                                vnfr_update[iface_text + ".{}".format(k)] = v
 +                                vnfr_update_rollback[iface_text + ".{}".format(k)] = vdur_interface.get(v)
 +                        if pdu_interface.get("ip-address"):
 +                            if vdur_interface.get("mgmt-interface"):
 +                                vnfr_update_rollback[vdu_text + ".ip-address"] = vdur.get("ip-address")
 +                                vnfr_update[vdu_text + ".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"):
 +                            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")})
                          break
  
 -        if vnfr_update:
 -            self.db.set_one("vnfrs", {"_id": vnfr["_id"]}, vnfr_update)
 -            rollback.append({"topic": "vnfrs", "_id": vnfr["_id"], "operation": "set", "content": rollback_vnfr})
 -        return
 +        return ifaces_forcing_vim_network
  
      def _update_vnfrs(self, session, rollback, nsr, indata):
          vnfrs = None
              vnfr_update_rollback["vim-account-id"] = vnfr.get("vim-account-id")
  
              # get pdu
 -            self._look_for_pdu(session, rollback, vnfr, vim_account)
 -            # TODO change instantiation parameters to set network
 +            ifaces_forcing_vim_network = self._look_for_pdu(session, rollback, vnfr, vim_account, vnfr_update,
 +                                                            vnfr_update_rollback)
 +
 +            # updata database vnfr
 +            self.db.set_one("vnfrs", {"_id": vnfr["_id"]}, vnfr_update)
 +            rollback.append({"topic": "vnfrs", "_id": vnfr["_id"], "operation": "set", "content": vnfr_update_rollback})
  
 -    def _create_nslcmop(self, session, nsInstanceId, operation, params):
 +            # Update indada in case pdu forces to use a concrete vim-network-name
 +            # TODO check if user has already insert a vim-network-name and raises an error
 +            if not ifaces_forcing_vim_network:
 +                continue
 +            for iface_info in ifaces_forcing_vim_network:
 +                if iface_info.get("ns-vld-id"):
 +                    if "vld" not in indata:
 +                        indata["vld"] = []
 +                    indata["vld"].append({key: iface_info[key] for key in
 +                                          ("name", "vim-network-name", "vim-network-id") if iface_info.get(key)})
 +
 +                elif iface_info.get("vnf-vld-id"):
 +                    if "vnf" not in indata:
 +                        indata["vnf"] = []
 +                    indata["vnf"].append({
 +                        "member-vnf-index": member_vnf_index,
 +                        "internal-vld": [{key: iface_info[key] for key in
 +                                          ("name", "vim-network-name", "vim-network-id") if iface_info.get(key)}]
 +                    })
 +
 +    @staticmethod
 +    def _create_nslcmop(nsr_id, operation, params):
 +        """
 +        Creates a ns-lcm-opp content to be stored at database.
 +        :param nsr_id: internal id of the instance
 +        :param operation: instantiate, terminate, scale, action, ...
 +        :param params: user parameters for the operation
 +        :return: dictionary following SOL005 format
 +        """
          now = time()
          _id = str(uuid4())
          nslcmop = {
              "_id": _id,
              "operationState": "PROCESSING",  # COMPLETED,PARTIALLY_COMPLETED,FAILED_TEMP,FAILED,ROLLING_BACK,ROLLED_BACK
              "statusEnteredTime": now,
 -            "nsInstanceId": nsInstanceId,
 +            "nsInstanceId": nsr_id,
              "lcmOperationType": operation,
              "startTime": now,
              "isAutomaticInvocation": False,
              "isCancelPending": False,
              "links": {
                  "self": "/osm/nslcm/v1/ns_lcm_op_occs/" + _id,
 -                "nsInstance": "/osm/nslcm/v1/ns_instances/" + nsInstanceId,
 +                "nsInstance": "/osm/nslcm/v1/ns_instances/" + nsr_id,
              }
          }
          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
                      raise EngineException("ns_instance '{}' cannot be '{}' because it is already instantiated".format(
                          nsInstanceId, operation), HTTPStatus.CONFLICT)
              self._check_ns_operation(session, nsr, operation, indata)
 +
              if operation == "instantiate":
                  self._update_vnfrs(session, rollback, nsr, indata)
 -            nslcmop_desc = self._create_nslcmop(session, nsInstanceId, operation, indata)
 +
 +            nslcmop_desc = self._create_nslcmop(nsInstanceId, operation, indata)
              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)
  
      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
+             self.logger.info(str(slice_request))
+             step = "getting nstd id='{}' from database".format(slice_request.get("nstdId"))
+             _filter = {"id": slice_request["nstdId"]}
+             _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"
+             # "instantiation-parameters.netslice-subnet": []
+             # TODO: Equal as template for now
+             nsi_descriptor = {
+                 "id": nsi_id,
+                 "nst-ref": nstd["id"],
+                 "instantiation-parameters": {
+                     "netslice-subnet": []
+                 },
+                 "network-slice-template": nstd,
+                 "_id": nsi_id,
+             }
+             # 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"])
+             step = "creating nsi at database"
+             self.format_on_new(nsi_descriptor, session["project_id"], make_public=make_public)
+             nsi_descriptor["_admin"]["nsiState"] = "NOT_INSTANTIATED"          
+             # creates Network Services records (NSRs)
+             step = "creating nsrs at database using NsrTopic.new()"
+             nsrs_list = []
+             for service in services:
+                 indata_ns = {}
+                 indata_ns["nsdId"] = service["_id"]
+                 indata_ns["nsName"] = service["name"]
+                 indata_ns["vimAccountId"] = indata.get("vimAccountId")
+                 indata_ns["nsDescription"] = service["description"]
+                 indata_ns["key-pair-ref"] = None
+                 # NsrTopic(rollback, session, indata_ns, kwargs, headers, force)
+                 _id_nsr = NsrTopic.new(self, rollback, session, indata_ns, kwargs, headers, force)
+                 nsrs_item = {"nsrId": _id_nsr}
+                 nsrs_list.append(nsrs_item)
+             # Adding the nsrs list to the nsi
+             nsi_descriptor["_admin"]["nsrs-detailed-list"] = nsrs_list
+             # 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(nsId):
+             # TODO change to vnfR (??)
+             for ns in nstd["netslice-subnet"]:
+                 if nsId == ns["id"]:
+                     nsd_id = ns["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 nsId='{}' is not one of the "
+                                       "nst:netslice-subnet".format(nsId))
+         if operation == "instantiate":
+             # check the existance of netslice-subnet items
+             for in_nst in get_iterable(indata.get("netslice-subnet")):           
+                 nstd = check_valid_netslice_subnet_id(in_nst["nsdId"])
+     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 self.delete(session, nsiInstanceId)
+                 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)
diff --combined osm_nbi/nbi.py
@@@ -14,7 -14,6 +14,7 @@@ import sy
  from authconn import AuthException
  from auth import Authenticator
  from engine import Engine, EngineException
 +from validation import ValidationError
  from osm_common.dbbase import DbException
  from osm_common.fsbase import FsException
  from osm_common.msgbase import MsgException
@@@ -33,7 -32,7 +33,7 @@@ auth_database_version = '1.0
  """
  North Bound Interface  (O: OSM specific; 5,X: SOL005 not implemented yet; O5: SOL005 implemented)
  URL: /osm                                                       GET     POST    PUT     DELETE  PATCH
-         /nsd/v1                                                 O       O
+         /nsd/v1
              /ns_descriptors_content                             O       O
                  /<nsdInfoId>                                    O       O       O       O
              /ns_descriptors                                     O5      O5
                  /<vnfInstanceId>                                O
              /subscriptions                                      5       5
                  /<subscriptionId>                               5                       X
          /pdu/v1
              /pdu_descriptor                                     O       O
                  /<id>                                           O               O       O       O
          /admin/v1
              /tokens                                             O       O
                  /<id>                                           O                       O
              /sdns                                               O       O
                  /<id>                                           O                       O       O
  
+         /nst/v1                                                 O       O
+             /netslice_templates_content                         O       O
+                 /<nstInfoId>                                    O       O       O       O
+             /netslice_templates                                 O       O
+                 /<nstInfoId>                                    O                       O       O
+                     /nst_content                                O               O
+                     /nst                                        O
+                     /artifacts[/<artifactPath>]                 O
+             /subscriptions                                      X       X
+                 /<subscriptionId>                               X                       X
+         /nsilcm/v1
+             /netslice_instances_content                         O       O
+                 /<SliceInstanceId>                              O                       O
+             /netslice_instances                                 O       O
+                 /<SliceInstanceId>                              O                       O
+                     instantiate                                         O
+                     terminate                                           O
+                     action                                              O
+             /nsi_lcm_op_occs                                    O       O
+                 /<nsiLcmOpOccId>                                O                       O       O
+             /subscriptions                                      X       X
+                 /<subscriptionId>                               X                       X
  query string:
      Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force.
 +        simpleFilterExpr := <attrName>["."<attrName>]*["."<op>]"="<value>[","<value>]*
 +        filterExpr := <simpleFilterExpr>["&"<simpleFilterExpr>]*
 +        op := "eq" | "neq" (or "ne") | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont"
 +        attrName := string
      For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any
      item of the array, that is, pass if any item of the array pass the filter.
      It allows both ne and neq for not equal
@@@ -193,7 -214,7 +219,7 @@@ class Server(object)
                                                 "<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
                                                 },
                      "ns_descriptors": {"METHODS": ("GET", "POST"),
 -                                       "<ID>": {"METHODS": ("GET", "DELETE"), "TODO": "PATCH",
 +                                       "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
                                                  "nsd_content": {"METHODS": ("GET", "PUT")},
                                                  "nsd": {"METHODS": "GET"},  # descriptor inside package
                                                  "artifacts": {"*": {"METHODS": "GET"}}
                                        },
                  }
              },
+             "nst": {
+                 "v1": {
+                     "netslice_templates_content": {"METHODS": ("GET", "POST"),
+                                                    "<ID>": {"METHODS": ("GET", "PUT", "DELETE")}
+                                                    },
+                     "netslice_templates": {"METHODS": ("GET", "POST"),
+                                            "<ID>": {"METHODS": ("GET", "DELETE"), "TODO": "PATCH",
+                                                     "nst_content": {"METHODS": ("GET", "PUT")},
+                                                     "nst": {"METHODS": "GET"},  # descriptor inside package
+                                                     "artifacts": {"*": {"METHODS": "GET"}}
+                                                     }
+                                            },
+                     "subscriptions": {"TODO": ("GET", "POST"),
+                                       "<ID>": {"TODO": ("GET", "DELETE")}
+                                       },
+                 }
+             },
+             "nsilcm": {
+                 "v1": {
+                     "netslice_instances_content": {"METHODS": ("GET", "POST"),
+                                                    "<ID>": {"METHODS": ("GET", "DELETE")}
+                                                    },
+                     "netslice_instances": {"METHODS": ("GET", "POST"),
+                                            "<ID>": {"METHODS": ("GET", "DELETE"),
+                                                     "terminate": {"METHODS": "POST"},
+                                                     "instantiate": {"METHODS": "POST"},
+                                                     "action": {"METHODS": "POST"},
+                                                     }
+                                            },
+                     "nsi_lcm_op_occs": {"METHODS": "GET",
+                                         "<ID>": {"METHODS": "GET"},
+                                         },
+                 }
+             },
          }
  
      def _format_in(self, kwargs):
              if not main_topic or not version or not topic:
                  raise NbiException("URL must contain at least 'main_topic/version/topic'",
                                     HTTPStatus.METHOD_NOT_ALLOWED)
-             if main_topic not in ("admin", "vnfpkgm", "nsd", "nslcm", "pdu"):
 -            if main_topic not in ("admin", "vnfpkgm", "nsd", "nslcm", "nst", "nsilcm"):
++            if main_topic not in ("admin", "vnfpkgm", "nsd", "nslcm", "pdu", "nst", "nsilcm"):
                  raise NbiException("URL main_topic '{}' not supported".format(main_topic),
                                     HTTPStatus.METHOD_NOT_ALLOWED)
              if version != 'v1':
                  force = kwargs.pop("FORCE")
              else:
                  force = False
              self._check_valid_url_method(method, main_topic, version, topic, _id, item, *args)
              if main_topic == "admin" and topic == "tokens":
                  return self.token(method, _id, kwargs)
  
                      engine_topic = "nslcmops"
                  if topic == "vnfrs" or topic == "vnf_instances":
                      engine_topic = "vnfrs"
+             elif main_topic == "nst":
+                 engine_topic = "nsts"
+             elif main_topic == "nsilcm":
+                 engine_topic = "nsis"
+                 if topic == "nsi_lcm_op_occs":
+                     engine_topic = "nsilcmops" 
              elif main_topic == "pdu":
                  engine_topic = "pdus"
              if engine_topic == "vims":   # TODO this is for backward compatibility, it will remove in the future
                  engine_topic = "vim_accounts"
  
              if method == "GET":
-                 if item in ("nsd_content", "package_content", "artifacts", "vnfd", "nsd"):
-                     if item in ("vnfd", "nsd"):
+                 if item in ("nsd_content", "package_content", "artifacts", "vnfd", "nsd", "nst", "nst_content"):
+                     if item in ("vnfd", "nsd", "nst"):
                          path = "$DESCRIPTOR"
                      elif args:
                          path = args
                  else:
                      outdata = self.engine.get_item(session, engine_topic, _id)
              elif method == "POST":
-                 if topic in ("ns_descriptors_content", "vnf_packages_content"):
+                 if topic in ("ns_descriptors_content", "vnf_packages_content", "netslice_templates_content"):
                      _id = cherrypy.request.headers.get("Transaction-Id")
                      if not _id:
                          _id = self.engine.new_item(rollback, session, engine_topic, {}, None, cherrypy.request.headers,
                      self._set_location_header(main_topic, version, "ns_lcm_op_occs", _id)
                      outdata = {"id": _id}
                      cherrypy.response.status = HTTPStatus.ACCEPTED.value
+                 elif topic == "netslice_instances_content":
+                     # creates NetSlice_Instance_record (NSIR)
+                     _id = self.engine.new_item(rollback, session, engine_topic, indata, kwargs, force=force)
+                     self._set_location_header(main_topic, version, topic, _id)
+                     indata["lcmOperationType"] = "instantiate"
+                     indata["nsiInstanceId"] = _id
+                     self.engine.new_item(rollback, session, "nsilcmops", indata, kwargs)
+                     outdata = {"id": _id}
+                     
+                 elif topic == "netslice_instances" and item:
+                     indata["lcmOperationType"] = item
+                     indata["nsiInstanceId"] = _id
+                     _id = self.engine.new_item(rollback, session, "nsilcmops", indata, kwargs)
+                     self._set_location_header(main_topic, version, "nsi_lcm_op_occs", _id)
+                     outdata = {"id": _id}
+                     cherrypy.response.status = HTTPStatus.ACCEPTED.value
                  else:
                      _id = self.engine.new_item(rollback, session, engine_topic, indata, kwargs,
                                                 cherrypy.request.headers, force=force)
                          opp_id = self.engine.new_item(rollback, session, "nslcmops", nslcmop_desc, None)
                          outdata = {"_id": opp_id}
                          cherrypy.response.status = HTTPStatus.ACCEPTED.value
+                     elif topic == "netslice_instances_content" and not force:
+                         nsilcmop_desc = {
+                             "lcmOperationType": "terminate",
+                             "nsiInstanceId": _id,
+                             "autoremove": True
+                         }
+                         opp_id = self.engine.new_item(rollback, session, "nsilcmops", nsilcmop_desc, None)
+                         outdata = {"_id": opp_id}
+                         cherrypy.response.status = HTTPStatus.ACCEPTED.value
                      else:
                          self.engine.del_item(session, engine_topic, _id, force)
                          cherrypy.response.status = HTTPStatus.NO_CONTENT.value
                  if not indata and not kwargs:
                      raise NbiException("Nothing to update. Provide payload and/or query string",
                                         HTTPStatus.BAD_REQUEST)
-                 if item in ("nsd_content", "package_content") and method == "PUT":
+                 if item in ("nsd_content", "package_content", "nst_content") and method == "PUT":
                      completed = self.engine.upload_content(session, engine_topic, _id, indata, kwargs,
                                                             cherrypy.request.headers, force=force)
                      if not completed:
                  raise NbiException("Method {} not allowed".format(method), HTTPStatus.METHOD_NOT_ALLOWED)
              return self._format_out(outdata, session, _format)
          except Exception as e:
 -            if isinstance(e, (NbiException, EngineException, DbException, FsException, MsgException, AuthException)):
 +            if isinstance(e, (NbiException, EngineException, DbException, FsException, MsgException, AuthException,
 +                              ValidationError)):
                  http_code_value = cherrypy.response.status = e.http_code.value
                  http_code_name = e.http_code.name
                  cherrypy.log("Exception {}".format(e))
@@@ -66,7 -66,7 +66,7 @@@ the
      done
  fi
  
- for item in vim_accounts sdns nsrs vnfrs nslcmops nsds vnfds projects pdus # vims
 -for item in vim_accounts sdns nsrs vnfrs nslcmops nsds vnfds projects nsts nsis nsilcmops # vims
++for item in vim_accounts sdns nsrs vnfrs nslcmops nsds vnfds projects pdus nsts nsis nsilcmops # vims
  do
      curl --insecure ${OSMNBI_URL}/test/db-clear/${item}
      echo " ${item}"
diff --combined osm_nbi/tests/test.py
@@@ -116,15 -116,10 +116,15 @@@ class TestRest
          # contains ID of tests obtained from Location response header. "" key contains last obtained id
          self.test_ids = {}
          self.old_test_description = ""
 +        self.test_name = None
 +        self.step = 0
  
      def set_header(self, header):
          self.s.headers.update(header)
  
 +    def set_tet_name(self, test_name):
 +        self.test_name = test_name
 +
      def unset_header(self, key):
          if key in self.s.headers:
              del self.s.headers[key]
                  _id = location[location.rfind("/") + 1:]
                  if _id:
                      self.test_ids[name] = str(_id)
 +                    self.test_ids["last_id"] = str(_id)  # last id
                      self.test_ids[""] = str(_id)  # last id
              return r
          except TestException as e:
@@@ -593,7 -587,6 +593,7 @@@ class TestDeploy
          self.descriptor_url = "https://osm-download.etsi.org/ftp/osm-3.0-three/2nd-hackfest/packages/"
          self.vnfd_filenames = ("cirros_vnf.tar.gz",)
          self.nsd_filename = "cirros_2vnf_ns.tar.gz"
 +        self.descriptor_edit = None
          self.uses_configuration = False
          self.uss = {}
          self.passwds = {}
          self.timeout = 120
          self.passed_tests = 0
          self.total_tests = 0
 +        self.qforce = ""
  
      def create_descriptors(self, engine):
          temp_dir = os.path.dirname(os.path.abspath(__file__)) + "/temp/"
          if not os.path.exists(temp_dir):
              os.makedirs(temp_dir)
 -        for vnfd_filename in self.vnfd_filenames:
 +        for vnfd_index, vnfd_filename in enumerate(self.vnfd_filenames):
              if "/" in vnfd_filename:
                  vnfd_filename_path = vnfd_filename
                  if not os.path.exists(vnfd_filename_path):
                  # vnfd CREATE AND UPLOAD in one step:
                  test_name = "DEPLOY{}".format(self.step)
                  engine.test(test_name, "Onboard VNFD in one step", "POST",
 -                            "/vnfpkgm/v1/vnf_packages_content", headers, "@b" + vnfd_filename_path, 201,
 -                            {"Location": "/vnfpkgm/v1/vnf_packages_content/", "Content-Type": "application/yaml"}, yaml)
 +                            "/vnfpkgm/v1/vnf_packages_content" + self.qforce, headers, "@b" + vnfd_filename_path, 201,
 +                            {"Location": "/vnfpkgm/v1/vnf_packages_content/", "Content-Type": "application/yaml"},
 +                            "yaml")
                  self.vnfds_test.append(test_name)
 -                self.vnfds_id.append(engine.test_ids[test_name])
 +                self.vnfds_id.append(engine.test_ids["last_id"])
                  self.step += 1
              else:
                  # vnfd CREATE AND UPLOAD ZIP
                              headers_json, None, 201,
                              {"Location": "/vnfpkgm/v1/vnf_packages/", "Content-Type": "application/json"}, "json")
                  self.vnfds_test.append(test_name)
 -                self.vnfds_id.append(engine.test_ids[test_name])
 +                self.vnfds_id.append(engine.test_ids["last_id"])
                  self.step += 1
                  # location = r.headers["Location"]
                  # vnfd_id = location[location.rfind("/")+1:]
                  engine.test("DEPLOY{}".format(self.step), "Onboard VNFD step 2 as ZIP", "PUT",
 -                            "/vnfpkgm/v1/vnf_packages/<>/package_content",
 +                            "/vnfpkgm/v1/vnf_packages/<>/package_content" + self.qforce,
                              headers, "@b" + vnfd_filename_path, 204, None, 0)
                  self.step += 2
  
 +            if self.descriptor_edit:
 +                if "vnfd{}".format(vnfd_index) in self.descriptor_edit:
 +                    # Modify VNFD
 +                    engine.test("DEPLOY{}".format(self.step), "Edit VNFD ", "PATCH",
 +                                "/vnfpkgm/v1/vnf_packages/{}".format(self.vnfds_id[-1]),
 +                                headers_yaml, self.descriptor_edit["vnfd{}".format(vnfd_index)], 204, None, None)
 +                    self.step += 1
 +
          if "/" in self.nsd_filename:
              nsd_filename_path = self.nsd_filename
              if not os.path.exists(nsd_filename_path):
          if self.step % 2 == 0:
              # nsd CREATE AND UPLOAD in one step:
              engine.test("DEPLOY{}".format(self.step), "Onboard NSD in one step", "POST",
 -                        "/nsd/v1/ns_descriptors_content", headers, "@b" + nsd_filename_path, 201,
 +                        "/nsd/v1/ns_descriptors_content" + self.qforce, headers, "@b" + nsd_filename_path, 201,
                          {"Location": "/nsd/v1/ns_descriptors_content/", "Content-Type": "application/yaml"}, yaml)
              self.step += 1
 +            self.nsd_id = engine.test_ids["last_id"]
          else:
              # nsd CREATE AND UPLOAD ZIP
              engine.test("DEPLOY{}".format(self.step), "Onboard NSD step 1", "POST", "/nsd/v1/ns_descriptors",
                          headers_json, None, 201,
                          {"Location": "/nsd/v1/ns_descriptors/", "Content-Type": "application/json"}, "json")
              self.step += 1
 +            self.nsd_id = engine.test_ids["last_id"]
              # location = r.headers["Location"]
              # vnfd_id = location[location.rfind("/")+1:]
              engine.test("DEPLOY{}".format(self.step), "Onboard NSD step 2 as ZIP", "PUT",
 -                        "/nsd/v1/ns_descriptors/<>/nsd_content",
 +                        "/nsd/v1/ns_descriptors/<>/nsd_content" + self.qforce,
                          headers, "@b" + nsd_filename_path, 204, None, 0)
              self.step += 2
 -        self.nsd_id = engine.test_ids[self.nsd_test]
 +
 +        if self.descriptor_edit and "nsd" in self.descriptor_edit:
 +            # Modify NSD
 +            engine.test("DEPLOY{}".format(self.step), "Edit NSD ", "PATCH",
 +                        "/nsd/v1/ns_descriptors/{}".format(self.nsd_id),
 +                        headers_yaml, self.descriptor_edit["nsd"], 204, None, None)
 +            self.step += 1
  
      def delete_descriptors(self, engine):
          # delete descriptors
          engine.test("DEPLOY{}".format(self.step), "Delete NSSD SOL005", "DELETE",
 -                    "/nsd/v1/ns_descriptors/<{}>".format(self.nsd_test),
 +                    "/nsd/v1/ns_descriptors/{}".format(self.nsd_id),
                      headers_yaml, None, 204, None, 0)
          self.step += 1
 -        for vnfd_test in self.vnfds_test:
 +        for vnfd_id in self.vnfds_id:
              engine.test("DEPLOY{}".format(self.step), "Delete VNFD SOL005", "DELETE",
 -                        "/vnfpkgm/v1/vnf_packages/<{}>".format(vnfd_test), headers_yaml, None, 204, None, 0)
 +                        "/vnfpkgm/v1/vnf_packages/{}".format(vnfd_id), headers_yaml, None, 204, None, 0)
              self.step += 1
  
      def instantiate(self, engine, ns_data):
                          headers_yaml, ns_data_text, 201,
                          {"Location": "nslcm/v1/ns_instances/", "Content-Type": "application/yaml"}, "yaml")
          self.ns_test = "DEPLOY{}".format(self.step)
 -        self.ns_id = engine.test_ids[self.ns_test]
 -        engine.test_ids[self.ns_test]
 +        self.ns_id = engine.test_ids["last_id"]
          self.step += 1
          r = engine.test("DEPLOY{}".format(self.step), "Instantiate NS step 2", "POST",
                          "/nslcm/v1/ns_instances/<{}>/instantiate".format(self.ns_test), headers_yaml, ns_data_text,
              from pssh.utils import load_private_key
              from ssh2 import exceptions as ssh2Exception
          except ImportError as e:
 -            logger.critical("package <pssh> or/and <urllib3> is not installed. Please add it with 'pip3 install "
 -                            "parallel-ssh' and/or 'pip3 install urllib3': {}".format(e))
 +            logger.critical("Package <pssh> or/and <urllib3> is not installed. Please add them with 'pip3 install "
 +                            "parallel-ssh urllib3': {}".format(e))
          urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
          try:
              p_host = os.environ.get("PROXY_HOST")
  
          if manual_check:
              input('NS has been deployed. Perform manual check and press enter to resume')
 -        else:
 +        elif test_osm:
              self.test_ns(engine, test_osm, self.cmds, self.uss, self.pss, self.keys, self.timeout)
          self.aditional_operations(engine, test_osm, manual_check)
          self.terminate(engine)
@@@ -1213,7 -1189,7 +1213,7 @@@ class TestDeployHackfest3Charmed(TestDe
          if manual_check:
              input('NS service primitive has been executed. Check that file /home/ubuntu/OSMTESTNBI is present at '
                    'TODO_PUT_IP')
 -        else:
 +        elif test_osm:
              cmds = {'1': [''], '2': ['ls -lrt /home/ubuntu/OSMTESTNBI', ]}
              uss = {'1': "ubuntu", '2': "ubuntu"}
              pss = {'1': "osm4u", '2': "osm4u"}
          # # TODO check automatic
  
  
 +class TestDeploySingleVdu(TestDeployHackfest3Charmed):
 +    description = "Generate a single VDU base on editing Hackfest3Charmed descriptors and deploy"
 +
 +    def __init__(self):
 +        super().__init__()
 +        self.qforce = "?FORCE=True"
 +        self.descriptor_edit = {
 +            # Modify VNFD to remove one VDU
 +            "vnfd0": {
 +                "vdu": {
 +                    "$[0]": {
 +                        "interface": {"$[0]": {"external-connection-point-ref": "pdu-mgmt"}}
 +                    },
 +                    "$[1]": None
 +                },
 +                "vnf-configuration": None,
 +                "connection-point": {
 +                    "$[0]": {
 +                        "id": "pdu-mgmt",
 +                        "name": "pdu-mgmt",
 +                        "short-name": "pdu-mgmt"
 +                    },
 +                    "$[1]": None
 +                },
 +                "mgmt-interface": {"cp": "pdu-mgmt"},
 +                "description": "A vnf single vdu to be used as PDU",
 +                "id": "vdu-as-pdu",
 +                "internal-vld": {
 +                    "$[0]": {
 +                        "id": "pdu_internal",
 +                        "name": "pdu_internal",
 +                        "internal-connection-point": {"$[1]": None},
 +                        "short-name": "pdu_internal",
 +                        "type": "ELAN"
 +                    }
 +                }
 +            },
 +
 +            # Modify NSD accordingly
 +            "nsd": {
 +                "constituent-vnfd": {
 +                    "$[0]": {"vnfd-id-ref": "vdu-as-pdu"},
 +                    "$[1]": None,
 +                },
 +                "description": "A nsd to deploy the vnf to act as as PDU",
 +                "id": "nsd-as-pdu",
 +                "name": "nsd-as-pdu",
 +                "short-name": "nsd-as-pdu",
 +                "vld": {
 +                    "$[0]": {
 +                        "id": "mgmt_pdu",
 +                        "name": "mgmt_pdu",
 +                        "short-name": "mgmt_pdu",
 +                        "vnfd-connection-point-ref": {
 +                            "$[0]": {
 +                                "vnfd-connection-point-ref": "pdu-mgmt",
 +                                "vnfd-id-ref": "vdu-as-pdu",
 +                            },
 +                            "$[1]": None
 +                        },
 +                        "type": "ELAN"
 +                    },
 +                    "$[1]": None,
 +                }
 +            }
 +        }
 +
 +
 +class TestDeployHnfd(TestDeployHackfest3Charmed):
 +    description = "Generate a HNFD base on editing Hackfest3Charmed descriptors and deploy"
 +
 +    def __init__(self):
 +        super().__init__()
 +        self.pduDeploy = TestDeploySingleVdu()
 +        self.pdu_interface_0 = {}
 +        self.pdu_interface_1 = {}
 +
 +        self.pdu_id = None
 +        # self.vnf_to_pdu = """
 +        #     vdu:
 +        #         "$[0]":
 +        #             pdu-type: PDU-TYPE-1
 +        #             interface:
 +        #                 "$[0]":
 +        #                     name: mgmt-iface
 +        #                 "$[1]":
 +        #                     name: pdu-iface-internal
 +        #     id: hfn1
 +        #     description: HFND, one PDU + One VDU
 +        #     name: hfn1
 +        #     short-name: hfn1
 +        #
 +        # """
 +
 +        self.pdu_descriptor = {
 +            "name": "my-PDU",
 +            "type": "PDU-TYPE-1",
 +            "vim_accounts": "to-override",
 +            "interfaces": [
 +                {
 +                    "name": "mgmt-iface",
 +                    "mgmt": True,
 +                    "type": "overlay",
 +                    "ip-address": "to override",
 +                    "mac-address": "mac_address",
 +                    "vim-network-name": "mgmt",
 +                },
 +                {
 +                    "name": "pdu-iface-internal",
 +                    "mgmt": False,
 +                    "type": "overlay",
 +                    "ip-address": "to override",
 +                    "mac-address": "mac_address",
 +                    "vim-network-name": "pdu_internal",  # OSMNBITEST-PDU-pdu_internal
 +                },
 +            ]
 +        }
 +        self.vnfd_filenames = ("hackfest_3charmed_vnfd.tar.gz", "hackfest_3charmed_vnfd.tar.gz")
 +
 +        self.descriptor_edit = {
 +            "vnfd0": {
 +                "id": "hfnd1",
 +                "name": "hfn1",
 +                "short-name": "hfn1",
 +                "vdu": {
 +                    "$[0]": {
 +                        "pdu-type": "PDU-TYPE-1",
 +                        "interface": {
 +                            "$[0]": {"name": "mgmt-iface"},
 +                            "$[1]": {"name": "pdu-iface-internal"},
 +                        }
 +                    }
 +                }
 +            },
 +            "nsd": {
 +                "constituent-vnfd": {
 +                    "$[1]": {"vnfd-id-ref": "hfnd1"}
 +                }
 +            }
 +        }
 +
 +    def create_descriptors(self, engine):
 +        super().create_descriptors(engine)
 +
 +        # Create PDU
 +        self.pdu_descriptor["interfaces"][0].update(self.pdu_interface_0)
 +        self.pdu_descriptor["interfaces"][1].update(self.pdu_interface_1)
 +        self.pdu_descriptor["vim_accounts"] = [self.vim_id]
 +        # TODO get vim-network-name from vnfr.vld.name
 +        self.pdu_descriptor["interfaces"][1]["vim-network-name"] = "{}-{}-{}".format(
 +            os.environ.get("OSMNBITEST_NS_NAME", "OSMNBITEST"),
 +            "PDU", self.pdu_descriptor["interfaces"][1]["vim-network-name"])
 +        test_name = "DEPLOY{}".format(self.step)
 +        engine.test(test_name, "Onboard PDU descriptor", "POST", "/pdu/v1/pdu_descriptors",
 +                    {"Location": "/pdu/v1/pdu_descriptors/", "Content-Type": "application/yaml"}, self.pdu_descriptor,
 +                    201, r_header_yaml, "yaml")
 +        self.pdu_id = engine.test_ids["last_id"]
 +        self.step += 1
 +
 +    def run(self, engine, test_osm, manual_check, test_params=None):
 +        engine.get_autorization()
 +        nsname = os.environ.get("OSMNBITEST_NS_NAME", "OSMNBITEST")
 +
 +        # create real VIM if not exist
 +        self.vim_id = engine.get_create_vim(test_osm)
 +        # instanciate PDU
 +        self.pduDeploy.create_descriptors(engine)
 +        self.pduDeploy.instantiate(engine, {"nsDescription": "to be used as PDU", "nsName": nsname + "-PDU",
 +                                            "nsdId": self.pduDeploy.nsd_id, "vimAccountId": self.vim_id})
 +        if manual_check:
 +            input('VNF to be used as PDU has been deployed. Perform manual check and press enter to resume')
 +        elif test_osm:
 +            self.pduDeploy.test_ns(engine, test_osm, self.pduDeploy.cmds, self.pduDeploy.uss, self.pduDeploy.pss,
 +                                   self.pduDeploy.keys, self.pduDeploy.timeout)
 +
 +        if test_osm:
 +            r = engine.test("DEPLOY{}".format(self.step), "GET IP_ADDRESS OF VNFR", "GET",
 +                            "/nslcm/v1/vnfrs?nsr-id-ref={}".format(self.pduDeploy.ns_id), headers_json, None,
 +                            200, r_header_json, "json")
 +            self.step += 1
 +            vnfr_data = r.json()
 +            # print(vnfr_data)
 +
 +            self.pdu_interface_0["ip-address"] = vnfr_data[0]["vdur"][0]["interfaces"][0].get("ip-address")
 +            self.pdu_interface_1["ip-address"] = vnfr_data[0]["vdur"][0]["interfaces"][1].get("ip-address")
 +            self.pdu_interface_0["mac-address"] = vnfr_data[0]["vdur"][0]["interfaces"][0].get("mac-address")
 +            self.pdu_interface_1["mac-address"] = vnfr_data[0]["vdur"][0]["interfaces"][1].get("mac-address")
 +            if not self.pdu_interface_0["ip-address"]:
 +                raise TestException("Vnfr has not managment ip address")
 +        else:
 +            self.pdu_interface_0["ip-address"] = "192.168.10.10"
 +            self.pdu_interface_1["ip-address"] = "192.168.11.10"
 +            self.pdu_interface_0["mac-address"] = "52:33:44:55:66:13"
 +            self.pdu_interface_1["mac-address"] = "52:33:44:55:66:14"
 +
 +        self.create_descriptors(engine)
 +
 +        ns_data = {"nsDescription": "default description", "nsName": nsname, "nsdId": self.nsd_id,
 +                   "vimAccountId": self.vim_id}
 +        if test_params and test_params.get("ns-config"):
 +            if isinstance(test_params["ns-config"], str):
 +                ns_data.update(yaml.load(test_params["ns-config"]))
 +            else:
 +                ns_data.update(test_params["ns-config"])
 +
 +        self.instantiate(engine, ns_data)
 +        if manual_check:
 +            input('NS has been deployed. Perform manual check and press enter to resume')
 +        elif test_osm:
 +            self.test_ns(engine, test_osm, self.cmds, self.uss, self.pss, self.keys, self.timeout)
 +        self.aditional_operations(engine, test_osm, manual_check)
 +        self.terminate(engine)
 +        self.pduDeploy.terminate(engine)
 +        self.delete_descriptors(engine)
 +        self.pduDeploy.delete_descriptors(engine)
 +
 +        self.step += 1
 +
 +        self.print_results()
 +
 +    def delete_descriptors(self, engine):
 +        super().delete_descriptors(engine)
 +        # delete pdu
 +        engine.test("DEPLOY{}".format(self.step), "Delete PDU SOL005", "DELETE",
 +                    "/pdu/v1/pdu_descriptors/{}".format(self.pdu_id),
 +                    headers_yaml, None, 204, None, 0)
 +
 +
  class TestDescriptors:
      description = "Test VNFD, NSD, PDU descriptors CRUD and dependencies"
  
          engine.test(test_name, "Onboard VNFD in one step", "POST",
                      "/vnfpkgm/v1/vnf_packages_content", headers_zip_yaml, "@b" + vnfd_filename_path, 201,
                      {"Location": "/vnfpkgm/v1/vnf_packages_content/", "Content-Type": "application/yaml"}, "yaml")
 -        self.vnfd_id = engine.test_ids[test_name]
 +        self.vnfd_id = engine.test_ids["last_id"]
          self.step += 1
  
          # get vnfd descriptor
          engine.test(test_name, "Onboard NSD in one step", "POST",
                      "/nsd/v1/ns_descriptors_content", headers_zip_yaml, "@b" + nsd_filename_path, 201,
                      {"Location": "/nsd/v1/ns_descriptors_content/", "Content-Type": "application/yaml"}, "yaml")
 -        self.nsd_id = engine.test_ids[test_name]
 +        self.nsd_id = engine.test_ids["last_id"]
          self.step += 1
  
          # get nsd descriptor
          self.step += 1
  
  
+ class TestNstTemplates:
+     description = "Upload a NST to OSM"
+     def __init__(self):
+         self.nst_filenames = ("@./cirros_slice/cirros_slice.yaml")
+     def run(self, engine, test_osm, manual_check, test_params=None):
+         # nst CREATE
+         engine.get_autorization()
+         r = engine.test("NST1", "Onboard NST", "POST", "/nst/v1/netslice_templates_content", headers_yaml, 
+                         self.nst_filenames, 
+                         201, {"Location": "/nst/v1/netslice_templates_content", "Content-Type": "application/yaml"}, 
+                         "yaml")
+         location = r.headers["Location"]
+         nst_id = location[location.rfind("/")+1:]
+         # nstd SHOW OSM format
+         r = engine.test("NST2", "Show NSTD OSM format", "GET", 
+                         "/nst/v1/netslice_templates/{}".format(nst_id), headers_json, None, 
+                         200, r_header_json, "json")      
+         # nstd DELETE
+         r = engine.test("NST3", "Delete NSTD", "DELETE", 
+                         "/nst/v1/netslice_templates/{}".format(nst_id), headers_json, None, 
+                         204, None, 0)
  if __name__ == "__main__":
      global logger
      test = ""
              "TestDescriptors": TestDescriptors,
              "TestDeployHackfest1": TestDeployHackfest1,
              # "Deploy-MultiVIM": TestDeployMultiVIM,
 +            "DeploySingleVdu": TestDeploySingleVdu,
 +            "DeployHnfd": TestDeployHnfd,
+             "Upload-Slice-Template": TestNstTemplates,
          }
          test_to_do = []
          test_params = {}
diff --combined osm_nbi/validation.py
@@@ -1,7 -1,6 +1,7 @@@
  # -*- coding: utf-8 -*-
  
  from jsonschema import validate as js_v, exceptions as js_e
 +from http import HTTPStatus
  
  __author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
  __version__ = "0.1"
@@@ -193,6 -192,7 +193,7 @@@ ns_instantiate = 
      "properties": {
          "lcmOperationType": string_schema,
          "nsInstanceId": id_schema,
+         "netsliceInstanceId": id_schema,
          "nsName": name_schema,
          "nsDescription": {"oneOf": [description_schema, {"type": "null"}]},
          "nsdId": id_schema,
@@@ -427,18 -427,18 +428,18 @@@ pdu_interface = 
          "name": nameshort_schema,
          "mgmt": bool_schema,
          "type": {"enum": ["overlay", 'underlay']},
 -        "ip_address": ip_schema,
 +        "ip-address": ip_schema,
          # TODO, add user, password, ssh-key
 -        "mac_address": mac_schema,
 -        "vim_network_name": nameshort_schema,  # interface is connected to one vim network, or switch port
 -        "vim_network_id": nameshort_schema,
 -        # provide this in case SDN assist must deal with this interface
 -        "switch_dpid": dpid_Schema,
 -        "switch_port": nameshort_schema,
 -        "switch_mac": nameshort_schema,
 -        "switch_vlan": vlan_schema,
 +        "mac-address": mac_schema,
 +        "vim-network-name": nameshort_schema,  # interface is connected to one vim network, or switch port
 +        # TODO "vim-network-id": nameshort_schema,
 +        # provide this in case SDN assist must deal with this interface
 +        # "switch-dpid": dpid_Schema,
 +        # "switch-port": nameshort_schema,
 +        # "switch-mac": nameshort_schema,
 +        # "switch-vlan": vlan_schema,
      },
 -    "required": ["name", "mgmt", "ip_address"],
 +    "required": ["name", "mgmt", "ip-address"],
      "additionalProperties": False
  }
  pdu_new_schema = {
          "vim_accounts": nameshort_list_schema,
          "interfaces": {
              "type": "array",
 -            "items": {"type": pdu_interface},
 +            "items": pdu_interface,
              "minItems": 1
          }
      },
@@@ -477,7 -477,7 +478,7 @@@ pdu_edit_schema = 
              array_edition_schema,
              {
                  "type": "array",
 -                "items": {"type": pdu_interface},
 +                "items": pdu_interface,
                  "minItems": 1
              }
          ]}
@@@ -560,11 -560,41 +561,43 @@@ nbi_edit_input_schemas = 
      "pdus": pdu_edit_schema,
  }
  
+ # NETSLICE SCHEMAS
+ nsi_instantiate = {
+     "title": "netslice action instantiate input schema",
+     "$schema": "http://json-schema.org/draft-04/schema#",
+     "type": "object",
+     "properties": {
+         "lcmOperationType": string_schema,
+         "nsiInstanceId": id_schema,
+         "nsiName": name_schema,
+         "nsiDescription": {"oneOf": [description_schema, {"type": "null"}]},
+         "nstdId": string_schema,
+         "vimAccountId": id_schema,
+         "ssh_keys": {"type": "string"},
+         "nsi_id": id_schema,
+         "ns": {
+             "type": "array",
+             "minItems": 1,
+             "items": ns_instantiate
+         },
+     },
+     "required": ["nsiName", "nstdId", "vimAccountId"],
+     "additionalProperties": False
+ }
+ nsi_action = {
+ }
+ nsi_terminate = {
+     
+ }
  
  class ValidationError(Exception):
 -    pass
 +    def __init__(self, message, http_code=HTTPStatus.UNPROCESSABLE_ENTITY):
 +        self.http_code = http_code
 +        Exception.__init__(self, message)
  
  
  def validate_input(indata, schema_to_use):
          else:
              error_pos = ""
          raise ValidationError("Format error {} '{}' ".format(error_pos, e.message))
 +    except js_e.SchemaError:
 +        raise ValidationError("Bad json schema {}".format(schema_to_use), http_code=HTTPStatus.INTERNAL_SERVER_ERROR)