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_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 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_abandon_executions_on_complete(self): liveaction_db = self.MODELS['liveactions']['successful_liveaction.yaml'] executions_util.create_execution_object(liveaction_db) expected_msg = 'LiveAction %s already in a completed state %s\.' % \ (str(liveaction_db.id), liveaction_db.status) self.assertRaisesRegexp(ValueError, expected_msg, executions_util.abandon_execution_if_incomplete, liveaction_id=str(liveaction_db.id))
def test_execution_creation_with_web_url(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.assertIsNotNone(execution.web_url) execution_id = str(execution.id) self.assertIn(('history/%s/general' % execution_id), execution.web_url)
def test_execution_creation_with_web_url(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.assertTrue(execution.web_url is not None) execution_id = str(execution.id) self.assertTrue(('history/%s/general' % execution_id) in execution.web_url)
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 test_abandon_executions(self): liveaction_db = self.MODELS['liveactions']['liveaction1.yaml'] executions_util.create_execution_object(liveaction_db) execution_db = executions_util.abandon_execution_if_incomplete( liveaction_id=str(liveaction_db.id)) self.assertEquals(execution_db.status, 'abandoned') runners_utils.invoke_post_run.assert_called_once()
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_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 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_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 setUp(self): super(RunnersUtilityTests, self).setUp() loader = fixturesloader.FixturesLoader() self.models = loader.save_fixtures_to_db(fixtures_pack=FIXTURES_PACK, fixtures_dict=TEST_FIXTURES) self.liveaction_db = self.models['liveactions']['liveaction1.yaml'] exe_svc.create_execution_object(self.liveaction_db) self.action_db = action_db_utils.get_action_by_ref( self.liveaction_db.action)
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_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_execution_update(self): liveaction = self.MODELS['liveactions']['liveaction1.yaml'] executions_util.create_execution_object(liveaction) liveaction.status = 'running' pre_update_timestamp = date_utils.get_datetime_utc_now() executions_util.update_execution(liveaction) post_update_timestamp = date_utils.get_datetime_utc_now() execution = self._get_action_execution(liveaction__id=str(liveaction.id), raise_exception=True) self.assertEquals(len(execution.log), 2) self.assertEquals(execution.log[1]['status'], liveaction.status) self.assertGreater(execution.log[1]['timestamp'], pre_update_timestamp) self.assertLess(execution.log[1]['timestamp'], post_update_timestamp)
def test_execution_update(self): liveaction = self.MODELS['liveactions']['liveaction1.yaml'] executions_util.create_execution_object(liveaction) liveaction.status = 'running' pre_update_timestamp = date_utils.get_datetime_utc_now() executions_util.update_execution(liveaction) post_update_timestamp = date_utils.get_datetime_utc_now() execution = self._get_action_execution(liveaction__id=str(liveaction.id), raise_exception=True) self.assertEqual(len(execution.log), 2) self.assertEqual(execution.log[1]['status'], liveaction.status) self.assertGreater(execution.log[1]['timestamp'], pre_update_timestamp) self.assertLess(execution.log[1]['timestamp'], post_update_timestamp)
def setUp(self): super(RunnersUtilityTests, self).setUp() loader = fixturesloader.FixturesLoader() self.models = loader.save_fixtures_to_db( fixtures_pack=FIXTURES_PACK, fixtures_dict=TEST_FIXTURES ) self.liveaction_db = self.models['liveactions']['liveaction1.yaml'] exe_svc.create_execution_object(self.liveaction_db) self.action_db = action_db_utils.get_action_by_ref(self.liveaction_db.action)
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_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_skip_execution_update(self): liveaction = self.MODELS["liveactions"]["successful_liveaction.yaml"] executions_util.create_execution_object(liveaction) pre_update_status = liveaction.status liveaction.status = "running" executions_util.update_execution(liveaction) execution = self._get_action_execution(liveaction__id=str( liveaction.id), raise_exception=True) self.assertEqual(len(execution.log), 1) # Check status is not updated if it's already in completed state. self.assertEqual(pre_update_status, action_constants.LIVEACTION_STATUS_SUCCEEDED) self.assertEqual(execution.log[0]["status"], pre_update_status)
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 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 _create_liveaction_db( self, status=action_constants.LIVEACTION_STATUS_REQUESTED): action_db = action_utils.get_action_by_ref('core.noop') liveaction_db = LiveActionDB( action=action_db.ref, parameters=None, start_timestamp=date_utils.get_datetime_utc_now(), status=status) liveaction_db = action.LiveAction.add_or_update(liveaction_db, publish=False) executions.create_execution_object(liveaction_db) return liveaction_db
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_execution_creation_chains(self): childliveaction = self.MODELS["liveactions"]["childliveaction.yaml"] child_exec = executions_util.create_execution_object(childliveaction) parent_execution_id = childliveaction.context["parent"]["execution_id"] parent_execution = ActionExecution.get_by_id(parent_execution_id) child_execs = parent_execution.children self.assertIn(str(child_exec.id), child_execs)
def test_delayed_executions_recovery_before_timeout(self): # Create a live action that's delayed but has not passed the timeout. liveaction = LiveActionDB(action='wolfpack.action-1', parameters={'actionstr': 'foo'}, start_timestamp=date_utils.get_datetime_utc_now(), status=action_constants.LIVEACTION_STATUS_DELAYED) liveaction = LiveAction.add_or_update(liveaction, publish=False) executions.create_execution_object(liveaction, publish=False) # Run the rescheduling routine. scheduler.recover_delayed_executions() # The live action is expected to stay "delayed". liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(liveaction.status, action_constants.LIVEACTION_STATUS_DELAYED)
def test_execution_creation_chains(self): childliveaction = self.MODELS['liveactions']['childliveaction.yaml'] child_exec = executions_util.create_execution_object(childliveaction) parent_execution_id = childliveaction.context['parent']['execution_id'] parent_execution = ActionExecution.get_by_id(parent_execution_id) child_execs = parent_execution.children self.assertTrue(str(child_exec.id) in child_execs)
def test_abandon_executions_on_complete(self): liveaction_db = self.MODELS["liveactions"]["successful_liveaction.yaml"] executions_util.create_execution_object(liveaction_db) expected_msg = r"LiveAction %s already in a completed state %s\." % ( str(liveaction_db.id), liveaction_db.status, ) self.assertRaisesRegexp( ValueError, expected_msg, executions_util.abandon_execution_if_incomplete, liveaction_id=str(liveaction_db.id), ) runners_utils.invoke_post_run.assert_not_called()
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_dispatch(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) # 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_dispatch(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) # 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_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 test_execution_creation_chains(self): """ Test children and parent relationship is established. """ childliveaction = self.MODELS['liveactions']['childliveaction.json'] child_exec = executions_util.create_execution_object(childliveaction) parent_exection = self._get_action_execution( liveaction__id=childliveaction.context.get('parent', '')) child_execs = parent_exection.children self.assertTrue(str(child_exec.id) in child_execs)
def test_execution_creation_chains(self): """ Test children and parent relationship is established. """ childliveaction = self.MODELS['liveactions']['childliveaction.yaml'] child_exec = executions_util.create_execution_object(childliveaction) parent_exection = self._get_action_execution( liveaction__id=childliveaction.context.get('parent', '')) child_execs = parent_exection.children self.assertTrue(str(child_exec.id) in child_execs)
def test_delayed_executions_recovery(self): # Create a live action that's already delayed pass the allowed timeout. dt_now = date_utils.get_datetime_utc_now() dt_delta = datetime.timedelta(seconds=cfg.CONF.scheduler.delayed_execution_recovery) dt_timeout = dt_now - dt_delta liveaction = LiveActionDB(action='wolfpack.action-1', parameters={'actionstr': 'foo'}, start_timestamp=dt_timeout, status=action_constants.LIVEACTION_STATUS_DELAYED) liveaction = LiveAction.add_or_update(liveaction, publish=False) executions.create_execution_object(liveaction, publish=False) # Run the rescheduling routine. scheduler.recover_delayed_executions() # The live action is expected to complete. liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(liveaction.status, action_constants.LIVEACTION_STATUS_SUCCEEDED)
def test_state_db_not_created_for_async_actions(self): runner_container = get_runner_container() params = {"actionstr": "foo", "actionint": 20, "async_test": True} liveaction_db = self._get_liveaction_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 = [ state for state in states if state.execution_id == liveaction_db.id ] self.assertTrue( len(found) == 0, "There should not be a state db object.")
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_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 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, "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_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_post_run_is_always_called_after_run(self): # 1. post_run should be called on success, failure, etc. runner_container = get_runner_container() params = { 'actionstr': 'bar', 'mock_status': action_constants.LIVEACTION_STATUS_SUCCEEDED } liveaction_db = self._get_failingaction_exec_db_model(params) liveaction_db = LiveAction.add_or_update(liveaction_db) executions.create_execution_object(liveaction_db) global global_runner global_runner = None original_get_runner = runner_container._get_runner def mock_get_runner(*args, **kwargs): global global_runner runner = original_get_runner(*args, **kwargs) global_runner = runner return runner runner_container._get_runner = mock_get_runner # Note: We can't assert here that post_run hasn't been called yet because runner instance # is only instantiated later inside dispatch method runner_container.dispatch(liveaction_db) self.assertTrue(global_runner.post_run_called) # 2. Verify post_run is called if run() throws runner_container = get_runner_container() params = { 'actionstr': 'bar', 'raise': True } liveaction_db = self._get_failingaction_exec_db_model(params) liveaction_db = LiveAction.add_or_update(liveaction_db) executions.create_execution_object(liveaction_db) global_runner = None original_get_runner = runner_container._get_runner def mock_get_runner(*args, **kwargs): global global_runner runner = original_get_runner(*args, **kwargs) global_runner = runner return runner runner_container._get_runner = mock_get_runner # Note: We can't assert here that post_run hasn't been called yet because runner instance # is only instantiated later inside dispatch method runner_container.dispatch(liveaction_db) self.assertTrue(global_runner.post_run_called) # 2. Verify post_run is also called if _delete_auth_token throws runner_container = get_runner_container() runner_container._delete_auth_token = mock.Mock(side_effect=ValueError('throw')) params = { 'actionstr': 'bar', 'mock_status': action_constants.LIVEACTION_STATUS_SUCCEEDED } liveaction_db = self._get_failingaction_exec_db_model(params) liveaction_db = LiveAction.add_or_update(liveaction_db) executions.create_execution_object(liveaction_db) global_runner = None original_get_runner = runner_container._get_runner def mock_get_runner(*args, **kwargs): global global_runner runner = original_get_runner(*args, **kwargs) global_runner = runner return runner runner_container._get_runner = mock_get_runner # Note: We can't assert here that post_run hasn't been called yet because runner instance # is only instantiated later inside dispatch method runner_container.dispatch(liveaction_db) self.assertTrue(global_runner.post_run_called)
def request(liveaction): """ Request an action execution. :return: (liveaction, execution) :rtype: tuple """ # Use the user context from the parent action execution. Subtasks in a workflow # action can be invoked by a system user and so we want to use the user context # from the original workflow action. if getattr(liveaction, 'context', None) and 'parent' in liveaction.context: parent_user = liveaction.context['parent'].get('user', None) if parent_user: liveaction.context['user'] = parent_user # Validate action. action_db = action_utils.get_action_by_ref(liveaction.action) if not action_db: raise ValueError('Action "%s" cannot be found.' % liveaction.action) if not action_db.enabled: raise ValueError('Unable to execute. Action "%s" is disabled.' % liveaction.action) runnertype_db = action_utils.get_runnertype_by_name(action_db.runner_type['name']) if not hasattr(liveaction, 'parameters'): liveaction.parameters = dict() # Validate action parameters. schema = util_schema.get_parameter_schema(action_db) validator = util_schema.get_validator() util_schema.validate(liveaction.parameters, schema, validator, use_default=True) # validate that no immutable params are being overriden. Although possible to # ignore the override it is safer to inform the user to avoid surprises. immutables = _get_immutable_params(action_db.parameters) immutables.extend(_get_immutable_params(runnertype_db.runner_parameters)) overridden_immutables = [p for p in six.iterkeys(liveaction.parameters) if p in immutables] if len(overridden_immutables) > 0: raise ValueError('Override of immutable parameter(s) %s is unsupported.' % str(overridden_immutables)) # Set notification settings for action. # XXX: There are cases when we don't want notifications to be sent for a particular # execution. So we should look at liveaction.parameters['notify'] # and not set liveaction.notify. if action_db.notify: liveaction.notify = action_db.notify # Write to database and send to message queue. liveaction.status = action_constants.LIVEACTION_STATUS_REQUESTED liveaction.start_timestamp = date_utils.get_datetime_utc_now() # Publish creation after both liveaction and actionexecution are created. liveaction = LiveAction.add_or_update(liveaction, publish=False) # Get trace_db if it exists. This could throw. If it throws, we have to cleanup # liveaction object so we don't see things in requested mode. trace_db = None try: _, trace_db = trace_service.get_trace_db_by_live_action(liveaction) except StackStormDBObjectNotFoundError as e: _cleanup_liveaction(liveaction) raise TraceNotFoundException(str(e)) execution = executions.create_execution_object(liveaction, publish=False) if trace_db: trace_service.add_or_update_given_trace_db( trace_db=trace_db, action_executions=[str(execution.id)]) # Assume that this is a creation. LiveAction.publish_create(liveaction) LiveAction.publish_status(liveaction) ActionExecution.publish_create(execution) extra = {'liveaction_db': liveaction, 'execution_db': execution} LOG.audit('Action execution requested. LiveAction.id=%s, ActionExecution.id=%s' % (liveaction.id, execution.id), extra=extra) return liveaction, execution
def request(liveaction): """ Request an action execution. :return: (liveaction, execution) :rtype: tuple """ # Use the user context from the parent action execution. Subtasks in a workflow # action can be invoked by a system user and so we want to use the user context # from the original workflow action. if getattr(liveaction, "context", None) and "parent" in liveaction.context: parent_user = liveaction.context["parent"].get("user", None) if parent_user: liveaction.context["user"] = parent_user # Validate action. action_db = action_utils.get_action_by_ref(liveaction.action) if not action_db: raise ValueError('Action "%s" cannot be found.' % liveaction.action) if not action_db.enabled: raise ValueError('Unable to execute. Action "%s" is disabled.' % liveaction.action) runnertype_db = action_utils.get_runnertype_by_name(action_db.runner_type["name"]) if not hasattr(liveaction, "parameters"): liveaction.parameters = dict() # Validate action parameters. schema = util_schema.get_parameter_schema(action_db) validator = util_schema.get_validator() util_schema.validate(liveaction.parameters, schema, validator, use_default=True) # validate that no immutable params are being overriden. Although possible to # ignore the override it is safer to inform the user to avoid surprises. immutables = _get_immutable_params(action_db.parameters) immutables.extend(_get_immutable_params(runnertype_db.runner_parameters)) overridden_immutables = [p for p in six.iterkeys(liveaction.parameters) if p in immutables] if len(overridden_immutables) > 0: raise ValueError("Override of immutable parameter(s) %s is unsupported." % str(overridden_immutables)) # Set notification settings for action. # XXX: There are cases when we don't want notifications to be sent for a particular # execution. So we should look at liveaction.parameters['notify'] # and not set liveaction.notify. if action_db.notify: liveaction.notify = action_db.notify # Write to database and send to message queue. liveaction.status = action_constants.LIVEACTION_STATUS_REQUESTED liveaction.start_timestamp = date_utils.get_datetime_utc_now() # Publish creation after both liveaction and actionexecution are created. liveaction = LiveAction.add_or_update(liveaction, publish=False) execution = executions.create_execution_object(liveaction, publish=False) # Assume that this is a creation. LiveAction.publish_create(liveaction) LiveAction.publish_status(liveaction) ActionExecution.publish_create(execution) extra = {"liveaction_db": liveaction, "execution_db": execution} LOG.audit( "Action execution requested. LiveAction.id=%s, ActionExecution.id=%s" % (liveaction.id, execution.id), extra=extra, ) return liveaction, execution
def create_request(liveaction, action_db=None, runnertype_db=None): """ Create an action execution. :param action_db: Action model to operate one. If not provided, one is retrieved from the database using values from "liveaction". :type action_db: :class:`ActionDB` :param runnertype_db: Runner model to operate one. If not provided, one is retrieved from the database using values from "liveaction". :type runnertype_db: :class:`RunnerTypeDB` :return: (liveaction, execution) :rtype: tuple """ # We import this here to avoid conflicts w/ runners that might import this # file since the runners don't have the config context by default. from st2common.metrics.base import get_driver # Use the user context from the parent action execution. Subtasks in a workflow # action can be invoked by a system user and so we want to use the user context # from the original workflow action. parent_context = executions.get_parent_context(liveaction) or {} parent_user = parent_context.get('user', None) if parent_user: liveaction.context['user'] = parent_user # Validate action if not action_db: action_db = action_utils.get_action_by_ref(liveaction.action) if not action_db: raise ValueError('Action "%s" cannot be found.' % liveaction.action) if not action_db.enabled: raise ValueError('Unable to execute. Action "%s" is disabled.' % liveaction.action) if not runnertype_db: runnertype_db = action_utils.get_runnertype_by_name(action_db.runner_type['name']) if not hasattr(liveaction, 'parameters'): liveaction.parameters = dict() # For consistency add pack to the context here in addition to RunnerContainer.dispatch() method liveaction.context['pack'] = action_db.pack # Validate action parameters. schema = util_schema.get_schema_for_action_parameters(action_db, runnertype_db) validator = util_schema.get_validator() util_schema.validate(liveaction.parameters, schema, validator, use_default=True, allow_default_none=True) # validate that no immutable params are being overriden. Although possible to # ignore the override it is safer to inform the user to avoid surprises. immutables = _get_immutable_params(action_db.parameters) immutables.extend(_get_immutable_params(runnertype_db.runner_parameters)) overridden_immutables = [p for p in six.iterkeys(liveaction.parameters) if p in immutables] if len(overridden_immutables) > 0: raise ValueError('Override of immutable parameter(s) %s is unsupported.' % str(overridden_immutables)) # Set notification settings for action. # XXX: There are cases when we don't want notifications to be sent for a particular # execution. So we should look at liveaction.parameters['notify'] # and not set liveaction.notify. if not _is_notify_empty(action_db.notify): liveaction.notify = action_db.notify # Write to database and send to message queue. liveaction.status = action_constants.LIVEACTION_STATUS_REQUESTED liveaction.start_timestamp = date_utils.get_datetime_utc_now() # Set the "action_is_workflow" attribute liveaction.action_is_workflow = action_db.is_workflow() # Publish creation after both liveaction and actionexecution are created. liveaction = LiveAction.add_or_update(liveaction, publish=False) # Get trace_db if it exists. This could throw. If it throws, we have to cleanup # liveaction object so we don't see things in requested mode. trace_db = None try: _, trace_db = trace_service.get_trace_db_by_live_action(liveaction) except db_exc.StackStormDBObjectNotFoundError as e: _cleanup_liveaction(liveaction) raise trace_exc.TraceNotFoundException(six.text_type(e)) execution = executions.create_execution_object(liveaction=liveaction, action_db=action_db, runnertype_db=runnertype_db, publish=False) if trace_db: trace_service.add_or_update_given_trace_db( trace_db=trace_db, action_executions=[ trace_service.get_trace_component_for_action_execution(execution, liveaction) ]) get_driver().inc_counter('action.executions.%s' % (liveaction.status)) return liveaction, execution