Merge "Add support for out of place share backup restores"

This commit is contained in:
Zuul
2025-08-28 21:26:22 +00:00
committed by Gerrit Code Review
7 changed files with 109 additions and 8 deletions

View File

@@ -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 = {}

View File

@@ -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="<backup>",
help=_('ID of backup to restore.')
)
parser.add_argument(
"--target-share",
metavar="<target-share>",
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):

View File

@@ -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()

View File

@@ -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):

View File

@@ -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()):

View File

@@ -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

View File

@@ -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.