def post(self, action): """ Create a new action. Handles requests: POST /actions/ """ if not hasattr(action, 'pack'): setattr(action, 'pack', DEFAULT_PACK_NAME) try: action_validator.validate_action(action) except ValueValidationException as e: abort(http_client.BAD_REQUEST, str(e)) return # ActionsController._validate_action_parameters(action, runnertype_db) action_model = ActionAPI.to_model(action) LOG.debug('/actions/ POST verified ActionAPI object=%s', action) action_db = Action.add_or_update(action_model) LOG.debug('/actions/ POST saved ActionDB object=%s', action_db) extra = {'action_db': action_db} LOG.audit('Action created. Action.id=%s' % (action_db.id), extra=extra) action_api = ActionAPI.from_model(action_db) return action_api
def test_chained_executions(self): liveaction = LiveActionDB(action='core.chain') liveaction, _ = action_service.request(liveaction) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(liveaction.status, action_constants.LIVEACTION_STATUS_FAILED) execution = self._get_action_execution(liveaction__id=str(liveaction.id), raise_exception=True) action = action_utils.get_action_by_ref('core.chain') self.assertDictEqual(execution.action, vars(ActionAPI.from_model(action))) runner = RunnerType.get_by_name(action.runner_type['name']) self.assertDictEqual(execution.runner, vars(RunnerTypeAPI.from_model(runner))) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(execution.start_timestamp, liveaction.start_timestamp) self.assertEqual(execution.end_timestamp, liveaction.end_timestamp) self.assertEqual(execution.result, liveaction.result) self.assertEqual(execution.status, liveaction.status) self.assertEqual(execution.context, liveaction.context) self.assertEqual(execution.liveaction['callback'], liveaction.callback) self.assertEqual(execution.liveaction['action'], liveaction.action) self.assertGreater(len(execution.children), 0) for child in execution.children: record = ActionExecution.get(id=child, raise_exception=True) self.assertEqual(record.parent, str(execution.id)) self.assertEqual(record.action['name'], 'local') self.assertEqual(record.runner['name'], 'run-local')
def test_basic_execution(self): liveaction = LiveActionDB(action='executions.local', parameters={'cmd': 'uname -a'}) liveaction, _ = action_service.request(liveaction) liveaction = self._wait_on_status(liveaction, action_constants.LIVEACTION_STATUS_FAILED) execution = self._get_action_execution( liveaction__id=str(liveaction.id), raise_exception=True ) self.assertDictEqual(execution.trigger, {}) self.assertDictEqual(execution.trigger_type, {}) self.assertDictEqual(execution.trigger_instance, {}) self.assertDictEqual(execution.rule, {}) action = action_utils.get_action_by_ref('executions.local') self.assertDictEqual(execution.action, vars(ActionAPI.from_model(action))) runner = RunnerType.get_by_name(action.runner_type['name']) self.assertDictEqual(execution.runner, vars(RunnerTypeAPI.from_model(runner))) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(execution.start_timestamp, liveaction.start_timestamp) self.assertEqual(execution.end_timestamp, liveaction.end_timestamp) self.assertEqual(execution.result, liveaction.result) self.assertEqual(execution.status, liveaction.status) self.assertEqual(execution.context, liveaction.context) self.assertEqual(execution.liveaction['callback'], liveaction.callback) self.assertEqual(execution.liveaction['action'], liveaction.action)
def post(self, action): """ Create a new action. Handles requests: POST /actions/ """ # Perform validation validate_not_part_of_system_pack(action) action_validator.validate_action(action) # Write pack data files to disk (if any are provided) data_files = getattr(action, 'data_files', []) written_data_files = [] if data_files: written_data_files = self._handle_data_files(pack_name=action.pack, data_files=data_files) action_model = ActionAPI.to_model(action) LOG.debug('/actions/ POST verified ActionAPI object=%s', action) action_db = Action.add_or_update(action_model) LOG.debug('/actions/ POST saved ActionDB object=%s', action_db) # Dispatch an internal trigger for each written data file. This way user # automate comitting this files to git using StackStorm rule if written_data_files: self._dispatch_trigger_for_written_data_files( action_db=action_db, written_data_files=written_data_files) extra = {'acion_db': action_db} LOG.audit('Action created. Action.id=%s' % (action_db.id), extra=extra) action_api = ActionAPI.from_model(action_db) return action_api
def test_basic_execution(self): liveaction = LiveActionDB(action="executions.local", parameters={"cmd": "uname -a"}) liveaction, _ = action_service.request(liveaction) liveaction = self._wait_on_status( liveaction, action_constants.LIVEACTION_STATUS_FAILED) execution = self._get_action_execution(liveaction__id=str( liveaction.id), raise_exception=True) self.assertDictEqual(execution.trigger, {}) self.assertDictEqual(execution.trigger_type, {}) self.assertDictEqual(execution.trigger_instance, {}) self.assertDictEqual(execution.rule, {}) action = action_utils.get_action_by_ref("executions.local") self.assertDictEqual(execution.action, vars(ActionAPI.from_model(action))) runner = RunnerType.get_by_name(action.runner_type["name"]) self.assertDictEqual(execution.runner, vars(RunnerTypeAPI.from_model(runner))) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(execution.start_timestamp, liveaction.start_timestamp) # NOTE: Timestamp of liveaction and execution may be a bit different, depending on how long # it takes to persist each object in the database self.assertEqual( execution.end_timestamp.replace(microsecond=0), liveaction.end_timestamp.replace(microsecond=0), ) self.assertEqual(execution.result, liveaction.result) self.assertEqual(execution.status, liveaction.status) self.assertEqual(execution.context, liveaction.context) self.assertEqual(execution.liveaction["callback"], liveaction.callback) self.assertEqual(execution.liveaction["action"], liveaction.action)
def test_chained_executions(self): with mock.patch("st2common.runners.register_runner", mock.MagicMock(return_value=action_chain_runner)): liveaction = LiveActionDB(action="executions.chain") liveaction, _ = action_service.request(liveaction) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(liveaction.status, action_constants.LIVEACTION_STATUS_FAILED) execution = self._get_action_execution(liveaction__id=str(liveaction.id), raise_exception=True) action = action_utils.get_action_by_ref("executions.chain") self.assertDictEqual(execution.action, vars(ActionAPI.from_model(action))) runner = RunnerType.get_by_name(action.runner_type["name"]) self.assertDictEqual(execution.runner, vars(RunnerTypeAPI.from_model(runner))) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(execution.start_timestamp, liveaction.start_timestamp) self.assertEqual(execution.end_timestamp, liveaction.end_timestamp) self.assertEqual(execution.result, liveaction.result) self.assertEqual(execution.status, liveaction.status) self.assertEqual(execution.context, liveaction.context) self.assertEqual(execution.liveaction["callback"], liveaction.callback) self.assertEqual(execution.liveaction["action"], liveaction.action) self.assertGreater(len(execution.children), 0) for child in execution.children: record = ActionExecution.get(id=child, raise_exception=True) self.assertEqual(record.parent, str(execution.id)) self.assertEqual(record.action["name"], "local") self.assertEqual(record.runner["name"], "run-local")
def test_basic_execution(self): action_ref = ResourceReference(name='local', pack='core') execution = ActionExecutionDB(action=action_ref.ref, parameters={'cmd': 'uname -a'}) execution = action_service.schedule(execution) execution = ActionExecution.get_by_id(str(execution.id)) self.assertEqual(execution.status, ACTIONEXEC_STATUS_SUCCEEDED) history = ActionExecutionHistory.get(execution__id=str(execution.id), raise_exception=True) self.assertDictEqual(history.trigger, {}) self.assertDictEqual(history.trigger_type, {}) self.assertDictEqual(history.trigger_instance, {}) self.assertDictEqual(history.rule, {}) action, _ = action_utils.get_action_by_dict({ 'name': action_ref.name, 'pack': action_ref.pack }) self.assertDictEqual(history.action, vars(ActionAPI.from_model(action))) runner = RunnerType.get_by_name(action.runner_type['name']) self.assertDictEqual(history.runner, vars(RunnerTypeAPI.from_model(runner))) execution = ActionExecution.get_by_id(str(execution.id)) self.assertDictEqual(history.execution, vars(ActionExecutionAPI.from_model(execution)))
def test_chained_executions(self): action_ref = ResourceReference(name='chain', pack='core') execution = ActionExecutionDB(action=action_ref.ref) execution = action_service.schedule(execution) execution = ActionExecution.get_by_id(str(execution.id)) self.assertEqual(execution.status, ACTIONEXEC_STATUS_SUCCEEDED) history = ActionExecutionHistory.get(execution__id=str(execution.id), raise_exception=True) action, _ = action_utils.get_action_by_dict({ 'name': action_ref.name, 'pack': action_ref.pack }) self.assertDictEqual(history.action, vars(ActionAPI.from_model(action))) runner = RunnerType.get_by_name(action.runner_type['name']) self.assertDictEqual(history.runner, vars(RunnerTypeAPI.from_model(runner))) execution = ActionExecution.get_by_id(str(execution.id)) self.assertDictEqual(history.execution, vars(ActionExecutionAPI.from_model(execution))) self.assertGreater(len(history.children), 0) for child in history.children: record = ActionExecutionHistory.get(id=child, raise_exception=True) self.assertEqual(record.parent, str(history.id)) self.assertEqual(record.action['name'], 'local') self.assertEqual(record.runner['name'], 'run-local')
def put(self, action_ref_or_id, action): action_db = self._get_by_ref_or_id(ref_or_id=action_ref_or_id) action_id = action_db.id try: validate_not_part_of_system_pack(action_db) except ValueValidationException as e: abort(http_client.BAD_REQUEST, str(e)) if not getattr(action, 'pack', None): action.pack = action_db.pack try: action_validator.validate_action(action) except ValueValidationException as e: abort(http_client.BAD_REQUEST, str(e)) return try: action_db = ActionAPI.to_model(action) action_db.id = action_id action_db = Action.add_or_update(action_db) except (ValidationError, ValueError) as e: LOG.exception('Unable to update action data=%s', action) abort(http_client.BAD_REQUEST, str(e)) return action_api = ActionAPI.from_model(action_db) LOG.debug('PUT /actions/ client_result=%s', action_api) return action_api
def post(self, action): """ Create a new action. Handles requests: POST /actions/ """ # Perform validation validate_not_part_of_system_pack(action) action_validator.validate_action(action) # Write pack data files to disk (if any are provided) data_files = getattr(action, 'data_files', []) written_data_files = [] if data_files: written_data_files = self._handle_data_files(pack_name=action.pack, data_files=data_files) action_model = ActionAPI.to_model(action) LOG.debug('/actions/ POST verified ActionAPI object=%s', action) action_db = Action.add_or_update(action_model) LOG.debug('/actions/ POST saved ActionDB object=%s', action_db) # Dispatch an internal trigger for each written data file. This way user # automate comitting this files to git using StackStorm rule if written_data_files: self._dispatch_trigger_for_written_data_files(action_db=action_db, written_data_files=written_data_files) extra = {'acion_db': action_db} LOG.audit('Action created. Action.id=%s' % (action_db.id), extra=extra) action_api = ActionAPI.from_model(action_db) return action_api
def test_execution_creation_action_triggered_by_rule(self): # Wait for the action execution to complete and then confirm outcome. trigger_type = self.MODELS['triggertypes']['triggertype2.yaml'] trigger = self.MODELS['triggers']['trigger2.yaml'] trigger_instance = self.MODELS['triggerinstances']['trigger_instance_1.yaml'] test_liveaction = self.FIXTURES['liveactions']['liveaction3.yaml'] rule = self.MODELS['rules']['rule3.yaml'] # Setup LiveAction to point to right rule and trigger_instance. # XXX: We need support for dynamic fixtures. test_liveaction['context']['rule']['id'] = str(rule.id) test_liveaction['context']['trigger_instance']['id'] = str(trigger_instance.id) test_liveaction_api = LiveActionAPI(**test_liveaction) test_liveaction = LiveAction.add_or_update(LiveActionAPI.to_model(test_liveaction_api)) liveaction = LiveAction.get(context__trigger_instance__id=str(trigger_instance.id)) self.assertIsNotNone(liveaction) self.assertEqual(liveaction.status, action_constants.LIVEACTION_STATUS_REQUESTED) executions_util.create_execution_object(liveaction) execution = self._get_action_execution(liveaction__id=str(liveaction.id), raise_exception=True) self.assertDictEqual(execution.trigger, vars(TriggerAPI.from_model(trigger))) self.assertDictEqual(execution.trigger_type, vars(TriggerTypeAPI.from_model(trigger_type))) self.assertDictEqual(execution.trigger_instance, vars(TriggerInstanceAPI.from_model(trigger_instance))) self.assertDictEqual(execution.rule, vars(RuleAPI.from_model(rule))) action = action_utils.get_action_by_ref(liveaction.action) self.assertDictEqual(execution.action, vars(ActionAPI.from_model(action))) runner = RunnerType.get_by_name(action.runner_type['name']) self.assertDictEqual(execution.runner, vars(RunnerTypeAPI.from_model(runner))) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEquals(execution.liveaction['id'], str(liveaction.id))
def test_execution_creation_manual_action_run(self): liveaction = self.MODELS['liveactions']['liveaction1.yaml'] pre_creation_timestamp = date_utils.get_datetime_utc_now() executions_util.create_execution_object(liveaction) post_creation_timestamp = date_utils.get_datetime_utc_now() execution = self._get_action_execution(liveaction__id=str( liveaction.id), raise_exception=True) self.assertDictEqual(execution.trigger, {}) self.assertDictEqual(execution.trigger_type, {}) self.assertDictEqual(execution.trigger_instance, {}) self.assertDictEqual(execution.rule, {}) action = action_utils.get_action_by_ref('core.local') self.assertDictEqual(execution.action, vars(ActionAPI.from_model(action))) runner = RunnerType.get_by_name(action.runner_type['name']) self.assertDictEqual(execution.runner, vars(RunnerTypeAPI.from_model(runner))) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEquals(execution.liveaction['id'], str(liveaction.id)) self.assertEquals(len(execution.log), 1) self.assertEquals(execution.log[0]['status'], liveaction.status) self.assertGreater(execution.log[0]['timestamp'], pre_creation_timestamp) self.assertLess(execution.log[0]['timestamp'], post_creation_timestamp)
def record_action_execution(self, body): try: history_id = bson.ObjectId() execution = ActionExecution.get_by_id(str(body.id)) action_ref = ResourceReference.from_string_reference( ref=execution.action) action_db, _ = action_utils.get_action_by_dict({ 'name': action_ref.name, 'pack': action_ref.pack }) runner = RunnerType.get_by_name(action_db.runner_type['name']) attrs = { 'id': history_id, 'action': vars(ActionAPI.from_model(action_db)), 'runner': vars(RunnerTypeAPI.from_model(runner)), 'execution': vars(ActionExecutionAPI.from_model(execution)) } if 'rule' in execution.context: rule = reference.get_model_from_ref( Rule, execution.context.get('rule', {})) attrs['rule'] = vars(RuleAPI.from_model(rule)) if 'trigger_instance' in execution.context: trigger_instance_id = execution.context.get( 'trigger_instance', {}) trigger_instance_id = trigger_instance_id.get('id', None) trigger_instance = TriggerInstance.get_by_id( trigger_instance_id) trigger = reference.get_model_by_resource_ref( db_api=Trigger, ref=trigger_instance.trigger) trigger_type = reference.get_model_by_resource_ref( db_api=TriggerType, ref=trigger.type) trigger_instance = reference.get_model_from_ref( TriggerInstance, execution.context.get('trigger_instance', {})) attrs['trigger_instance'] = vars( TriggerInstanceAPI.from_model(trigger_instance)) attrs['trigger'] = vars(TriggerAPI.from_model(trigger)) attrs['trigger_type'] = vars( TriggerTypeAPI.from_model(trigger_type)) parent = ActionExecutionHistory.get( execution__id=execution.context.get('parent', '')) if parent: attrs['parent'] = str(parent.id) if str(history_id) not in parent.children: parent.children.append(str(history_id)) ActionExecutionHistory.add_or_update(parent) history = ActionExecutionHistoryDB(**attrs) history = ActionExecutionHistory.add_or_update(history) except: LOG.exception('An unexpected error occurred while creating the ' 'action execution history.') raise
def test_triggered_execution(self): docs = { 'trigger_type': copy.deepcopy(fixture.ARTIFACTS['trigger_type']), 'trigger': copy.deepcopy(fixture.ARTIFACTS['trigger']), 'rule': copy.deepcopy(fixture.ARTIFACTS['rule']), 'trigger_instance': copy.deepcopy(fixture.ARTIFACTS['trigger_instance']) } # Trigger an action execution. trigger_type = TriggerType.add_or_update( TriggerTypeAPI.to_model(TriggerTypeAPI(**docs['trigger_type']))) trigger = Trigger.add_or_update( TriggerAPI.to_model(TriggerAPI(**docs['trigger']))) rule = RuleAPI.to_model(RuleAPI(**docs['rule'])) rule.trigger = reference.get_str_resource_ref_from_model(trigger) rule = Rule.add_or_update(rule) trigger_instance = TriggerInstance.add_or_update( TriggerInstanceAPI.to_model( TriggerInstanceAPI(**docs['trigger_instance']))) enforcer = RuleEnforcer(trigger_instance, rule) enforcer.enforce() # Wait for the action execution to complete and then confirm outcome. liveaction = LiveAction.get( context__trigger_instance__id=str(trigger_instance.id)) self.assertIsNotNone(liveaction) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(liveaction.status, LIVEACTION_STATUS_FAILED) execution = self._get_action_execution(liveaction__id=str( liveaction.id), raise_exception=True) self.assertDictEqual(execution.trigger, vars(TriggerAPI.from_model(trigger))) self.assertDictEqual(execution.trigger_type, vars(TriggerTypeAPI.from_model(trigger_type))) self.assertDictEqual( execution.trigger_instance, vars(TriggerInstanceAPI.from_model(trigger_instance))) self.assertDictEqual(execution.rule, vars(RuleAPI.from_model(rule))) action = action_utils.get_action_by_ref(liveaction.action) self.assertDictEqual(execution.action, vars(ActionAPI.from_model(action))) runner = RunnerType.get_by_name(action.runner_type['name']) self.assertDictEqual(execution.runner, vars(RunnerTypeAPI.from_model(runner))) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(execution.start_timestamp, liveaction.start_timestamp) self.assertEqual(execution.end_timestamp, liveaction.end_timestamp) self.assertEqual(execution.result, liveaction.result) self.assertEqual(execution.status, liveaction.status) self.assertEqual(execution.context, liveaction.context) self.assertEqual(execution.liveaction['callback'], liveaction.callback) self.assertEqual(execution.liveaction['action'], liveaction.action)
def test_triggered_execution(self): docs = { 'trigger_type': copy.deepcopy(fixture.ARTIFACTS['trigger_type']), 'trigger': copy.deepcopy(fixture.ARTIFACTS['trigger']), 'rule': copy.deepcopy(fixture.ARTIFACTS['rule']), 'trigger_instance': copy.deepcopy(fixture.ARTIFACTS['trigger_instance']) } # Trigger an action execution. trigger_type = TriggerType.add_or_update( TriggerTypeAPI.to_model(TriggerTypeAPI(**docs['trigger_type']))) trigger = Trigger.add_or_update( TriggerAPI.to_model(TriggerAPI(**docs['trigger']))) rule = RuleAPI.to_model(RuleAPI(**docs['rule'])) rule.trigger = reference.get_str_resource_ref_from_model(trigger) rule = Rule.add_or_update(rule) trigger_instance = TriggerInstance.add_or_update( TriggerInstanceAPI.to_model( TriggerInstanceAPI(**docs['trigger_instance']))) enforcer = RuleEnforcer(trigger_instance, rule) enforcer.enforce() # Wait for the action execution to complete and then confirm outcome. execution = ActionExecution.get( context__trigger_instance__id=str(trigger_instance.id)) self.assertIsNotNone(execution) execution = ActionExecution.get_by_id(str(execution.id)) self.assertEqual(execution.status, ACTIONEXEC_STATUS_SUCCEEDED) history = ActionExecutionHistory.get(execution__id=str(execution.id), raise_exception=True) self.assertDictEqual(history.trigger, vars(TriggerAPI.from_model(trigger))) self.assertDictEqual(history.trigger_type, vars(TriggerTypeAPI.from_model(trigger_type))) self.assertDictEqual( history.trigger_instance, vars(TriggerInstanceAPI.from_model(trigger_instance))) self.assertDictEqual(history.rule, vars(RuleAPI.from_model(rule))) action_ref = ResourceReference.from_string_reference( ref=execution.action) action, _ = action_utils.get_action_by_dict({ 'name': action_ref.name, 'pack': action_ref.pack }) self.assertDictEqual(history.action, vars(ActionAPI.from_model(action))) runner = RunnerType.get_by_name(action.runner_type['name']) self.assertDictEqual(history.runner, vars(RunnerTypeAPI.from_model(runner))) execution = ActionExecution.get_by_id(str(execution.id)) self.assertDictEqual(history.execution, vars(ActionExecutionAPI.from_model(execution)))
def post(self, action): """ Create a new action. Handles requests: POST /actions/ """ LOG.info('POST /actions/ with action data=%s', action) if not hasattr(action, 'enabled'): LOG.debug( 'POST /actions/ incoming action data has enabled field unset. ' 'Defaulting enabled to True.') setattr(action, 'enabled', True) else: action.enabled = bool(action.enabled) if not hasattr(action, 'pack'): setattr(action, 'pack', DEFAULT_PACK_NAME) try: action_validator.validate_action(action) except ValueValidationException as e: abort(http_client.BAD_REQUEST, str(e)) return # ActionsController._validate_action_parameters(action, runnertype_db) action_model = ActionAPI.to_model(action) LOG.debug('/actions/ POST verified ActionAPI object=%s', action) try: action_db = Action.add_or_update(action_model) except StackStormDBObjectConflictError as e: # If an existing DB object conflicts with new object then raise error. LOG.warn( '/actions/ POST unable to save ActionDB object "%s" due to uniqueness ' 'conflict. %s', action_model, str(e)) abort(http_client.CONFLICT, str(e), body={'conflict-id': e.conflict_id}) return except Exception as e: LOG.exception( '/actions/ POST unable to save ActionDB object "%s". %s', action_model, e) abort(http_client.INTERNAL_SERVER_ERROR, str(e)) return LOG.debug('/actions/ POST saved ActionDB object=%s', action_db) LOG.audit('Action created. Action=%s', action_db) action_api = ActionAPI.from_model(action_db) LOG.debug('POST /actions/ client_result=%s', action_api) return action_api
def create_execution_object(liveaction, action_db=None, runnertype_db=None, publish=True): if not action_db: action_db = action_utils.get_action_by_ref(liveaction.action) if not runnertype_db: runnertype_db = RunnerType.get_by_name(action_db.runner_type['name']) attrs = { 'action': vars(ActionAPI.from_model(action_db)), 'parameters': liveaction['parameters'], 'runner': vars(RunnerTypeAPI.from_model(runnertype_db)) } attrs.update(_decompose_liveaction(liveaction)) if 'rule' in liveaction.context: rule = reference.get_model_from_ref(Rule, liveaction.context.get('rule', {})) attrs['rule'] = vars(RuleAPI.from_model(rule)) if 'trigger_instance' in liveaction.context: trigger_instance_id = liveaction.context.get('trigger_instance', {}) trigger_instance_id = trigger_instance_id.get('id', None) trigger_instance = TriggerInstance.get_by_id(trigger_instance_id) trigger = reference.get_model_by_resource_ref(db_api=Trigger, ref=trigger_instance.trigger) trigger_type = reference.get_model_by_resource_ref(db_api=TriggerType, ref=trigger.type) trigger_instance = reference.get_model_from_ref( TriggerInstance, liveaction.context.get('trigger_instance', {})) attrs['trigger_instance'] = vars(TriggerInstanceAPI.from_model(trigger_instance)) attrs['trigger'] = vars(TriggerAPI.from_model(trigger)) attrs['trigger_type'] = vars(TriggerTypeAPI.from_model(trigger_type)) parent = _get_parent_execution(liveaction) if parent: attrs['parent'] = str(parent.id) attrs['log'] = [_create_execution_log_entry(liveaction['status'])] # TODO: This object initialization takes 20-30or so ms execution = ActionExecutionDB(**attrs) # TODO: Do 100% research this is fully safe and unique in distributed setups execution.id = ObjectId() execution.web_url = _get_web_url_for_execution(str(execution.id)) # NOTE: User input data is already validate as part of the API request, # other data is set by us. Skipping validation here makes operation 10%-30% faster execution = ActionExecution.add_or_update(execution, publish=publish, validate=False) if parent and str(execution.id) not in parent.children: values = {} values['push__children'] = str(execution.id) ActionExecution.update(parent, **values) return execution
def get_all(self, **kwargs): """ List all actions. Handles requests: GET /actions/views/overview """ LOG.info('GET all /actions/views/overview with filters=%s', kwargs) kwargs = self._get_filters(**kwargs) action_dbs = Action.get_all(**kwargs) action_apis = [ActionAPI.from_model(action_db) for action_db in action_dbs] return map(self._transform_action_api, action_apis)
def get_one(self, action_id): """ List action by id. Handle: GET /actions/views/overview/1 """ LOG.info('GET /actions/views/overview with id=%s', action_id) action_db = LookupUtils._get_action_by_id(action_id) action_api = ActionAPI.from_model(action_db) return self._transform_action_api(action_api)
def get_one(self, action_id): """ List action by id. Handle: GET /actions/views/overview/1 """ LOG.info('GET /actions/views/overview with id=%s', action_id) action_db = LookupUtils._get_action_by_id(action_id) action_api = ActionAPI.from_model(action_db) return self._transform_action_api(action_api)
def post(self, action, requester_user): """ Create a new action. Handles requests: POST /actions/ """ permission_type = PermissionType.ACTION_CREATE rbac_utils = get_rbac_backend().get_utils_class() rbac_utils.assert_user_has_resource_api_permission( user_db=requester_user, resource_api=action, permission_type=permission_type) try: # Perform validation validate_not_part_of_system_pack(action) action_validator.validate_action(action) except ( ValidationError, ValueError, ValueValidationException, InvalidActionParameterException, ) as e: LOG.exception("Unable to create action data=%s", action) abort(http_client.BAD_REQUEST, six.text_type(e)) return # Write pack data files to disk (if any are provided) data_files = getattr(action, "data_files", []) written_data_files = [] if data_files: written_data_files = self._handle_data_files(pack_ref=action.pack, data_files=data_files) action_model = ActionAPI.to_model(action) LOG.debug("/actions/ POST verified ActionAPI object=%s", action) action_db = Action.add_or_update(action_model) LOG.debug("/actions/ POST saved ActionDB object=%s", action_db) # Dispatch an internal trigger for each written data file. This way user # automate comitting this files to git using StackStorm rule if written_data_files: self._dispatch_trigger_for_written_data_files( action_db=action_db, written_data_files=written_data_files) extra = {"acion_db": action_db} LOG.audit("Action created. Action.id=%s" % (action_db.id), extra=extra) action_api = ActionAPI.from_model(action_db) return Response(json=action_api, status=http_client.CREATED)
def create_execution_object(liveaction, publish=True): action_db = action_utils.get_action_by_ref(liveaction.action) runner = RunnerType.get_by_name(action_db.runner_type['name']) attrs = { 'action': vars(ActionAPI.from_model(action_db)), 'parameters': liveaction['parameters'], 'runner': vars(RunnerTypeAPI.from_model(runner)) } attrs.update(_decompose_liveaction(liveaction)) if 'rule' in liveaction.context: rule = reference.get_model_from_ref(Rule, liveaction.context.get('rule', {})) attrs['rule'] = vars(RuleAPI.from_model(rule)) if 'trigger_instance' in liveaction.context: trigger_instance_id = liveaction.context.get('trigger_instance', {}) trigger_instance_id = trigger_instance_id.get('id', None) trigger_instance = TriggerInstance.get_by_id(trigger_instance_id) trigger = reference.get_model_by_resource_ref( db_api=Trigger, ref=trigger_instance.trigger) trigger_type = reference.get_model_by_resource_ref(db_api=TriggerType, ref=trigger.type) trigger_instance = reference.get_model_from_ref( TriggerInstance, liveaction.context.get('trigger_instance', {})) attrs['trigger_instance'] = vars( TriggerInstanceAPI.from_model(trigger_instance)) attrs['trigger'] = vars(TriggerAPI.from_model(trigger)) attrs['trigger_type'] = vars(TriggerTypeAPI.from_model(trigger_type)) parent = _get_parent_execution(liveaction) if parent: attrs['parent'] = str(parent.id) attrs['log'] = [_create_execution_log_entry(liveaction['status'])] execution = ActionExecutionDB(**attrs) execution = ActionExecution.add_or_update(execution, publish=False) # Update the web_url field in execution. Unfortunately, we need # the execution id for constructing the URL which we only get # after the model is written to disk. execution.web_url = _get_web_url_for_execution(str(execution.id)) execution = ActionExecution.add_or_update(execution, publish=publish) if parent: if str(execution.id) not in parent.children: parent.children.append(str(execution.id)) ActionExecution.add_or_update(parent) return execution
def post(self, action): """ Create a new action. Handles requests: POST /actions/ """ LOG.info('POST /actions/ with action data=%s', action) if not hasattr(action, 'enabled'): LOG.debug('POST /actions/ incoming action data has enabled field unset. ' 'Defaulting enabled to True.') setattr(action, 'enabled', True) else: action.enabled = bool(action.enabled) if not hasattr(action, 'pack'): setattr(action, 'pack', 'default') try: action_validator.validate_action(action) except ValueValidationException as e: abort(http_client.BAD_REQUEST, str(e)) return # ActionsController._validate_action_parameters(action, runnertype_db) action_model = ActionAPI.to_model(action) LOG.debug('/actions/ POST verified ActionAPI object=%s', action) try: action_db = Action.add_or_update(action_model) except NotUniqueError as e: # If an existing DB object conflicts with new object then raise error. LOG.warn('/actions/ POST unable to save ActionDB object "%s" due to uniqueness ' 'conflict. %s', action_model, str(e)) abort(http_client.CONFLICT, str(e)) return except Exception as e: LOG.exception('/actions/ POST unable to save ActionDB object "%s". %s', action_model, e) abort(http_client.INTERNAL_SERVER_ERROR, str(e)) return LOG.debug('/actions/ POST saved ActionDB object=%s', action_db) LOG.audit('Action created. Action=%s', action_db) action_api = ActionAPI.from_model(action_db) LOG.debug('POST /actions/ client_result=%s', action_api) return action_api
def get_all(self, **kwargs): """ List all actions. Handles requests: GET /actions/views/overview """ LOG.info('GET all /actions/views/overview with filters=%s', kwargs) kwargs = self._get_filters(**kwargs) action_dbs = Action.get_all(**kwargs) action_apis = [ ActionAPI.from_model(action_db) for action_db in action_dbs ] return map(self._transform_action_api, action_apis)
def create_execution_object(liveaction, publish=True): action_db = action_utils.get_action_by_ref(liveaction.action) runner = RunnerType.get_by_name(action_db.runner_type['name']) attrs = { 'action': vars(ActionAPI.from_model(action_db)), 'parameters': liveaction['parameters'], 'runner': vars(RunnerTypeAPI.from_model(runner)) } attrs.update(_decompose_liveaction(liveaction)) if 'rule' in liveaction.context: rule = reference.get_model_from_ref(Rule, liveaction.context.get('rule', {})) attrs['rule'] = vars(RuleAPI.from_model(rule)) if 'trigger_instance' in liveaction.context: trigger_instance_id = liveaction.context.get('trigger_instance', {}) trigger_instance_id = trigger_instance_id.get('id', None) trigger_instance = TriggerInstance.get_by_id(trigger_instance_id) trigger = reference.get_model_by_resource_ref(db_api=Trigger, ref=trigger_instance.trigger) trigger_type = reference.get_model_by_resource_ref(db_api=TriggerType, ref=trigger.type) trigger_instance = reference.get_model_from_ref( TriggerInstance, liveaction.context.get('trigger_instance', {})) attrs['trigger_instance'] = vars(TriggerInstanceAPI.from_model(trigger_instance)) attrs['trigger'] = vars(TriggerAPI.from_model(trigger)) attrs['trigger_type'] = vars(TriggerTypeAPI.from_model(trigger_type)) parent = _get_parent_execution(liveaction) if parent: attrs['parent'] = str(parent.id) attrs['log'] = [_create_execution_log_entry(liveaction['status'])] execution = ActionExecutionDB(**attrs) execution = ActionExecution.add_or_update(execution, publish=False) # Update the web_url field in execution. Unfortunately, we need # the execution id for constructing the URL which we only get # after the model is written to disk. execution.web_url = _get_web_url_for_execution(str(execution.id)) execution = ActionExecution.add_or_update(execution, publish=publish) if parent: if str(execution.id) not in parent.children: parent.children.append(str(execution.id)) ActionExecution.add_or_update(parent) return execution
def put(self, action, ref_or_id, requester_user): action_db = self._get_by_ref_or_id(ref_or_id=ref_or_id) # Assert permissions permission_type = PermissionType.ACTION_MODIFY rbac_utils = get_rbac_backend().get_utils_class() rbac_utils.assert_user_has_resource_db_permission( user_db=requester_user, resource_db=action_db, permission_type=permission_type, ) action_id = action_db.id if not getattr(action, "pack", None): action.pack = action_db.pack # Perform validation validate_not_part_of_system_pack(action) action_validator.validate_action(action) # Write pack data files to disk (if any are provided) data_files = getattr(action, "data_files", []) written_data_files = [] if data_files: written_data_files = self._handle_data_files(pack_ref=action.pack, data_files=data_files) try: action_db = ActionAPI.to_model(action) LOG.debug("/actions/ PUT incoming action: %s", action_db) action_db.id = action_id action_db = Action.add_or_update(action_db) LOG.debug("/actions/ PUT after add_or_update: %s", action_db) except (ValidationError, ValueError) as e: LOG.exception("Unable to update action data=%s", action) abort(http_client.BAD_REQUEST, six.text_type(e)) return # Dispatch an internal trigger for each written data file. This way user # automate committing this files to git using StackStorm rule if written_data_files: self._dispatch_trigger_for_written_data_files( action_db=action_db, written_data_files=written_data_files) action_api = ActionAPI.from_model(action_db) LOG.debug("PUT /actions/ client_result=%s", action_api) return action_api
def test_triggered_execution(self): docs = { 'trigger_type': copy.deepcopy(fixture.ARTIFACTS['trigger_type']), 'trigger': copy.deepcopy(fixture.ARTIFACTS['trigger']), 'rule': copy.deepcopy(fixture.ARTIFACTS['rule']), 'trigger_instance': copy.deepcopy(fixture.ARTIFACTS['trigger_instance'])} # Trigger an action execution. trigger_type = TriggerType.add_or_update( TriggerTypeAPI.to_model(TriggerTypeAPI(**docs['trigger_type']))) trigger = Trigger.add_or_update(TriggerAPI.to_model(TriggerAPI(**docs['trigger']))) rule = RuleAPI.to_model(RuleAPI(**docs['rule'])) rule.trigger = reference.get_str_resource_ref_from_model(trigger) rule = Rule.add_or_update(rule) trigger_instance = TriggerInstance.add_or_update( TriggerInstanceAPI.to_model(TriggerInstanceAPI(**docs['trigger_instance']))) trace_service.add_or_update_given_trace_context( trace_context={'trace_tag': 'test_triggered_execution_trace'}, trigger_instances=[str(trigger_instance.id)]) enforcer = RuleEnforcer(trigger_instance, rule) enforcer.enforce() # Wait for the action execution to complete and then confirm outcome. liveaction = LiveAction.get(context__trigger_instance__id=str(trigger_instance.id)) self.assertIsNotNone(liveaction) liveaction = self._wait_on_status(liveaction, action_constants.LIVEACTION_STATUS_FAILED) execution = self._get_action_execution( liveaction__id=str(liveaction.id), raise_exception=True ) self.assertDictEqual(execution.trigger, vars(TriggerAPI.from_model(trigger))) self.assertDictEqual(execution.trigger_type, vars(TriggerTypeAPI.from_model(trigger_type))) self.assertDictEqual(execution.trigger_instance, vars(TriggerInstanceAPI.from_model(trigger_instance))) self.assertDictEqual(execution.rule, vars(RuleAPI.from_model(rule))) action = action_utils.get_action_by_ref(liveaction.action) self.assertDictEqual(execution.action, vars(ActionAPI.from_model(action))) runner = RunnerType.get_by_name(action.runner_type['name']) self.assertDictEqual(execution.runner, vars(RunnerTypeAPI.from_model(runner))) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(execution.start_timestamp, liveaction.start_timestamp) self.assertEqual(execution.end_timestamp, liveaction.end_timestamp) self.assertEqual(execution.result, liveaction.result) self.assertEqual(execution.status, liveaction.status) self.assertEqual(execution.context, liveaction.context) self.assertEqual(execution.liveaction['callback'], liveaction.callback) self.assertEqual(execution.liveaction['action'], liveaction.action)
def test_execution_creation_manual_action_run(self): liveaction = self.MODELS['liveactions']['liveaction1.yaml'] executions_util.create_execution_object(liveaction) execution = self._get_action_execution(liveaction__id=str(liveaction.id), raise_exception=True) self.assertDictEqual(execution.trigger, {}) self.assertDictEqual(execution.trigger_type, {}) self.assertDictEqual(execution.trigger_instance, {}) self.assertDictEqual(execution.rule, {}) action = action_utils.get_action_by_ref('core.local') self.assertDictEqual(execution.action, vars(ActionAPI.from_model(action))) runner = RunnerType.get_by_name(action.runner_type['name']) self.assertDictEqual(execution.runner, vars(RunnerTypeAPI.from_model(runner))) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEquals(execution.liveaction['id'], str(liveaction.id))
def post(self, action, requester_user): """ Create a new action. Handles requests: POST /actions/ """ permission_type = PermissionType.ACTION_CREATE rbac_utils = get_rbac_backend().get_utils_class() rbac_utils.assert_user_has_resource_api_permission(user_db=requester_user, resource_api=action, permission_type=permission_type) try: # Perform validation validate_not_part_of_system_pack(action) action_validator.validate_action(action) except (ValidationError, ValueError, ValueValidationException, InvalidActionParameterException) as e: LOG.exception('Unable to create action data=%s', action) abort(http_client.BAD_REQUEST, six.text_type(e)) return # Write pack data files to disk (if any are provided) data_files = getattr(action, 'data_files', []) written_data_files = [] if data_files: written_data_files = self._handle_data_files(pack_ref=action.pack, data_files=data_files) action_model = ActionAPI.to_model(action) LOG.debug('/actions/ POST verified ActionAPI object=%s', action) action_db = Action.add_or_update(action_model) LOG.debug('/actions/ POST saved ActionDB object=%s', action_db) # Dispatch an internal trigger for each written data file. This way user # automate comitting this files to git using StackStorm rule if written_data_files: self._dispatch_trigger_for_written_data_files(action_db=action_db, written_data_files=written_data_files) extra = {'acion_db': action_db} LOG.audit('Action created. Action.id=%s' % (action_db.id), extra=extra) action_api = ActionAPI.from_model(action_db) return Response(json=action_api, status=http_client.CREATED)
def record_action_execution(self, body): try: history_id = bson.ObjectId() execution = ActionExecution.get_by_id(str(body.id)) action_ref = ResourceReference.from_string_reference(ref=execution.action) action_db, _ = action_utils.get_action_by_dict( {'name': action_ref.name, 'pack': action_ref.pack}) runner = RunnerType.get_by_name(action_db.runner_type['name']) attrs = { 'id': history_id, 'action': vars(ActionAPI.from_model(action_db)), 'runner': vars(RunnerTypeAPI.from_model(runner)), 'execution': vars(ActionExecutionAPI.from_model(execution)) } if 'rule' in execution.context: rule = reference.get_model_from_ref(Rule, execution.context.get('rule', {})) attrs['rule'] = vars(RuleAPI.from_model(rule)) if 'trigger_instance' in execution.context: trigger_instance_id = execution.context.get('trigger_instance', {}) trigger_instance_id = trigger_instance_id.get('id', None) trigger_instance = TriggerInstance.get_by_id(trigger_instance_id) trigger = reference.get_model_by_resource_ref(db_api=Trigger, ref=trigger_instance.trigger) trigger_type = reference.get_model_by_resource_ref(db_api=TriggerType, ref=trigger.type) trigger_instance = reference.get_model_from_ref( TriggerInstance, execution.context.get('trigger_instance', {})) attrs['trigger_instance'] = vars(TriggerInstanceAPI.from_model(trigger_instance)) attrs['trigger'] = vars(TriggerAPI.from_model(trigger)) attrs['trigger_type'] = vars(TriggerTypeAPI.from_model(trigger_type)) parent = ActionExecutionHistory.get(execution__id=execution.context.get('parent', '')) if parent: attrs['parent'] = str(parent.id) if str(history_id) not in parent.children: parent.children.append(str(history_id)) ActionExecutionHistory.add_or_update(parent) history = ActionExecutionHistoryDB(**attrs) history = ActionExecutionHistory.add_or_update(history) except: LOG.exception('An unexpected error occurred while creating the ' 'action execution history.') raise
def put(self, action, ref_or_id, requester_user): action_db = self._get_by_ref_or_id(ref_or_id=ref_or_id) # Assert permissions permission_type = PermissionType.ACTION_MODIFY rbac_utils = get_rbac_backend().get_utils_class() rbac_utils.assert_user_has_resource_db_permission(user_db=requester_user, resource_db=action_db, permission_type=permission_type) action_id = action_db.id if not getattr(action, 'pack', None): action.pack = action_db.pack # Perform validation validate_not_part_of_system_pack(action) action_validator.validate_action(action) # Write pack data files to disk (if any are provided) data_files = getattr(action, 'data_files', []) written_data_files = [] if data_files: written_data_files = self._handle_data_files(pack_ref=action.pack, data_files=data_files) try: action_db = ActionAPI.to_model(action) LOG.debug('/actions/ PUT incoming action: %s', action_db) action_db.id = action_id action_db = Action.add_or_update(action_db) LOG.debug('/actions/ PUT after add_or_update: %s', action_db) except (ValidationError, ValueError) as e: LOG.exception('Unable to update action data=%s', action) abort(http_client.BAD_REQUEST, six.text_type(e)) return # Dispatch an internal trigger for each written data file. This way user # automate committing this files to git using StackStorm rule if written_data_files: self._dispatch_trigger_for_written_data_files(action_db=action_db, written_data_files=written_data_files) action_api = ActionAPI.from_model(action_db) LOG.debug('PUT /actions/ client_result=%s', action_api) return action_api
def test_basic_execution(self): action_ref = ResourceReference(name='local', pack='core') execution = ActionExecutionDB(action=action_ref.ref, parameters={'cmd': 'uname -a'}) execution = action_service.schedule(execution) execution = ActionExecution.get_by_id(str(execution.id)) self.assertEqual(execution.status, ACTIONEXEC_STATUS_SUCCEEDED) history = ActionExecutionHistory.get(execution__id=str(execution.id), raise_exception=True) self.assertDictEqual(history.trigger, {}) self.assertDictEqual(history.trigger_type, {}) self.assertDictEqual(history.trigger_instance, {}) self.assertDictEqual(history.rule, {}) action, _ = action_utils.get_action_by_dict( {'name': action_ref.name, 'pack': action_ref.pack}) self.assertDictEqual(history.action, vars(ActionAPI.from_model(action))) runner = RunnerType.get_by_name(action.runner_type['name']) self.assertDictEqual(history.runner, vars(RunnerTypeAPI.from_model(runner))) execution = ActionExecution.get_by_id(str(execution.id)) self.assertDictEqual(history.execution, vars(ActionExecutionAPI.from_model(execution)))
def create_execution_object(liveaction, publish=True): action_db = action_utils.get_action_by_ref(liveaction.action) runner = RunnerType.get_by_name(action_db.runner_type['name']) attrs = { 'action': vars(ActionAPI.from_model(action_db)), 'runner': vars(RunnerTypeAPI.from_model(runner)) } attrs.update(_decompose_liveaction(liveaction)) if 'rule' in liveaction.context: rule = reference.get_model_from_ref(Rule, liveaction.context.get('rule', {})) attrs['rule'] = vars(RuleAPI.from_model(rule)) if 'trigger_instance' in liveaction.context: trigger_instance_id = liveaction.context.get('trigger_instance', {}) trigger_instance_id = trigger_instance_id.get('id', None) trigger_instance = TriggerInstance.get_by_id(trigger_instance_id) trigger = reference.get_model_by_resource_ref( db_api=Trigger, ref=trigger_instance.trigger) trigger_type = reference.get_model_by_resource_ref(db_api=TriggerType, ref=trigger.type) trigger_instance = reference.get_model_from_ref( TriggerInstance, liveaction.context.get('trigger_instance', {})) attrs['trigger_instance'] = vars( TriggerInstanceAPI.from_model(trigger_instance)) attrs['trigger'] = vars(TriggerAPI.from_model(trigger)) attrs['trigger_type'] = vars(TriggerTypeAPI.from_model(trigger_type)) parent = ActionExecution.get( liveaction__id=liveaction.context.get('parent', '')) if parent: attrs['parent'] = str(parent.id) execution = ActionExecutionDB(**attrs) execution = ActionExecution.add_or_update(execution, publish=publish) if parent: if str(execution.id) not in parent.children: parent.children.append(str(execution.id)) ActionExecution.add_or_update(parent) return execution
def create_execution_object(liveaction, publish=True): action_db = action_utils.get_action_by_ref(liveaction.action) runner = RunnerType.get_by_name(action_db.runner_type['name']) attrs = { 'action': vars(ActionAPI.from_model(action_db)), 'parameters': liveaction['parameters'], 'runner': vars(RunnerTypeAPI.from_model(runner)) } attrs.update(_decompose_liveaction(liveaction)) if 'rule' in liveaction.context: rule = reference.get_model_from_ref(Rule, liveaction.context.get('rule', {})) attrs['rule'] = vars(RuleAPI.from_model(rule)) if 'trigger_instance' in liveaction.context: trigger_instance_id = liveaction.context.get('trigger_instance', {}) trigger_instance_id = trigger_instance_id.get('id', None) trigger_instance = TriggerInstance.get_by_id(trigger_instance_id) trigger = reference.get_model_by_resource_ref(db_api=Trigger, ref=trigger_instance.trigger) trigger_type = reference.get_model_by_resource_ref(db_api=TriggerType, ref=trigger.type) trigger_instance = reference.get_model_from_ref( TriggerInstance, liveaction.context.get('trigger_instance', {})) attrs['trigger_instance'] = vars(TriggerInstanceAPI.from_model(trigger_instance)) attrs['trigger'] = vars(TriggerAPI.from_model(trigger)) attrs['trigger_type'] = vars(TriggerTypeAPI.from_model(trigger_type)) parent = _get_parent_execution(liveaction) if parent: attrs['parent'] = str(parent.id) execution = ActionExecutionDB(**attrs) execution = ActionExecution.add_or_update(execution, publish=publish) if parent: if str(execution.id) not in parent.children: parent.children.append(str(execution.id)) ActionExecution.add_or_update(parent) return execution
def test_chained_executions(self): action_ref = ResourceReference(name='chain', pack='core') execution = ActionExecutionDB(action=action_ref.ref) execution = action_service.schedule(execution) execution = ActionExecution.get_by_id(str(execution.id)) self.assertEqual(execution.status, ACTIONEXEC_STATUS_SUCCEEDED) history = ActionExecutionHistory.get(execution__id=str(execution.id), raise_exception=True) action, _ = action_utils.get_action_by_dict( {'name': action_ref.name, 'pack': action_ref.pack}) self.assertDictEqual(history.action, vars(ActionAPI.from_model(action))) runner = RunnerType.get_by_name(action.runner_type['name']) self.assertDictEqual(history.runner, vars(RunnerTypeAPI.from_model(runner))) execution = ActionExecution.get_by_id(str(execution.id)) self.assertDictEqual(history.execution, vars(ActionExecutionAPI.from_model(execution))) self.assertGreater(len(history.children), 0) for child in history.children: record = ActionExecutionHistory.get(id=child, raise_exception=True) self.assertEqual(record.parent, str(history.id)) self.assertEqual(record.action['name'], 'local') self.assertEqual(record.runner['name'], 'run-local')
def post(self, action): """ Create a new action. Handles requests: POST /actions/ """ if not hasattr(action, 'pack'): setattr(action, 'pack', DEFAULT_PACK_NAME) try: action_validator.validate_action(action) except ValueValidationException as e: abort(http_client.BAD_REQUEST, str(e)) return # ActionsController._validate_action_parameters(action, runnertype_db) action_model = ActionAPI.to_model(action) LOG.debug('/actions/ POST verified ActionAPI object=%s', action) try: action_db = Action.add_or_update(action_model) except StackStormDBObjectConflictError as e: # If an existing DB object conflicts with new object then raise error. LOG.warn('/actions/ POST unable to save ActionDB object "%s" due to uniqueness ' 'conflict. %s', action_model, str(e)) abort(http_client.CONFLICT, str(e), body={'conflict-id': e.conflict_id}) return except Exception as e: LOG.exception('/actions/ POST unable to save ActionDB object "%s". %s', action_model, e) abort(http_client.INTERNAL_SERVER_ERROR, str(e)) return LOG.debug('/actions/ POST saved ActionDB object=%s', action_db) extra = {'action_db': action_db} LOG.audit('Action created. Action.id=%s' % (action_db.id), extra=extra) action_api = ActionAPI.from_model(action_db) return action_api
def put(self, action_ref_or_id, action): action_db = self._get_by_ref_or_id(ref_or_id=action_ref_or_id) # Assert permissions action_id = action_db.id if not getattr(action, 'pack', None): action.pack = action_db.pack # Perform validation validate_not_part_of_system_pack(action) action_validator.validate_action(action) # Write pack data files to disk (if any are provided) data_files = getattr(action, 'data_files', []) written_data_files = [] if data_files: written_data_files = self._handle_data_files(pack_name=action.pack, data_files=data_files) try: action_db = ActionAPI.to_model(action) LOG.debug('/actions/ PUT incoming action: %s', action_db) action_db.id = action_id action_db = Action.add_or_update(action_db) LOG.debug('/actions/ PUT after add_or_update: %s', action_db) except (ValidationError, ValueError) as e: LOG.exception('Unable to update action data=%s', action) abort(http_client.BAD_REQUEST, str(e)) return # Dispatch an internal trigger for each written data file. This way user # automate comitting this files to git using StackStorm rule if written_data_files: self._dispatch_trigger_for_written_data_files( action_db=action_db, written_data_files=written_data_files) action_api = ActionAPI.from_model(action_db) LOG.debug('PUT /actions/ client_result=%s', action_api) return action_api
def test_basic_execution(self): execution = ActionExecutionDB(action='core.local', parameters={'cmd': 'uname -a'}) execution = action_service.schedule(execution) execution = ActionExecution.get_by_id(str(execution.id)) self.assertEqual(execution.status, ACTIONEXEC_STATUS_FAILED) history = ActionExecutionHistory.get(execution__id=str(execution.id), raise_exception=True) self.assertDictEqual(history.trigger, {}) self.assertDictEqual(history.trigger_type, {}) self.assertDictEqual(history.trigger_instance, {}) self.assertDictEqual(history.rule, {}) action = action_utils.get_action_by_ref('core.local') self.assertDictEqual(history.action, vars(ActionAPI.from_model(action))) runner = RunnerType.get_by_name(action.runner_type['name']) self.assertDictEqual(history.runner, vars(RunnerTypeAPI.from_model(runner))) execution = ActionExecution.get_by_id(str(execution.id)) self.assertDictEqual(history.execution, vars(ActionExecutionAPI.from_model(execution)))
def test_execution_creation_manual_action_run(self): liveaction = self.MODELS['liveactions']['liveaction1.yaml'] pre_creation_timestamp = date_utils.get_datetime_utc_now() executions_util.create_execution_object(liveaction) post_creation_timestamp = date_utils.get_datetime_utc_now() execution = self._get_action_execution(liveaction__id=str(liveaction.id), raise_exception=True) self.assertDictEqual(execution.trigger, {}) self.assertDictEqual(execution.trigger_type, {}) self.assertDictEqual(execution.trigger_instance, {}) self.assertDictEqual(execution.rule, {}) action = action_utils.get_action_by_ref('core.local') self.assertDictEqual(execution.action, vars(ActionAPI.from_model(action))) runner = RunnerType.get_by_name(action.runner_type['name']) self.assertDictEqual(execution.runner, vars(RunnerTypeAPI.from_model(runner))) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEquals(execution.liveaction['id'], str(liveaction.id)) self.assertEquals(len(execution.log), 1) self.assertEquals(execution.log[0]['status'], liveaction.status) self.assertGreater(execution.log[0]['timestamp'], pre_creation_timestamp) self.assertLess(execution.log[0]['timestamp'], post_creation_timestamp)
def put(self, action_ref_or_id, action): action_db = self._get_by_ref_or_id(ref_or_id=action_ref_or_id) # Assert permissions action_id = action_db.id if not getattr(action, 'pack', None): action.pack = action_db.pack # Perform validation validate_not_part_of_system_pack(action) action_validator.validate_action(action) # Write pack data files to disk (if any are provided) data_files = getattr(action, 'data_files', []) written_data_files = [] if data_files: written_data_files = self._handle_data_files(pack_name=action.pack, data_files=data_files) try: action_db = ActionAPI.to_model(action) action_db.id = action_id action_db = Action.add_or_update(action_db) except (ValidationError, ValueError) as e: LOG.exception('Unable to update action data=%s', action) abort(http_client.BAD_REQUEST, str(e)) return # Dispatch an internal trigger for each written data file. This way user # automate comitting this files to git using StackStorm rule if written_data_files: self._dispatch_trigger_for_written_data_files(action_db=action_db, written_data_files=written_data_files) action_api = ActionAPI.from_model(action_db) LOG.debug('PUT /actions/ client_result=%s', action_api) return action_api
def test_triggered_execution(self): docs = { 'trigger_type': copy.deepcopy(fixture.ARTIFACTS['trigger_type']), 'trigger': copy.deepcopy(fixture.ARTIFACTS['trigger']), 'rule': copy.deepcopy(fixture.ARTIFACTS['rule']), 'trigger_instance': copy.deepcopy(fixture.ARTIFACTS['trigger_instance'])} # Trigger an action execution. trigger_type = TriggerType.add_or_update( TriggerTypeAPI.to_model(TriggerTypeAPI(**docs['trigger_type']))) trigger = Trigger.add_or_update(TriggerAPI.to_model(TriggerAPI(**docs['trigger']))) rule = RuleAPI.to_model(RuleAPI(**docs['rule'])) rule.trigger = reference.get_str_resource_ref_from_model(trigger) rule = Rule.add_or_update(rule) trigger_instance = TriggerInstance.add_or_update( TriggerInstanceAPI.to_model(TriggerInstanceAPI(**docs['trigger_instance']))) enforcer = RuleEnforcer(trigger_instance, rule) enforcer.enforce() # Wait for the action execution to complete and then confirm outcome. execution = ActionExecution.get(context__trigger_instance__id=str(trigger_instance.id)) self.assertIsNotNone(execution) execution = ActionExecution.get_by_id(str(execution.id)) self.assertEqual(execution.status, ACTIONEXEC_STATUS_SUCCEEDED) history = ActionExecutionHistory.get(execution__id=str(execution.id), raise_exception=True) self.assertDictEqual(history.trigger, vars(TriggerAPI.from_model(trigger))) self.assertDictEqual(history.trigger_type, vars(TriggerTypeAPI.from_model(trigger_type))) self.assertDictEqual(history.trigger_instance, vars(TriggerInstanceAPI.from_model(trigger_instance))) self.assertDictEqual(history.rule, vars(RuleAPI.from_model(rule))) action_ref = ResourceReference.from_string_reference(ref=execution.action) action, _ = action_utils.get_action_by_dict( {'name': action_ref.name, 'pack': action_ref.pack}) self.assertDictEqual(history.action, vars(ActionAPI.from_model(action))) runner = RunnerType.get_by_name(action.runner_type['name']) self.assertDictEqual(history.runner, vars(RunnerTypeAPI.from_model(runner))) execution = ActionExecution.get_by_id(str(execution.id)) self.assertDictEqual(history.execution, vars(ActionExecutionAPI.from_model(execution)))
def put(self, action_ref_or_id, action): try: action_db = self._get_by_ref_or_id(ref_or_id=action_ref_or_id) except Exception as e: LOG.exception(e.message) abort(http_client.NOT_FOUND, e.message) return action_id = action_db.id try: validate_not_part_of_system_pack(action_db) except ValueValidationException as e: abort(http_client.BAD_REQUEST, str(e)) if not getattr(action, 'pack', None): action.pack = action_db.pack try: action_validator.validate_action(action) except ValueValidationException as e: abort(http_client.BAD_REQUEST, str(e)) return try: action_db = ActionAPI.to_model(action) action_db.id = action_id action_db = Action.add_or_update(action_db) except (ValidationError, ValueError) as e: LOG.exception('Unable to update action data=%s', action) abort(http_client.BAD_REQUEST, str(e)) return action_api = ActionAPI.from_model(action_db) LOG.debug('PUT /actions/ client_result=%s', action_api) return action_api
def test_chained_executions(self): liveaction = LiveActionDB(action="executions.chain") liveaction, _ = action_service.request(liveaction) liveaction = self._wait_on_status( liveaction, action_constants.LIVEACTION_STATUS_FAILED) execution = self._get_action_execution(liveaction__id=str( liveaction.id), raise_exception=True) action = action_utils.get_action_by_ref("executions.chain") self.assertDictEqual(execution.action, vars(ActionAPI.from_model(action))) runner = RunnerType.get_by_name(action.runner_type["name"]) self.assertDictEqual(execution.runner, vars(RunnerTypeAPI.from_model(runner))) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(execution.start_timestamp, liveaction.start_timestamp) # NOTE: Timestamp of liveaction and execution may be a bit different, depending on how long # it takes to persist each object in the database self.assertEqual( execution.end_timestamp.replace(microsecond=0), liveaction.end_timestamp.replace(microsecond=0), ) self.assertEqual(execution.result, liveaction.result) self.assertEqual(execution.status, liveaction.status) self.assertEqual(execution.context, liveaction.context) self.assertEqual(execution.liveaction["callback"], liveaction.callback) self.assertEqual(execution.liveaction["action"], liveaction.action) self.assertGreater(len(execution.children), 0) for child in execution.children: record = ActionExecution.get(id=child, raise_exception=True) self.assertEqual(record.parent, str(execution.id)) self.assertEqual(record.action["name"], "local") self.assertEqual(record.runner["name"], "local-shell-cmd")
def test_triggered_execution(self): docs = { "trigger_type": copy.deepcopy(fixture.ARTIFACTS["trigger_type"]), "trigger": copy.deepcopy(fixture.ARTIFACTS["trigger"]), "rule": copy.deepcopy(fixture.ARTIFACTS["rule"]), "trigger_instance": copy.deepcopy(fixture.ARTIFACTS["trigger_instance"]), } # Trigger an action execution. trigger_type = TriggerType.add_or_update( TriggerTypeAPI.to_model(TriggerTypeAPI(**docs["trigger_type"]))) trigger = Trigger.add_or_update( TriggerAPI.to_model(TriggerAPI(**docs["trigger"]))) rule = RuleAPI.to_model(RuleAPI(**docs["rule"])) rule.trigger = reference.get_str_resource_ref_from_model(trigger) rule = Rule.add_or_update(rule) trigger_instance = TriggerInstance.add_or_update( TriggerInstanceAPI.to_model( TriggerInstanceAPI(**docs["trigger_instance"]))) trace_service.add_or_update_given_trace_context( trace_context={"trace_tag": "test_triggered_execution_trace"}, trigger_instances=[str(trigger_instance.id)], ) enforcer = RuleEnforcer(trigger_instance, rule) enforcer.enforce() # Wait for the action execution to complete and then confirm outcome. liveaction = LiveAction.get( context__trigger_instance__id=str(trigger_instance.id)) self.assertIsNotNone(liveaction) liveaction = self._wait_on_status( liveaction, action_constants.LIVEACTION_STATUS_FAILED) execution = self._get_action_execution(liveaction__id=str( liveaction.id), raise_exception=True) self.assertDictEqual(execution.trigger, vars(TriggerAPI.from_model(trigger))) self.assertDictEqual(execution.trigger_type, vars(TriggerTypeAPI.from_model(trigger_type))) self.assertDictEqual( execution.trigger_instance, vars(TriggerInstanceAPI.from_model(trigger_instance)), ) self.assertDictEqual(execution.rule, vars(RuleAPI.from_model(rule))) action = action_utils.get_action_by_ref(liveaction.action) self.assertDictEqual(execution.action, vars(ActionAPI.from_model(action))) runner = RunnerType.get_by_name(action.runner_type["name"]) self.assertDictEqual(execution.runner, vars(RunnerTypeAPI.from_model(runner))) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(execution.start_timestamp, liveaction.start_timestamp) # NOTE: Timestamp of liveaction and execution may be a bit different, depending on how long # it takes to persist each object in the database self.assertEqual( execution.end_timestamp.replace(microsecond=0), liveaction.end_timestamp.replace(microsecond=0), ) self.assertEqual(execution.result, liveaction.result) self.assertEqual(execution.status, liveaction.status) self.assertEqual(execution.context, liveaction.context) self.assertEqual(execution.liveaction["callback"], liveaction.callback) self.assertEqual(execution.liveaction["action"], liveaction.action)
def clone(self, dest_data, ref_or_id, requester_user): """ Clone an action from source pack to destination pack. Handles requests: POST /actions/{ref_or_id}/clone """ source_action_db = self._get_by_ref_or_id(ref_or_id=ref_or_id) if not source_action_db: msg = "The requested source for cloning operation doesn't exists" abort(http_client.BAD_REQUEST, six.text_type(msg)) extra = {"action_db": source_action_db} LOG.audit("Source action found. Action.id=%s" % (source_action_db.id), extra=extra) try: permission_type = PermissionType.ACTION_VIEW rbac_utils = get_rbac_backend().get_utils_class() rbac_utils.assert_user_has_resource_db_permission( user_db=requester_user, resource_db=source_action_db, permission_type=permission_type, ) except ResourceAccessDeniedError as e: abort(http_client.UNAUTHORIZED, six.text_type(e)) cloned_dest_action_db = clone_action_db( source_action_db=source_action_db, dest_pack=dest_data.dest_pack, dest_action=dest_data.dest_action, ) cloned_action_api = ActionAPI.from_model(cloned_dest_action_db) try: permission_type = PermissionType.ACTION_CREATE rbac_utils.assert_user_has_resource_api_permission( user_db=requester_user, resource_api=cloned_action_api, permission_type=permission_type, ) except ResourceAccessDeniedError as e: abort(http_client.UNAUTHORIZED, six.text_type(e)) dest_pack_base_path = get_pack_base_path(pack_name=dest_data.dest_pack) if not os.path.isdir(dest_pack_base_path): msg = "Destination pack '%s' doesn't exist" % (dest_data.dest_pack) abort(http_client.BAD_REQUEST, six.text_type(msg)) dest_pack_base_path = get_pack_base_path(pack_name=dest_data.dest_pack) dest_ref = ".".join([dest_data.dest_pack, dest_data.dest_action]) dest_action_db = self._get_by_ref(resource_ref=dest_ref) try: validate_not_part_of_system_pack_by_name(dest_data.dest_pack) except ValueValidationException as e: abort(http_client.BAD_REQUEST, six.text_type(e)) if dest_action_db: if not dest_data.overwrite: msg = "The requested destination action already exists" abort(http_client.BAD_REQUEST, six.text_type(msg)) try: permission_type = PermissionType.ACTION_DELETE rbac_utils.assert_user_has_resource_db_permission( user_db=requester_user, resource_db=dest_action_db, permission_type=permission_type, ) options = GenericRequestParam(remove_files=True) dest_metadata_file = dest_action_db["metadata_file"] dest_entry_point = dest_action_db["entry_point"] temp_sub_dir = str(uuid.uuid4()) temp_backup_action_files( dest_pack_base_path, dest_metadata_file, dest_entry_point, temp_sub_dir, ) self.delete(options, dest_ref, requester_user) except ResourceAccessDeniedError as e: abort(http_client.UNAUTHORIZED, six.text_type(e)) except Exception as e: LOG.debug( "Exception encountered during deleting existing destination action. " "Exception was: %s", e, ) abort(http_client.INTERNAL_SERVER_ERROR, six.text_type(e)) try: post_response = self.post(cloned_action_api, requester_user) if post_response.status_code != http_client.CREATED: raise Exception("Could not add cloned action to database.") cloned_dest_action_db["id"] = post_response.json["id"] clone_action_files( source_action_db=source_action_db, dest_action_db=cloned_dest_action_db, dest_pack_base_path=dest_pack_base_path, ) extra = {"cloned_acion_db": cloned_dest_action_db} LOG.audit("Action cloned. Action.id=%s" % (cloned_dest_action_db.id), extra=extra) if dest_action_db: remove_temp_action_files(temp_sub_dir) return post_response except PermissionError as e: LOG.error("No permission to clone the action. Exception was %s", e) delete_action_files_from_pack( pack_name=cloned_dest_action_db["pack"], entry_point=cloned_dest_action_db["entry_point"], metadata_file=cloned_dest_action_db["metadata_file"], ) if post_response.status_code == http_client.CREATED: Action.delete(cloned_dest_action_db) if dest_action_db: self._restore_action(dest_action_db, dest_pack_base_path, temp_sub_dir) abort(http_client.FORBIDDEN, six.text_type(e)) except Exception as e: LOG.error( "Exception encountered during cloning action. Exception was %s", e, ) delete_action_files_from_pack( pack_name=cloned_dest_action_db["pack"], entry_point=cloned_dest_action_db["entry_point"], metadata_file=cloned_dest_action_db["metadata_file"], ) if post_response.status_code == http_client.CREATED: Action.delete(cloned_dest_action_db) if dest_action_db: self._restore_action(dest_action_db, dest_pack_base_path, temp_sub_dir) abort(http_client.INTERNAL_SERVER_ERROR, six.text_type(e))
def test_user_has_resource_api_permission(self): resolver = ActionPermissionsResolver() # Admin user, should always return true user_db = self.users['admin'] resource_db = self.resources['action_1'] resource_api = ActionAPI.from_model(resource_db) self.assertUserHasResourceApiPermission( resolver=resolver, user_db=user_db, resource_api=resource_api, permission_type=PermissionType.ACTION_CREATE) # Observer, should return false user_db = self.users['observer'] resource_db = self.resources['action_1'] resource_api = ActionAPI.from_model(resource_db) self.assertUserDoesntHaveResourceApiPermission( resolver=resolver, user_db=user_db, resource_api=resource_api, permission_type=PermissionType.ACTION_CREATE) # No roles, should return false user_db = self.users['no_roles'] resource_db = self.resources['action_1'] resource_api = ActionAPI.from_model(resource_db) self.assertUserDoesntHaveResourceApiPermission( resolver=resolver, user_db=user_db, resource_api=resource_api, permission_type=PermissionType.ACTION_CREATE) # Custom role with no permission grants, should return false user_db = self.users['1_custom_role_no_permissions'] resource_db = self.resources['action_1'] resource_api = ActionAPI.from_model(resource_db) self.assertUserDoesntHaveResourceApiPermission( resolver=resolver, user_db=user_db, resource_api=resource_api, permission_type=PermissionType.ACTION_CREATE) # Custom role with "action_create" grant on parent pack user_db = self.users['action_pack_action_create_grant'] resource_db = self.resources['action_1'] resource_api = ActionAPI.from_model(resource_db) self.assertUserHasResourceApiPermission( resolver=resolver, user_db=user_db, resource_api=resource_api, permission_type=PermissionType.ACTION_CREATE) # Custom role with "action_all" grant on the parent pack user_db = self.users['action_pack_action_all_grant'] resource_db = self.resources['action_1'] resource_api = ActionAPI.from_model(resource_db) self.assertUserHasResourceApiPermission( resolver=resolver, user_db=user_db, resource_api=resource_api, permission_type=PermissionType.ACTION_CREATE) # Custom role with "action_create" grant directly on the resource user_db = self.users['action_action_create_grant'] resource_db = self.resources['action_1'] resource_api = ActionAPI.from_model(resource_db) self.assertUserHasResourceApiPermission( resolver=resolver, user_db=user_db, resource_api=resource_api, permission_type=PermissionType.ACTION_CREATE) # Custom role with "action_all" grant directly on the resource user_db = self.users['action_action_all_grant'] resource_db = self.resources['action_1'] resource_api = ActionAPI.from_model(resource_db) self.assertUserHasResourceApiPermission( resolver=resolver, user_db=user_db, resource_api=resource_api, permission_type=PermissionType.ACTION_CREATE)
def test_user_has_resource_api_permission(self): resolver = ActionPermissionsResolver() # Admin user, should always return true user_db = self.users['admin'] resource_db = self.resources['action_1'] resource_api = ActionAPI.from_model(resource_db) self.assertUserHasResourceApiPermission( resolver=resolver, user_db=user_db, resource_api=resource_api, permission_type=PermissionType.ACTION_CREATE) # Observer, should return false user_db = self.users['observer'] resource_db = self.resources['action_1'] resource_api = ActionAPI.from_model(resource_db) self.assertUserDoesntHaveResourceApiPermission( resolver=resolver, user_db=user_db, resource_api=resource_api, permission_type=PermissionType.ACTION_CREATE) # No roles, should return false user_db = self.users['no_roles'] resource_db = self.resources['action_1'] resource_api = ActionAPI.from_model(resource_db) self.assertUserDoesntHaveResourceApiPermission( resolver=resolver, user_db=user_db, resource_api=resource_api, permission_type=PermissionType.ACTION_CREATE) # Custom role with no permission grants, should return false user_db = self.users['1_custom_role_no_permissions'] resource_db = self.resources['action_1'] resource_api = ActionAPI.from_model(resource_db) self.assertUserDoesntHaveResourceApiPermission( resolver=resolver, user_db=user_db, resource_api=resource_api, permission_type=PermissionType.ACTION_CREATE) # Custom role with "action_create" grant on parent pack user_db = self.users['action_pack_action_create_grant'] resource_db = self.resources['action_1'] resource_api = ActionAPI.from_model(resource_db) self.assertUserHasResourceApiPermission( resolver=resolver, user_db=user_db, resource_api=resource_api, permission_type=PermissionType.ACTION_CREATE) # Custom role with "action_all" grant on the parent pack user_db = self.users['action_pack_action_all_grant'] resource_db = self.resources['action_1'] resource_api = ActionAPI.from_model(resource_db) self.assertUserHasResourceApiPermission( resolver=resolver, user_db=user_db, resource_api=resource_api, permission_type=PermissionType.ACTION_CREATE) # Custom role with "action_create" grant directly on the resource user_db = self.users['action_action_create_grant'] resource_db = self.resources['action_1'] resource_api = ActionAPI.from_model(resource_db) self.assertUserHasResourceApiPermission( resolver=resolver, user_db=user_db, resource_api=resource_api, permission_type=PermissionType.ACTION_CREATE) # Custom role with "action_all" grant directly on the resource user_db = self.users['action_action_all_grant'] resource_db = self.resources['action_1'] resource_api = ActionAPI.from_model(resource_db) self.assertUserHasResourceApiPermission( resolver=resolver, user_db=user_db, resource_api=resource_api, permission_type=PermissionType.ACTION_CREATE)