From 7860e7acbc40d63de3f3050c1d22d5f6d114266a Mon Sep 17 00:00:00 2001 From: Ekaterina Chernova Date: Tue, 19 Apr 2022 16:05:30 +0300 Subject: [PATCH] Handle FK error when creating/updating software deployments Software deployments have FK constraints on software configs. This change ensures the DBReferenceError caused by the constraints is properly caught. With this change now heat returns 400 Bad Response, instead of 500 Internal Server Error when a user tries to create a software deployment with a non-existing software config. Also, the stack_user_project_id field is defined as 64-chars-long string in DB model, so we should ensure that the input value is shorter than 65 chars. Otherwise it also results in DB error. Story: 2010001 Task: 45098 Change-Id: I03274dc0cffa226140eb720458cce81e8b5ce187 --- heat/db/api.py | 16 +++++-- heat/engine/service_software_config.py | 6 ++- .../engine/service/test_software_config.py | 46 +++++++++++++++++++ 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/heat/db/api.py b/heat/db/api.py index feec7d0530..577677054c 100644 --- a/heat/db/api.py +++ b/heat/db/api.py @@ -1244,8 +1244,13 @@ def software_deployment_create(context, values): obj_ref = models.SoftwareDeployment() obj_ref.update(values) - with context.session.begin(): - obj_ref.save(context.session) + try: + with context.session.begin(): + obj_ref.save(context.session) + except db_exception.DBReferenceError: + # NOTE(tkajinam): config_id is the only FK in SoftwareDeployment + err_msg = _('Config with id %s not found') % values['config_id'] + raise exception.Invalid(reason=err_msg) return obj_ref @@ -1278,7 +1283,12 @@ def software_deployment_get_all(context, server_id=None): def software_deployment_update(context, deployment_id, values): deployment = software_deployment_get(context, deployment_id) - update_and_save(context, deployment, values) + try: + update_and_save(context, deployment, values) + except db_exception.DBReferenceError: + # NOTE(tkajinam): config_id is the only FK in SoftwareDeployment + err_msg = _('Config with id %s not found') % values['config_id'] + raise exception.Invalid(reason=err_msg) return deployment diff --git a/heat/engine/service_software_config.py b/heat/engine/service_software_config.py index 1953971019..767535a852 100644 --- a/heat/engine/service_software_config.py +++ b/heat/engine/service_software_config.py @@ -268,7 +268,11 @@ class SoftwareConfigService(object): input_values, action, status, status_reason, stack_user_project_id, deployment_id=None): - + if stack_user_project_id is not None: + if len(stack_user_project_id) > 64: + raise exception.Invalid( + reason='"stack_user_project_id" ' + 'should be no more than 64 characters') if deployment_id is None: deployment_id = str(uuid.uuid4()) sd = software_deployment_object.SoftwareDeployment.create(cnxt, { diff --git a/heat/tests/engine/service/test_software_config.py b/heat/tests/engine/service/test_software_config.py index 9aba366a51..1e7fa1df55 100644 --- a/heat/tests/engine/service/test_software_config.py +++ b/heat/tests/engine/service/test_software_config.py @@ -504,6 +504,31 @@ class SoftwareConfigServiceTest(common.HeatTestCase): self.assertEqual(deployment_id, deployment['id']) self.assertEqual(kwargs['input_values'], deployment['input_values']) + def test_create_software_deployment_invalid_stack_user_project_id(self): + sc_kwargs = { + 'group': 'Heat::Chef', + 'name': 'config_heat', + 'config': '...', + 'inputs': [{'name': 'mode'}], + 'outputs': [{'name': 'endpoint'}], + 'options': {} + } + config = self._create_software_config(**sc_kwargs) + config_id = config['id'] + sd_kwargs = { + 'config_id': config_id, + 'input_values': {'mode': 'standalone'}, + 'action': 'INIT', + 'status': 'COMPLETE', + 'status_reason': '', + # stack_user_project should be no more than 64 characters + 'stack_user_project_id': 'a' * 65 + } + ex = self.assertRaises(dispatcher.ExpectedException, + self._create_software_deployment, + **sd_kwargs) + self.assertEqual(exception.Invalid, ex.exc_info[0]) + @mock.patch.object(service_software_config.SoftwareConfigService, '_refresh_swift_software_deployment') def test_show_software_deployment_refresh( @@ -1084,3 +1109,24 @@ class SoftwareConfigIOSchemaTest(common.HeatTestCase): self.assertEqual({'type': 'Number', 'name': 'baz', 'default': ''}, inp.as_dict()) + + +class SoftwareConfigServiceTestWithConstraint(SoftwareConfigServiceTest): + """Test cases which require FK constraints""" + + def setUp(self): + self.useFixture(utils.ForeignKeyConstraintFixture()) + super(SoftwareConfigServiceTestWithConstraint, self).setUp() + + def test_create_software_deployment_invalid_config_id(self): + kwargs = { + 'config_id': 'this_is_invalid', + 'input_values': {'mode': 'standalone'}, + 'action': 'INIT', + 'status': 'COMPLETE', + 'status_reason': '' + } + ex = self.assertRaises(dispatcher.ExpectedException, + self._create_software_deployment, + **kwargs) + self.assertEqual(exception.Invalid, ex.exc_info[0])