Add new CLI commands for sub-executions new API endpoints
*2 new CLI commands were added: - execution-get-sub-executions returns the sub-executions of a given execution id - task-get-sub-executions returns the sub-executions of a given task-execution id both commands have the options --errors_only: returns only the error routes - default is False --max_depth: the max depth for the returned executions - if a negative value is given, then the API will return all sub-executions - default is -1 Change-Id: Ifcd25cfdbfb99613ff1bdccf8b94b3929f02a71d Implements: blueprint mistral-execution-origin Signed-off-by: ali <ali.abdelal@nokia.com>
This commit is contained in:
parent
e1e75d61eb
commit
23270d272a
@ -173,7 +173,9 @@ class ResourceManager(object):
|
||||
for resource_data in resource]
|
||||
return self.resource_class(self, resource)
|
||||
|
||||
def _list(self, url, response_key=None, headers=None):
|
||||
def _list(self, url, response_key=None, headers=None,
|
||||
returned_res_cls=None):
|
||||
|
||||
try:
|
||||
resp = self.http_client.get(url, headers)
|
||||
except exceptions.HttpError as ex:
|
||||
@ -182,7 +184,9 @@ class ResourceManager(object):
|
||||
if resp.status_code != 200:
|
||||
self._raise_api_exception(resp)
|
||||
|
||||
return [self.resource_class(self, resource_data)
|
||||
resource_class = returned_res_cls or self.resource_class
|
||||
|
||||
return [resource_class(self, resource_data)
|
||||
for resource_data in extract_json(resp, response_key)]
|
||||
|
||||
def _get(self, url, response_key=None, headers=None):
|
||||
|
@ -98,6 +98,15 @@ class ExecutionManager(base.ResourceManager):
|
||||
|
||||
return self._get('/executions/%s' % id)
|
||||
|
||||
def get_ex_sub_executions(self, id, errors_only='', max_depth=-1):
|
||||
ex_sub_execs_path = '/executions/%s/executions%s'
|
||||
params = '?max_depth=%s&errors_only=%s' % (max_depth, errors_only)
|
||||
|
||||
return self._list(
|
||||
ex_sub_execs_path % (id, params),
|
||||
response_key='executions'
|
||||
)
|
||||
|
||||
def delete(self, id, force=None):
|
||||
self._ensure_not_empty(id=id)
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from mistralclient.api import base
|
||||
from mistralclient.api.v2 import executions
|
||||
|
||||
|
||||
class Task(base.Resource):
|
||||
@ -50,6 +51,16 @@ class TaskManager(base.ResourceManager):
|
||||
|
||||
return self._get('/tasks/%s' % id)
|
||||
|
||||
def get_task_sub_executions(self, id, errors_only='', max_depth=-1):
|
||||
task_sub_execs_path = '/tasks/%s/executions%s'
|
||||
params = '?max_depth=%s&errors_only=%s' % (max_depth, errors_only)
|
||||
|
||||
return self._list(
|
||||
task_sub_execs_path % (id, params),
|
||||
response_key='executions',
|
||||
returned_res_cls=executions.Execution
|
||||
)
|
||||
|
||||
def rerun(self, task_ex_id, reset=True, env=None):
|
||||
url = '/tasks/%s' % task_ex_id
|
||||
|
||||
|
@ -24,6 +24,7 @@ from oslo_serialization import jsonutils
|
||||
|
||||
from osc_lib.command import command
|
||||
|
||||
from cliff.lister import Lister as cliff_lister
|
||||
from mistralclient.commands.v2 import base
|
||||
from mistralclient import utils
|
||||
|
||||
@ -510,3 +511,71 @@ class GetPublished(command.Command):
|
||||
LOG.debug("Task result is not JSON.")
|
||||
|
||||
self.app.stdout.write(published or "\n")
|
||||
|
||||
|
||||
class SubExecutionsBaseLister(cliff_lister):
|
||||
|
||||
def _get_format_function(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def _get_resources_function(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(SubExecutionsBaseLister, self).get_parser(prog_name)
|
||||
|
||||
parser.add_argument(
|
||||
'id',
|
||||
metavar='ID',
|
||||
help='origin id'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--errors-only',
|
||||
dest='errors_only',
|
||||
action='store_true',
|
||||
help='Only error paths will be included.'
|
||||
)
|
||||
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 _get_resources(self, parsed_args):
|
||||
resource_function = self._get_resources_function()
|
||||
errors_only = parsed_args.errors_only or ''
|
||||
|
||||
return resource_function(
|
||||
parsed_args.id,
|
||||
errors_only=errors_only,
|
||||
max_depth=parsed_args.max_depth,
|
||||
)
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
format_func = self._get_format_function()
|
||||
execs_list = self._get_resources(parsed_args)
|
||||
|
||||
if not isinstance(execs_list, list):
|
||||
execs_list = [execs_list]
|
||||
|
||||
data = [format_func(r)[1] for r in execs_list]
|
||||
|
||||
return (format_func()[0], data) if data else format_func()
|
||||
|
||||
|
||||
class SubExecutionsLister(SubExecutionsBaseLister):
|
||||
|
||||
def _get_format_function(self):
|
||||
return ExecutionFormatter.format_list
|
||||
|
||||
def _get_resources_function(self):
|
||||
mistral_client = self.app.client_manager.workflow_engine
|
||||
|
||||
return mistral_client.executions.get_ex_sub_executions
|
||||
|
@ -23,6 +23,7 @@ from oslo_serialization import jsonutils
|
||||
from osc_lib.command import command
|
||||
|
||||
from mistralclient.commands.v2 import base
|
||||
from mistralclient.commands.v2 import executions
|
||||
from mistralclient import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -211,3 +212,14 @@ class Rerun(command.ShowOne):
|
||||
)
|
||||
|
||||
return TaskFormatter.format(execution)
|
||||
|
||||
|
||||
class SubExecutionsLister(executions.SubExecutionsBaseLister):
|
||||
|
||||
def _get_format_function(self):
|
||||
return executions.ExecutionFormatter.format_list
|
||||
|
||||
def _get_resources_function(self):
|
||||
mistral_client = self.app.client_manager.workflow_engine
|
||||
|
||||
return mistral_client.tasks.get_task_sub_executions
|
||||
|
@ -732,10 +732,14 @@ class MistralShell(app.App):
|
||||
mistralclient.commands.v2.executions.GetReport,
|
||||
'execution-get-published':
|
||||
mistralclient.commands.v2.executions.GetPublished,
|
||||
'execution-get-sub-executions':
|
||||
mistralclient.commands.v2.executions.SubExecutionsLister,
|
||||
'task-list': mistralclient.commands.v2.tasks.List,
|
||||
'task-get': mistralclient.commands.v2.tasks.Get,
|
||||
'task-get-published': mistralclient.commands.v2.tasks.GetPublished,
|
||||
'task-get-result': mistralclient.commands.v2.tasks.GetResult,
|
||||
'task-get-sub-executions':
|
||||
mistralclient.commands.v2.tasks.SubExecutionsLister,
|
||||
'task-rerun': mistralclient.commands.v2.tasks.Rerun,
|
||||
'action-list': mistralclient.commands.v2.actions.List,
|
||||
'action-get': mistralclient.commands.v2.actions.Get,
|
||||
|
@ -53,7 +53,7 @@ SUB_WF_EXEC = executions.Execution(
|
||||
'workflow_namespace': '',
|
||||
'root_execution_id': 'ROOT_EXECUTION_ID',
|
||||
'description': '',
|
||||
'state': 'RUNNING',
|
||||
'state': 'ERROR',
|
||||
'state_info': None,
|
||||
'created_at': '1',
|
||||
'updated_at': '1',
|
||||
@ -83,12 +83,13 @@ SUB_WF_EX_RESULT = (
|
||||
'',
|
||||
'abc',
|
||||
'ROOT_EXECUTION_ID',
|
||||
'RUNNING',
|
||||
'ERROR',
|
||||
None,
|
||||
'1',
|
||||
'1'
|
||||
)
|
||||
|
||||
EXECS_LIST = [EXEC, SUB_WF_EXEC]
|
||||
EXEC_PUBLISHED = {"bar1": "val1", "var2": 2}
|
||||
EXEC_WITH_PUBLISHED_DICT = EXEC_DICT.copy()
|
||||
EXEC_WITH_PUBLISHED_DICT.update(
|
||||
@ -241,6 +242,61 @@ class TestCLIExecutionsV2(base.BaseCommandTest):
|
||||
result[1]
|
||||
)
|
||||
|
||||
def test_sub_executions(self):
|
||||
self.client.executions.get_ex_sub_executions.return_value = \
|
||||
EXECS_LIST
|
||||
|
||||
result = self.call(
|
||||
execution_cmd.SubExecutionsLister,
|
||||
app_args=[EXEC_DICT['id']]
|
||||
)
|
||||
|
||||
self.assertEqual([EX_RESULT, SUB_WF_EX_RESULT], result[1])
|
||||
self.assertEqual(
|
||||
1,
|
||||
self.client.executions.get_ex_sub_executions.call_count
|
||||
)
|
||||
self.assertEqual(
|
||||
[mock.call(EXEC_DICT['id'], errors_only='', max_depth=-1)],
|
||||
self.client.executions.get_ex_sub_executions.call_args_list
|
||||
)
|
||||
|
||||
def test_sub_executions_errors_only(self):
|
||||
self.client.executions.get_ex_sub_executions.return_value = \
|
||||
EXECS_LIST
|
||||
|
||||
self.call(
|
||||
execution_cmd.SubExecutionsLister,
|
||||
app_args=[EXEC_DICT['id'], '--errors-only']
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
1,
|
||||
self.client.executions.get_ex_sub_executions.call_count
|
||||
)
|
||||
self.assertEqual(
|
||||
[mock.call(EXEC_DICT['id'], errors_only=True, max_depth=-1)],
|
||||
self.client.executions.get_ex_sub_executions.call_args_list
|
||||
)
|
||||
|
||||
def test_sub_executions_with_max_depth(self):
|
||||
self.client.executions.get_ex_sub_executions.return_value = \
|
||||
EXECS_LIST
|
||||
|
||||
self.call(
|
||||
execution_cmd.SubExecutionsLister,
|
||||
app_args=[EXEC_DICT['id'], '--max-depth', '3']
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
1,
|
||||
self.client.executions.get_ex_sub_executions.call_count
|
||||
)
|
||||
self.assertEqual(
|
||||
[mock.call(EXEC_DICT['id'], errors_only='', max_depth=3)],
|
||||
self.client.executions.get_ex_sub_executions.call_args_list
|
||||
)
|
||||
|
||||
def test_list_with_pagination(self):
|
||||
self.client.executions.list.return_value = [EXEC]
|
||||
|
||||
|
@ -19,6 +19,7 @@ from oslo_serialization import jsonutils
|
||||
|
||||
import mock
|
||||
|
||||
from mistralclient.api.v2.executions import Execution
|
||||
from mistralclient.api.v2 import tasks
|
||||
from mistralclient.commands.v2 import tasks as task_cmd
|
||||
from mistralclient.tests.unit import base
|
||||
@ -35,6 +36,37 @@ TASK_DICT = {
|
||||
'updated_at': '1',
|
||||
}
|
||||
|
||||
TASK_SUB_WF_EXEC = Execution(
|
||||
mock,
|
||||
{
|
||||
'id': '456',
|
||||
'workflow_id': '123e4567-e89b-12d3-a456-426655440000',
|
||||
'workflow_name': 'some_sub_wf',
|
||||
'workflow_namespace': '',
|
||||
'root_execution_id': 'ROOT_EXECUTION_ID',
|
||||
'description': '',
|
||||
'state': 'ERROR',
|
||||
'state_info': None,
|
||||
'created_at': '1',
|
||||
'updated_at': '1',
|
||||
'task_execution_id': '123'
|
||||
}
|
||||
)
|
||||
|
||||
TASK_SUB_WF_EX_RESULT = (
|
||||
'456',
|
||||
'123e4567-e89b-12d3-a456-426655440000',
|
||||
'some_sub_wf',
|
||||
'',
|
||||
'',
|
||||
'123',
|
||||
'ROOT_EXECUTION_ID',
|
||||
'ERROR',
|
||||
None,
|
||||
'1',
|
||||
'1'
|
||||
)
|
||||
|
||||
TASK_RESULT = {"test": "is", "passed": "successfully"}
|
||||
TASK_PUBLISHED = {"bar1": "val1", "var2": 2}
|
||||
|
||||
@ -131,3 +163,58 @@ class TestCLITasksV2(base.BaseCommandTest):
|
||||
)
|
||||
|
||||
self.assertEqual(EXPECTED_TASK_RESULT, result[1])
|
||||
|
||||
def test_sub_executions(self):
|
||||
self.client.tasks.get_task_sub_executions.return_value = \
|
||||
TASK_SUB_WF_EXEC
|
||||
|
||||
result = self.call(
|
||||
task_cmd.SubExecutionsLister,
|
||||
app_args=[TASK_DICT['id']]
|
||||
)
|
||||
|
||||
self.assertEqual([TASK_SUB_WF_EX_RESULT], result[1])
|
||||
self.assertEqual(
|
||||
1,
|
||||
self.client.tasks.get_task_sub_executions.call_count
|
||||
)
|
||||
self.assertEqual(
|
||||
[mock.call(TASK_DICT['id'], errors_only='', max_depth=-1)],
|
||||
self.client.tasks.get_task_sub_executions.call_args_list
|
||||
)
|
||||
|
||||
def test_sub_executions_errors_only(self):
|
||||
self.client.tasks.get_task_sub_executions.return_value = \
|
||||
TASK_SUB_WF_EXEC
|
||||
|
||||
self.call(
|
||||
task_cmd.SubExecutionsLister,
|
||||
app_args=[TASK_DICT['id'], '--errors-only']
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
1,
|
||||
self.client.tasks.get_task_sub_executions.call_count
|
||||
)
|
||||
self.assertEqual(
|
||||
[mock.call(TASK_DICT['id'], errors_only=True, max_depth=-1)],
|
||||
self.client.tasks.get_task_sub_executions.call_args_list
|
||||
)
|
||||
|
||||
def test_sub_executions_with_max_depth(self):
|
||||
self.client.tasks.get_task_sub_executions.return_value = \
|
||||
TASK_SUB_WF_EXEC
|
||||
|
||||
self.call(
|
||||
task_cmd.SubExecutionsLister,
|
||||
app_args=[TASK_DICT['id'], '--max-depth', '3']
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
1,
|
||||
self.client.tasks.get_task_sub_executions.call_count
|
||||
)
|
||||
self.assertEqual(
|
||||
[mock.call(TASK_DICT['id'], errors_only='', max_depth=3)],
|
||||
self.client.tasks.get_task_sub_executions.call_args_list
|
||||
)
|
||||
|
@ -52,10 +52,23 @@ SUB_WF_EXEC = {
|
||||
}
|
||||
}
|
||||
|
||||
ERROR_SUB_WF_EXEC = {
|
||||
'id': "456",
|
||||
'workflow_id': '123e4567-e89b-12d3-a456-426655440000',
|
||||
'workflow_name': 'my_sub_wf',
|
||||
'workflow_namespace': '',
|
||||
'task_execution_id': "abc",
|
||||
'description': '',
|
||||
'state': 'ERROR',
|
||||
'input': {}
|
||||
}
|
||||
|
||||
|
||||
SOURCE_EXEC = EXEC
|
||||
SOURCE_EXEC['source_execution_id'] = EXEC['workflow_id']
|
||||
URL_TEMPLATE = '/executions'
|
||||
URL_TEMPLATE_ID = '/executions/%s'
|
||||
URL_TEMPLATE_SUB_EXECUTIONS = '/executions/%s/executions%s'
|
||||
|
||||
|
||||
class TestExecutionsV2(base.BaseClientV2Test):
|
||||
@ -279,3 +292,21 @@ class TestExecutionsV2(base.BaseClientV2Test):
|
||||
report = self.executions.get_report(EXEC['id'])
|
||||
|
||||
self.assertDictEqual(expected_json, report)
|
||||
|
||||
def test_get_sub_executions(self):
|
||||
url = self.TEST_URL + URL_TEMPLATE_SUB_EXECUTIONS \
|
||||
% (EXEC['id'], '?max_depth=-1&errors_only=')
|
||||
|
||||
self.requests_mock.get(url, json={'executions': [EXEC, SUB_WF_EXEC]})
|
||||
|
||||
sub_execution_list = self.executions.get_ex_sub_executions(EXEC['id'])
|
||||
|
||||
self.assertEqual(2, len(sub_execution_list))
|
||||
self.assertDictEqual(
|
||||
executions.Execution(self.executions, EXEC).to_dict(),
|
||||
sub_execution_list[0].to_dict()
|
||||
)
|
||||
self.assertDictEqual(
|
||||
executions.Execution(self.executions, SUB_WF_EXEC).to_dict(),
|
||||
sub_execution_list[1].to_dict()
|
||||
)
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from mistralclient.api.v2.executions import Execution
|
||||
from mistralclient.api.v2 import tasks
|
||||
from mistralclient.tests.unit.v2 import base
|
||||
|
||||
@ -30,9 +31,20 @@ TASK = {
|
||||
'result': {'some': 'result'}
|
||||
}
|
||||
|
||||
SUB_WF_EXEC = {
|
||||
'id': "456",
|
||||
'workflow_id': '123e4567-e89b-12d3-a456-426655440000',
|
||||
'workflow_name': 'my_sub_wf',
|
||||
'workflow_namespace': '',
|
||||
'task_execution_id': "1",
|
||||
'description': '',
|
||||
'state': 'RUNNING',
|
||||
'input': {}
|
||||
}
|
||||
|
||||
URL_TEMPLATE = '/tasks'
|
||||
URL_TEMPLATE_ID = '/tasks/%s'
|
||||
URL_TEMPLATE_SUB_EXECUTIONS = '/tasks/%s/executions%s'
|
||||
|
||||
|
||||
class TestTasksV2(base.BaseClientV2Test):
|
||||
@ -136,3 +148,17 @@ class TestTasksV2(base.BaseClientV2Test):
|
||||
'env': jsonutils.dumps({'k1': 'foobar'})
|
||||
}
|
||||
self.assertDictEqual(body, self.requests_mock.last_request.json())
|
||||
|
||||
def test_get_sub_executions(self):
|
||||
url = self.TEST_URL + URL_TEMPLATE_SUB_EXECUTIONS \
|
||||
% (TASK['id'], '?max_depth=-1&errors_only=')
|
||||
|
||||
self.requests_mock.get(url, json={'executions': [SUB_WF_EXEC]})
|
||||
|
||||
sub_execution_list = self.tasks.get_task_sub_executions(TASK['id'])
|
||||
|
||||
self.assertEqual(1, len(sub_execution_list))
|
||||
self.assertDictEqual(
|
||||
Execution(self.executions, SUB_WF_EXEC).to_dict(),
|
||||
sub_execution_list[0].to_dict()
|
||||
)
|
||||
|
9
releasenotes/notes/add_sub_executions_new_commands.yaml
Normal file
9
releasenotes/notes/add_sub_executions_new_commands.yaml
Normal file
@ -0,0 +1,9 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Added 2 new CLI commands, "execution-get-sub-executions" returns
|
||||
sub-executions of a given execution id and "task-get-sub-executions"
|
||||
returns the sub-executions of a given task execution id.
|
||||
Both commands have the options "--max-depth" which is the max depth of a
|
||||
sub-execution, and "--errors-only" that allows to find only ERROR paths of
|
||||
the execution tree.
|
Loading…
Reference in New Issue
Block a user