diff --git a/doc/source/command-objects/subnet-pool.rst b/doc/source/command-objects/subnet-pool.rst index 8abf25a67..516b9bf4a 100644 --- a/doc/source/command-objects/subnet-pool.rst +++ b/doc/source/command-objects/subnet-pool.rst @@ -81,18 +81,18 @@ Create subnet pool subnet pool delete ------------------ -Delete subnet pool +Delete subnet pool(s) .. program:: subnet pool delete .. code:: bash os subnet pool delete - + [ ...] .. _subnet_pool_delete-subnet-pool: .. describe:: - Subnet pool to delete (name or ID) + Subnet pool(s) to delete (name or ID) subnet pool list ---------------- diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index ff6354e65..c52d73f93 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -119,18 +119,18 @@ Create new subnet subnet delete ------------- -Delete a subnet +Delete subnet(s) .. program:: subnet delete .. code:: bash os subnet delete - + [ ...] .. _subnet_delete-subnet: .. describe:: - Subnet to delete (name or ID) + Subnet(s) to delete (name or ID) subnet list ----------- diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index ceb1cb147..a2e326226 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -14,6 +14,7 @@ """Subnet action implementations""" import copy +import logging from osc_lib.cli import parseractions from osc_lib.command import command @@ -24,6 +25,9 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +LOG = logging.getLogger(__name__) + + def _format_allocation_pools(data): pool_formatted = ['%s-%s' % (pool.get('start', ''), pool.get('end', '')) for pool in data] @@ -270,21 +274,37 @@ class CreateSubnet(command.ShowOne): class DeleteSubnet(command.Command): - """Delete subnet""" + """Delete subnet(s)""" def get_parser(self, prog_name): parser = super(DeleteSubnet, self).get_parser(prog_name) parser.add_argument( 'subnet', metavar="", - help=_("Subnet to delete (name or ID)") + nargs='+', + help=_("Subnet(s) to delete (name or ID)") ) return parser def take_action(self, parsed_args): client = self.app.client_manager.network - client.delete_subnet( - client.find_subnet(parsed_args.subnet)) + result = 0 + + for subnet in parsed_args.subnet: + try: + obj = client.find_subnet(subnet, ignore_missing=False) + client.delete_subnet(obj) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete subnet with " + "name or ID '%(subnet)s': %(e)s") + % {'subnet': subnet, 'e': e}) + + if result > 0: + total = len(parsed_args.subnet) + msg = (_("%(result)s of %(total)s subnets failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListSubnet(command.Lister): diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 79f98fd91..55dfed839 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -13,14 +13,20 @@ """Subnet pool action implementations""" +import logging + from osc_lib.cli import parseractions from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +LOG = logging.getLogger(__name__) + + def _get_columns(item): columns = list(item.keys()) if 'tenant_id' in columns: @@ -176,21 +182,37 @@ class CreateSubnetPool(command.ShowOne): class DeleteSubnetPool(command.Command): - """Delete subnet pool""" + """Delete subnet pool(s)""" def get_parser(self, prog_name): parser = super(DeleteSubnetPool, self).get_parser(prog_name) parser.add_argument( 'subnet_pool', metavar='', - help=_("Subnet pool to delete (name or ID)") + nargs='+', + help=_("Subnet pool(s) to delete (name or ID)") ) return parser def take_action(self, parsed_args): client = self.app.client_manager.network - obj = client.find_subnet_pool(parsed_args.subnet_pool) - client.delete_subnet_pool(obj) + result = 0 + + for pool in parsed_args.subnet_pool: + try: + obj = client.find_subnet_pool(pool, ignore_missing=False) + client.delete_subnet_pool(obj) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete subnet pool with " + "name or ID '%(pool)s': %(e)s") + % {'pool': pool, 'e': e}) + + if result > 0: + total = len(parsed_args.subnet_pool) + msg = (_("%(result)s of %(total)s subnet pools failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListSubnetPool(command.Lister): diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 6b09e2978..a23efc2d1 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -771,6 +771,25 @@ class FakeSubnet(object): return subnets + @staticmethod + def get_subnets(subnets=None, count=2): + """Get an iterable MagicMock object with a list of faked subnets. + + If subnets list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List subnets: + A list of FakeResource objects faking subnets + :param int count: + The number of subnets to fake + :return: + An iterable Mock object with side_effect set to a list of faked + subnets + """ + if subnets is None: + subnets = FakeSubnet.create_subnets(count) + return mock.MagicMock(side_effect=subnets) + class FakeFloatingIP(object): """Fake one or more floating ip.""" @@ -910,3 +929,22 @@ class FakeSubnetPool(object): ) return subnet_pools + + @staticmethod + def get_subnet_pools(subnet_pools=None, count=2): + """Get an iterable MagicMock object with a list of faked subnet pools. + + If subnet_pools list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List subnet pools: + A list of FakeResource objects faking subnet pools + :param int count: + The number of subnet pools to fake + :return: + An iterable Mock object with side_effect set to a list of faked + subnet pools + """ + if subnet_pools is None: + subnet_pools = FakeSubnetPool.create_subnet_pools(count) + return mock.MagicMock(side_effect=subnet_pools) diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py index de7e18212..a57a03089 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -13,7 +13,9 @@ import copy import mock +from mock import call +from osc_lib import exceptions from osc_lib import utils from openstackclient.network.v2 import subnet as subnet_v2 @@ -361,32 +363,82 @@ class TestCreateSubnet(TestSubnet): class TestDeleteSubnet(TestSubnet): - # The subnet to delete. - _subnet = network_fakes.FakeSubnet.create_one_subnet() + # The subnets to delete. + _subnets = network_fakes.FakeSubnet.create_subnets(count=2) def setUp(self): super(TestDeleteSubnet, self).setUp() self.network.delete_subnet = mock.Mock(return_value=None) - self.network.find_subnet = mock.Mock(return_value=self._subnet) + self.network.find_subnet = ( + network_fakes.FakeSubnet.get_subnets(self._subnets)) # Get the command object to test self.cmd = subnet_v2.DeleteSubnet(self.app, self.namespace) - def test_delete(self): + def test_subnet_delete(self): arglist = [ - self._subnet.name, + self._subnets[0].name, ] verifylist = [ - ('subnet', self._subnet.name), + ('subnet', [self._subnets[0].name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.network.delete_subnet.assert_called_once_with(self._subnet) + self.network.delete_subnet.assert_called_once_with(self._subnets[0]) self.assertIsNone(result) + def test_multi_subnets_delete(self): + arglist = [] + verifylist = [] + + for s in self._subnets: + arglist.append(s.name) + verifylist = [ + ('subnet', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for s in self._subnets: + calls.append(call(s)) + self.network.delete_subnet.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_subnets_delete_with_exception(self): + arglist = [ + self._subnets[0].name, + 'unexist_subnet', + ] + verifylist = [ + ('subnet', + [self._subnets[0].name, 'unexist_subnet']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._subnets[0], exceptions.CommandError] + self.network.find_subnet = ( + mock.MagicMock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 subnets failed to delete.', str(e)) + + self.network.find_subnet.assert_any_call( + self._subnets[0].name, ignore_missing=False) + self.network.find_subnet.assert_any_call( + 'unexist_subnet', ignore_missing=False) + self.network.delete_subnet.assert_called_once_with( + self._subnets[0] + ) + class TestListSubnet(TestSubnet): # The subnets going to be listed up. diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/network/v2/test_subnet_pool.py index 10ef76d81..7a96b30f6 100644 --- a/openstackclient/tests/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/network/v2/test_subnet_pool.py @@ -14,7 +14,9 @@ import argparse import copy import mock +from mock import call +from osc_lib import exceptions from osc_lib import utils from openstackclient.network.v2 import subnet_pool @@ -263,36 +265,85 @@ class TestCreateSubnetPool(TestSubnetPool): class TestDeleteSubnetPool(TestSubnetPool): - # The subnet pool to delete. - _subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool() + # The subnet pools to delete. + _subnet_pools = network_fakes.FakeSubnetPool.create_subnet_pools(count=2) def setUp(self): super(TestDeleteSubnetPool, self).setUp() self.network.delete_subnet_pool = mock.Mock(return_value=None) - self.network.find_subnet_pool = mock.Mock( - return_value=self._subnet_pool + self.network.find_subnet_pool = ( + network_fakes.FakeSubnetPool.get_subnet_pools(self._subnet_pools) ) # Get the command object to test self.cmd = subnet_pool.DeleteSubnetPool(self.app, self.namespace) - def test_delete(self): + def test_subnet_pool_delete(self): arglist = [ - self._subnet_pool.name, + self._subnet_pools[0].name, ] verifylist = [ - ('subnet_pool', self._subnet_pool.name), + ('subnet_pool', [self._subnet_pools[0].name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.network.delete_subnet_pool.assert_called_once_with( - self._subnet_pool) + self._subnet_pools[0]) self.assertIsNone(result) + def test_multi_subnet_pools_delete(self): + arglist = [] + verifylist = [] + + for s in self._subnet_pools: + arglist.append(s.name) + verifylist = [ + ('subnet_pool', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for s in self._subnet_pools: + calls.append(call(s)) + self.network.delete_subnet_pool.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_subnet_pools_delete_with_exception(self): + arglist = [ + self._subnet_pools[0].name, + 'unexist_subnet_pool', + ] + verifylist = [ + ('subnet_pool', + [self._subnet_pools[0].name, 'unexist_subnet_pool']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._subnet_pools[0], exceptions.CommandError] + self.network.find_subnet_pool = ( + mock.MagicMock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 subnet pools failed to delete.', str(e)) + + self.network.find_subnet_pool.assert_any_call( + self._subnet_pools[0].name, ignore_missing=False) + self.network.find_subnet_pool.assert_any_call( + 'unexist_subnet_pool', ignore_missing=False) + self.network.delete_subnet_pool.assert_called_once_with( + self._subnet_pools[0] + ) + class TestListSubnetPool(TestSubnetPool): # The subnet pools going to be listed up. diff --git a/releasenotes/notes/bp-multi-argument-network-e43e192ac95db94d.yaml b/releasenotes/notes/bp-multi-argument-network-e43e192ac95db94d.yaml index 0c56e8205..9bfc86a71 100644 --- a/releasenotes/notes/bp-multi-argument-network-e43e192ac95db94d.yaml +++ b/releasenotes/notes/bp-multi-argument-network-e43e192ac95db94d.yaml @@ -1,5 +1,6 @@ --- features: - - Support bulk deletion for ``floating ip delete``, ``security group delete``, - and ``security group rule delete`` commands in networkv2. + - Support bulk deletion for ``subnet pool delete``, ``subnet delete``, + ``floating ip delete``, ``security group delete`` and + ``security group rule delete``. [Blueprint `multi-argument-network `_]