Merge "Differentiate between InstanceNotFound and ConstraintNotMet"

This commit is contained in:
Zuul 2021-03-09 21:03:12 +00:00 committed by Gerrit Code Review
commit 8a0b12a26a
2 changed files with 46 additions and 0 deletions

View File

@ -1162,6 +1162,14 @@ def instance_destroy(context, instance_uuid, constraint=None,
# to make sure that the constraints were met.
count = query.soft_delete()
if count == 0:
# The failure to soft delete could be due to one of two things:
# 1) A racing request has deleted the instance out from under us
# 2) A constraint was not met
# Try to read the instance back once more and let it raise
# InstanceNotFound if 1) happened. This will give the caller an error
# that more accurately reflects the reason for the failure.
_instance_get_by_uuid(context, instance_uuid)
# Else, raise ConstraintNotMet if 2) happened.
raise exception.ConstraintNotMet()
models_to_delete = [

View File

@ -2653,6 +2653,44 @@ class InstanceTestCase(test.TestCase, ModelsObjectComparatorMixin):
self.assertRaises(exception.InstanceNotFound,
db.instance_destroy, ctxt, instance['uuid'])
def test_instance_destroy_already_destroyed_race(self):
# Test the scenario where the instance record still exists when we
# begin instance_destroy but becomes deleted by a racing request
# before we call query.soft_delete.
ctxt = context.get_admin_context()
instance = self.create_instance_with_args()
# Save the real implementation of _instance_get_by_uuid before we mock
# it later.
real_get_i = sqlalchemy_api._instance_get_by_uuid
# We will delete the instance record before we begin and mock
# _instance_get_by_uuid to simulate the instance still existing at the
# beginning of instance_destroy by returning the instance only the
# first time it is called.
# We want to actually delete the instance record so that we can verify
# the behavior and handling when query.soft_delete is called after the
# record has been deleted.
db.instance_destroy(ctxt, instance['uuid'])
# Mock the _instance_get_by_uuid method to return the instance object
# the first time it is called and pass through to the real
# implementation for any subsequent calls.
def fake_get_i(*a, **kw):
if not fake_get_i.called:
fake_get_i.called = True
return instance
return real_get_i(*a, **kw)
with mock.patch.object(sqlalchemy_api,
'_instance_get_by_uuid') as mock_get_i:
fake_get_i.called = False
mock_get_i.side_effect = fake_get_i
# We expect InstanceNotFound to be raised in the case of the
# record having already been deleted.
self.assertRaises(exception.InstanceNotFound,
db.instance_destroy, ctxt, instance['uuid'])
def test_instance_destroy_hard(self):
ctxt = context.get_admin_context()
instance = self.create_instance_with_args()