summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Smith <dansmith@redhat.com>2022-05-16 11:58:05 -0700
committerAbhishek Kekane <akekane@redhat.com>2022-05-17 06:16:34 +0000
commitcc98dbef31be30372b59b2d18b78694ce0879aa0 (patch)
treeae1b63deb9cb81be5a2ebee0ad6d465a52a29723
parent1beee78e096ddba4729be10af6ac4ddec8e79da0 (diff)
downloadglance-cc98dbef31be30372b59b2d18b78694ce0879aa0.tar.gz
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 (cherry picked from commit 35e6c57bc90365796f45ada9271e532068557b8a)
-rw-r--r--glance/api/v2/metadef_namespaces.py8
-rw-r--r--glance/tests/unit/v2/test_metadef_resources.py39
2 files changed, 46 insertions, 1 deletions
diff --git a/glance/api/v2/metadef_namespaces.py b/glance/api/v2/metadef_namespaces.py
index f64be0809..14345cd34 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 781bce0fb..0969a7ae7 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)