def _mark_inquiry_complete(self, inquiry_id, result): """Mark Inquiry as completed This function updates the local LiveAction and Execution with a successful status as well as call the "post_run" function for the Inquirer runner so that the appropriate callback function is executed :param inquiry: The Inquiry for which the response is given :param requester_user: The user providing the response :rtype: bool - True if requester_user is able to respond. False if not. """ # Update inquiry's execution result with a successful status and the validated response liveaction_db = action_utils.update_liveaction_status( status=action_constants.LIVEACTION_STATUS_SUCCEEDED, runner_info=system_info.get_process_info(), result=result, liveaction_id=inquiry_id) executions.update_execution(liveaction_db) # Call Inquiry runner's post_run to trigger callback to workflow runner_container = get_runner_container() action_db = get_action_by_ref(liveaction_db.action) runnertype_db = 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
def _mark_inquiry_complete(self, inquiry_id, result): """Mark Inquiry as completed This function updates the local LiveAction and Execution with a successful status as well as call the "post_run" function for the Inquirer runner so that the appropriate callback function is executed :param inquiry: The Inquiry for which the response is given :param requester_user: The user providing the response :rtype: bool - True if requester_user is able to respond. False if not. """ # Update inquiry's execution result with a successful status and the validated response liveaction_db = action_utils.update_liveaction_status( status=action_constants.LIVEACTION_STATUS_SUCCEEDED, runner_info=system_info.get_process_info(), result=result, liveaction_id=inquiry_id) executions.update_execution(liveaction_db) # Call Inquiry runner's post_run to trigger callback to workflow runner_container = get_runner_container() action_db = get_action_by_ref(liveaction_db.action) runnertype_db = 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
def dispatch(self, actionexec_db): action_ref = ResourceReference.from_string_reference( ref=actionexec_db.action) (action_db, _) = get_action_by_dict({ 'name': action_ref.name, 'pack': action_ref.pack }) runnertype_db = get_runnertype_by_name(action_db.runner_type['name']) runner_type = runnertype_db.name LOG.info('Dispatching runner for Action "%s"', actionexec_db) LOG.debug(' liverunner_type: %s', runner_type) LOG.debug(' RunnerType: %s', runnertype_db) LOG.debug(' ActionExecution: %s', actionexec_db) # Get runner instance. runner = self._get_runner(runnertype_db) LOG.debug('Runner instance for RunnerType "%s" is: %s', runnertype_db.name, runner) # Invoke pre_run, run, post_run cycle. result, actionexec_db = self._do_run(runner, runnertype_db, action_db, actionexec_db) LOG.debug('runner do_run result: %s', result) actionsensor.post_trigger(actionexec_db) LOG.audit( 'ActionExecution complete. actionexec_id="%s" resulted in ' 'actionexecution_db="%s"', actionexec_db.id, actionexec_db) return result
def _do_enforce(self): # TODO: Refactor this to avoid additional lookup in cast_params action_ref = self.rule.action['ref'] # Verify action referenced in the rule exists in the database action_db = action_utils.get_action_by_ref(action_ref) if not action_db: raise ValueError('Action "%s" doesn\'t exist' % (action_ref)) runnertype_db = action_utils.get_runnertype_by_name(action_db.runner_type['name']) params = self.rule.action.parameters LOG.info('Invoking action %s for trigger_instance %s with params %s.', self.rule.action.ref, self.trigger_instance.id, json.dumps(params)) # update trace before invoking the action. trace_context = self._update_trace() LOG.debug('Updated trace %s with rule %s.', trace_context, self.rule.id) context, additional_contexts = self.get_action_execution_context( action_db=action_db, trace_context=trace_context) return self._invoke_action(action_db=action_db, runnertype_db=runnertype_db, params=params, context=context, additional_contexts=additional_contexts)
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
def dispatch(self, actionexec_db): action_ref = ResourceReference.from_string_reference(ref=actionexec_db.action) (action_db, _) = get_action_by_dict( {'name': action_ref.name, 'pack': action_ref.pack}) runnertype_db = get_runnertype_by_name(action_db.runner_type['name']) runner_type = runnertype_db.name LOG.info('Dispatching runner for Action "%s"', actionexec_db) LOG.debug(' liverunner_type: %s', runner_type) LOG.debug(' RunnerType: %s', runnertype_db) LOG.debug(' ActionExecution: %s', actionexec_db) # Get runner instance. runner = self._get_runner(runnertype_db) LOG.debug('Runner instance for RunnerType "%s" is: %s', runnertype_db.name, runner) # Invoke pre_run, run, post_run cycle. result, actionexec_db = self._do_run(runner, runnertype_db, action_db, actionexec_db) LOG.debug('runner do_run result: %s', result) actionsensor.post_trigger(actionexec_db) LOG.audit('ActionExecution complete.', extra={'actionexecution': actionexec_db.to_serializable_dict()}) return result
def invoke_post_run(liveaction_db, action_db=None): LOG.info('Invoking post run for action execution %s.', liveaction_db.id) # Identify action and runner. if not action_db: action_db = action_db_utils.get_action_by_ref(liveaction_db.action) if not action_db: LOG.exception('Unable to invoke post run. Action %s no longer exists.', liveaction_db.action) return LOG.info('Action execution %s runs %s of runner type %s.', liveaction_db.id, action_db.name, action_db.runner_type['name']) # Get an instance of the action runner. runnertype_db = action_db_utils.get_runnertype_by_name( action_db.runner_type['name']) runner = runners.get_runner(runnertype_db.runner_module) # Configure the action runner. runner.container_service = RunnerContainerService() runner.action = action_db runner.action_name = action_db.name runner.action_execution_id = str(liveaction_db.id) runner.entry_point = RunnerContainerService.get_entry_point_abs_path( pack=action_db.pack, entry_point=action_db.entry_point) runner.context = getattr(liveaction_db, 'context', dict()) runner.callback = getattr(liveaction_db, 'callback', dict()) runner.libs_dir_path = RunnerContainerService.get_action_libs_abs_path( pack=action_db.pack, entry_point=action_db.entry_point) # Invoke the post_run method. runner.post_run(liveaction_db.status, liveaction_db.result)
def _invoke_post_run(self, actionexec_db, action_db): LOG.info( "Invoking post run for action execution %s. Action=%s; Runner=%s", actionexec_db.id, action_db.name, action_db.runner_type["name"], ) # Get an instance of the action runner. runnertype_db = get_runnertype_by_name(action_db.runner_type["name"]) runner = get_runner(runnertype_db.runner_module) # Configure the action runner. runner.container_service = RunnerContainerService() runner.action = action_db runner.action_name = action_db.name runner.action_execution_id = str(actionexec_db.id) runner.entry_point = RunnerContainerService.get_entry_point_abs_path( pack=action_db.pack, entry_point=action_db.entry_point ) runner.context = getattr(actionexec_db, "context", dict()) runner.callback = getattr(actionexec_db, "callback", dict()) runner.libs_dir_path = RunnerContainerService.get_action_libs_abs_path( pack=action_db.pack, entry_point=action_db.entry_point ) # Invoke the post_run method. runner.post_run(actionexec_db.status, actionexec_db.result)
def register_runner_types(): LOG.debug('Start : register default RunnerTypes.') for runnertype in RUNNER_TYPES: try: runnertype_db = get_runnertype_by_name(runnertype['name']) update = True except StackStormDBObjectNotFoundError: runnertype_db = None update = False runnertype_api = RunnerTypeAPI(**runnertype) runnertype_api.validate() runner_type_model = RunnerTypeAPI.to_model(runnertype_api) if runnertype_db: runner_type_model.id = runnertype_db.id try: runnertype_db = RunnerType.add_or_update(runner_type_model) extra = {'runnertype_db': runnertype_db} if update: LOG.audit('RunnerType updated. RunnerType %s', runnertype_db, extra=extra) else: LOG.audit('RunnerType created. RunnerType %s', runnertype_db, extra=extra) except Exception: LOG.exception('Unable to register runner type %s.', runnertype['name']) LOG.debug('End : register default RunnerTypes.')
def dispatch(self, liveaction_db): action_db = get_action_by_ref(liveaction_db.action) if not action_db: raise Exception("Action %s not found in DB." % (liveaction_db.action)) runnertype_db = get_runnertype_by_name(action_db.runner_type["name"]) extra = {"liveaction_db": liveaction_db, "runnertype_db": runnertype_db} LOG.info("Dispatching Action to a runner", extra=extra) # Get runner instance. runner = self._get_runner(runnertype_db, action_db, liveaction_db) LOG.debug('Runner instance for RunnerType "%s" is: %s', runnertype_db.name, runner) # Process the request. if liveaction_db.status == action_constants.LIVEACTION_STATUS_CANCELING: liveaction_db = self._do_cancel( runner=runner, runnertype_db=runnertype_db, action_db=action_db, liveaction_db=liveaction_db ) else: liveaction_db = self._do_run( runner=runner, runnertype_db=runnertype_db, action_db=action_db, liveaction_db=liveaction_db ) return liveaction_db.result
def test_get_runnertype_existing(self): # Lookup by id and verify name equals. runner = action_db_utils.get_runnertype_by_id(ActionDBUtilsTestCase.runnertype_db.id) self.assertEqual(runner.name, ActionDBUtilsTestCase.runnertype_db.name) # Lookup by name and verify id equals. runner = action_db_utils.get_runnertype_by_name(ActionDBUtilsTestCase.runnertype_db.name) self.assertEqual(runner.id, ActionDBUtilsTestCase.runnertype_db.id)
def cast_params(action_ref, params): """ """ action_db = action_db_util.get_action_by_ref(action_ref) action_parameters_schema = action_db.parameters runnertype_db = action_db_util.get_runnertype_by_name(action_db.runner_type['name']) runner_parameters_schema = runnertype_db.runner_parameters # combine into 1 list of parameter schemas parameters_schema = {} if runner_parameters_schema: parameters_schema.update(runner_parameters_schema) if action_parameters_schema: parameters_schema.update(action_parameters_schema) # cast each param individually for k, v in six.iteritems(params): parameter_schema = parameters_schema.get(k, None) if not parameter_schema: LOG.debug('Will skip cast of param[name: %s, value: %s]. No schema.', k, v) continue parameter_type = parameter_schema.get('type', None) if not parameter_type: LOG.debug('Will skip cast of param[name: %s, value: %s]. No type.', k, v) continue cast = get_cast(cast_type=parameter_type) if not cast: LOG.debug('Will skip cast of param[name: %s, value: %s]. No cast for %s.', k, v, parameter_type) continue LOG.debug('Casting param: %s of type %s to type: %s', v, type(v), parameter_type) params[k] = cast(v) return params
def dispatch(self, liveaction_db): action_db = get_action_by_ref(liveaction_db.action) if not action_db: raise Exception('Action %s not found in DB.' % (liveaction_db.action)) runnertype_db = get_runnertype_by_name(action_db.runner_type['name']) extra = { 'liveaction_db': liveaction_db, 'runnertype_db': runnertype_db } LOG.info('Dispatching Action to a runner', extra=extra) # Get runner instance. runner = get_runner(runnertype_db.runner_module) LOG.debug('Runner instance for RunnerType "%s" is: %s', runnertype_db.name, runner) # Invoke pre_run, run, post_run cycle. liveaction_db = self._do_run(runner, runnertype_db, action_db, liveaction_db) extra = {'result': liveaction_db.result} LOG.debug('Runner do_run result', extra=extra) extra = {'liveaction_db': liveaction_db} LOG.audit('Liveaction completed', extra=extra) return liveaction_db.result
def get_schema_for_action_parameters(action_db): """ Dynamically construct JSON schema for the provided action from the parameters metadata. Note: This schema is used to validate parameters which are passed to the action. """ from st2common.util.action_db import get_runnertype_by_name runner_type = get_runnertype_by_name(action_db.runner_type['name']) parameters_schema = copy.deepcopy(runner_type.runner_parameters) for name, schema in six.iteritems(action_db.parameters): if name not in parameters_schema.keys(): parameters_schema.update({name: schema}) else: for attribute, value in six.iteritems(schema): validate_runner_parameter_attribute_override( action_db.ref, name, attribute, value, parameters_schema[name].get(attribute)) parameters_schema[name][attribute] = value schema = get_schema_for_resource_parameters( parameters_schema=parameters_schema) if parameters_schema: schema['title'] = action_db.name if action_db.description: schema['description'] = action_db.description return schema
def register_runner(runner_type, experimental): # For backward compatibility reasons, we also register runners under the old names runner_names = [runner_type['name']] + runner_type.get('aliases', []) for runner_name in runner_names: runner_type['name'] = runner_name runner_experimental = runner_type.get('experimental', False) if runner_experimental and not experimental: LOG.debug('Skipping experimental runner "%s"' % (runner_name)) continue # Remove additional, non db-model attributes non_db_attributes = ['experimental', 'aliases'] for attribute in non_db_attributes: if attribute in runner_type: del runner_type[attribute] try: runner_type_db = get_runnertype_by_name(runner_name) update = True except StackStormDBObjectNotFoundError: runner_type_db = None update = False # Note: We don't want to overwrite "enabled" attribute which is already in the database # (aka we don't want to re-enable runner which has been disabled by the user) if runner_type_db and runner_type_db['enabled'] != runner_type[ 'enabled']: runner_type['enabled'] = runner_type_db['enabled'] # If package is not provided, assume it's the same as module name for backward # compatibility reasons if not runner_type.get('runner_package', None): runner_type['runner_package'] = runner_type['runner_module'] runner_type_api = RunnerTypeAPI(**runner_type) runner_type_api.validate() runner_type_model = RunnerTypeAPI.to_model(runner_type_api) if runner_type_db: runner_type_model.id = runner_type_db.id try: runner_type_db = RunnerType.add_or_update(runner_type_model) extra = {'runner_type_db': runner_type_db} if update: LOG.audit('RunnerType updated. RunnerType %s', runner_type_db, extra=extra) else: LOG.audit('RunnerType created. RunnerType %s', runner_type_db, extra=extra) except Exception: LOG.exception('Unable to register runner type %s.', runner_type['name']) return 0 return 1
def dispatch(self, liveaction_db): action_db = get_action_by_ref(liveaction_db.action) if not action_db: raise Exception('Action %s not found in dB.' % liveaction_db.action) runnertype_db = get_runnertype_by_name(action_db.runner_type['name']) runner_type = runnertype_db.name LOG.info('Dispatching Action to runner \n%s', json.dumps(liveaction_db.to_serializable_dict(), indent=4)) LOG.debug(' liverunner_type: %s', runner_type) LOG.debug(' RunnerType: %s', runnertype_db) # Get runner instance. runner = get_runner(runnertype_db.runner_module) LOG.debug('Runner instance for RunnerType "%s" is: %s', runnertype_db.name, runner) # Invoke pre_run, run, post_run cycle. liveaction_db = self._do_run(runner, runnertype_db, action_db, liveaction_db) LOG.debug('runner do_run result: %s', liveaction_db.result) liveaction_serializable = liveaction_db.to_serializable_dict() extra = {'liveaction_db': liveaction_db} LOG.audit('liveaction complete.', extra=extra) LOG.info('result :\n%s.', json.dumps(liveaction_serializable.get('result', None), indent=4)) return liveaction_db.result
def _do_enforce(self): # TODO: Refactor this to avoid additional lookup in cast_params action_ref = self.rule.action['ref'] # Verify action referenced in the rule exists in the database action_db = action_utils.get_action_by_ref(action_ref) if not action_db: raise ValueError('Action "%s" doesn\'t exist' % (action_ref)) runnertype_db = action_utils.get_runnertype_by_name( action_db.runner_type['name']) params = self.rule.action.parameters LOG.info('Invoking action %s for trigger_instance %s with params %s.', self.rule.action.ref, self.trigger_instance.id, json.dumps(params)) # update trace before invoking the action. trace_context = self._update_trace() LOG.debug('Updated trace %s with rule %s.', trace_context, self.rule.id) context, additional_contexts = self.get_action_execution_context( action_db=action_db, trace_context=trace_context) return self._invoke_action(action_db=action_db, runnertype_db=runnertype_db, params=params, context=context, additional_contexts=additional_contexts)
def dispatch(self, liveaction_db): action_db = get_action_by_ref(liveaction_db.action) if not action_db: raise Exception('Action %s not found in DB.' % (liveaction_db.action)) runnertype_db = get_runnertype_by_name(action_db.runner_type['name']) extra = { 'liveaction_db': liveaction_db, 'runnertype_db': runnertype_db } LOG.info('Dispatching Action to a runner', extra=extra) # Get runner instance. runner = self._get_runner(runnertype_db, action_db, liveaction_db) LOG.debug('Runner instance for RunnerType "%s" is: %s', runnertype_db.name, runner) # Process the request. liveaction_db = (self._do_cancel(runner, runnertype_db, action_db, liveaction_db) if liveaction_db.status == action_constants.LIVEACTION_STATUS_CANCELING else self._do_run(runner, runnertype_db, action_db, liveaction_db)) return liveaction_db.result
def get_schema_for_action_parameters(action_db): """ Dynamically construct JSON schema for the provided action from the parameters metadata. Note: This schema is used to validate parameters which are passed to the action. """ from st2common.util.action_db import get_runnertype_by_name runner_type = get_runnertype_by_name(action_db.runner_type['name']) parameters_schema = copy.deepcopy(runner_type.runner_parameters) for name, schema in six.iteritems(action_db.parameters): if name not in parameters_schema.keys(): parameters_schema.update({name: schema}) else: for attribute, value in six.iteritems(schema): validate_runner_parameter_attribute_override( action_db.ref, name, attribute, value, parameters_schema[name].get(attribute)) parameters_schema[name][attribute] = value schema = get_schema_for_resource_parameters(parameters_schema=parameters_schema) if parameters_schema: schema['title'] = action_db.name if action_db.description: schema['description'] = action_db.description return schema
def dispatch(self, liveaction_db): action_db = get_action_by_ref(liveaction_db.action) if not action_db: raise Exception('Action %s not found in dB.' % liveaction_db.action) runnertype_db = get_runnertype_by_name(action_db.runner_type['name']) runner_type = runnertype_db.name LOG.info('Dispatching Action to runner \n%s', json.dumps(liveaction_db.to_serializable_dict(), indent=4)) LOG.debug(' liverunner_type: %s', runner_type) LOG.debug(' RunnerType: %s', runnertype_db) # Get runner instance. runner = get_runner(runnertype_db.runner_module) LOG.debug('Runner instance for RunnerType "%s" is: %s', runnertype_db.name, runner) # Invoke pre_run, run, post_run cycle. liveaction_db = self._do_run(runner, runnertype_db, action_db, liveaction_db) LOG.debug('runner do_run result: %s', liveaction_db.result) liveaction_serializable = liveaction_db.to_serializable_dict() extra = {'liveaction_db': liveaction_db} LOG.audit('liveaction complete.', extra=extra) LOG.info( 'result :\n%s.', json.dumps(liveaction_serializable.get('result', None), indent=4)) return liveaction_db.result
def _schedule_execution(self, liveaction): # Initialize execution context if it does not exist. if not hasattr(liveaction, 'context'): liveaction.context = dict() liveaction.context['user'] = get_requester() LOG.debug('User is: %s' % liveaction.context['user']) # Retrieve other st2 context from request header. if 'st2-context' in pecan.request.headers and pecan.request.headers['st2-context']: context = jsonify.try_loads(pecan.request.headers['st2-context']) if not isinstance(context, dict): raise ValueError('Unable to convert st2-context from the headers into JSON.') liveaction.context.update(context) # Schedule the action execution. liveaction_db = LiveActionAPI.to_model(liveaction) liveaction_db, actionexecution_db = action_service.create_request(liveaction_db) action_db = action_utils.get_action_by_ref(liveaction_db.action) runnertype_db = action_utils.get_runnertype_by_name(action_db.runner_type['name']) try: liveaction_db.parameters = param_utils.render_live_params( runnertype_db.runner_parameters, action_db.parameters, liveaction_db.parameters, liveaction_db.context) except ParamException as e: raise ValueValidationException(str(e)) liveaction_db = LiveAction.add_or_update(liveaction_db, publish=False) _, actionexecution_db = action_service.publish_request(liveaction_db, actionexecution_db) from_model_kwargs = self._get_from_model_kwargs_for_request(request=pecan.request) return ActionExecutionAPI.from_model(actionexecution_db, from_model_kwargs)
def register_runner_types(): LOG.debug('Start : register default RunnerTypes.') for runnertype in RUNNER_TYPES: try: runnertype_db = get_runnertype_by_name(runnertype['name']) update = True except StackStormDBObjectNotFoundError: runnertype_db = None update = False runnertype_api = RunnerTypeAPI(**runnertype) runnertype_api.validate() runner_type_model = RunnerTypeAPI.to_model(runnertype_api) if runnertype_db: runner_type_model.id = runnertype_db.id try: runnertype_db = RunnerType.add_or_update(runner_type_model) extra = {'runnertype_db': runnertype_db} if update: LOG.audit('RunnerType updated. RunnerType %s', runnertype_db, extra=extra) else: LOG.audit('RunnerType created. RunnerType %s', runnertype_db, extra=extra) except Exception: LOG.exception('Unable to register runner type %s.', runnertype['name']) LOG.debug('End : register default RunnerTypes.')
def request_action_execution(wf_ex_db, task_ex_db, st2_ctx, ac_ex_req): wf_ac_ex_id = wf_ex_db.action_execution action_ref = ac_ex_req['action'] action_input = ac_ex_req['input'] item_id = ac_ex_req.get('item_id') # If the task is with items and item_id is not provided, raise exception. if task_ex_db.itemized and item_id is None: msg = 'Unable to request action execution. Identifier for the item is not provided.' raise Exception(msg) # Identify the action to execute. action_db = action_utils.get_action_by_ref(ref=action_ref) if not action_db: error = 'Unable to find action "%s".' % action_ref raise ac_exc.InvalidActionReferencedException(error) # Identify the runner for the action. runner_type_db = action_utils.get_runnertype_by_name( action_db.runner_type['name']) # Set context for the action execution. ac_ex_ctx = { 'parent': st2_ctx, 'orquesta': { 'workflow_execution_id': str(wf_ex_db.id), 'task_execution_id': str(task_ex_db.id), 'task_name': task_ex_db.task_name, 'task_id': task_ex_db.task_id } } if item_id is not None: ac_ex_ctx['orquesta']['item_id'] = item_id # Render action execution parameters and setup action execution object. ac_ex_params = param_utils.render_live_params( runner_type_db.runner_parameters or {}, action_db.parameters or {}, action_input or {}, ac_ex_ctx) lv_ac_db = lv_db_models.LiveActionDB(action=action_ref, workflow_execution=str(wf_ex_db.id), task_execution=str(task_ex_db.id), context=ac_ex_ctx, parameters=ac_ex_params) # Set the task execution to running first otherwise a race can occur # where the action execution finishes first and the completion handler # conflicts with this status update. task_ex_db.status = states.RUNNING task_ex_db = wf_db_access.TaskExecution.update(task_ex_db, publish=False) # Request action execution. lv_ac_db, ac_ex_db = ac_svc.request(lv_ac_db) msg = '[%s] Action execution "%s" requested for task "%s".' LOG.info(msg, wf_ac_ex_id, str(ac_ex_db.id), task_ex_db.task_id) return ac_ex_db
def cast_params(action_ref, params, cast_overrides=None): """""" params = params or {} action_db = action_db_util.get_action_by_ref(action_ref) if not action_db: raise ValueError('Action with ref "%s" doesn\'t exist' % (action_ref)) action_parameters_schema = action_db.parameters runnertype_db = action_db_util.get_runnertype_by_name( action_db.runner_type["name"]) runner_parameters_schema = runnertype_db.runner_parameters # combine into 1 list of parameter schemas parameters_schema = {} if runner_parameters_schema: parameters_schema.update(runner_parameters_schema) if action_parameters_schema: parameters_schema.update(action_parameters_schema) # cast each param individually for k, v in six.iteritems(params): parameter_schema = parameters_schema.get(k, None) if not parameter_schema: LOG.debug( "Will skip cast of param[name: %s, value: %s]. No schema.", k, v) continue parameter_type = parameter_schema.get("type", None) if not parameter_type: LOG.debug("Will skip cast of param[name: %s, value: %s]. No type.", k, v) continue # Pick up cast from teh override and then from the system suppied ones. cast = cast_overrides.get(parameter_type, None) if cast_overrides else None if not cast: cast = get_cast(cast_type=parameter_type) if not cast: LOG.debug( "Will skip cast of param[name: %s, value: %s]. No cast for %s.", k, v, parameter_type, ) continue LOG.debug("Casting param: %s of type %s to type: %s", v, type(v), parameter_type) try: params[k] = cast(v) except Exception as e: v_type = type(v).__name__ msg = ( 'Failed to cast value "%s" (type: %s) for parameter "%s" of type "%s": %s. ' "Perhaps the value is of an invalid type?" % (v, v_type, k, parameter_type, six.text_type(e))) raise ValueError(msg) return params
def _schedule_execution(self, liveaction, user=None, context_string=None, show_secrets=False): # Initialize execution context if it does not exist. if not hasattr(liveaction, 'context'): liveaction.context = dict() liveaction.context['user'] = user LOG.debug('User is: %s' % liveaction.context['user']) # Retrieve other st2 context from request header. if context_string: context = try_loads(context_string) if not isinstance(context, dict): raise ValueError( 'Unable to convert st2-context from the headers into JSON.' ) liveaction.context.update(context) # Schedule the action execution. liveaction_db = LiveActionAPI.to_model(liveaction) liveaction_db, actionexecution_db = action_service.create_request( liveaction_db) action_db = action_utils.get_action_by_ref(liveaction_db.action) runnertype_db = action_utils.get_runnertype_by_name( action_db.runner_type['name']) try: liveaction_db.parameters = param_utils.render_live_params( runnertype_db.runner_parameters, action_db.parameters, liveaction_db.parameters, liveaction_db.context) except ParamException: # By this point the execution is already in the DB therefore need to mark it failed. _, e, tb = sys.exc_info() action_service.update_status(liveaction=liveaction_db, new_status=LIVEACTION_STATUS_FAILED, result={ 'error': str(e), 'traceback': ''.join( traceback.format_tb(tb, 20)) }) # Might be a good idea to return the actual ActionExecution rather than bubble up # the execption. raise ValueValidationException(str(e)) liveaction_db = LiveAction.add_or_update(liveaction_db, publish=False) _, actionexecution_db = action_service.publish_request( liveaction_db, actionexecution_db) execution_api = ActionExecutionAPI.from_model( actionexecution_db, mask_secrets=(not show_secrets)) return Response(json=execution_api, status=http_client.CREATED)
def register_runner_types(experimental=False): """ :param experimental: True to also register experimental runners. :type experimental: ``bool`` """ LOG.debug('Start : register default RunnerTypes.') for runner_type in RUNNER_TYPES: runner_type = copy.deepcopy(runner_type) # For backward compatibility reasons, we also register runners under the old names runner_names = [runner_type['name']] + runner_type.get('aliases', []) for runner_name in runner_names: runner_type['name'] = runner_name runner_experimental = runner_type.get('experimental', False) if runner_experimental and not experimental: LOG.debug('Skipping experimental runner "%s"' % (runner_name)) continue # Remove additional, non db-model attributes non_db_attributes = ['experimental', 'aliases'] for attribute in non_db_attributes: if attribute in runner_type: del runner_type[attribute] try: runner_type_db = get_runnertype_by_name(runner_name) update = True except StackStormDBObjectNotFoundError: runner_type_db = None update = False # Note: We don't want to overwrite "enabled" attribute which is already in the database # (aka we don't want to re-enable runner which has been disabled by the user) if runner_type_db and runner_type_db['enabled'] != runner_type['enabled']: runner_type['enabled'] = runner_type_db['enabled'] runner_type_api = RunnerTypeAPI(**runner_type) runner_type_api.validate() runner_type_model = RunnerTypeAPI.to_model(runner_type_api) if runner_type_db: runner_type_model.id = runner_type_db.id try: runner_type_db = RunnerType.add_or_update(runner_type_model) extra = {'runner_type_db': runner_type_db} if update: LOG.audit('RunnerType updated. RunnerType %s', runner_type_db, extra=extra) else: LOG.audit('RunnerType created. RunnerType %s', runner_type_db, extra=extra) except Exception: LOG.exception('Unable to register runner type %s.', runner_type['name']) LOG.debug('End : register default RunnerTypes.')
def _get_runner_model(action_api): runner_db = None # Check if runner exists. try: runner_db = get_runnertype_by_name(action_api.runner_type) except StackStormDBObjectNotFoundError: msg = 'RunnerType %s is not found.' % action_api.runner_type raise ValueValidationException(msg) return runner_db
def register_runner_types(experimental=False): """ :param experimental: True to also register experimental runners. :type experimental: ``bool`` """ LOG.debug('Start : register default RunnerTypes.') for runner_type in RUNNER_TYPES: runner_type = copy.deepcopy(runner_type) # For backward compatibility reasons, we also register runners under the old names runner_names = [runner_type['name']] + runner_type.get('aliases', []) for runner_name in runner_names: runner_type['name'] = runner_name runner_experimental = runner_type.get('experimental', False) if runner_experimental and not experimental: LOG.debug('Skipping experimental runner "%s"' % (runner_name)) continue # Remove additional, non db-model attributes non_db_attributes = ['experimental', 'aliases'] for attribute in non_db_attributes: if attribute in runner_type: del runner_type[attribute] try: runner_type_db = get_runnertype_by_name(runner_name) update = True except StackStormDBObjectNotFoundError: runner_type_db = None update = False runner_type_api = RunnerTypeAPI(**runner_type) runner_type_api.validate() runner_type_model = RunnerTypeAPI.to_model(runner_type_api) if runner_type_db: runner_type_model.id = runner_type_db.id try: runner_type_db = RunnerType.add_or_update(runner_type_model) extra = {'runner_type_db': runner_type_db} if update: LOG.audit('RunnerType updated. RunnerType %s', runner_type_db, extra=extra) else: LOG.audit('RunnerType created. RunnerType %s', runner_type_db, extra=extra) except Exception: LOG.exception('Unable to register runner type %s.', runner_type['name']) LOG.debug('End : register default RunnerTypes.')
def _get_runner_model(action_api): runner_db = None # Check if runner exists. try: runner_db = get_runnertype_by_name(action_api.runner_type) except StackStormDBObjectNotFoundError: msg = 'RunnerType %s is not found.' % action_api.runner_type raise ValueValidationException(msg) return runner_db
def request(wf_def, ac_ex_db, st2_ctx): wf_ac_ex_id = str(ac_ex_db.id) LOG.info('[%s] Processing action execution request for workflow.', wf_ac_ex_id) # Load workflow definition into workflow spec model. spec_module = specs_loader.get_spec_module('native') wf_spec = spec_module.instantiate(wf_def) # Inspect the workflow spec. inspect(wf_spec, st2_ctx, raise_exception=True) # Identify the action to execute. action_db = action_utils.get_action_by_ref(ref=ac_ex_db.action['ref']) if not action_db: error = 'Unable to find action "%s".' % ac_ex_db.action['ref'] raise ac_exc.InvalidActionReferencedException(error) # Identify the runner for the action. runner_type_db = action_utils.get_runnertype_by_name( action_db.runner_type['name']) # Render action execution parameters. runner_params, action_params = param_utils.render_final_params( runner_type_db.runner_parameters, action_db.parameters, ac_ex_db.parameters, ac_ex_db.context) # Instantiate the workflow conductor. conductor_params = {'inputs': action_params, 'context': st2_ctx} conductor = conducting.WorkflowConductor(wf_spec, **conductor_params) # Set the initial workflow state to requested. conductor.request_workflow_state(states.REQUESTED) # Serialize the conductor which initializes some internal values. data = conductor.serialize() # Create a record for workflow execution. wf_ex_db = wf_db_models.WorkflowExecutionDB(action_execution=str( ac_ex_db.id), spec=data['spec'], graph=data['graph'], flow=data['flow'], context=data['context'], input=data['input'], output=data['output'], errors=data['errors'], status=data['state']) # Insert new record into the database and publish to the message bus. wf_ex_db = wf_db_access.WorkflowExecution.insert(wf_ex_db, publish=True) LOG.info('[%s] Workflow execution "%s" created.', wf_ac_ex_id, str(wf_ex_db.id)) return wf_ex_db
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
def get_runner_model(action_api): runner_db = None # Check if runner exists. try: runner_db = get_runnertype_by_name(action_api.runner_type) except StackStormDBObjectNotFoundError: msg = ('RunnerType %s is not found. If you are using old and deprecated runner name, you ' 'need to switch to a new one. For more information, please see ' 'https://docs.stackstorm.com/upgrade_notes.html#st2-v0-9' % (action_api.runner_type)) raise ValueValidationException(msg) return runner_db
def register_runner(runner_type, experimental): # For backward compatibility reasons, we also register runners under the old names runner_names = [runner_type['name']] + runner_type.get('aliases', []) for runner_name in runner_names: runner_type['name'] = runner_name runner_experimental = runner_type.get('experimental', False) if runner_experimental and not experimental: LOG.debug('Skipping experimental runner "%s"' % (runner_name)) continue # Remove additional, non db-model attributes non_db_attributes = ['experimental', 'aliases'] for attribute in non_db_attributes: if attribute in runner_type: del runner_type[attribute] try: runner_type_db = get_runnertype_by_name(runner_name) update = True except StackStormDBObjectNotFoundError: runner_type_db = None update = False # Note: We don't want to overwrite "enabled" attribute which is already in the database # (aka we don't want to re-enable runner which has been disabled by the user) if runner_type_db and runner_type_db['enabled'] != runner_type['enabled']: runner_type['enabled'] = runner_type_db['enabled'] # If package is not provided, assume it's the same as module name for backward # compatibility reasons if not runner_type.get('runner_package', None): runner_type['runner_package'] = runner_type['runner_module'] runner_type_api = RunnerTypeAPI(**runner_type) runner_type_api.validate() runner_type_model = RunnerTypeAPI.to_model(runner_type_api) if runner_type_db: runner_type_model.id = runner_type_db.id try: runner_type_db = RunnerType.add_or_update(runner_type_model) extra = {'runner_type_db': runner_type_db} if update: LOG.audit('RunnerType updated. RunnerType %s', runner_type_db, extra=extra) else: LOG.audit('RunnerType created. RunnerType %s', runner_type_db, extra=extra) except Exception: LOG.exception('Unable to register runner type %s.', runner_type['name']) return 0 return 1
def _invoke_action(self, action_db, runnertype_db, params, context=None, additional_contexts=None): """ Schedule an action execution. :type action_exec_spec: :class:`ActionExecutionSpecDB` :param params: Partially rendered parameters to execute the action with. :type params: ``dict`` :rtype: :class:`LiveActionDB` on successful scheduling, None otherwise. """ action_ref = action_db.ref runnertype_db = action_utils.get_runnertype_by_name( action_db.runner_type['name']) liveaction_db = LiveActionDB(action=action_ref, context=context, parameters=params) try: liveaction_db.parameters = self.get_resolved_parameters( runnertype_db=runnertype_db, action_db=action_db, params=liveaction_db.parameters, context=liveaction_db.context, additional_contexts=additional_contexts) except param_exc.ParamException as e: # We still need to create a request, so liveaction_db is assigned an ID liveaction_db, execution_db = action_service.create_request( liveaction_db) # By this point the execution is already in the DB therefore need to mark it failed. _, e, tb = sys.exc_info() action_service.update_status( liveaction=liveaction_db, new_status=action_constants.LIVEACTION_STATUS_FAILED, result={ 'error': str(e), 'traceback': ''.join(traceback.format_tb(tb, 20)) }) # Might be a good idea to return the actual ActionExecution rather than bubble up # the exception. raise validation_exc.ValueValidationException(str(e)) liveaction_db, execution_db = action_service.request(liveaction_db) return execution_db
def _cast_params(action_ref, params): def cast_object(x): if isinstance(x, str) or isinstance(x, unicode): try: return json.loads(x) except: return ast.literal_eval(x) else: return x casts = { 'array': (lambda x: ast.literal_eval(x) if isinstance(x, str) or isinstance(x, unicode) else x), 'boolean': (lambda x: ast.literal_eval(x.capitalize()) if isinstance(x, str) or isinstance(x, unicode) else x), 'integer': int, 'number': float, 'object': cast_object, 'string': str } action_db = action_db_util.get_action_by_ref(action_ref) action_parameters_schema = action_db.parameters runnertype_db = action_db_util.get_runnertype_by_name( action_db.runner_type['name']) runner_parameters_schema = runnertype_db.runner_parameters # combine into 1 list of parameter schemas parameters_schema = {} if runner_parameters_schema: parameters_schema.update(runner_parameters_schema) if action_parameters_schema: parameters_schema.update(action_parameters_schema) # cast each param individually for k, v in six.iteritems(params): parameter_schema = parameters_schema.get(k, None) if not parameter_schema: continue parameter_type = parameter_schema.get('type', None) if not parameter_type: continue cast = casts.get(parameter_type, None) LOG.debug('Casting param: %s of type %s to type: %s', v, type(v), parameter_type) if not cast: continue params[k] = cast(v) return params
def register_runner_types(experimental=False): """ :param experimental: True to also register experimental runners. :type experimental: ``bool`` """ LOG.debug("Start : register default RunnerTypes.") for runner_type in RUNNER_TYPES: runner_type = copy.deepcopy(runner_type) # For backward compatibility reasons, we also register runners under the old names runner_names = [runner_type["name"]] + runner_type.get("aliases", []) for runner_name in runner_names: runner_type["name"] = runner_name runner_experimental = runner_type.get("experimental", False) if runner_experimental and not experimental: LOG.debug('Skipping experimental runner "%s"' % (runner_name)) continue # Remove additional, non db-model attributes non_db_attributes = ["experimental", "aliases"] for attribute in non_db_attributes: if attribute in runner_type: del runner_type[attribute] try: runner_type_db = get_runnertype_by_name(runner_name) update = True except StackStormDBObjectNotFoundError: runner_type_db = None update = False runner_type_api = RunnerTypeAPI(**runner_type) runner_type_api.validate() runner_type_model = RunnerTypeAPI.to_model(runner_type_api) if runner_type_db: runner_type_model.id = runner_type_db.id try: runner_type_db = RunnerType.add_or_update(runner_type_model) extra = {"runner_type_db": runner_type_db} if update: LOG.audit("RunnerType updated. RunnerType %s", runner_type_db, extra=extra) else: LOG.audit("RunnerType created. RunnerType %s", runner_type_db, extra=extra) except Exception: LOG.exception("Unable to register runner type %s.", runner_type["name"]) LOG.debug("End : register default RunnerTypes.")
def invoke_post_run(liveaction_db, action_db=None): # NOTE: This import has intentionally been moved here to avoid massive performance overhead # (1+ second) for other functions inside this module which don't need to use those imports. from st2common.runners import base as runners from st2common.util import action_db as action_db_utils from st2common.content import utils as content_utils LOG.info("Invoking post run for action execution %s.", liveaction_db.id) # Identify action and runner. if not action_db: action_db = action_db_utils.get_action_by_ref(liveaction_db.action) if not action_db: LOG.error( "Unable to invoke post run. Action %s no longer exists.", liveaction_db.action, ) return LOG.info( "Action execution %s runs %s of runner type %s.", liveaction_db.id, action_db.name, action_db.runner_type["name"], ) # Get instance of the action runner and related configuration. runner_type_db = action_db_utils.get_runnertype_by_name( action_db.runner_type["name"]) runner = runners.get_runner(name=runner_type_db.name) entry_point = content_utils.get_entry_point_abs_path( pack=action_db.pack, entry_point=action_db.entry_point) libs_dir_path = content_utils.get_action_libs_abs_path( pack=action_db.pack, entry_point=action_db.entry_point) # Configure the action runner. runner.runner_type_db = runner_type_db runner.action = action_db runner.action_name = action_db.name runner.liveaction = liveaction_db runner.liveaction_id = str(liveaction_db.id) runner.entry_point = entry_point runner.context = getattr(liveaction_db, "context", dict()) runner.callback = getattr(liveaction_db, "callback", dict()) runner.libs_dir_path = libs_dir_path # Invoke the post_run method. runner.post_run(liveaction_db.status, liveaction_db.result)
def schedule(liveaction): """ Schedule an action to be run. :return: (liveaction, execution) :rtype: tuple """ # Use the user context from the parent action execution. Subtasks in a workflow # action can be invoked by a system user and so we want to use the user context # from the original workflow action. if getattr(liveaction, 'context', None) and 'parent' in liveaction.context: parent = LiveAction.get_by_id(liveaction.context['parent']) liveaction.context['user'] = getattr(parent, 'context', dict()).get('user') # Validate action. action_db = action_utils.get_action_by_ref(liveaction.action) if not action_db: raise ValueError('Action "%s" cannot be found.' % liveaction.action) if not action_db.enabled: raise ValueError('Unable to execute. Action "%s" is disabled.' % liveaction.action) runnertype_db = action_utils.get_runnertype_by_name(action_db.runner_type['name']) if not hasattr(liveaction, 'parameters'): liveaction.parameters = dict() # Validate action parameters. schema = util_schema.get_parameter_schema(action_db) validator = util_schema.get_validator() jsonschema.validate(liveaction.parameters, schema, validator) # validate that no immutable params are being overriden. Although possible to # ignore the override it is safer to inform the user to avoid surprises. immutables = _get_immutable_params(action_db.parameters) immutables.extend(_get_immutable_params(runnertype_db.runner_parameters)) overridden_immutables = [p for p in six.iterkeys(liveaction.parameters) if p in immutables] if len(overridden_immutables) > 0: raise ValueError('Override of immutable parameter(s) %s is unsupported.' % str(overridden_immutables)) # Write to database and send to message queue. liveaction.status = LIVEACTION_STATUS_SCHEDULED liveaction.start_timestamp = isotime.add_utc_tz(datetime.datetime.utcnow()) # Publish creation after both liveaction and actionexecution are created. liveaction = LiveAction.add_or_update(liveaction, publish=False) execution = executions.create_execution_object(liveaction, publish=False) # assume that this is a creation. LiveAction.publish_create(liveaction) ActionExecution.publish_create(execution) LOG.audit('Action execution scheduled. LiveAction=%s. ActionExecution=%s', liveaction, execution) return liveaction, execution
def get_runner_model(action_api): runner_db = None # Check if runner exists. try: runner_db = get_runnertype_by_name(action_api.runner_type) except StackStormDBObjectNotFoundError: msg = ( 'RunnerType %s is not found. If you are using old and deprecated runner name, you ' 'need to switch to a new one. For more information, please see ' 'https://docs.stackstorm.com/upgrade_notes.html#st2-v0-9' % (action_api.runner_type)) raise ValueValidationException(msg) return runner_db
def request(wf_def, ac_ex_db): # Load workflow definition into workflow spec model. spec_module = specs_loader.get_spec_module('native') wf_spec = spec_module.instantiate(wf_def) # Inspect the workflow spec. wf_spec.inspect(raise_exception=True) # Identify the action to execute. action_db = ac_db_util.get_action_by_ref(ref=ac_ex_db.action['ref']) if not action_db: error = 'Unable to find action "%s".' % ac_ex_db.action['ref'] raise ac_exc.InvalidActionReferencedException(error) # Identify the runner for the action. runner_type_db = ac_db_util.get_runnertype_by_name(action_db.runner_type['name']) # Render action execution parameters. runner_params, action_params = param_utils.render_final_params( runner_type_db.runner_parameters, action_db.parameters, ac_ex_db.parameters, ac_ex_db.context ) # Instantiate the workflow conductor. conductor = conducting.WorkflowConductor(wf_spec, **action_params) conductor.set_workflow_state(states.REQUESTED) # Serialize the conductor which initializes some internal values. data = conductor.serialize() # Create a record for workflow execution. wf_ex_db = wf_db_models.WorkflowExecutionDB( action_execution=str(ac_ex_db.id), spec=data['spec'], graph=data['graph'], flow=data['flow'], input=data['input'], output=data['output'], errors=data['errors'], status=data['state'] ) # Insert new record into the database and publish to the message bus. wf_ex_db = wf_db_access.WorkflowExecution.insert(wf_ex_db, publish=True) return wf_ex_db
def cast_params(action_ref, params, cast_overrides=None): """ """ params = params or {} action_db = action_db_util.get_action_by_ref(action_ref) if not action_db: raise ValueError('Action with ref "%s" doesn\'t exist' % (action_ref)) action_parameters_schema = action_db.parameters runnertype_db = action_db_util.get_runnertype_by_name(action_db.runner_type['name']) runner_parameters_schema = runnertype_db.runner_parameters # combine into 1 list of parameter schemas parameters_schema = {} if runner_parameters_schema: parameters_schema.update(runner_parameters_schema) if action_parameters_schema: parameters_schema.update(action_parameters_schema) # cast each param individually for k, v in six.iteritems(params): parameter_schema = parameters_schema.get(k, None) if not parameter_schema: LOG.debug('Will skip cast of param[name: %s, value: %s]. No schema.', k, v) continue parameter_type = parameter_schema.get('type', None) if not parameter_type: LOG.debug('Will skip cast of param[name: %s, value: %s]. No type.', k, v) continue # Pick up cast from teh override and then from the system suppied ones. cast = cast_overrides.get(parameter_type, None) if cast_overrides else None if not cast: cast = get_cast(cast_type=parameter_type) if not cast: LOG.debug('Will skip cast of param[name: %s, value: %s]. No cast for %s.', k, v, parameter_type) continue LOG.debug('Casting param: %s of type %s to type: %s', v, type(v), parameter_type) try: params[k] = cast(v) except Exception as e: v_type = type(v).__name__ msg = ('Failed to cast value "%s" (type: %s) for parameter "%s" of type "%s": %s. ' 'Perhaphs the value is of an invalid type?' % (v, v_type, k, parameter_type, str(e))) raise ValueError(msg) return params
def get_parameter_schema(model): # Dynamically construct JSON schema from the parameters metadata. schema = {} from st2common.util.action_db import get_runnertype_by_name runner_type = get_runnertype_by_name(model.runner_type['name']) normalize = lambda x: {k: v if v else SCHEMA_ANY_TYPE for k, v in six.iteritems(x)} properties = normalize(runner_type.runner_parameters) properties.update(normalize(model.parameters)) if properties: schema['title'] = model.name if model.description: schema['description'] = model.description schema['type'] = 'object' schema['properties'] = properties schema['additionalProperties'] = False return schema
def cast_params(action_ref, params, cast_overrides=None): """ """ params = params or {} action_db = action_db_util.get_action_by_ref(action_ref) if not action_db: raise ValueError('Action with ref "%s" doesn\'t exist' % (action_ref)) action_parameters_schema = action_db.parameters runnertype_db = action_db_util.get_runnertype_by_name( action_db.runner_type['name']) runner_parameters_schema = runnertype_db.runner_parameters # combine into 1 list of parameter schemas parameters_schema = {} if runner_parameters_schema: parameters_schema.update(runner_parameters_schema) if action_parameters_schema: parameters_schema.update(action_parameters_schema) # cast each param individually for k, v in six.iteritems(params): parameter_schema = parameters_schema.get(k, None) if not parameter_schema: LOG.debug( 'Will skip cast of param[name: %s, value: %s]. No schema.', k, v) continue parameter_type = parameter_schema.get('type', None) if not parameter_type: LOG.debug('Will skip cast of param[name: %s, value: %s]. No type.', k, v) continue # Pick up cast from teh override and then from the system suppied ones. cast = cast_overrides.get(parameter_type, None) if cast_overrides else None if not cast: cast = get_cast(cast_type=parameter_type) if not cast: LOG.debug( 'Will skip cast of param[name: %s, value: %s]. No cast for %s.', k, v, parameter_type) continue LOG.debug('Casting param: %s of type %s to type: %s', v, type(v), parameter_type) params[k] = cast(v) return params
def validate_action(action_api): runner_db = None # Check if runner exists. try: runner_db = get_runnertype_by_name(action_api.runner_type) except StackStormDBObjectNotFoundError: msg = 'RunnerType %s is not found.' % action_api.runner_type raise ValueValidationException(msg) # Check if pack is valid. if not _is_valid_pack(action_api.pack): msg = 'Content pack %s does not exist in %s.' % ( action_api.pack, cfg.CONF.content.packs_base_path) raise ValueValidationException(msg) # Check if parameters defined are valid. _validate_parameters(action_api.parameters, runner_db.runner_parameters)
def get_schema_for_action_parameters(action_db, runnertype_db=None): """ Dynamically construct JSON schema for the provided action from the parameters metadata. Note: This schema is used to validate parameters which are passed to the action. """ if not runnertype_db: from st2common.util.action_db import get_runnertype_by_name runnertype_db = get_runnertype_by_name(action_db.runner_type["name"]) # Note: We need to perform a deep merge because user can only specify a single parameter # attribute when overriding it in an action metadata. parameters_schema = {} deep_update(parameters_schema, runnertype_db.runner_parameters) deep_update(parameters_schema, action_db.parameters) # Perform validation, make sure user is not providing parameters which can't # be overriden runner_parameter_names = list(runnertype_db.runner_parameters.keys()) for name, schema in six.iteritems(action_db.parameters): if name not in runner_parameter_names: continue for attribute, value in six.iteritems(schema): runner_param_value = runnertype_db.runner_parameters[name].get( attribute) validate_runner_parameter_attribute_override( action_ref=action_db.ref, param_name=name, attr_name=attribute, runner_param_attr_value=runner_param_value, action_param_attr_value=value, ) schema = get_schema_for_resource_parameters( parameters_schema=parameters_schema) if parameters_schema: schema["title"] = action_db.name if action_db.description: schema["description"] = action_db.description return schema
def validate_action(action_api): runner_db = None # Check if runner exists. try: runner_db = get_runnertype_by_name(action_api.runner_type) except StackStormDBObjectNotFoundError: msg = 'RunnerType %s is not found.' % action_api.runner_type raise ValueValidationException(msg) # Check if pack is valid. if not _is_valid_pack(action_api.pack): msg = 'Content pack %s does not exist in %s.' % ( action_api.pack, cfg.CONF.content.packs_base_path) raise ValueValidationException(msg) # Check if parameters defined are valid. _validate_parameters(action_api.parameters, runner_db.runner_parameters)
def dispatch(self, liveaction_db): action_db = get_action_by_ref(liveaction_db.action) if not action_db: raise Exception("Action %s not found in DB." % (liveaction_db.action)) liveaction_db.context["pack"] = action_db.pack runner_type_db = get_runnertype_by_name(action_db.runner_type["name"]) extra = { "liveaction_db": liveaction_db, "runner_type_db": runner_type_db } LOG.info("Dispatching Action to a runner", extra=extra) # Get runner instance. runner = self._get_runner(runner_type_db, action_db, liveaction_db) LOG.debug('Runner instance for RunnerType "%s" is: %s', runner_type_db.name, runner) # Process the request. funcs = { action_constants.LIVEACTION_STATUS_REQUESTED: self._do_run, action_constants.LIVEACTION_STATUS_SCHEDULED: self._do_run, action_constants.LIVEACTION_STATUS_RUNNING: self._do_run, action_constants.LIVEACTION_STATUS_CANCELING: self._do_cancel, action_constants.LIVEACTION_STATUS_PAUSING: self._do_pause, action_constants.LIVEACTION_STATUS_RESUMING: self._do_resume, } if liveaction_db.status not in funcs: raise actionrunner.ActionRunnerDispatchError( "Action runner is unable to dispatch the liveaction because it is " 'in an unsupported status of "%s".' % liveaction_db.status) with CounterWithTimer(key="action.executions"): status = liveaction_db.status with CounterWithTimer(key="action.executions.process.%s" % (status)): liveaction_db = funcs[liveaction_db.status](runner) return liveaction_db.result
def dispatch(self, liveaction_db): action_db = get_action_by_ref(liveaction_db.action) if not action_db: raise Exception('Action %s not found in DB.' % (liveaction_db.action)) liveaction_db.context['pack'] = action_db.pack runnertype_db = get_runnertype_by_name(action_db.runner_type['name']) extra = { 'liveaction_db': liveaction_db, 'runnertype_db': runnertype_db } LOG.info('Dispatching Action to a runner', extra=extra) # Get runner instance. runner = self._get_runner(runnertype_db, action_db, liveaction_db) LOG.debug('Runner instance for RunnerType "%s" is: %s', runnertype_db.name, runner) # Process the request. funcs = { action_constants.LIVEACTION_STATUS_REQUESTED: self._do_run, action_constants.LIVEACTION_STATUS_SCHEDULED: self._do_run, action_constants.LIVEACTION_STATUS_RUNNING: self._do_run, action_constants.LIVEACTION_STATUS_CANCELING: self._do_cancel, action_constants.LIVEACTION_STATUS_PAUSING: self._do_pause, action_constants.LIVEACTION_STATUS_RESUMING: self._do_resume } if liveaction_db.status not in funcs: raise actionrunner.ActionRunnerDispatchError( 'Action runner is unable to dispatch the liveaction because it is ' 'in an unsupported status of "%s".' % liveaction_db.status) liveaction_db = funcs[liveaction_db.status]( runner=runner, runnertype_db=runnertype_db, action_db=action_db, liveaction_db=liveaction_db) return liveaction_db.result
def _invoke_action(self, action_db, runnertype_db, params, context=None, additional_contexts=None): """ Schedule an action execution. :type action_exec_spec: :class:`ActionExecutionSpecDB` :param params: Partially rendered parameters to execute the action with. :type params: ``dict`` :rtype: :class:`LiveActionDB` on successful scheduling, None otherwise. """ action_ref = action_db.ref runnertype_db = action_utils.get_runnertype_by_name(action_db.runner_type['name']) liveaction_db = LiveActionDB(action=action_ref, context=context, parameters=params) try: liveaction_db.parameters = self.get_resolved_parameters( runnertype_db=runnertype_db, action_db=action_db, params=liveaction_db.parameters, context=liveaction_db.context, additional_contexts=additional_contexts) except param_exc.ParamException as e: # We still need to create a request, so liveaction_db is assigned an ID liveaction_db, execution_db = action_service.create_request(liveaction_db) # By this point the execution is already in the DB therefore need to mark it failed. _, e, tb = sys.exc_info() action_service.update_status( liveaction=liveaction_db, new_status=action_constants.LIVEACTION_STATUS_FAILED, result={'error': six.text_type(e), 'traceback': ''.join(traceback.format_tb(tb, 20))}) # Might be a good idea to return the actual ActionExecution rather than bubble up # the exception. raise validation_exc.ValueValidationException(six.text_type(e)) liveaction_db, execution_db = action_service.request(liveaction_db) return execution_db
def _schedule_execution(self, liveaction, user=None): # Initialize execution context if it does not exist. if not hasattr(liveaction, 'context'): liveaction.context = dict() liveaction.context['user'] = user LOG.debug('User is: %s' % liveaction.context['user']) # Retrieve other st2 context from request header. if 'st2-context' in pecan.request.headers and pecan.request.headers['st2-context']: context = jsonify.try_loads(pecan.request.headers['st2-context']) if not isinstance(context, dict): raise ValueError('Unable to convert st2-context from the headers into JSON.') liveaction.context.update(context) # Schedule the action execution. liveaction_db = LiveActionAPI.to_model(liveaction) liveaction_db, actionexecution_db = action_service.create_request(liveaction_db) action_db = action_utils.get_action_by_ref(liveaction_db.action) runnertype_db = action_utils.get_runnertype_by_name(action_db.runner_type['name']) try: liveaction_db.parameters = param_utils.render_live_params( runnertype_db.runner_parameters, action_db.parameters, liveaction_db.parameters, liveaction_db.context) except ParamException: # By this point the execution is already in the DB therefore need to mark it failed. _, e, tb = sys.exc_info() action_service.update_status( liveaction=liveaction_db, new_status=LIVEACTION_STATUS_FAILED, result={'error': str(e), 'traceback': ''.join(traceback.format_tb(tb, 20))}) # Might be a good idea to return the actual ActionExecution rather than bubble up # the execption. raise ValueValidationException(str(e)) liveaction_db = LiveAction.add_or_update(liveaction_db, publish=False) _, actionexecution_db = action_service.publish_request(liveaction_db, actionexecution_db) from_model_kwargs = self._get_from_model_kwargs_for_request(request=pecan.request) return ActionExecutionAPI.from_model(actionexecution_db, from_model_kwargs)
def _cast_params(action_ref, params): casts = { 'array': (lambda x: ast.literal_eval(x) if isinstance(x, str) or isinstance(x, unicode) else x), 'boolean': (lambda x: ast.literal_eval(x.capitalize()) if isinstance(x, str) or isinstance(x, unicode) else x), 'integer': int, 'number': float, 'object': (lambda x: json.loads(x) if isinstance(x, str) or isinstance(x, unicode) else x), 'string': str } action_db = action_db_util.get_action_by_ref( ResourceReference.from_string_reference(ref=action_ref)) action_parameters_schema = action_db.parameters runnertype_db = action_db_util.get_runnertype_by_name( action_db.runner_type['name']) runner_parameters_schema = runnertype_db.runner_parameters # combine into 1 list of parameter schemas parameters_schema = {} if runner_parameters_schema: parameters_schema.update(runner_parameters_schema) if action_parameters_schema: parameters_schema.update(action_parameters_schema) # cast each param individually for k, v in six.iteritems(params): parameter_schema = parameters_schema.get(k, None) if not parameter_schema: continue parameter_type = parameter_schema.get('type', None) if not parameter_type: continue cast = casts.get(parameter_type, None) if not cast: continue params[k] = cast(v) return params
def dispatch(self, liveaction_db): action_db = get_action_by_ref(liveaction_db.action) if not action_db: raise Exception('Action %s not found in DB.' % (liveaction_db.action)) runnertype_db = get_runnertype_by_name(action_db.runner_type['name']) extra = {'liveaction_db': liveaction_db, 'runnertype_db': runnertype_db} LOG.info('Dispatching Action to a runner', extra=extra) # Get runner instance. runner = self._get_runner(runnertype_db, action_db, liveaction_db) LOG.debug('Runner instance for RunnerType "%s" is: %s', runnertype_db.name, runner) # Process the request. liveaction_db = (self._do_cancel(runner, runnertype_db, action_db, liveaction_db) if liveaction_db.status == action_constants.LIVEACTION_STATUS_CANCELING else self._do_run(runner, runnertype_db, action_db, liveaction_db)) return liveaction_db.result
def schedule(execution): # Use the user context from the parent action execution. Subtasks in a workflow # action can be invoked by a system user and so we want to use the user context # from the original workflow action. if getattr(execution, 'context', None) and 'parent' in execution.context: parent = ActionExecution.get_by_id(execution.context['parent']) execution.context['user'] = getattr(parent, 'context', dict()).get('user') # Validate action. action_db = action_utils.get_action_by_ref(execution.action) if not action_db: raise ValueError('Action "%s" cannot be found.' % execution.action) if not action_db.enabled: raise ValueError('Unable to execute. Action "%s" is disabled.' % execution.action) runnertype_db = action_utils.get_runnertype_by_name(action_db.runner_type['name']) if not hasattr(execution, 'parameters'): execution.parameters = dict() # Validate action parameters. schema = util_schema.get_parameter_schema(action_db) validator = util_schema.get_validator() jsonschema.validate(execution.parameters, schema, validator) # validate that no immutable params are being overriden. Although possible to # ignore the override it is safer to inform the user to avoid surprises. immutables = _get_immutable_params(action_db.parameters) immutables.extend(_get_immutable_params(runnertype_db.runner_parameters)) overridden_immutables = [p for p in six.iterkeys(execution.parameters) if p in immutables] if len(overridden_immutables) > 0: raise ValueError('Override of immutable parameter(s) %s is unsupported.' % str(overridden_immutables)) # Write to database and send to message queue. execution.status = ACTIONEXEC_STATUS_SCHEDULED execution.start_timestamp = isotime.add_utc_tz(datetime.datetime.utcnow()) execution = ActionExecution.add_or_update(execution) LOG.audit('Action execution scheduled. ActionExecution=%s.', execution) return execution
def get_schema_for_action_parameters(action_db): """ Dynamically construct JSON schema for the provided action from the parameters metadata. Note: This schema is used to validate parameters which are passed to the action. """ from st2common.util.action_db import get_runnertype_by_name runner_type = get_runnertype_by_name(action_db.runner_type['name']) parameters_schema = {} parameters_schema.update(runner_type.runner_parameters) parameters_schema.update(action_db.parameters) schema = get_schema_for_resource_parameters(parameters_schema=parameters_schema) if parameters_schema: schema['title'] = action_db.name if action_db.description: schema['description'] = action_db.description return schema
def get_schema_for_action_parameters(action_db, runnertype_db=None): """ Dynamically construct JSON schema for the provided action from the parameters metadata. Note: This schema is used to validate parameters which are passed to the action. """ if not runnertype_db: from st2common.util.action_db import get_runnertype_by_name runnertype_db = get_runnertype_by_name(action_db.runner_type['name']) # Note: We need to perform a deep merge because user can only specify a single parameter # attribute when overriding it in an action metadata. parameters_schema = {} deep_update(parameters_schema, runnertype_db.runner_parameters) deep_update(parameters_schema, action_db.parameters) # Perform validation, make sure user is not providing parameters which can't # be overriden runner_parameter_names = list(runnertype_db.runner_parameters.keys()) for name, schema in six.iteritems(action_db.parameters): if name not in runner_parameter_names: continue for attribute, value in six.iteritems(schema): runner_param_value = runnertype_db.runner_parameters[name].get(attribute) validate_runner_parameter_attribute_override(action_ref=action_db.ref, param_name=name, attr_name=attribute, runner_param_attr_value=runner_param_value, action_param_attr_value=value) schema = get_schema_for_resource_parameters(parameters_schema=parameters_schema) if parameters_schema: schema['title'] = action_db.name if action_db.description: schema['description'] = action_db.description return schema
def cast_params(action_ref, params, cast_overrides=None): """ """ params = params or {} action_db = action_db_util.get_action_by_ref(action_ref) if not action_db: raise ValueError('Action with ref "%s" doesn\'t exist' % (action_ref)) action_parameters_schema = action_db.parameters runnertype_db = action_db_util.get_runnertype_by_name(action_db.runner_type["name"]) runner_parameters_schema = runnertype_db.runner_parameters # combine into 1 list of parameter schemas parameters_schema = {} if runner_parameters_schema: parameters_schema.update(runner_parameters_schema) if action_parameters_schema: parameters_schema.update(action_parameters_schema) # cast each param individually for k, v in six.iteritems(params): parameter_schema = parameters_schema.get(k, None) if not parameter_schema: LOG.debug("Will skip cast of param[name: %s, value: %s]. No schema.", k, v) continue parameter_type = parameter_schema.get("type", None) if not parameter_type: LOG.debug("Will skip cast of param[name: %s, value: %s]. No type.", k, v) continue # Pick up cast from teh override and then from the system suppied ones. cast = cast_overrides.get(parameter_type, None) if cast_overrides else None if not cast: cast = get_cast(cast_type=parameter_type) if not cast: LOG.debug("Will skip cast of param[name: %s, value: %s]. No cast for %s.", k, v, parameter_type) continue LOG.debug("Casting param: %s of type %s to type: %s", v, type(v), parameter_type) params[k] = cast(v) return params
def get_schema_for_action_parameters(action_db): """ Dynamically construct JSON schema for the provided action from the parameters metadata. Note: This schema is used to validate parameters which are passed to the action. """ def normalize(x): return {k: v if v else SCHEMA_ANY_TYPE for k, v in six.iteritems(x)} schema = {} from st2common.util.action_db import get_runnertype_by_name runner_type = get_runnertype_by_name(action_db.runner_type['name']) properties = normalize(runner_type.runner_parameters) properties.update(normalize(action_db.parameters)) if properties: schema['title'] = action_db.name if action_db.description: schema['description'] = action_db.description schema['type'] = 'object' schema['properties'] = properties schema['additionalProperties'] = False return schema
def _cast_params(action_ref, params): casts = { 'array': (lambda x: ast.literal_eval(x) if isinstance(x, str) or isinstance(x, unicode) else x), 'boolean': (lambda x: ast.literal_eval(x.capitalize()) if isinstance(x, str) or isinstance(x, unicode) else x), 'integer': int, 'number': float, 'object': (lambda x: json.loads(x) if isinstance(x, str) or isinstance(x, unicode) else x), 'string': str } action_db = action_db_util.get_action_by_ref( ResourceReference.from_string_reference(ref=action_ref)) action_parameters_schema = action_db.parameters runnertype_db = action_db_util.get_runnertype_by_name(action_db.runner_type['name']) runner_parameters_schema = runnertype_db.runner_parameters # combine into 1 list of parameter schemas parameters_schema = {} if runner_parameters_schema: parameters_schema.update(runner_parameters_schema) if action_parameters_schema: parameters_schema.update(action_parameters_schema) # cast each param individually for k, v in six.iteritems(params): parameter_schema = parameters_schema.get(k, None) if not parameter_schema: continue parameter_type = parameter_schema.get('type', None) if not parameter_type: continue cast = casts.get(parameter_type, None) if not cast: continue params[k] = cast(v) return params