From 83551d2a0c7604179c0988c13d58455a6b289cc8 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 3 Jun 2021 14:38:55 +0100 Subject: [PATCH] volume: Add 'volume group type *' commands These mirror the 'cinder group-type-*' commands, with arguments copied across essentially verbatim. The only significant departure is the merging of some commands, such as 'group-type-default' and 'group-type-list' into 'group type list', and 'group-type-update' and 'group-type-key' into 'group type set/unset'. volume group type create volume group type delete volume group type list volume group type show volume group type set volume group type unset Change-Id: Iee6ee2f1f276e6ef6f75a74f8f2980f14c0d5e2f Signed-off-by: Stephen Finucane --- .../cli/command-objects/volume-group-type.rst | 8 + doc/source/cli/commands.rst | 1 + doc/source/cli/data/cinder.csv | 16 +- openstackclient/tests/unit/volume/v3/fakes.py | 4 +- .../unit/volume/v3/test_volume_group_type.py | 475 ++++++++++++++++++ .../volume/v3/volume_group_type.py | 410 +++++++++++++++ ...-group-type-commands-13eabc7664a5c2bc.yaml | 7 + setup.cfg | 7 + 8 files changed, 919 insertions(+), 9 deletions(-) create mode 100644 doc/source/cli/command-objects/volume-group-type.rst create mode 100644 openstackclient/tests/unit/volume/v3/test_volume_group_type.py create mode 100644 openstackclient/volume/v3/volume_group_type.py create mode 100644 releasenotes/notes/add-volume-group-type-commands-13eabc7664a5c2bc.yaml diff --git a/doc/source/cli/command-objects/volume-group-type.rst b/doc/source/cli/command-objects/volume-group-type.rst new file mode 100644 index 0000000000..edb88dc7b0 --- /dev/null +++ b/doc/source/cli/command-objects/volume-group-type.rst @@ -0,0 +1,8 @@ +================= +volume group type +================= + +Block Storage v3 + +.. autoprogram-cliff:: openstack.volume.v3 + :command: volume group type * diff --git a/doc/source/cli/commands.rst b/doc/source/cli/commands.rst index b91a896f41..ada38e3e57 100644 --- a/doc/source/cli/commands.rst +++ b/doc/source/cli/commands.rst @@ -160,6 +160,7 @@ referring to both Compute and Volume quotas. * ``volume backup record``: (**Volume**) volume record that can be imported or exported * ``volume backend``: (**Volume**) volume backend storage * ``volume group``: (**Volume**) group of volumes +* ``volume group type``: (**Volume**) deployment-specific types of volumes groups available * ``volume host``: (**Volume**) the physical computer for volumes * ``volume message``: (**Volume**) volume API internal messages detailing volume failure messages * ``volume qos``: (**Volume**) quality-of-service (QoS) specification for volumes diff --git a/doc/source/cli/data/cinder.csv b/doc/source/cli/data/cinder.csv index 031aa43900..5de8ea5cdd 100644 --- a/doc/source/cli/data/cinder.csv +++ b/doc/source/cli/data/cinder.csv @@ -57,14 +57,14 @@ group-snapshot-create,,Creates a group snapshot. (Supported by API versions 3.14 group-snapshot-delete,,Removes one or more group snapshots. (Supported by API versions 3.14 - 3.latest) group-snapshot-list,,Lists all group snapshots. (Supported by API versions 3.14 - 3.latest) group-snapshot-show,,Shows group snapshot details. (Supported by API versions 3.14 - 3.latest) -group-specs-list,,Lists current group types and specs. (Supported by API versions 3.11 - 3.latest) -group-type-create,,Creates a group type. (Supported by API versions 3.11 - 3.latest) -group-type-default,,List the default group type. (Supported by API versions 3.11 - 3.latest) -group-type-delete,,Deletes group type or types. (Supported by API versions 3.11 - 3.latest) -group-type-key,,Sets or unsets group_spec for a group type. (Supported by API versions 3.11 - 3.latest) -group-type-list,,Lists available 'group types'. (Admin only will see private types) (Supported by API versions 3.11 - 3.latest) -group-type-show,,Show group type details. (Supported by API versions 3.11 - 3.latest) -group-type-update,,Updates group type name description and/or is_public. (Supported by API versions 3.11 - 3.latest) +group-specs-list,volume group type list,Lists current group types and specs. (Supported by API versions 3.11 - 3.latest) +group-type-create,volume group type create,Creates a group type. (Supported by API versions 3.11 - 3.latest) +group-type-default,volume group type list --default,List the default group type. (Supported by API versions 3.11 - 3.latest) +group-type-delete,volume group type delete,Deletes group type or types. (Supported by API versions 3.11 - 3.latest) +group-type-key,volume group type set,Sets or unsets group_spec for a group type. (Supported by API versions 3.11 - 3.latest) +group-type-list,volume group type set,Lists available 'group types'. (Admin only will see private types) (Supported by API versions 3.11 - 3.latest) +group-type-show,volume group type show,Show group type details. (Supported by API versions 3.11 - 3.latest) +group-type-update,volume group type set,Updates group type name description and/or is_public. (Supported by API versions 3.11 - 3.latest) group-update,volume group set,Updates a group. (Supported by API versions 3.13 - 3.latest) image-metadata,volume set --image-property,Sets or deletes volume image metadata. image-metadata-show,volume show,Shows volume image metadata. diff --git a/openstackclient/tests/unit/volume/v3/fakes.py b/openstackclient/tests/unit/volume/v3/fakes.py index b0c96290f8..c300ca3855 100644 --- a/openstackclient/tests/unit/volume/v3/fakes.py +++ b/openstackclient/tests/unit/volume/v3/fakes.py @@ -129,10 +129,11 @@ class FakeVolumeGroupType: """Fake one or more volume group types.""" @staticmethod - def create_one_volume_group_type(attrs=None): + def create_one_volume_group_type(attrs=None, methods=None): """Create a fake group type. :param attrs: A dictionary with all attributes of group type + :param methods: A dictionary with all methods :return: A FakeResource object with id, name, description, etc. """ attrs = attrs or {} @@ -152,6 +153,7 @@ class FakeVolumeGroupType: group_type = fakes.FakeResource( None, group_type_info, + methods=methods, loaded=True) return group_type diff --git a/openstackclient/tests/unit/volume/v3/test_volume_group_type.py b/openstackclient/tests/unit/volume/v3/test_volume_group_type.py new file mode 100644 index 0000000000..7e758a2c1f --- /dev/null +++ b/openstackclient/tests/unit/volume/v3/test_volume_group_type.py @@ -0,0 +1,475 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from unittest import mock + +from cinderclient import api_versions +from osc_lib.cli import format_columns +from osc_lib import exceptions + +from openstackclient.tests.unit.volume.v3 import fakes as volume_fakes +from openstackclient.volume.v3 import volume_group_type + + +class TestVolumeGroupType(volume_fakes.TestVolume): + + def setUp(self): + super().setUp() + + self.volume_group_types_mock = \ + self.app.client_manager.volume.group_types + self.volume_group_types_mock.reset_mock() + + +class TestVolumeGroupTypeCreate(TestVolumeGroupType): + + maxDiff = 2000 + + fake_volume_group_type = \ + volume_fakes.FakeVolumeGroupType.create_one_volume_group_type() + + columns = ( + 'ID', + 'Name', + 'Description', + 'Is Public', + 'Properties', + ) + data = ( + fake_volume_group_type.id, + fake_volume_group_type.name, + fake_volume_group_type.description, + fake_volume_group_type.is_public, + format_columns.DictColumn(fake_volume_group_type.group_specs), + ) + + def setUp(self): + super().setUp() + + self.volume_group_types_mock.create.return_value = \ + self.fake_volume_group_type + + self.cmd = volume_group_type.CreateVolumeGroupType(self.app, None) + + def test_volume_group_type_create(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.11') + + arglist = [ + self.fake_volume_group_type.name, + ] + verifylist = [ + ('name', self.fake_volume_group_type.name), + ('description', None), + ('is_public', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_group_types_mock.create.assert_called_once_with( + self.fake_volume_group_type.name, + None, + True) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_group_type_create_with_options(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.11') + + arglist = [ + self.fake_volume_group_type.name, + '--description', 'foo', + '--private', + ] + verifylist = [ + ('name', self.fake_volume_group_type.name), + ('description', 'foo'), + ('is_public', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_group_types_mock.create.assert_called_once_with( + self.fake_volume_group_type.name, + 'foo', + False) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_group_type_create_pre_v311(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.10') + + arglist = [ + self.fake_volume_group_type.name, + ] + verifylist = [ + ('name', self.fake_volume_group_type.name), + ('description', None), + ('is_public', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.11 or greater is required', + str(exc)) + + +class TestVolumeGroupTypeDelete(TestVolumeGroupType): + + fake_volume_group_type = \ + volume_fakes.FakeVolumeGroupType.create_one_volume_group_type() + + def setUp(self): + super().setUp() + + self.volume_group_types_mock.get.return_value = \ + self.fake_volume_group_type + self.volume_group_types_mock.delete.return_value = None + + self.cmd = volume_group_type.DeleteVolumeGroupType(self.app, None) + + def test_volume_group_type_delete(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.11') + + arglist = [ + self.fake_volume_group_type.id, + ] + verifylist = [ + ('group_type', self.fake_volume_group_type.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.volume_group_types_mock.delete.assert_called_once_with( + self.fake_volume_group_type.id, + ) + self.assertIsNone(result) + + def test_volume_group_type_delete_pre_v311(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.10') + + arglist = [ + self.fake_volume_group_type.id, + ] + verifylist = [ + ('group_type', self.fake_volume_group_type.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.11 or greater is required', + str(exc)) + + +class TestVolumeGroupTypeSet(TestVolumeGroupType): + + fake_volume_group_type = \ + volume_fakes.FakeVolumeGroupType.create_one_volume_group_type( + methods={ + 'get_keys': {'foo': 'bar'}, + 'set_keys': None, + 'unset_keys': None, + }) + + columns = ( + 'ID', + 'Name', + 'Description', + 'Is Public', + 'Properties', + ) + data = ( + fake_volume_group_type.id, + fake_volume_group_type.name, + fake_volume_group_type.description, + fake_volume_group_type.is_public, + format_columns.DictColumn(fake_volume_group_type.group_specs), + ) + + def setUp(self): + super().setUp() + + self.volume_group_types_mock.get.return_value = \ + self.fake_volume_group_type + self.volume_group_types_mock.update.return_value = \ + self.fake_volume_group_type + + self.cmd = volume_group_type.SetVolumeGroupType(self.app, None) + + def test_volume_group_type_set(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.11') + + self.fake_volume_group_type.set_keys.return_value = None + + arglist = [ + self.fake_volume_group_type.id, + '--name', 'foo', + '--description', 'hello, world', + '--public', + '--property', 'fizz=buzz', + ] + verifylist = [ + ('group_type', self.fake_volume_group_type.id), + ('name', 'foo'), + ('description', 'hello, world'), + ('is_public', True), + ('no_property', False), + ('properties', {'fizz': 'buzz'}), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_group_types_mock.update.assert_called_once_with( + self.fake_volume_group_type.id, + name='foo', + description='hello, world', + is_public=True, + ) + self.fake_volume_group_type.set_keys.assert_called_once_with( + {'fizz': 'buzz'}, + ) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_group_type_with_no_property_option(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.11') + + arglist = [ + self.fake_volume_group_type.id, + '--no-property', + '--property', 'fizz=buzz', + ] + verifylist = [ + ('group_type', self.fake_volume_group_type.id), + ('name', None), + ('description', None), + ('is_public', None), + ('no_property', True), + ('properties', {'fizz': 'buzz'}), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_group_types_mock.get.assert_called_once_with( + self.fake_volume_group_type.id) + self.fake_volume_group_type.get_keys.assert_called_once_with() + self.fake_volume_group_type.unset_keys.assert_called_once_with( + {'foo': 'bar'}.keys()) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_group_type_set_pre_v311(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.10') + + arglist = [ + self.fake_volume_group_type.id, + '--name', 'foo', + '--description', 'hello, world', + ] + verifylist = [ + ('group_type', self.fake_volume_group_type.id), + ('name', 'foo'), + ('description', 'hello, world'), + ('is_public', None), + ('no_property', False), + ('properties', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.11 or greater is required', + str(exc)) + + +class TestVolumeGroupTypeUnset(TestVolumeGroupType): + + fake_volume_group_type = \ + volume_fakes.FakeVolumeGroupType.create_one_volume_group_type( + methods={'unset_keys': None}) + + columns = ( + 'ID', + 'Name', + 'Description', + 'Is Public', + 'Properties', + ) + data = ( + fake_volume_group_type.id, + fake_volume_group_type.name, + fake_volume_group_type.description, + fake_volume_group_type.is_public, + format_columns.DictColumn(fake_volume_group_type.group_specs), + ) + + def setUp(self): + super().setUp() + + self.volume_group_types_mock.get.return_value = \ + self.fake_volume_group_type + + self.cmd = volume_group_type.UnsetVolumeGroupType(self.app, None) + + def test_volume_group_type_unset(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.11') + + arglist = [ + self.fake_volume_group_type.id, + '--property', 'fizz', + ] + verifylist = [ + ('group_type', self.fake_volume_group_type.id), + ('properties', ['fizz']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_group_types_mock.get.assert_has_calls([ + mock.call(self.fake_volume_group_type.id), + mock.call(self.fake_volume_group_type.id), + ]) + self.fake_volume_group_type.unset_keys.assert_called_once_with( + ['fizz']) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_group_type_unset_pre_v311(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.10') + + arglist = [ + self.fake_volume_group_type.id, + '--property', 'fizz', + ] + verifylist = [ + ('group_type', self.fake_volume_group_type.id), + ('properties', ['fizz']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.11 or greater is required', + str(exc)) + + +class TestVolumeGroupTypeList(TestVolumeGroupType): + + fake_volume_group_types = \ + volume_fakes.FakeVolumeGroupType.create_volume_group_types() + + columns = ( + 'ID', + 'Name', + 'Is Public', + 'Properties', + ) + data = [ + ( + fake_volume_group_type.id, + fake_volume_group_type.name, + fake_volume_group_type.is_public, + fake_volume_group_type.group_specs, + ) for fake_volume_group_type in fake_volume_group_types + ] + + def setUp(self): + super().setUp() + + self.volume_group_types_mock.list.return_value = \ + self.fake_volume_group_types + self.volume_group_types_mock.default.return_value = \ + self.fake_volume_group_types[0] + + self.cmd = volume_group_type.ListVolumeGroupType(self.app, None) + + def test_volume_group_type_list(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.11') + + arglist = [ + ] + verifylist = [ + ('show_default', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_group_types_mock.list.assert_called_once_with() + self.assertEqual(self.columns, columns) + self.assertCountEqual(tuple(self.data), data) + + def test_volume_group_type_list_with_default_option(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.11') + + arglist = [ + '--default', + ] + verifylist = [ + ('show_default', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_group_types_mock.default.assert_called_once_with() + self.assertEqual(self.columns, columns) + self.assertCountEqual(tuple([self.data[0]]), data) + + def test_volume_group_type_list_pre_v311(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.10') + + arglist = [ + ] + verifylist = [ + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.11 or greater is required', + str(exc)) diff --git a/openstackclient/volume/v3/volume_group_type.py b/openstackclient/volume/v3/volume_group_type.py new file mode 100644 index 0000000000..860fa544a5 --- /dev/null +++ b/openstackclient/volume/v3/volume_group_type.py @@ -0,0 +1,410 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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 logging + +from cinderclient import api_versions +from osc_lib.cli import format_columns +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 _ + +LOG = logging.getLogger(__name__) + + +def _format_group_type(group): + columns = ( + 'id', + 'name', + 'description', + 'is_public', + 'group_specs', + ) + column_headers = ( + 'ID', + 'Name', + 'Description', + 'Is Public', + 'Properties', + ) + + # TODO(stephenfin): Consider using a formatter for volume_types since it's + # a list + return ( + column_headers, + utils.get_item_properties( + group, + columns, + formatters={ + 'group_specs': format_columns.DictColumn, + }, + ), + ) + + +class CreateVolumeGroupType(command.ShowOne): + """Create a volume group type. + + This command requires ``--os-volume-api-version`` 3.11 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'name', + metavar='', + help=_('Name of new volume group type.'), + ) + parser.add_argument( + '--description', + metavar='', + help=_('Description of the volume group type.') + ) + type_group = parser.add_mutually_exclusive_group() + type_group.add_argument( + '--public', + dest='is_public', + action='store_true', + default=True, + help=_( + 'Volume group type is available to other projects (default)' + ), + ) + type_group.add_argument( + '--private', + dest='is_public', + action='store_false', + help=_('Volume group type is not available to other projects') + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.11'): + msg = _( + "--os-volume-api-version 3.11 or greater is required to " + "support the 'volume group type create' command" + ) + raise exceptions.CommandError(msg) + + group_type = volume_client.group_types.create( + parsed_args.name, + parsed_args.description, + parsed_args.is_public) + + return _format_group_type(group_type) + + +class DeleteVolumeGroupType(command.Command): + """Delete a volume group type. + + This command requires ``--os-volume-api-version`` 3.11 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'group_type', + metavar='', + help=_('Name or ID of volume group type to delete'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.11'): + msg = _( + "--os-volume-api-version 3.11 or greater is required to " + "support the 'volume group type delete' command" + ) + raise exceptions.CommandError(msg) + + group_type = utils.find_resource( + volume_client.group_types, + parsed_args.group_type, + ) + + volume_client.group_types.delete(group_type.id) + + +class SetVolumeGroupType(command.ShowOne): + """Update a volume group type. + + This command requires ``--os-volume-api-version`` 3.11 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'group_type', + metavar='', + help=_('Name or ID of volume group type.'), + ) + parser.add_argument( + '--name', + metavar='', + help=_('New name for volume group type.'), + ) + parser.add_argument( + '--description', + metavar='', + help=_('New description for volume group type.'), + ) + type_group = parser.add_mutually_exclusive_group() + type_group.add_argument( + '--public', + dest='is_public', + action='store_true', + default=None, + help=_('Make volume group type available to other projects.'), + ) + type_group.add_argument( + '--private', + dest='is_public', + action='store_false', + help=_('Make volume group type unavailable to other projects.') + ) + parser.add_argument( + '--no-property', + action='store_true', + help=_( + 'Remove all properties from this volume group type ' + '(specify both --no-property and --property ' + 'to remove the current properties before setting ' + 'new properties)' + ), + ) + parser.add_argument( + '--property', + metavar='', + action=parseractions.KeyValueAction, + dest='properties', + help=_( + 'Property to add or modify for this volume group type ' + '(repeat option to set multiple properties)' + ), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.11'): + msg = _( + "--os-volume-api-version 3.11 or greater is required to " + "support the 'volume group type set' command" + ) + raise exceptions.CommandError(msg) + + group_type = utils.find_resource( + volume_client.group_types, + parsed_args.group_type, + ) + + kwargs = {} + errors = 0 + + if parsed_args.name is not None: + kwargs['name'] = parsed_args.name + + if parsed_args.description is not None: + kwargs['description'] = parsed_args.description + + if parsed_args.is_public is not None: + kwargs['is_public'] = parsed_args.is_public + + if kwargs: + try: + group_type = volume_client.group_types.update( + group_type.id, **kwargs) + except Exception as e: + LOG.error(_("Failed to update group type: %s"), e) + errors += 1 + + if parsed_args.no_property: + try: + keys = group_type.get_keys().keys() + group_type.unset_keys(keys) + except Exception as e: + LOG.error(_("Failed to clear group type properties: %s"), e) + errors += 1 + + if parsed_args.properties: + try: + group_type.set_keys(parsed_args.properties) + except Exception as e: + LOG.error(_("Failed to set group type properties: %s"), e) + errors += 1 + + if errors > 0: + msg = _( + "Command Failed: One or more of the operations failed" + ) + raise exceptions.CommandError() + + return _format_group_type(group_type) + + +class UnsetVolumeGroupType(command.ShowOne): + """Unset properties of a volume group type. + + This command requires ``--os-volume-api-version`` 3.11 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'group_type', + metavar='', + help=_('Name or ID of volume group type.'), + ) + parser.add_argument( + '--property', + metavar='', + action='append', + dest='properties', + help=_( + 'Property to remove from this volume group type ' + '(repeat option to unset multiple properties)' + ), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.11'): + msg = _( + "--os-volume-api-version 3.11 or greater is required to " + "support the 'volume group type unset' command" + ) + raise exceptions.CommandError(msg) + + group_type = utils.find_resource( + volume_client.group_types, + parsed_args.group_type, + ) + + group_type.unset_keys(parsed_args.properties) + + group_type = utils.find_resource( + volume_client.group_types, + parsed_args.group_type, + ) + + return _format_group_type(group_type) + + +class ListVolumeGroupType(command.Lister): + """Lists all volume group types. + + This command requires ``--os-volume-api-version`` 3.11 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + '--default', + action='store_true', + dest='show_default', + default=False, + help=_('List the default volume group type.'), + ) + # TODO(stephenfin): Add once we have an equivalent command for + # 'cinder list-filters' + # parser.add_argument( + # '--filter', + # metavar='', + # action=parseractions.KeyValueAction, + # dest='filters', + # help=_( + # "Filter key and value pairs. Use 'foo' to " + # "check enabled filters from server. Use 'key~=value' for " + # "inexact filtering if the key supports " + # "(supported by --os-volume-api-version 3.33 or above)" + # ), + # ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.11'): + msg = _( + "--os-volume-api-version 3.11 or greater is required to " + "support the 'volume group type list' command" + ) + raise exceptions.CommandError(msg) + + if parsed_args.show_default: + group_types = [volume_client.group_types.default()] + else: + group_types = volume_client.group_types.list() + + column_headers = ( + 'ID', + 'Name', + 'Is Public', + 'Properties', + ) + columns = ( + 'id', + 'name', + 'is_public', + 'group_specs', + ) + + return ( + column_headers, + ( + utils.get_item_properties(a, columns) + for a in group_types + ), + ) + + +class ShowVolumeGroupType(command.ShowOne): + """Show detailed information for a volume group type. + + This command requires ``--os-volume-api-version`` 3.11 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'group_type', + metavar='', + help=_('Name or ID of volume group type.'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.11'): + msg = _( + "--os-volume-api-version 3.11 or greater is required to " + "support the 'volume group type show' command" + ) + raise exceptions.CommandError(msg) + + group_type = utils.find_resource( + volume_client.group_types, + parsed_args.group, + ) + + return _format_group_type(group_type) diff --git a/releasenotes/notes/add-volume-group-type-commands-13eabc7664a5c2bc.yaml b/releasenotes/notes/add-volume-group-type-commands-13eabc7664a5c2bc.yaml new file mode 100644 index 0000000000..02ffd38dda --- /dev/null +++ b/releasenotes/notes/add-volume-group-type-commands-13eabc7664a5c2bc.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add ``volume group type create``, ``volume group type delete``, + ``volume group type list``, ``volume group type set/unset`` and + ``volume group type show`` commands to create, delete, list, update, + and show volume group types, respectively. diff --git a/setup.cfg b/setup.cfg index 1d031a8fc4..2a01ae7b0d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -719,8 +719,15 @@ openstack.volume.v3 = volume_group_list = openstackclient.volume.v3.volume_group:ListVolumeGroup volume_group_failover = openstackclient.volume.v3.volume_group:FailoverVolumeGroup volume_group_set = openstackclient.volume.v3.volume_group:SetVolumeGroup + volume_group_unset = openstackclient.volume.v3.volume_group:UnsetVolumeGroup volume_group_show = openstackclient.volume.v3.volume_group:ShowVolumeGroup + volume_group_type_create = openstackclient.volume.v3.volume_group_type:CreateVolumeGroupType + volume_group_type_delete = openstackclient.volume.v3.volume_group_type:DeleteVolumeGroupType + volume_group_type_list = openstackclient.volume.v3.volume_group_type:ListVolumeGroupType + volume_group_type_set = openstackclient.volume.v3.volume_group_type:SetVolumeGroupType + volume_group_type_show = openstackclient.volume.v3.volume_group_type:ShowVolumeGroupType + volume_host_set = openstackclient.volume.v2.volume_host:SetVolumeHost volume_message_delete = openstackclient.volume.v3.volume_message:DeleteMessage