Fix possible DB race conditions in REST controller

Two (or more) consecutive DB API calls must use the
same transaction.

Change-Id: I68f7fd7b205818d3049c456b717beccf17153727
This commit is contained in:
Xavier Hardy 2016-11-15 14:26:01 +01:00
parent 9b39d7bc62
commit ae06418726
7 changed files with 126 additions and 112 deletions

View File

@ -43,6 +43,7 @@ def _load_deferred_output_field(action_ex):
def _get_action_execution(id):
with db_api.transaction():
action_ex = db_api.get_action_execution(id)
return _get_action_execution_resource(action_ex)
@ -275,15 +276,18 @@ class ActionExecutionsController(rest.RestController):
raise exc.NotAllowedException("Action execution deletion is not "
"allowed.")
with db_api.transaction():
action_ex = db_api.get_action_execution(id)
if action_ex.task_execution_id:
raise exc.NotAllowedException("Only ad-hoc action execution can "
"be deleted.")
raise exc.NotAllowedException(
"Only ad-hoc action execution can be deleted."
)
if not states.is_completed(action_ex.state):
raise exc.NotAllowedException("Only completed action execution "
"can be deleted.")
raise exc.NotAllowedException(
"Only completed action execution can be deleted."
)
return db_api.delete_action_execution(id)

View File

@ -96,10 +96,11 @@ class EventTriggersController(rest.RestController):
UPDATE_NOT_ALLOWED
)
db_api.ensure_event_trigger_exists(id)
LOG.info('Update event trigger: [id=%s, values=%s]', id, values)
with db_api.transaction():
db_api.ensure_event_trigger_exists(id)
db_model = triggers.update_event_trigger(id, values)
return resources.EventTrigger.from_dict(db_model.to_dict())
@ -112,6 +113,7 @@ class EventTriggersController(rest.RestController):
LOG.info("Delete event trigger [id=%s]", id)
with db_api.transaction():
event_trigger = db_api.get_event_trigger(id)
triggers.delete_event_trigger(event_trigger.to_dict())

View File

@ -95,6 +95,7 @@ class ExecutionsController(rest.RestController):
LOG.info('Update execution [id=%s, execution=%s]' % (id, wf_ex))
with db_api.transaction():
db_api.ensure_workflow_execution_exists(id)
delta = {}
@ -137,7 +138,6 @@ class ExecutionsController(rest.RestController):
)
if not delta.get('state') and delta.get('env'):
with db_api.transaction():
wf_ex = db_api.get_workflow_execution(id)
wf_ex = wf_service.update_workflow_execution_env(
wf_ex,

View File

@ -121,6 +121,7 @@ class MembersController(rest.RestController):
msg = "Member id must be provided."
raise exc.WorkflowException(msg)
with db_api.transaction():
wf_db = db_api.get_workflow_definition(self.resource_id)
if wf_db.scope != 'private':

View File

@ -140,6 +140,7 @@ class TasksController(rest.RestController):
acl.enforce('tasks:get', context.ctx())
LOG.info("Fetch task [id=%s]" % id)
with db_api.transaction():
task_ex = db_api.get_task_execution(id)
return _get_task_resource_with_result(task_ex)
@ -245,6 +246,7 @@ class TasksController(rest.RestController):
LOG.info("Update task execution [id=%s, task=%s]" % (id, task))
with db_api.transaction():
task_ex = db_api.get_task_execution(id)
task_spec = spec_parser.get_task_spec(task_ex.spec)
task_name = task.name or None
@ -254,7 +256,10 @@ class TasksController(rest.RestController):
if task_name and task_name != task_ex.name:
raise exc.WorkflowException('Task name does not match.')
wf_ex = db_api.get_workflow_execution(task_ex.workflow_execution_id)
wf_ex = db_api.get_workflow_execution(
task_ex.workflow_execution_id
)
wf_name = task.workflow_name or None
if wf_name and wf_name != wf_ex.name:
@ -262,7 +267,8 @@ class TasksController(rest.RestController):
if task.state != states.RUNNING:
raise exc.WorkflowException(
'Invalid task state. Only updating task to rerun is supported.'
'Invalid task state. '
'Only updating task to rerun is supported.'
)
if task_ex.state != states.ERROR:
@ -282,6 +288,7 @@ class TasksController(rest.RestController):
env=env
)
with db_api.transaction():
task_ex = db_api.get_task_execution(id)
return _get_task_resource_with_result(task_ex)

View File

@ -158,7 +158,6 @@ def create_event_trigger(name, exchange, topic, event, workflow_id,
def delete_event_trigger(event_trigger):
with db_api.transaction():
db_api.delete_event_trigger(event_trigger['id'])
trigs = db_api.get_event_triggers(
@ -177,7 +176,6 @@ def delete_event_trigger(event_trigger):
def update_event_trigger(id, values):
with db_api.transaction():
trig = db_api.update_event_trigger(id, values)
# NOTE(kong): Send RPC message within the db transaction, rollback if

View File

@ -25,6 +25,7 @@ import six
import webob
from wsme import exc as wsme_exc
from mistral.db.v2.sqlalchemy import api as db_api
from mistral import exceptions as exc
LOG = logging.getLogger(__name__)
@ -163,6 +164,7 @@ def get_all(list_cls, cls, get_all_function, get_function,
list_to_return = []
if resource_function:
with db_api.transaction():
# do not filter fields yet, resource_function needs the ORM object
db_list = get_all_function(
limit=limit,