Support ID for workflow operations in CLI

Change-Id: I812bf5b3790ed361b583adae30f9ab19c31dcb51
Implements: blueprint support-id-in-workflow-operation
This commit is contained in:
Lingxian Kong 2015-12-23 00:43:41 +08:00
parent 9883b2ac1a
commit 0f2b48a77c
17 changed files with 194 additions and 65 deletions

View File

@ -0,0 +1,11 @@
---
version: '2.0'
wf_single:
type: direct
tasks:
hello:
action: std.echo output="Hello"
publish:
result: <% $.hello %>

View File

@ -43,11 +43,13 @@ class WorkflowManager(base.ResourceManager):
return [self.resource_class(self, resource_data)
for resource_data in base.extract_json(resp, 'workflows')]
def update(self, definition, scope='private'):
def update(self, definition, scope='private', id=None):
self._ensure_not_empty(definition=definition)
url_pre = ('/workflows/%s' % id) if id else '/workflows'
resp = self.client.http_client.put(
'/workflows?scope=%s' % scope,
'%s?scope=%s' % (url_pre, scope),
definition,
headers={'content-type': 'text/plain'}
)
@ -55,6 +57,9 @@ class WorkflowManager(base.ResourceManager):
if resp.status_code != 200:
self._raise_api_exception(resp)
if id:
return self.resource_class(self, base.extract_json(resp, None))
return [self.resource_class(self, resource_data)
for resource_data in base.extract_json(resp, 'workflows')]
@ -81,15 +86,15 @@ class WorkflowManager(base.ResourceManager):
response_key='workflows',
)
def get(self, name):
self._ensure_not_empty(name=name)
def get(self, identifier):
self._ensure_not_empty(identifier=identifier)
return self._get('/workflows/%s' % name)
return self._get('/workflows/%s' % identifier)
def delete(self, name):
self._ensure_not_empty(name=name)
def delete(self, identifier):
self._ensure_not_empty(identifier=identifier)
self._delete('/workflows/%s' % name)
self._delete('/workflows/%s' % identifier)
def validate(self, definition):
self._ensure_not_empty(definition=definition)

View File

@ -41,7 +41,11 @@ class MistralLister(lister.Lister):
f = self._get_format_function()
data = [f(r)[1] for r in self._get_resources(parsed_args)]
ret = self._get_resources(parsed_args)
if not isinstance(ret, list):
ret = [ret]
data = [f(r)[1] for r in ret]
if data:
return f()[0], data

View File

@ -80,13 +80,13 @@ class Get(show.ShowOne):
def get_parser(self, prog_name):
parser = super(Get, self).get_parser(prog_name)
parser.add_argument('name', help='Workflow name')
parser.add_argument('identifier', help='Workflow ID or name.')
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
wf = mistral_client.workflows.get(parsed_args.name)
wf = mistral_client.workflows.get(parsed_args.identifier)
return format(wf)
@ -100,7 +100,7 @@ class Create(base.MistralLister):
parser.add_argument(
'definition',
type=argparse.FileType('r'),
help='Workflow definition file'
help='Workflow definition file.'
)
parser.add_argument(
'--public',
@ -134,7 +134,11 @@ class Delete(command.Command):
def get_parser(self, prog_name):
parser = super(Delete, self).get_parser(prog_name)
parser.add_argument('name', nargs='+', help='Name of workflow(s).')
parser.add_argument(
'identifier',
nargs='+',
help='Name or ID of workflow(s).'
)
return parser
@ -142,7 +146,7 @@ class Delete(command.Command):
mistral_client = self.app.client_manager.workflow_engine
utils.do_action_on_many(
lambda s: mistral_client.workflows.delete(s),
parsed_args.name,
parsed_args.identifier,
"Request to delete workflow %s has been accepted.",
"Unable to delete the specified workflow(s)."
)
@ -159,6 +163,7 @@ class Update(base.MistralLister):
type=argparse.FileType('r'),
help='Workflow definition'
)
parser.add_argument('--id', help='Workflow ID.')
parser.add_argument(
'--public',
action='store_true',
@ -176,7 +181,8 @@ class Update(base.MistralLister):
return mistral_client.workflows.update(
parsed_args.definition.read(),
scope=scope
scope=scope,
id=parsed_args.id
)
@ -186,15 +192,15 @@ class GetDefinition(command.Command):
def get_parser(self, prog_name):
parser = super(GetDefinition, self).get_parser(prog_name)
parser.add_argument('name', help='Workflow name')
parser.add_argument('identifier', help='Workflow ID or name.')
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
definition = mistral_client.workflows.get(parsed_args.name).definition
wf = mistral_client.workflows.get(parsed_args.identifier)
self.app.stdout.write(definition or "\n")
self.app.stdout.write(wf.definition or "\n")
class Validate(show.ShowOne):

View File

@ -40,6 +40,9 @@ class MistralClientTestBase(base.MistralCLIAuth, base.MistralCLIAltAuth):
cls.wf_def = os.path.relpath(
'functionaltests/resources/v2/wf_v2.yaml', os.getcwd())
cls.wf_single_def = os.path.relpath(
'functionaltests/resources/v2/wf_single_v2.yaml', os.getcwd())
cls.wf_with_delay_def = os.path.relpath(
'functionaltests/resources/v2/wf_delay_v2.yaml', os.getcwd())
@ -109,7 +112,7 @@ class MistralClientTestBase(base.MistralCLIAuth, base.MistralCLIAltAuth):
self.mistral_cli,
admin,
'workflow-delete',
params=workflow['Name']
params=workflow['ID']
)
return wf

View File

@ -153,7 +153,7 @@ class WorkflowIsolationCLITests(base_v2.MistralClientTestBase):
exceptions.CommandFailed,
self.mistral_alt_user,
"workflow-get",
params=wf[0]["Name"]
params=wf[0]["ID"]
)
def test_create_public_workflow(self):
@ -176,7 +176,7 @@ class WorkflowIsolationCLITests(base_v2.MistralClientTestBase):
exceptions.CommandFailed,
self.mistral_alt_user,
"workflow-delete",
params=wf[0]["Name"]
params=wf[0]["ID"]
)
@ -246,30 +246,45 @@ class ActionIsolationCLITests(base_v2.MistralClientTestBase):
class CronTriggerIsolationCLITests(base_v2.MistralClientTestBase):
def test_cron_trigger_name_uniqueness(self):
wf = self.workflow_create(self.wf_def)
self.cron_trigger_create(
"trigger", wf[0]["Name"], "{}", "5 * * * *")
"admin_trigger",
wf[0]["ID"],
"{}",
"5 * * * *"
)
self.assertRaises(
exceptions.CommandFailed,
self.cron_trigger_create,
"trigger",
"5 * * * *",
wf[0]["Name"],
"admin_trigger",
wf[0]["ID"],
"{}"
"5 * * * *",
)
wf = self.workflow_create(self.wf_def, admin=False)
self.cron_trigger_create("trigger", wf[0]["Name"], "{}", "5 * * * *",
None, None, admin=False)
self.cron_trigger_create(
"user_trigger",
wf[0]["ID"],
"{}",
"5 * * * *",
None,
None,
admin=False
)
self.assertRaises(
exceptions.CommandFailed,
self.cron_trigger_create,
"trigger", wf[0]["Name"], "{}", "5 * * * *",
None, None, admin=False
"user_trigger",
wf[0]["ID"],
"{}",
"5 * * * *",
None,
None,
admin=False
)
def test_cron_trigger_isolation(self):

View File

@ -241,37 +241,85 @@ class WorkflowCLITests(base_v2.MistralClientTestBase):
def test_workflow_update(self):
wf = self.workflow_create(self.wf_def)
wf_name = wf[0]['Name']
wf_id = wf[0]['ID']
created_wf_info = self.get_item_info(
get_from=wf, get_by='Name', value=wf_name)
get_from=wf,
get_by='Name',
value=wf_name
)
# Update a workflow with definition unchanged.
upd_wf = self.mistral_admin(
'workflow-update', params='{0}'.format(self.wf_def))
'workflow-update',
params='{0}'.format(self.wf_def)
)
self.assertTableStruct(upd_wf, ['Name', 'Created at', 'Updated at'])
updated_wf_info = self.get_item_info(
get_from=upd_wf, get_by='Name', value=wf_name)
get_from=upd_wf,
get_by='Name',
value=wf_name
)
self.assertEqual(wf_name, upd_wf[0]['Name'])
self.assertEqual(
created_wf_info['Created at'].split(".")[0],
updated_wf_info['Created at']
)
self.assertEqual(
created_wf_info['Updated at'],
updated_wf_info['Updated at']
)
self.assertEqual(created_wf_info['Created at'].split(".")[0],
updated_wf_info['Created at'])
self.assertEqual(created_wf_info['Updated at'],
updated_wf_info['Updated at'])
# Update a workflow with definition changed.
upd_wf = self.mistral_admin(
'workflow-update', params='{0}'.format(self.wf_with_delay_def))
'workflow-update',
params='{0}'.format(self.wf_with_delay_def)
)
self.assertTableStruct(upd_wf, ['Name', 'Created at', 'Updated at'])
updated_wf_info = self.get_item_info(
get_from=upd_wf, get_by='Name', value=wf_name)
get_from=upd_wf,
get_by='Name',
value=wf_name
)
self.assertEqual(wf_name, upd_wf[0]['Name'])
self.assertEqual(
created_wf_info['Created at'].split(".")[0],
updated_wf_info['Created at']
)
self.assertNotEqual(
created_wf_info['Updated at'],
updated_wf_info['Updated at']
)
self.assertEqual(created_wf_info['Created at'].split(".")[0],
updated_wf_info['Created at'])
self.assertNotEqual(created_wf_info['Updated at'],
updated_wf_info['Updated at'])
# Update a workflow with uuid.
upd_wf = self.mistral_admin(
'workflow-update',
params='{0} --id {1}'.format(self.wf_with_delay_def, wf_id)
)
self.assertTableStruct(upd_wf, ['Name', 'Created at', 'Updated at'])
updated_wf_info = self.get_item_info(
get_from=upd_wf,
get_by='ID',
value=wf_id
)
self.assertEqual(wf_name, upd_wf[0]['Name'])
self.assertEqual(
created_wf_info['Created at'].split(".")[0],
updated_wf_info['Created at']
)
self.assertNotEqual(
created_wf_info['Updated at'],
updated_wf_info['Updated at']
)
def test_workflow_update_truncate_input(self):
input_value = "very_long_input_parameter_name_that_should_be_truncated"
@ -301,6 +349,15 @@ class WorkflowCLITests(base_v2.MistralClientTestBase):
fetched_wf_name = self.get_value_of_field(fetched, 'Name')
self.assertEqual(wf_name, fetched_wf_name)
def test_workflow_get_with_id(self):
created = self.workflow_create(self.wf_def)
wf_name = created[0]['Name']
wf_id = created[0]['ID']
fetched = self.mistral_admin('workflow-get', params=wf_id)
fetched_wf_name = self.get_value_of_field(fetched, 'Name')
self.assertEqual(wf_name, fetched_wf_name)
def test_workflow_get_definition(self):
wf = self.workflow_create(self.wf_def)
wf_name = wf[0]['Name']

View File

@ -93,7 +93,7 @@ class TestCLIActionExecutions(base.BaseCommandTest):
)
def test_list(self):
self.client.action_executions.list.return_value = (ACTION_EX,)
self.client.action_executions.list.return_value = [ACTION_EX]
result = self.call(action_ex_cmd.List)

View File

@ -52,7 +52,7 @@ ACTION_WITH_DEF = actions.Action(mock, ACTION_WITH_DEF_DICT)
class TestCLIActionsV2(base.BaseCommandTest):
@mock.patch('argparse.open', create=True)
def test_create(self, mock_open):
self.client.actions.create.return_value = (ACTION,)
self.client.actions.create.return_value = [ACTION]
result = self.call(action_cmd.Create, app_args=['1.txt'])
@ -63,7 +63,7 @@ class TestCLIActionsV2(base.BaseCommandTest):
@mock.patch('argparse.open', create=True)
def test_create_public(self, mock_open):
self.client.actions.create.return_value = (ACTION,)
self.client.actions.create.return_value = [ACTION]
result = self.call(
action_cmd.Create,
@ -91,7 +91,7 @@ class TestCLIActionsV2(base.BaseCommandTest):
mock.Mock(),
action_long_input_dict
)
self.client.actions.create.return_value = (workflow_long_input,)
self.client.actions.create.return_value = [workflow_long_input]
result = self.call(action_cmd.Create, app_args=['1.txt'])
@ -103,7 +103,7 @@ class TestCLIActionsV2(base.BaseCommandTest):
@mock.patch('argparse.open', create=True)
def test_update(self, mock_open):
self.client.actions.update.return_value = (ACTION,)
self.client.actions.update.return_value = [ACTION]
result = self.call(action_cmd.Update, app_args=['my_action.yaml'])
@ -114,7 +114,7 @@ class TestCLIActionsV2(base.BaseCommandTest):
@mock.patch('argparse.open', create=True)
def test_update_public(self, mock_open):
self.client.actions.update.return_value = (ACTION,)
self.client.actions.update.return_value = [ACTION]
result = self.call(
action_cmd.Update,
@ -132,7 +132,7 @@ class TestCLIActionsV2(base.BaseCommandTest):
)
def test_list(self):
self.client.actions.list.return_value = (ACTION,)
self.client.actions.list.return_value = [ACTION]
result = self.call(action_cmd.List)

View File

@ -58,7 +58,7 @@ class TestCLITriggersV2(base.BaseCommandTest):
)
def test_list(self):
self.client.cron_triggers.list.return_value = (TRIGGER,)
self.client.cron_triggers.list.return_value = [TRIGGER]
result = self.call(cron_triggers_cmd.List)

View File

@ -92,7 +92,7 @@ class TestCLIEnvironmentsV2(base.BaseCommandTest):
self._test_update(yml)
def test_list(self):
self.client.environments.list.return_value = (ENVIRONMENT,)
self.client.environments.list.return_value = [ENVIRONMENT]
expected = (ENVIRONMENT_DICT['name'],
ENVIRONMENT_DICT['description'],
ENVIRONMENT_DICT['scope'],

View File

@ -139,7 +139,7 @@ class TestCLIExecutionsV2(base.BaseCommandTest):
)
def test_list(self):
self.client.executions.list.return_value = (EXEC, SUB_WF_EXEC)
self.client.executions.list.return_value = [EXEC, SUB_WF_EXEC]
result = self.call(execution_cmd.List)
@ -149,7 +149,7 @@ class TestCLIExecutionsV2(base.BaseCommandTest):
)
def test_list_with_pagination(self):
self.client.executions.list.return_value = (EXEC,)
self.client.executions.list.return_value = [EXEC]
self.call(execution_cmd.List)
self.client.executions.list.assert_called_once_with(

View File

@ -29,7 +29,7 @@ SERVICE = services.Service(mock, SERVICE_DICT)
class TestCLIServicesV2(base.BaseCommandTest):
def test_list(self):
self.client.services.list.return_value = (SERVICE,)
self.client.services.list.return_value = [SERVICE]
expected = (SERVICE_DICT['name'], SERVICE_DICT['type'],)
result = self.call(service_cmd.List)

View File

@ -49,14 +49,14 @@ EXPECTED_TASK_RESULT = ('123', 'some', 'thing', '321', 'RUNNING', None)
class TestCLITasksV2(base.BaseCommandTest):
def test_list(self):
self.client.tasks.list.return_value = (TASK,)
self.client.tasks.list.return_value = [TASK]
result = self.call(task_cmd.List)
self.assertEqual([EXPECTED_TASK_RESULT], result[1])
def test_list_with_workflow_execution(self):
self.client.tasks.list.return_value = (TASK,)
self.client.tasks.list.return_value = [TASK]
result = self.call(task_cmd.List, app_args=['workflow_execution'])

View File

@ -65,7 +65,7 @@ class TestCLIWorkbooksV2(base.BaseCommandTest):
self.assertEqual(('a', 'a, b', '1', '1'), result[1])
def test_list(self):
self.client.workbooks.list.return_value = (WORKBOOK,)
self.client.workbooks.list.return_value = [WORKBOOK]
result = self.call(workbook_cmd.List)

View File

@ -50,7 +50,7 @@ WORKFLOW_WITH_DEF = workflows.Workflow(mock, WF_WITH_DEF_DICT)
class TestCLIWorkflowsV2(base.BaseCommandTest):
@mock.patch('argparse.open', create=True)
def test_create(self, mock_open):
self.client.workflows.create.return_value = (WORKFLOW,)
self.client.workflows.create.return_value = [WORKFLOW]
result = self.call(workflow_cmd.Create, app_args=['1.txt'])
@ -61,7 +61,7 @@ class TestCLIWorkflowsV2(base.BaseCommandTest):
@mock.patch('argparse.open', create=True)
def test_create_public(self, mock_open):
self.client.workflows.create.return_value = (WORKFLOW,)
self.client.workflows.create.return_value = [WORKFLOW]
result = self.call(
workflow_cmd.Create,
@ -86,7 +86,7 @@ class TestCLIWorkflowsV2(base.BaseCommandTest):
)
wf_long_input_dict['input'] = long_input
workflow_long_input = workflows.Workflow(mock, wf_long_input_dict)
self.client.workflows.create.return_value = (workflow_long_input,)
self.client.workflows.create.return_value = [workflow_long_input]
result = self.call(workflow_cmd.Create, app_args=['1.txt'])
@ -98,7 +98,7 @@ class TestCLIWorkflowsV2(base.BaseCommandTest):
@mock.patch('argparse.open', create=True)
def test_update(self, mock_open):
self.client.workflows.update.return_value = (WORKFLOW,)
self.client.workflows.update.return_value = [WORKFLOW]
result = self.call(workflow_cmd.Update, app_args=['1.txt'])
@ -109,7 +109,7 @@ class TestCLIWorkflowsV2(base.BaseCommandTest):
@mock.patch('argparse.open', create=True)
def test_update_public(self, mock_open):
self.client.workflows.update.return_value = (WORKFLOW,)
self.client.workflows.update.return_value = [WORKFLOW]
result = self.call(
workflow_cmd.Update,
@ -126,8 +126,22 @@ class TestCLIWorkflowsV2(base.BaseCommandTest):
self.client.workflows.update.call_args[1]['scope']
)
@mock.patch('argparse.open', create=True)
def test_update_with_id(self, mock_open):
self.client.workflows.update.return_value = WORKFLOW
result = self.call(
workflow_cmd.Update,
app_args=['1.txt', '--id', '1-2-3-4']
)
self.assertEqual(
[('1-2-3-4', 'a', '12345', 'a, b', 'param', '1', '1')],
result[1]
)
def test_list(self):
self.client.workflows.list.return_value = (WORKFLOW,)
self.client.workflows.list.return_value = [WORKFLOW]
result = self.call(workflow_cmd.List)

View File

@ -68,6 +68,20 @@ class TestWorkflowsV2(base.BaseClientV2Test):
headers={'content-type': 'text/plain'}
)
def test_update_with_id(self):
mock = self.mock_http_put(content=WORKFLOW)
wf = self.workflows.update(WF_DEF, id='123')
self.assertIsNotNone(wf)
self.assertEqual(WF_DEF, wf.definition)
mock.assert_called_once_with(
'/workflows/123?scope=private',
WF_DEF,
headers={'content-type': 'text/plain'}
)
def test_list(self):
mock = self.mock_http_get(content={'workflows': [WORKFLOW]})