Add 'execution-get-report' command
Implements blueprint: workflow-error-analysis Depends-On: Id3e17821e04b7a1b84dfea5126d223d90ad8e3c2 Change-Id: I52b1d87ada2ead4f47a402b8aa683a0fa1629e70
This commit is contained in:
@@ -31,12 +31,11 @@ class ExecutionManager(base.ResourceManager):
|
|||||||
def create(self, workflow_identifier='', namespace='',
|
def create(self, workflow_identifier='', namespace='',
|
||||||
workflow_input=None, description='', source_execution_id=None,
|
workflow_input=None, description='', source_execution_id=None,
|
||||||
**params):
|
**params):
|
||||||
ident = workflow_identifier or source_execution_id
|
self._ensure_not_empty(
|
||||||
self._ensure_not_empty(workflow_identifier=ident)
|
workflow_identifier=workflow_identifier or source_execution_id
|
||||||
|
)
|
||||||
|
|
||||||
data = {
|
data = {'description': description}
|
||||||
'description': description,
|
|
||||||
}
|
|
||||||
|
|
||||||
if uuidutils.is_uuid_like(source_execution_id):
|
if uuidutils.is_uuid_like(source_execution_id):
|
||||||
data.update({'source_execution_id': source_execution_id})
|
data.update({'source_execution_id': source_execution_id})
|
||||||
@@ -101,10 +100,31 @@ class ExecutionManager(base.ResourceManager):
|
|||||||
|
|
||||||
def delete(self, id, force=None):
|
def delete(self, id, force=None):
|
||||||
self._ensure_not_empty(id=id)
|
self._ensure_not_empty(id=id)
|
||||||
qparams = {}
|
|
||||||
if force:
|
|
||||||
qparams['force'] = True
|
|
||||||
|
|
||||||
query_string = self._build_query_params(filters=qparams)
|
query_params = {}
|
||||||
|
|
||||||
|
if force:
|
||||||
|
query_params['force'] = True
|
||||||
|
|
||||||
|
query_string = self._build_query_params(filters=query_params)
|
||||||
|
|
||||||
self._delete('/executions/%s%s' % (id, query_string))
|
self._delete('/executions/%s%s' % (id, query_string))
|
||||||
|
|
||||||
|
def get_report(self, id, errors_only=True, max_depth=None):
|
||||||
|
self._ensure_not_empty(id=id)
|
||||||
|
|
||||||
|
query_params = {}
|
||||||
|
|
||||||
|
if errors_only:
|
||||||
|
query_params['errors_only'] = True
|
||||||
|
|
||||||
|
if max_depth is not None:
|
||||||
|
query_params['max_depth'] = max_depth
|
||||||
|
|
||||||
|
query_string = self._build_query_params(filters=query_params)
|
||||||
|
|
||||||
|
resp = self.http_client.get(
|
||||||
|
'/executions/%s/report%s' % (id, query_string)
|
||||||
|
)
|
||||||
|
|
||||||
|
return resp.json()
|
||||||
|
@@ -215,7 +215,9 @@ class Delete(command.Command):
|
|||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
mistral_client = self.app.client_manager.workflow_engine
|
mistral_client = self.app.client_manager.workflow_engine
|
||||||
|
|
||||||
force = parsed_args.force
|
force = parsed_args.force
|
||||||
|
|
||||||
utils.do_action_on_many(
|
utils.do_action_on_many(
|
||||||
lambda s: mistral_client.executions.delete(s, force=force),
|
lambda s: mistral_client.executions.delete(s, force=force),
|
||||||
parsed_args.execution,
|
parsed_args.execution,
|
||||||
@@ -290,6 +292,7 @@ class GetInput(command.Command):
|
|||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
mistral_client = self.app.client_manager.workflow_engine
|
mistral_client = self.app.client_manager.workflow_engine
|
||||||
|
|
||||||
ex_input = mistral_client.executions.get(parsed_args.id).input
|
ex_input = mistral_client.executions.get(parsed_args.id).input
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -313,6 +316,7 @@ class GetOutput(command.Command):
|
|||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
mistral_client = self.app.client_manager.workflow_engine
|
mistral_client = self.app.client_manager.workflow_engine
|
||||||
|
|
||||||
output = mistral_client.executions.get(parsed_args.id).output
|
output = mistral_client.executions.get(parsed_args.id).output
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -322,3 +326,139 @@ class GetOutput(command.Command):
|
|||||||
LOG.debug("Execution output is not JSON.")
|
LOG.debug("Execution output is not JSON.")
|
||||||
|
|
||||||
self.app.stdout.write(output or "\n")
|
self.app.stdout.write(output or "\n")
|
||||||
|
|
||||||
|
|
||||||
|
REPORT_ENTRY_INDENT = 4
|
||||||
|
|
||||||
|
|
||||||
|
class GetReport(command.Command):
|
||||||
|
"""Print execution report."""
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(GetReport, self).get_parser(prog_name)
|
||||||
|
|
||||||
|
parser.add_argument('id', help='Execution ID')
|
||||||
|
parser.add_argument(
|
||||||
|
'--errors-only',
|
||||||
|
dest='errors_only',
|
||||||
|
action='store_true',
|
||||||
|
help='Only error paths will be included.'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--no-errors-only',
|
||||||
|
dest='errors_only',
|
||||||
|
action='store_false',
|
||||||
|
help='Not only error paths will be included.'
|
||||||
|
)
|
||||||
|
parser.set_defaults(errors_only=True)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--max-depth',
|
||||||
|
dest='max_depth',
|
||||||
|
nargs='?',
|
||||||
|
type=int,
|
||||||
|
default=-1,
|
||||||
|
help='Maximum depth of the workflow execution tree. '
|
||||||
|
'If 0, only the root workflow execution and its '
|
||||||
|
'tasks will be included'
|
||||||
|
)
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def print_line(self, line, level=0):
|
||||||
|
self.app.stdout.write(
|
||||||
|
"%s%s\n" % (' ' * (level * REPORT_ENTRY_INDENT), line)
|
||||||
|
)
|
||||||
|
|
||||||
|
def print_workflow_execution_entry(self, wf_ex, level):
|
||||||
|
self.print_line(
|
||||||
|
"workflow '%s' [%s] %s" %
|
||||||
|
(wf_ex['name'], wf_ex['state'], wf_ex['id']),
|
||||||
|
level
|
||||||
|
)
|
||||||
|
|
||||||
|
if 'task_executions' in wf_ex:
|
||||||
|
for t_ex in wf_ex['task_executions']:
|
||||||
|
self.print_task_execution_entry(t_ex, level + 1)
|
||||||
|
|
||||||
|
def print_task_execution_entry(self, t_ex, level):
|
||||||
|
self.print_line(
|
||||||
|
"task '%s' [%s] %s" %
|
||||||
|
(t_ex['name'], t_ex['state'], t_ex['id']),
|
||||||
|
level
|
||||||
|
)
|
||||||
|
|
||||||
|
if t_ex['state'] == 'ERROR':
|
||||||
|
state_info = t_ex['state_info']
|
||||||
|
state_info = state_info[0:200] + '...'
|
||||||
|
|
||||||
|
self.print_line('(error info: %s)' % state_info, level)
|
||||||
|
|
||||||
|
if 'action_executions' in t_ex:
|
||||||
|
for a_ex in t_ex['action_executions']:
|
||||||
|
self.print_action_execution_entry(a_ex, level + 1)
|
||||||
|
|
||||||
|
if 'workflow_executions' in t_ex:
|
||||||
|
for wf_ex in t_ex['workflow_executions']:
|
||||||
|
self.print_workflow_execution_entry(wf_ex, level + 1)
|
||||||
|
|
||||||
|
def print_action_execution_entry(self, a_ex, level):
|
||||||
|
self.print_line(
|
||||||
|
"action '%s' [%s] %s" %
|
||||||
|
(a_ex['name'], a_ex['state'], a_ex['id']),
|
||||||
|
level
|
||||||
|
)
|
||||||
|
|
||||||
|
def print_statistics(self, stat):
|
||||||
|
self.print_line(
|
||||||
|
'Number of tasks in SUCCESS state: %s' %
|
||||||
|
stat['success_tasks_count']
|
||||||
|
)
|
||||||
|
self.print_line(
|
||||||
|
'Number of tasks in ERROR state: %s' % stat['error_tasks_count']
|
||||||
|
)
|
||||||
|
self.print_line(
|
||||||
|
'Number of tasks in RUNNING state: %s' %
|
||||||
|
stat['running_tasks_count']
|
||||||
|
)
|
||||||
|
self.print_line(
|
||||||
|
'Number of tasks in IDLE state: %s' % stat['idle_tasks_count']
|
||||||
|
)
|
||||||
|
self.print_line(
|
||||||
|
'Number of tasks in PAUSED state: %s\n' %
|
||||||
|
stat['paused_tasks_count']
|
||||||
|
)
|
||||||
|
|
||||||
|
def print_report(self, report_json):
|
||||||
|
self.print_line(
|
||||||
|
"\nTo get more details on a task failure "
|
||||||
|
"run: mistral task-get <id> -c 'State info'\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
frame_line = '=' * 30
|
||||||
|
|
||||||
|
self.print_line(
|
||||||
|
'%s General Statistics %s\n' %
|
||||||
|
(frame_line, frame_line)
|
||||||
|
)
|
||||||
|
self.print_statistics(report_json['statistics'])
|
||||||
|
|
||||||
|
self.print_line(
|
||||||
|
'%s Workflow Execution Tree %s\n' %
|
||||||
|
(frame_line, frame_line)
|
||||||
|
)
|
||||||
|
self.print_workflow_execution_entry(
|
||||||
|
report_json['root_workflow_execution'],
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
mistral_client = self.app.client_manager.workflow_engine
|
||||||
|
|
||||||
|
report_json = mistral_client.executions.get_report(
|
||||||
|
parsed_args.id,
|
||||||
|
errors_only=parsed_args.errors_only,
|
||||||
|
max_depth=parsed_args.max_depth
|
||||||
|
)
|
||||||
|
|
||||||
|
self.print_report(report_json)
|
||||||
|
@@ -728,6 +728,8 @@ class MistralShell(app.App):
|
|||||||
mistralclient.commands.v2.executions.GetInput,
|
mistralclient.commands.v2.executions.GetInput,
|
||||||
'execution-get-output':
|
'execution-get-output':
|
||||||
mistralclient.commands.v2.executions.GetOutput,
|
mistralclient.commands.v2.executions.GetOutput,
|
||||||
|
'execution-get-report':
|
||||||
|
mistralclient.commands.v2.executions.GetReport,
|
||||||
'task-list': mistralclient.commands.v2.tasks.List,
|
'task-list': mistralclient.commands.v2.tasks.List,
|
||||||
'task-get': mistralclient.commands.v2.tasks.Get,
|
'task-get': mistralclient.commands.v2.tasks.Get,
|
||||||
'task-get-published': mistralclient.commands.v2.tasks.GetPublished,
|
'task-get-published': mistralclient.commands.v2.tasks.GetPublished,
|
||||||
|
@@ -265,3 +265,17 @@ class TestExecutionsV2(base.BaseClientV2Test):
|
|||||||
self.requests_mock.delete(url, status_code=204)
|
self.requests_mock.delete(url, status_code=204)
|
||||||
|
|
||||||
self.executions.delete(EXEC['id'])
|
self.executions.delete(EXEC['id'])
|
||||||
|
|
||||||
|
def test_report(self):
|
||||||
|
url = self.TEST_URL + URL_TEMPLATE_ID % EXEC['id'] + '/report'
|
||||||
|
|
||||||
|
expected_json = {
|
||||||
|
'root_workflow_execution': {},
|
||||||
|
'statistics': {}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.requests_mock.get(url, json=expected_json)
|
||||||
|
|
||||||
|
report = self.executions.get_report(EXEC['id'])
|
||||||
|
|
||||||
|
self.assertDictEqual(expected_json, report)
|
||||||
|
Reference in New Issue
Block a user