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>
Dockerfile.local
osm_nbi/descriptor_topics.py
osm_nbi/engine.py
osm_nbi/html_out.py
osm_nbi/html_public/version
osm_nbi/instance_topics.py
osm_nbi/nbi.py
osm_nbi/tests/cirros_slice/cirros_slice.yaml [new file with mode: 0644]
osm_nbi/tests/clear-all.sh
osm_nbi/tests/test.py
osm_nbi/validation.py

index 7096163..372e6a9 100644 (file)
@@ -6,7 +6,7 @@ FROM ubuntu:16.04
 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 \
@@ -80,6 +80,8 @@ ENV OSMNBI_AUTHENTICATION_BACKEND               internal
 #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"]
 
index 57b2ce0..6285e38 100644 (file)
@@ -664,6 +664,79 @@ class NsdTopic(DescriptorTopic):
             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"
index 3cdacd9..4713b89 100644 (file)
@@ -8,8 +8,8 @@ from osm_common.msgbase import MsgException
 from http import HTTPStatus
 from base_topic import EngineException, versiontuple
 from admin_topics import UserTopic, ProjectTopic, VimAccountTopic, SdnTopic
-from descriptor_topics import VnfdTopic, NsdTopic, PduTopic
-from instance_topics import NsrTopic, VnfrTopic, NsLcmOpTopic
+from descriptor_topics import VnfdTopic, NsdTopic, PduTopic, NstTopic
+from instance_topics import NsrTopic, VnfrTopic, NsLcmOpTopic, NsiTopic, NsiLcmOpTopic
 from base64 import b64encode
 from os import urandom
 
@@ -21,6 +21,7 @@ class Engine(object):
     map_from_topic_to_class = {
         "vnfds": VnfdTopic,
         "nsds": NsdTopic,
+        "nsts": NstTopic,
         "pdus": PduTopic,
         "nsrs": NsrTopic,
         "vnfrs": VnfrTopic,
@@ -29,6 +30,8 @@ class Engine(object):
         "sdns": SdnTopic,
         "users": UserTopic,
         "projects": ProjectTopic,
+        "nsis": NsiTopic,
+        "nsilcmops": NsiLcmOpTopic
         # [NEW_TOPIC]: add an entry here
     }
 
index 1b3c1d9..6b596d7 100644 (file)
@@ -25,6 +25,8 @@ html_start = """
       <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 +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):
     """
@@ -126,6 +137,8 @@ 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)
@@ -143,6 +156,10 @@ def format(data, request, response, session):
                     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:
index b338202..1301985 100644 (file)
@@ -5,7 +5,7 @@ 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
 
@@ -305,17 +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
@@ -630,7 +619,8 @@ class NsLcmOpTopic(BaseTopic):
         }
         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
@@ -677,7 +667,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.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)
@@ -689,3 +680,307 @@ 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)
+
+
+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)
index c5262a8..b7a6990 100644 (file)
@@ -33,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
@@ -76,9 +76,11 @@ URL: /osm                                                       GET     POST
                 /<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
@@ -91,6 +93,30 @@ URL: /osm                                                       GET     POST
             /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>]*
@@ -252,6 +278,40 @@ class Server(object):
                                       },
                 }
             },
+            "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):
@@ -606,7 +666,7 @@ class Server(object):
             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", "pdu", "nst", "nsilcm"):
                 raise NbiException("URL main_topic '{}' not supported".format(main_topic),
                                    HTTPStatus.METHOD_NOT_ALLOWED)
             if version != 'v1':
@@ -620,9 +680,7 @@ class Server(object):
                 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)
 
@@ -645,14 +703,20 @@ class Server(object):
                     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
@@ -668,7 +732,7 @@ class Server(object):
                 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,
@@ -696,6 +760,22 @@ class Server(object):
                     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)
@@ -718,6 +798,15 @@ class Server(object):
                         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
@@ -729,7 +818,7 @@ class Server(object):
                 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:
diff --git a/osm_nbi/tests/cirros_slice/cirros_slice.yaml b/osm_nbi/tests/cirros_slice/cirros_slice.yaml
new file mode 100644 (file)
index 0000000..6f66c27
--- /dev/null
@@ -0,0 +1,17 @@
+nst:
+    nst:
+    -   id: cirros_nst
+        name: cirros_netslice_template
+        SNSSAI-identifier:
+        -   slice-service-type: eMBB
+        quality-of-service:
+        -   id: 1
+        netslice-subnet:
+        -   id: cirros_nsd
+            is-shared-nss: 'false'
+            description: Slice example for osm-5.0
+            nsd-ref: cirros_nsd
+            instantiation-parameters:
+            -   nsName: cirros_nsd
+                nsdId: cirros_nsd
+                vimAccountId: openstack_18
\ No newline at end of file
index 9ea6f36..dd95417 100755 (executable)
@@ -66,7 +66,7 @@ then
     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 pdus nsts nsis nsilcmops # vims
 do
     curl --insecure ${OSMNBI_URL}/test/db-clear/${item}
     echo " ${item}"
index dc7072c..51e1494 100755 (executable)
@@ -1586,6 +1586,33 @@ class TestDescriptors:
         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 = ""
@@ -1622,6 +1649,7 @@ if __name__ == "__main__":
             # "Deploy-MultiVIM": TestDeployMultiVIM,
             "DeploySingleVdu": TestDeploySingleVdu,
             "DeployHnfd": TestDeployHnfd,
+            "Upload-Slice-Template": TestNstTemplates,
         }
         test_to_do = []
         test_params = {}
index f81f45c..b8953b5 100644 (file)
@@ -193,6 +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,
@@ -560,6 +561,38 @@ 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):
     def __init__(self, message, http_code=HTTPStatus.UNPROCESSABLE_ENTITY):