diff --git a/cinderclient/tests/unit/v2/fakes.py b/cinderclient/tests/unit/v2/fakes.py index 4093d109a..f641ee78e 100644 --- a/cinderclient/tests/unit/v2/fakes.py +++ b/cinderclient/tests/unit/v2/fakes.py @@ -766,6 +766,20 @@ class FakeHTTPClient(base_client.HTTPClient): return (200, {}, {'backup': _stub_backup_full(backup1, base_uri, tenant_id)}) + def get_backups_1234(self, **kw): + base_uri = 'http://localhost:8776' + tenant_id = '0fa851f6668144cf9cd8c8419c1646c1' + backup1 = '1234' + return (200, {}, + {'backup': _stub_backup_full(backup1, base_uri, tenant_id)}) + + def get_backups_5678(self, **kw): + base_uri = 'http://localhost:8776' + tenant_id = '0fa851f6668144cf9cd8c8419c1646c1' + backup1 = '5678' + return (200, {}, + {'backup': _stub_backup_full(backup1, base_uri, tenant_id)}) + def get_backups_detail(self, **kw): base_uri = 'http://localhost:8776' tenant_id = '0fa851f6668144cf9cd8c8419c1646c1' @@ -794,6 +808,15 @@ class FakeHTTPClient(base_client.HTTPClient): return (200, {}, {'restore': _stub_restore()}) + def post_backups_76a17945_3c6f_435c_975b_b5685db10b62_action(self, **kw): + return(200, {}, None) + + def post_backups_1234_action(self, **kw): + return(200, {}, None) + + def post_backups_5678_action(self, **kw): + return(200, {}, None) + def get_backups_76a17945_3c6f_435c_975b_b5685db10b62_export_record(self, **kw): return (200, diff --git a/cinderclient/tests/unit/v2/test_shell.py b/cinderclient/tests/unit/v2/test_shell.py index 04f09550f..2cad670ef 100644 --- a/cinderclient/tests/unit/v2/test_shell.py +++ b/cinderclient/tests/unit/v2/test_shell.py @@ -552,6 +552,24 @@ class ShellTest(utils.TestCase): self.assert_called_anytime('POST', '/snapshots/5678/action', body=expected) + def test_backup_reset_state(self): + self.run_command('backup-reset-state 1234') + expected = {'os-reset_status': {'status': 'available'}} + self.assert_called('POST', '/backups/1234/action', body=expected) + + def test_backup_reset_state_with_flag(self): + self.run_command('backup-reset-state --state error 1234') + expected = {'os-reset_status': {'status': 'error'}} + self.assert_called('POST', '/backups/1234/action', body=expected) + + def test_backup_reset_state_multiple(self): + self.run_command('backup-reset-state 1234 5678') + expected = {'os-reset_status': {'status': 'available'}} + self.assert_called_anytime('POST', '/backups/1234/action', + body=expected) + self.assert_called_anytime('POST', '/backups/5678/action', + body=expected) + def test_type_list(self): self.run_command('type-list') self.assert_called_anytime('GET', '/types?is_public=None') diff --git a/cinderclient/tests/unit/v2/test_volume_backups.py b/cinderclient/tests/unit/v2/test_volume_backups.py index 361d69167..296305f89 100644 --- a/cinderclient/tests/unit/v2/test_volume_backups.py +++ b/cinderclient/tests/unit/v2/test_volume_backups.py @@ -78,6 +78,17 @@ class VolumeBackupsTest(utils.TestCase): self.assertIsInstance(info, volume_backups_restore.VolumeBackupsRestore) + def test_reset_state(self): + b = cs.backups.list()[0] + api = '/backups/76a17945-3c6f-435c-975b-b5685db10b62/action' + b.reset_state(state='error') + cs.assert_called('POST', api) + cs.backups.reset_state('76a17945-3c6f-435c-975b-b5685db10b62', + state='error') + cs.assert_called('POST', api) + cs.backups.reset_state(b, state='error') + cs.assert_called('POST', api) + def test_record_export(self): backup_id = '76a17945-3c6f-435c-975b-b5685db10b62' cs.backups.export_record(backup_id) diff --git a/cinderclient/v2/shell.py b/cinderclient/v2/shell.py index b4447d9ab..584902f7c 100644 --- a/cinderclient/v2/shell.py +++ b/cinderclient/v2/shell.py @@ -1429,6 +1429,36 @@ def do_backup_import(cs, args): utils.print_dict(info) +@utils.arg('backup', metavar='', nargs='+', + help='Name or ID of the backup to modify.') +@utils.arg('--state', metavar='', + default='available', + help='The state to assign to the backup. Valid values are ' + '"available", "error", "creating", "deleting", and ' + '"error_deleting". Default=available.') +@utils.service_type('volumev2') +def do_backup_reset_state(cs, args): + """Explicitly updates the backup state.""" + failure_count = 0 + + single = (len(args.backup) == 1) + + for backup in args.backup: + try: + _find_backup(cs, backup).reset_state(args.state) + except Exception as e: + failure_count += 1 + msg = "Reset state for backup %s failed: %s" % (backup, e) + if not single: + print(msg) + + if failure_count == len(args.backup): + if not single: + msg = ("Unable to reset the state for any of the specified " + "backups.") + raise exceptions.CommandError(msg) + + @utils.arg('volume', metavar='', help='Name or ID of volume to transfer.') @utils.arg('--name', diff --git a/cinderclient/v2/volume_backups.py b/cinderclient/v2/volume_backups.py index 9d9ad1d12..d97d846e3 100644 --- a/cinderclient/v2/volume_backups.py +++ b/cinderclient/v2/volume_backups.py @@ -29,6 +29,9 @@ class VolumeBackup(base.Resource): """Delete this volume backup.""" return self.manager.delete(self) + def reset_state(self, state): + self.manager.reset_state(self, state) + class VolumeBackupManager(base.ManagerWithFind): """Manage :class:`VolumeBackup` resources.""" @@ -82,6 +85,17 @@ class VolumeBackupManager(base.ManagerWithFind): """ self._delete("/backups/%s" % base.getid(backup)) + def reset_state(self, backup, state): + """Update the specified volume backup with the provided state.""" + return self._action('os-reset_status', backup, {'status': state}) + + def _action(self, action, backup, info=None, **kwargs): + """Perform a volume backup action.""" + body = {action: info} + self.run_hooks('modify_body_for_action', body, **kwargs) + url = '/backups/%s/action' % base.getid(backup) + return self.api.client.post(url, body=body) + def export_record(self, backup_id): """Export volume backup metadata record.