def test_notify_triggers_end_timestamp_none(self): liveaction_db = LiveActionDB(action='core.local') liveaction_db.id = bson.ObjectId() liveaction_db.description = '' liveaction_db.status = 'succeeded' liveaction_db.parameters = {} on_success = NotificationSubSchema(message='Action succeeded.') on_failure = NotificationSubSchema(message='Action failed.') liveaction_db.notify = NotificationSchema(on_success=on_success, on_failure=on_failure) liveaction_db.start_timestamp = date_utils.get_datetime_utc_now() # This tests for end_timestamp being set to None, which can happen when a policy cancels # a request. # The assertions within "MockDispatcher.dispatch" will validate that the underlying code # handles this properly, so all we need to do is keep the call to "notifier.process" below liveaction_db.end_timestamp = None LiveAction.add_or_update(liveaction_db) execution = MOCK_EXECUTION execution.liveaction = vars(LiveActionAPI.from_model(liveaction_db)) execution.status = liveaction_db.status dispatcher = NotifierTestCase.MockDispatcher(self) notifier = Notifier(connection=None, queues=[], trigger_dispatcher=dispatcher) notifier.process(execution)
def test_notify_triggers_jinja_patterns(self, dispatch): liveaction_db = LiveActionDB(action='core.local') liveaction_db.id = bson.ObjectId() liveaction_db.description = '' liveaction_db.status = 'succeeded' liveaction_db.parameters = {'cmd': 'mamma mia', 'runner_foo': 'foo'} on_success = NotificationSubSchema(message='Command {{action_parameters.cmd}} succeeded.', data={'stdout': '{{action_results.stdout}}'}) liveaction_db.notify = NotificationSchema(on_success=on_success) liveaction_db.start_timestamp = date_utils.get_datetime_utc_now() liveaction_db.end_timestamp = \ (liveaction_db.start_timestamp + datetime.timedelta(seconds=50)) LiveAction.add_or_update(liveaction_db) execution = MOCK_EXECUTION execution.liveaction = vars(LiveActionAPI.from_model(liveaction_db)) execution.status = liveaction_db.status notifier = Notifier(connection=None, queues=[]) notifier.process(execution) exp = {'status': 'succeeded', 'start_timestamp': isotime.format(liveaction_db.start_timestamp), 'route': 'notify.default', 'runner_ref': 'local-shell-cmd', 'channel': 'notify.default', 'message': u'Command mamma mia succeeded.', 'data': {'result': '{}', 'stdout': 'stuff happens'}, 'action_ref': u'core.local', 'execution_id': str(MOCK_EXECUTION.id), 'end_timestamp': isotime.format(liveaction_db.end_timestamp)} dispatch.assert_called_once_with('core.st2.generic.notifytrigger', payload=exp, trace_context={}) notifier.process(execution)
def test_notify_triggers_end_timestamp_none(self): liveaction_db = LiveActionDB(action='core.local') liveaction_db.id = bson.ObjectId() liveaction_db.description = '' liveaction_db.status = 'succeeded' liveaction_db.parameters = {} on_success = NotificationSubSchema(message='Action succeeded.') on_failure = NotificationSubSchema(message='Action failed.') liveaction_db.notify = NotificationSchema(on_success=on_success, on_failure=on_failure) liveaction_db.start_timestamp = date_utils.get_datetime_utc_now() # This tests for end_timestamp being set to None, which can happen when a policy cancels # a request. # The assertions within "MockDispatcher.dispatch" will validate that the underlying code # handles this properly, so all we need to do is keep the call to "notifier.process" below liveaction_db.end_timestamp = None LiveAction.add_or_update(liveaction_db) execution = MOCK_EXECUTION execution.liveaction = vars(LiveActionAPI.from_model(liveaction_db)) execution.status = liveaction_db.status dispatcher = NotifierTestCase.MockDispatcher(self) notifier = Notifier(connection=None, queues=[], trigger_dispatcher=dispatcher) notifier.process(execution)
def test_notify_triggers_jinja_patterns(self, dispatch): liveaction_db = LiveActionDB(action='core.local') liveaction_db.id = bson.ObjectId() liveaction_db.description = '' liveaction_db.status = 'succeeded' liveaction_db.parameters = {'cmd': 'mamma mia', 'runner_foo': 'foo'} on_success = NotificationSubSchema(message='Command {{action_parameters.cmd}} succeeded.', data={'stdout': '{{action_results.stdout}}'}) liveaction_db.notify = NotificationSchema(on_success=on_success) liveaction_db.start_timestamp = date_utils.get_datetime_utc_now() liveaction_db.end_timestamp = \ (liveaction_db.start_timestamp + datetime.timedelta(seconds=50)) LiveAction.add_or_update(liveaction_db) execution = MOCK_EXECUTION execution.liveaction = vars(LiveActionAPI.from_model(liveaction_db)) execution.status = liveaction_db.status notifier = Notifier(connection=None, queues=[]) notifier.process(execution) exp = {'status': 'succeeded', 'start_timestamp': isotime.format(liveaction_db.start_timestamp), 'route': 'notify.default', 'runner_ref': 'local-shell-cmd', 'channel': 'notify.default', 'message': u'Command mamma mia succeeded.', 'data': {'result': '{}', 'stdout': 'stuff happens'}, 'action_ref': u'core.local', 'execution_id': str(MOCK_EXECUTION.id), 'end_timestamp': isotime.format(liveaction_db.end_timestamp)} dispatch.assert_called_once_with('core.st2.generic.notifytrigger', payload=exp, trace_context={}) notifier.process(execution)
def delete(self, exec_id): """ Stops a single execution. Handles requests: DELETE /actionexecutions/<id> """ execution_api = self._get_one(id=exec_id) if not execution_api: abort(http_client.NOT_FOUND, 'Execution with id %s not found.' % exec_id) return liveaction_id = execution_api.liveaction['id'] if not liveaction_id: abort( http_client.INTERNAL_SERVER_ERROR, 'Execution object missing link to liveaction %s.' % liveaction_id) try: liveaction_db = LiveAction.get_by_id(liveaction_id) except: abort( http_client.INTERNAL_SERVER_ERROR, 'Execution object missing link to liveaction %s.' % liveaction_id) return if liveaction_db.status == LIVEACTION_STATUS_CANCELED: abort(http_client.OK, 'Action is already in "canceled" state.') if liveaction_db.status not in CANCELABLE_STATES: abort( http_client.OK, 'Action cannot be canceled. State = %s.' % liveaction_db.status) return liveaction_db.status = 'canceled' liveaction_db.end_timestamp = date_utils.get_datetime_utc_now() liveaction_db.result = {'message': 'Action canceled by user.'} try: LiveAction.add_or_update(liveaction_db) except: LOG.exception( 'Failed updating status to canceled for liveaction %s.', liveaction_db.id) abort(http_client.INTERNAL_SERVER_ERROR, 'Failed canceling execution.') return execution_db = execution_service.update_execution(liveaction_db) from_model_kwargs = self._get_from_model_kwargs_for_request( request=pecan.request) return ActionExecutionAPI.from_model(execution_db, from_model_kwargs)
def test_created_temporary_auth_token_is_correctly_scoped_to_user_who_ran_the_action( self): params = { 'actionstr': 'bar', 'mock_status': action_constants.LIVEACTION_STATUS_SUCCEEDED } global global_runner global_runner = None def mock_get_runner(*args, **kwargs): global global_runner runner = original_get_runner(*args, **kwargs) global_runner = runner return runner # user joe_1 runner_container = get_runner_container() original_get_runner = runner_container._get_runner liveaction_db = self._get_failingaction_exec_db_model(params) liveaction_db = LiveAction.add_or_update(liveaction_db) liveaction_db.context = {'user': '******'} executions.create_execution_object(liveaction_db) runner_container._get_runner = mock_get_runner self.assertEqual(getattr(global_runner, 'auth_token', None), None) runner_container.dispatch(liveaction_db) self.assertEqual(global_runner.auth_token.user, 'user_joe_1') self.assertEqual(global_runner.auth_token.metadata['service'], 'actions_container') runner_container._get_runner = original_get_runner # user mark_1 global_runner = None runner_container = get_runner_container() original_get_runner = runner_container._get_runner liveaction_db = self._get_failingaction_exec_db_model(params) liveaction_db = LiveAction.add_or_update(liveaction_db) liveaction_db.context = {'user': '******'} executions.create_execution_object(liveaction_db) original_get_runner = runner_container._get_runner runner_container._get_runner = mock_get_runner self.assertEqual(getattr(global_runner, 'auth_token', None), None) runner_container.dispatch(liveaction_db) self.assertEqual(global_runner.auth_token.user, 'user_mark_2') self.assertEqual(global_runner.auth_token.metadata['service'], 'actions_container')
def test_created_temporary_auth_token_is_correctly_scoped_to_user_who_ran_the_action(self): params = { 'actionstr': 'bar', 'mock_status': action_constants.LIVEACTION_STATUS_SUCCEEDED } global global_runner global_runner = None def mock_get_runner(*args, **kwargs): global global_runner runner = original_get_runner(*args, **kwargs) global_runner = runner return runner # user joe_1 runner_container = get_runner_container() original_get_runner = runner_container._get_runner liveaction_db = self._get_failingaction_exec_db_model(params) liveaction_db = LiveAction.add_or_update(liveaction_db) liveaction_db.context = {'user': '******'} executions.create_execution_object(liveaction_db) runner_container._get_runner = mock_get_runner self.assertEqual(getattr(global_runner, 'auth_token', None), None) runner_container.dispatch(liveaction_db) self.assertEqual(global_runner.auth_token.user, 'user_joe_1') self.assertEqual(global_runner.auth_token.metadata['service'], 'actions_container') runner_container._get_runner = original_get_runner # user mark_1 global_runner = None runner_container = get_runner_container() original_get_runner = runner_container._get_runner liveaction_db = self._get_failingaction_exec_db_model(params) liveaction_db = LiveAction.add_or_update(liveaction_db) liveaction_db.context = {'user': '******'} executions.create_execution_object(liveaction_db) original_get_runner = runner_container._get_runner runner_container._get_runner = mock_get_runner self.assertEqual(getattr(global_runner, 'auth_token', None), None) runner_container.dispatch(liveaction_db) self.assertEqual(global_runner.auth_token.user, 'user_mark_2') self.assertEqual(global_runner.auth_token.metadata['service'], 'actions_container')
def delete(self, exec_id): """ Stops a single execution. Handles requests: DELETE /actionexecutions/<id> """ execution_api = self._get_one(id=exec_id) if not execution_api: abort(http_client.NOT_FOUND, 'Execution with id %s not found.' % exec_id) return liveaction_id = execution_api.liveaction['id'] if not liveaction_id: abort(http_client.INTERNAL_SERVER_ERROR, 'Execution object missing link to liveaction %s.' % liveaction_id) try: liveaction_db = LiveAction.get_by_id(liveaction_id) except: abort(http_client.INTERNAL_SERVER_ERROR, 'Execution object missing link to liveaction %s.' % liveaction_id) return if liveaction_db.status == LIVEACTION_STATUS_CANCELED: abort(http_client.OK, 'Action is already in "canceled" state.') if liveaction_db.status not in CANCELABLE_STATES: abort(http_client.OK, 'Action cannot be canceled. State = %s.' % liveaction_db.status) return liveaction_db.status = 'canceled' liveaction_db.end_timestamp = date_utils.get_datetime_utc_now() liveaction_db.result = {'message': 'Action canceled by user.'} try: LiveAction.add_or_update(liveaction_db) except: LOG.exception('Failed updating status to canceled for liveaction %s.', liveaction_db.id) abort(http_client.INTERNAL_SERVER_ERROR, 'Failed canceling execution.') return execution_db = execution_service.update_execution(liveaction_db) from_model_kwargs = self._get_from_model_kwargs_for_request(request=pecan.request) return ActionExecutionAPI.from_model(execution_db, from_model_kwargs)
def test_processing_when_task_completed(self, mock_execution_queue_delete, mock_action_service): self.reset() liveaction_db = self._create_liveaction_db() LiveAction.publish_status(liveaction_db) liveaction_db.status = action_constants.LIVEACTION_STATUS_CANCELED LiveAction.add_or_update(liveaction_db) schedule_q_db = self.scheduling_queue._get_next_execution() scheduling_queue.get_handler()._handle_execution(schedule_q_db) mock_action_service.update_status.assert_not_called() mock_execution_queue_delete.assert_called_once() ActionExecutionSchedulingQueue.delete(schedule_q_db)
def test_processing_when_task_completed(self, mock_execution_queue_delete, mock_action_service): self.reset() liveaction_db = self._create_liveaction_db() LiveAction.publish_status(liveaction_db) liveaction_db.status = action_constants.LIVEACTION_STATUS_CANCELED LiveAction.add_or_update(liveaction_db) schedule_q_db = self.scheduling_queue._get_next_execution() scheduling_queue.get_handler()._handle_execution(schedule_q_db) mock_action_service.update_status.assert_not_called() mock_execution_queue_delete.assert_called_once() ActionExecutionSchedulingQueue.delete(schedule_q_db)
def update_liveaction_status(status=None, result=None, context=None, end_timestamp=None, liveaction_id=None, runner_info=None, liveaction_db=None, publish=True): """ Update the status of the specified LiveAction to the value provided in new_status. The LiveAction may be specified using either liveaction_id, or as an liveaction_db instance. """ if (liveaction_id is None) and (liveaction_db is None): raise ValueError( 'Must specify an liveaction_id or an liveaction_db when ' 'calling update_LiveAction_status') if liveaction_db is None: liveaction_db = get_liveaction_by_id(liveaction_id) if status not in LIVEACTION_STATUSES: raise ValueError( 'Attempting to set status for LiveAction "%s" ' 'to unknown status string. Unknown status is "%s"', liveaction_db, status) extra = {'liveaction_db': liveaction_db} LOG.debug('Updating ActionExection: "%s" with status="%s"', liveaction_db.id, status, extra=extra) old_status = liveaction_db.status liveaction_db.status = status if result: liveaction_db.result = result if context: liveaction_db.context.update(context) if end_timestamp: liveaction_db.end_timestamp = end_timestamp if runner_info: liveaction_db.runner_info = runner_info liveaction_db = LiveAction.add_or_update(liveaction_db) LOG.debug('Updated status for LiveAction object.', extra=extra) if publish and status != old_status: LiveAction.publish_status(liveaction_db) LOG.debug('Published status for LiveAction object.', extra=extra) return liveaction_db
def test_update_same_liveaction_status(self): liveaction_db = LiveActionDB() liveaction_db.status = 'requested' liveaction_db.start_timestamp = get_datetime_utc_now() liveaction_db.action = ResourceReference( name=ActionDBUtilsTestCase.action_db.name, pack=ActionDBUtilsTestCase.action_db.pack).ref params = { 'actionstr': 'foo', 'some_key_that_aint_exist_in_action_or_runner': 'bar', 'runnerint': 555 } liveaction_db.parameters = params liveaction_db = LiveAction.add_or_update(liveaction_db) origliveaction_db = copy.copy(liveaction_db) # Update by id. newliveaction_db = action_db_utils.update_liveaction_status( status='requested', liveaction_id=liveaction_db.id) # Verify id didn't change. self.assertEqual(origliveaction_db.id, newliveaction_db.id) self.assertEqual(newliveaction_db.status, 'requested') # Verify that state is not published. self.assertFalse(LiveActionPublisher.publish_state.called)
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 test_update_LiveAction_status_invalid(self): liveaction_db = LiveActionDB() liveaction_db.status = "initializing" liveaction_db.start_timestamp = get_datetime_utc_now() liveaction_db.action = ResourceReference( name=ActionDBUtilsTestCase.action_db.name, pack=ActionDBUtilsTestCase.action_db.pack, ).ref params = { "actionstr": "foo", "some_key_that_aint_exist_in_action_or_runner": "bar", "runnerint": 555, } liveaction_db.parameters = params liveaction_db = LiveAction.add_or_update(liveaction_db) # Update by id. self.assertRaises( ValueError, action_db_utils.update_liveaction_status, status="mea culpa", liveaction_id=liveaction_db.id, ) # Verify that state is not published. self.assertFalse(LiveActionPublisher.publish_state.called)
def test_liveaction_gets_deleted(self): now = date_utils.get_datetime_utc_now() start_ts = now - timedelta(days=15) end_ts = now - timedelta(days=14) liveaction_model = copy.deepcopy( self.models['liveactions']['liveaction4.yaml']) liveaction_model['start_timestamp'] = start_ts liveaction_model['end_timestamp'] = end_ts liveaction_model[ 'status'] = action_constants.LIVEACTION_STATUS_SUCCEEDED liveaction = LiveAction.add_or_update(liveaction_model) # Write one execution before cut-off threshold exec_model = copy.deepcopy( self.models['executions']['execution1.yaml']) exec_model['start_timestamp'] = start_ts exec_model['end_timestamp'] = end_ts exec_model['status'] = action_constants.LIVEACTION_STATUS_SUCCEEDED exec_model['id'] = bson.ObjectId() exec_model['liveaction']['id'] = str(liveaction.id) ActionExecution.add_or_update(exec_model) liveactions = LiveAction.get_all() executions = ActionExecution.get_all() self.assertEqual(len(liveactions), 1) self.assertEqual(len(executions), 1) purge_executions(logger=LOG, timestamp=now - timedelta(days=10)) liveactions = LiveAction.get_all() executions = ActionExecution.get_all() self.assertEqual(len(executions), 0) self.assertEqual(len(liveactions), 0)
def test_state_db_created_for_polling_async_actions(self): runner_container = get_runner_container() params = {"actionstr": "foo", "actionint": 20, "async_test": True} liveaction_db = self._get_liveaction_model( RunnerContainerTest.polling_async_action_db, params) liveaction_db = LiveAction.add_or_update(liveaction_db) executions.create_execution_object(liveaction_db) # Assert that execution ran without exceptions. with mock.patch( "st2actions.container.base.get_runner", mock.Mock(return_value=polling_async_runner.get_runner()), ): runner_container.dispatch(liveaction_db) states = ActionExecutionState.get_all() found = [ state for state in states if state.execution_id == liveaction_db.id ] self.assertTrue(len(found) > 0, "There should be a state db object.") self.assertTrue( len(found) == 1, "There should only be one state db object.") self.assertIsNotNone(found[0].query_context) self.assertIsNotNone(found[0].query_module)
def test_update_liveaction_with_incorrect_output_schema(self): liveaction_db = LiveActionDB() liveaction_db.status = 'initializing' liveaction_db.start_timestamp = get_datetime_utc_now() liveaction_db.action = ResourceReference( name=ActionDBUtilsTestCase.action_db.name, pack=ActionDBUtilsTestCase.action_db.pack).ref params = { 'actionstr': 'foo', 'some_key_that_aint_exist_in_action_or_runner': 'bar', 'runnerint': 555 } liveaction_db.parameters = params runner = mock.MagicMock() runner.output_schema = {"notaparam": {"type": "boolean"}} liveaction_db.runner = runner liveaction_db = LiveAction.add_or_update(liveaction_db) origliveaction_db = copy.copy(liveaction_db) now = get_datetime_utc_now() status = 'succeeded' result = 'Work is done.' context = {'third_party_id': uuid.uuid4().hex} newliveaction_db = action_db_utils.update_liveaction_status( status=status, result=result, context=context, end_timestamp=now, liveaction_id=liveaction_db.id) self.assertEqual(origliveaction_db.id, newliveaction_db.id) self.assertEqual(newliveaction_db.status, status) self.assertEqual(newliveaction_db.result, result) self.assertDictEqual(newliveaction_db.context, context) self.assertEqual(newliveaction_db.end_timestamp, now)
def setup_action_models(cls): pack = 'wolfpack' name = 'action-1' parameters = { 'actionint': {'type': 'number', 'default': 10, 'position': 0}, 'actionfloat': {'type': 'float', 'required': False, 'position': 1}, 'actionstr': {'type': 'string', 'required': True, 'position': 2}, 'actionbool': {'type': 'boolean', 'required': False, 'position': 3}, 'actionlist': {'type': 'list', 'required': False, 'position': 4}, 'actionobject': {'type': 'object', 'required': False, 'position': 5}, 'actionnull': {'type': 'null', 'required': False, 'position': 6}, 'runnerdummy': {'type': 'string', 'default': 'actiondummy'} } action_db = ActionDB(pack=pack, name=name, description='awesomeness', enabled=True, ref=ResourceReference(name=name, pack=pack).ref, entry_point='', runner_type={'name': 'test-runner'}, parameters=parameters) ActionDBUtilsTestCase.action_db = Action.add_or_update(action_db) liveaction_db = LiveActionDB() liveaction_db.status = 'initializing' liveaction_db.start_timestamp = get_datetime_utc_now() liveaction_db.action = ActionDBUtilsTestCase.action_db.ref params = { 'actionstr': 'foo', 'some_key_that_aint_exist_in_action_or_runner': 'bar', 'runnerint': 555 } liveaction_db.parameters = params ActionDBUtilsTestCase.liveaction_db = LiveAction.add_or_update(liveaction_db)
def test_state_db_created_for_polling_async_actions(self): runner_container = get_runner_container() params = { 'actionstr': 'foo', 'actionint': 20, 'async_test': True } liveaction_db = self._get_liveaction_model( RunnerContainerTest.polling_async_action_db, params ) liveaction_db = LiveAction.add_or_update(liveaction_db) executions.create_execution_object(liveaction_db) # Assert that execution ran without exceptions. runner_container.dispatch(liveaction_db) states = ActionExecutionState.get_all() found = [state for state in states if state.execution_id == liveaction_db.id] self.assertTrue(len(found) > 0, 'There should be a state db object.') self.assertTrue(len(found) == 1, 'There should only be one state db object.') self.assertTrue(found[0].query_context is not None) self.assertTrue(found[0].query_module is not None)
def test_read_execution(benchmark, fixture_file: str, compression): with open(os.path.join(FIXTURES_DIR, fixture_file), "rb") as fp: content = fp.read() cfg.CONF.set_override(name="compressors", group="database", override=compression) # NOTE: It's important we correctly reestablish connection before each setting change disconnect() connection = db_setup() if compression is None: assert "compressors" not in str(connection) elif compression == "zstd": assert "compressors=['zstd']" in str(connection) live_action_db = LiveActionDB() live_action_db.status = "succeeded" live_action_db.action = "core.local" live_action_db.result = content inserted_live_action_db = LiveAction.add_or_update(live_action_db) def run_benchmark(): retrieved_live_action_db = LiveAction.get_by_id( inserted_live_action_db.id) return retrieved_live_action_db retrieved_live_action_db = benchmark(run_benchmark) # Assert that result is correctly converted back to dict on retrieval assert retrieved_live_action_db == inserted_live_action_db
def setup_action_models(cls): action_db = ActionDB() action_db.name = 'action-1' action_db.description = 'awesomeness' action_db.enabled = True action_db.pack = 'wolfpack' action_db.ref = ResourceReference(name=action_db.name, pack=action_db.pack).ref action_db.entry_point = '' action_db.runner_type = {'name': 'test-runner'} action_db.parameters = { 'actionstr': {'type': 'string', 'position': 1, 'required': True}, 'actionint': {'type': 'number', 'default': 10, 'position': 0}, 'runnerdummy': {'type': 'string', 'default': 'actiondummy'} } ActionDBUtilsTestCase.action_db = Action.add_or_update(action_db) liveaction_db = LiveActionDB() liveaction_db.status = 'initializing' liveaction_db.start_timestamp = get_datetime_utc_now() liveaction_db.action = ActionDBUtilsTestCase.action_db.ref params = { 'actionstr': 'foo', 'some_key_that_aint_exist_in_action_or_runner': 'bar', 'runnerint': 555 } liveaction_db.parameters = params ActionDBUtilsTestCase.liveaction_db = LiveAction.add_or_update(liveaction_db)
def test_read_large_execution(benchmark, fixture_file: str, approach: str) -> None: with open(os.path.join(FIXTURES_DIR, fixture_file), "r") as fp: content = fp.read() data = json.loads(content) db_setup() # 1. Insert the large execution model_cls = get_model_class_for_approach(approach=approach) live_action_db = model_cls() live_action_db.status = "succeeded" live_action_db.action = "core.local" live_action_db.result = data inserted_live_action_db = LiveAction.add_or_update(live_action_db) def run_benchmark(): retrieved_live_action_db = LiveAction.get_by_id( inserted_live_action_db.id) return retrieved_live_action_db retrieved_live_action_db = benchmark.pedantic(run_benchmark, iterations=3, rounds=3) # Assert that result is correctly converted back to dict on retrieval assert retrieved_live_action_db == inserted_live_action_db assert retrieved_live_action_db.result == data
def test_liveaction_gets_deleted(self): now = date_utils.get_datetime_utc_now() start_ts = now - timedelta(days=15) end_ts = now - timedelta(days=14) liveaction_model = copy.deepcopy(self.models['liveactions']['liveaction4.yaml']) liveaction_model['start_timestamp'] = start_ts liveaction_model['end_timestamp'] = end_ts liveaction_model['status'] = action_constants.LIVEACTION_STATUS_SUCCEEDED liveaction = LiveAction.add_or_update(liveaction_model) # Write one execution before cut-off threshold exec_model = copy.deepcopy(self.models['executions']['execution1.yaml']) exec_model['start_timestamp'] = start_ts exec_model['end_timestamp'] = end_ts exec_model['status'] = action_constants.LIVEACTION_STATUS_SUCCEEDED exec_model['id'] = bson.ObjectId() exec_model['liveaction']['id'] = str(liveaction.id) ActionExecution.add_or_update(exec_model) liveactions = LiveAction.get_all() executions = ActionExecution.get_all() self.assertEqual(len(liveactions), 1) self.assertEqual(len(executions), 1) purge_executions(logger=LOG, timestamp=now - timedelta(days=10)) liveactions = LiveAction.get_all() executions = ActionExecution.get_all() self.assertEqual(len(executions), 0) self.assertEqual(len(liveactions), 0)
def test_read_large_string_value(benchmark, fixture_file: str, approach: str) -> None: with open(os.path.join(FIXTURES_DIR, fixture_file), "rb") as fp: content = fp.read() db_setup() if approach == "string_field": model_cls = LiveActionDB_StringField content = content.decode("utf-8") elif approach == "binary_field": model_cls = LiveActionDB_BinaryField else: raise ValueError("Unsupported approach") # 1. Insert the model live_action_db = model_cls() live_action_db.status = "succeeded" live_action_db.action = "core.local" live_action_db.value = content inserted_live_action_db = LiveAction.add_or_update(live_action_db) def run_benchmark(): retrieved_live_action_db = LiveAction.get_by_id( inserted_live_action_db.id) return retrieved_live_action_db retrieved_live_action_db = benchmark.pedantic(run_benchmark, iterations=10, rounds=10) assert retrieved_live_action_db == inserted_live_action_db assert retrieved_live_action_db.value == content
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 run_benchmark(): live_action_db = model_cls() live_action_db.status = "succeeded" live_action_db.action = "core.local" live_action_db.value = content inserted_live_action_db = LiveAction.add_or_update(live_action_db) return inserted_live_action_db
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 update_liveaction_status( status=None, result=None, context=None, end_timestamp=None, liveaction_id=None, runner_info=None, liveaction_db=None, publish=True, ): """ Update the status of the specified LiveAction to the value provided in new_status. The LiveAction may be specified using either liveaction_id, or as an liveaction_db instance. """ if (liveaction_id is None) and (liveaction_db is None): raise ValueError("Must specify an liveaction_id or an liveaction_db when " "calling update_LiveAction_status") if liveaction_db is None: liveaction_db = get_liveaction_by_id(liveaction_id) if status not in LIVEACTION_STATUSES: raise ValueError( 'Attempting to set status for LiveAction "%s" ' 'to unknown status string. Unknown status is "%s"', liveaction_db, status, ) extra = {"liveaction_db": liveaction_db} LOG.debug('Updating ActionExection: "%s" with status="%s"', liveaction_db.id, status, extra=extra) old_status = liveaction_db.status liveaction_db.status = status if result: liveaction_db.result = result if context: liveaction_db.context.update(context) if end_timestamp: liveaction_db.end_timestamp = end_timestamp if runner_info: liveaction_db.runner_info = runner_info liveaction_db = LiveAction.add_or_update(liveaction_db) LOG.debug("Updated status for LiveAction object.", extra=extra) if publish and status != old_status: LiveAction.publish_status(liveaction_db) LOG.debug("Published status for LiveAction object.", extra=extra) return liveaction_db
def test_update_canceled_liveaction(self): liveaction_db = LiveActionDB() liveaction_db.status = "initializing" liveaction_db.start_timestamp = get_datetime_utc_now() liveaction_db.action = ResourceReference( name=ActionDBUtilsTestCase.action_db.name, pack=ActionDBUtilsTestCase.action_db.pack, ).ref params = { "actionstr": "foo", "some_key_that_aint_exist_in_action_or_runner": "bar", "runnerint": 555, } liveaction_db.parameters = params liveaction_db = LiveAction.add_or_update(liveaction_db) origliveaction_db = copy.copy(liveaction_db) # Update by id. newliveaction_db = action_db_utils.update_liveaction_status( status="running", liveaction_id=liveaction_db.id) # Verify id didn't change. self.assertEqual(origliveaction_db.id, newliveaction_db.id) self.assertEqual(newliveaction_db.status, "running") # Verify that state is published. self.assertTrue(LiveActionPublisher.publish_state.called) LiveActionPublisher.publish_state.assert_called_once_with( newliveaction_db, "running") # Cancel liveaction. now = get_datetime_utc_now() status = "canceled" newliveaction_db = action_db_utils.update_liveaction_status( status=status, end_timestamp=now, liveaction_id=liveaction_db.id) self.assertEqual(origliveaction_db.id, newliveaction_db.id) self.assertEqual(newliveaction_db.status, status) self.assertEqual(newliveaction_db.end_timestamp, now) # Since liveaction has already been canceled, check that anymore update of # status, result, context, and end timestamp are not processed. now = get_datetime_utc_now() status = "succeeded" result = "Work is done." context = {"third_party_id": uuid.uuid4().hex} newliveaction_db = action_db_utils.update_liveaction_status( status=status, result=result, context=context, end_timestamp=now, liveaction_id=liveaction_db.id, ) self.assertEqual(origliveaction_db.id, newliveaction_db.id) self.assertEqual(newliveaction_db.status, "canceled") self.assertNotEqual(newliveaction_db.result, result) self.assertNotEqual(newliveaction_db.context, context) self.assertNotEqual(newliveaction_db.end_timestamp, now)
def _create_inquiry(self, ttl, timestamp): action_db = self.models['actions']['ask.yaml'] liveaction_db = LiveActionDB() liveaction_db.status = action_constants.LIVEACTION_STATUS_PENDING liveaction_db.start_timestamp = timestamp liveaction_db.action = ResourceReference(name=action_db.name, pack=action_db.pack).ref liveaction_db.result = {'ttl': ttl} liveaction_db = LiveAction.add_or_update(liveaction_db) executions.create_execution_object(liveaction_db)
def _create_liveaction_db(self, status=action_constants.LIVEACTION_STATUS_REQUESTED): action_ref = 'wolfpack.action-1' parameters = {'actionstr': 'fu'} liveaction_db = LiveActionDB(action=action_ref, parameters=parameters, status=status) liveaction_db = LiveAction.add_or_update(liveaction_db) execution_service.create_execution_object(liveaction_db, publish=False) return liveaction_db
def _create_liveaction_db(self, status=action_constants.LIVEACTION_STATUS_REQUESTED): action_ref = 'wolfpack.action-1' parameters = {'actionstr': 'fu'} liveaction_db = LiveActionDB(action=action_ref, parameters=parameters, status=status) liveaction_db = LiveAction.add_or_update(liveaction_db) execution_service.create_execution_object(liveaction_db, publish=False) return liveaction_db
def _create_inquiry(self, ttl, timestamp): action_db = self.models['actions']['ask.yaml'] liveaction_db = LiveActionDB() liveaction_db.status = action_constants.LIVEACTION_STATUS_PENDING liveaction_db.start_timestamp = timestamp liveaction_db.action = ResourceReference(name=action_db.name, pack=action_db.pack).ref liveaction_db.result = {'ttl': ttl} liveaction_db = LiveAction.add_or_update(liveaction_db) executions.create_execution_object(liveaction_db)
def run_benchmark(): live_action_db = model_cls() live_action_db.status = "succeeded" live_action_db.action = "core.local" live_action_db.field1 = data live_action_db.field2 = data live_action_db.field3 = data inserted_live_action_db = LiveAction.add_or_update(live_action_db) return inserted_live_action_db
def test_notify_triggers_jinja_patterns(self, dispatch): liveaction_db = LiveActionDB(action="core.local") liveaction_db.id = bson.ObjectId() liveaction_db.description = "" liveaction_db.status = "succeeded" liveaction_db.parameters = {"cmd": "mamma mia", "runner_foo": "foo"} on_success = NotificationSubSchema( message="Command {{action_parameters.cmd}} succeeded.", data={"stdout": "{{action_results.stdout}}"}, ) liveaction_db.notify = NotificationSchema(on_success=on_success) liveaction_db.start_timestamp = date_utils.get_datetime_utc_now() liveaction_db.end_timestamp = (liveaction_db.start_timestamp + datetime.timedelta(seconds=50)) LiveAction.add_or_update(liveaction_db) execution = MOCK_EXECUTION execution.liveaction = vars(LiveActionAPI.from_model(liveaction_db)) execution.status = liveaction_db.status notifier = Notifier(connection=None, queues=[]) notifier.process(execution) exp = { "status": "succeeded", "start_timestamp": isotime.format(liveaction_db.start_timestamp), "route": "notify.default", "runner_ref": "local-shell-cmd", "channel": "notify.default", "message": "Command mamma mia succeeded.", "data": { "result": "{}", "stdout": "stuff happens" }, "action_ref": "core.local", "execution_id": str(MOCK_EXECUTION.id), "end_timestamp": isotime.format(liveaction_db.end_timestamp), } dispatch.assert_called_once_with("core.st2.generic.notifytrigger", payload=exp, trace_context={}) notifier.process(execution)
def _update_action_results(self, execution_id, status, results): liveaction_db = LiveAction.get_by_id(execution_id) if not liveaction_db: raise Exception('No DB model for liveaction_id: %s' % execution_id) liveaction_db.result = results liveaction_db.status = status # update liveaction, update actionexecution and then publish update. updated_liveaction = LiveAction.add_or_update(liveaction_db, publish=False) executions.update_execution(updated_liveaction) LiveAction.publish_update(updated_liveaction) return updated_liveaction
def test_dispatch_runner_failure(self): runner_container = get_runner_container() params = {'actionstr': 'bar'} liveaction_db = self._get_failingaction_exec_db_model(params) liveaction_db = LiveAction.add_or_update(liveaction_db) executions.create_execution_object(liveaction_db) runner_container.dispatch(liveaction_db) # pickup updated liveaction_db liveaction_db = LiveAction.get_by_id(liveaction_db.id) self.assertTrue('error' in liveaction_db.result) self.assertTrue('traceback' in liveaction_db.result)
def test_worker_shutdown(self): cfg.CONF.set_override(name="graceful_shutdown", override=False, group="actionrunner") action_worker = actions_worker.get_worker() temp_file = None # Create a temporary file that is deleted when the file is closed and then set up an # action to wait for this file to be deleted. This allows this test to run the action # over a separate thread, run the shutdown sequence on the main thread, and then let # the local runner to exit gracefully and allow _run_action to finish execution. with tempfile.NamedTemporaryFile() as fp: temp_file = fp.name self.assertIsNotNone(temp_file) self.assertTrue(os.path.isfile(temp_file)) # Launch the action execution in a separate thread. params = { "cmd": "while [ -e '%s' ]; do sleep 0.1; done" % temp_file } liveaction_db = self._get_liveaction_model( WorkerTestCase.local_action_db, params) liveaction_db = LiveAction.add_or_update(liveaction_db) executions.create_execution_object(liveaction_db) runner_thread = eventlet.spawn(action_worker._run_action, liveaction_db) # Wait for the worker up to 10s to add the liveaction to _running_liveactions. for i in range(0, int(10 / 0.1)): eventlet.sleep(0.1) if len(action_worker._running_liveactions) > 0: break self.assertEqual(len(action_worker._running_liveactions), 1) # Shutdown the worker to trigger the abandon process. action_worker.shutdown() liveaction_db = LiveAction.get_by_id(liveaction_db.id) # Verify that _running_liveactions is empty and the liveaction is abandoned. self.assertEqual(len(action_worker._running_liveactions), 0) self.assertEqual( liveaction_db.status, action_constants.LIVEACTION_STATUS_ABANDONED, str(liveaction_db), ) # Make sure the temporary file has been deleted. self.assertFalse(os.path.isfile(temp_file)) # Wait for the local runner to complete. This will activate the finally block in # _run_action but will not result in KeyError because the discard method is used to # to remove the liveaction from _running_liveactions. runner_thread.wait()
def test_dispatch_runner_failure(self): runner_container = get_runner_container() params = {"actionstr": "bar"} liveaction_db = self._get_failingaction_exec_db_model(params) liveaction_db = LiveAction.add_or_update(liveaction_db) executions.create_execution_object(liveaction_db) runner_container.dispatch(liveaction_db) # pickup updated liveaction_db liveaction_db = LiveAction.get_by_id(liveaction_db.id) self.assertTrue("message" in liveaction_db.result) self.assertTrue("traceback" in liveaction_db.result)
def _create_nested_executions(self, depth=2): """Utility function for easily creating nested LiveAction and ActionExecutions for testing returns (childmost_liveaction_db, parentmost_liveaction_db) """ if depth <= 0: raise Exception("Please provide a depth > 0") root_liveaction_db = LiveActionDB() root_liveaction_db.status = action_constants.LIVEACTION_STATUS_PAUSED root_liveaction_db.action = ACTION_WORKFLOW_REF root_liveaction_db = LiveAction.add_or_update(root_liveaction_db) root_ex = executions.create_execution_object(root_liveaction_db) last_id = root_ex['id'] # Create children to the specified depth for i in range(depth): # Childmost liveaction should use ACTION_REF, everything else # should use ACTION_WORKFLOW_REF if i == depth: action = ACTION_REF else: action = ACTION_WORKFLOW_REF child_liveaction_db = LiveActionDB() child_liveaction_db.status = action_constants.LIVEACTION_STATUS_PAUSED child_liveaction_db.action = action child_liveaction_db.context = { "parent": { "execution_id": last_id } } child_liveaction_db = LiveAction.add_or_update(child_liveaction_db) parent_ex = executions.create_execution_object(child_liveaction_db) last_id = parent_ex.id # Return the last-created child as well as the root return (child_liveaction_db, root_liveaction_db)
def test_dispatch_non_utf8_result(self): runner_container = get_runner_container() params = {"cmd": "python -c 'print \"\\x82\"'"} liveaction_db = self._get_liveaction_model(RunnerContainerTest.local_action_db, params) liveaction_db = LiveAction.add_or_update(liveaction_db) executions.create_execution_object(liveaction_db) try: runner_container.dispatch(liveaction_db) self.fail("Mongo won't handle non UTF-8 strings. Should have failed.") except InvalidStringData: pass
def _update_action_results(self, execution_id, status, results): liveaction_db = LiveAction.get_by_id(execution_id) if not liveaction_db: raise Exception('No DB model for liveaction_id: %s' % execution_id) liveaction_db.result = results liveaction_db.status = status # update liveaction, update actionexecution and then publish update. updated_liveaction = LiveAction.add_or_update(liveaction_db, publish=False) executions.update_execution(updated_liveaction) LiveAction.publish_update(updated_liveaction) return updated_liveaction
def test_dispatch_override_default_action_params(self): runner_container = get_runner_container() params = {"actionstr": "foo", "actionint": 20} liveaction_db = self._get_action_exec_db_model(RunnerContainerTest.action_db, params) liveaction_db = LiveAction.add_or_update(liveaction_db) executions.create_execution_object(liveaction_db) # Assert that execution ran successfully. runner_container.dispatch(liveaction_db) liveaction_db = LiveAction.get_by_id(liveaction_db.id) result = liveaction_db.result self.assertTrue(result.get("action_params").get("actionint") == 20) self.assertTrue(result.get("action_params").get("actionstr") == "foo")
def test_update_canceled_liveaction(self): liveaction_db = LiveActionDB() liveaction_db.status = 'initializing' liveaction_db.start_timestamp = get_datetime_utc_now() liveaction_db.action = ResourceReference( name=ActionDBUtilsTestCase.action_db.name, pack=ActionDBUtilsTestCase.action_db.pack).ref params = { 'actionstr': 'foo', 'some_key_that_aint_exist_in_action_or_runner': 'bar', 'runnerint': 555 } liveaction_db.parameters = params liveaction_db = LiveAction.add_or_update(liveaction_db) origliveaction_db = copy.copy(liveaction_db) # Update by id. newliveaction_db = action_db_utils.update_liveaction_status( status='running', liveaction_id=liveaction_db.id) # Verify id didn't change. self.assertEqual(origliveaction_db.id, newliveaction_db.id) self.assertEqual(newliveaction_db.status, 'running') # Verify that state is published. self.assertTrue(LiveActionPublisher.publish_state.called) LiveActionPublisher.publish_state.assert_called_once_with(newliveaction_db, 'running') # Cancel liveaction. now = get_datetime_utc_now() status = 'canceled' newliveaction_db = action_db_utils.update_liveaction_status( status=status, end_timestamp=now, liveaction_id=liveaction_db.id) self.assertEqual(origliveaction_db.id, newliveaction_db.id) self.assertEqual(newliveaction_db.status, status) self.assertEqual(newliveaction_db.end_timestamp, now) # Since liveaction has already been canceled, check that anymore update of # status, result, context, and end timestamp are not processed. now = get_datetime_utc_now() status = 'succeeded' result = 'Work is done.' context = {'third_party_id': uuid.uuid4().hex} newliveaction_db = action_db_utils.update_liveaction_status( status=status, result=result, context=context, end_timestamp=now, liveaction_id=liveaction_db.id) self.assertEqual(origliveaction_db.id, newliveaction_db.id) self.assertEqual(newliveaction_db.status, 'canceled') self.assertNotEqual(newliveaction_db.result, result) self.assertNotEqual(newliveaction_db.context, context) self.assertNotEqual(newliveaction_db.end_timestamp, now)
def test_notify_triggers(self): liveaction_db = LiveActionDB(action='core.local') liveaction_db.id = bson.ObjectId() liveaction_db.description = '' liveaction_db.status = 'succeeded' liveaction_db.parameters = {} on_success = NotificationSubSchema(message='Action succeeded.') on_failure = NotificationSubSchema(message='Action failed.') liveaction_db.notify = NotificationSchema(on_success=on_success, on_failure=on_failure) liveaction_db.start_timestamp = date_utils.get_datetime_utc_now() liveaction_db.end_timestamp = \ (liveaction_db.start_timestamp + datetime.timedelta(seconds=50)) LiveAction.add_or_update(liveaction_db) execution = MOCK_EXECUTION execution.liveaction = vars(LiveActionAPI.from_model(liveaction_db)) execution.status = liveaction_db.status dispatcher = NotifierTestCase.MockDispatcher(self) notifier = Notifier(connection=None, queues=[], trigger_dispatcher=dispatcher) notifier.process(execution)
def test_dispatch_override_default_action_params(self): runner_container = get_runner_container() params = {'actionstr': 'foo', 'actionint': 20} liveaction_db = self._get_liveaction_model( RunnerContainerTest.action_db, params) liveaction_db = LiveAction.add_or_update(liveaction_db) executions.create_execution_object(liveaction_db) # Assert that execution ran successfully. runner_container.dispatch(liveaction_db) liveaction_db = LiveAction.get_by_id(liveaction_db.id) result = liveaction_db.result self.assertTrue(result.get('action_params').get('actionint') == 20) self.assertTrue(result.get('action_params').get('actionstr') == 'foo')
def test_dispatch_unsupported_status(self): runner_container = get_runner_container() params = {'actionstr': 'bar'} liveaction_db = self._get_liveaction_model(RunnerContainerTest.action_db, params) liveaction_db = LiveAction.add_or_update(liveaction_db) executions.create_execution_object(liveaction_db) # Manually set the liveaction_db to some unsupported status. liveaction_db.status = action_constants.LIVEACTION_STATUS_CANCELED # Assert exception is raised on dispatch. self.assertRaises( ActionRunnerDispatchError, runner_container.dispatch, liveaction_db )
def test_non_utf8_action_result_string(self): action_worker = actions_worker.get_worker() params = {"cmd": "python -c 'print \"\\x82\"'"} liveaction_db = self._get_liveaction_model(WorkerTestCase.local_action_db, params) liveaction_db = LiveAction.add_or_update(liveaction_db) execution_db = executions.create_execution_object(liveaction_db) try: action_worker._run_action(liveaction_db) except InvalidStringData: liveaction_db = LiveAction.get_by_id(liveaction_db.id) self.assertEqual(liveaction_db.status, "failed") self.assertTrue("error" in liveaction_db.result) self.assertTrue("traceback" in liveaction_db.result) execution_db = ActionExecution.get_by_id(execution_db.id) self.assertEqual(liveaction_db.status, "failed")
def test_state_db_creation_async_actions(self): runner_container = get_runner_container() params = {"actionstr": "foo", "actionint": 20, "async_test": True} liveaction_db = self._get_action_exec_db_model(RunnerContainerTest.async_action_db, params) liveaction_db = LiveAction.add_or_update(liveaction_db) executions.create_execution_object(liveaction_db) # Assert that execution ran without exceptions. runner_container.dispatch(liveaction_db) states = ActionExecutionState.get_all() found = None for state in states: if state.execution_id == liveaction_db.id: found = state self.assertTrue(found is not None, "There should be a state db object.") self.assertTrue(found.query_context is not None) self.assertTrue(found.query_module is not None)
def test_dispatch(self): runner_container = get_runner_container() params = {"actionstr": "bar"} liveaction_db = self._get_action_exec_db_model(RunnerContainerTest.action_db, params) liveaction_db = LiveAction.add_or_update(liveaction_db) executions.create_execution_object(liveaction_db) # Assert that execution ran successfully. runner_container.dispatch(liveaction_db) liveaction_db = LiveAction.get_by_id(liveaction_db.id) result = liveaction_db.result self.assertTrue(result.get("action_params").get("actionint") == 10) self.assertTrue(result.get("action_params").get("actionstr") == "bar") # Assert that context is written correctly. context = {"user": "******", "third_party_system": {"ref_id": "1234"}} self.assertDictEqual(liveaction_db.context, context)
def test_non_utf8_action_result_string(self): action_worker = actions_worker.get_worker() params = { 'cmd': "python -c 'print \"\\x82\"'" } liveaction_db = self._get_liveaction_model(WorkerTestCase.local_action_db, params) liveaction_db = LiveAction.add_or_update(liveaction_db) execution_db = executions.create_execution_object(liveaction_db) try: action_worker._run_action(liveaction_db) except InvalidStringData: liveaction_db = LiveAction.get_by_id(liveaction_db.id) self.assertEqual(liveaction_db.status, action_constants.LIVEACTION_STATUS_FAILED) self.assertTrue('error' in liveaction_db.result) self.assertTrue('traceback' in liveaction_db.result) execution_db = ActionExecution.get_by_id(execution_db.id) self.assertEqual(liveaction_db.status, action_constants.LIVEACTION_STATUS_FAILED)
def test_worker_shutdown(self): action_worker = actions_worker.get_worker() temp_file = None # Create a temporary file that is deleted when the file is closed and then set up an # action to wait for this file to be deleted. This allows this test to run the action # over a separate thread, run the shutdown sequence on the main thread, and then let # the local runner to exit gracefully and allow _run_action to finish execution. with tempfile.NamedTemporaryFile() as fp: temp_file = fp.name self.assertIsNotNone(temp_file) self.assertTrue(os.path.isfile(temp_file)) # Launch the action execution in a separate thread. params = {'cmd': 'while [ -e \'%s\' ]; do sleep 0.1; done' % temp_file} liveaction_db = self._get_liveaction_model(WorkerTestCase.local_action_db, params) liveaction_db = LiveAction.add_or_update(liveaction_db) executions.create_execution_object(liveaction_db) runner_thread = eventlet.spawn(action_worker._run_action, liveaction_db) # Wait for the worker up to 10s to add the liveaction to _running_liveactions. for i in range(0, int(10 / 0.1)): eventlet.sleep(0.1) if len(action_worker._running_liveactions) > 0: break self.assertEqual(len(action_worker._running_liveactions), 1) # Shutdown the worker to trigger the abandon process. action_worker.shutdown() liveaction_db = LiveAction.get_by_id(liveaction_db.id) # Verify that _running_liveactions is empty and the liveaction is abandoned. self.assertEqual(len(action_worker._running_liveactions), 0) self.assertEqual(liveaction_db.status, action_constants.LIVEACTION_STATUS_ABANDONED, str(liveaction_db)) # Make sure the temporary file has been deleted. self.assertFalse(os.path.isfile(temp_file)) # Wait for the local runner to complete. This will activate the finally block in # _run_action but will not result in KeyError because the discard method is used to # to remove the liveaction from _running_liveactions. runner_thread.wait()
def _update_action_results(self, execution_id, status, results): liveaction_db = LiveAction.get_by_id(execution_id) if not liveaction_db: raise Exception('No DB model for liveaction_id: %s' % execution_id) liveaction_db.result = results liveaction_db.status = status done = status in DONE_STATES if done and not liveaction_db.end_timestamp: # Action has completed, record end_timestamp liveaction_db.end_timestamp = date_utils.get_datetime_utc_now() # update liveaction, update actionexecution and then publish update. updated_liveaction = LiveAction.add_or_update(liveaction_db, publish=False) executions.update_execution(updated_liveaction) LiveAction.publish_update(updated_liveaction) return updated_liveaction
def test_update_liveaction_result_with_dotted_key(self): liveaction_db = LiveActionDB() liveaction_db.status = 'initializing' liveaction_db.start_timestamp = get_datetime_utc_now() liveaction_db.action = ResourceReference( name=ActionDBUtilsTestCase.action_db.name, pack=ActionDBUtilsTestCase.action_db.pack).ref params = { 'actionstr': 'foo', 'some_key_that_aint_exist_in_action_or_runner': 'bar', 'runnerint': 555 } liveaction_db.parameters = params liveaction_db = LiveAction.add_or_update(liveaction_db) origliveaction_db = copy.copy(liveaction_db) # Update by id. newliveaction_db = action_db_utils.update_liveaction_status( status='running', liveaction_id=liveaction_db.id) # Verify id didn't change. self.assertEqual(origliveaction_db.id, newliveaction_db.id) self.assertEqual(newliveaction_db.status, 'running') # Verify that state is published. self.assertTrue(LiveActionPublisher.publish_state.called) LiveActionPublisher.publish_state.assert_called_once_with(newliveaction_db, 'running') now = get_datetime_utc_now() status = 'succeeded' result = {'a': 1, 'b': True, 'a.b.c': 'abc'} context = {'third_party_id': uuid.uuid4().hex} newliveaction_db = action_db_utils.update_liveaction_status( status=status, result=result, context=context, end_timestamp=now, liveaction_id=liveaction_db.id) self.assertEqual(origliveaction_db.id, newliveaction_db.id) self.assertEqual(newliveaction_db.status, status) self.assertIn('a.b.c', list(result.keys())) self.assertDictEqual(newliveaction_db.result, result) self.assertDictEqual(newliveaction_db.context, context) self.assertEqual(newliveaction_db.end_timestamp, now)
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)