Don't fail when trying to unprotect unprotected snapshot on RBD
If rbd driver tries to unprotect snapshot which is already unprotected, rbd lib raise InvalidArgument exception. Rbd driver in glance_store will now handle such exception and will continue deleting snapshot and image in such case. Change-Id: I612b4421d5065dde002e3c0e0cb22537d2467adb Closes-Bug: #1686063
This commit is contained in:
parent
cc11b4a903
commit
a783a7442b
|
@ -388,7 +388,7 @@ class Store(driver.Store):
|
||||||
if snapshot_name is not None:
|
if snapshot_name is not None:
|
||||||
with rbd.Image(ioctx, image_name) as image:
|
with rbd.Image(ioctx, image_name) as image:
|
||||||
try:
|
try:
|
||||||
image.unprotect_snap(snapshot_name)
|
self._unprotect_snapshot(image, snapshot_name)
|
||||||
image.remove_snap(snapshot_name)
|
image.remove_snap(snapshot_name)
|
||||||
except rbd.ImageNotFound as exc:
|
except rbd.ImageNotFound as exc:
|
||||||
msg = (_("Snap Operating Exception "
|
msg = (_("Snap Operating Exception "
|
||||||
|
@ -422,6 +422,15 @@ class Store(driver.Store):
|
||||||
msg = _("RBD image %s does not exist") % image_name
|
msg = _("RBD image %s does not exist") % image_name
|
||||||
raise exceptions.NotFound(message=msg)
|
raise exceptions.NotFound(message=msg)
|
||||||
|
|
||||||
|
def _unprotect_snapshot(self, image, snap_name):
|
||||||
|
try:
|
||||||
|
image.unprotect_snap(snap_name)
|
||||||
|
except rbd.InvalidArgument:
|
||||||
|
# NOTE(slaweq): if snapshot was unprotected already, rbd library
|
||||||
|
# raises InvalidArgument exception without any "clear" message.
|
||||||
|
# Such exception is not dangerous for us so it will be just logged
|
||||||
|
LOG.debug("Snapshot %s is unprotected already" % snap_name)
|
||||||
|
|
||||||
@capabilities.check
|
@capabilities.check
|
||||||
def add(self, image_id, image_file, image_size, context=None,
|
def add(self, image_id, image_file, image_size, context=None,
|
||||||
verifier=None):
|
verifier=None):
|
||||||
|
|
|
@ -24,6 +24,10 @@ from glance_store.tests import base
|
||||||
from glance_store.tests.unit import test_store_capabilities
|
from glance_store.tests.unit import test_store_capabilities
|
||||||
|
|
||||||
|
|
||||||
|
class TestException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class MockRados(object):
|
class MockRados(object):
|
||||||
|
|
||||||
class Error(Exception):
|
class Error(Exception):
|
||||||
|
@ -80,6 +84,9 @@ class MockRBD(object):
|
||||||
class ImageNotFound(Exception):
|
class ImageNotFound(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class InvalidArgument(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
class Image(object):
|
class Image(object):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
@ -298,6 +305,54 @@ class TestStore(base.StoreBaseTest,
|
||||||
self.called_commands_expected = ['unprotect_snap', 'remove_snap',
|
self.called_commands_expected = ['unprotect_snap', 'remove_snap',
|
||||||
'remove']
|
'remove']
|
||||||
|
|
||||||
|
@mock.patch.object(MockRBD.RBD, 'remove')
|
||||||
|
@mock.patch.object(MockRBD.Image, 'remove_snap')
|
||||||
|
@mock.patch.object(MockRBD.Image, 'unprotect_snap')
|
||||||
|
def test_delete_image_w_unprotected_snap(self, unprotect, remove_snap,
|
||||||
|
remove):
|
||||||
|
def _fake_unprotect_snap(*args, **kwargs):
|
||||||
|
self.called_commands_actual.append('unprotect_snap')
|
||||||
|
raise MockRBD.InvalidArgument()
|
||||||
|
|
||||||
|
def _fake_remove_snap(*args, **kwargs):
|
||||||
|
self.called_commands_actual.append('remove_snap')
|
||||||
|
|
||||||
|
def _fake_remove(*args, **kwargs):
|
||||||
|
self.called_commands_actual.append('remove')
|
||||||
|
|
||||||
|
remove.side_effect = _fake_remove
|
||||||
|
unprotect.side_effect = _fake_unprotect_snap
|
||||||
|
remove_snap.side_effect = _fake_remove_snap
|
||||||
|
self.store._delete_image('fake_pool', self.location.image,
|
||||||
|
snapshot_name='snap')
|
||||||
|
|
||||||
|
self.called_commands_expected = ['unprotect_snap', 'remove_snap',
|
||||||
|
'remove']
|
||||||
|
|
||||||
|
@mock.patch.object(MockRBD.RBD, 'remove')
|
||||||
|
@mock.patch.object(MockRBD.Image, 'remove_snap')
|
||||||
|
@mock.patch.object(MockRBD.Image, 'unprotect_snap')
|
||||||
|
def test_delete_image_w_snap_with_error(self, unprotect, remove_snap,
|
||||||
|
remove):
|
||||||
|
def _fake_unprotect_snap(*args, **kwargs):
|
||||||
|
self.called_commands_actual.append('unprotect_snap')
|
||||||
|
raise TestException()
|
||||||
|
|
||||||
|
def _fake_remove_snap(*args, **kwargs):
|
||||||
|
self.called_commands_actual.append('remove_snap')
|
||||||
|
|
||||||
|
def _fake_remove(*args, **kwargs):
|
||||||
|
self.called_commands_actual.append('remove')
|
||||||
|
|
||||||
|
remove.side_effect = _fake_remove
|
||||||
|
unprotect.side_effect = _fake_unprotect_snap
|
||||||
|
remove_snap.side_effect = _fake_remove_snap
|
||||||
|
self.assertRaises(TestException, self.store._delete_image,
|
||||||
|
'fake_pool', self.location.image,
|
||||||
|
snapshot_name='snap')
|
||||||
|
|
||||||
|
self.called_commands_expected = ['unprotect_snap']
|
||||||
|
|
||||||
def test_delete_image_w_snap_exc_image_busy(self):
|
def test_delete_image_w_snap_exc_image_busy(self):
|
||||||
def _fake_unprotect_snap(*args, **kwargs):
|
def _fake_unprotect_snap(*args, **kwargs):
|
||||||
self.called_commands_actual.append('unprotect_snap')
|
self.called_commands_actual.append('unprotect_snap')
|
||||||
|
|
Loading…
Reference in New Issue