Fix failing namespace list delete race

If a namespace is deleted by another client while we are doing a
namespace list operation, we will fail the list with NotFound if we
try to pull the resource_type_associations list. The latter re-queries
the DB for the namespace and will raise NotFound to us. This is
especially bad because the namespace being deleted need not even
belong to the caller of the list, as is the case in a tempest run.

This makes us catch the failure and continue the operation, reporting
no associations so that the client gets a consistent view and no
error.

Closes-Bug: #1973631
Change-Id: I09fc9164a08f42507d2aec44c5b382a72f232571
This commit is contained in:
Dan Smith 2022-05-16 11:58:05 -07:00
parent d7fa7a0321
commit 35e6c57bc9
2 changed files with 46 additions and 1 deletions

View File

@ -98,7 +98,13 @@ class NamespaceController(object):
# Get resource type associations
filters = dict()
filters['namespace'] = db_namespace.namespace
repo_rs_type_list = rs_repo.list(filters=filters)
try:
repo_rs_type_list = rs_repo.list(filters=filters)
except exception.NotFound:
# NOTE(danms): If we fail to list resource_types
# for this namespace, do not fail the entire
# namespace list operation with NotFound.
repo_rs_type_list = []
resource_type_list = [
ResourceTypeAssociation.to_wsme_model(
resource_type

View File

@ -18,6 +18,7 @@ from unittest import mock
from oslo_serialization import jsonutils
import webob
import wsme
from glance.api import policy
from glance.api.v2 import metadef_namespaces as namespaces
@ -296,6 +297,44 @@ class TestMetadefsControllers(base.IsolatedUnitTest):
expected = set([NAMESPACE1, NAMESPACE3])
self.assertEqual(expected, actual)
def test_namespace_index_resource_type_delete_race(self):
request = unit_test_utils.get_fake_request()
filters = {'resource_types': [RESOURCE_TYPE1]}
real_gmrtr = self.namespace_controller.gateway.\
get_metadef_resource_type_repo
def race_delete(*a, **k):
self.db.metadef_namespace_delete(request.context, NAMESPACE3)
return real_gmrtr(*a, **k)
with mock.patch.object(self.namespace_controller.gateway,
'get_metadef_resource_type_repo') as g:
# NOTE(danms): We simulate a late delete of one of our
# namespaces by hijacking the call to get the metadef RT
# repo and doing a delete at that point, before we iterate
# the list of namespaces we already pulled from the DB. If
# the code in the index API method changes, this will need
# to be updated.
g.side_effect = race_delete
output = self.namespace_controller.index(request, filters=filters)
output = output.to_dict()
self.assertEqual(2, len(output['namespaces']))
actual = set([namespace.namespace for namespace
in output['namespaces']])
# We should still see both namespaces
expected = set([NAMESPACE1, NAMESPACE3])
self.assertEqual(expected, actual)
# And the first (undeleted) one should have the expected
# associations...
self.assertEqual(
1, len(output['namespaces'][0].resource_type_associations))
# ...but the one we deleted should be empty
self.assertEqual(
wsme.types.Unset,
output['namespaces'][1].resource_type_associations)
def test_namespace_show(self):
request = unit_test_utils.get_fake_request()
output = self.namespace_controller.show(request, NAMESPACE1)