diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py index ae15587a5..84ca4cbb0 100644 --- a/cinderclient/tests/unit/v3/test_shell.py +++ b/cinderclient/tests/unit/v3/test_shell.py @@ -1107,6 +1107,19 @@ class ShellTest(utils.TestCase): 'metadata': {'foo': 'bar'}, }} self.assert_called('POST', '/backups', body=expected) + def test_backup_with_az(self): + self.run_command('--os-volume-api-version 3.51 backup-create ' + '--availability-zone AZ2 --name 1234 1234') + expected = {'backup': {'volume_id': 1234, + 'container': None, + 'name': '1234', + 'description': None, + 'incremental': False, + 'force': False, + 'snapshot_id': None, + 'availability_zone': 'AZ2'}} + self.assert_called('POST', '/backups', body=expected) + @mock.patch("cinderclient.utils.print_list") def test_snapshot_list_with_userid(self, mock_print_list): """Ensure 3.41 provides User ID header.""" diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py index cd385b47f..f012cc8d3 100644 --- a/cinderclient/v3/shell.py +++ b/cinderclient/v3/shell.py @@ -2332,6 +2332,11 @@ def do_service_get_log(cs, args): default=None, start_version='3.43', help='Metadata key and value pairs. Default=None.') +@utils.arg('--availability-zone', + default=None, + start_version='3.51', + help='AZ where the backup should be stored, by default it will be ' + 'the same as the source.') def do_backup_create(cs, args): """Creates a volume backup.""" if args.display_name is not None: @@ -2340,25 +2345,22 @@ def do_backup_create(cs, args): if args.display_description is not None: args.description = args.display_description - volume = utils.find_volume(cs, args.volume) - if hasattr(args, 'metadata') and args.metadata: - backup = cs.backups.create(volume.id, - args.container, - args.name, - args.description, - args.incremental, - args.force, - args.snapshot_id, - shell_utils.extract_metadata(args)) - else: - backup = cs.backups.create(volume.id, - args.container, - args.name, - args.description, - args.incremental, - args.force, - args.snapshot_id) + kwargs = {} + if getattr(args, 'metadata', None): + kwargs['metadata'] = shell_utils.extract_metadata(args) + az = getattr(args, 'availability_zone', None) + if az: + kwargs['availability_zone'] = az + volume = utils.find_volume(cs, args.volume) + backup = cs.backups.create(volume.id, + args.container, + args.name, + args.description, + args.incremental, + args.force, + args.snapshot_id, + **kwargs) info = {"volume_id": volume.id} info.update(backup._info) diff --git a/cinderclient/v3/volume_backups.py b/cinderclient/v3/volume_backups.py index 8ebd31fbc..b07ecfb4c 100644 --- a/cinderclient/v3/volume_backups.py +++ b/cinderclient/v3/volume_backups.py @@ -57,14 +57,8 @@ class VolumeBackupManager(volume_backups.VolumeBackupManager): the new backup will be based on the snapshot. :rtype: :class:`VolumeBackup` """ - body = {'backup': {'volume_id': volume_id, - 'container': container, - 'name': name, - 'description': description, - 'incremental': incremental, - 'force': force, - 'snapshot_id': snapshot_id, }} - return self._create('/backups', body, 'backup') + return self._create_backup(volume_id, container, name, description, + incremental, force, snapshot_id) @api_versions.wraps("3.43") # noqa: F811 def create(self, volume_id, container=None, @@ -87,12 +81,46 @@ class VolumeBackupManager(volume_backups.VolumeBackupManager): :rtype: :class:`VolumeBackup` """ # pylint: disable=function-redefined + return self._create_backup(volume_id, container, name, description, + incremental, force, snapshot_id, metadata) + + @api_versions.wraps("3.51") # noqa: F811 + def create(self, volume_id, container=None, name=None, description=None, + incremental=False, force=False, snapshot_id=None, metadata=None, + availability_zone=None): + return self._create_backup(volume_id, container, name, description, + incremental, force, snapshot_id, metadata, + availability_zone) + + def _create_backup(self, volume_id, container=None, name=None, + description=None, incremental=False, force=False, + snapshot_id=None, metadata=None, + availability_zone=None): + """Creates a volume backup. + + :param volume_id: The ID of the volume to backup. + :param container: The name of the backup service container. + :param name: The name of the backup. + :param description: The description of the backup. + :param incremental: Incremental backup. + :param force: If True, allows an in-use volume to be backed up. + :param metadata: Key Value pairs + :param snapshot_id: The ID of the snapshot to backup. This should + be a snapshot of the src volume, when specified, + the new backup will be based on the snapshot. + :param availability_zone: The AZ where we want the backup stored. + :rtype: :class:`VolumeBackup` + """ + # pylint: disable=function-redefined body = {'backup': {'volume_id': volume_id, 'container': container, 'name': name, 'description': description, 'incremental': incremental, 'force': force, - 'snapshot_id': snapshot_id, - 'metadata': metadata, }} + 'snapshot_id': snapshot_id, }} + if metadata: + body['backup']['metadata'] = metadata + if availability_zone: + body['backup']['availability_zone'] = availability_zone return self._create('/backups', body, 'backup') diff --git a/releasenotes/notes/feature-cross-az-backups-9d428ad4dfc552e1.yaml b/releasenotes/notes/feature-cross-az-backups-9d428ad4dfc552e1.yaml new file mode 100644 index 000000000..d1bb1a4a2 --- /dev/null +++ b/releasenotes/notes/feature-cross-az-backups-9d428ad4dfc552e1.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Support cross AZ backup creation specifying desired backup service AZ + (added in microversion v3.51)