Esempio n. 1
0
    def run(self, action_parameters):

        liveaction_db = action_utils.get_liveaction_by_id(self.liveaction_id)
        exc = ActionExecution.get(liveaction__id=str(liveaction_db.id))

        # Assemble and dispatch trigger
        trigger_ref = ResourceReference.to_string_reference(
            pack=INQUIRY_TRIGGER['pack'], name=INQUIRY_TRIGGER['name'])
        trigger_payload = {"id": str(exc.id), "route": self.route}
        self.trigger_dispatcher.dispatch(trigger_ref, trigger_payload)

        # We only want to request a pause if this has a parent
        if liveaction_db.context.get("parent"):

            # Get the root liveaction and request that it pauses
            root_liveaction = action_service.get_root_liveaction(liveaction_db)
            action_service.request_pause(root_liveaction,
                                         self.context.get('user', None))

        result = {
            "schema": self.schema,
            "roles": self.roles_param,
            "users": self.users_param,
            "route": self.route,
            "ttl": self.ttl
        }
        return (LIVEACTION_STATUS_PENDING, result, None)
Esempio n. 2
0
def respond(inquiry, response, requester=None):
    # Set requester to system user is not provided.
    if not requester:
        requester = cfg.CONF.system_user.user

    # Retrieve the liveaction from the database.
    liveaction_db = lv_db_access.LiveAction.get_by_id(
        inquiry.liveaction.get("id"))

    # Resume the parent workflow first. If the action execution for the inquiry is updated first,
    # it triggers handling of the action execution completion which will interact with the paused
    # parent workflow. The resuming logic that is executed here will then race with the completion
    # of the inquiry action execution, which will randomly result in the parent workflow stuck in
    # paused state.
    if liveaction_db.context.get("parent"):
        LOG.debug('Resuming workflow parent(s) for inquiry "%s".' %
                  str(inquiry.id))

        # For action execution under Action Chain workflows, request the entire
        # workflow to resume. Orquesta handles resume differently and so does not require root
        # to resume. Orquesta allows for specifc branches to resume while other is paused. When
        # there is no other paused branches, the conductor will resume the rest of the workflow.
        resume_target = (
            action_service.get_parent_liveaction(liveaction_db)
            if workflow_service.is_action_execution_under_workflow_context(
                liveaction_db) else
            action_service.get_root_liveaction(liveaction_db))

        if resume_target.status in action_constants.LIVEACTION_PAUSE_STATES:
            action_service.request_resume(resume_target, requester)

    # Succeed the liveaction and update result with the inquiry response.
    LOG.debug('Updating response for inquiry "%s".' % str(inquiry.id))

    result = fast_deepcopy_dict(inquiry.result)
    result["response"] = response

    liveaction_db = action_utils.update_liveaction_status(
        status=action_constants.LIVEACTION_STATUS_SUCCEEDED,
        end_timestamp=date_utils.get_datetime_utc_now(),
        runner_info=sys_info_utils.get_process_info(),
        result=result,
        liveaction_id=str(liveaction_db.id),
    )

    # Sync the liveaction with the corresponding action execution.
    execution_service.update_execution(liveaction_db)

    # Invoke inquiry post run to trigger a callback to parent workflow.
    LOG.debug('Invoking post run for inquiry "%s".' % str(inquiry.id))
    runner_container = container.get_runner_container()
    action_db = action_utils.get_action_by_ref(liveaction_db.action)
    runnertype_db = action_utils.get_runnertype_by_name(
        action_db.runner_type["name"])
    runner = runner_container._get_runner(runnertype_db, action_db,
                                          liveaction_db)
    runner.post_run(status=action_constants.LIVEACTION_STATUS_SUCCEEDED,
                    result=result)

    return liveaction_db
Esempio n. 3
0
    def run(self, action_parameters):

        liveaction_db = action_utils.get_liveaction_by_id(self.liveaction_id)
        exc = ActionExecution.get(liveaction__id=str(liveaction_db.id))

        # Assemble and dispatch trigger
        trigger_ref = ResourceReference.to_string_reference(
            pack=INQUIRY_TRIGGER['pack'],
            name=INQUIRY_TRIGGER['name']
        )
        trigger_payload = {
            "id": str(exc.id),
            "route": self.route
        }
        self.trigger_dispatcher.dispatch(trigger_ref, trigger_payload)

        # We only want to request a pause if this has a parent
        if liveaction_db.context.get("parent"):

            # Get the root liveaction and request that it pauses
            root_liveaction = action_service.get_root_liveaction(liveaction_db)
            action_service.request_pause(
                root_liveaction,
                self.context.get('user', None)
            )

        result = {
            "schema": self.schema,
            "roles": self.roles_param,
            "users": self.users_param,
            "route": self.route,
            "ttl": self.ttl
        }
        return (LIVEACTION_STATUS_PENDING, result, None)
Esempio n. 4
0
    def test_root_liveaction(self):
        """Test that get_root_liveaction correctly retrieves the root liveaction"""

        # Test a variety of depths
        for i in range(1, 7):

            child, expected_root = self._create_nested_executions(depth=i)
            actual_root = action_service.get_root_liveaction(child)
            self.assertEqual(expected_root["id"], actual_root["id"])
Esempio n. 5
0
def purge_inquiries(logger):
    """Purge Inquiries that have exceeded their configured TTL

    At the moment, Inquiries do not have their own database model, so this function effectively
    is another, more specialized GC for executions. It will look for executions with a 'pending'
    status that use the 'inquirer' runner, which is the current definition for an Inquiry.

    Then it will mark those that have a nonzero TTL have existed longer than their TTL as
    "timed out". It will then request that the parent workflow(s) resume, where the failure
    can be handled as the user desires.
    """

    # Get all existing Inquiries
    filters = {'runner__name': 'inquirer', 'status': action_constants.LIVEACTION_STATUS_PENDING}
    inquiries = list(ActionExecution.query(**filters))

    gc_count = 0

    # Inspect each Inquiry, and determine if TTL is expired
    for inquiry in inquiries:

        ttl = int(inquiry.result.get('ttl'))
        if ttl <= 0:
            logger.debug("Inquiry %s has a TTL of %s. Skipping." % (inquiry.id, ttl))
            continue

        min_since_creation = int(
            (get_datetime_utc_now() - inquiry.start_timestamp).total_seconds() / 60
        )

        logger.debug("Inquiry %s has a TTL of %s and was started %s minute(s) ago" % (
                     inquiry.id, ttl, min_since_creation))

        if min_since_creation > ttl:
            gc_count += 1
            logger.info("TTL expired for Inquiry %s. Marking as timed out." % inquiry.id)

            liveaction_db = action_utils.update_liveaction_status(
                status=action_constants.LIVEACTION_STATUS_TIMED_OUT,
                result=inquiry.result,
                liveaction_id=inquiry.liveaction.get('id'))
            executions.update_execution(liveaction_db)

            # Call Inquiry runner's post_run to trigger callback to workflow
            action_db = get_action_by_ref(liveaction_db.action)
            invoke_post_run(liveaction_db=liveaction_db, action_db=action_db)

            if liveaction_db.context.get("parent"):
                # Request that root workflow resumes
                root_liveaction = action_service.get_root_liveaction(liveaction_db)
                action_service.request_resume(
                    root_liveaction,
                    UserDB(cfg.CONF.system_user.user)
                )

    logger.info('Marked %s ttl-expired Inquiries as "timed out".' % (gc_count))
Esempio n. 6
0
def respond(inquiry, response, requester=None):
    # Set requester to system user is not provided.
    if not requester:
        requester = cfg.CONF.system_user.user

    # Retrieve the liveaction from the database.
    liveaction_db = lv_db_access.LiveAction.get_by_id(inquiry.liveaction.get('id'))

    # Resume the parent workflow first. If the action execution for the inquiry is updated first,
    # it triggers handling of the action execution completion which will interact with the paused
    # parent workflow. The resuming logic that is executed here will then race with the completion
    # of the inquiry action execution, which will randomly result in the parent workflow stuck in
    # paused state.
    if liveaction_db.context.get('parent'):
        LOG.debug('Resuming workflow parent(s) for inquiry "%s".' % str(inquiry.id))

        # For action execution under Action Chain and Mistral workflows, request the entire
        # workflow to resume. Orquesta handles resume differently and so does not require root
        # to resume. Orquesta allows for specifc branches to resume while other is paused. When
        # there is no other paused branches, the conductor will resume the rest of the workflow.
        resume_target = (
            action_service.get_parent_liveaction(liveaction_db)
            if workflow_service.is_action_execution_under_workflow_context(liveaction_db)
            else action_service.get_root_liveaction(liveaction_db)
        )

        if resume_target.status in action_constants.LIVEACTION_PAUSE_STATES:
            action_service.request_resume(resume_target, requester)

    # Succeed the liveaction and update result with the inquiry response.
    LOG.debug('Updating response for inquiry "%s".' % str(inquiry.id))

    result = copy.deepcopy(inquiry.result)
    result['response'] = response

    liveaction_db = action_utils.update_liveaction_status(
        status=action_constants.LIVEACTION_STATUS_SUCCEEDED,
        end_timestamp=date_utils.get_datetime_utc_now(),
        runner_info=sys_info_utils.get_process_info(),
        result=result,
        liveaction_id=str(liveaction_db.id)
    )

    # Sync the liveaction with the corresponding action execution.
    execution_service.update_execution(liveaction_db)

    # Invoke inquiry post run to trigger a callback to parent workflow.
    LOG.debug('Invoking post run for inquiry "%s".' % str(inquiry.id))
    runner_container = container.get_runner_container()
    action_db = action_utils.get_action_by_ref(liveaction_db.action)
    runnertype_db = action_utils.get_runnertype_by_name(action_db.runner_type['name'])
    runner = runner_container._get_runner(runnertype_db, action_db, liveaction_db)
    runner.post_run(status=action_constants.LIVEACTION_STATUS_SUCCEEDED, result=result)

    return liveaction_db
Esempio n. 7
0
    def post_run(self, status, result):
        # If the action execution goes into pending state at the onstart of the inquiry,
        # then paused the parent/root workflow in the post run. Previously, the pause request
        # is made in the run method, but because the liveaction hasn't update to pending status
        # yet, there is a race condition where the pause request is mishandled.
        if status == action_constants.LIVEACTION_STATUS_PENDING:
            pause_parent = (
                self.liveaction.context.get("parent") and
                not workflow_service.is_action_execution_under_workflow_context(self.liveaction)
            )

            # For action execution under Action Chain and Mistral workflows, request the entire
            # workflow to pause. Orquesta handles pause differently and so does not require parent
            # to pause. Orquesta allows for other branches to keep running. When there is no other
            # active branches, the conductor will see there is only the pending task and will know
            # to pause the workflow.
            if pause_parent:
                root_liveaction = action_service.get_root_liveaction(self.liveaction)
                action_service.request_pause(root_liveaction, self.context.get('user', None))

        # Invoke post run of parent for common post run related work.
        super(Inquirer, self).post_run(status, result)
Esempio n. 8
0
    def post_run(self, status, result):
        # If the action execution goes into pending state at the onstart of the inquiry,
        # then paused the parent/root workflow in the post run. Previously, the pause request
        # is made in the run method, but because the liveaction hasn't update to pending status
        # yet, there is a race condition where the pause request is mishandled.
        if status == action_constants.LIVEACTION_STATUS_PENDING:
            pause_parent = (
                self.liveaction.context.get("parent") and
                not workflow_service.is_action_execution_under_workflow_context(self.liveaction)
            )

            # For action execution under Action Chain workflows, request the entire
            # workflow to pause. Orquesta handles pause differently and so does not require parent
            # to pause. Orquesta allows for other branches to keep running. When there is no other
            # active branches, the conductor will see there is only the pending task and will know
            # to pause the workflow.
            if pause_parent:
                root_liveaction = action_service.get_root_liveaction(self.liveaction)
                action_service.request_pause(root_liveaction, self.context.get('user', None))

        # Invoke post run of parent for common post run related work.
        super(Inquirer, self).post_run(status, result)
Esempio n. 9
0
    def put(self, inquiry_id, response_data, requester_user):
        """Provide response data to an Inquiry

            In general, provided the response data validates against the provided
            schema, and the user has the appropriate permissions to respond,
            this will set the Inquiry execution to a successful status, and resume
            the parent workflow.

            Handles requests:
                PUT /inquiries/<inquiry id>
        """

        LOG.debug("Inquiry %s received response payload: %s" %
                  (inquiry_id, response_data.response))

        # Retrieve details of the inquiry via ID (i.e. params like schema)
        inquiry = self._get_one_by_id(
            id=inquiry_id,
            requester_user=requester_user,
            permission_type=PermissionType.INQUIRY_RESPOND)

        sanity_result, msg = self._inquiry_sanity_check(inquiry)
        if not sanity_result:
            abort(http_client.BAD_REQUEST, msg)

        if not requester_user:
            requester_user = UserDB(cfg.CONF.system_user.user)

        # Determine permission of this user to respond to this Inquiry
        if not self._can_respond(inquiry, requester_user):
            abort(
                http_client.FORBIDDEN,
                'Requesting user does not have permission to respond to inquiry %s.'
                % inquiry_id)

        # Validate the body of the response against the schema parameter for this inquiry
        schema = inquiry.schema
        LOG.debug("Validating inquiry response: %s against schema: %s" %
                  (response_data.response, schema))
        try:
            util_schema.validate(instance=response_data.response,
                                 schema=schema,
                                 cls=util_schema.CustomValidator,
                                 use_default=True,
                                 allow_default_none=True)
        except Exception as e:
            LOG.debug(
                "Failed to validate response data against provided schema: %s"
                % e.message)
            abort(http_client.BAD_REQUEST,
                  'Response did not pass schema validation.')

        # Update inquiry for completion
        new_result = copy.deepcopy(inquiry.result)
        new_result["response"] = response_data.response
        liveaction_db = self._mark_inquiry_complete(
            inquiry.liveaction.get('id'), new_result)

        # We only want to request a workflow resume if this has a parent
        if liveaction_db.context.get("parent"):

            # Request that root workflow resumes
            root_liveaction = action_service.get_root_liveaction(liveaction_db)
            action_service.request_resume(root_liveaction, requester_user)

        return {"id": inquiry_id, "response": response_data.response}
Esempio n. 10
0
    def put(self, inquiry_id, response_data, requester_user):
        """Provide response data to an Inquiry

            In general, provided the response data validates against the provided
            schema, and the user has the appropriate permissions to respond,
            this will set the Inquiry execution to a successful status, and resume
            the parent workflow.

            Handles requests:
                PUT /inquiries/<inquiry id>
        """

        LOG.debug("Inquiry %s received response payload: %s" % (inquiry_id, response_data.response))

        # Retrieve details of the inquiry via ID (i.e. params like schema)
        inquiry = self._get_one_by_id(
            id=inquiry_id,
            requester_user=requester_user,
            permission_type=PermissionType.INQUIRY_RESPOND
        )

        sanity_result, msg = self._inquiry_sanity_check(inquiry)
        if not sanity_result:
            abort(http_client.BAD_REQUEST, msg)

        if not requester_user:
            requester_user = UserDB(cfg.CONF.system_user.user)

        # Determine permission of this user to respond to this Inquiry
        if not self._can_respond(inquiry, requester_user):
            abort(
                http_client.FORBIDDEN,
                'Requesting user does not have permission to respond to inquiry %s.' % inquiry_id
            )

        # Validate the body of the response against the schema parameter for this inquiry
        schema = inquiry.schema
        LOG.debug("Validating inquiry response: %s against schema: %s" %
                  (response_data.response, schema))
        try:
            util_schema.validate(instance=response_data.response, schema=schema,
                                 cls=util_schema.CustomValidator, use_default=True,
                                 allow_default_none=True)
        except Exception as e:
            LOG.debug("Failed to validate response data against provided schema: %s" % e.message)
            abort(http_client.BAD_REQUEST, 'Response did not pass schema validation.')

        # Update inquiry for completion
        new_result = copy.deepcopy(inquiry.result)
        new_result["response"] = response_data.response
        liveaction_db = self._mark_inquiry_complete(
            inquiry.liveaction.get('id'),
            new_result
        )

        # We only want to request a workflow resume if this has a parent
        if liveaction_db.context.get("parent"):

            # Request that root workflow resumes
            root_liveaction = action_service.get_root_liveaction(liveaction_db)
            action_service.request_resume(
                root_liveaction,
                requester_user
            )

        return {
            "id": inquiry_id,
            "response": response_data.response
        }