From 4996e2b86746e359f1662ea4545c9b6ed3809107 Mon Sep 17 00:00:00 2001 From: Zachary Goggin Date: Fri, 11 Apr 2025 14:30:09 +0000 Subject: [PATCH] Add support for out of place share backup restores Add CLI support and tests for performing a restore operation with Manilas share backup API that targets a non-source share i.e. a share other then that used to create the backup.) Partially-Implements: blueprint out-of-place-restore Depends-On: I060b0dc579e3057f2cb046ebe3271287f8fbc9f9 Change-Id: I8414c62010cd369e27cc5b693612c59d4e7516a3 Signed-off-by: Zachary Goggin --- manilaclient/api_versions.py | 2 +- manilaclient/osc/v2/share_backups.py | 43 ++++++++++++++++++- .../functional/osc/test_share_backups.py | 18 ++++++++ .../tests/unit/osc/v2/test_share_backups.py | 22 ++++++++++ .../tests/unit/v2/test_share_backups.py | 14 +++++- manilaclient/v2/share_backups.py | 11 +++-- ...out-of-place-restore-98740badef59ba32.yaml | 7 +++ 7 files changed, 109 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/share-backup-out-of-place-restore-98740badef59ba32.yaml diff --git a/manilaclient/api_versions.py b/manilaclient/api_versions.py index 18f9ca7d8..af613fb53 100644 --- a/manilaclient/api_versions.py +++ b/manilaclient/api_versions.py @@ -27,7 +27,7 @@ from manilaclient import utils LOG = logging.getLogger(__name__) -MAX_VERSION = '2.90' +MAX_VERSION = '2.91' MIN_VERSION = '2.0' DEPRECATED_VERSION = '1.0' _VERSIONED_METHOD_MAP = {} diff --git a/manilaclient/osc/v2/share_backups.py b/manilaclient/osc/v2/share_backups.py index 8c9dc9e07..f48ebbe94 100644 --- a/manilaclient/osc/v2/share_backups.py +++ b/manilaclient/osc/v2/share_backups.py @@ -20,10 +20,12 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils as osc_utils +from manilaclient import api_versions from manilaclient.common._i18n import _ from manilaclient.common import constants from manilaclient.osc import utils + LOG = logging.getLogger(__name__) @@ -282,14 +284,51 @@ class RestoreShareBackup(command.Command): metavar="", help=_('ID of backup to restore.') ) + parser.add_argument( + "--target-share", + metavar="", + default=None, + help=_('share to restore backup to. Source share if none supplied') + ) + parser.add_argument( + '--wait', + action='store_true', + default=False, + help=_('Wait for restore conclusion') + ) return parser def take_action(self, parsed_args): share_client = self.app.client_manager.share + kwargs = {} + share_backup = osc_utils.find_resource( share_client.share_backups, - parsed_args.backup) - share_client.share_backups.restore(share_backup.id) + parsed_args.backup + ) + target_share_id = None + if parsed_args.target_share is not None: + if share_client.api_version < api_versions.APIVersion('2.91'): + raise exceptions.CommandError( + 'performing targeted restores is only available ' + 'for API microversion >= 2.91') + else: + target_share_id = osc_utils.find_resource( + share_client.shares, + parsed_args.target_share + ).id + kwargs['target_share_id'] = target_share_id + + share_client.share_backups.restore(share_backup.id, **kwargs) + + if parsed_args.wait: + if not osc_utils.wait_for_status( + status_f=share_client.shares.get, + res_id=(target_share_id or share_backup.share_id), + success_status=['available'], + error_status=['error', 'backup_restoring_error'] + ): + LOG.error(_("ERROR: share is in error state.")) class SetShareBackup(command.Command): diff --git a/manilaclient/tests/functional/osc/test_share_backups.py b/manilaclient/tests/functional/osc/test_share_backups.py index 6c7ed6dd4..978c617c3 100644 --- a/manilaclient/tests/functional/osc/test_share_backups.py +++ b/manilaclient/tests/functional/osc/test_share_backups.py @@ -69,6 +69,24 @@ class ShareBackupCLITest(base.OSCClientTestBase): self.assertEqual('test_backup_show', show_result["name"]) self.assertEqual('Description', show_result["description"]) + def test_share_backup_restore(self): + share = self.create_share() + + backup = self.create_backup( + share_id=share['id'], + backup_options={'dummy': True}) + + self.openstack( + f'share backup restore {backup["id"]} --wait') + + backup = json.loads(self.openstack( + f'share backup show -f json {backup["id"]}')) + share = json.loads(self.openstack( + f'share show -f json {share["id"]}')) + + self.assertEqual('available', backup['status']) + self.assertEqual('available', share['status']) + def test_share_backup_set(self): share = self.create_share() diff --git a/manilaclient/tests/unit/osc/v2/test_share_backups.py b/manilaclient/tests/unit/osc/v2/test_share_backups.py index 5c8cdb653..4c9b76e37 100644 --- a/manilaclient/tests/unit/osc/v2/test_share_backups.py +++ b/manilaclient/tests/unit/osc/v2/test_share_backups.py @@ -309,7 +309,11 @@ class TestShareBackupRestore(TestShareBackup): self.share_backup = ( manila_fakes.FakeShareBackup.create_one_backup() ) + self.target_share = ( + manila_fakes.FakeShare.create_one_share() + ) self.backups_mock.get.return_value = self.share_backup + self.shares_mock.get.return_value = self.target_share self.cmd = osc_share_backups.RestoreShareBackup( self.app, None) @@ -325,6 +329,24 @@ class TestShareBackupRestore(TestShareBackup): self.backups_mock.restore.assert_called_with(self.share_backup.id) self.assertIsNone(result) + def test_share_backup_restore_to_target(self): + arglist = [ + self.share_backup.id, + '--target-share', self.target_share.id + ] + verifylist = [ + ('backup', self.share_backup.id), + ('target_share', self.target_share.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.backups_mock.restore.assert_called_with( + self.share_backup.id, + target_share_id=self.target_share.id + ) + self.assertIsNone(result) + class TestShareBackupSet(TestShareBackup): diff --git a/manilaclient/tests/unit/v2/test_share_backups.py b/manilaclient/tests/unit/v2/test_share_backups.py index 954bd98bb..9c027b750 100644 --- a/manilaclient/tests/unit/v2/test_share_backups.py +++ b/manilaclient/tests/unit/v2/test_share_backups.py @@ -20,6 +20,7 @@ from manilaclient.tests.unit.v2 import fakes from manilaclient.v2 import share_backups FAKE_BACKUP = 'fake_backup' +FAKE_TARGET_SHARE_ID = 'fake_target_share_id' @ddt.ddt @@ -30,7 +31,7 @@ class ShareBackupsTest(utils.TestCase): def setUp(self): super(ShareBackupsTest, self).setUp() - microversion = api_versions.APIVersion("2.80") + microversion = api_versions.APIVersion("2.91") self.manager = share_backups.ShareBackupManager( fakes.FakeClient(api_version=microversion)) @@ -58,7 +59,16 @@ class ShareBackupsTest(utils.TestCase): with mock.patch.object(self.manager, '_action', mock.Mock()): self.manager.restore(FAKE_BACKUP) self.manager._action.assert_called_once_with( - 'restore', FAKE_BACKUP) + 'restore', FAKE_BACKUP, info=None) + + def test_restore_to_target(self): + with mock.patch.object(self.manager, '_action', mock.Mock()): + self.manager.restore( + FAKE_BACKUP, + target_share_id=FAKE_TARGET_SHARE_ID + ) + self.manager._action.assert_called_once_with( + 'restore', FAKE_BACKUP, info=FAKE_TARGET_SHARE_ID) def test_list(self): with mock.patch.object(self.manager, '_list', mock.Mock()): diff --git a/manilaclient/v2/share_backups.py b/manilaclient/v2/share_backups.py index 3f2bccc85..9714dbda9 100644 --- a/manilaclient/v2/share_backups.py +++ b/manilaclient/v2/share_backups.py @@ -110,10 +110,15 @@ class ShareBackupManager(base.ManagerWithFind): url = RESOURCE_PATH % backup_id self._delete(url) - @api_versions.wraps("2.80") + @api_versions.wraps("2.80", "2.90") @api_versions.experimental_api - def restore(self, backup): - return self._action('restore', backup) + def restore(self, backup_id): + return self._action('restore', backup_id) + + @api_versions.wraps("2.91") + @api_versions.experimental_api + def restore(self, backup_id, target_share_id=None): # noqa F811 + return self._action('restore', backup_id, info=target_share_id) @api_versions.wraps("2.80") @api_versions.experimental_api diff --git a/releasenotes/notes/share-backup-out-of-place-restore-98740badef59ba32.yaml b/releasenotes/notes/share-backup-out-of-place-restore-98740badef59ba32.yaml new file mode 100644 index 000000000..9481b1cb1 --- /dev/null +++ b/releasenotes/notes/share-backup-out-of-place-restore-98740badef59ba32.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Added support for targeted share backup restores via the openstackclient + plugin. You can use the openstack client to restore a share backup from + one source share to another target share, given the backup or share driver + provides support for the operation. Available from microversion 2.91.