diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py index a7be695d4..ccfb45b35 100644 --- a/cinderclient/tests/unit/v3/test_shell.py +++ b/cinderclient/tests/unit/v3/test_shell.py @@ -990,3 +990,11 @@ class ShellTest(utils.TestCase): self.assertEqual([mock.call(poll_period)] * 2, mock_time.sleep.call_args_list) self.assertEqual([mock.call(some_id)] * 2, poll_fn.call_args_list) + + def test_backup_with_metadata(self): + cmd = '--os-volume-api-version 3.43 ' + cmd += 'backup-create ' + cmd += '--metadata foo=bar ' + cmd += '1234' + self.run_command(cmd) + self.assert_called('POST', '/backups') diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py index 97740e4c2..d81bb03c5 100644 --- a/cinderclient/v3/shell.py +++ b/cinderclient/v3/shell.py @@ -858,13 +858,19 @@ def do_upload_to_image(cs, args): args.disk_format)) -@api_versions.wraps('3.9') +@api_versions.wraps('3.9', '3.43') @utils.arg('backup', metavar='', help='Name or ID of backup to rename.') @utils.arg('--name', nargs='?', metavar='', help='New name for backup.') @utils.arg('--description', metavar='', help='Backup description. Default=None.') +@utils.arg('--metadata', + nargs='*', + metavar='', + default=None, + help='Metadata key and value pairs. Default=None.', + start_version='3.43') def do_backup_update(cs, args): """Renames a backup.""" kwargs = {} @@ -875,6 +881,10 @@ def do_backup_update(cs, args): if args.description is not None: kwargs['description'] = args.description + if cs.api_version >= api_versions.APIVersion("3.43"): + if args.metadata is not None: + kwargs['metadata'] = args.metadata + if not kwargs: msg = 'Must supply either name or description.' raise exceptions.ClientException(code=1, message=msg) @@ -2005,3 +2015,68 @@ def do_service_get_log(cs, args): args.prefix) columns = ('Binary', 'Host', 'Prefix', 'Level') utils.print_list(log_levels, columns) + +@api_versions.wraps('3.43') +@utils.arg('volume', metavar='', + help='Name or ID of volume to backup.') +@utils.arg('--container', metavar='', + default=None, + help='Backup container name. Default=None.') +@utils.arg('--display-name', + help=argparse.SUPPRESS) +@utils.arg('--name', metavar='', + default=None, + help='Backup name. Default=None.') +@utils.arg('--display-description', + help=argparse.SUPPRESS) +@utils.arg('--description', + metavar='', + default=None, + help='Backup description. Default=None.') +@utils.arg('--incremental', + action='store_true', + help='Incremental backup. Default=False.', + default=False) +@utils.arg('--force', + action='store_true', + help='Allows or disallows backup of a volume ' + 'when the volume is attached to an instance. ' + 'If set to True, backs up the volume whether ' + 'its status is "available" or "in-use". The backup ' + 'of an "in-use" volume means your data is crash ' + 'consistent. Default=False.', + default=False) +@utils.arg('--snapshot-id', + metavar='', + default=None, + help='ID of snapshot to backup. Default=None.') +@utils.arg('--metadata', + nargs='*', + metavar='', + default=None, + help='Metadata key and value pairs. Default=None.') +def do_backup_create(cs, args): + """Creates a volume backup.""" + if args.display_name is not None: + args.name = args.display_name + + if args.display_description is not None: + args.description = args.display_description + + 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, + args.metadata) + + info = {"volume_id": volume.id} + info.update(backup._info) + + if 'links' in info: + info.pop('links') + + utils.print_dict(info) diff --git a/cinderclient/v3/volume_backups.py b/cinderclient/v3/volume_backups.py index 4d0594803..0ac9e1b93 100644 --- a/cinderclient/v3/volume_backups.py +++ b/cinderclient/v3/volume_backups.py @@ -26,15 +26,45 @@ VolumeBackup = volume_backups.VolumeBackup class VolumeBackupManager(volume_backups.VolumeBackupManager): - @api_versions.wraps("3.9") + @api_versions.wraps("3.9", "3.43") def update(self, backup, **kwargs): """Update the name or description for a backup. :param backup: The :class:`Backup` to update. """ + # NOTE(jdg): Placing 3.43 in versions.wraps above for clarity, + # but it's irrelevant as this just uses the kwargs, should we + # remove that? if not kwargs: return body = {"backup": kwargs} return self._update("/backups/%s" % base.getid(backup), body) + + @api_versions.wraps("3.43") + def create(self, volume_id, container=None, + name=None, description=None, + incremental=False, force=False, + snapshot_id=None, + metadata=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 + :rtype: :class:`VolumeBackup` + """ + body = {'backup': {'volume_id': volume_id, + 'container': container, + 'name': name, + 'description': description, + 'incremental': incremental, + 'force': force, + 'snapshot_id': snapshot_id, + 'metadata': metadata, }} + return self._create('/backups', body, 'backup')