PATCH support for enabling and disabling of NSDs and VNFDs.
[osm/NBI.git] / osm_nbi / descriptor_topics.py
index c1c1f63..ab0467a 100644 (file)
@@ -146,29 +146,27 @@ class DescriptorTopic(BaseTopic):
         :return: _id, None: identity of the inserted data; and None as there is not any operation
         """
 
-        try:
-            # Check Quota
-            self.check_quota(session)
+        # No needed to capture exceptions
+        # Check Quota
+        self.check_quota(session)
 
-            # _remove_envelop
-            if indata:
-                if "userDefinedData" in indata:
-                    indata = indata['userDefinedData']
+        # _remove_envelop
+        if indata:
+            if "userDefinedData" in indata:
+                indata = indata['userDefinedData']
 
-            # Override descriptor with query string kwargs
-            self._update_input_with_kwargs(indata, kwargs)
-            # uncomment when this method is implemented.
-            # Avoid override in this case as the target is userDefinedData, but not vnfd,nsd descriptors
-            # indata = DescriptorTopic._validate_input_new(self, indata, project_id=session["force"])
-
-            content = {"_admin": {"userDefinedData": indata}}
-            self.format_on_new(content, session["project_id"], make_public=session["public"])
-            _id = self.db.create(self.topic, content)
-            rollback.append({"topic": self.topic, "_id": _id})
-            self._send_msg("created", {"_id": _id})
-            return _id, None
-        except ValidationError as e:
-            raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
+        # Override descriptor with query string kwargs
+        self._update_input_with_kwargs(indata, kwargs)
+        # uncomment when this method is implemented.
+        # Avoid override in this case as the target is userDefinedData, but not vnfd,nsd descriptors
+        # indata = DescriptorTopic._validate_input_new(self, indata, project_id=session["force"])
+
+        content = {"_admin": {"userDefinedData": indata}}
+        self.format_on_new(content, session["project_id"], make_public=session["public"])
+        _id = self.db.create(self.topic, content)
+        rollback.append({"topic": self.topic, "_id": _id})
+        self._send_msg("created", {"_id": _id})
+        return _id, None
 
     def upload_content(self, session, _id, indata, kwargs, headers):
         """
@@ -420,6 +418,39 @@ class DescriptorTopic(BaseTopic):
             raise EngineException("Error in pyangbind validation: {}".format(str(e)),
                                   http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
 
+    def _validate_input_edit(self, indata, content, force=False):
+        # not needed to validate with pyangbind becuase it will be validated at check_conflict_on_edit
+        if "_id" in indata:
+            indata.pop("_id")
+        if "_admin" not in indata:
+            indata["_admin"] = {}
+
+        if "operationalState" in indata:
+            if indata["operationalState"] in ("ENABLED", "DISABLED"):
+                indata["_admin"]["operationalState"] = indata.pop("operationalState")
+            else:
+                raise EngineException("State '{}' is not a valid operational state"
+                                      .format(indata["operationalState"]),
+                                      http_code=HTTPStatus.BAD_REQUEST)
+
+        # In the case of user defined data, we need to put the data in the root of the object 
+        # to preserve current expected behaviour
+        if "userDefinedData" in indata:
+            data = indata.pop("userDefinedData")
+            if type(data) == dict:
+                indata["_admin"]["userDefinedData"] = data
+            else:
+                raise EngineException("userDefinedData should be an object, but is '{}' instead"
+                                      .format(type(data)),
+                                      http_code=HTTPStatus.BAD_REQUEST)
+        
+        if ("operationalState" in indata["_admin"] and
+                content["_admin"]["operationalState"] == indata["_admin"]["operationalState"]):
+            raise EngineException("operationalState already {}".format(content["_admin"]["operationalState"]),
+                                  http_code=HTTPStatus.CONFLICT)
+
+        return indata
+
 
 class VnfdTopic(DescriptorTopic):
     topic = "vnfds"
@@ -569,13 +600,14 @@ class VnfdTopic(DescriptorTopic):
         vld_names = []  # For detection of duplicated VLD names
         for ivld in get_iterable(indata.get("internal-vld")):
             # BEGIN Detection of duplicated VLD names
-            ivld_name = ivld["name"]
-            if ivld_name in vld_names:
-                raise EngineException("Duplicated VLD name '{}' in vnfd[id={}]:internal-vld[id={}]"
-                                      .format(ivld["name"], indata["id"], ivld["id"]),
-                                      http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
-            else:
-                vld_names.append(ivld_name)
+            ivld_name = ivld.get("name")
+            if ivld_name:
+                if ivld_name in vld_names:
+                    raise EngineException("Duplicated VLD name '{}' in vnfd[id={}]:internal-vld[id={}]"
+                                          .format(ivld["name"], indata["id"], ivld["id"]),
+                                          http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
+                else:
+                    vld_names.append(ivld_name)
             # END Detection of duplicated VLD names
             for icp in get_iterable(ivld.get("internal-connection-point")):
                 icp_mark = False
@@ -673,10 +705,6 @@ class VnfdTopic(DescriptorTopic):
                                           http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
         return indata
 
-    def _validate_input_edit(self, indata, force=False):
-        # not needed to validate with pyangbind becuase it will be validated at check_conflict_on_edit
-        return indata
-
     def _validate_package_folders(self, storage_params, folder, file=None):
         if not storage_params or not storage_params.get("pkg-dir"):
             return False
@@ -774,8 +802,41 @@ class NsdTopic(DescriptorTopic):
                         http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
         return indata
 
-    def _validate_input_edit(self, indata, force=False):
+    def _validate_input_edit(self, indata, content, force=False):
         # not needed to validate with pyangbind becuase it will be validated at check_conflict_on_edit
+        """
+        indata looks as follows:
+            - In the new case (conformant) 
+                {'nsdOperationalState': 'DISABLED', 'userDefinedData': {'id': 'string23', 
+                '_id': 'c6ddc544-cede-4b94-9ebe-be07b298a3c1', 'name': 'simon46'}}
+            - In the old case (backwards-compatible)
+                {'id': 'string23', '_id': 'c6ddc544-cede-4b94-9ebe-be07b298a3c1', 'name': 'simon46'}
+        """
+        if "_admin" not in indata:
+            indata["_admin"] = {}
+
+        if "nsdOperationalState" in indata:
+            if indata["nsdOperationalState"] in ("ENABLED", "DISABLED"):
+                indata["_admin"]["operationalState"] = indata.pop("nsdOperationalState")
+            else:
+                raise EngineException("State '{}' is not a valid operational state"
+                                      .format(indata["nsdOperationalState"]),
+                                      http_code=HTTPStatus.BAD_REQUEST)
+
+        # In the case of user defined data, we need to put the data in the root of the object 
+        # to preserve current expected behaviour
+        if "userDefinedData" in indata:
+            data = indata.pop("userDefinedData")
+            if type(data) == dict:
+                indata["_admin"]["userDefinedData"] = data
+            else:
+                raise EngineException("userDefinedData should be an object, but is '{}' instead"
+                                      .format(type(data)),
+                                      http_code=HTTPStatus.BAD_REQUEST)
+        if ("operationalState" in indata["_admin"] and
+                content["_admin"]["operationalState"] == indata["_admin"]["operationalState"]):
+            raise EngineException("nsdOperationalState already {}".format(content["_admin"]["operationalState"]),
+                                  http_code=HTTPStatus.CONFLICT)
         return indata
 
     def _check_descriptor_dependencies(self, session, descriptor):
@@ -857,6 +918,7 @@ class NsdTopic(DescriptorTopic):
 class NstTopic(DescriptorTopic):
     topic = "nsts"
     topic_msg = "nst"
+    quota_name = "slice_templates"
 
     def __init__(self, db, fs, msg, auth):
         DescriptorTopic.__init__(self, db, fs, msg, auth)
@@ -877,10 +939,6 @@ class NstTopic(DescriptorTopic):
             clean_indata = clean_indata['nst:nst'][0]
         return clean_indata
 
-    def _validate_input_edit(self, indata, force=False):
-        # TODO validate with pyangbind, serialize
-        return indata
-
     def _validate_input_new(self, indata, storage_params, force=False):
         indata = self.pyangbind_validation("nsts", indata, force)
         return indata.copy()
@@ -930,6 +988,7 @@ class NstTopic(DescriptorTopic):
 class PduTopic(BaseTopic):
     topic = "pdus"
     topic_msg = "pdu"
+    quota_name = "pduds"
     schema_new = pdu_new_schema
     schema_edit = pdu_edit_schema