diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index f802f637f7..ef9c2fab5a 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -16,6 +16,7 @@ import argparse from unittest import mock from unittest.mock import call +from cinderclient import api_versions from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils @@ -47,6 +48,9 @@ class TestVolume(volume_fakes.TestVolume): self.snapshots_mock = self.app.client_manager.volume.volume_snapshots self.snapshots_mock.reset_mock() + self.backups_mock = self.app.client_manager.volume.backups + self.backups_mock.reset_mock() + self.types_mock = self.app.client_manager.volume.volume_types self.types_mock.reset_mock() @@ -129,6 +133,7 @@ class TestVolumeCreate(TestVolume): source_volid=None, consistencygroup_id=None, scheduler_hints=None, + backup_id=None, ) self.assertEqual(self.columns, columns) @@ -174,6 +179,7 @@ class TestVolumeCreate(TestVolume): source_volid=None, consistencygroup_id=consistency_group.id, scheduler_hints={'k': 'v'}, + backup_id=None, ) self.assertEqual(self.columns, columns) @@ -210,6 +216,7 @@ class TestVolumeCreate(TestVolume): source_volid=None, consistencygroup_id=None, scheduler_hints=None, + backup_id=None, ) self.assertEqual(self.columns, columns) @@ -248,6 +255,7 @@ class TestVolumeCreate(TestVolume): source_volid=None, consistencygroup_id=None, scheduler_hints=None, + backup_id=None, ) self.assertEqual(self.columns, columns) @@ -286,6 +294,7 @@ class TestVolumeCreate(TestVolume): source_volid=None, consistencygroup_id=None, scheduler_hints=None, + backup_id=None, ) self.assertEqual(self.columns, columns) @@ -323,11 +332,72 @@ class TestVolumeCreate(TestVolume): source_volid=None, consistencygroup_id=None, scheduler_hints=None, + backup_id=None, ) self.assertEqual(self.columns, columns) self.assertCountEqual(self.datalist, data) + def test_volume_create_with_backup(self): + backup = volume_fakes.create_one_backup() + self.new_volume.backup_id = backup.id + arglist = [ + '--backup', self.new_volume.backup_id, + self.new_volume.name, + ] + verifylist = [ + ('backup', self.new_volume.backup_id), + ('name', self.new_volume.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.backups_mock.get.return_value = backup + + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.47') + + # In base command class ShowOne in cliff, abstract method take_action() + # returns a two-part tuple with a tuple of column names and a tuple of + # data to be shown. + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_once_with( + size=backup.size, + snapshot_id=None, + name=self.new_volume.name, + description=None, + volume_type=None, + availability_zone=None, + metadata=None, + imageRef=None, + source_volid=None, + consistencygroup_id=None, + scheduler_hints=None, + backup_id=backup.id, + ) + + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.datalist, data) + + def test_volume_create_with_backup_pre_347(self): + backup = volume_fakes.create_one_backup() + self.new_volume.backup_id = backup.id + arglist = [ + '--backup', self.new_volume.backup_id, + self.new_volume.name, + ] + verifylist = [ + ('backup', self.new_volume.backup_id), + ('name', self.new_volume.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.backups_mock.get.return_value = backup + + exc = self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + self.assertIn("--os-volume-api-version 3.47 or greater", str(exc)) + def test_volume_create_with_bootable_and_readonly(self): arglist = [ '--bootable', @@ -361,6 +431,7 @@ class TestVolumeCreate(TestVolume): source_volid=None, consistencygroup_id=None, scheduler_hints=None, + backup_id=None, ) self.assertEqual(self.columns, columns) @@ -403,6 +474,7 @@ class TestVolumeCreate(TestVolume): source_volid=None, consistencygroup_id=None, scheduler_hints=None, + backup_id=None, ) self.assertEqual(self.columns, columns) @@ -454,6 +526,7 @@ class TestVolumeCreate(TestVolume): source_volid=None, consistencygroup_id=None, scheduler_hints=None, + backup_id=None, ) self.assertEqual(2, mock_error.call_count) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 1e1fde9226..53f6e643d3 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -71,10 +71,10 @@ def _check_size_arg(args): volume is not specified. """ - if ((args.snapshot or args.source) + if ((args.snapshot or args.source or args.backup) is None and args.size is None): - msg = _("--size is a required option if snapshot " - "or source volume is not specified.") + msg = _("--size is a required option if snapshot, backup " + "or source volume are not specified.") raise exceptions.CommandError(msg) @@ -117,6 +117,12 @@ class CreateVolume(command.ShowOne): metavar="", help=_("Volume to clone (name or ID)"), ) + source_group.add_argument( + "--backup", + metavar="", + help=_("Restore backup to a volume (name or ID) " + "(supported by --os-volume-api-version 3.47 or later)"), + ) source_group.add_argument( "--source-replicated", metavar="", @@ -177,9 +183,16 @@ class CreateVolume(command.ShowOne): def take_action(self, parsed_args): _check_size_arg(parsed_args) + volume_client = self.app.client_manager.volume image_client = self.app.client_manager.image + if parsed_args.backup and not ( + volume_client.api_version.matches('3.47')): + msg = _("--os-volume-api-version 3.47 or greater is required " + "to create a volume from backup.") + raise exceptions.CommandError(msg) + source_volume = None if parsed_args.source: source_volume = utils.find_resource( @@ -213,6 +226,15 @@ class CreateVolume(command.ShowOne): # snapshot size. size = max(size or 0, snapshot_obj.size) + backup = None + if parsed_args.backup: + backup_obj = utils.find_resource( + volume_client.backups, + parsed_args.backup) + backup = backup_obj.id + # As above + size = max(size or 0, backup_obj.size) + volume = volume_client.volumes.create( size=size, snapshot_id=snapshot, @@ -225,6 +247,7 @@ class CreateVolume(command.ShowOne): source_volid=source_volume, consistencygroup_id=consistency_group, scheduler_hints=parsed_args.hint, + backup_id=backup, ) if parsed_args.bootable or parsed_args.non_bootable: diff --git a/releasenotes/notes/add-backup-option-to-create-vol-fc36c2c745ebcff5.yaml b/releasenotes/notes/add-backup-option-to-create-vol-fc36c2c745ebcff5.yaml new file mode 100644 index 0000000000..081ebcea74 --- /dev/null +++ b/releasenotes/notes/add-backup-option-to-create-vol-fc36c2c745ebcff5.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Added ``--backup`` option to the ``volume create`` command.