From bf02b048bf43dbb86f1e072d040f2a9f66d04130 Mon Sep 17 00:00:00 2001 From: wangxiyuan Date: Mon, 17 Aug 2015 10:34:22 +0800 Subject: [PATCH] Support image deletion in batches in v2 Client doesn't support image deletion in batches in v2 now. It's useful. So it's need to add it. Change-Id: Idf5a6890b3fd01a65fecab2033b21367c30bc6b1 Closes-bug:#1485407 --- glanceclient/common/utils.py | 4 ++ glanceclient/tests/unit/v2/test_shell_v2.py | 50 ++++++++++++++++----- glanceclient/v2/shell.py | 26 ++++++++--- 3 files changed, 63 insertions(+), 17 deletions(-) diff --git a/glanceclient/common/utils.py b/glanceclient/common/utils.py index 955a3650..37dc43cd 100644 --- a/glanceclient/common/utils.py +++ b/glanceclient/common/utils.py @@ -286,6 +286,10 @@ def exit(msg='', exit_code=1): sys.exit(exit_code) +def print_err(msg): + print(encodeutils.safe_decode(msg), file=sys.stderr) + + def save_image(data, path): """Save an image to the specified path. diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index 9df3cd7a..1a5f5013 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -13,6 +13,7 @@ # 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 argparse import json import mock import os @@ -106,13 +107,15 @@ class ShellV2Test(testtools.TestCase): utils.print_dict = mock.Mock() utils.save_image = mock.Mock() - def assert_exits_with_msg(self, func, func_args, err_msg): + def assert_exits_with_msg(self, func, func_args, err_msg=None): with mock.patch.object(utils, 'exit') as mocked_utils_exit: mocked_utils_exit.return_value = '%s' % err_msg func(self.gc, func_args) - - mocked_utils_exit.assert_called_once_with(err_msg) + if err_msg: + mocked_utils_exit.assert_called_once_with(err_msg) + else: + mocked_utils_exit.assert_called_once_with() def _run_command(self, cmd): self.shell.main(cmd.split()) @@ -591,24 +594,49 @@ class ShellV2Test(testtools.TestCase): mocked_data.assert_called_once_with('IMG-01') def test_do_image_delete(self): - args = self._make_args({'id': 'pass', 'file': 'test'}) + args = argparse.Namespace(id=['image1', 'image2']) with mock.patch.object(self.gc.images, 'delete') as mocked_delete: mocked_delete.return_value = 0 test_shell.do_image_delete(self.gc, args) + self.assertEqual(2, mocked_delete.call_count) - mocked_delete.assert_called_once_with('pass') + @mock.patch.object(utils, 'exit') + @mock.patch.object(utils, 'print_err') + def test_do_image_delete_with_invalid_ids(self, mocked_print_err, + mocked_utils_exit): + args = argparse.Namespace(id=['image1', 'image2']) + with mock.patch.object(self.gc.images, 'delete') as mocked_delete: + mocked_delete.side_effect = exc.HTTPNotFound + + test_shell.do_image_delete(self.gc, args) + + self.assertEqual(2, mocked_delete.call_count) + self.assertEqual(2, mocked_print_err.call_count) + mocked_utils_exit.assert_called_once_with() + + @mock.patch.object(utils, 'exit') + @mock.patch.object(utils, 'print_err') + def test_do_image_delete_with_forbidden_ids(self, mocked_print_err, + mocked_utils_exit): + args = argparse.Namespace(id=['image1', 'image2']) + with mock.patch.object(self.gc.images, 'delete') as mocked_delete: + mocked_delete.side_effect = exc.HTTPForbidden + + test_shell.do_image_delete(self.gc, args) + + self.assertEqual(2, mocked_delete.call_count) + self.assertEqual(2, mocked_print_err.call_count) + mocked_utils_exit.assert_called_once_with() def test_do_image_delete_deleted(self): image_id = 'deleted-img' - args = self._make_args({'id': image_id}) - with mock.patch.object(self.gc.images, 'delete') as mocked_get: - mocked_get.side_effect = exc.HTTPNotFound + args = argparse.Namespace(id=[image_id]) + with mock.patch.object(self.gc.images, 'delete') as mocked_delete: + mocked_delete.side_effect = exc.HTTPNotFound - msg = "No image with an ID of '%s' exists." % image_id self.assert_exits_with_msg(func=test_shell.do_image_delete, - func_args=args, - err_msg=msg) + func_args=args) def test_do_member_list(self): args = self._make_args({'image_id': 'IMG-01'}) diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index 593630db..23c6753a 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -311,14 +311,28 @@ def do_image_upload(gc, args): gc.images.upload(args.id, image_data, args.size) -@utils.arg('id', metavar='', help='ID of image to delete.') +@utils.arg('id', metavar='', nargs='+', + help='ID of image(s) to delete.') def do_image_delete(gc, args): """Delete specified image.""" - try: - gc.images.delete(args.id) - except exc.HTTPNotFound: - msg = "No image with an ID of '%s' exists." % args.id - utils.exit(msg) + failure_flag = False + for args_id in args.id: + try: + gc.images.delete(args_id) + except exc.HTTPForbidden: + msg = "You are not permitted to delete the image '%s'." % args_id + utils.print_err(msg) + failure_flag = True + except exc.HTTPNotFound: + msg = "No image with an ID of '%s' exists." % args_id + utils.print_err(msg) + failure_flag = True + except exc.HTTPException as e: + msg = "'%s': Unable to delete image '%s'" % (e, args_id) + utils.print_err(msg) + failure_flag = True + if failure_flag: + utils.exit() @utils.arg('image_id', metavar='',