From 477ae240eb4fa92b2769cb4856310d2af88b8f24 Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Mon, 8 Jun 2026 12:43:43 +0200 Subject: [PATCH] quota: Fix quota details API error with unloaded service plugins When a service plugin package (e.g. neutron-fwaas, neutron-vpnaas, networking-sfc) is installed but its service plugin is not configured in ``service_plugins``, the quota details API endpoint (GET /v2.0/quotas/{project_id}/details) returns a 500 Server Error. The installed package registers quota resources (e.g. firewall_group, firewall_policy, firewall_rule) at import time via ``resource_helper.build_resource_info(register_quota=True)``. When the quota details endpoint iterates over all registered resources to count usage, it calls ``_count_resource()`` which looks for a plugin that provides ``get__count`` or ``get_``. Since the service plugin is not loaded, no plugin supports counting those resources, and a ``NotImplementedError`` is raised. Catch the ``NotImplementedError`` in ``DbQuotaDriver.get_detailed_project_quotas()`` and skip the resource instead of letting the exception propagate as a 500 error. Also guard the project-specific limit update loop against skipped resources. Closes-Bug: #2155846 Assisted-By: Claude Opus 4.6 Signed-off-by: Rodolfo Alonso Hernandez Change-Id: I923e90279edf3de3fa85c83fd46e1b5dec0468de --- neutron/db/quota/driver.py | 10 +++++++-- neutron/tests/unit/db/quota/test_driver.py | 22 +++++++++++++++++++ ...oaded-service-plugin-f4c2a0766eb045b0.yaml | 10 +++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/fix-quota-details-unloaded-service-plugin-f4c2a0766eb045b0.yaml diff --git a/neutron/db/quota/driver.py b/neutron/db/quota/driver.py index 694a40735fa..fc6dffc52c4 100644 --- a/neutron/db/quota/driver.py +++ b/neutron/db/quota/driver.py @@ -97,7 +97,12 @@ def get_detailed_project_quotas(self, context, resources, project_id): # pass it regardless to keep the quota driver API intact plugins = directory.get_plugins() plugin = plugins.get(key, plugins[constants.CORE]) - used = resource.count(context, plugin, project_id) + try: + used = resource.count(context, plugin, project_id) + except NotImplementedError: + LOG.info('Skipping quota resource %s: no plugin loaded ' + 'that supports counting it.', key) + continue project_quota_ext[key] = { 'limit': resource.default, @@ -108,7 +113,8 @@ def get_detailed_project_quotas(self, context, resources, project_id): quota_objs = quota_obj.Quota.get_objects(context, project_id=project_id) for item in quota_objs: - project_quota_ext[item['resource']]['limit'] = item['limit'] + if item['resource'] in project_quota_ext: + project_quota_ext[item['resource']]['limit'] = item['limit'] return project_quota_ext @staticmethod diff --git a/neutron/tests/unit/db/quota/test_driver.py b/neutron/tests/unit/db/quota/test_driver.py index d3f0f7c3cc7..362279efb23 100644 --- a/neutron/tests/unit/db/quota/test_driver.py +++ b/neutron/tests/unit/db/quota/test_driver.py @@ -308,6 +308,28 @@ def test_get_detailed_project_quotas_multiple_resource(self): self.assertEqual(7, detailed_quota[self.resource_2]['reserved']) self.assertEqual(3, detailed_quota[self.resource_2]['used']) + def test_get_detailed_project_quotas_skips_uncount_resource(self): + def _raise_not_implemented(context, resource, project_id): + raise NotImplementedError( + 'No plugins that support counting %s found.' % resource) + + resources = { + self.resource_1: + TestTrackedResource(self.resource_1, test_quota.MehModel), + self.resource_2: + TestCountableResource(self.resource_2, + _raise_not_implemented)} + self.plugin.update_quota_limit(self.context, self.project_1, + self.resource_1, 6) + self.plugin.update_quota_limit(self.context, self.project_1, + self.resource_2, 9) + detailed_quota = self.plugin.get_detailed_project_quotas( + self.context, resources, self.project_1) + + self.assertIn(self.resource_1, detailed_quota) + self.assertNotIn(self.resource_2, detailed_quota) + self.assertEqual(6, detailed_quota[self.resource_1]['limit']) + def test_quota_limit_check(self): resources = self._create_resources() self.plugin.update_quota_limit(self.context, self.project_1, diff --git a/releasenotes/notes/fix-quota-details-unloaded-service-plugin-f4c2a0766eb045b0.yaml b/releasenotes/notes/fix-quota-details-unloaded-service-plugin-f4c2a0766eb045b0.yaml new file mode 100644 index 00000000000..636cd66722d --- /dev/null +++ b/releasenotes/notes/fix-quota-details-unloaded-service-plugin-f4c2a0766eb045b0.yaml @@ -0,0 +1,10 @@ +--- +fixes: + - | + Fixed a 500 Internal Server Error when querying the quota details API + (``GET /v2.0/quotas/{project_id}/details``) while a service plugin + package (e.g. ``neutron-fwaas``, ``neutron-vpnaas``, + ``networking-sfc``) is installed but its service plugin is not + configured in ``service_plugins``. The quota engine now gracefully + skips resources whose service plugin is not loaded instead of raising + a ``NotImplementedError``.