Esempio n. 1
0
def _on_action_update(action_ex):
    """Handles action update event.

    :param action_ex: Action execution.
    """

    task_ex = action_ex.task_execution

    if not task_ex:
        return

    task_spec = spec_parser.get_task_spec(task_ex.spec)

    wf_ex = task_ex.workflow_execution

    task = _create_task(
        wf_ex,
        spec_parser.get_workflow_spec_by_execution_id(wf_ex.id),
        task_spec,
        task_ex.in_context,
        task_ex
    )

    try:
        task.on_action_update(action_ex)

        if states.is_paused(action_ex.state):
            wf_handler.pause_workflow(wf_ex)

        if states.is_running(action_ex.state):
            # If any subworkflow of the parent workflow is paused,
            # then keep the parent workflow execution paused.
            for task_ex in wf_ex.task_executions:
                if states.is_paused(task_ex.state):
                    return

            # Otherwise if no other subworkflow is paused,
            # then resume the parent workflow execution.
            wf_handler.resume_workflow(wf_ex)

    except exc.MistralException as e:
        wf_ex = task_ex.workflow_execution

        msg = ("Failed to handle action update [error=%s, wf=%s, task=%s,"
               " action=%s]:\n%s" %
               (e, wf_ex.name, task_ex.name, action_ex.name, tb.format_exc()))

        LOG.error(msg)

        task.set_state(states.ERROR, msg)

        wf_handler.force_fail_workflow(wf_ex, msg)

        return
Esempio n. 2
0
def _on_action_update(action_ex):
    """Handles action update event.

    :param action_ex: Action execution.
    """

    task_ex = action_ex.task_execution

    if not task_ex:
        return

    task_spec = spec_parser.get_task_spec(task_ex.spec)

    wf_ex = task_ex.workflow_execution

    task = _create_task(
        wf_ex,
        spec_parser.get_workflow_spec_by_execution_id(wf_ex.id),
        task_spec,
        task_ex.in_context,
        task_ex
    )

    try:
        task.on_action_update(action_ex)

        if states.is_paused(action_ex.state):
            wf_handler.pause_workflow(wf_ex)

        if states.is_running(action_ex.state):
            # If any subworkflow of the parent workflow is paused,
            # then keep the parent workflow execution paused.
            for task_ex in wf_ex.task_executions:
                if states.is_paused(task_ex.state):
                    return

            # Otherwise if no other subworkflow is paused,
            # then resume the parent workflow execution.
            wf_handler.resume_workflow(wf_ex)

    except exc.MistralException as e:
        wf_ex = task_ex.workflow_execution

        msg = ("Failed to handle action update [error=%s, wf=%s, task=%s,"
               " action=%s]:\n%s" %
               (e, wf_ex.name, task_ex.name, action_ex.name, tb.format_exc()))

        force_fail_task(task_ex, msg, task=task)

        return

    _check_affected_tasks(task)
Esempio n. 3
0
def set_workflow_state(wf_ex, state, msg=None):
    if states.is_completed(state):
        stop_workflow(wf_ex, state, msg)
    elif states.is_paused(state):
        pause_workflow(wf_ex, msg)
    else:
        raise exc.MistralError('Invalid workflow state [wf_ex=%s, state=%s]' %
                               (wf_ex, state))
Esempio n. 4
0
def set_workflow_state(wf_ex, state, msg=None):
    if states.is_completed(state):
        stop_workflow(wf_ex, state, msg)
    elif states.is_paused(state):
        pause_workflow(wf_ex, msg)
    else:
        raise exc.MistralError(
            'Invalid workflow state [wf_ex=%s, state=%s]' % (wf_ex, state)
        )
Esempio n. 5
0
def set_workflow_state(wf_ex, state, msg=None):
    if states.is_completed(state):
        stop_workflow(wf_ex, state, msg)
    elif states.is_paused(state):
        pause_workflow(wf_ex, msg)
    else:
        raise exc.MistralError(
            'Invalid workflow execution state [wf_ex_id=%s, wf_name=%s, '
            'state=%s]' % (wf_ex.id, wf_ex.name, state))
Esempio n. 6
0
def set_workflow_state(wf_ex, state, msg=None):
    if states.is_completed(state):
        stop_workflow(wf_ex, state, msg)
    elif states.is_paused(state):
        pause_workflow(wf_ex, msg)
    else:
        raise exc.MistralError(
            'Invalid workflow execution state [wf_ex_id=%s, wf_name=%s, '
            'state=%s]' % (wf_ex.id, wf_ex.name, state)
        )
Esempio n. 7
0
    def resume_workflow(self, wf_ex_id, env=None):
        # TODO(rakhmerov): Rewrite this functionality with Task abstraction.
        with db_api.transaction():
            wf_ex = wf_handler.lock_workflow_execution(wf_ex_id)

            if (not states.is_paused(wf_ex.state) and
                    not states.is_idle(wf_ex.state)):
                return wf_ex.get_clone()

            return self._continue_workflow(wf_ex, env=env)
Esempio n. 8
0
    def complete(self, state, state_info=None):
        """Complete task and set specified state.

        Method sets specified task state and runs all necessary post
        completion logic such as publishing workflow variables and
        scheduling new workflow commands.

        :param state: New task state.
        :param state_info: New state information (i.e. error message).
        """

        assert self.task_ex

        # Ignore if task already completed.
        if self.is_completed():
            return

        # If we were unable to change the task state it means that it was
        # already changed by a concurrent process. In this case we need to
        # skip all regular completion logic like scheduling new tasks,
        # running engine commands and publishing.
        if not self.set_state(state, state_info):
            return

        data_flow.publish_variables(self.task_ex, self.task_spec)

        if not self.task_spec.get_keep_result():
            # Destroy task result.
            for ex in self.task_ex.action_executions:
                if hasattr(ex, 'output'):
                    ex.output = {}

        self._after_task_complete()

        # Ignore DELAYED state.
        if self.task_ex.state == states.RUNNING_DELAYED:
            return

        # If workflow is paused we shouldn't schedule new commands
        # and mark task as processed.
        if states.is_paused(self.wf_ex.state):
            return

        wf_ctrl = wf_base.get_controller(self.wf_ex, self.wf_spec)

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

        # Mark task as processed after all decisions have been made
        # upon its completion.
        self.task_ex.processed = True

        dispatcher.dispatch_workflow_commands(self.wf_ex, cmds)
Esempio n. 9
0
    def resume_workflow(self, wf_ex_id, env=None):
        try:
            with db_api.transaction():
                wf_ex = wf_handler.lock_workflow_execution(wf_ex_id)

                if (not states.is_paused(wf_ex.state)
                        and not states.is_idle(wf_ex.state)):
                    return wf_ex.get_clone()

                return self._continue_workflow(wf_ex, env=env)
        except Exception as e:
            LOG.error("Failed to resume execution id=%s: %s\n%s", wf_ex_id, e,
                      traceback.format_exc())
            self._fail_workflow(wf_ex_id, e)
            raise e
Esempio n. 10
0
    def complete(self, state, state_info=None):
        """Complete task and set specified state.

        Method sets specified task state and runs all necessary post
        completion logic such as publishing workflow variables and
        scheduling new workflow commands.

        :param state: New task state.
        :param state_info: New state information (i.e. error message).
        """

        assert self.task_ex

        # Ignore if task already completed.
        if states.is_completed(self.task_ex.state):
            return

        self.set_state(state, state_info)

        data_flow.publish_variables(self.task_ex, self.task_spec)

        if not self.task_spec.get_keep_result():
            # Destroy task result.
            for ex in self.task_ex.action_executions:
                if hasattr(ex, 'output'):
                    ex.output = {}

        self._after_task_complete()

        # Ignore DELAYED state.
        if self.task_ex.state == states.RUNNING_DELAYED:
            return

        # If workflow is paused we shouldn't schedule new commands
        # and mark task as processed.
        if states.is_paused(self.wf_ex.state):
            return

        wf_ctrl = wf_base.get_controller(self.wf_ex, self.wf_spec)

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

        # Mark task as processed after all decisions have been made
        # upon its completion.
        self.task_ex.processed = True

        dispatcher.dispatch_workflow_commands(self.wf_ex, cmds)
Esempio n. 11
0
    def resume_workflow(self, wf_ex_id, env=None):
        try:
            with db_api.transaction():
                wf_ex = wf_handler.lock_workflow_execution(wf_ex_id)

                if (not states.is_paused(wf_ex.state) and
                        not states.is_idle(wf_ex.state)):
                    return wf_ex.get_clone()

                return self._continue_workflow(wf_ex, env=env)
        except Exception as e:
            LOG.error(
                "Failed to resume execution id=%s: %s\n%s",
                wf_ex_id, e, traceback.format_exc()
            )
            self._fail_workflow(wf_ex_id, e)
            raise e
Esempio n. 12
0
    def pause(self, msg=None):
        """Pause workflow.

        :param msg: Additional explaining message.
        """

        assert self.wf_ex

        if states.is_paused(self.wf_ex.state):
            return

        # Set the state of this workflow to paused.
        self.set_state(states.PAUSED, state_info=msg)

        # 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)
Esempio n. 13
0
def parse(cur_state, state, category=EventCategory.WORKFLOW):
    """
    states {
        IDLE = 'IDLE'
        WAITING = 'WAITING'
        RUNNING = 'RUNNING'
        RUNNING_DELAYED = 'DELAYED'
        PAUSED = 'PAUSED'
        SUCCESS = 'SUCCESS'
        CANCELLED = 'CANCELLED'
        ERROR = 'ERROR'
    }
    :param wtype: Workflow or Task
    :param cur_state: 当前状态
    :param state: 要更新到的状态
    :return: etype
    """
    if cur_state == states.IDLE and state == states.RUNNING:
        if EventCategory().is_task(category):
            return TASK_START
        else:
            return EXECUTION_START
    elif cur_state == states.WAITING and state == states.RUNNING:
        if EventCategory().is_task(category):
            return TASK_RUNNING
    elif states.is_completed(state):
        if EventCategory().is_task(category):
            return TASK_END
        else:
            return EXECUTION_END
    elif states.is_paused(state):
        if EventCategory().is_task(category):
            return TASK_PAUSE
        else:
            return EXECUTION_PAUSE
    elif cur_state == states.PAUSED and state == states.RUNNING:
        if EventCategory().is_task(category):
            return TASK_RESUME
        else:
            return EXECUTION_RESUME
Esempio n. 14
0
    def pause(self, msg=None):
        """Pause workflow.

        :param msg: Additional explaining message.
        """

        assert self.wf_ex

        if states.is_paused(self.wf_ex.state):
            return

        # Set the state of this workflow to paused.
        self.set_state(states.PAUSED, state_info=msg)

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

        # 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)
Esempio n. 15
0
    def put(self, id, wf_ex):
        """Update the specified workflow execution.

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

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

        @rest_utils.rest_retry_on_db_error
        def _compute_delta(wf_ex):
            with db_api.transaction():
                # ensure that workflow execution exists
                db_api.get_workflow_execution(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

        delta, wf_ex = _compute_delta(wf_ex)

        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())
Esempio n. 16
0
    def complete(self, state, state_info=None):
        """Complete task and set specified state.

        Method sets specified task state and runs all necessary post
        completion logic such as publishing workflow variables and
        scheduling new workflow commands.

        :param state: New task state.
        :param state_info: New state information (i.e. error message).
        """

        assert self.task_ex

        # Record the current task state.
        old_task_state = self.task_ex.state

        # Ignore if task already completed.
        if self.is_completed():
            # Publish task event again so subscribers know
            # task completed state is being processed again.
            self.notify(old_task_state, self.task_ex.state)

            return

        # If we were unable to change the task state it means that it was
        # already changed by a concurrent process. In this case we need to
        # skip all regular completion logic like scheduling new tasks,
        # running engine commands and publishing.
        if not self.set_state(state, state_info):
            return

        data_flow.publish_variables(self.task_ex, self.task_spec)

        if not self.task_spec.get_keep_result():
            # Destroy task result.
            for ex in self.task_ex.action_executions:
                if hasattr(ex, 'output'):
                    ex.output = {}

        self._after_task_complete()

        # Ignore DELAYED state.
        if self.task_ex.state == states.RUNNING_DELAYED:
            return

        # If workflow is paused we shouldn't schedule new commands
        # and mark task as processed.
        if states.is_paused(self.wf_ex.state):
            # Publish task event even if the workflow is paused.
            self.notify(old_task_state, self.task_ex.state)

            return

        wf_ctrl = wf_base.get_controller(self.wf_ex, self.wf_spec)

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

        # Check whether the task generated any next tasks.
        if any([not commands.is_engine_command(c) for c in cmds]):
            self.task_ex.has_next_tasks = True

        # Check whether the error is handled.
        if self.task_ex.state == states.ERROR:
            self.task_ex.error_handled = any([c.handles_error for c in cmds])

        # Mark task as processed after all decisions have been made
        # upon its completion.
        self.task_ex.processed = True

        self.register_workflow_completion_check()

        self.save_finished_time()

        # Publish task event.
        self.notify(old_task_state, self.task_ex.state)

        dispatcher.dispatch_workflow_commands(self.wf_ex, cmds)
Esempio n. 17
0
    def complete(self, state, state_info=None):
        """Complete task and set specified state.

        Method sets specified task state and runs all necessary post
        completion logic such as publishing workflow variables and
        scheduling new workflow commands.

        :param state: New task state.
        :param state_info: New state information (i.e. error message).
        """

        assert self.task_ex

        # Ignore if task already completed.
        if self.is_completed():
            return

        # If we were unable to change the task state it means that it was
        # already changed by a concurrent process. In this case we need to
        # skip all regular completion logic like scheduling new tasks,
        # running engine commands and publishing.
        if not self.set_state(state, state_info):
            return

        data_flow.publish_variables(self.task_ex, self.task_spec)

        if not self.task_spec.get_keep_result():
            # Destroy task result.
            for ex in self.task_ex.action_executions:
                if hasattr(ex, 'output'):
                    ex.output = {}

        self._after_task_complete()

        # Ignore DELAYED state.
        if self.task_ex.state == states.RUNNING_DELAYED:
            return

        wf_ctrl = wf_base.get_controller(self.wf_ex, self.wf_spec)

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

        # Save next task names in DB to avoid evaluating them again
        # in the future.
        self.task_ex.next_tasks = []

        for c in cmds:
            if commands.is_engine_command(c):
                continue

            event = c.triggered_by[0]['event'] if c.triggered_by else None

            self.task_ex.next_tasks.append((c.task_spec.get_name(), event))

        self.task_ex.has_next_tasks = bool(self.task_ex.next_tasks)

        # Check whether the error is handled.
        if self.task_ex.state == states.ERROR:
            self.task_ex.error_handled = any([c.handles_error for c in cmds])

        # If workflow is paused we shouldn't schedule new commands
        # and mark task as processed.
        if states.is_paused(self.wf_ex.state):
            return

        # Mark task as processed after all decisions have been made
        # upon its completion.
        self.task_ex.processed = True

        self.register_workflow_completion_check()

        dispatcher.dispatch_workflow_commands(self.wf_ex, cmds)
Esempio n. 18
0
    def complete(self, state, state_info=None):
        """Complete task and set specified state.

        Method sets specified task state and runs all necessary post
        completion logic such as publishing workflow variables and
        scheduling new workflow commands.

        :param state: New task state.
        :param state_info: New state information (i.e. error message).
        """

        assert self.task_ex

        # Record the current task state.
        old_task_state = self.task_ex.state

        # Ignore if task already completed.
        if self.is_completed():
            # Publish task event again so subscribers know
            # task completed state is being processed again.
            self.notify(old_task_state, self.task_ex.state)

            return

        # If we were unable to change the task state it means that it was
        # already changed by a concurrent process. In this case we need to
        # skip all regular completion logic like scheduling new tasks,
        # running engine commands and publishing.
        if not self.set_state(state, state_info):
            return

        data_flow.publish_variables(self.task_ex, self.task_spec)

        if not self.task_spec.get_keep_result():
            # Destroy task result.
            for ex in self.task_ex.action_executions:
                if hasattr(ex, 'output'):
                    ex.output = {}

        self._after_task_complete()

        # Ignore DELAYED state.
        if self.task_ex.state == states.RUNNING_DELAYED:
            return

        # If workflow is paused we shouldn't schedule new commands
        # and mark task as processed.
        if states.is_paused(self.wf_ex.state):
            # Publish task event even if the workflow is paused.
            self.notify(old_task_state, self.task_ex.state)

            return

        wf_ctrl = wf_base.get_controller(self.wf_ex, self.wf_spec)

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

        # Check whether the task generated any next tasks.
        if any([not commands.is_engine_command(c) for c in cmds]):
            self.task_ex.has_next_tasks = True

        # Check whether the error is handled.
        if self.task_ex.state == states.ERROR:
            self.task_ex.error_handled = any([c.handles_error for c in cmds])

        # Mark task as processed after all decisions have been made
        # upon its completion.
        self.task_ex.processed = True

        self.register_workflow_completion_check()

        self.save_finished_time()

        # Publish task event.
        self.notify(old_task_state, self.task_ex.state)

        dispatcher.dispatch_workflow_commands(self.wf_ex, cmds)
Esempio n. 19
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()
        )