Example #1
0
    def rerun(self, task_ex, reset=True, env=None):
        """Rerun workflow from the given task.

        :param task_ex: Task execution that the workflow needs to rerun from.
        :param reset: If True, reset task state including deleting its action
            executions.
        :param env: Environment.
        """

        assert self.wf_ex

        wf_service.update_workflow_execution_env(self.wf_ex, env)

        self._recursive_rerun()

        wf_ctrl = wf_base.get_controller(self.wf_ex)

        # Calculate commands to process next.
        cmds = wf_ctrl.rerun_tasks([task_ex], reset=reset)

        if cmds:
            # Import the task_handler module here to avoid circular reference.
            from mistral.engine import policies

            policies.RetryPolicy.refresh_runtime_context(task_ex)

        self._continue_workflow(cmds)
Example #2
0
    def rerun(self, task_ex, reset=True, env=None):
        """Rerun workflow from the given task.

        :param task_ex: Task execution that the workflow needs to rerun from.
        :param reset: If True, reset task state including deleting its action
            executions.
        :param env: Environment.
        """

        assert self.wf_ex

        # Since some lookup utils functions may use cache for completed tasks
        # we need to clean caches to make sure that stale objects can't be
        # retrieved.
        lookup_utils.clear_caches()

        wf_service.update_workflow_execution_env(self.wf_ex, env)

        self.set_state(states.RUNNING, recursive=True)

        wf_ctrl = wf_base.get_controller(self.wf_ex)

        # Calculate commands to process next.
        cmds = wf_ctrl.rerun_tasks([task_ex], reset=reset)

        if cmds:
            # Import the task_handler module here to avoid circular reference.
            from mistral.engine import policies

            policies.RetryPolicy.refresh_runtime_context(task_ex)

        self._continue_workflow(cmds)
Example #3
0
    def resume(self, env=None):
        """Resume workflow.

        :param env: Environment.
        """

        assert self.wf_ex

        wf_service.update_workflow_execution_env(self.wf_ex, env)

        self.set_state(states.RUNNING)

        # Publish event.
        self.notify(events.WORKFLOW_RESUMED)

        wf_ctrl = wf_base.get_controller(self.wf_ex)

        # Calculate commands to process next.
        cmds = wf_ctrl.continue_workflow()

        self._continue_workflow(cmds)

        # If workflow execution is a subworkflow,
        # schedule update to the task execution.
        if self.wf_ex.task_execution_id:
            # Import the task_handler module here to avoid circular reference.
            from mistral.engine import task_handler

            task_handler.schedule_on_action_update(self.wf_ex)
Example #4
0
    def rerun(self, task_ex, reset=True, env=None):
        """Rerun workflow from the given task.

        :param task_ex: Task execution that the workflow needs to rerun from.
        :param reset: If True, reset task state including deleting its action
            executions.
        :param env: Environment.
        """

        assert self.wf_ex

        wf_service.update_workflow_execution_env(self.wf_ex, env)

        self._recursive_rerun()

        wf_ctrl = wf_base.get_controller(self.wf_ex)

        # Calculate commands to process next.
        cmds = wf_ctrl.rerun_tasks([task_ex], reset=reset)

        if cmds:
            # Import the task_handler module here to avoid circular reference.
            from mistral.engine import policies

            policies.RetryPolicy.refresh_runtime_context(task_ex)

        self._continue_workflow(cmds)
Example #5
0
    def rerun(self, task, reset=True, env=None):
        """Rerun workflow from the given task.

        :param task: An engine task associated with the task the workflow
            needs to rerun from.
        :param reset: If True, reset task state including deleting its action
            executions.
        :param env: Environment.
        """

        assert self.wf_ex

        wf_service.update_workflow_execution_env(self.wf_ex, env)

        self._recursive_rerun()

        wf_ctrl = wf_base.get_controller(self.wf_ex)

        # Calculate commands to process next.
        cmds = wf_ctrl.rerun_tasks([task.task_ex], reset=reset)

        if cmds:
            task.cleanup_runtime_context()

        self._continue_workflow(cmds)
Example #6
0
    def rerun(self, task_ex, reset=True, env=None):
        """Rerun workflow from the given task.

        :param task_ex: Task execution that the workflow needs to rerun from.
        :param reset: If True, reset task state including deleting its action
            executions.
        :param env: Environment.
        """

        assert self.wf_ex

        # Since some lookup utils functions may use cache for completed tasks
        # we need to clean caches to make sure that stale objects can't be
        # retrieved.
        lookup_utils.clean_caches()

        wf_service.update_workflow_execution_env(self.wf_ex, env)

        self.set_state(states.RUNNING, recursive=True)

        wf_ctrl = wf_base.get_controller(self.wf_ex)

        # Calculate commands to process next.
        cmds = wf_ctrl.rerun_tasks([task_ex], reset=reset)

        self._continue_workflow(cmds)
Example #7
0
    def resume(self, env=None):
        """Resume workflow.

        :param env: Environment.
        """

        assert self.wf_ex

        wf_service.update_workflow_execution_env(self.wf_ex, env)

        self.set_state(states.RUNNING)

        wf_ctrl = wf_base.get_controller(self.wf_ex)

        # Calculate commands to process next.
        cmds = wf_ctrl.continue_workflow()

        self._continue_workflow(cmds)

        # If workflow execution is a subworkflow,
        # schedule update to the task execution.
        if self.wf_ex.task_execution_id:
            # Import the task_handler module here to avoid circular reference.
            from mistral.engine import task_handler

            task_handler.schedule_on_action_update(self.wf_ex)
Example #8
0
    def resume(self, env=None):
        """Resume workflow.

        :param env: Environment.
        """

        assert self.wf_ex

        wf_service.update_workflow_execution_env(self.wf_ex, env)

        self.set_state(states.RUNNING, recursive=True)

        self._continue_workflow(env=env)
Example #9
0
    def resume(self, env=None):
        """Resume workflow.

        :param env: Environment.
        """

        assert self.wf_ex

        wf_service.update_workflow_execution_env(self.wf_ex, env)

        self.set_state(states.RUNNING, recursive=True)

        self._continue_workflow(env=env)
Example #10
0
    def rerun(self, task_ex, reset=True, env=None):
        """Rerun workflow from the given task.

        :param task_ex: Task execution that the workflow needs to rerun from.
        :param reset: If True, reset task state including deleting its action
            executions.
        :param env: Environment.
        """

        assert self.wf_ex

        wf_service.update_workflow_execution_env(self.wf_ex, env)

        self.set_state(states.RUNNING, recursive=True)

        self._continue_workflow(task_ex, reset, env=env)
Example #11
0
    def rerun(self, task_ex, reset=True, env=None):
        """Rerun workflow from the given task.

        :param task_ex: Task execution that the workflow needs to rerun from.
        :param reset: If True, reset task state including deleting its action
            executions.
        :param env: Environment.
        """

        assert self.wf_ex

        wf_service.update_workflow_execution_env(self.wf_ex, env)

        self.set_state(states.RUNNING, recursive=True)

        self._continue_workflow(task_ex, reset, env=env)
Example #12
0
    def _continue_workflow(self, wf_ex, task_ex=None, reset=True, env=None):
        wf_ex = wf_service.update_workflow_execution_env(wf_ex, env)

        wf_handler.set_execution_state(wf_ex,
                                       states.RUNNING,
                                       set_upstream=True)

        wf_ctrl = wf_base.WorkflowController.get_controller(wf_ex)

        # Calculate commands to process next.
        cmds = wf_ctrl.continue_workflow(task_ex=task_ex, reset=reset, env=env)

        # When resuming a workflow we need to ignore all 'pause'
        # commands because workflow controller takes tasks that
        # completed within the period when the workflow was pause.
        cmds = list(
            filter(lambda c: not isinstance(c, commands.PauseWorkflow), cmds))

        # Since there's no explicit task causing the operation
        # we need to mark all not processed tasks as processed
        # because workflow controller takes only completed tasks
        # with flag 'processed' equal to False.
        for t_ex in wf_ex.task_executions:
            if states.is_completed(t_ex.state) and not t_ex.processed:
                t_ex.processed = True

        self._dispatch_workflow_commands(wf_ex, cmds)

        if not cmds:
            if not wf_utils.find_incomplete_task_executions(wf_ex):
                wf_handler.succeed_workflow(
                    wf_ex, wf_ctrl.evaluate_workflow_final_context())

        return wf_ex.get_clone()
Example #13
0
    def rerun(self, task_ex, reset=True, env=None):
        """Rerun workflow from the given task.

        :param task_ex: Task execution that the workflow needs to rerun from.
        :param reset: If True, reset task state including deleting its action
            executions.
        :param env: Environment.
        """

        assert self.wf_ex

        # Since some lookup utils functions may use cache for completed tasks
        # we need to clean caches to make sure that stale objects can't be
        # retrieved.
        lookup_utils.clear_caches()

        # Add default wf_ex.params['env'] for rerun, pass commands extensions.
        # It uses in update_workflow_execution_env method.
        #
        # task_input: customer manual input data
        # task_output: customer manual output data
        # task_action: rerun | pass
        if 'env' not in self.wf_ex.params:
            self.wf_ex.params['env'] = {}
        self._task_input_data = env.pop('task_input', None)
        self._task_action = env.pop('task_action', None)
        self._task_output_data = env.pop('task_output', None)

        wf_service.update_workflow_execution_env(self.wf_ex, env)

        self.set_state(states.RUNNING, recursive=True)

        wf_ctrl = wf_base.get_controller(self.wf_ex)

        # Calculate commands to process next.
        cmds = wf_ctrl.rerun_tasks([task_ex], reset=reset)

        # Add manual input field in task execution instance for delivering
        # input data to task handling level.
        for cmd in cmds:
            cmd.task_ex._manual_input = self._task_input_data
            cmd.task_ex._manual_action = self._task_action
            cmd.task_ex._manual_output = self._task_output_data

        self._continue_workflow(cmds)
Example #14
0
    def resume(self, env=None):
        """Resume workflow.

        :param env: Environment.
        """

        assert self.wf_ex

        wf_service.update_workflow_execution_env(self.wf_ex, env)

        self.set_state(states.RUNNING, recursive=True)

        wf_ctrl = wf_base.get_controller(self.wf_ex)

        # Calculate commands to process next.
        cmds = wf_ctrl.continue_workflow()

        self._continue_workflow(cmds)
Example #15
0
        def _compute_delta(wf_ex):
            with db_api.transaction():
                # ensure that workflow execution exists
                db_api.get_workflow_execution(
                    id,
                    fields=(db_models.WorkflowExecution.id,)
                )

                delta = {}

                if wf_ex.state:
                    delta['state'] = wf_ex.state

                if wf_ex.description:
                    delta['description'] = wf_ex.description

                if wf_ex.params and wf_ex.params.get('env'):
                    delta['env'] = wf_ex.params.get('env')

                # Currently we can change only state, description, or env.
                if len(delta.values()) <= 0:
                    raise exc.InputException(
                        'The property state, description, or env '
                        'is not provided for update.'
                    )

                # Description cannot be updated together with state.
                if delta.get('description') and delta.get('state'):
                    raise exc.InputException(
                        'The property description must be updated '
                        'separately from state.'
                    )

                # If state change, environment cannot be updated
                # if not RUNNING.
                if (delta.get('env') and
                        delta.get('state') and
                        delta['state'] != states.RUNNING):
                    raise exc.InputException(
                        'The property env can only be updated when workflow '
                        'execution is not running or on resume from pause.'
                    )

                if delta.get('description'):
                    wf_ex = db_api.update_workflow_execution(
                        id,
                        {'description': delta['description']}
                    )

                if not delta.get('state') and delta.get('env'):
                    wf_ex = db_api.get_workflow_execution(id)
                    wf_ex = wf_service.update_workflow_execution_env(
                        wf_ex,
                        delta.get('env')
                    )

                return delta, wf_ex
Example #16
0
    def resume(self, env=None):
        """Resume workflow.

        :param env: Environment.
        """

        assert self.wf_ex

        wf_service.update_workflow_execution_env(self.wf_ex, env)

        self.set_state(states.RUNNING, recursive=True)

        wf_ctrl = wf_base.get_controller(self.wf_ex)

        # Calculate commands to process next.
        cmds = wf_ctrl.continue_workflow()

        self._continue_workflow(cmds)
Example #17
0
        def _compute_delta(wf_ex):
            with db_api.transaction():
                # ensure that workflow execution exists
                db_api.get_workflow_execution(
                    id,
                    fields=(db_models.WorkflowExecution.id,)
                )

                delta = {}

                if wf_ex.state:
                    delta['state'] = wf_ex.state

                if wf_ex.description:
                    delta['description'] = wf_ex.description

                if wf_ex.params and wf_ex.params.get('env'):
                    delta['env'] = wf_ex.params.get('env')

                # Currently we can change only state, description, or env.
                if len(delta.values()) <= 0:
                    raise exc.InputException(
                        'The property state, description, or env '
                        'is not provided for update.'
                    )

                # Description cannot be updated together with state.
                if delta.get('description') and delta.get('state'):
                    raise exc.InputException(
                        'The property description must be updated '
                        'separately from state.'
                    )

                # If state change, environment cannot be updated
                # if not RUNNING.
                if (delta.get('env') and
                        delta.get('state') and
                        delta['state'] != states.RUNNING):
                    raise exc.InputException(
                        'The property env can only be updated when workflow '
                        'execution is not running or on resume from pause.'
                    )

                if delta.get('description'):
                    wf_ex = db_api.update_workflow_execution(
                        id,
                        {'description': delta['description']}
                    )

                if not delta.get('state') and delta.get('env'):
                    wf_ex = db_api.get_workflow_execution(id)
                    wf_ex = wf_service.update_workflow_execution_env(
                        wf_ex,
                        delta.get('env')
                    )

                return delta, wf_ex
Example #18
0
    def rerun(self, task_ex, reset=True, env=None):
        """Rerun workflow from the given task.

        :param task_ex: Task execution that the workflow needs to rerun from.
        :param reset: If True, reset task state including deleting its action
            executions.
        :param env: Environment.
        """

        assert self.wf_ex

        wf_service.update_workflow_execution_env(self.wf_ex, env)

        self.set_state(states.RUNNING, recursive=True)

        _update_task_environment(task_ex, env)

        wf_ctrl = wf_base.get_controller(self.wf_ex)

        # Calculate commands to process next.
        cmds = wf_ctrl.rerun_tasks([task_ex], reset=reset)

        self._continue_workflow(cmds)
Example #19
0
    def resume(self, env=None):
        """Resume workflow.

        :param env: Environment.
        """

        assert self.wf_ex

        wf_service.update_workflow_execution_env(self.wf_ex, env)

        self.set_state(states.RUNNING, recursive=True)

        wf_ctrl = wf_base.get_controller(self.wf_ex)

        # Calculate commands to process next.
        cmds = wf_ctrl.continue_workflow()

        if env:
            for cmd in cmds:
                if isinstance(cmd, commands.RunExistingTask):
                    _update_task_environment(cmd.task_ex, env)

        self._continue_workflow(cmds)
Example #20
0
    def test_update_workflow_execution_env(self):
        wf_exec_template = {
            'spec': {},
            'start_params': {
                'task': 'my_task1'
            },
            'state': 'PAUSED',
            'state_info': None,
            'params': {
                'env': {
                    'k1': 'abc'
                }
            },
            'created_at': None,
            'updated_at': None,
            'context': {
                '__env': {
                    'k1': 'fee fi fo fum'
                }
            },
            'task_id': None,
            'trust_id': None,
            'description': None,
            'output': None
        }

        states_permitted = [states.IDLE, states.PAUSED, states.ERROR]

        update_env = {'k1': 'foobar'}

        for state in states_permitted:
            wf_exec = copy.deepcopy(wf_exec_template)
            wf_exec['state'] = state

            with db_api.transaction():
                created = db_api.create_workflow_execution(wf_exec)

                self.assertIsNone(created.updated_at)

                updated = wf_service.update_workflow_execution_env(
                    created, update_env)

            self.assertDictEqual(update_env, updated.params['env'])
            self.assertDictEqual(update_env, updated.context['__env'])

            fetched = db_api.get_workflow_execution(created.id)

            self.assertEqual(updated, fetched)
            self.assertIsNotNone(fetched.updated_at)
    def _continue_workflow(self, wf_ex, task_ex=None, reset=True, env=None):
        wf_ex = wf_service.update_workflow_execution_env(wf_ex, env)

        wf_handler.set_execution_state(
            wf_ex,
            states.RUNNING,
            set_upstream=True
        )

        wf_ctrl = wf_base.get_controller(wf_ex)

        # TODO(rakhmerov): Add YAQL error handling.
        # Calculate commands to process next.
        cmds = wf_ctrl.continue_workflow(task_ex=task_ex, reset=reset, env=env)

        # When resuming a workflow we need to ignore all 'pause'
        # commands because workflow controller takes tasks that
        # completed within the period when the workflow was paused.
        cmds = list(
            filter(
                lambda c: not isinstance(c, commands.PauseWorkflow),
                cmds
            )
        )

        # Since there's no explicit task causing the operation
        # we need to mark all not processed tasks as processed
        # because workflow controller takes only completed tasks
        # with flag 'processed' equal to False.
        for t_ex in wf_ex.task_executions:
            if states.is_completed(t_ex.state) and not t_ex.processed:
                t_ex.processed = True

        wf_spec = spec_parser.get_workflow_spec(wf_ex.spec)

        self._dispatch_workflow_commands(wf_ex, cmds, wf_spec)

        if not cmds:
            if not wf_utils.find_incomplete_task_executions(wf_ex):
                wf_handler.succeed_workflow(
                    wf_ex,
                    wf_ctrl.evaluate_workflow_final_context(),
                    wf_spec
                )

        return wf_ex.get_clone()
    def test_update_workflow_execution_env(self):
        wf_exec_template = {
            'spec': {},
            'start_params': {'task': 'my_task1'},
            'state': 'PAUSED',
            'state_info': None,
            'params': {'env': {'k1': 'abc'}},
            'created_at': None,
            'updated_at': None,
            'context': {'__env': {'k1': 'fee fi fo fum'}},
            'task_id': None,
            'trust_id': None,
            'description': None,
            'output': None
        }

        states_permitted = [
            states.IDLE,
            states.PAUSED,
            states.ERROR
        ]

        update_env = {'k1': 'foobar'}

        for state in states_permitted:
            wf_exec = copy.deepcopy(wf_exec_template)
            wf_exec['state'] = state

            with db_api.transaction():
                created = db_api.create_workflow_execution(wf_exec)

                self.assertIsNone(created.updated_at)

                updated = wf_service.update_workflow_execution_env(
                    created,
                    update_env
                )

            self.assertDictEqual(update_env, updated.params['env'])
            self.assertDictEqual(update_env, updated.context['__env'])

            fetched = db_api.get_workflow_execution(created.id)

            self.assertEqual(updated, fetched)
            self.assertIsNotNone(fetched.updated_at)
Example #23
0
    def _continue_workflow(wf_ex, task_ex=None, reset=True, env=None):
        wf_ex = wf_service.update_workflow_execution_env(wf_ex, env)

        wf_handler.set_workflow_state(
            wf_ex,
            states.RUNNING,
            set_upstream=True
        )

        wf_ctrl = wf_base.get_controller(wf_ex)

        # TODO(rakhmerov): Add error handling.
        # Calculate commands to process next.
        cmds = wf_ctrl.continue_workflow(task_ex=task_ex, reset=reset, env=env)

        # When resuming a workflow we need to ignore all 'pause'
        # commands because workflow controller takes tasks that
        # completed within the period when the workflow was paused.
        # TODO(rakhmerov): This all should be in workflow handler, it's too
        # specific for engine level.
        cmds = list(
            filter(
                lambda c: not isinstance(c, commands.PauseWorkflow),
                cmds
            )
        )

        # Since there's no explicit task causing the operation
        # we need to mark all not processed tasks as processed
        # because workflow controller takes only completed tasks
        # with flag 'processed' equal to False.
        for t_ex in wf_ex.task_executions:
            if states.is_completed(t_ex.state) and not t_ex.processed:
                t_ex.processed = True

        dispatcher.dispatch_workflow_commands(wf_ex, cmds)

        if not cmds:
            wf_handler.check_workflow_completion(wf_ex)

        return wf_ex.get_clone()
Example #24
0
    def put(self, id, wf_ex):
        """Update the specified workflow execution.

        :param id: execution ID.
        :param wf_ex: Execution object.
        """
        acl.enforce('executions:update', context.ctx())
        LOG.info('Update execution [id=%s, execution=%s]' % (id, wf_ex))

        db_api.ensure_workflow_execution_exists(id)

        delta = {}

        if wf_ex.state:
            delta['state'] = wf_ex.state

        if wf_ex.description:
            delta['description'] = wf_ex.description

        if wf_ex.params and wf_ex.params.get('env'):
            delta['env'] = wf_ex.params.get('env')

        # Currently we can change only state, description, or env.
        if len(delta.values()) <= 0:
            raise exc.InputException(
                'The property state, description, or env '
                'is not provided for update.'
            )

        # Description cannot be updated together with state.
        if delta.get('description') and delta.get('state'):
            raise exc.InputException(
                'The property description must be updated '
                'separately from state.'
            )

        # If state change, environment cannot be updated if not RUNNING.
        if (delta.get('env') and
                delta.get('state') and delta['state'] != states.RUNNING):
            raise exc.InputException(
                'The property env can only be updated when workflow '
                'execution is not running or on resume from pause.'
            )

        if delta.get('description'):
            wf_ex = db_api.update_workflow_execution(
                id,
                {'description': delta['description']}
            )

        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,
                    delta.get('env')
                )

        if delta.get('state'):
            if delta.get('state') == states.PAUSED:
                wf_ex = rpc.get_engine_client().pause_workflow(id)
            elif delta.get('state') == states.RUNNING:
                wf_ex = rpc.get_engine_client().resume_workflow(
                    id,
                    env=delta.get('env')
                )
            elif delta.get('state') in [states.SUCCESS, states.ERROR]:
                msg = wf_ex.state_info if wf_ex.state_info else None
                wf_ex = rpc.get_engine_client().stop_workflow(
                    id,
                    delta.get('state'),
                    msg
                )
            else:
                # To prevent changing state in other cases throw a message.
                raise exc.InputException(
                    "Cannot change state to %s. Allowed states are: '%s" % (
                        wf_ex.state,
                        ', '.join([
                            states.RUNNING,
                            states.PAUSED,
                            states.SUCCESS,
                            states.ERROR
                        ])
                    )
                )

        return Execution.from_dict(
            wf_ex if isinstance(wf_ex, dict) else wf_ex.to_dict()
        )
Example #25
0
    def put(self, id, wf_ex):
        """Update the specified workflow execution.

        :param id: execution ID.
        :param wf_ex: Execution object.
        """
        acl.enforce('executions:update', context.ctx())

        LOG.info('Update execution [id=%s, execution=%s]' % (id, wf_ex))

        with db_api.transaction():
            db_api.ensure_workflow_execution_exists(id)

            delta = {}

            if wf_ex.state:
                delta['state'] = wf_ex.state

            if wf_ex.description:
                delta['description'] = wf_ex.description

            if wf_ex.params and wf_ex.params.get('env'):
                delta['env'] = wf_ex.params.get('env')

            # Currently we can change only state, description, or env.
            if len(delta.values()) <= 0:
                raise exc.InputException(
                    'The property state, description, or env '
                    'is not provided for update.')

            # Description cannot be updated together with state.
            if delta.get('description') and delta.get('state'):
                raise exc.InputException(
                    'The property description must be updated '
                    'separately from state.')

            # If state change, environment cannot be updated if not RUNNING.
            if (delta.get('env') and delta.get('state')
                    and delta['state'] != states.RUNNING):
                raise exc.InputException(
                    'The property env can only be updated when workflow '
                    'execution is not running or on resume from pause.')

            if delta.get('description'):
                wf_ex = db_api.update_workflow_execution(
                    id, {'description': delta['description']})

            if not delta.get('state') and delta.get('env'):
                wf_ex = db_api.get_workflow_execution(id)
                wf_ex = wf_service.update_workflow_execution_env(
                    wf_ex, delta.get('env'))

        if delta.get('state'):
            if states.is_paused(delta.get('state')):
                wf_ex = rpc.get_engine_client().pause_workflow(id)
            elif delta.get('state') == states.RUNNING:
                wf_ex = rpc.get_engine_client().resume_workflow(
                    id, env=delta.get('env'))
            elif states.is_completed(delta.get('state')):
                msg = wf_ex.state_info if wf_ex.state_info else None
                wf_ex = rpc.get_engine_client().stop_workflow(
                    id, delta.get('state'), msg)
            else:
                # To prevent changing state in other cases throw a message.
                raise exc.InputException(
                    "Cannot change state to %s. Allowed states are: '%s" %
                    (wf_ex.state, ', '.join([
                        states.RUNNING, states.PAUSED, states.SUCCESS,
                        states.ERROR, states.CANCELLED
                    ])))

        return resources.Execution.from_dict(
            wf_ex if isinstance(wf_ex, dict) else wf_ex.to_dict())