fix bug 865 fix error deleting a non-instantiated ns
[osm/NBI.git] / osm_nbi / instance_topics.py
index 100c9b4..8024996 100644 (file)
@@ -18,8 +18,8 @@ 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, nsi_instantiate
-from base_topic import BaseTopic, EngineException, get_iterable
+from osm_nbi.validation import validate_input, ValidationError, ns_instantiate, ns_action, ns_scale, nsi_instantiate
+from osm_nbi.base_topic import BaseTopic, EngineException, get_iterable
 # from descriptor_topics import DescriptorTopic
 from yaml import safe_dump
 from osm_common.dbbase import DbException
@@ -31,9 +31,10 @@ __author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
 class NsrTopic(BaseTopic):
     topic = "nsrs"
     topic_msg = "ns"
+    schema_new = ns_instantiate
 
-    def __init__(self, db, fs, msg):
-        BaseTopic.__init__(self, db, fs, msg)
+    def __init__(self, db, fs, msg, auth):
+        BaseTopic.__init__(self, db, fs, msg, auth)
 
     def _check_descriptor_dependencies(self, session, descriptor):
         """
@@ -53,6 +54,7 @@ class NsrTopic(BaseTopic):
     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"]["nsState"] = "NOT_INSTANTIATED"
+        return None
 
     def check_conflict_on_del(self, session, _id, db_content):
         """
@@ -180,6 +182,10 @@ class NsrTopic(BaseTopic):
         """
 
         try:
+            step = "checking quotas"
+            self.check_quota(session)
+
+            step = "validating input parameters"
             ns_request = self._remove_envelop(indata)
             # Override descriptor with query string kwargs
             self._update_input_with_kwargs(ns_request, kwargs)
@@ -226,7 +232,7 @@ class NsrTopic(BaseTopic):
                 "id": nsr_id,
                 "_id": nsr_id,
                 # "input-parameter": xpath, value,
-                "ssh-authorized-key": ns_request.get("key-pair-ref"),  # TODO remove
+                "ssh-authorized-key": ns_request.get("ssh_keys"),  # TODO remove
             }
             ns_request["nsr_id"] = nsr_id
             # Create vld
@@ -360,7 +366,7 @@ class NsrTopic(BaseTopic):
                     member_vnf["vnfd-id-ref"], member_vnf["member-vnf-index"])
 
                 # add at database
-                BaseTopic.format_on_new(vnfr_descriptor, session["project_id"], make_public=session["public"])
+                self.format_on_new(vnfr_descriptor, session["project_id"], make_public=session["public"])
                 self.db.create("vnfrs", vnfr_descriptor)
                 rollback.append({"topic": "vnfrs", "_id": vnfr_id})
                 nsr_descriptor["constituent-vnfr-ref"].append(vnfr_id)
@@ -373,12 +379,12 @@ class NsrTopic(BaseTopic):
             step = "creating nsr temporal folder"
             self.fs.mkdir(nsr_id)
 
-            return nsr_id
+            return nsr_id, None
+        except ValidationError as e:   # TODO remove try Except, it is captured at nbi.py
+            raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
         except Exception as e:
             self.logger.exception("Exception {} at NsrTopic.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, content=None):
         raise EngineException("Method edit called directly", HTTPStatus.INTERNAL_SERVER_ERROR)
@@ -388,8 +394,8 @@ class VnfrTopic(BaseTopic):
     topic = "vnfrs"
     topic_msg = None
 
-    def __init__(self, db, fs, msg):
-        BaseTopic.__init__(self, db, fs, msg)
+    def __init__(self, db, fs, msg, auth):
+        BaseTopic.__init__(self, db, fs, msg, auth)
 
     def delete(self, session, _id, dry_run=False):
         raise EngineException("Method delete called directly", HTTPStatus.INTERNAL_SERVER_ERROR)
@@ -412,8 +418,8 @@ class NsLcmOpTopic(BaseTopic):
         "terminate": None,
     }
 
-    def __init__(self, db, fs, msg):
-        BaseTopic.__init__(self, db, fs, msg)
+    def __init__(self, db, fs, msg, auth):
+        BaseTopic.__init__(self, db, fs, msg, auth)
 
     def _check_ns_operation(self, session, nsr, operation, indata):
         """
@@ -423,22 +429,34 @@ class NsLcmOpTopic(BaseTopic):
         :param indata: descriptor with the parameters of the operation
         :return: None
         """
-        vnfds = {}
+        vnf_member_index_to_vnfd = {}  # map between vnf_member_index to vnf descriptor.
         vim_accounts = []
         wim_accounts = []
         nsd = nsr["nsd"]
 
         def check_valid_vnf_member_index(member_vnf_index):
-            # TODO change to vnfR
-            for vnf in nsd["constituent-vnfd"]:
-                if member_vnf_index == vnf["member-vnf-index"]:
-                    vnfd_id = vnf["vnfd-id-ref"]
-                    if vnfd_id not in vnfds:
-                        vnfds[vnfd_id] = self.db.get_one("vnfds", {"id": vnfd_id})
-                    return vnfds[vnfd_id]
-            else:
+            # Obtain vnf descriptor. The vnfr is used to get the vnfd._id used for this member_vnf_index
+            if vnf_member_index_to_vnfd.get(member_vnf_index):
+                return vnf_member_index_to_vnfd[member_vnf_index]
+            vnfr = self.db.get_one("vnfrs",
+                                   {"nsr-id-ref": nsr["_id"], "member-vnf-index-ref": member_vnf_index},
+                                   fail_on_empty=False)
+            if not vnfr:
                 raise EngineException("Invalid parameter member_vnf_index='{}' is not one of the "
                                       "nsd:constituent-vnfd".format(member_vnf_index))
+            vnfd = self.db.get_one("vnfds", {"_id": vnfr["vnfd-id"]}, fail_on_empty=False)
+            if not vnfd:
+                raise EngineException("vnfd id={} has been deleted!. Operation cannot be performed".
+                                      format(vnfr["vnfd-id"]))
+            vnf_member_index_to_vnfd[member_vnf_index] = vnfd  # add to cache, avoiding a later look for
+            return vnfd
+
+        def check_valid_vdu(vnfd, vdu_id):
+            for vdud in get_iterable(vnfd.get("vdu")):
+                if vdud["id"] == vdu_id:
+                    return vdud
+            else:
+                raise EngineException("Invalid parameter vdu_id='{}' not present at vnfd:vdu:id".format(vdu_id))
 
         def _check_vnf_instantiation_params(in_vnfd, vnfd):
 
@@ -517,7 +535,11 @@ class NsLcmOpTopic(BaseTopic):
                 indata["member_vnf_index"] = indata.pop("vnf_member_index")    # for backward compatibility
             if indata.get("member_vnf_index"):
                 vnfd = check_valid_vnf_member_index(indata["member_vnf_index"])
-                descriptor_configuration = vnfd.get("vnf-configuration", {}).get("config-primitive")
+                if indata.get("vdu_id"):
+                    vdud = check_valid_vdu(vnfd, indata["vdu_id"])
+                    descriptor_configuration = vdud.get("vdu-configuration", {}).get("config-primitive")
+                else:
+                    descriptor_configuration = vnfd.get("vnf-configuration", {}).get("config-primitive")
             else:  # use a NSD
                 descriptor_configuration = nsd.get("ns-configuration", {}).get("config-primitive")
             # check primitive
@@ -790,7 +812,7 @@ class NsLcmOpTopic(BaseTopic):
             if not nsr["_admin"].get("nsState") or nsr["_admin"]["nsState"] == "NOT_INSTANTIATED":
                 if operation == "terminate" and indata.get("autoremove"):
                     # NSR must be deleted
-                    return None    # a none in this case is used to indicate not instantiated. It can be removed
+                    return None, None    # a none in this case is used to indicate not instantiated. It can be removed
                 if operation != "instantiate":
                     raise EngineException("ns_instance '{}' cannot be '{}' because it is not instantiated".format(
                         nsInstanceId, operation), HTTPStatus.CONFLICT)
@@ -809,8 +831,8 @@ class NsLcmOpTopic(BaseTopic):
             rollback.append({"topic": "nslcmops", "_id": _id})
             if not slice_object:
                 self.msg.write("ns", operation, nslcmop_desc)
-            return _id
-        except ValidationError as e:
+            return _id, None
+        except ValidationError as e:  # TODO remove try Except, it is captured at nbi.py
             raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
         # except DbException as e:
         #     raise EngineException("Cannot get ns_instance '{}': {}".format(e), HTTPStatus.NOT_FOUND)
@@ -826,9 +848,9 @@ class NsiTopic(BaseTopic):
     topic = "nsis"
     topic_msg = "nsi"
 
-    def __init__(self, db, fs, msg):
-        BaseTopic.__init__(self, db, fs, msg)
-        self.nsrTopic = NsrTopic(db, fs, msg)
+    def __init__(self, db, fs, msg, auth):
+        BaseTopic.__init__(self, db, fs, msg, auth)
+        self.nsrTopic = NsrTopic(db, fs, msg, auth)
 
     @staticmethod
     def _format_ns_request(ns_request):
@@ -987,12 +1009,15 @@ class NsiTopic(BaseTopic):
         """
 
         try:
+            step = "checking quotas"
+            self.check_quota(session)
+
+            step = ""
             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, session["force"])
 
-            step = ""
             # look for nstd
             step = "getting nstd id='{}' from database".format(slice_request.get("nstId"))
             _filter = self._get_project_filter(session)
@@ -1079,14 +1104,15 @@ class NsiTopic(BaseTopic):
                 # Is the nss shared and instantiated?
                 _filter["_admin.nsrs-detailed-list.ANYINDEX.shared"] = True
                 _filter["_admin.nsrs-detailed-list.ANYINDEX.nsd-id"] = service["nsd-ref"]
+                _filter["_admin.nsrs-detailed-list.ANYINDEX.nss-id"] = service["id"]
                 nsi = self.db.get_one("nsis", _filter, fail_on_empty=False, fail_on_more=False)
-                
                 if nsi and service.get("is-shared-nss"):
                     nsrs_detailed_list = nsi["_admin"]["nsrs-detailed-list"]
                     for nsrs_detailed_item in nsrs_detailed_list:
                         if nsrs_detailed_item["nsd-id"] == service["nsd-ref"]:
-                            _id_nsr = nsrs_detailed_item["nsrId"]
-                            break
+                            if nsrs_detailed_item["nss-id"] == service["id"]:
+                                _id_nsr = nsrs_detailed_item["nsrId"]
+                                break
                     for netslice_subnet in nsi["_admin"]["netslice-subnet"]:
                         if netslice_subnet["nss-id"] == service["id"]:
                             indata_ns = netslice_subnet
@@ -1101,7 +1127,8 @@ class NsiTopic(BaseTopic):
                     indata_ns["nsName"] = slice_request.get("nsiName") + "." + service["id"]
                     indata_ns["vimAccountId"] = slice_request.get("vimAccountId")
                     indata_ns["nsDescription"] = service["description"]
-                    indata_ns["key-pair-ref"] = slice_request.get("key-pair-ref")
+                    if slice_request.get("ssh_keys"):
+                        indata_ns["ssh_keys"] = slice_request.get("ssh_keys")
 
                     if ns_params:
                         for ns_param in ns_params:
@@ -1112,9 +1139,9 @@ class NsiTopic(BaseTopic):
                                 break                   
 
                     # Creates Nsr objects
-                    _id_nsr = self.nsrTopic.new(rollback, session, indata_ns, kwargs, headers)
+                    _id_nsr, _ = self.nsrTopic.new(rollback, session, indata_ns, kwargs, headers)
                 nsrs_item = {"nsrId": _id_nsr, "shared": service.get("is-shared-nss"), "nsd-id": service["nsd-ref"], 
-                             "nslcmop_instantiate": None}
+                             "nss-id": service["id"], "nslcmop_instantiate": None}
                 indata_ns["nss-id"] = service["id"]
                 nsrs_list.append(nsrs_item)
                 nsi_netslice_subnet.append(indata_ns)
@@ -1129,8 +1156,8 @@ class NsiTopic(BaseTopic):
             # 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:
+            return nsi_id, None
+        except Exception as e:   # TODO remove try Except, it is captured at nbi.py
             self.logger.exception("Exception {} at NsiTopic.new()".format(e), exc_info=True)
             raise EngineException("Error {}: {}".format(step, e))
         except ValidationError as e:
@@ -1148,9 +1175,9 @@ class NsiLcmOpTopic(BaseTopic):
         "terminate": None
     }
     
-    def __init__(self, db, fs, msg):
-        BaseTopic.__init__(self, db, fs, msg)
-        self.nsi_NsLcmOpTopic = NsLcmOpTopic(self.db, self.fs, self.msg)
+    def __init__(self, db, fs, msg, auth):
+        BaseTopic.__init__(self, db, fs, msg, auth)
+        self.nsi_NsLcmOpTopic = NsLcmOpTopic(self.db, self.fs, self.msg, self.auth)
 
     def _check_nsi_operation(self, session, nsir, operation, indata):
         """
@@ -1243,7 +1270,7 @@ class NsiLcmOpTopic(BaseTopic):
             if not nsir["_admin"].get("nsiState") or nsir["_admin"]["nsiState"] == "NOT_INSTANTIATED":
                 if operation == "terminate" and indata.get("autoremove"):
                     # NSIR must be deleted
-                    return None    # a none in this case is used to indicate not instantiated. It can be removed
+                    return None, None    # a none in this case is used to indicate not instantiated. It can be removed
                 if operation != "instantiate":
                     raise EngineException("netslice_instance '{}' cannot be '{}' because it is not instantiated".format(
                         netsliceInstanceId, operation), HTTPStatus.CONFLICT)
@@ -1292,11 +1319,10 @@ class NsiLcmOpTopic(BaseTopic):
                     indata_ns["nsInstanceId"] = service["_id"]
                     # Including netslice_id in the ns instantiate Operation
                     indata_ns["netsliceInstanceId"] = netsliceInstanceId
-                    del indata_ns["key-pair-ref"]
-                    # Creating NS_LCM_OP with the flag slice_object=True to not trigger the service instantiation 
+                    # Creating NS_LCM_OP with the flag slice_object=True to not trigger the service instantiation
                     # message via kafka bus
-                    nslcmop = self.nsi_NsLcmOpTopic.new(rollback, session, indata_ns, kwargs, headers, 
-                                                        slice_object=True)
+                    nslcmop, _ = self.nsi_NsLcmOpTopic.new(rollback, session, indata_ns, kwargs, headers,
+                                                           slice_object=True)
                     nslcmops.append(nslcmop)
                     if operation == "terminate":
                         nslcmop = None
@@ -1318,7 +1344,7 @@ class NsiLcmOpTopic(BaseTopic):
             _id = self.db.create("nsilcmops", nsilcmop_desc)
             rollback.append({"topic": "nsilcmops", "_id": _id})
             self.msg.write("nsi", operation, nsilcmop_desc)
-            return _id
+            return _id, None
         except ValidationError as e:
             raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)