Merge "Add support for out of place share backup restores"
This commit is contained in:
@@ -27,7 +27,7 @@ from manilaclient import utils
|
|||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
MAX_VERSION = '2.90'
|
MAX_VERSION = '2.91'
|
||||||
MIN_VERSION = '2.0'
|
MIN_VERSION = '2.0'
|
||||||
DEPRECATED_VERSION = '1.0'
|
DEPRECATED_VERSION = '1.0'
|
||||||
_VERSIONED_METHOD_MAP = {}
|
_VERSIONED_METHOD_MAP = {}
|
||||||
|
@@ -20,10 +20,12 @@ from osc_lib.command import command
|
|||||||
from osc_lib import exceptions
|
from osc_lib import exceptions
|
||||||
from osc_lib import utils as osc_utils
|
from osc_lib import utils as osc_utils
|
||||||
|
|
||||||
|
from manilaclient import api_versions
|
||||||
from manilaclient.common._i18n import _
|
from manilaclient.common._i18n import _
|
||||||
from manilaclient.common import constants
|
from manilaclient.common import constants
|
||||||
from manilaclient.osc import utils
|
from manilaclient.osc import utils
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -282,14 +284,51 @@ class RestoreShareBackup(command.Command):
|
|||||||
metavar="<backup>",
|
metavar="<backup>",
|
||||||
help=_('ID of backup to restore.')
|
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
|
return parser
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
share_client = self.app.client_manager.share
|
share_client = self.app.client_manager.share
|
||||||
|
kwargs = {}
|
||||||
|
|
||||||
share_backup = osc_utils.find_resource(
|
share_backup = osc_utils.find_resource(
|
||||||
share_client.share_backups,
|
share_client.share_backups,
|
||||||
parsed_args.backup)
|
parsed_args.backup
|
||||||
share_client.share_backups.restore(share_backup.id)
|
)
|
||||||
|
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):
|
class SetShareBackup(command.Command):
|
||||||
|
@@ -69,6 +69,24 @@ class ShareBackupCLITest(base.OSCClientTestBase):
|
|||||||
self.assertEqual('test_backup_show', show_result["name"])
|
self.assertEqual('test_backup_show', show_result["name"])
|
||||||
self.assertEqual('Description', show_result["description"])
|
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):
|
def test_share_backup_set(self):
|
||||||
share = self.create_share()
|
share = self.create_share()
|
||||||
|
|
||||||
|
@@ -309,7 +309,11 @@ class TestShareBackupRestore(TestShareBackup):
|
|||||||
self.share_backup = (
|
self.share_backup = (
|
||||||
manila_fakes.FakeShareBackup.create_one_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.backups_mock.get.return_value = self.share_backup
|
||||||
|
self.shares_mock.get.return_value = self.target_share
|
||||||
self.cmd = osc_share_backups.RestoreShareBackup(
|
self.cmd = osc_share_backups.RestoreShareBackup(
|
||||||
self.app, None)
|
self.app, None)
|
||||||
|
|
||||||
@@ -325,6 +329,24 @@ class TestShareBackupRestore(TestShareBackup):
|
|||||||
self.backups_mock.restore.assert_called_with(self.share_backup.id)
|
self.backups_mock.restore.assert_called_with(self.share_backup.id)
|
||||||
self.assertIsNone(result)
|
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):
|
class TestShareBackupSet(TestShareBackup):
|
||||||
|
|
||||||
|
@@ -20,6 +20,7 @@ from manilaclient.tests.unit.v2 import fakes
|
|||||||
from manilaclient.v2 import share_backups
|
from manilaclient.v2 import share_backups
|
||||||
|
|
||||||
FAKE_BACKUP = 'fake_backup'
|
FAKE_BACKUP = 'fake_backup'
|
||||||
|
FAKE_TARGET_SHARE_ID = 'fake_target_share_id'
|
||||||
|
|
||||||
|
|
||||||
@ddt.ddt
|
@ddt.ddt
|
||||||
@@ -30,7 +31,7 @@ class ShareBackupsTest(utils.TestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(ShareBackupsTest, self).setUp()
|
super(ShareBackupsTest, self).setUp()
|
||||||
microversion = api_versions.APIVersion("2.80")
|
microversion = api_versions.APIVersion("2.91")
|
||||||
self.manager = share_backups.ShareBackupManager(
|
self.manager = share_backups.ShareBackupManager(
|
||||||
fakes.FakeClient(api_version=microversion))
|
fakes.FakeClient(api_version=microversion))
|
||||||
|
|
||||||
@@ -58,7 +59,16 @@ class ShareBackupsTest(utils.TestCase):
|
|||||||
with mock.patch.object(self.manager, '_action', mock.Mock()):
|
with mock.patch.object(self.manager, '_action', mock.Mock()):
|
||||||
self.manager.restore(FAKE_BACKUP)
|
self.manager.restore(FAKE_BACKUP)
|
||||||
self.manager._action.assert_called_once_with(
|
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):
|
def test_list(self):
|
||||||
with mock.patch.object(self.manager, '_list', mock.Mock()):
|
with mock.patch.object(self.manager, '_list', mock.Mock()):
|
||||||
|
@@ -110,10 +110,15 @@ class ShareBackupManager(base.ManagerWithFind):
|
|||||||
url = RESOURCE_PATH % backup_id
|
url = RESOURCE_PATH % backup_id
|
||||||
self._delete(url)
|
self._delete(url)
|
||||||
|
|
||||||
@api_versions.wraps("2.80")
|
@api_versions.wraps("2.80", "2.90")
|
||||||
@api_versions.experimental_api
|
@api_versions.experimental_api
|
||||||
def restore(self, backup):
|
def restore(self, backup_id):
|
||||||
return self._action('restore', backup)
|
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.wraps("2.80")
|
||||||
@api_versions.experimental_api
|
@api_versions.experimental_api
|
||||||
|
@@ -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.
|
Reference in New Issue
Block a user