Add 'execution-get-report' command

Implements blueprint: workflow-error-analysis

Depends-On: Id3e17821e04b7a1b84dfea5126d223d90ad8e3c2

Change-Id: I52b1d87ada2ead4f47a402b8aa683a0fa1629e70
This commit is contained in:
Renat Akhmerov 2019-02-05 14:51:21 +07:00
parent b99cebafd4
commit 8cbf2fa1b9
4 changed files with 185 additions and 9 deletions

View File

@ -31,12 +31,11 @@ class ExecutionManager(base.ResourceManager):
def create(self, workflow_identifier='', namespace='',
workflow_input=None, description='', source_execution_id=None,
**params):
ident = workflow_identifier or source_execution_id
self._ensure_not_empty(workflow_identifier=ident)
self._ensure_not_empty(
workflow_identifier=workflow_identifier or source_execution_id
)
data = {
'description': description,
}
data = {'description': description}
if uuidutils.is_uuid_like(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):
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))
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()

View File

@ -215,7 +215,9 @@ class Delete(command.Command):
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
force = parsed_args.force
utils.do_action_on_many(
lambda s: mistral_client.executions.delete(s, force=force),
parsed_args.execution,
@ -290,6 +292,7 @@ class GetInput(command.Command):
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
ex_input = mistral_client.executions.get(parsed_args.id).input
try:
@ -313,6 +316,7 @@ class GetOutput(command.Command):
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
output = mistral_client.executions.get(parsed_args.id).output
try:
@ -322,3 +326,139 @@ class GetOutput(command.Command):
LOG.debug("Execution output is not JSON.")
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)

View File

@ -728,6 +728,8 @@ class MistralShell(app.App):
mistralclient.commands.v2.executions.GetInput,
'execution-get-output':
mistralclient.commands.v2.executions.GetOutput,
'execution-get-report':
mistralclient.commands.v2.executions.GetReport,
'task-list': mistralclient.commands.v2.tasks.List,
'task-get': mistralclient.commands.v2.tasks.Get,
'task-get-published': mistralclient.commands.v2.tasks.GetPublished,

View File

@ -265,3 +265,17 @@ class TestExecutionsV2(base.BaseClientV2Test):
self.requests_mock.delete(url, status_code=204)
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)