From ec01268ea93141439542fb162a6a12bc2bf533fe Mon Sep 17 00:00:00 2001 From: whoami-rajat Date: Mon, 6 Feb 2023 23:09:03 +0530 Subject: [PATCH] Add options to create volume group from source This patch adds ``--source-group`` and ``--group-snapshot`` options to the ``volume group create`` command to allow creating group from a source group or a group snapshot. Change-Id: I87482a5dd43c519dfdcf981635aa879914a70a5c --- doc/source/cli/data/cinder.csv | 2 +- .../tests/unit/volume/v3/test_volume_group.py | 131 ++++++++++++++++++ openstackclient/volume/v3/volume_group.py | 117 ++++++++++++---- ...oup-from-src-options-6fcb0c87f617ca91.yaml | 6 + 4 files changed, 226 insertions(+), 30 deletions(-) create mode 100644 releasenotes/notes/add-create-group-from-src-options-6fcb0c87f617ca91.yaml diff --git a/doc/source/cli/data/cinder.csv b/doc/source/cli/data/cinder.csv index d5dc42c1a1..84ea409e25 100644 --- a/doc/source/cli/data/cinder.csv +++ b/doc/source/cli/data/cinder.csv @@ -45,7 +45,7 @@ freeze-host,volume host set --disable,Freeze and disable the specified cinder-vo get-capabilities,volume backend capability show,Show capabilities of a volume backend. Admin only. get-pools,volume backend pool list,Show pool information for backends. Admin only. group-create,volume group create,Creates a group. (Supported by API versions 3.13 - 3.latest) -group-create-from-src,,Creates a group from a group snapshot or a source group. (Supported by API versions 3.14 - 3.latest) +group-create-from-src,volume group create [--source-group|--group-snapshot],Creates a group from a group snapshot or a source group. (Supported by API versions 3.14 - 3.latest) group-delete,volume group delete,Removes one or more groups. (Supported by API versions 3.13 - 3.latest) group-disable-replication,volume group set --disable-replication,Disables replication for group. (Supported by API versions 3.38 - 3.latest) group-enable-replication,volume group set --enable-replication,Enables replication for group. (Supported by API versions 3.38 - 3.latest) diff --git a/openstackclient/tests/unit/volume/v3/test_volume_group.py b/openstackclient/tests/unit/volume/v3/test_volume_group.py index 96079a0848..a8338a8007 100644 --- a/openstackclient/tests/unit/volume/v3/test_volume_group.py +++ b/openstackclient/tests/unit/volume/v3/test_volume_group.py @@ -10,9 +10,12 @@ # License for the specific language governing permissions and limitations # under the License. +from unittest import mock + from cinderclient import api_versions from osc_lib import exceptions +from openstackclient.tests.unit import utils as tests_utils from openstackclient.tests.unit.volume.v3 import fakes as volume_fakes from openstackclient.volume.v3 import volume_group @@ -32,6 +35,10 @@ class TestVolumeGroup(volume_fakes.TestVolume): self.volume_types_mock = self.app.client_manager.volume.volume_types self.volume_types_mock.reset_mock() + self.volume_group_snapshots_mock = \ + self.app.client_manager.volume.group_snapshots + self.volume_group_snapshots_mock.reset_mock() + class TestVolumeGroupCreate(TestVolumeGroup): @@ -43,6 +50,8 @@ class TestVolumeGroupCreate(TestVolumeGroup): 'volume_types': [fake_volume_type.id], }, ) + fake_volume_group_snapshot = \ + volume_fakes.create_one_volume_group_snapshot() columns = ( 'ID', @@ -79,6 +88,10 @@ class TestVolumeGroupCreate(TestVolumeGroup): self.fake_volume_group_type self.volume_groups_mock.create.return_value = self.fake_volume_group self.volume_groups_mock.get.return_value = self.fake_volume_group + self.volume_groups_mock.create_from_src.return_value = \ + self.fake_volume_group + self.volume_group_snapshots_mock.get.return_value = \ + self.fake_volume_group_snapshot self.cmd = volume_group.CreateVolumeGroup(self.app, None) @@ -115,6 +128,29 @@ class TestVolumeGroupCreate(TestVolumeGroup): self.assertEqual(self.columns, columns) self.assertCountEqual(self.data, data) + def test_volume_group_create_no_volume_type(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.13') + + arglist = [ + self.fake_volume_group_type.id + ] + verifylist = [ + ('volume_group_type', self.fake_volume_group_type.id), + ('name', None), + ('description', None), + ('availability_zone', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + ' is a required argument', + str(exc)) + def test_volume_group_create_with_options(self): self.app.client_manager.volume.api_version = \ api_versions.APIVersion('3.13') @@ -176,6 +212,101 @@ class TestVolumeGroupCreate(TestVolumeGroup): '--os-volume-api-version 3.13 or greater is required', str(exc)) + def test_volume_group_create_from_source_group(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.14') + + arglist = [ + '--source-group', self.fake_volume_group.id, + ] + verifylist = [ + ('source_group', self.fake_volume_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_groups_mock.get.assert_has_calls( + [mock.call(self.fake_volume_group.id), + mock.call(self.fake_volume_group.id)]) + self.volume_groups_mock.create_from_src.assert_called_once_with( + None, + self.fake_volume_group.id, + None, + None, + ) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_group_create_from_group_snapshot(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.14') + + arglist = [ + '--group-snapshot', self.fake_volume_group_snapshot.id, + ] + verifylist = [ + ('group_snapshot', self.fake_volume_group_snapshot.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_group_snapshots_mock.get.assert_called_once_with( + self.fake_volume_group_snapshot.id) + self.volume_groups_mock.get.assert_called_once_with( + self.fake_volume_group.id) + self.volume_groups_mock.create_from_src.assert_called_once_with( + self.fake_volume_group_snapshot.id, + None, + None, + None, + ) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_group_create_from_src_pre_v314(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.13') + + arglist = [ + '--source-group', self.fake_volume_group.id, + ] + verifylist = [ + ('source_group', self.fake_volume_group.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.14 or greater is required', + str(exc)) + + def test_volume_group_create_from_src_source_group_group_snapshot(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.14') + + arglist = [ + '--source-group', self.fake_volume_group.id, + '--group-snapshot', self.fake_volume_group_snapshot.id, + ] + verifylist = [ + ('source_group', self.fake_volume_group.id), + ('group_snapshot', self.fake_volume_group_snapshot.id), + ] + + exc = self.assertRaises(tests_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist) + self.assertIn( + '--group-snapshot: not allowed with argument --source-group', + str(exc)) + class TestVolumeGroupDelete(TestVolumeGroup): diff --git a/openstackclient/volume/v3/volume_group.py b/openstackclient/volume/v3/volume_group.py index db4e9a94fa..69b18cebf4 100644 --- a/openstackclient/volume/v3/volume_group.py +++ b/openstackclient/volume/v3/volume_group.py @@ -82,15 +82,17 @@ class CreateVolumeGroup(command.ShowOne): def get_parser(self, prog_name): parser = super().get_parser(prog_name) - parser.add_argument( + source_parser = parser.add_mutually_exclusive_group() + source_parser.add_argument( 'volume_group_type', metavar='', + nargs='?', help=_('Name or ID of volume group type to use.'), ) parser.add_argument( 'volume_types', metavar='', - nargs='+', + nargs='*', default=[], help=_('Name or ID of volume type(s) to use.'), ) @@ -107,44 +109,101 @@ class CreateVolumeGroup(command.ShowOne): parser.add_argument( '--availability-zone', metavar='', - help=_('Availability zone for volume group.'), + help=_('Availability zone for volume group. ' + '(not available if creating group from source)'), + ) + source_parser.add_argument( + '--source-group', + metavar='', + help=_('Existing volume group (name or ID) ' + '(supported by --os-volume-api-version 3.14 or later)'), + ) + source_parser.add_argument( + '--group-snapshot', + metavar='', + help=_('Existing group snapshot (name or ID) ' + '(supported by --os-volume-api-version 3.14 or later)'), ) return parser def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - if volume_client.api_version < api_versions.APIVersion('3.13'): - msg = _( - "--os-volume-api-version 3.13 or greater is required to " - "support the 'volume group create' command" - ) - raise exceptions.CommandError(msg) - - volume_group_type = utils.find_resource( - volume_client.group_types, - parsed_args.volume_group_type, - ) - - volume_types = [] - for volume_type in parsed_args.volume_types: - volume_types.append( - utils.find_resource( - volume_client.volume_types, - volume_type, + if parsed_args.volume_group_type: + if volume_client.api_version < api_versions.APIVersion('3.13'): + msg = _( + "--os-volume-api-version 3.13 or greater is required to " + "support the 'volume group create' command" ) + raise exceptions.CommandError(msg) + if not parsed_args.volume_types: + msg = _( + " is a required argument when creating a " + "group from group type." + ) + raise exceptions.CommandError(msg) + + volume_group_type = utils.find_resource( + volume_client.group_types, + parsed_args.volume_group_type, ) + volume_types = [] + for volume_type in parsed_args.volume_types: + volume_types.append( + utils.find_resource( + volume_client.volume_types, + volume_type, + ) + ) - group = volume_client.groups.create( - volume_group_type.id, - ','.join(x.id for x in volume_types), - parsed_args.name, - parsed_args.description, - availability_zone=parsed_args.availability_zone) + group = volume_client.groups.create( + volume_group_type.id, + ','.join(x.id for x in volume_types), + parsed_args.name, + parsed_args.description, + availability_zone=parsed_args.availability_zone) - group = volume_client.groups.get(group.id) + group = volume_client.groups.get(group.id) + return _format_group(group) - return _format_group(group) + else: + if volume_client.api_version < api_versions.APIVersion('3.14'): + msg = _( + "--os-volume-api-version 3.14 or greater is required to " + "support the 'volume group create " + "[--source-group|--group-snapshot]' command" + ) + raise exceptions.CommandError(msg) + if (parsed_args.source_group is None and + parsed_args.group_snapshot is None): + msg = _( + "Either --source-group or " + "'--group-snapshot ' needs to be " + "provided to run the 'volume group create " + "[--source-group|--group-snapshot]' command" + ) + raise exceptions.CommandError(msg) + if parsed_args.availability_zone: + msg = _("'--availability-zone' option will not work " + "if creating group from source.") + LOG.warning(msg) + + source_group = None + if parsed_args.source_group: + source_group = utils.find_resource(volume_client.groups, + parsed_args.source_group) + group_snapshot = None + if parsed_args.group_snapshot: + group_snapshot = utils.find_resource( + volume_client.group_snapshots, + parsed_args.group_snapshot) + group = volume_client.groups.create_from_src( + group_snapshot.id if group_snapshot else None, + source_group.id if source_group else None, + parsed_args.name, + parsed_args.description) + group = volume_client.groups.get(group.id) + return _format_group(group) class DeleteVolumeGroup(command.Command): diff --git a/releasenotes/notes/add-create-group-from-src-options-6fcb0c87f617ca91.yaml b/releasenotes/notes/add-create-group-from-src-options-6fcb0c87f617ca91.yaml new file mode 100644 index 0000000000..9a4f1cb30a --- /dev/null +++ b/releasenotes/notes/add-create-group-from-src-options-6fcb0c87f617ca91.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Added ``--source-group`` and ``--group-snapshot`` options to the + ``volume group create`` command to allow creating group from + a source group or a group snapshot.