From cc7773f53bac61f602ce8ebc908878fd0914724c Mon Sep 17 00:00:00 2001 From: Rajat Dhasmana Date: Fri, 15 Sep 2023 01:38:32 +0530 Subject: [PATCH] Add support for managing volumes This patch adds support for the cinder manage command to bring a volume under OpenStack management. Change-Id: I12b63bfc4f0c9bc29cf9d4efd9a5cd038ec00af3 --- .../tests/unit/volume/v3/test_volume.py | 229 ++++++++++++++++++ openstackclient/volume/v3/volume.py | 95 +++++++- ...olume-manage-command-088890446d0e81c7.yaml | 6 + 3 files changed, 329 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/add-volume-manage-command-088890446d0e81c7.yaml diff --git a/openstackclient/tests/unit/volume/v3/test_volume.py b/openstackclient/tests/unit/volume/v3/test_volume.py index a9db383ad5..ad6595bea7 100644 --- a/openstackclient/tests/unit/volume/v3/test_volume.py +++ b/openstackclient/tests/unit/volume/v3/test_volume.py @@ -198,3 +198,232 @@ class TestVolumeRevertToSnapshot(BaseVolumeTest): self.snapshot.id, ignore_missing=False, ) + + +class TestVolumeCreate(BaseVolumeTest): + columns = ( + 'attachments', + 'availability_zone', + 'consistency_group_id', + 'created_at', + 'description', + 'extended_replication_status', + 'group_id', + 'host', + 'id', + 'image_id', + 'is_bootable', + 'is_encrypted', + 'is_multiattach', + 'location', + 'metadata', + 'migration_id', + 'migration_status', + 'name', + 'project_id', + 'provider_id', + 'replication_driver_data', + 'replication_status', + 'scheduler_hints', + 'size', + 'snapshot_id', + 'source_volume_id', + 'status', + 'updated_at', + 'user_id', + 'volume_image_metadata', + 'volume_type', + ) + + def setUp(self): + super().setUp() + + self.new_volume = sdk_fakes.generate_fake_resource( + _volume.Volume, **{'size': 1} + ) + + self.datalist = ( + self.new_volume.attachments, + self.new_volume.availability_zone, + self.new_volume.consistency_group_id, + self.new_volume.created_at, + self.new_volume.description, + self.new_volume.extended_replication_status, + self.new_volume.group_id, + self.new_volume.host, + self.new_volume.id, + self.new_volume.image_id, + self.new_volume.is_bootable, + self.new_volume.is_encrypted, + self.new_volume.is_multiattach, + self.new_volume.location, + self.new_volume.metadata, + self.new_volume.migration_id, + self.new_volume.migration_status, + self.new_volume.name, + self.new_volume.project_id, + self.new_volume.provider_id, + self.new_volume.replication_driver_data, + self.new_volume.replication_status, + self.new_volume.scheduler_hints, + self.new_volume.size, + self.new_volume.snapshot_id, + self.new_volume.source_volume_id, + self.new_volume.status, + self.new_volume.updated_at, + self.new_volume.user_id, + self.new_volume.volume_image_metadata, + self.new_volume.volume_type, + ) + + # Get the command object to test + self.cmd = volume.CreateVolume(self.app, None) + + def test_volume_create_remote_source(self): + self.volume_sdk_client.manage_volume.return_value = self.new_volume + + arglist = [ + '--remote-source', + 'key=val', + '--host', + 'fake_host', + self.new_volume.name, + ] + verifylist = [ + ('remote_source', {'key': 'val'}), + ('host', 'fake_host'), + ('name', self.new_volume.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_sdk_client.manage_volume.assert_called_with( + host='fake_host', + ref={'key': 'val'}, + name=parsed_args.name, + description=parsed_args.description, + volume_type=parsed_args.type, + availability_zone=parsed_args.availability_zone, + metadata=parsed_args.property, + bootable=parsed_args.bootable, + cluster=getattr(parsed_args, 'cluster', None), + ) + + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.datalist, data) + + def test_volume_create_remote_source_pre_316(self): + self._set_mock_microversion('3.15') + arglist = [ + '--remote-source', + 'key=val', + '--cluster', + 'fake_cluster', + self.new_volume.name, + ] + verifylist = [ + ('remote_source', {'key': 'val'}), + ('cluster', 'fake_cluster'), + ('name', self.new_volume.name), + ] + 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.16 or greater is required', str(exc) + ) + + def test_volume_create_remote_source_host_and_cluster(self): + self._set_mock_microversion('3.16') + arglist = [ + '--remote-source', + 'key=val', + '--host', + 'fake_host', + '--cluster', + 'fake_cluster', + self.new_volume.name, + ] + verifylist = [ + ('remote_source', {'key': 'val'}), + ('host', 'fake_host'), + ('cluster', 'fake_cluster'), + ('name', self.new_volume.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, self.cmd.take_action, parsed_args + ) + self.assertIn( + 'Only one of --host or --cluster needs to be specified', str(exc) + ) + + def test_volume_create_remote_source_no_host_or_cluster(self): + arglist = [ + '--remote-source', + 'key=val', + self.new_volume.name, + ] + verifylist = [ + ('remote_source', {'key': 'val'}), + ('name', self.new_volume.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, self.cmd.take_action, parsed_args + ) + self.assertIn( + 'One of --host or --cluster needs to be specified to ', str(exc) + ) + + def test_volume_create_remote_source_size(self): + arglist = [ + '--size', + str(self.new_volume.size), + '--remote-source', + 'key=val', + self.new_volume.name, + ] + verifylist = [ + ('size', self.new_volume.size), + ('remote_source', {'key': 'val'}), + ('name', self.new_volume.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, self.cmd.take_action, parsed_args + ) + self.assertIn( + '--size, --consistency-group, --hint, --read-only and ' + '--read-write options are not supported', + str(exc), + ) + + def test_volume_create_host_no_remote_source(self): + arglist = [ + '--size', + str(self.new_volume.size), + '--host', + 'fake_host', + self.new_volume.name, + ] + verifylist = [ + ('size', self.new_volume.size), + ('host', 'fake_host'), + ('name', self.new_volume.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, self.cmd.take_action, parsed_args + ) + self.assertIn( + '--host and --cluster options are only supported ', + str(exc), + ) diff --git a/openstackclient/volume/v3/volume.py b/openstackclient/volume/v3/volume.py index 7f5a88fcd9..b73b3b2ee3 100644 --- a/openstackclient/volume/v3/volume.py +++ b/openstackclient/volume/v3/volume.py @@ -18,6 +18,7 @@ import logging from openstack import utils as sdk_utils 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 @@ -139,8 +140,100 @@ class CreateVolume(volume_v2.CreateVolume): raise exceptions.CommandError(msg) def get_parser(self, prog_name): - parser, _ = self._get_parser(prog_name) + parser, source_group = self._get_parser(prog_name) + + source_group.add_argument( + "--remote-source", + metavar="", + action=parseractions.KeyValueAction, + help=_( + "The attribute(s) of the existing remote volume " + "(admin required) (repeat option to specify multiple " + "attributes) e.g.: '--remote-source source-name=test_name " + "--remote-source source-id=test_id'" + ), + ) + parser.add_argument( + "--host", + metavar="", + help=_( + "Cinder host on which the existing volume resides; " + "takes the form: host@backend-name#pool. This is only " + "used along with the --remote-source option." + ), + ) + parser.add_argument( + "--cluster", + metavar="", + help=_( + "Cinder cluster on which the existing volume resides; " + "takes the form: cluster@backend-name#pool. This is only " + "used along with the --remote-source option. " + "(supported by --os-volume-api-version 3.16 or above)", + ), + ) return parser def take_action(self, parsed_args): + CreateVolume._check_size_arg(parsed_args) + + volume_client_sdk = self.app.client_manager.sdk_connection.volume + + if ( + parsed_args.host or parsed_args.cluster + ) and not parsed_args.remote_source: + msg = _( + "The --host and --cluster options are only supported " + "with --remote-source parameter." + ) + raise exceptions.CommandError(msg) + + if parsed_args.remote_source: + if ( + parsed_args.size + or parsed_args.consistency_group + or parsed_args.hint + or parsed_args.read_only + or parsed_args.read_write + ): + msg = _( + "The --size, --consistency-group, --hint, --read-only " + "and --read-write options are not supported with the " + "--remote-source parameter." + ) + raise exceptions.CommandError(msg) + if parsed_args.cluster: + if not sdk_utils.supports_microversion( + volume_client_sdk, '3.16' + ): + msg = _( + "--os-volume-api-version 3.16 or greater is required " + "to support the cluster parameter." + ) + raise exceptions.CommandError(msg) + if parsed_args.cluster and parsed_args.host: + msg = _( + "Only one of --host or --cluster needs to be specified " + "to manage a volume." + ) + raise exceptions.CommandError(msg) + if not parsed_args.cluster and not parsed_args.host: + msg = _( + "One of --host or --cluster needs to be specified to " + "manage a volume." + ) + raise exceptions.CommandError(msg) + volume = volume_client_sdk.manage_volume( + host=parsed_args.host, + cluster=parsed_args.cluster, + ref=parsed_args.remote_source, + name=parsed_args.name, + description=parsed_args.description, + volume_type=parsed_args.type, + availability_zone=parsed_args.availability_zone, + metadata=parsed_args.property, + bootable=parsed_args.bootable, + ) + return zip(*sorted(volume.items())) + return self._take_action(parsed_args) diff --git a/releasenotes/notes/add-volume-manage-command-088890446d0e81c7.yaml b/releasenotes/notes/add-volume-manage-command-088890446d0e81c7.yaml new file mode 100644 index 0000000000..e30def1936 --- /dev/null +++ b/releasenotes/notes/add-volume-manage-command-088890446d0e81c7.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add support for managing volumes with + ``openstack volume create --remote-source + --host `` command.