From 8d1b5bda3820a76d2af9758fc985e6e5e6004b33 Mon Sep 17 00:00:00 2001 From: Ihar Hrachyshka Date: Wed, 6 Sep 2017 12:55:24 -0700 Subject: [PATCH] CountableResource: try count/get functions for all plugins It's of no guarantee that core plugin implements counter/getter function for a CountableResource. Instead of just trying core plugin, try every plugin registered in the directory. To retain backwards compatibility, we also make sure that core plugin is checked first. Change-Id: I5245e217e1f44281f85febbdfaf873321253dc5d Closes-Bug: #1714769 (cherry picked from commit 07bfe6adb96ee0a88b9dd54d7e4b0bb684b63e3c) --- neutron/db/quota/driver.py | 2 + neutron/quota/resource.py | 41 +++++++++++++------- neutron/tests/unit/db/quota/test_driver.py | 2 +- neutron/tests/unit/quota/test_resource.py | 44 ++++++++++++++++++++++ 4 files changed, 75 insertions(+), 14 deletions(-) diff --git a/neutron/db/quota/driver.py b/neutron/db/quota/driver.py index d5e962b99af..0cfb44a972c 100644 --- a/neutron/db/quota/driver.py +++ b/neutron/db/quota/driver.py @@ -93,6 +93,8 @@ class DbQuotaDriver(object): used = resource.count_used(context, tenant_id, resync_usage=False) else: + # NOTE(ihrachys) .count won't use the plugin we pass, but we + # 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, tenant_id) diff --git a/neutron/quota/resource.py b/neutron/quota/resource.py index 456a556d2b1..674d8da55b7 100644 --- a/neutron/quota/resource.py +++ b/neutron/quota/resource.py @@ -12,6 +12,8 @@ # License for the specific language governing permissions and limitations # under the License. +from neutron_lib.plugins import constants +from neutron_lib.plugins import directory from oslo_config import cfg from oslo_log import log from oslo_utils import excutils @@ -24,20 +26,32 @@ from neutron.db.quota import api as quota_api LOG = log.getLogger(__name__) -def _count_resource(context, plugin, collection_name, tenant_id): +def _count_resource(context, collection_name, tenant_id): count_getter_name = "get_%s_count" % collection_name + getter_name = "get_%s" % collection_name - # Some plugins support a count method for particular resources, - # using a DB's optimized counting features. We try to use that one - # if present. Otherwise just use regular getter to retrieve all objects - # and count in python, allowing older plugins to still be supported - try: - obj_count_getter = getattr(plugin, count_getter_name) - return obj_count_getter(context, filters={'tenant_id': [tenant_id]}) - except (NotImplementedError, AttributeError): - obj_getter = getattr(plugin, "get_%s" % collection_name) - obj_list = obj_getter(context, filters={'tenant_id': [tenant_id]}) - return len(obj_list) if obj_list else 0 + plugins = directory.get_plugins() + for pname in sorted(plugins, + # inspect core plugin first + key=lambda n: n != constants.CORE): + # Some plugins support a count method for particular resources, using a + # DB's optimized counting features. We try to use that one if present. + # Otherwise just use regular getter to retrieve all objects and count + # in python, allowing older plugins to still be supported + try: + obj_count_getter = getattr(plugins[pname], count_getter_name) + return obj_count_getter( + context, filters={'tenant_id': [tenant_id]}) + except (NotImplementedError, AttributeError): + try: + obj_getter = getattr(plugins[pname], getter_name) + obj_list = obj_getter( + context, filters={'tenant_id': [tenant_id]}) + return len(obj_list) if obj_list else 0 + except (NotImplementedError, AttributeError): + pass + raise NotImplementedError( + 'No plugins that support counting %s found.' % collection_name) class BaseResource(object): @@ -129,7 +143,8 @@ class CountableResource(BaseResource): self._count_func = count def count(self, context, plugin, tenant_id, **kwargs): - return self._count_func(context, plugin, self.plural_name, tenant_id) + # NOTE(ihrachys) _count_resource doesn't receive plugin + return self._count_func(context, self.plural_name, tenant_id) class TrackedResource(BaseResource): diff --git a/neutron/tests/unit/db/quota/test_driver.py b/neutron/tests/unit/db/quota/test_driver.py index 8a4e9fb1571..3cfd2e03b6b 100644 --- a/neutron/tests/unit/db/quota/test_driver.py +++ b/neutron/tests/unit/db/quota/test_driver.py @@ -29,7 +29,7 @@ from neutron.tests.unit import testlib_api DB_PLUGIN_KLASS = 'neutron.db.db_base_plugin_v2.NeutronDbPluginV2' -def _count_resource(context, plugin, resource, tenant_id): +def _count_resource(context, resource, tenant_id): """A fake counting function to determine current used counts""" if resource[-1] == 's': resource = resource[:-1] diff --git a/neutron/tests/unit/quota/test_resource.py b/neutron/tests/unit/quota/test_resource.py index b808e10aa2f..93dfd356723 100644 --- a/neutron/tests/unit/quota/test_resource.py +++ b/neutron/tests/unit/quota/test_resource.py @@ -14,6 +14,8 @@ import mock from neutron_lib import context +from neutron_lib.plugins import constants +from neutron_lib.plugins import directory from oslo_config import cfg from oslo_utils import uuidutils import testtools @@ -325,3 +327,45 @@ class TestTrackedResource(testlib_api.SqlTestCase): self.assertNotIn(self.tenant_id, res._out_of_sync_tenants) mock_set_quota_usage.assert_called_once_with( self.context, self.resource, self.tenant_id, in_use=2) + + +class Test_CountResource(base.BaseTestCase): + + def test_all_plugins_checked(self): + plugin1 = mock.Mock() + plugin2 = mock.Mock() + plugins = {'plugin1': plugin1, 'plugin2': plugin2} + + for name, plugin in plugins.items(): + plugin.get_floatingips_count.side_effect = NotImplementedError + plugin.get_floatingips.side_effect = NotImplementedError + directory.add_plugin(name, plugin) + + context = mock.Mock() + collection_name = 'floatingips' + tenant_id = 'fakeid' + self.assertRaises( + NotImplementedError, + resource._count_resource, context, collection_name, tenant_id) + + for plugin in plugins.values(): + for func in (plugin.get_floatingips_count, plugin.get_floatingips): + func.assert_called_with( + context, filters={'tenant_id': [tenant_id]}) + + def test_core_plugin_checked_first(self): + plugin1 = mock.Mock() + plugin2 = mock.Mock() + + plugin1.get_floatingips_count.side_effect = NotImplementedError + plugin1.get_floatingips.side_effect = NotImplementedError + directory.add_plugin('plugin1', plugin1) + + plugin2.get_floatingips_count.return_value = 10 + directory.add_plugin(constants.CORE, plugin2) + + context = mock.Mock() + collection_name = 'floatingips' + tenant_id = 'fakeid' + self.assertEqual( + 10, resource._count_resource(context, collection_name, tenant_id))