From 35e6c57bc90365796f45ada9271e532068557b8a Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Mon, 16 May 2022 11:58:05 -0700 Subject: [PATCH] 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 --- glance/api/v2/metadef_namespaces.py | 8 +++- .../tests/unit/v2/test_metadef_resources.py | 39 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/glance/api/v2/metadef_namespaces.py b/glance/api/v2/metadef_namespaces.py index f64be08099..14345cd348 100644 --- a/glance/api/v2/metadef_namespaces.py +++ b/glance/api/v2/metadef_namespaces.py @@ -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 diff --git a/glance/tests/unit/v2/test_metadef_resources.py b/glance/tests/unit/v2/test_metadef_resources.py index 781bce0fb5..0969a7ae71 100644 --- a/glance/tests/unit/v2/test_metadef_resources.py +++ b/glance/tests/unit/v2/test_metadef_resources.py @@ -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)