Correct volume restore behavior
Now cinder volume restore will enter UpdateReplacement flow, the volume will be deleted and a new one be created. This patch will correct the restore behavior to call backup-restore API to just restore the volume. Change-Id: I2d2c330572279113c403e8fda41999521bfd4966 Closes-Bug: #1451359
This commit is contained in:
parent
a21614438c
commit
4e2e6a597d
|
@ -215,6 +215,14 @@ class VolumeResizeProgress(object):
|
||||||
self.size = size
|
self.size = size
|
||||||
|
|
||||||
|
|
||||||
|
class VolumeBackupRestoreProgress(object):
|
||||||
|
def __init__(self, vol_id, backup_id):
|
||||||
|
self.called = False
|
||||||
|
self.complete = False
|
||||||
|
self.vol_id = vol_id
|
||||||
|
self.backup_id = backup_id
|
||||||
|
|
||||||
|
|
||||||
class VolumeConstraint(constraints.BaseCustomConstraint):
|
class VolumeConstraint(constraints.BaseCustomConstraint):
|
||||||
|
|
||||||
expected_exceptions = (exception.EntityNotFound,)
|
expected_exceptions = (exception.EntityNotFound,)
|
||||||
|
|
|
@ -76,7 +76,8 @@ class CinderVolume(vb.BaseVolume):
|
||||||
),
|
),
|
||||||
BACKUP_ID: properties.Schema(
|
BACKUP_ID: properties.Schema(
|
||||||
properties.Schema.STRING,
|
properties.Schema.STRING,
|
||||||
_('If specified, the backup to create the volume from.')
|
_('If specified, the backup to create the volume from.'),
|
||||||
|
update_allowed=True,
|
||||||
),
|
),
|
||||||
NAME: properties.Schema(
|
NAME: properties.Schema(
|
||||||
properties.Schema.STRING,
|
properties.Schema.STRING,
|
||||||
|
@ -301,12 +302,43 @@ class CinderVolume(vb.BaseVolume):
|
||||||
LOG.info(_LI('Volume %(id)s resize complete'), {'id': vol.id})
|
LOG.info(_LI('Volume %(id)s resize complete'), {'id': vol.id})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _backup_restore(self, vol_id, backup_id):
|
||||||
|
try:
|
||||||
|
self.client().restores.restore(backup_id, vol_id)
|
||||||
|
except Exception as ex:
|
||||||
|
if self.client_plugin().is_client_exception(ex):
|
||||||
|
raise exception.Error(_(
|
||||||
|
"Failed to restore volume %(vol)s from backup %(backup)s "
|
||||||
|
"- %(err)s") % {'vol': vol_id,
|
||||||
|
'backup': backup_id,
|
||||||
|
'err': ex})
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _check_backup_restore_complete(self):
|
||||||
|
vol = self.client().volumes.get(self.resource_id)
|
||||||
|
if vol.status == 'restoring-backup':
|
||||||
|
LOG.debug("Volume %s is being restoring from backup" % vol.id)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if vol.status != 'available':
|
||||||
|
LOG.info(_LI("Restore failed: Volume %(vol)s is in %(status)s "
|
||||||
|
"state."), {'vol': vol.id, 'status': vol.status})
|
||||||
|
raise resource.ResourceUnknownStatus(
|
||||||
|
resource_status=vol.status,
|
||||||
|
result=_('Volume backup restore failed'))
|
||||||
|
|
||||||
|
LOG.info(_LI('Volume %(id)s backup restore complete'), {'id': vol.id})
|
||||||
|
return True
|
||||||
|
|
||||||
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
|
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
|
||||||
vol = None
|
vol = None
|
||||||
cinder = self.client()
|
cinder = self.client()
|
||||||
prg_resize = None
|
prg_resize = None
|
||||||
prg_attach = None
|
prg_attach = None
|
||||||
prg_detach = None
|
prg_detach = None
|
||||||
|
prg_backup_restore = None
|
||||||
# update the name and description for cinder volume
|
# update the name and description for cinder volume
|
||||||
if self.NAME in prop_diff or self.DESCRIPTION in prop_diff:
|
if self.NAME in prop_diff or self.DESCRIPTION in prop_diff:
|
||||||
vol = cinder.volumes.get(self.resource_id)
|
vol = cinder.volumes.get(self.resource_id)
|
||||||
|
@ -339,6 +371,11 @@ class CinderVolume(vb.BaseVolume):
|
||||||
if self.READ_ONLY in prop_diff:
|
if self.READ_ONLY in prop_diff:
|
||||||
flag = prop_diff.get(self.READ_ONLY)
|
flag = prop_diff.get(self.READ_ONLY)
|
||||||
cinder.volumes.update_readonly_flag(self.resource_id, flag)
|
cinder.volumes.update_readonly_flag(self.resource_id, flag)
|
||||||
|
# restore the volume from backup
|
||||||
|
if self.BACKUP_ID in prop_diff:
|
||||||
|
prg_backup_restore = heat_cinder.VolumeBackupRestoreProgress(
|
||||||
|
vol_id=self.resource_id,
|
||||||
|
backup_id=prop_diff.get(self.BACKUP_ID))
|
||||||
# extend volume size
|
# extend volume size
|
||||||
if self.SIZE in prop_diff:
|
if self.SIZE in prop_diff:
|
||||||
if not vol:
|
if not vol:
|
||||||
|
@ -366,7 +403,7 @@ class CinderVolume(vb.BaseVolume):
|
||||||
prg_attach = heat_cinder.VolumeAttachProgress(
|
prg_attach = heat_cinder.VolumeAttachProgress(
|
||||||
server_id, vol.id, device)
|
server_id, vol.id, device)
|
||||||
|
|
||||||
return prg_detach, prg_resize, prg_attach
|
return prg_backup_restore, prg_detach, prg_resize, prg_attach
|
||||||
|
|
||||||
def _detach_volume_to_complete(self, prg_detach):
|
def _detach_volume_to_complete(self, prg_detach):
|
||||||
if not prg_detach.called:
|
if not prg_detach.called:
|
||||||
|
@ -396,7 +433,17 @@ class CinderVolume(vb.BaseVolume):
|
||||||
return prg_attach.complete
|
return prg_attach.complete
|
||||||
|
|
||||||
def check_update_complete(self, checkers):
|
def check_update_complete(self, checkers):
|
||||||
prg_detach, prg_resize, prg_attach = checkers
|
prg_backup_restore, prg_detach, prg_resize, prg_attach = checkers
|
||||||
|
if prg_backup_restore:
|
||||||
|
if not prg_backup_restore.called:
|
||||||
|
prg_backup_restore.called = self._backup_restore(
|
||||||
|
prg_backup_restore.vol_id,
|
||||||
|
prg_backup_restore.backup_id)
|
||||||
|
return False
|
||||||
|
if not prg_backup_restore.complete:
|
||||||
|
prg_backup_restore.complete = \
|
||||||
|
self._check_backup_restore_complete()
|
||||||
|
return prg_backup_restore.complete and not prg_resize
|
||||||
if not prg_resize:
|
if not prg_resize:
|
||||||
return True
|
return True
|
||||||
# detach volume
|
# detach volume
|
||||||
|
@ -518,8 +565,11 @@ class CinderVolume(vb.BaseVolume):
|
||||||
|
|
||||||
def handle_restore(self, defn, restore_data):
|
def handle_restore(self, defn, restore_data):
|
||||||
backup_id = restore_data['resource_data']['backup_id']
|
backup_id = restore_data['resource_data']['backup_id']
|
||||||
|
# we can't ignore 'size' property: if user update the size
|
||||||
|
# of volume after snapshot, we need to change to old size
|
||||||
|
# when restore the volume.
|
||||||
ignore_props = (
|
ignore_props = (
|
||||||
self.IMAGE_REF, self.IMAGE, self.SOURCE_VOLID, self.SIZE)
|
self.IMAGE_REF, self.IMAGE, self.SOURCE_VOLID)
|
||||||
props = dict(
|
props = dict(
|
||||||
(key, value) for (key, value) in
|
(key, value) for (key, value) in
|
||||||
six.iteritems(defn.properties(self.properties_schema))
|
six.iteritems(defn.properties(self.properties_schema))
|
||||||
|
|
|
@ -998,9 +998,8 @@ class CinderVolumeTest(vt_base.BaseVolumeTest):
|
||||||
stack_name, combinations,
|
stack_name, combinations,
|
||||||
err_msg, exception.StackValidationFailed)
|
err_msg, exception.StackValidationFailed)
|
||||||
|
|
||||||
def test_volume_restore(self):
|
def _test_volume_restore(self, stack_name, final_status='available',
|
||||||
stack_name = 'test_cvolume_restore_stack'
|
stack_final_status=('RESTORE', 'COMPLETE')):
|
||||||
|
|
||||||
# create script
|
# create script
|
||||||
cinder.CinderClientPlugin._create().MultipleTimes().AndReturn(
|
cinder.CinderClientPlugin._create().MultipleTimes().AndReturn(
|
||||||
self.cinder_fc)
|
self.cinder_fc)
|
||||||
|
@ -1023,12 +1022,12 @@ class CinderVolumeTest(vt_base.BaseVolumeTest):
|
||||||
# restore script
|
# restore script
|
||||||
fvbr = vt_base.FakeBackupRestore('vol-123')
|
fvbr = vt_base.FakeBackupRestore('vol-123')
|
||||||
self.m.StubOutWithMock(self.cinder_fc.restores, 'restore')
|
self.m.StubOutWithMock(self.cinder_fc.restores, 'restore')
|
||||||
self.cinder_fc.restores.restore('backup-123').AndReturn(fvbr)
|
self.cinder_fc.restores.restore('backup-123',
|
||||||
self.cinder_fc.volumes.get('vol-123').AndReturn(fv)
|
'vol-123').AndReturn(fvbr)
|
||||||
self.cinder_fc.volumes.update('vol-123',
|
fv_restoring = vt_base.FakeVolume('restoring-backup', id=fv.id)
|
||||||
description='test_description',
|
self.cinder_fc.volumes.get('vol-123').AndReturn(fv_restoring)
|
||||||
name='test_name')
|
fv_final = vt_base.FakeVolume(final_status, id=fv.id)
|
||||||
self.cinder_fc.volumes.get('vol-123').AndReturn(fv)
|
self.cinder_fc.volumes.get('vol-123').AndReturn(fv_final)
|
||||||
|
|
||||||
self.m.ReplayAll()
|
self.m.ReplayAll()
|
||||||
|
|
||||||
|
@ -1048,10 +1047,18 @@ class CinderVolumeTest(vt_base.BaseVolumeTest):
|
||||||
|
|
||||||
stack.restore(fake_snapshot)
|
stack.restore(fake_snapshot)
|
||||||
|
|
||||||
self.assertEqual((stack.RESTORE, stack.COMPLETE), stack.state)
|
self.assertEqual(stack_final_status, stack.state)
|
||||||
|
|
||||||
self.m.VerifyAll()
|
self.m.VerifyAll()
|
||||||
|
|
||||||
|
def test_volume_restore_success(self):
|
||||||
|
self._test_volume_restore(stack_name='test_volume_restore_success')
|
||||||
|
|
||||||
|
def test_volume_restore_failed(self):
|
||||||
|
self._test_volume_restore(stack_name='test_volume_restore_failed',
|
||||||
|
final_status='error',
|
||||||
|
stack_final_status=('RESTORE', 'FAILED'))
|
||||||
|
|
||||||
def test_handle_delete_snapshot_no_backup(self):
|
def test_handle_delete_snapshot_no_backup(self):
|
||||||
stack_name = 'test_handle_delete_snapshot_no_backup'
|
stack_name = 'test_handle_delete_snapshot_no_backup'
|
||||||
mock_vs = {
|
mock_vs = {
|
||||||
|
|
Loading…
Reference in New Issue