From 96578cb8ab9a4b95144c33d0af38863fce8d8892 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Fri, 30 Dec 2016 13:22:07 +0800 Subject: [PATCH] Error handling for delete commands in identity Add missing multi deletion error handling for identity delete commands. All delete commands in identity support error handling now. Change-Id: I05626dcb5e516a423d610906347b02236ba7eeaf --- openstackclient/identity/v2_0/project.py | 24 ++++++++++--- openstackclient/identity/v2_0/role.py | 23 ++++++++++--- openstackclient/identity/v2_0/user.py | 24 ++++++++++--- openstackclient/identity/v3/group.py | 22 +++++++++--- openstackclient/identity/v3/project.py | 30 +++++++++++----- openstackclient/identity/v3/role.py | 27 +++++++++++---- openstackclient/identity/v3/trust.py | 26 ++++++++++++-- openstackclient/identity/v3/user.py | 30 +++++++++++----- .../tests/unit/identity/v2_0/test_project.py | 29 ++++++++++++++++ .../tests/unit/identity/v2_0/test_role.py | 27 +++++++++++++++ .../tests/unit/identity/v2_0/test_user.py | 27 +++++++++++++++ .../tests/unit/identity/v3/test_group.py | 27 +++++++++++++++ .../tests/unit/identity/v3/test_project.py | 27 +++++++++++++++ .../tests/unit/identity/v3/test_role.py | 34 +++++++++++++++++++ .../tests/unit/identity/v3/test_trust.py | 31 +++++++++++++++++ .../tests/unit/identity/v3/test_user.py | 29 ++++++++++++++++ 16 files changed, 392 insertions(+), 45 deletions(-) diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index 8526d6bdf6..ca565d4dd6 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -20,6 +20,7 @@ import logging from keystoneauth1 import exceptions as ks_exc from osc_lib.cli import parseractions from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -117,12 +118,25 @@ class DeleteProject(command.Command): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity + errors = 0 for project in parsed_args.projects: - project_obj = utils.find_resource( - identity_client.tenants, - project, - ) - identity_client.tenants.delete(project_obj.id) + try: + project_obj = utils.find_resource( + identity_client.tenants, + project, + ) + identity_client.tenants.delete(project_obj.id) + except Exception as e: + errors += 1 + LOG.error(_("Failed to delete project with " + "name or ID '%(project)s': %(e)s"), + {'project': project, 'e': e}) + + if errors > 0: + total = len(parsed_args.projects) + msg = (_("%(errors)s of %(total)s projects failed " + "to delete.") % {'errors': errors, 'total': total}) + raise exceptions.CommandError(msg) class ListProject(command.Lister): diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index 0a28a70a8f..e254e05fd8 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -124,12 +124,25 @@ class DeleteRole(command.Command): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity + errors = 0 for role in parsed_args.roles: - role_obj = utils.find_resource( - identity_client.roles, - role, - ) - identity_client.roles.delete(role_obj.id) + try: + role_obj = utils.find_resource( + identity_client.roles, + role, + ) + identity_client.roles.delete(role_obj.id) + except Exception as e: + errors += 1 + LOG.error(_("Failed to delete role with " + "name or ID '%(role)s': %(e)s"), + {'role': role, 'e': e}) + + if errors > 0: + total = len(parsed_args.roles) + msg = (_("%(errors)s of %(total)s roles failed " + "to delete.") % {'errors': errors, 'total': total}) + raise exceptions.CommandError(msg) class ListRole(command.Lister): diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index ddd5b981e0..2a3dde6b52 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -19,6 +19,7 @@ import logging from keystoneauth1 import exceptions as ks_exc from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -145,12 +146,25 @@ class DeleteUser(command.Command): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity + errors = 0 for user in parsed_args.users: - user_obj = utils.find_resource( - identity_client.users, - user, - ) - identity_client.users.delete(user_obj.id) + try: + user_obj = utils.find_resource( + identity_client.users, + user, + ) + identity_client.users.delete(user_obj.id) + except Exception as e: + errors += 1 + LOG.error(_("Failed to delete user with " + "name or ID '%(user)s': %(e)s"), + {'user': user, 'e': e}) + + if errors > 0: + total = len(parsed_args.users) + msg = (_("%(errors)s of %(total)s users failed " + "to delete.") % {'errors': errors, 'total': total}) + raise exceptions.CommandError(msg) class ListUser(command.Lister): diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index df684c129b..a03a86ebd2 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -20,6 +20,7 @@ import sys from keystoneauth1 import exceptions as ks_exc from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -194,11 +195,24 @@ class DeleteGroup(command.Command): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity + errors = 0 for group in parsed_args.groups: - group_obj = common.find_group(identity_client, - group, - parsed_args.domain) - identity_client.groups.delete(group_obj.id) + try: + group_obj = common.find_group(identity_client, + group, + parsed_args.domain) + identity_client.groups.delete(group_obj.id) + except Exception as e: + errors += 1 + LOG.error(_("Failed to delete group with " + "name or ID '%(group)s': %(e)s"), + {'group': group, 'e': e}) + + if errors > 0: + total = len(parsed_args.groups) + msg = (_("%(errors)s of %(total)s groups failed " + "to delete.") % {'errors': errors, 'total': total}) + raise exceptions.CommandError(msg) class ListGroup(command.Lister): diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index a634865911..12197cdde1 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -20,6 +20,7 @@ import logging from keystoneauth1 import exceptions as ks_exc from osc_lib.cli import parseractions from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -148,15 +149,28 @@ class DeleteProject(command.Command): domain = None if parsed_args.domain: domain = common.find_domain(identity_client, parsed_args.domain) + errors = 0 for project in parsed_args.projects: - if domain is not None: - project_obj = utils.find_resource(identity_client.projects, - project, - domain_id=domain.id) - else: - project_obj = utils.find_resource(identity_client.projects, - project) - identity_client.projects.delete(project_obj.id) + try: + if domain is not None: + project_obj = utils.find_resource(identity_client.projects, + project, + domain_id=domain.id) + else: + project_obj = utils.find_resource(identity_client.projects, + project) + identity_client.projects.delete(project_obj.id) + except Exception as e: + errors += 1 + LOG.error(_("Failed to delete project with " + "name or ID '%(project)s': %(e)s"), + {'project': project, 'e': e}) + + if errors > 0: + total = len(parsed_args.projects) + msg = (_("%(errors)s of %(total)s projects failed " + "to delete.") % {'errors': errors, 'total': total}) + raise exceptions.CommandError(msg) class ListProject(command.Lister): diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index c9d0fbf305..994ecc9c8b 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -20,6 +20,7 @@ import sys from keystoneauth1 import exceptions as ks_exc from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -223,14 +224,26 @@ class DeleteRole(command.Command): if parsed_args.domain: domain_id = common.find_domain(identity_client, parsed_args.domain).id - + errors = 0 for role in parsed_args.roles: - role_obj = utils.find_resource( - identity_client.roles, - role, - domain_id=domain_id - ) - identity_client.roles.delete(role_obj.id) + try: + role_obj = utils.find_resource( + identity_client.roles, + role, + domain_id=domain_id + ) + identity_client.roles.delete(role_obj.id) + except Exception as e: + errors += 1 + LOG.error(_("Failed to delete role with " + "name or ID '%(role)s': %(e)s"), + {'role': role, 'e': e}) + + if errors > 0: + total = len(parsed_args.roles) + msg = (_("%(errors)s of %(total)s roles failed " + "to delete.") % {'errors': errors, 'total': total}) + raise exceptions.CommandError(msg) class ListRole(command.Lister): diff --git a/openstackclient/identity/v3/trust.py b/openstackclient/identity/v3/trust.py index 62d72ea142..04ee4dce5a 100644 --- a/openstackclient/identity/v3/trust.py +++ b/openstackclient/identity/v3/trust.py @@ -14,8 +14,10 @@ """Identity v3 Trust action implementations""" import datetime +import logging from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -23,6 +25,9 @@ from openstackclient.i18n import _ from openstackclient.identity import common +LOG = logging.getLogger(__name__) + + class CreateTrust(command.ShowOne): _description = _("Create new trust") @@ -145,9 +150,24 @@ class DeleteTrust(command.Command): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - for t in parsed_args.trust: - trust_obj = utils.find_resource(identity_client.trusts, t) - identity_client.trusts.delete(trust_obj.id) + + errors = 0 + for trust in parsed_args.trust: + try: + trust_obj = utils.find_resource(identity_client.trusts, + trust) + identity_client.trusts.delete(trust_obj.id) + except Exception as e: + errors += 1 + LOG.error(_("Failed to delete trust with " + "name or ID '%(trust)s': %(e)s"), + {'trust': trust, 'e': e}) + + if errors > 0: + total = len(parsed_args.trust) + msg = (_("%(errors)s of %(total)s trusts failed " + "to delete.") % {'errors': errors, 'total': total}) + raise exceptions.CommandError(msg) class ListTrust(command.Lister): diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 796cf28c22..19a4c29875 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -20,6 +20,7 @@ import logging from keystoneauth1 import exceptions as ks_exc from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -161,15 +162,28 @@ class DeleteUser(command.Command): domain = None if parsed_args.domain: domain = common.find_domain(identity_client, parsed_args.domain) + errors = 0 for user in parsed_args.users: - if domain is not None: - user_obj = utils.find_resource(identity_client.users, - user, - domain_id=domain.id) - else: - user_obj = utils.find_resource(identity_client.users, - user) - identity_client.users.delete(user_obj.id) + try: + if domain is not None: + user_obj = utils.find_resource(identity_client.users, + user, + domain_id=domain.id) + else: + user_obj = utils.find_resource(identity_client.users, + user) + identity_client.users.delete(user_obj.id) + except Exception as e: + errors += 1 + LOG.error(_("Failed to delete user with " + "name or ID '%(user)s': %(e)s"), + {'user': user, 'e': e}) + + if errors > 0: + total = len(parsed_args.users) + msg = (_("%(errors)s of %(total)s users failed " + "to delete.") % {'errors': errors, 'total': total}) + raise exceptions.CommandError(msg) class ListUser(command.Lister): diff --git a/openstackclient/tests/unit/identity/v2_0/test_project.py b/openstackclient/tests/unit/identity/v2_0/test_project.py index c1f00762e4..4e1077db0d 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_project.py +++ b/openstackclient/tests/unit/identity/v2_0/test_project.py @@ -13,8 +13,11 @@ # under the License. # +import mock + from keystoneauth1 import exceptions as ks_exc from osc_lib import exceptions +from osc_lib import utils from openstackclient.identity.v2_0 import project from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes @@ -302,6 +305,32 @@ class TestProjectDelete(TestProject): ) self.assertIsNone(result) + @mock.patch.object(utils, 'find_resource') + def test_delete_multi_projects_with_exception(self, find_mock): + find_mock.side_effect = [self.fake_project, + exceptions.CommandError] + arglist = [ + self.fake_project.id, + 'unexist_project', + ] + verifylist = [ + ('projects', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 projects failed to delete.', + str(e)) + + find_mock.assert_any_call(self.projects_mock, self.fake_project.id) + find_mock.assert_any_call(self.projects_mock, 'unexist_project') + + self.assertEqual(2, find_mock.call_count) + self.projects_mock.delete.assert_called_once_with(self.fake_project.id) + class TestProjectList(TestProject): diff --git a/openstackclient/tests/unit/identity/v2_0/test_role.py b/openstackclient/tests/unit/identity/v2_0/test_role.py index 68ebf1418a..684ce803a2 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_role.py +++ b/openstackclient/tests/unit/identity/v2_0/test_role.py @@ -17,6 +17,7 @@ import mock from keystoneauth1 import exceptions as ks_exc from osc_lib import exceptions +from osc_lib import utils from openstackclient.identity.v2_0 import role from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes @@ -240,6 +241,32 @@ class TestRoleDelete(TestRole): ) self.assertIsNone(result) + @mock.patch.object(utils, 'find_resource') + def test_delete_multi_roles_with_exception(self, find_mock): + find_mock.side_effect = [self.fake_role, + exceptions.CommandError] + arglist = [ + self.fake_role.id, + 'unexist_role', + ] + verifylist = [ + ('roles', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 roles failed to delete.', + str(e)) + + find_mock.assert_any_call(self.roles_mock, self.fake_role.id) + find_mock.assert_any_call(self.roles_mock, 'unexist_role') + + self.assertEqual(2, find_mock.call_count) + self.roles_mock.delete.assert_called_once_with(self.fake_role.id) + class TestRoleList(TestRole): diff --git a/openstackclient/tests/unit/identity/v2_0/test_user.py b/openstackclient/tests/unit/identity/v2_0/test_user.py index 765f8559a8..a8b9497ecf 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_user.py +++ b/openstackclient/tests/unit/identity/v2_0/test_user.py @@ -17,6 +17,7 @@ import mock from keystoneauth1 import exceptions as ks_exc from osc_lib import exceptions +from osc_lib import utils from openstackclient.identity.v2_0 import user from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes @@ -411,6 +412,32 @@ class TestUserDelete(TestUser): ) self.assertIsNone(result) + @mock.patch.object(utils, 'find_resource') + def test_delete_multi_users_with_exception(self, find_mock): + find_mock.side_effect = [self.fake_user, + exceptions.CommandError] + arglist = [ + self.fake_user.id, + 'unexist_user', + ] + verifylist = [ + ('users', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 users failed to delete.', + str(e)) + + find_mock.assert_any_call(self.users_mock, self.fake_user.id) + find_mock.assert_any_call(self.users_mock, 'unexist_user') + + self.assertEqual(2, find_mock.call_count) + self.users_mock.delete.assert_called_once_with(self.fake_user.id) + class TestUserList(TestUser): diff --git a/openstackclient/tests/unit/identity/v3/test_group.py b/openstackclient/tests/unit/identity/v3/test_group.py index eb50adb526..8558de950a 100644 --- a/openstackclient/tests/unit/identity/v3/test_group.py +++ b/openstackclient/tests/unit/identity/v3/test_group.py @@ -16,6 +16,7 @@ from mock import call from keystoneauth1 import exceptions as ks_exc from osc_lib import exceptions +from osc_lib import utils from openstackclient.identity.v3 import group from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes @@ -257,6 +258,32 @@ class TestGroupDelete(TestGroup): self.groups_mock.delete.assert_called_once_with(self.groups[0].id) self.assertIsNone(result) + @mock.patch.object(utils, 'find_resource') + def test_delete_multi_groups_with_exception(self, find_mock): + find_mock.side_effect = [self.groups[0], + exceptions.CommandError] + arglist = [ + self.groups[0].id, + 'unexist_group', + ] + verifylist = [ + ('groups', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 groups failed to delete.', + str(e)) + + find_mock.assert_any_call(self.groups_mock, self.groups[0].id) + find_mock.assert_any_call(self.groups_mock, 'unexist_group') + + self.assertEqual(2, find_mock.call_count) + self.groups_mock.delete.assert_called_once_with(self.groups[0].id) + class TestGroupList(TestGroup): diff --git a/openstackclient/tests/unit/identity/v3/test_project.py b/openstackclient/tests/unit/identity/v3/test_project.py index 702d920975..2b89809004 100644 --- a/openstackclient/tests/unit/identity/v3/test_project.py +++ b/openstackclient/tests/unit/identity/v3/test_project.py @@ -16,6 +16,7 @@ import mock from osc_lib import exceptions +from osc_lib import utils from openstackclient.identity.v3 import project from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes @@ -445,6 +446,32 @@ class TestProjectDelete(TestProject): ) self.assertIsNone(result) + @mock.patch.object(utils, 'find_resource') + def test_delete_multi_projects_with_exception(self, find_mock): + find_mock.side_effect = [self.project, + exceptions.CommandError] + arglist = [ + self.project.id, + 'unexist_project', + ] + verifylist = [ + ('projects', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 projects failed to delete.', + str(e)) + + find_mock.assert_any_call(self.projects_mock, self.project.id) + find_mock.assert_any_call(self.projects_mock, 'unexist_project') + + self.assertEqual(2, find_mock.call_count) + self.projects_mock.delete.assert_called_once_with(self.project.id) + class TestProjectList(TestProject): diff --git a/openstackclient/tests/unit/identity/v3/test_role.py b/openstackclient/tests/unit/identity/v3/test_role.py index 448e18d372..c0b68bdfad 100644 --- a/openstackclient/tests/unit/identity/v3/test_role.py +++ b/openstackclient/tests/unit/identity/v3/test_role.py @@ -14,6 +14,10 @@ # import copy +import mock + +from osc_lib import exceptions +from osc_lib import utils from openstackclient.identity.v3 import role from openstackclient.tests.unit import fakes @@ -428,6 +432,36 @@ class TestRoleDelete(TestRole): ) self.assertIsNone(result) + @mock.patch.object(utils, 'find_resource') + def test_delete_multi_roles_with_exception(self, find_mock): + find_mock.side_effect = [self.roles_mock.get.return_value, + exceptions.CommandError] + arglist = [ + identity_fakes.role_name, + 'unexist_role', + ] + verifylist = [ + ('roles', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 roles failed to delete.', + str(e)) + + find_mock.assert_any_call(self.roles_mock, + identity_fakes.role_name, + domain_id=None) + find_mock.assert_any_call(self.roles_mock, + 'unexist_role', + domain_id=None) + + self.assertEqual(2, find_mock.call_count) + self.roles_mock.delete.assert_called_once_with(identity_fakes.role_id) + class TestRoleList(TestRole): diff --git a/openstackclient/tests/unit/identity/v3/test_trust.py b/openstackclient/tests/unit/identity/v3/test_trust.py index 4eeb8bfe17..93e8f63da1 100644 --- a/openstackclient/tests/unit/identity/v3/test_trust.py +++ b/openstackclient/tests/unit/identity/v3/test_trust.py @@ -12,6 +12,10 @@ # import copy +import mock + +from osc_lib import exceptions +from osc_lib import utils from openstackclient.identity.v3 import trust from openstackclient.tests.unit import fakes @@ -148,6 +152,33 @@ class TestTrustDelete(TestTrust): ) self.assertIsNone(result) + @mock.patch.object(utils, 'find_resource') + def test_delete_multi_trusts_with_exception(self, find_mock): + find_mock.side_effect = [self.trusts_mock.get.return_value, + exceptions.CommandError] + arglist = [ + identity_fakes.trust_id, + 'unexist_trust', + ] + verifylist = [ + ('trust', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 trusts failed to delete.', + str(e)) + + find_mock.assert_any_call(self.trusts_mock, identity_fakes.trust_id) + find_mock.assert_any_call(self.trusts_mock, 'unexist_trust') + + self.assertEqual(2, find_mock.call_count) + self.trusts_mock.delete.assert_called_once_with( + identity_fakes.trust_id) + class TestTrustList(TestTrust): diff --git a/openstackclient/tests/unit/identity/v3/test_user.py b/openstackclient/tests/unit/identity/v3/test_user.py index 6150a5f3df..3c1f49a680 100644 --- a/openstackclient/tests/unit/identity/v3/test_user.py +++ b/openstackclient/tests/unit/identity/v3/test_user.py @@ -16,6 +16,9 @@ import contextlib import mock +from osc_lib import exceptions +from osc_lib import utils + from openstackclient.identity.v3 import user from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes @@ -465,6 +468,32 @@ class TestUserDelete(TestUser): ) self.assertIsNone(result) + @mock.patch.object(utils, 'find_resource') + def test_delete_multi_users_with_exception(self, find_mock): + find_mock.side_effect = [self.user, + exceptions.CommandError] + arglist = [ + self.user.id, + 'unexist_user', + ] + verifylist = [ + ('users', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 users failed to delete.', + str(e)) + + find_mock.assert_any_call(self.users_mock, self.user.id) + find_mock.assert_any_call(self.users_mock, 'unexist_user') + + self.assertEqual(2, find_mock.call_count) + self.users_mock.delete.assert_called_once_with(self.user.id) + class TestUserList(TestUser):