diff --git a/doc/source/command-objects/volume-type.rst b/doc/source/command-objects/volume-type.rst index 50acf9fa65..dfc169cd14 100644 --- a/doc/source/command-objects/volume-type.rst +++ b/doc/source/command-objects/volume-type.rst @@ -48,18 +48,18 @@ Create new volume type volume type delete ------------------ -Delete volume type +Delete volume type(s) .. program:: volume type delete .. code:: bash os volume type delete - + [ ...] .. _volume_type_delete-volume-type: .. describe:: - Volume type to delete (name or ID) + Volume type(s) to delete (name or ID) volume type list ---------------- diff --git a/functional/tests/volume/v1/test_volume_type.py b/functional/tests/volume/v1/test_volume_type.py index 824b20d7c4..5f1f957e8c 100644 --- a/functional/tests/volume/v1/test_volume_type.py +++ b/functional/tests/volume/v1/test_volume_type.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import time import uuid from functional.tests.volume.v1 import common @@ -59,3 +60,15 @@ class VolumeTypeTests(common.BaseVolumeTests): self.assertEqual("", raw_output) raw_output = self.openstack('volume type show ' + self.NAME + opts) self.assertEqual("c='d'\n", raw_output) + + def test_multi_delete(self): + vol_type1 = uuid.uuid4().hex + vol_type2 = uuid.uuid4().hex + self.openstack('volume type create ' + vol_type1) + time.sleep(5) + self.openstack('volume type create ' + vol_type2) + time.sleep(5) + cmd = 'volume type delete %s %s' % (vol_type1, vol_type2) + time.sleep(5) + raw_output = self.openstack(cmd) + self.assertOutput('', raw_output) diff --git a/functional/tests/volume/v2/test_volume_type.py b/functional/tests/volume/v2/test_volume_type.py index 4c315334c8..114e429802 100644 --- a/functional/tests/volume/v2/test_volume_type.py +++ b/functional/tests/volume/v2/test_volume_type.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import time import uuid from functional.tests.volume.v2 import common @@ -69,3 +70,15 @@ class VolumeTypeTests(common.BaseVolumeTests): raw_output = self.openstack( 'volume type unset --project admin ' + self.NAME) self.assertEqual("", raw_output) + + def test_multi_delete(self): + vol_type1 = uuid.uuid4().hex + vol_type2 = uuid.uuid4().hex + self.openstack('volume type create ' + vol_type1) + time.sleep(5) + self.openstack('volume type create ' + vol_type2) + time.sleep(5) + cmd = 'volume type delete %s %s' % (vol_type1, vol_type2) + time.sleep(5) + raw_output = self.openstack(cmd) + self.assertOutput('', raw_output) diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py index 7eea1c3c45..174f33f2fb 100644 --- a/openstackclient/tests/volume/v2/test_type.py +++ b/openstackclient/tests/volume/v2/test_type.py @@ -128,13 +128,13 @@ class TestTypeDelete(TestType): self.volume_type.id ] verifylist = [ - ("volume_type", self.volume_type.id) + ("volume_types", [self.volume_type.id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.types_mock.delete.assert_called_with(self.volume_type.id) + self.types_mock.delete.assert_called_with(self.volume_type) self.assertIsNone(result) diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index 289966e52e..3fe4fa05f1 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -15,14 +15,20 @@ """Volume v1 Type 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 import six from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateVolumeType(command.ShowOne): """Create new volume type""" @@ -56,22 +62,39 @@ class CreateVolumeType(command.ShowOne): class DeleteVolumeType(command.Command): - """Delete volume type""" + """Delete volume type(s)""" def get_parser(self, prog_name): parser = super(DeleteVolumeType, self).get_parser(prog_name) parser.add_argument( - 'volume_type', + 'volume_types', metavar='', - help=_('Volume type to delete (name or ID)'), + nargs='+', + help=_('Volume type(s) to delete (name or ID)'), ) return parser def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - volume_type_id = utils.find_resource( - volume_client.volume_types, parsed_args.volume_type).id - volume_client.volume_types.delete(volume_type_id) + result = 0 + + for volume_type in parsed_args.volume_types: + try: + vol_type = utils.find_resource(volume_client.volume_types, + volume_type) + + volume_client.volume_types.delete(vol_type) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete volume type with " + "name or ID '%(volume_type)s': %(e)s") + % {'volume_type': volume_type, 'e': e}) + + if result > 0: + total = len(parsed_args.volume_types) + msg = (_("%(result)s of %(total)s volume types failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListVolumeType(command.Lister): diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 5e9faa1ded..87f4c5472a 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -92,22 +92,39 @@ class CreateVolumeType(command.ShowOne): class DeleteVolumeType(command.Command): - """Delete volume type""" + """Delete volume type(s)""" def get_parser(self, prog_name): parser = super(DeleteVolumeType, self).get_parser(prog_name) parser.add_argument( - "volume_type", + "volume_types", metavar="", - help=_("Volume type to delete (name or ID)") + nargs="+", + help=_("Volume type(s) to delete (name or ID)") ) return parser def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - volume_type = utils.find_resource( - volume_client.volume_types, parsed_args.volume_type) - volume_client.volume_types.delete(volume_type.id) + result = 0 + + for volume_type in parsed_args.volume_types: + try: + vol_type = utils.find_resource(volume_client.volume_types, + volume_type) + + volume_client.volume_types.delete(vol_type) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete volume type with " + "name or ID '%(volume_type)s': %(e)s") + % {'volume_type': volume_type, 'e': e}) + + if result > 0: + total = len(parsed_args.volume_types) + msg = (_("%(result)s of %(total)s volume types failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListVolumeType(command.Lister): diff --git a/releasenotes/notes/bug-1592906-e69b37377278d9c2.yaml b/releasenotes/notes/bug-1592906-e69b37377278d9c2.yaml new file mode 100644 index 0000000000..fee971d6dd --- /dev/null +++ b/releasenotes/notes/bug-1592906-e69b37377278d9c2.yaml @@ -0,0 +1,5 @@ +--- +features: + - Support bulk deletion for ``volume type delete``. + [Bug `1592906 `_] +