PATCH support for enabling and disabling of NSDs and VNFDs. 83/9483/16
authorFrank Bryden <frank.bryden@etsi.org>
Mon, 27 Jul 2020 13:55:11 +0000 (13:55 +0000)
committertierno <alfonso.tiernosepulveda@telefonica.com>
Fri, 7 Aug 2020 13:05:53 +0000 (15:05 +0200)
Moved the validation code to _validate_input_edit.
Updated relevant tests.

Change-Id: I0666f28e1f06585f3648b0a88812f7348c622d86
Signed-off-by: Frank Bryden <frank.bryden@etsi.org>
osm_nbi/admin_topics.py
osm_nbi/base_topic.py
osm_nbi/descriptor_topics.py
osm_nbi/tests/test_admin_topics.py

index f831b63..998c12f 100644 (file)
@@ -420,8 +420,8 @@ class SdnTopic(CommonVimWimSdn):
         input = super()._validate_input_new(input, force)
         return self._obtain_url(input, True)
 
-    def _validate_input_edit(self, input, force=False):
-        input = super()._validate_input_edit(input, force)
+    def _validate_input_edit(self, input, content, force=False):
+        input = super()._validate_input_edit(input, content, force)
         return self._obtain_url(input, False)
 
 
@@ -715,10 +715,9 @@ class UserTopicAuth(UserTopic):
         if kwargs:
             BaseTopic._update_input_with_kwargs(indata, kwargs)
         try:
-            indata = self._validate_input_edit(indata, force=session["force"])
-
             if not content:
                 content = self.show(session, _id)
+            indata = self._validate_input_edit(indata, content, force=session["force"])
             self.check_conflict_on_edit(session, content, indata, _id=_id)
             # self.format_on_edit(content, indata)
 
@@ -1035,10 +1034,9 @@ class ProjectTopicAuth(ProjectTopic):
         if kwargs:
             BaseTopic._update_input_with_kwargs(indata, kwargs)
         try:
-            indata = self._validate_input_edit(indata, force=session["force"])
-
             if not content:
                 content = self.show(session, _id)
+            indata = self._validate_input_edit(indata, content, force=session["force"])
             self.check_conflict_on_edit(session, content, indata, _id=_id)
             self.format_on_edit(content, indata)
 
@@ -1099,7 +1097,7 @@ class RoleTopicAuth(BaseTopic):
 
         return input
 
-    def _validate_input_edit(self, input, force=False):
+    def _validate_input_edit(self, input, content, force=False):
         """
         Validates input user content for updating an entry.
 
@@ -1335,9 +1333,9 @@ class RoleTopicAuth(BaseTopic):
         if kwargs:
             self._update_input_with_kwargs(indata, kwargs)
         try:
-            indata = self._validate_input_edit(indata, force=session["force"])
             if not content:
                 content = self.show(session, _id)
+            indata = self._validate_input_edit(indata, content, force=session["force"])
             deep_update_rfc7396(content, indata)
             self.check_conflict_on_edit(session, content, indata, _id=_id)
             self.format_on_edit(content, indata)
index daa7a6b..216c9df 100644 (file)
@@ -140,7 +140,7 @@ class BaseTopic:
             validate_input(input, self.schema_new)
         return input
 
-    def _validate_input_edit(self, input, force=False):
+    def _validate_input_edit(self, input, content, force=False):
         """
         Validates input user content for an edition. It uses jsonschema. Some overrides will use pyangbind
         :param input: user input content for the new topic
@@ -538,11 +538,13 @@ class BaseTopic:
             if indata and session.get("set_project"):
                 raise EngineException("Cannot edit content and set to project (query string SET_PROJECT) at same time",
                                       HTTPStatus.UNPROCESSABLE_ENTITY)
-            indata = self._validate_input_edit(indata, force=session["force"])
-
+            
             # TODO self._check_edition(session, indata, _id, force)
             if not content:
                 content = self.show(session, _id)
+            
+            indata = self._validate_input_edit(indata, content, force=session["force"])
+            
             deep_update_rfc7396(content, indata)
 
             # To allow project addressing by name AS WELL AS _id. Get the _id, just in case the provided one is a name
index 517f1db..ab0467a 100644 (file)
@@ -418,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"
@@ -672,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
@@ -773,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):
@@ -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()
index a26d576..2c8154e 100755 (executable)
@@ -481,6 +481,7 @@ class Test_UserTopicAuth(TestCase):
             new_name = "other-user-name"
             new_prms = [{}]
             self.auth.get_role_list.side_effect = [[user], []]
+            self.auth.get_user_list.side_effect = [[user]]
             with self.assertRaises(EngineException, msg="Accepted wrong project-role mappings") as e:
                 self.topic.edit(self.fake_session, uid, {"username": new_name, "project_role_mappings": new_prms})
             self.assertEqual(e.exception.http_code, HTTPStatus.UNPROCESSABLE_ENTITY, "Wrong HTTP status code")
@@ -698,6 +699,7 @@ class Test_CommonVimWimSdn(TestCase):
             self.assertEqual(operation["detailed-status"], "", "Wrong operation detailed status info")
             self.assertIsNone(operation["operationParams"], "Wrong operation parameters")
         with self.subTest(i=2):
+            self.db.get_one.side_effect = [cvws]
             with self.assertRaises(EngineException, msg="Accepted wrong property") as e:
                 self.topic.edit(self.fake_session, str(uuid4()), {"name": "new-name", "extra_prop": "anything"})
             self.assertEqual(e.exception.http_code, HTTPStatus.UNPROCESSABLE_ENTITY, "Wrong HTTP status code")