From c0aabf968630bcb5607d6d5869a06550209e2c48 Mon Sep 17 00:00:00 2001 From: Frank Bryden Date: Mon, 27 Jul 2020 13:55:11 +0000 Subject: [PATCH] PATCH support for enabling and disabling of NSDs and VNFDs. Moved the validation code to _validate_input_edit. Updated relevant tests. Change-Id: I0666f28e1f06585f3648b0a88812f7348c622d86 Signed-off-by: Frank Bryden --- osm_nbi/admin_topics.py | 14 +++--- osm_nbi/base_topic.py | 8 ++-- osm_nbi/descriptor_topics.py | 76 ++++++++++++++++++++++++++---- osm_nbi/tests/test_admin_topics.py | 2 + 4 files changed, 80 insertions(+), 20 deletions(-) diff --git a/osm_nbi/admin_topics.py b/osm_nbi/admin_topics.py index f831b63..998c12f 100644 --- a/osm_nbi/admin_topics.py +++ b/osm_nbi/admin_topics.py @@ -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) diff --git a/osm_nbi/base_topic.py b/osm_nbi/base_topic.py index daa7a6b..216c9df 100644 --- a/osm_nbi/base_topic.py +++ b/osm_nbi/base_topic.py @@ -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 diff --git a/osm_nbi/descriptor_topics.py b/osm_nbi/descriptor_topics.py index 517f1db..ab0467a 100644 --- a/osm_nbi/descriptor_topics.py +++ b/osm_nbi/descriptor_topics.py @@ -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() diff --git a/osm_nbi/tests/test_admin_topics.py b/osm_nbi/tests/test_admin_topics.py index a26d576..2c8154e 100755 --- a/osm_nbi/tests/test_admin_topics.py +++ b/osm_nbi/tests/test_admin_topics.py @@ -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") -- 2.25.1