From a9cb38545262b9b793b81e7f409dc73fae072430 Mon Sep 17 00:00:00 2001 From: Yi Feng Date: Mon, 7 Jun 2021 13:37:16 +0900 Subject: [PATCH] Fix Patch Individual VNF instance In the condition when the vnf instance hasn't been instantiated, an error occurs when Patching Individual VNF instance. 1. Fix the exception caused by the NULL value of vnf's updated_at field. 2. Fix the exception happened when one of its attributes is not existed in method to_dict in Class VnfInfoModifications. 3. Fix the problem that vnfInstanceName and vnfInstanceDescription are not displayed in notification when vnfdId is included in the fields which need modified. 4. In the API reference, modify the method of modifying an "Individual VNF instance" resource from POST to PATCH. Closes-Bug: #1915100 Change-Id: Iddb5bb540b817d4fd24e2c7c12f885bd39f8662b --- api-ref/source/v1/vnflcm.inc | 2 +- tacker/api/vnflcm/v1/controller.py | 4 +- tacker/conductor/conductor_server.py | 4 +- tacker/objects/vnf_lcm_op_occs.py | 31 +++++++----- .../unit/conductor/test_conductor_server.py | 48 ++++++++++++++++++- tacker/tests/unit/vnflcm/fakes.py | 33 +++++++++++-- 6 files changed, 98 insertions(+), 24 deletions(-) diff --git a/api-ref/source/v1/vnflcm.inc b/api-ref/source/v1/vnflcm.inc index d233ea1c6..5a5da373a 100644 --- a/api-ref/source/v1/vnflcm.inc +++ b/api-ref/source/v1/vnflcm.inc @@ -616,7 +616,7 @@ Request Example Modify a VNF instance ======================== -.. rest_method:: POST /vnflcm/v1/vnf_instances/{vnfInstanceId} +.. rest_method:: PATCH /vnflcm/v1/vnf_instances/{vnfInstanceId} This method modifies an "Individual VNF instance" resource. diff --git a/tacker/api/vnflcm/v1/controller.py b/tacker/api/vnflcm/v1/controller.py index 2e468256e..d4239b83e 100644 --- a/tacker/api/vnflcm/v1/controller.py +++ b/tacker/api/vnflcm/v1/controller.py @@ -802,7 +802,7 @@ class VnfLcmController(wsgi.Controller): return self._view_builder.show_lcm_op_occs(vnf_lcm_op_occs) - @wsgi.response(http_client.OK) + @wsgi.response(http_client.ACCEPTED) @wsgi.expected_errors((http_client.FORBIDDEN, http_client.NOT_FOUND)) def update(self, request, id, body): context = request.environ['tacker.context'] @@ -906,7 +906,7 @@ class VnfLcmController(wsgi.Controller): vnf_lcm_opoccs = { 'vnf_instance_id': id, 'id': op_occs_uuid, - 'state_entered_time': vnf_data.get('updated_at'), + 'state_entered_time': timeutils.utcnow(), 'operationParams': str(body)} self.rpc_api.update( diff --git a/tacker/conductor/conductor_server.py b/tacker/conductor/conductor_server.py index 6a52d8d28..4a963b429 100644 --- a/tacker/conductor/conductor_server.py +++ b/tacker/conductor/conductor_server.py @@ -2113,7 +2113,7 @@ class Conductor(manager.Manager): lcm_op_obj.operation = fields.InstanceOperation.MODIFY_INFO lcm_op_obj.is_automatic_invocation = 0 lcm_op_obj.is_cancel_pending = 0 - lcm_op_obj.operationParams = vnf_lcm_opoccs.get('operationParams') + lcm_op_obj.operation_params = vnf_lcm_opoccs.get('operationParams') try: lcm_op_obj.create() @@ -2161,7 +2161,9 @@ class Conductor(manager.Manager): vnfd_pkg_data) else: changed_info = objects.vnf_lcm_op_occs.VnfInfoModifications() + if body_data.get('vnf_instance_name'): changed_info.vnf_instance_name = body_data.get('vnf_instance_name') + if body_data.get('vnf_instance_description'): changed_info.vnf_instance_description = body_data.get( 'vnf_instance_description') diff --git a/tacker/objects/vnf_lcm_op_occs.py b/tacker/objects/vnf_lcm_op_occs.py index 19db410fb..4c5e5966b 100644 --- a/tacker/objects/vnf_lcm_op_occs.py +++ b/tacker/objects/vnf_lcm_op_occs.py @@ -854,19 +854,24 @@ class VnfInfoModifications(base.TackerObject, return obj def to_dict(self): - return { - 'vnf_instance_name': self.vnf_instance_name, - 'vnf_instance_description': self.vnf_instance_description, - 'metadata': self.metadata, - 'vim_connection_info': self.vim_connection_info, - 'vim_connection_info_delete_ids': - self.vim_connection_info_delete_ids, - 'vnf_pkg_id': self.vnf_pkg_id, - 'vnfd_id': self.vnfd_id, - 'vnf_provider': self.vnf_provider, - 'vnf_product_name': self.vnf_product_name, - 'vnf_software_version': self.vnf_software_version, - 'vnfd_version': self.vnfd_version} + """For the attributes of this class, if an attribute exists and is not + + null, it means that the attribute has been modified. This method + + returns a dictionary containing the modified attributes. + """ + dct = {} + for field in self.fields: + if field in self and getattr(self, field): + value = getattr(self, field) + # Since the type of vim_connection_info is ListOfObjectsField, + # the objects in vim_connection_info also need to be converted + # into dictionary, otherwise an error will occur + # when serialized. + if field == "vim_connection_info": + value = [vim_conn.to_dict() for vim_conn in value] + dct[field] = value + return dct @base.TackerObjectRegistry.register diff --git a/tacker/tests/unit/conductor/test_conductor_server.py b/tacker/tests/unit/conductor/test_conductor_server.py index eb7696ab4..74f1470f2 100644 --- a/tacker/tests/unit/conductor/test_conductor_server.py +++ b/tacker/tests/unit/conductor/test_conductor_server.py @@ -158,8 +158,8 @@ class TestConductor(SqlTestCase, unit_base.FixturedTestCase): 'state_entered_time': datetime.datetime( 1900, 1, 1, 1, 1, 1, tzinfo=iso8601.UTC), - 'operationParams': { - "key": "value"}} + 'operationParams': 'operationParams' + } return vnf_lcm_opoccs def _create_vnfd_pkg_data(self): @@ -2974,6 +2974,50 @@ class TestConductor(SqlTestCase, unit_base.FixturedTestCase): self.vnfd_pkg_data, vnfd_id) + @mock.patch.object(conductor_server, 'revert_update_lcm') + @mock.patch.object(t_context.get_admin_context().session, "add") + @mock.patch.object(objects.vnf_lcm_op_occs.VnfLcmOpOcc, "save") + @mock.patch.object(objects.VnfInstance, "update") + @mock.patch.object(objects.vnf_lcm_op_occs.VnfLcmOpOcc, "create") + def test_update_lcm_without_vnf_instance_name(self, mock_create, + mock_update, mock_save, + mock_add, mock_revert): + mock_create.return_value = "OK" + mock_update.return_value = datetime.datetime( + 1900, 1, 1, 1, 1, 1, tzinfo=iso8601.UTC) + mock_add.return_value = "OK" + mock_save.return_value = "OK" + vnfd_id = "2c69a161-0000-4b0f-bcf8-391f8fc76600" + self.body_data.pop('vnf_instance_name') + self.conductor.update( + self.context, + self.vnf_lcm_opoccs, + self.body_data, + self.vnfd_pkg_data, + vnfd_id) + + @mock.patch.object(conductor_server, 'revert_update_lcm') + @mock.patch.object(t_context.get_admin_context().session, "add") + @mock.patch.object(objects.vnf_lcm_op_occs.VnfLcmOpOcc, "save") + @mock.patch.object(objects.VnfInstance, "update") + @mock.patch.object(objects.vnf_lcm_op_occs.VnfLcmOpOcc, "create") + def test_update_lcm_without_vnf_instance_description(self, mock_create, + mock_update, mock_save, + mock_add, mock_revert): + mock_create.return_value = "OK" + mock_update.return_value = datetime.datetime( + 1900, 1, 1, 1, 1, 1, tzinfo=iso8601.UTC) + mock_add.return_value = "OK" + mock_save.return_value = "OK" + vnfd_id = "2c69a161-0000-4b0f-bcf8-391f8fc76600" + self.body_data.pop('vnf_instance_description') + self.conductor.update( + self.context, + self.vnf_lcm_opoccs, + self.body_data, + self.vnfd_pkg_data, + vnfd_id) + def test_update_vim(self): vim_id = uuidsentinel.vim_id status = "REACHABLE" diff --git a/tacker/tests/unit/vnflcm/fakes.py b/tacker/tests/unit/vnflcm/fakes.py index 2d6d12894..6ba84e435 100644 --- a/tacker/tests/unit/vnflcm/fakes.py +++ b/tacker/tests/unit/vnflcm/fakes.py @@ -968,9 +968,18 @@ def vnflcm_fail_check_added_params(error_point=7): vim_level_resource_type="OS::Neutron::Net"), ext_link_ports=[ext_link_port_info] ) + vim_connection_info = {"id": 'f8c35bd0-4d67-4436-9f11-14b8a84c92aa', + "vim_id": 'f8c35bd0-4d67-4436-9f11-14b8a84c92aa', + "vim_type": 'openstack', + "access_info": {"key1": 'value1', "key2": 'value2'}} changed_info_values = objects.VnfInfoModifications( vnf_instance_name="fake_name", vnf_instance_description="fake_vnf_instance_description", + metadata={'key': 'value'}, + vim_connection_info=[VimConnectionInfo(**vim_connection_info)], + vim_connection_info_delete_ids=[ + 'f8c35bd0-4d67-4436-9f11-14b8a84c92bb'], + vnf_pkg_id='f26f181d-7891-4720-b022-b074ec1733ef', vnfd_id="f26f181d-7891-4720-b022-b074ec1733ef", vnf_provider="fake_vnf_provider", vnf_product_name="fake_vnf_product_name", @@ -1413,10 +1422,15 @@ VNFLCMOPOCC_RESPONSE = { }] }, 'changedInfo': { - 'metadata': {}, - 'vimConnectionInfo': [], - 'vimConnectionInfoDeleteIds': [], - 'vnfPkgId': None, + 'metadata': {'key': 'value'}, + 'vimConnectionInfo': [ + {"id": 'f8c35bd0-4d67-4436-9f11-14b8a84c92aa', + "vimId": 'f8c35bd0-4d67-4436-9f11-14b8a84c92aa', + "vimType": 'openstack', + 'interfaceInfo': {}, + "accessInfo": {"key1": 'value1', "key2": 'value2'}}], + 'vimConnectionInfoDeleteIds': ['f8c35bd0-4d67-4436-9f11-14b8a84c92bb'], + 'vnfPkgId': 'f26f181d-7891-4720-b022-b074ec1733ef', 'vnfInstanceName': 'fake_name', 'vnfInstanceDescription': "fake_vnf_instance_description", 'vnfdId': 'f26f181d-7891-4720-b022-b074ec1733ef', @@ -1528,11 +1542,20 @@ def fake_vnf_lcm_op_occs(): } resource_changes_obj = objects.ResourceChanges(**resource_changes) + vim_connection_info = { + "id": 'f8c35bd0-4d67-4436-9f11-14b8a84c92aa', + "vim_id": 'f8c35bd0-4d67-4436-9f11-14b8a84c92aa', + "vim_type": 'openstack', + "access_info": {"key1": 'value1', "key2": 'value2'}} changed_info = { "vnf_instance_name": "fake_name", "vnf_instance_description": "fake_vnf_instance_description", - "metadata": {}, + 'metadata': {'key': 'value'}, + 'vim_connection_info': [VimConnectionInfo(**vim_connection_info)], + 'vim_connection_info_delete_ids': [ + 'f8c35bd0-4d67-4436-9f11-14b8a84c92bb'], + 'vnf_pkg_id': 'f26f181d-7891-4720-b022-b074ec1733ef', "vnfd_id": "f26f181d-7891-4720-b022-b074ec1733ef", "vnf_provider": "fake_vnf_provider", "vnf_product_name": "fake_vnf_product_name",