def test_chained_executions(self): liveaction = LiveActionDB(action='core.chain') liveaction, _ = action_service.request(liveaction) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(liveaction.status, action_constants.LIVEACTION_STATUS_FAILED) execution = self._get_action_execution(liveaction__id=str(liveaction.id), raise_exception=True) action = action_utils.get_action_by_ref('core.chain') self.assertDictEqual(execution.action, vars(ActionAPI.from_model(action))) runner = RunnerType.get_by_name(action.runner_type['name']) self.assertDictEqual(execution.runner, vars(RunnerTypeAPI.from_model(runner))) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(execution.start_timestamp, liveaction.start_timestamp) self.assertEqual(execution.end_timestamp, liveaction.end_timestamp) self.assertEqual(execution.result, liveaction.result) self.assertEqual(execution.status, liveaction.status) self.assertEqual(execution.context, liveaction.context) self.assertEqual(execution.liveaction['callback'], liveaction.callback) self.assertEqual(execution.liveaction['action'], liveaction.action) self.assertGreater(len(execution.children), 0) for child in execution.children: record = ActionExecution.get(id=child, raise_exception=True) self.assertEqual(record.parent, str(execution.id)) self.assertEqual(record.action['name'], 'local') self.assertEqual(record.runner['name'], 'run-local')
def update_status(liveaction, new_status, publish=True): if liveaction.status == new_status: return liveaction old_status = liveaction.status liveaction = action_utils.update_liveaction_status( status=new_status, liveaction_id=liveaction.id, publish=False) action_execution = executions.update_execution(liveaction) msg = ('The status of action execution is changed from %s to %s. ' '<LiveAction.id=%s, ActionExecution.id=%s>' % (old_status, new_status, liveaction.id, action_execution.id)) extra = { 'action_execution_db': action_execution, 'liveaction_db': liveaction } LOG.audit(msg, extra=extra) LOG.info(msg) if publish: LiveAction.publish_status(liveaction) return liveaction
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 _purge_models(execution_db): liveaction_id = execution_db.liveaction.get("id", None) if not liveaction_id: LOG.error("Invalid LiveAction id. Skipping delete: %s", execution_db) liveaction_db = None try: liveaction_db = LiveAction.get_by_id(liveaction_id) except: LOG.exception("LiveAction with id: %s not found. Skipping delete.", liveaction_id) else: global DELETED_COUNT DELETED_COUNT += 1 try: ActionExecution.delete(execution_db) except: LOG.exception("Exception deleting Execution model: %s", execution_db) else: if liveaction_db: try: LiveAction.delete(liveaction_db) except: LOG.exception("Zombie LiveAction left in db: %s.", liveaction_db)
def test_keep_state_object_on_error_at_update_result(self): tracker = self._get_tracker() querier = tracker.get_querier('test_querymodule') querier._delete_state_object = mock.Mock(return_value=None) querier.delete_state_object_on_error = False # Ensure state objects are present. state1 = ActionExecutionState.get_by_id(ResultsTrackerTests.states['state1.yaml'].id) state2 = ActionExecutionState.get_by_id(ResultsTrackerTests.states['state2.yaml'].id) self.assertIsNotNone(state1) self.assertIsNotNone(state2) with mock.patch.object( querier.__class__, '_update_action_results', mock.MagicMock(side_effect=Exception('Mock update exception.'))): tracker._bootstrap() eventlet.sleep(1) exec_id = str(ResultsTrackerTests.states['state1.yaml'].execution_id) exec_db = LiveAction.get_by_id(exec_id) self.assertDictEqual(exec_db.result, {}) exec_id = str(ResultsTrackerTests.states['state2.yaml'].execution_id) exec_db = LiveAction.get_by_id(exec_id) self.assertDictEqual(exec_db.result, {}) tracker.shutdown() # Ensure deletes are not called. querier._delete_state_object.assert_not_called()
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 _purge_action_models(execution_db): liveaction_id = execution_db.liveaction['id'] if not liveaction_id: print('Invalid LiveAction id. Skipping delete: %s', execution_db) liveaction_db = None try: liveaction_db = LiveAction.get_by_id(liveaction_id) except: print('LiveAction with id: %s not found. Skipping delete.', liveaction_id) else: global DELETED_COUNT DELETED_COUNT += 1 try: ActionExecution.delete(execution_db) except Exception as e: print('Exception deleting Execution model: %s, exception: %s', execution_db, str(e)) else: try: LiveAction.delete(liveaction_db) except Exception as e: print('Zombie LiveAction left in db: %s. Exception: %s', liveaction_db, str(e))
def update_status(liveaction, new_status, result=None, publish=True): if liveaction.status == new_status: return liveaction old_status = liveaction.status liveaction = action_utils.update_liveaction_status( status=new_status, result=result, liveaction_id=liveaction.id, publish=False ) action_execution = executions.update_execution(liveaction) msg = "The status of action execution is changed from %s to %s. " "<LiveAction.id=%s, ActionExecution.id=%s>" % ( old_status, new_status, liveaction.id, action_execution.id, ) extra = {"action_execution_db": action_execution, "liveaction_db": liveaction} LOG.audit(msg, extra=extra) LOG.info(msg) if publish: LiveAction.publish_status(liveaction) return liveaction
def test_cancel_subworkflow_action(self): liveaction1 = LiveActionDB(action=WF2_NAME, parameters=ACTION_PARAMS) liveaction1, execution1 = action_service.request(liveaction1) liveaction1 = LiveAction.get_by_id(str(liveaction1.id)) self.assertEqual(liveaction1.status, action_constants.LIVEACTION_STATUS_RUNNING) liveaction2 = LiveActionDB(action=WF1_NAME, parameters=ACTION_PARAMS) liveaction2, execution2 = action_service.request(liveaction2) liveaction2 = LiveAction.get_by_id(str(liveaction2.id)) self.assertEqual(liveaction2.status, action_constants.LIVEACTION_STATUS_RUNNING) # Mock the children of the parent execution to make this # test case has subworkflow execution. with mock.patch.object( ActionExecutionDB, 'children', new_callable=mock.PropertyMock) as action_ex_children_mock: action_ex_children_mock.return_value = [execution2.id] mistral_context = liveaction1.context.get('mistral', None) self.assertIsNotNone(mistral_context) self.assertEqual(mistral_context['execution_id'], WF2_EXEC.get('id')) self.assertEqual(mistral_context['workflow_name'], WF2_EXEC.get('workflow_name')) requester = cfg.CONF.system_user.user liveaction1, execution1 = action_service.request_cancellation(liveaction1, requester) self.assertTrue(executions.ExecutionManager.update.called) self.assertEqual(executions.ExecutionManager.update.call_count, 2) calls = [ mock.call(WF2_EXEC.get('id'), 'CANCELLED'), mock.call(WF1_EXEC.get('id'), 'CANCELLED') ] executions.ExecutionManager.update.assert_has_calls(calls, any_order=False)
def test_execution_cancellation(self): tracker = self._get_tracker() querier = tracker.get_querier('test_querymodule') querier._delete_state_object = mock.Mock(return_value=None) runners_utils.invoke_post_run = mock.Mock(return_value=None) # Ensure state objects are present. state1 = ActionExecutionState.get_by_id(ResultsTrackerTests.states['state1.yaml'].id) state2 = ActionExecutionState.get_by_id(ResultsTrackerTests.states['state2.yaml'].id) self.assertIsNotNone(state1) self.assertIsNotNone(state2) with mock.patch.object( querier.__class__, 'query', mock.MagicMock(return_value=(action_constants.LIVEACTION_STATUS_CANCELED, {}))): tracker._bootstrap() eventlet.sleep(2) exec_id = str(ResultsTrackerTests.states['state1.yaml'].execution_id) exec_db = LiveAction.get_by_id(exec_id) self.assertDictEqual(exec_db.result, {}) exec_id = str(ResultsTrackerTests.states['state2.yaml'].execution_id) exec_db = LiveAction.get_by_id(exec_id) self.assertDictEqual(exec_db.result, {}) tracker.shutdown() # Ensure deletes are called. self.assertEqual(2, querier._delete_state_object.call_count) # Ensure invoke_post_run is called. self.assertEqual(2, runners_utils.invoke_post_run.call_count)
def test_resume(self): # Launch the workflow execution. liveaction = LiveActionDB(action=WF1_NAME, parameters=ACTION_PARAMS) liveaction, execution = action_service.request(liveaction) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(liveaction.status, action_constants.LIVEACTION_STATUS_RUNNING) mistral_context = liveaction.context.get('mistral', None) self.assertIsNotNone(mistral_context) self.assertEqual(mistral_context['execution_id'], WF1_EXEC.get('id')) self.assertEqual(mistral_context['workflow_name'], WF1_EXEC.get('workflow_name')) # Pause the workflow execution. requester = cfg.CONF.system_user.user liveaction, execution = action_service.request_pause(liveaction, requester) executions.ExecutionManager.update.assert_called_with(WF1_EXEC.get('id'), 'PAUSED') liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(liveaction.status, action_constants.LIVEACTION_STATUS_PAUSING) # Manually update the liveaction from pausing to paused. The paused state # is usually updated by the mistral querier. action_service.update_status(liveaction, action_constants.LIVEACTION_STATUS_PAUSED) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(liveaction.status, action_constants.LIVEACTION_STATUS_PAUSED) # Resume the workflow execution. liveaction, execution = action_service.request_resume(liveaction, requester) executions.ExecutionManager.update.assert_called_with(WF1_EXEC.get('id'), 'RUNNING') liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(liveaction.status, action_constants.LIVEACTION_STATUS_RUNNING)
def test_chain_cancel_cascade_to_subworkflow(self): # A temp file is created during test setup. Ensure the temp file exists. # The test action chain will stall until this file is deleted. This gives # the unit test a moment to run any test related logic. path = self.temp_file_path self.assertTrue(os.path.exists(path)) action = TEST_PACK + '.' + 'test_cancel_with_subworkflow' params = {'tempfile': path, 'message': 'foobar'} liveaction = LiveActionDB(action=action, parameters=params) liveaction, execution = action_service.request(liveaction) liveaction = LiveAction.get_by_id(str(liveaction.id)) # Wait until the liveaction is running. liveaction = self._wait_on_status(liveaction, action_constants.LIVEACTION_STATUS_RUNNING) # Wait for subworkflow to register. execution = self._wait_for_children(execution) self.assertEqual(len(execution.children), 1) # Wait until the subworkflow is running. task1_exec = ActionExecution.get_by_id(execution.children[0]) task1_live = LiveAction.get_by_id(task1_exec.liveaction['id']) task1_live = self._wait_on_status(task1_live, action_constants.LIVEACTION_STATUS_RUNNING) # Request action chain to cancel. liveaction, execution = action_service.request_cancellation(liveaction, USERNAME) # Wait until the liveaction is canceling. liveaction = self._wait_on_status(liveaction, action_constants.LIVEACTION_STATUS_CANCELING) self.assertEqual(len(execution.children), 1) # Wait until the subworkflow is canceling. task1_exec = ActionExecution.get_by_id(execution.children[0]) task1_live = LiveAction.get_by_id(task1_exec.liveaction['id']) task1_live = self._wait_on_status(task1_live, action_constants.LIVEACTION_STATUS_CANCELING) # Delete the temporary file that the action chain is waiting on. os.remove(path) self.assertFalse(os.path.exists(path)) # Wait until the liveaction is canceled. liveaction = self._wait_on_status(liveaction, action_constants.LIVEACTION_STATUS_CANCELED) self.assertEqual(len(execution.children), 1) # Wait until the subworkflow is canceled. task1_exec = ActionExecution.get_by_id(execution.children[0]) task1_live = LiveAction.get_by_id(task1_exec.liveaction['id']) task1_live = self._wait_on_status(task1_live, action_constants.LIVEACTION_STATUS_CANCELED) # Wait for non-blocking threads to complete. Ensure runner is not running. MockLiveActionPublisherNonBlocking.wait_all() # Check liveaction result. self.assertIn('tasks', liveaction.result) self.assertEqual(len(liveaction.result['tasks']), 1) subworkflow = liveaction.result['tasks'][0] self.assertEqual(len(subworkflow['result']['tasks']), 1) self.assertEqual(subworkflow['state'], action_constants.LIVEACTION_STATUS_CANCELED)
def _update_to_scheduled(liveaction_db, execution_queue_item_db): liveaction_id = str(liveaction_db.id) queue_item_id = str(execution_queue_item_db.id) extra = { 'liveaction_id': liveaction_id, 'liveaction_status': liveaction_db.status, 'queue_item_id': queue_item_id } # Update liveaction status to "scheduled". LOG.info('Liveaction (%s) Status Update to Scheduled 1: %s (%s)', liveaction_id, liveaction_db.status, queue_item_id, extra=extra) if liveaction_db.status in [action_constants.LIVEACTION_STATUS_REQUESTED, action_constants.LIVEACTION_STATUS_DELAYED]: liveaction_db = action_service.update_status( liveaction_db, action_constants.LIVEACTION_STATUS_SCHEDULED, publish=False) # Publish the "scheduled" status here manually. Otherwise, there could be a # race condition with the update of the action_execution_db if the execution # of the liveaction completes first. LiveAction.publish_status(liveaction_db) extra['liveaction_status'] = liveaction_db.status # Delete execution queue entry only after status is published. ActionExecutionSchedulingQueue.delete(execution_queue_item_db) LOG.info('Liveaction (%s) Status Update to Scheduled 2: %s (%s)', liveaction_id, liveaction_db.status, queue_item_id)
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_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_chained_executions(self): with mock.patch("st2common.runners.register_runner", mock.MagicMock(return_value=action_chain_runner)): liveaction = LiveActionDB(action="executions.chain") liveaction, _ = action_service.request(liveaction) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(liveaction.status, action_constants.LIVEACTION_STATUS_FAILED) execution = self._get_action_execution(liveaction__id=str(liveaction.id), raise_exception=True) action = action_utils.get_action_by_ref("executions.chain") self.assertDictEqual(execution.action, vars(ActionAPI.from_model(action))) runner = RunnerType.get_by_name(action.runner_type["name"]) self.assertDictEqual(execution.runner, vars(RunnerTypeAPI.from_model(runner))) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(execution.start_timestamp, liveaction.start_timestamp) self.assertEqual(execution.end_timestamp, liveaction.end_timestamp) self.assertEqual(execution.result, liveaction.result) self.assertEqual(execution.status, liveaction.status) self.assertEqual(execution.context, liveaction.context) self.assertEqual(execution.liveaction["callback"], liveaction.callback) self.assertEqual(execution.liveaction["action"], liveaction.action) self.assertGreater(len(execution.children), 0) for child in execution.children: record = ActionExecution.get(id=child, raise_exception=True) self.assertEqual(record.parent, str(execution.id)) self.assertEqual(record.action["name"], "local") self.assertEqual(record.runner["name"], "run-local")
def 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_basic_cancel(self): liveaction = LiveActionDB(action='executions.local', parameters={'cmd': 'uname -a'}) liveaction, _ = action_service.request(liveaction) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(liveaction.status, action_constants.LIVEACTION_STATUS_RUNNING) # Cancel execution. action_service.request_cancellation(liveaction, cfg.CONF.system_user.user) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(liveaction.status, action_constants.LIVEACTION_STATUS_CANCELED) self.assertDictEqual(liveaction.result, MOCK_RESULT)
def process(self, request): """Schedules the LiveAction and publishes the request to the appropriate action runner(s). LiveAction in statuses other than "requested" are ignored. :param request: Action execution request. :type request: ``st2common.models.db.liveaction.LiveActionDB`` """ if request.status != action_constants.LIVEACTION_STATUS_REQUESTED: LOG.info( '%s is ignoring %s (id=%s) with "%s" status.', self.__class__.__name__, type(request), request.id, request.status, ) return try: liveaction_db = action_utils.get_liveaction_by_id(request.id) except StackStormDBObjectNotFoundError: LOG.exception("Failed to find liveaction %s in the database.", request.id) raise # Apply policies defined for the action. liveaction_db = self._apply_pre_run_policies(liveaction_db=liveaction_db) # Exit if the status of the request is no longer runnable. # The status could have be changed by one of the policies. if liveaction_db.status not in [ action_constants.LIVEACTION_STATUS_REQUESTED, action_constants.LIVEACTION_STATUS_SCHEDULED, ]: LOG.info( '%s is ignoring %s (id=%s) with "%s" status after policies are applied.', self.__class__.__name__, type(request), request.id, liveaction_db.status, ) return # Update liveaction status to "scheduled". if liveaction_db.status == action_constants.LIVEACTION_STATUS_REQUESTED: liveaction_db = action_service.update_status( liveaction_db, action_constants.LIVEACTION_STATUS_SCHEDULED, publish=False ) # Publish the "scheduled" status here manually. Otherwise, there could be a # race condition with the update of the action_execution_db if the execution # of the liveaction completes first. LiveAction.publish_status(liveaction_db)
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_invalid_trace_id_provided(self): liveactions = LiveAction.get_all() self.assertEqual(len(liveactions), 1) # fixtures loads it. self.traceable_liveaction['context']['trace_context'] = {'id_': 'balleilaka'} self.assertRaises(TraceNotFoundException, action_services.request, self.traceable_liveaction) # Make sure no liveactions are left behind liveactions = LiveAction.get_all() self.assertEqual(len(liveactions), 0)
def test_cancel_on_task_action_concurrency_by_attr(self): # Delete other policies in the test pack to avoid conflicts. required_policy = 'mistral_tests.cancel_on_concurrency_by_attr' self._drop_all_other_policies(required_policy) # Get threshold from the policy. policy = Policy.get_by_ref(required_policy) threshold = policy.parameters.get('threshold', 0) self.assertGreater(threshold, 0) params = {'friend': 'grande animalerie'} # Launch instances of the workflow up to threshold. for i in range(0, threshold): liveaction = LiveActionDB(action=WF1_NAME, parameters=params) liveaction, execution1 = action_service.request(liveaction) liveaction = LiveAction.get_by_id(str(liveaction.id)) liveaction = self._wait_on_status( liveaction, action_constants.LIVEACTION_STATUS_RUNNING ) # Check number of running instances running = LiveAction.count( action=WF1_NAME, status=action_constants.LIVEACTION_STATUS_RUNNING, parameters__friend=params['friend']) self.assertEqual(running, threshold) # Mock the mistral runner cancel method to assert cancel is called. mistral_runner_cls = runners.get_runner('mistral-v2').__class__ mock_cancel_return_value = (action_constants.LIVEACTION_STATUS_CANCELING, None, None) mock_cancel = mock.MagicMock(return_value=mock_cancel_return_value) with mock.patch.object(mistral_runner_cls, 'cancel', mock_cancel): # Launch another instance of the workflow with mistral callback defined # to indicate that this is executed under a workflow. callback = { 'source': MISTRAL_RUNNER_NAME, 'url': 'http://127.0.0.1:8989/v2/action_executions/12345' } liveaction2 = LiveActionDB(action=WF1_NAME, parameters=params, callback=callback) liveaction2, execution2 = action_service.request(liveaction2) liveaction2 = LiveAction.get_by_id(str(liveaction2.id)) # Assert cancel has been called. liveaction2 = self._wait_on_status( liveaction2, action_constants.LIVEACTION_STATUS_CANCELING ) mistral_runner_cls.cancel.assert_called_once_with()
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_failed_cancel(self): liveaction = LiveActionDB(action='executions.local', parameters={'cmd': 'uname -a'}) liveaction, _ = action_service.request(liveaction) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(liveaction.status, action_constants.LIVEACTION_STATUS_RUNNING) # Cancel execution. action_service.request_cancellation(liveaction, cfg.CONF.system_user.user) # Cancellation failed and execution state remains "canceling". ActionRunner.cancel.assert_called_once_with() liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(liveaction.status, action_constants.LIVEACTION_STATUS_CANCELING)
def test_no_double_entries(self): self.reset() liveaction_db = self._create_liveaction_db() LiveAction.publish_status(liveaction_db) LiveAction.publish_status(liveaction_db) schedule_q_db = self.scheduling_queue._get_next_execution() self.assertIsNotNone(schedule_q_db) schedule_q_db = self.scheduling_queue._get_next_execution() self.assertIsNone(schedule_q_db)
def test_bootstrap(self): tracker = results_tracker.get_tracker() tracker._bootstrap() eventlet.sleep(0.2) exec_id = str(ResultsTrackerTests.states['state1.yaml'].execution_id) exec_db = LiveAction.get_by_id(exec_id) self.assertTrue(exec_db.result['called_with'][exec_id] is not None, exec_db.result) exec_id = str(ResultsTrackerTests.states['state2.yaml'].execution_id) exec_db = LiveAction.get_by_id(exec_id) self.assertTrue(exec_db.result['called_with'][exec_id] is not None, exec_db.result) tracker.shutdown()
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_no_processing_of_non_requested_actions(self): self.reset() for status in action_constants.LIVEACTION_STATUSES: liveaction_db = self._create_liveaction_db(status=status) LiveAction.publish_status(liveaction_db) schedule_q_db = self.scheduling_queue._get_next_execution() if status is action_constants.LIVEACTION_STATUS_REQUESTED: self.assertIsNotNone(schedule_q_db) else: self.assertIsNone(schedule_q_db)
def test_noop_cancel(self): liveaction = LiveActionDB(action='executions.local', parameters={'cmd': 'uname -a'}) liveaction, _ = action_service.request(liveaction) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(liveaction.status, action_constants.LIVEACTION_STATUS_REQUESTED) # Cancel execution. action_service.request_cancellation(liveaction, cfg.CONF.system_user.user) # Cancel is only called when liveaction is still in running state. # Otherwise, the cancellation is only a state change. self.assertFalse(ActionRunner.cancel.called) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(liveaction.status, action_constants.LIVEACTION_STATUS_CANCELED)
def test_callback_paused_state(self): local_runner_cls = self.get_runner_class('local_runner') local_run_result = (action_constants.LIVEACTION_STATUS_PAUSED, NON_EMPTY_RESULT, None) local_runner_cls.run = mock.Mock(return_value=local_run_result) expected_mistral_status = self.status_map[local_run_result[0]] liveaction = LiveActionDB( action='core.local', parameters={'cmd': 'uname -a'}, callback={ 'source': MISTRAL_RUNNER_NAME, 'url': 'http://127.0.0.1:8989/v2/action_executions/12345' }) liveaction, execution = action_service.request(liveaction) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(liveaction.status, local_run_result[0]) action_executions.ActionExecutionManager.update.assert_called_with( '12345', state=expected_mistral_status, output=NON_EMPTY_RESULT)
def test_launch_workflow_with_notifications(self): notify_data = {'on_complete': {'routes': ['slack'], 'message': '"@channel: Action succeeded."', 'data': {}}} MistralRunner.entry_point = mock.PropertyMock(return_value=WF1_YAML_FILE_PATH) liveaction = LiveActionDB(action=WF1_NAME, parameters=ACTION_PARAMS, notify=notify_data) liveaction, execution = action_service.request(liveaction) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(liveaction.status, action_constants.LIVEACTION_STATUS_RUNNING) mistral_context = liveaction.context.get('mistral', None) self.assertIsNotNone(mistral_context) self.assertEqual(mistral_context['execution_id'], WF1_EXEC.get('id')) self.assertEqual(mistral_context['workflow_name'], WF1_EXEC.get('workflow_name')) workflow_input = copy.deepcopy(ACTION_PARAMS) workflow_input.update({'count': '3'}) env = { 'st2_execution_id': str(execution.id), 'st2_liveaction_id': str(liveaction.id), 'st2_action_api_url': 'http://0.0.0.0:9101/v1', '__actions': { 'st2.action': { 'st2_context': { 'api_url': 'http://0.0.0.0:9101/v1', 'endpoint': 'http://0.0.0.0:9101/v1/actionexecutions', 'parent': { 'execution_id': str(execution.id) }, 'notify': NotificationsHelper.from_model(liveaction.notify), 'skip_notify_tasks': [] } } } } executions.ExecutionManager.create.assert_called_with( WF1_NAME, workflow_input=workflow_input, env=env)
def test_resume_option_reset_tasks(self): patched_mistral_runner = runners.get_runner('mistral-v2').__class__ mock_resume_result = (action_constants.LIVEACTION_STATUS_RUNNING, { 'tasks': [] }, { 'execution_id': str(uuid.uuid4()) }) with mock.patch.object( patched_mistral_runner, 'resume_workflow', mock.MagicMock(return_value=mock_resume_result)): liveaction1 = LiveActionDB(action=WF1_NAME, parameters=ACTION_PARAMS) liveaction1, execution1 = action_service.request(liveaction1) self.assertFalse(patched_mistral_runner.resume_workflow.called) # Rerun the execution. context = { 're-run': { 'ref': execution1.id, 'tasks': ['x', 'y'], 'reset': ['y'] } } liveaction2 = LiveActionDB(action=WF1_NAME, parameters=ACTION_PARAMS, context=context) liveaction2, execution2 = action_service.request(liveaction2) liveaction2 = LiveAction.get_by_id(str(liveaction2.id)) self.assertEqual(liveaction2.status, action_constants.LIVEACTION_STATUS_RUNNING) task_specs = {'x': {'reset': False}, 'y': {'reset': True}} patched_mistral_runner.resume_workflow.assert_called_with( ex_ref=execution1, task_specs=task_specs)
def test_chain_cancel(self): # A temp file is created during test setup. Ensure the temp file exists. # The test action chain will stall until this file is deleted. This gives # the unit test a moment to run any test related logic. path = self.temp_file_path self.assertTrue(os.path.exists(path)) action = TEST_PACK + "." + "test_cancel" params = {"tempfile": path, "message": "foobar"} liveaction = LiveActionDB(action=action, parameters=params) liveaction, execution = action_service.request(liveaction) liveaction = LiveAction.get_by_id(str(liveaction.id)) # Wait until the liveaction is running. liveaction = self._wait_on_status( liveaction, action_constants.LIVEACTION_STATUS_RUNNING) # Request action chain to cancel. liveaction, execution = action_service.request_cancellation( liveaction, USERNAME) # Wait until the liveaction is canceling. liveaction = self._wait_on_status( liveaction, action_constants.LIVEACTION_STATUS_CANCELING) # Delete the temporary file that the action chain is waiting on. os.remove(path) self.assertFalse(os.path.exists(path)) # Wait until the liveaction is canceled. liveaction = self._wait_on_status( liveaction, action_constants.LIVEACTION_STATUS_CANCELED) # Wait for non-blocking threads to complete. Ensure runner is not running. MockLiveActionPublisherNonBlocking.wait_all() # Check liveaction result. self.assertIn("tasks", liveaction.result) self.assertEqual(len(liveaction.result["tasks"]), 1)
def process(self, execution_db): execution_id = str(execution_db.id) extra = {'execution': execution_db} LOG.debug('Processing execution %s', execution_id, extra=extra) if execution_db.status not in LIVEACTION_COMPLETED_STATES: LOG.debug( 'Skipping processing of execution %s since it\'s not in a completed state' % (execution_id), extra=extra) return liveaction_id = execution_db.liveaction['id'] liveaction_db = LiveAction.get_by_id(liveaction_id) self._apply_post_run_policies(liveaction_db=liveaction_db) if liveaction_db.notify is not None: self._post_notify_triggers(liveaction_db=liveaction_db, execution_db=execution_db) self._post_generic_trigger(liveaction_db=liveaction_db, execution_db=execution_db)
def test_callback_retry(self): cfg.CONF.set_default('max_attempts', 3, group='mistral') cfg.CONF.set_default('retry_wait', 0, group='mistral') liveaction = LiveActionDB( action='core.local', parameters={'cmd': 'uname -a'}, callback={ 'source': 'mistral', 'url': 'http://localhost:8989/v2/action_executions/12345' }) liveaction, execution = action_service.request(liveaction) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(liveaction.status, action_constants.LIVEACTION_STATUS_SUCCEEDED) calls = [ call('12345', state='SUCCESS', output=NON_EMPTY_RESULT) for i in range(0, 2) ] action_executions.ActionExecutionManager.update.assert_has_calls(calls)
def process(self, execution_db): execution_id = str(execution_db.id) extra = {'execution': execution_db} LOG.debug('Processing action execution "%s".', execution_id, extra=extra) # Get the corresponding liveaction record. liveaction_db = LiveAction.get_by_id(execution_db.liveaction['id']) if execution_db.status in LIVEACTION_COMPLETED_STATES: # If the action execution is executed under an orquesta workflow, policies for the # action execution will be applied by the workflow engine. A policy may affect the # final state of the action execution thereby impacting the state of the workflow. if not workflow_service.is_action_execution_under_workflow_context(execution_db): with CounterWithTimer(key='notifier.apply_post_run_policies'): policy_service.apply_post_run_policies(liveaction_db) if liveaction_db.notify: with CounterWithTimer(key='notifier.notify_trigger.post'): self._post_notify_triggers(liveaction_db=liveaction_db, execution_db=execution_db) self._post_generic_trigger(liveaction_db=liveaction_db, execution_db=execution_db)
def test_resume_option(self): MistralRunner.entry_point = mock.PropertyMock( return_value=WF1_YAML_FILE_PATH) liveaction1 = LiveActionDB(action=WF1_NAME, parameters=ACTION_PARAMS) liveaction1, execution1 = action_service.request(liveaction1) self.assertFalse(MistralRunner.resume.called) # Rerun the execution. context = {'re-run': {'ref': execution1.id, 'tasks': ['x']}} liveaction2 = LiveActionDB(action=WF1_NAME, parameters=ACTION_PARAMS, context=context) liveaction2, execution2 = action_service.request(liveaction2) liveaction2 = LiveAction.get_by_id(str(liveaction2.id)) self.assertEqual(liveaction2.status, action_constants.LIVEACTION_STATUS_RUNNING) task_specs = {'x': {'reset': False}} MistralRunner.resume.assert_called_with(ex_ref=execution1, task_specs=task_specs)
def test_callback_retry_exhausted(self): local_runner_cls = self.get_runner_class('local_runner') local_run_result = (action_constants.LIVEACTION_STATUS_SUCCEEDED, NON_EMPTY_RESULT, None) local_runner_cls.run = mock.Mock(return_value=local_run_result) liveaction = LiveActionDB( action='core.local', parameters={'cmd': 'uname -a'}, callback={ 'source': MISTRAL_RUNNER_NAME, 'url': 'http://127.0.0.1:8989/v2/action_executions/12345' } ) liveaction, execution = action_service.request(liveaction) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(liveaction.status, action_constants.LIVEACTION_STATUS_SUCCEEDED) # This test initially setup mock for action_executions.ActionExecutionManager.update # to fail the first 4 times and return success on the 5th times. The max attempts # is set to 3. We expect only 3 calls to pass thru the update method. calls = [call('12345', state='SUCCESS', output=NON_EMPTY_RESULT) for i in range(0, 2)] action_executions.ActionExecutionManager.update.assert_has_calls(calls)
def test_launch_workflow_with_st2_https(self): cfg.CONF.set_override('api_url', 'https://0.0.0.0:9101', group='auth') liveaction = LiveActionDB(action=WF1_NAME, parameters=ACTION_PARAMS) liveaction, execution = action_service.request(liveaction) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(liveaction.status, action_constants.LIVEACTION_STATUS_RUNNING) mistral_context = liveaction.context.get('mistral', None) self.assertIsNotNone(mistral_context) self.assertEqual(mistral_context['execution_id'], WF1_EXEC.get('id')) self.assertEqual(mistral_context['workflow_name'], WF1_EXEC.get('workflow_name')) workflow_input = copy.deepcopy(ACTION_PARAMS) workflow_input.update({'count': '3'}) env = { 'st2_execution_id': str(execution.id), 'st2_liveaction_id': str(liveaction.id), 'st2_action_api_url': 'https://0.0.0.0:9101/v1', '__actions': { 'st2.action': { 'st2_context': { 'api_url': 'https://0.0.0.0:9101/v1', 'endpoint': 'https://0.0.0.0:9101/v1/actionexecutions', 'parent': { 'pack': 'mistral_tests', 'execution_id': str(execution.id) }, 'notify': {}, 'skip_notify_tasks': [] } } } } executions.ExecutionManager.create.assert_called_with( WF1_NAME, workflow_input=workflow_input, env=env)
def test_callback_resuming_state(self): local_runner_cls = self.get_runner_class('local_runner') local_run_result = (action_constants.LIVEACTION_STATUS_RESUMING, NON_EMPTY_RESULT, None) local_runner_cls.run = mock.Mock(return_value=local_run_result) local_resume_result = (action_constants.LIVEACTION_STATUS_RUNNING, NON_EMPTY_RESULT, None) local_runner_cls.resume = mock.Mock(return_value=local_resume_result) liveaction = LiveActionDB( action='core.local', parameters={'cmd': 'uname -a'}, callback={ 'source': MISTRAL_RUNNER_NAME, 'url': 'http://127.0.0.1:8989/v2/action_executions/12345' }) liveaction, execution = action_service.request(liveaction) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(liveaction.status, local_resume_result[0]) self.assertFalse( action_executions.ActionExecutionManager.update.called)
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_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 cancel(self): mistral_ctx = self.context.get('mistral', dict()) if not mistral_ctx.get('execution_id'): raise Exception('Unable to cancel because mistral execution_id is missing.') # Cancels the main workflow execution. Any non-workflow tasks that are still # running will be allowed to complete gracefully. self._client.executions.update(mistral_ctx.get('execution_id'), 'CANCELLED') # If workflow is executed under another parent workflow, cancel the corresponding # action execution for the task in the parent workflow. if 'parent' in getattr(self, 'context', {}) and mistral_ctx.get('action_execution_id'): mistral_action_ex_id = mistral_ctx.get('action_execution_id') self._client.action_executions.update(mistral_action_ex_id, 'CANCELLED') # Identify the list of action executions that are workflows and still running. for child_exec_id in self.execution.children: child_exec = ActionExecution.get(id=child_exec_id) if (child_exec.runner['name'] in action_constants.WORKFLOW_RUNNER_TYPES and child_exec.status in action_constants.LIVEACTION_CANCELABLE_STATES): action_service.request_cancellation( LiveAction.get(id=child_exec.liveaction['id']), self.context.get('user', None) ) status = ( action_constants.LIVEACTION_STATUS_CANCELING if action_service.is_children_active(self.liveaction.id) else action_constants.LIVEACTION_STATUS_CANCELED ) return ( status, self.liveaction.result, self.liveaction.context )
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 test_launch_workflow(self): MistralRunner.entry_point = mock.PropertyMock( return_value=WF1_YAML_FILE_PATH) liveaction = LiveActionDB(action=WF1_NAME, parameters=ACTION_PARAMS) liveaction, execution = action_service.request(liveaction) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(liveaction.status, action_constants.LIVEACTION_STATUS_RUNNING) mistral_context = liveaction.context.get('mistral', None) self.assertIsNotNone(mistral_context) self.assertEqual(mistral_context['execution_id'], WF1_EXEC.get('id')) self.assertEqual(mistral_context['workflow_name'], WF1_EXEC.get('workflow_name')) workflow_input = copy.deepcopy(ACTION_PARAMS) workflow_input.update({'count': '3'}) env = { 'st2_execution_id': str(execution.id), 'st2_liveaction_id': str(liveaction.id), '__actions': { 'st2.action': { 'st2_context': { 'endpoint': 'http://0.0.0.0:9101/v1/actionexecutions', 'parent': { 'execution_id': str(execution.id) }, 'notify': {}, 'skip_notify_tasks': [] } } } } executions.ExecutionManager.create.assert_called_with( WF1_NAME, workflow_input=workflow_input, env=env)
def test_chained_executions(self): liveaction = LiveActionDB(action="executions.chain") liveaction, _ = action_service.request(liveaction) liveaction = self._wait_on_status( liveaction, action_constants.LIVEACTION_STATUS_FAILED) execution = self._get_action_execution(liveaction__id=str( liveaction.id), raise_exception=True) action = action_utils.get_action_by_ref("executions.chain") self.assertDictEqual(execution.action, vars(ActionAPI.from_model(action))) runner = RunnerType.get_by_name(action.runner_type["name"]) self.assertDictEqual(execution.runner, vars(RunnerTypeAPI.from_model(runner))) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(execution.start_timestamp, liveaction.start_timestamp) # NOTE: Timestamp of liveaction and execution may be a bit different, depending on how long # it takes to persist each object in the database self.assertEqual( execution.end_timestamp.replace(microsecond=0), liveaction.end_timestamp.replace(microsecond=0), ) self.assertEqual(execution.result, liveaction.result) self.assertEqual(execution.status, liveaction.status) self.assertEqual(execution.context, liveaction.context) self.assertEqual(execution.liveaction["callback"], liveaction.callback) self.assertEqual(execution.liveaction["action"], liveaction.action) self.assertGreater(len(execution.children), 0) for child in execution.children: record = ActionExecution.get(id=child, raise_exception=True) self.assertEqual(record.parent, str(execution.id)) self.assertEqual(record.action["name"], "local") self.assertEqual(record.runner["name"], "local-shell-cmd")
def test_save_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() model_cls = get_model_class_for_approach(approach=approach) def run_benchmark(): 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) return inserted_live_action_db inserted_live_action_db = benchmark(run_benchmark) retrieved_live_action_db = LiveAction.get_by_id(inserted_live_action_db.id) # Assert that result is correctly converted back to dict on retrieval assert inserted_live_action_db.result == data assert inserted_live_action_db == retrieved_live_action_db
def recover_delayed_executions(): coordinator = coordination.get_coordinator() 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 with coordinator.get_lock('st2-rescheduling-delayed-executions'): liveactions = LiveAction.query( status=action_constants.LIVEACTION_STATUS_DELAYED, start_timestamp__lte=dt_timeout, order_by=['start_timestamp']) if not liveactions: return LOG.info( 'There are %d liveactions that have been delayed for longer than %d seconds.', len(liveactions), cfg.CONF.scheduler.delayed_execution_recovery) # Update status to requested and publish status for each liveactions. rescheduled = 0 for instance in liveactions: try: action_service.update_status( instance, action_constants.LIVEACTION_STATUS_REQUESTED, publish=True) rescheduled += 1 except: LOG.exception( 'Unable to reschedule liveaction. <LiveAction.id=%s>', instance.id) LOG.info('Rescheduled %d out of %d delayed liveactions.', len(liveactions), rescheduled)
def test_callback_retry(self): local_runner_cls = self.get_runner_class('local_runner') local_run_result = (action_constants.LIVEACTION_STATUS_SUCCEEDED, NON_EMPTY_RESULT, None) local_runner_cls.run = mock.Mock(return_value=local_run_result) liveaction = LiveActionDB( action='core.local', parameters={'cmd': 'uname -a'}, callback={ 'source': MISTRAL_RUNNER_NAME, 'url': 'http://127.0.0.1:8989/v2/action_executions/12345' }) liveaction, execution = action_service.request(liveaction) liveaction = LiveAction.get_by_id(str(liveaction.id)) self.assertEqual(liveaction.status, action_constants.LIVEACTION_STATUS_SUCCEEDED) calls = [ call('12345', state='SUCCESS', output=NON_EMPTY_RESULT) for i in range(0, 2) ] action_executions.ActionExecutionManager.update.assert_has_calls(calls)
def cancel(self): mistral_ctx = self.context.get('mistral', dict()) if not mistral_ctx.get('execution_id'): raise Exception( 'Unable to cancel because mistral execution_id is missing.') # Cancels the main workflow execution. Any non-workflow tasks that are still # running will be allowed to complete gracefully. self._client.executions.update(mistral_ctx.get('execution_id'), 'CANCELLED') # Identify the list of action executions that are workflows and still running. for child_exec_id in self.execution.children: child_exec = ActionExecution.get(id=child_exec_id) if (child_exec.runner['name'] == self.runner_type_db.name and child_exec.status in action_constants.LIVEACTION_CANCELABLE_STATES): action_service.request_cancellation( LiveAction.get(id=child_exec.liveaction['id']), self.context.get('user', None)) return (action_constants.LIVEACTION_STATUS_CANCELING, self.liveaction.result, self.liveaction.context)
def put(self, id, liveaction_api, requester_user, show_secrets=False): """ Updates a single execution. Handles requests: PUT /executions/<id> """ if not requester_user: requester_user = UserDB(cfg.CONF.system_user.user) from_model_kwargs = { 'mask_secrets': self._get_mask_secrets(requester_user, show_secrets=show_secrets) } execution_api = self._get_one_by_id( id=id, requester_user=requester_user, from_model_kwargs=from_model_kwargs, permission_type=PermissionType.EXECUTION_STOP) if not execution_api: abort(http_client.NOT_FOUND, 'Execution with id %s not found.' % id) 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) if liveaction_db.status in action_constants.LIVEACTION_COMPLETED_STATES: abort(http_client.BAD_REQUEST, 'Execution is already in completed state.') def update_status(liveaction_api, liveaction_db): status = liveaction_api.status result = getattr(liveaction_api, 'result', None) liveaction_db = action_service.update_status( liveaction_db, status, result) actionexecution_db = ActionExecution.get( liveaction__id=str(liveaction_db.id)) return (liveaction_db, actionexecution_db) try: if (liveaction_db.status == action_constants.LIVEACTION_STATUS_CANCELING and liveaction_api.status == action_constants.LIVEACTION_STATUS_CANCELED): if action_service.is_children_active(liveaction_id): liveaction_api.status = action_constants.LIVEACTION_STATUS_CANCELING liveaction_db, actionexecution_db = update_status( liveaction_api, liveaction_db) elif (liveaction_api.status == action_constants.LIVEACTION_STATUS_CANCELING or liveaction_api.status == action_constants.LIVEACTION_STATUS_CANCELED): liveaction_db, actionexecution_db = action_service.request_cancellation( liveaction_db, requester_user.name or cfg.CONF.system_user.user) elif (liveaction_db.status == action_constants.LIVEACTION_STATUS_PAUSING and liveaction_api.status == action_constants.LIVEACTION_STATUS_PAUSED): if action_service.is_children_active(liveaction_id): liveaction_api.status = action_constants.LIVEACTION_STATUS_PAUSING liveaction_db, actionexecution_db = update_status( liveaction_api, liveaction_db) elif (liveaction_api.status == action_constants.LIVEACTION_STATUS_PAUSING or liveaction_api.status == action_constants.LIVEACTION_STATUS_PAUSED): liveaction_db, actionexecution_db = action_service.request_pause( liveaction_db, requester_user.name or cfg.CONF.system_user.user) elif liveaction_api.status == action_constants.LIVEACTION_STATUS_RESUMING: liveaction_db, actionexecution_db = action_service.request_resume( liveaction_db, requester_user.name or cfg.CONF.system_user.user) else: liveaction_db, actionexecution_db = update_status( liveaction_api, liveaction_db) except runner_exc.InvalidActionRunnerOperationError as e: LOG.exception('Failed updating liveaction %s. %s', liveaction_db.id, str(e)) abort(http_client.BAD_REQUEST, 'Failed updating execution. %s' % str(e)) except runner_exc.UnexpectedActionExecutionStatusError as e: LOG.exception('Failed updating liveaction %s. %s', liveaction_db.id, str(e)) abort(http_client.BAD_REQUEST, 'Failed updating execution. %s' % str(e)) except Exception as e: LOG.exception('Failed updating liveaction %s. %s', liveaction_db.id, str(e)) abort(http_client.INTERNAL_SERVER_ERROR, 'Failed updating execution due to unexpected error.') mask_secrets = self._get_mask_secrets(requester_user, show_secrets=show_secrets) execution_api = ActionExecutionAPI.from_model( actionexecution_db, mask_secrets=mask_secrets) return execution_api
def delete(self, id, requester_user, show_secrets=False): """ Stops a single execution. Handles requests: DELETE /executions/<id> """ if not requester_user: requester_user = UserDB(cfg.CONF.system_user.user) from_model_kwargs = { 'mask_secrets': self._get_mask_secrets(requester_user, show_secrets=show_secrets) } execution_api = self._get_one_by_id( id=id, requester_user=requester_user, from_model_kwargs=from_model_kwargs, permission_type=PermissionType.EXECUTION_STOP) if not execution_api: abort(http_client.NOT_FOUND, 'Execution with id %s not found.' % id) 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) if liveaction_db.status == action_constants.LIVEACTION_STATUS_CANCELED: LOG.info('Action %s already in "canceled" state; \ returning execution object.' % liveaction_db.id) return execution_api if liveaction_db.status not in action_constants.LIVEACTION_CANCELABLE_STATES: abort( http_client.OK, 'Action cannot be canceled. State = %s.' % liveaction_db.status) try: (liveaction_db, execution_db) = action_service.request_cancellation( liveaction_db, requester_user.name or cfg.CONF.system_user.user) except: LOG.exception('Failed requesting cancellation for liveaction %s.', liveaction_db.id) abort(http_client.INTERNAL_SERVER_ERROR, 'Failed canceling execution.') return ActionExecutionAPI.from_model( execution_db, mask_secrets=from_model_kwargs['mask_secrets'])
def create_request(liveaction): """ Create 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. parent_context = executions.get_parent_context(liveaction) if parent_context: parent_user = parent_context.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_schema_for_action_parameters(action_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(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=[ trace_service.get_trace_component_for_action_execution( execution, liveaction) ]) return liveaction, execution
def _schedule_execution(self, liveaction, requester_user, user=None, context_string=None, show_secrets=False, pack=None): # Initialize execution context if it does not exist. if not hasattr(liveaction, 'context'): liveaction.context = dict() liveaction.context['user'] = user liveaction.context['pack'] = pack 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) # Include RBAC context (if RBAC is available and enabled) if cfg.CONF.rbac.enable: user_db = UserDB(name=user) role_dbs = rbac_service.get_roles_for_user(user_db=user_db, include_remote=True) roles = [role_db.name for role_db in role_dbs] liveaction.context['rbac'] = {'user': user, 'roles': roles} # Schedule the action execution. liveaction_db = LiveActionAPI.to_model(liveaction) 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 param_exc.ParamException: # We still need to create a request, so liveaction_db is assigned an ID liveaction_db, actionexecution_db = action_service.create_request( liveaction_db) # By this point the execution is already in the DB therefore need to mark it failed. _, e, tb = sys.exc_info() action_service.update_status( liveaction=liveaction_db, new_status=action_constants.LIVEACTION_STATUS_FAILED, result={ 'error': str(e), 'traceback': ''.join(traceback.format_tb(tb, 20)) }) # Might be a good idea to return the actual ActionExecution rather than bubble up # the exception. raise validation_exc.ValueValidationException(str(e)) # The request should be created after the above call to render_live_params # so any templates in live parameters have a chance to render. liveaction_db, actionexecution_db = action_service.create_request( liveaction_db) liveaction_db = LiveAction.add_or_update(liveaction_db, publish=False) _, actionexecution_db = action_service.publish_request( liveaction_db, actionexecution_db) mask_secrets = self._get_mask_secrets(requester_user, show_secrets=show_secrets) execution_api = ActionExecutionAPI.from_model( actionexecution_db, mask_secrets=mask_secrets) return Response(json=execution_api, status=http_client.CREATED)
def _cleanup_liveaction(liveaction): try: LiveAction.delete(liveaction) except: LOG.exception('Failed cleaning up LiveAction: %s.', liveaction) pass
def _save_liveaction(liveaction): return LiveAction.add_or_update(liveaction)
def test_chain_cancel_cascade_to_parent_workflow(self): # A temp file is created during test setup. Ensure the temp file exists. # The test action chain will stall until this file is deleted. This gives # the unit test a moment to run any test related logic. path = self.temp_file_path self.assertTrue(os.path.exists(path)) action = TEST_PACK + '.' + 'test_cancel_with_subworkflow' params = {'tempfile': path, 'message': 'foobar'} liveaction = LiveActionDB(action=action, parameters=params) liveaction, execution = action_service.request(liveaction) liveaction = LiveAction.get_by_id(str(liveaction.id)) # Wait until the liveaction is running. liveaction = self._wait_on_status( liveaction, action_constants.LIVEACTION_STATUS_RUNNING) # Wait for subworkflow to register. execution = self._wait_for_children(execution) self.assertEqual(len(execution.children), 1) # Wait until the subworkflow is running. task1_exec = ActionExecution.get_by_id(execution.children[0]) task1_live = LiveAction.get_by_id(task1_exec.liveaction['id']) task1_live = self._wait_on_status( task1_live, action_constants.LIVEACTION_STATUS_RUNNING) # Request subworkflow to cancel. task1_live, task1_exec = action_service.request_cancellation( task1_live, USERNAME) # Wait until the subworkflow is canceling. task1_exec = ActionExecution.get_by_id(execution.children[0]) task1_live = LiveAction.get_by_id(task1_exec.liveaction['id']) task1_live = self._wait_on_status( task1_live, action_constants.LIVEACTION_STATUS_CANCELING) # Delete the temporary file that the action chain is waiting on. os.remove(path) self.assertFalse(os.path.exists(path)) # Wait until the subworkflow is canceled. task1_exec = ActionExecution.get_by_id(execution.children[0]) task1_live = LiveAction.get_by_id(task1_exec.liveaction['id']) task1_live = self._wait_on_status( task1_live, action_constants.LIVEACTION_STATUS_CANCELED) # Wait until the parent liveaction is canceled. liveaction = self._wait_on_status( liveaction, action_constants.LIVEACTION_STATUS_CANCELED) self.assertEqual(len(execution.children), 1) # Wait for non-blocking threads to complete. Ensure runner is not running. MockLiveActionPublisherNonBlocking.wait_all() # Check liveaction result. self.assertIn('tasks', liveaction.result) self.assertEqual(len(liveaction.result['tasks']), 1) subworkflow = liveaction.result['tasks'][0] self.assertEqual(len(subworkflow['result']['tasks']), 1) self.assertEqual(subworkflow['state'], action_constants.LIVEACTION_STATUS_CANCELED)
def test_migrate_executions(self): ActionExecutionDB._meta["allow_inheritance"] = True LiveActionDB._meta["allow_inheritance"] = True class ActionExecutionDB_OldFieldType(ActionExecutionDB): result = stormbase.EscapedDynamicField(default={}) class LiveActionDB_OldFieldType(LiveActionDB): result = stormbase.EscapedDynamicField(default={}) execution_dbs = ActionExecution.query( __raw__={"result": { "$not": { "$type": "binData", }, }}) self.assertEqual(len(execution_dbs), 0) execution_dbs = ActionExecution.query(__raw__={ "result": { "$type": "object", }, }) self.assertEqual(len(execution_dbs), 0) # 1. Insert data in old format liveaction_1_db = LiveActionDB_OldFieldType() liveaction_1_db.action = "foo.bar" liveaction_1_db.status = action_constants.LIVEACTION_STATUS_FAILED liveaction_1_db.result = MOCK_RESULT_1 liveaction_1_db.start_timestamp = datetime.datetime.utcnow().replace( tzinfo=datetime.timezone.utc) liveaction_1_db = LiveAction.add_or_update(liveaction_1_db, publish=False) execution_1_db = ActionExecutionDB_OldFieldType() execution_1_db.action = {"a": 1} execution_1_db.runner = {"a": 1} execution_1_db.liveaction = {"id": liveaction_1_db.id} execution_1_db.status = action_constants.LIVEACTION_STATUS_FAILED execution_1_db.result = MOCK_RESULT_1 execution_1_db.start_timestamp = datetime.datetime.utcnow().replace( tzinfo=datetime.timezone.utc) execution_1_db = ActionExecution.add_or_update(execution_1_db, publish=False) # This execution is not in a final state yet so it should not be migrated liveaction_2_db = LiveActionDB_OldFieldType() liveaction_2_db.action = "foo.bar2" liveaction_2_db.status = action_constants.LIVEACTION_STATUS_RUNNING liveaction_2_db.result = MOCK_RESULT_2 liveaction_2_db.start_timestamp = datetime.datetime.utcnow().replace( tzinfo=datetime.timezone.utc) liveaction_2_db = LiveAction.add_or_update(liveaction_2_db, publish=False) execution_2_db = ActionExecutionDB_OldFieldType() execution_2_db.action = {"a": 2} execution_2_db.runner = {"a": 2} execution_2_db.liveaction = {"id": liveaction_2_db.id} execution_2_db.status = action_constants.LIVEACTION_STATUS_RUNNING execution_2_db.result = MOCK_RESULT_2 execution_2_db.start_timestamp = datetime.datetime.utcnow().replace( tzinfo=datetime.timezone.utc) execution_2_db = ActionExecution.add_or_update(execution_2_db, publish=False) # This object is older than the default threshold so it should not be migrated execution_3_db = ActionExecutionDB_OldFieldType() execution_3_db.action = {"a": 2} execution_3_db.runner = {"a": 2} execution_3_db.liveaction = {"id": liveaction_2_db.id} execution_3_db.status = action_constants.LIVEACTION_STATUS_SUCCEEDED execution_3_db.result = MOCK_RESULT_1 execution_3_db.start_timestamp = datetime.datetime.utcfromtimestamp( 0).replace(tzinfo=datetime.timezone.utc) execution_3_db = ActionExecution.add_or_update(execution_3_db, publish=False) # Verify data has been inserted in old format execution_dbs = ActionExecution.query(__raw__={ "result": { "$type": "object", }, }) self.assertEqual(len(execution_dbs), 3) execution_dbs = ActionExecution.query( __raw__={"result": { "$not": { "$type": "binData", }, }}) self.assertEqual(len(execution_dbs), 3) execution_dbs = ActionExecution.query(__raw__={ "result": { "$type": "binData", }, }) self.assertEqual(len(execution_dbs), 0) liveaction_dbs = LiveAction.query(__raw__={ "result": { "$type": "object", }, }) self.assertEqual(len(liveaction_dbs), 2) liveaction_dbs = LiveAction.query( __raw__={"result": { "$not": { "$type": "binData", }, }}) self.assertEqual(len(liveaction_dbs), 2) liveaction_dbs = LiveAction.query(__raw__={ "result": { "$type": "binData", }, }) self.assertEqual(len(liveaction_dbs), 0) # Update inserted documents and remove special _cls field added by mongoengine. We need to # do that here due to how mongoengine works with subclasses. ActionExecution.query(__raw__={ "result": { "$type": "object", }, }).update(set___cls="ActionExecutionDB") LiveAction.query(__raw__={ "result": { "$type": "object", }, }).update(set___cls="LiveActionDB") # 2. Run migration start_dt = datetime.datetime.utcnow().replace( tzinfo=datetime.timezone.utc) - datetime.timedelta(hours=2) end_dt = datetime.datetime.utcnow().replace( tzinfo=datetime.timezone.utc) migration_module.migrate_executions(start_dt=start_dt, end_dt=end_dt) # 3. Verify data has been migrated - only 1 item should have been migrated since it's in a # completed state execution_dbs = ActionExecution.query(__raw__={ "result": { "$type": "object", }, }) self.assertEqual(len(execution_dbs), 2) execution_dbs = ActionExecution.query(__raw__={ "result": { "$type": "binData", }, }) self.assertEqual(len(execution_dbs), 1) execution_db_1_retrieved = ActionExecution.get_by_id(execution_1_db.id) self.assertEqual(execution_db_1_retrieved.result, MOCK_RESULT_1) execution_db_2_retrieved = ActionExecution.get_by_id(execution_2_db.id) self.assertEqual(execution_db_2_retrieved.result, MOCK_RESULT_2) liveaction_db_1_retrieved = LiveAction.get_by_id(liveaction_1_db.id) self.assertEqual(liveaction_db_1_retrieved.result, MOCK_RESULT_1) liveaction_db_2_retrieved = LiveAction.get_by_id(liveaction_2_db.id) self.assertEqual(liveaction_db_2_retrieved.result, MOCK_RESULT_2)
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)