Adding NSI operations (Create, Read, Delete)
[osm/NBI.git] / osm_nbi / instance_topics.py
index 4228bc5..41187dc 100644 (file)
@@ -5,7 +5,7 @@ from uuid import uuid4
 from http import HTTPStatus
 from time import time
 from copy import copy, deepcopy
 from http import HTTPStatus
 from time import time
 from copy import copy, deepcopy
-from validation import validate_input, ValidationError, ns_instantiate, ns_action, ns_scale
+from validation import validate_input, ValidationError, ns_instantiate, ns_action, ns_scale, nsi_instantiate
 from base_topic import BaseTopic, EngineException, get_iterable
 from descriptor_topics import DescriptorTopic
 
 from base_topic import BaseTopic, EngineException, get_iterable
 from descriptor_topics import DescriptorTopic
 
@@ -66,7 +66,7 @@ class NsrTopic(BaseTopic):
         self.db.del_list("vnfrs", {"nsr-id-ref": _id})
         # set all used pdus as free
         self.db.set_list("pdus", {"_admin.usage.nsr_id": _id},
         self.db.del_list("vnfrs", {"nsr-id-ref": _id})
         # set all used pdus as free
         self.db.set_list("pdus", {"_admin.usage.nsr_id": _id},
-                         {"_admin.usageSate": "NOT_IN_USE", "_admin.usage": None})
+                         {"_admin.usageState": "NOT_IN_USE", "_admin.usage": None})
         self._send_msg("deleted", {"_id": _id})
         return v
 
         self._send_msg("deleted", {"_id": _id})
         return v
 
@@ -266,17 +266,6 @@ class NsLcmOpTopic(BaseTopic):
     def __init__(self, db, fs, msg):
         BaseTopic.__init__(self, db, fs, msg)
 
     def __init__(self, db, fs, msg):
         BaseTopic.__init__(self, db, fs, msg)
 
-    def _validate_input_new(self, input, force=False):
-        """
-        Validates input user content for a new entry. It uses jsonschema for each type or operation.
-        :param input: user input content for the new topic
-        :param force: may be used for being more tolerant
-        :return: The same input content, or a changed version of it.
-        """
-        if self.schema_new:
-            validate_input(input, self.schema_new)
-        return input
-
     def _check_ns_operation(self, session, nsr, operation, indata):
         """
         Check that user has enter right parameters for the operation
     def _check_ns_operation(self, session, nsr, operation, indata):
         """
         Check that user has enter right parameters for the operation
@@ -432,7 +421,7 @@ class NsLcmOpTopic(BaseTopic):
             pdu_filter["vim.vim_accounts"] = vim_account
             pdu_filter["type"] = pdu_type
             pdu_filter["_admin.operationalState"] = "ENABLED"
             pdu_filter["vim.vim_accounts"] = vim_account
             pdu_filter["type"] = pdu_type
             pdu_filter["_admin.operationalState"] = "ENABLED"
-            pdu_filter["_admin.usageSate"] = "NOT_IN_USE",
+            pdu_filter["_admin.usageState"] = "NOT_IN_USE",
             # TODO feature 1417: "shared": True,
 
             available_pdus = self.db.get_list("pdus", pdu_filter)
             # TODO feature 1417: "shared": True,
 
             available_pdus = self.db.get_list("pdus", pdu_filter)
@@ -462,7 +451,7 @@ class NsLcmOpTopic(BaseTopic):
                 "_admin.usage.vdur": None,
             }
             self.db.set_one("pdus", {"_id": pdu["_id"]},
                 "_admin.usage.vdur": None,
             }
             self.db.set_one("pdus", {"_id": pdu["_id"]},
-                            {"_admin.usageSate": "IN_USE",
+                            {"_admin.usageState": "IN_USE",
                              "_admin.usage.vnfr_id": vnfr["_id"],
                              "_admin.usage.nsr_id": vnfr["nsr-id-ref"],
                              "_admin.usage.vdur": vdur["vdu-id-ref"]}
                              "_admin.usage.vnfr_id": vnfr["_id"],
                              "_admin.usage.nsr_id": vnfr["nsr-id-ref"],
                              "_admin.usage.vdur": vdur["vdu-id-ref"]}
@@ -535,7 +524,8 @@ class NsLcmOpTopic(BaseTopic):
         }
         return nslcmop
 
         }
         return nslcmop
 
-    def new(self, rollback, session, indata=None, kwargs=None, headers=None, force=False, make_public=False):
+    def new(self, rollback, session, indata=None, kwargs=None, headers=None, force=False, make_public=False,
+            slice_object=False):
         """
         Performs a new operation over a ns
         :param rollback: list to append created items at database in case a rollback must to be done
         """
         Performs a new operation over a ns
         :param rollback: list to append created items at database in case a rollback must to be done
@@ -580,7 +570,8 @@ class NsLcmOpTopic(BaseTopic):
             self.format_on_new(nslcmop_desc, session["project_id"], make_public=make_public)
             _id = self.db.create("nslcmops", nslcmop_desc)
             rollback.append({"topic": "nslcmops", "_id": _id})
             self.format_on_new(nslcmop_desc, session["project_id"], make_public=make_public)
             _id = self.db.create("nslcmops", nslcmop_desc)
             rollback.append({"topic": "nslcmops", "_id": _id})
-            self.msg.write("ns", operation, nslcmop_desc)
+            if not slice_object:
+                self.msg.write("ns", operation, nslcmop_desc)
             return _id
         except ValidationError as e:
             raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
             return _id
         except ValidationError as e:
             raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
@@ -608,17 +599,16 @@ class NsiTopic(BaseTopic):
         :param descriptor: descriptor to be inserted or edit
         :return: None or raises exception
         """
         :param descriptor: descriptor to be inserted or edit
         :return: None or raises exception
         """
-        if not descriptor.get("nstdId"):
+        if not descriptor.get("nst-ref"):
             return
             return
-        nstd_id = descriptor["nstdId"]
+        nstd_id = descriptor["nst-ref"]
         if not self.get_item_list(session, "nsts", {"id": nstd_id}):
         if not self.get_item_list(session, "nsts", {"id": nstd_id}):
-            raise EngineException("Descriptor error at nstdId='{}' references a non exist nstd".format(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)
                                   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)
-        content["_admin"]["nstState"] = "NOT_INSTANTIATED"
 
     def check_conflict_on_del(self, session, _id, force=False):
         if force:
 
     def check_conflict_on_del(self, session, _id, force=False):
         if force:
@@ -642,17 +632,28 @@ class NsiTopic(BaseTopic):
         BaseTopic.delete(self, session, _id, force, dry_run=True)
         if dry_run:
             return
         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})
         v = self.db.del_one("nsis", {"_id": _id})
-        self.db.del_list("nstlcmops", {"nstInstanceId": _id})
-        self.db.del_list("vnfrs", {"nsr-id-ref": _id})
-        # set all used pdus as free
-        self.db.set_list("pdus", {"_admin.usage.nsr_id": _id}, {"_admin.usageSate": "NOT_IN_USE", "_admin.usage": None})
+
+        # 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):
         """
         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 nsr into database. It also creates needed vnfrs
+        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 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
@@ -671,13 +672,18 @@ class NsiTopic(BaseTopic):
 
             step = ""
             # look for nstd
 
             step = ""
             # look for nstd
+            self.logger.info(str(slice_request))
             step = "getting nstd id='{}' from database".format(slice_request.get("nstdId"))
             step = "getting nstd id='{}' from database".format(slice_request.get("nstdId"))
-            _filter = {"_id": slice_request["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)
             _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())
             nsi_id = str(uuid4())
-            # now = time()
             step = "filling nsi_descriptor with input data"
             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"],
             nsi_descriptor = {
                 "id": nsi_id,
                 "nst-ref": nstd["id"],
@@ -685,28 +691,12 @@ class NsiTopic(BaseTopic):
                     "netslice-subnet": []
                 },
                 "network-slice-template": nstd,
                     "netslice-subnet": []
                 },
                 "network-slice-template": nstd,
-                # "nsr-ref-list": [],    #TODO: not used for now...
-                # "vlr-ref-list": [],    #TODO: not used for now...
                 "_id": nsi_id,
                 "_id": nsi_id,
-                
-                # TODO CHECK: what about the following params?
-                # "admin-status": "ENABLED",
-                # "description": slice_request.get("nsDescription", ""),
-                # "operational-status": "init",    # typedef ns-operational-
-                # "config-status": "init",         # typedef config-states
-                # "detailed-status": "scheduled",
-                # "orchestration-progress": {},
-                # # {"networks": {"active": 0, "total": 0}, "vms": {"active": 0, "total": 0}},
-                # "create-time": now,
-                # "operational-events": [],   # "id", "timestamp", "description", "event",
-                # "ssh-authorized-key": slice_request.get("key-pair-ref"),
             }
             }
-            # nstd["nsi_id"] = nsi_id
 
 
-            # TODO: ask if we have to develop the VNFR here or we can imply call the NsrTopic() for each service to 
-            # instantiate.
-            # Create netslice-subnet_record
+            # Creating netslice-subnet_record. 
             needed_nsds = {}
             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(
             for member_ns in nstd["netslice-subnet"]:
                 nsd_id = member_ns["nsd-ref"]
                 step = "getting nstd id='{}' constituent-nsd='{}' from database".format(
@@ -716,20 +706,38 @@ class NsiTopic(BaseTopic):
                     nsd = DescriptorTopic.get_one_by_id(self.db, session, "nsds", nsd_id)
                     nsd.pop("_admin")
                     needed_nsds[nsd_id] = 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]
                 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 = "filling nsir nsd-id='{}' constituent-nsd='{}' from database".format(
                     member_ns["nsd-ref"], member_ns["id"])
-                netslice_subnet_descriptor = {
-                    "nsName": member_ns["instantiation-parameters"]["name"],
-                    "nsdId": member_ns["instantiation-parameters"]["nsdId"],
-                    "vimAccountId": member_ns["instantiation-parameters"]["vimAccountId"]
-                }
-                nsi_descriptor["instantiation-parameters"]["netslice-subnet"].append(netslice_subnet_descriptor)
 
             step = "creating nsi at database"
             self.format_on_new(nsi_descriptor, session["project_id"], make_public=make_public)
 
             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
             self.db.create("nsis", nsi_descriptor)
             rollback.append({"topic": "nsis", "_id": nsi_id})
             return nsi_id
@@ -741,3 +749,141 @@ class NsiTopic(BaseTopic):
 
     def edit(self, session, _id, indata=None, kwargs=None, force=False, content=None):
         raise EngineException("Method edit called directly", HTTPStatus.INTERNAL_SERVER_ERROR)
 
     def edit(self, session, _id, indata=None, kwargs=None, force=False, content=None):
         raise EngineException("Method edit called directly", HTTPStatus.INTERNAL_SERVER_ERROR)
+
+
+class 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)