Merge "Support bulk deletion for delete commands in networkv2"
This commit is contained in:
		| @@ -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> | ||||
|         <subnet-pool> [<subnet-pool> ...] | ||||
|  | ||||
| .. _subnet_pool_delete-subnet-pool: | ||||
| .. describe:: <subnet-pool> | ||||
|  | ||||
|     Subnet pool to delete (name or ID) | ||||
|     Subnet pool(s) to delete (name or ID) | ||||
|  | ||||
| subnet pool list | ||||
| ---------------- | ||||
|   | ||||
| @@ -119,18 +119,18 @@ Create new subnet | ||||
| subnet delete | ||||
| ------------- | ||||
|  | ||||
| Delete a subnet | ||||
| Delete subnet(s) | ||||
|  | ||||
| .. program:: subnet delete | ||||
| .. code:: bash | ||||
|  | ||||
|     os subnet delete | ||||
|         <subnet> | ||||
|         <subnet> [<subnet> ...] | ||||
|  | ||||
| .. _subnet_delete-subnet: | ||||
| .. describe:: <subnet> | ||||
|  | ||||
|     Subnet to delete (name or ID) | ||||
|     Subnet(s) to delete (name or ID) | ||||
|  | ||||
| subnet list | ||||
| ----------- | ||||
|   | ||||
| @@ -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="<subnet>", | ||||
|             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): | ||||
|   | ||||
| @@ -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='<subnet-pool>', | ||||
|             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): | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
| @@ -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 <https://blueprints.launchpad.net/python-openstackclient/+spec/multi-argument-network>`_] | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Jenkins
					Jenkins