From 3609a0d4f86488a9eb68066675c758f49cf4c52d Mon Sep 17 00:00:00 2001 From: LingxianKong Date: Fri, 24 Apr 2015 19:04:13 +0800 Subject: [PATCH] Support resource deletion in batches Support CLI commands like: usage: mistral workflow-delete [-h] name [name ...] Change-Id: I467ecfddf88d30e437d00de2671c2a0487557693 Implements: blueprint mistral-batch-deleting-support --- mistralclient/commands/v2/actions.py | 11 +++++-- mistralclient/commands/v2/cron_triggers.py | 11 +++++-- mistralclient/commands/v2/environments.py | 12 +++++-- mistralclient/commands/v2/executions.py | 15 +++++++-- mistralclient/commands/v2/workbooks.py | 11 +++++-- mistralclient/commands/v2/workflows.py | 11 +++++-- .../tests/unit/v2/test_cli_actions.py | 16 ++++++++-- .../tests/unit/v2/test_cli_cron_triggers.py | 16 ++++++++-- .../tests/unit/v2/test_cli_environments.py | 16 ++++++++-- .../tests/unit/v2/test_cli_executions.py | 16 ++++++++-- .../tests/unit/v2/test_cli_workbooks.py | 16 ++++++++-- .../tests/unit/v2/test_cli_workflows.py | 16 ++++++++-- mistralclient/utils.py | 31 +++++++++++++++++++ 13 files changed, 170 insertions(+), 28 deletions(-) create mode 100644 mistralclient/utils.py diff --git a/mistralclient/commands/v2/actions.py b/mistralclient/commands/v2/actions.py index 01ea0af2..ad14fb25 100644 --- a/mistralclient/commands/v2/actions.py +++ b/mistralclient/commands/v2/actions.py @@ -22,6 +22,7 @@ from cliff import show from mistralclient.api.v2 import actions from mistralclient.commands.v2 import base +from mistralclient import utils LOG = logging.getLogger(__name__) @@ -125,12 +126,18 @@ class Delete(command.Command): def get_parser(self, prog_name): parser = super(Delete, self).get_parser(prog_name) - parser.add_argument('name', help='Action name') + parser.add_argument('name', nargs='+', help='Name of action(s).') return parser def take_action(self, parsed_args): - actions.ActionManager(self.app.client).delete(parsed_args.name) + action_mgr = actions.ActionManager(self.app.client) + utils.do_action_on_many( + lambda s: action_mgr.delete(s), + parsed_args.name, + "Request to delete action %s has been accepted.", + "Unable to delete the specified action(s)." + ) class Update(base.MistralLister): diff --git a/mistralclient/commands/v2/cron_triggers.py b/mistralclient/commands/v2/cron_triggers.py index 9972f563..e975265d 100644 --- a/mistralclient/commands/v2/cron_triggers.py +++ b/mistralclient/commands/v2/cron_triggers.py @@ -22,6 +22,7 @@ from cliff import show from mistralclient.api.v2 import cron_triggers from mistralclient.commands.v2 import base +from mistralclient import utils LOG = logging.getLogger(__name__) @@ -160,11 +161,15 @@ class Delete(command.Command): def get_parser(self, prog_name): parser = super(Delete, self).get_parser(prog_name) - parser.add_argument('name', help='Cron trigger name') + parser.add_argument('name', nargs='+', help='Name of cron trigger(s).') return parser def take_action(self, parsed_args): mgr = cron_triggers.CronTriggerManager(self.app.client) - - mgr.delete(parsed_args.name) + utils.do_action_on_many( + lambda s: mgr.delete(s), + parsed_args.name, + "Request to delete cron trigger %s has been accepted.", + "Unable to delete the specified cron trigger(s)." + ) diff --git a/mistralclient/commands/v2/environments.py b/mistralclient/commands/v2/environments.py index ef181c6f..90bc1c5f 100644 --- a/mistralclient/commands/v2/environments.py +++ b/mistralclient/commands/v2/environments.py @@ -22,6 +22,7 @@ import yaml from mistralclient.api.v2 import environments from mistralclient.commands.v2 import base +from mistralclient import utils LOG = logging.getLogger(__name__) @@ -153,13 +154,18 @@ class Delete(command.Command): def get_parser(self, prog_name): parser = super(Delete, self).get_parser(prog_name) - parser.add_argument('name', help='Environment name') + parser.add_argument('name', nargs='+', help='Name of environment(s).') return parser def take_action(self, parsed_args): - environments.EnvironmentManager(self.app.client).delete( - parsed_args.name) + env_mgr = environments.EnvironmentManager(self.app.client) + utils.do_action_on_many( + lambda s: env_mgr.delete(s), + parsed_args.name, + "Request to delete environment %s has been accepted.", + "Unable to delete the specified environment(s)." + ) class Update(show.ShowOne): diff --git a/mistralclient/commands/v2/executions.py b/mistralclient/commands/v2/executions.py index ba6f33f6..b065ad84 100644 --- a/mistralclient/commands/v2/executions.py +++ b/mistralclient/commands/v2/executions.py @@ -22,6 +22,7 @@ from cliff import show from mistralclient.api.v2 import executions from mistralclient.commands.v2 import base +from mistralclient import utils LOG = logging.getLogger(__name__) @@ -140,12 +141,22 @@ class Delete(command.Command): def get_parser(self, prog_name): parser = super(Delete, self).get_parser(prog_name) - parser.add_argument('id', help='Execution identifier') + parser.add_argument( + 'id', + nargs='+', + help='Id of execution identifier(s).' + ) return parser def take_action(self, parsed_args): - executions.ExecutionManager(self.app.client).delete(parsed_args.id) + exe_mgr = executions.ExecutionManager(self.app.client) + utils.do_action_on_many( + lambda s: exe_mgr.delete(s), + parsed_args.id, + "Request to delete execution %s has been accepted.", + "Unable to delete the specified execution(s)." + ) class Update(show.ShowOne): diff --git a/mistralclient/commands/v2/workbooks.py b/mistralclient/commands/v2/workbooks.py index 635eec65..8b9edccc 100644 --- a/mistralclient/commands/v2/workbooks.py +++ b/mistralclient/commands/v2/workbooks.py @@ -22,6 +22,7 @@ from cliff import show from mistralclient.api.v2 import workbooks from mistralclient.commands.v2 import base from mistralclient import exceptions as exc +from mistralclient import utils LOG = logging.getLogger(__name__) @@ -110,12 +111,18 @@ class Delete(command.Command): def get_parser(self, prog_name): parser = super(Delete, self).get_parser(prog_name) - parser.add_argument('name', help='Workbook name') + parser.add_argument('name', nargs='+', help='Name of workbook(s).') return parser def take_action(self, parsed_args): - workbooks.WorkbookManager(self.app.client).delete(parsed_args.name) + wb_mgr = workbooks.WorkbookManager(self.app.client) + utils.do_action_on_many( + lambda s: wb_mgr.delete(s), + parsed_args.name, + "Request to delete workbook %s has been accepted.", + "Unable to delete the specified workbook(s)." + ) class Update(show.ShowOne): diff --git a/mistralclient/commands/v2/workflows.py b/mistralclient/commands/v2/workflows.py index 6138214e..47abb110 100644 --- a/mistralclient/commands/v2/workflows.py +++ b/mistralclient/commands/v2/workflows.py @@ -22,6 +22,7 @@ from cliff import show from mistralclient.api.v2 import workflows from mistralclient.commands.v2 import base from mistralclient import exceptions as exc +from mistralclient import utils LOG = logging.getLogger(__name__) @@ -119,12 +120,18 @@ class Delete(command.Command): def get_parser(self, prog_name): parser = super(Delete, self).get_parser(prog_name) - parser.add_argument('name', help='Workflow name') + parser.add_argument('name', nargs='+', help='Name of workflow(s).') return parser def take_action(self, parsed_args): - workflows.WorkflowManager(self.app.client).delete(parsed_args.name) + wf_mgr = workflows.WorkflowManager(self.app.client) + utils.do_action_on_many( + lambda s: wf_mgr.delete(s), + parsed_args.name, + "Request to delete workflow %s has been accepted.", + "Unable to delete the specified workflow(s)." + ) class Update(base.MistralLister): diff --git a/mistralclient/tests/unit/v2/test_cli_actions.py b/mistralclient/tests/unit/v2/test_cli_actions.py index 3efc7e71..a8df078b 100644 --- a/mistralclient/tests/unit/v2/test_cli_actions.py +++ b/mistralclient/tests/unit/v2/test_cli_actions.py @@ -95,8 +95,20 @@ class TestCLIActionsV2(base.BaseCommandTest): ) @mock.patch('mistralclient.api.v2.actions.ActionManager.delete') - def test_delete(self, mock): - self.assertIsNone(self.call(action_cmd.Delete, app_args=['name'])) + def test_delete(self, del_mock): + self.call(action_cmd.Delete, app_args=['name']) + + del_mock.assert_called_once_with('name') + + @mock.patch('mistralclient.api.v2.actions.ActionManager.delete') + def test_delete_with_multi_names(self, del_mock): + self.call(action_cmd.Delete, app_args=['name1', 'name2']) + + self.assertEqual(2, del_mock.call_count) + self.assertEqual( + [mock.call('name1'), mock.call('name2')], + del_mock.call_args_list + ) @mock.patch('mistralclient.api.v2.actions.' 'ActionManager.get') diff --git a/mistralclient/tests/unit/v2/test_cli_cron_triggers.py b/mistralclient/tests/unit/v2/test_cli_cron_triggers.py index 1ea865ca..62520e70 100644 --- a/mistralclient/tests/unit/v2/test_cli_cron_triggers.py +++ b/mistralclient/tests/unit/v2/test_cli_cron_triggers.py @@ -80,7 +80,17 @@ class TestCLIWorkbooksV2(base.BaseCommandTest): ) @mock.patch('mistralclient.api.v2.cron_triggers.CronTriggerManager.delete') - def test_delete(self, mock): - self.assertIsNone( - self.call(cron_triggers_cmd.Delete, app_args=['name']) + def test_delete(self, del_mock): + self.call(cron_triggers_cmd.Delete, app_args=['name']) + + del_mock.assert_called_once_with('name') + + @mock.patch('mistralclient.api.v2.cron_triggers.CronTriggerManager.delete') + def test_delete_with_multi_names(self, del_mock): + self.call(cron_triggers_cmd.Delete, app_args=['name1', 'name2']) + + self.assertEqual(2, del_mock.call_count) + self.assertEqual( + [mock.call('name1'), mock.call('name2')], + del_mock.call_args_list ) diff --git a/mistralclient/tests/unit/v2/test_cli_environments.py b/mistralclient/tests/unit/v2/test_cli_environments.py index 0c7af656..8dbbfed7 100644 --- a/mistralclient/tests/unit/v2/test_cli_environments.py +++ b/mistralclient/tests/unit/v2/test_cli_environments.py @@ -115,5 +115,17 @@ class TestCLIEnvironmentsV2(base.BaseCommandTest): self.assertEqual(EXPECTED_RESULT, result[1]) @mock.patch('mistralclient.api.v2.environments.EnvironmentManager.delete') - def test_delete(self, mock): - self.assertIsNone(self.call(environment_cmd.Delete, app_args=['name'])) + def test_delete(self, del_mock): + self.call(environment_cmd.Delete, app_args=['name']) + + del_mock.assert_called_once_with('name') + + @mock.patch('mistralclient.api.v2.environments.EnvironmentManager.delete') + def test_delete_with_multi_names(self, del_mock): + self.call(environment_cmd.Delete, app_args=['name1', 'name2']) + + self.assertEqual(2, del_mock.call_count) + self.assertEqual( + [mock.call('name1'), mock.call('name2')], + del_mock.call_args_list + ) diff --git a/mistralclient/tests/unit/v2/test_cli_executions.py b/mistralclient/tests/unit/v2/test_cli_executions.py index 4f5c8a21..e6b29317 100644 --- a/mistralclient/tests/unit/v2/test_cli_executions.py +++ b/mistralclient/tests/unit/v2/test_cli_executions.py @@ -82,7 +82,17 @@ class TestCLIExecutionsV2(base.BaseCommandTest): '1', '1'), result[1]) @mock.patch('mistralclient.api.v2.executions.ExecutionManager.delete') - def test_delete(self, mock): - result = self.call(execution_cmd.Delete, app_args=['id']) + def test_delete(self, del_mock): + self.call(execution_cmd.Delete, app_args=['id']) - self.assertIsNone(result) + del_mock.assert_called_once_with('id') + + @mock.patch('mistralclient.api.v2.executions.ExecutionManager.delete') + def test_delete_with_multi_names(self, del_mock): + self.call(execution_cmd.Delete, app_args=['id1', 'id2']) + + self.assertEqual(2, del_mock.call_count) + self.assertEqual( + [mock.call('id1'), mock.call('id2')], + del_mock.call_args_list + ) diff --git a/mistralclient/tests/unit/v2/test_cli_workbooks.py b/mistralclient/tests/unit/v2/test_cli_workbooks.py index 1530680c..b8c55fae 100644 --- a/mistralclient/tests/unit/v2/test_cli_workbooks.py +++ b/mistralclient/tests/unit/v2/test_cli_workbooks.py @@ -86,8 +86,20 @@ class TestCLIWorkbooksV2(base.BaseCommandTest): self.assertEqual(('a', 'a, b', '1', '1'), result[1]) @mock.patch('mistralclient.api.v2.workbooks.WorkbookManager.delete') - def test_delete(self, mock): - self.assertIsNone(self.call(workbook_cmd.Delete, app_args=['name'])) + def test_delete(self, del_mock): + self.call(workbook_cmd.Delete, app_args=['name']) + + del_mock.assert_called_once_with('name') + + @mock.patch('mistralclient.api.v2.workbooks.WorkbookManager.delete') + def test_delete_with_multi_names(self, del_mock): + self.call(workbook_cmd.Delete, app_args=['name1', 'name2']) + + self.assertEqual(2, del_mock.call_count) + self.assertEqual( + [mock.call('name1'), mock.call('name2')], + del_mock.call_args_list + ) @mock.patch('mistralclient.api.v2.workbooks.WorkbookManager.get') def test_get_definition(self, mock): diff --git a/mistralclient/tests/unit/v2/test_cli_workflows.py b/mistralclient/tests/unit/v2/test_cli_workflows.py index c15ae4e7..51202b8b 100644 --- a/mistralclient/tests/unit/v2/test_cli_workflows.py +++ b/mistralclient/tests/unit/v2/test_cli_workflows.py @@ -80,8 +80,20 @@ class TestCLIWorkflowsV2(base.BaseCommandTest): self.assertEqual(('a', 'a, b', 'param', '1', '1'), result[1]) @mock.patch('mistralclient.api.v2.workflows.WorkflowManager.delete') - def test_delete(self, mock): - self.assertIsNone(self.call(workflow_cmd.Delete, app_args=['name'])) + def test_delete(self, del_mock): + self.call(workflow_cmd.Delete, app_args=['name']) + + del_mock.assert_called_once_with('name') + + @mock.patch('mistralclient.api.v2.workflows.WorkflowManager.delete') + def test_delete_with_multi_names(self, del_mock): + self.call(workflow_cmd.Delete, app_args=['name1', 'name2']) + + self.assertEqual(2, del_mock.call_count) + self.assertEqual( + [mock.call('name1'), mock.call('name2')], + del_mock.call_args_list + ) @mock.patch('mistralclient.api.v2.workflows.' 'WorkflowManager.get') diff --git a/mistralclient/utils.py b/mistralclient/utils.py new file mode 100644 index 00000000..748d7321 --- /dev/null +++ b/mistralclient/utils.py @@ -0,0 +1,31 @@ +# Copyright 2015 - Huawei Technologies Co. Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import exceptions + + +def do_action_on_many(action, resources, success_msg, error_msg): + """Helper to run an action on many resources.""" + failure_flag = False + + for resource in resources: + try: + action(resource) + print(success_msg % resource) + except Exception as e: + failure_flag = True + print(e) + + if failure_flag: + raise exceptions.MistralClientException(error_msg)