diff --git a/heat/db/sqlalchemy/api.py b/heat/db/sqlalchemy/api.py index dfdbcde005..fd7cb9ad8b 100644 --- a/heat/db/sqlalchemy/api.py +++ b/heat/db/sqlalchemy/api.py @@ -802,8 +802,13 @@ def stack_delete(context, stack_id): delete_softly(context, s) +def _is_duplicate_error(exc): + return isinstance(exc, db_exception.DBDuplicateEntry) + + @oslo_db_api.wrap_db_retry(max_retries=3, retry_on_deadlock=True, - retry_interval=0.5, inc_retry_interval=True) + retry_interval=0.5, inc_retry_interval=True, + exception_checker=_is_duplicate_error) def stack_lock_create(context, stack_id, engine_id): with db_context.writer.independent.using(context) as session: lock = session.query(models.StackLock).get(stack_id) diff --git a/heat/engine/service.py b/heat/engine/service.py index fb94cb6b62..f0929a1355 100644 --- a/heat/engine/service.py +++ b/heat/engine/service.py @@ -2449,15 +2449,17 @@ class EngineService(service.ServiceBase): service_objects.Service.delete(cnxt, service_ref['id']) def reset_stack_status(self): - cnxt = context.get_admin_context() filters = { 'status': parser.Stack.IN_PROGRESS, 'convergence': False } - stacks = stack_object.Stack.get_all(cnxt, + stacks = stack_object.Stack.get_all(context.get_admin_context(), filters=filters, show_nested=True) for s in stacks: + # Build one context per stack, so that it can safely be passed to + # to thread. + cnxt = context.get_admin_context() stack_id = s.id lock = stack_lock.StackLock(cnxt, stack_id, self.engine_id) engine_id = lock.get_engine_id() diff --git a/heat/tests/test_engine_service.py b/heat/tests/test_engine_service.py index 743e97e059..854979c272 100644 --- a/heat/tests/test_engine_service.py +++ b/heat/tests/test_engine_service.py @@ -1328,7 +1328,7 @@ class StackServiceTest(common.HeatTestCase): self.eng.reset_stack_status() - mock_admin_context.assert_called_once_with() + mock_admin_context.assert_called() filters = { 'status': parser.Stack.IN_PROGRESS, 'convergence': False