diff options
author | Zuul <zuul@review.opendev.org> | 2019-10-08 09:49:18 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2019-10-08 09:49:18 +0000 |
commit | 18f1dbb4521ee24bfda61866f71f393ca0f65b17 (patch) | |
tree | adb25498f8567a81125b1189f9e988168dc8bc6a /heat | |
parent | 3029011dea16994d495bfa6a1cdb03a6451334da (diff) | |
parent | 335b7cf519150f9a00349b161be911a5b527c34f (diff) | |
download | heat-18f1dbb4521ee24bfda61866f71f393ca0f65b17.tar.gz |
Merge "Add a non-racy check for unique stack names"
Diffstat (limited to 'heat')
-rw-r--r-- | heat/db/sqlalchemy/api.py | 12 | ||||
-rw-r--r-- | heat/engine/service.py | 3 | ||||
-rw-r--r-- | heat/tests/db/test_sqlalchemy_api.py | 15 |
3 files changed, 29 insertions, 1 deletions
diff --git a/heat/db/sqlalchemy/api.py b/heat/db/sqlalchemy/api.py index 3966b0e3a..dd3740a1f 100644 --- a/heat/db/sqlalchemy/api.py +++ b/heat/db/sqlalchemy/api.py @@ -571,7 +571,7 @@ def stack_get_by_name(context, stack_name): models.Stack.tenant == context.tenant_id, models.Stack.stack_user_project_id == context.tenant_id) ).filter_by(name=stack_name) - return query.first() + return query.order_by(models.Stack.created_at).first() def stack_get(context, stack_id, show_deleted=False, eager_load=True): @@ -757,7 +757,17 @@ def stack_count_all(context, filters=None, def stack_create(context, values): stack_ref = models.Stack() stack_ref.update(values) + stack_name = stack_ref.name stack_ref.save(context.session) + + # Even though we just created a stack with this name, we may not find + # it again because some unit tests create stacks with deleted_at set. Also + # some backup stacks may not be found, for reasons that are unclear. + earliest = stack_get_by_name(context, stack_name) + if earliest is not None and earliest.id != stack_ref.id: + context.session.query(models.Stack).filter_by(id=stack_ref.id).delete() + raise exception.StackExists(stack_name=stack_name) + return stack_ref diff --git a/heat/engine/service.py b/heat/engine/service.py index d761577b5..62af50a95 100644 --- a/heat/engine/service.py +++ b/heat/engine/service.py @@ -674,6 +674,9 @@ class EngineService(service.ServiceBase): raise exception.MissingCredentialError(required='X-Auth-Key') def _validate_new_stack(self, cnxt, stack_name, parsed_template): + # We'll check that the stack name is unique in the tenant while + # storing it in the database to avoid races, but also check it here + # before validating so we can fail early. if stack_object.Stack.get_by_name(cnxt, stack_name): raise exception.StackExists(stack_name=stack_name) diff --git a/heat/tests/db/test_sqlalchemy_api.py b/heat/tests/db/test_sqlalchemy_api.py index 856f54c38..a8356178f 100644 --- a/heat/tests/db/test_sqlalchemy_api.py +++ b/heat/tests/db/test_sqlalchemy_api.py @@ -357,6 +357,21 @@ class SqlAlchemyTest(common.HeatTestCase): st = db_api.stack_get_by_name(self.ctx, name) self.assertIsNone(st) + def test_stack_create_multiple(self): + name = 'stack_race' + stack = self._setup_test_stack(name, UUID1, + stack_user_project_id=UUID2)[1] + self.assertRaises(exception.StackExists, + self._setup_test_stack, + name, UUID2, stack_user_project_id=UUID2) + + st = db_api.stack_get_by_name(self.ctx, name) + self.assertEqual(UUID1, st.id) + + stack.delete() + + self.assertIsNone(db_api.stack_get_by_name(self.ctx, name)) + def test_nested_stack_get_by_name(self): stack1 = self._setup_test_stack('neststack1', UUID1)[1] stack2 = self._setup_test_stack('neststack2', UUID2, |