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 07bfe6adb9
)
This commit is contained in:
parent
671cbad9b9
commit
8d1b5bda38
|
@ -93,6 +93,8 @@ class DbQuotaDriver(object):
|
||||||
used = resource.count_used(context, tenant_id,
|
used = resource.count_used(context, tenant_id,
|
||||||
resync_usage=False)
|
resync_usage=False)
|
||||||
else:
|
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()
|
plugins = directory.get_plugins()
|
||||||
plugin = plugins.get(key, plugins[constants.CORE])
|
plugin = plugins.get(key, plugins[constants.CORE])
|
||||||
used = resource.count(context, plugin, tenant_id)
|
used = resource.count(context, plugin, tenant_id)
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from neutron_lib.plugins import constants
|
||||||
|
from neutron_lib.plugins import directory
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
from oslo_utils import excutils
|
from oslo_utils import excutils
|
||||||
|
@ -24,20 +26,32 @@ from neutron.db.quota import api as quota_api
|
||||||
LOG = log.getLogger(__name__)
|
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
|
count_getter_name = "get_%s_count" % collection_name
|
||||||
|
getter_name = "get_%s" % collection_name
|
||||||
|
|
||||||
# Some plugins support a count method for particular resources,
|
plugins = directory.get_plugins()
|
||||||
# using a DB's optimized counting features. We try to use that one
|
for pname in sorted(plugins,
|
||||||
# if present. Otherwise just use regular getter to retrieve all objects
|
# inspect core plugin first
|
||||||
# and count in python, allowing older plugins to still be supported
|
key=lambda n: n != constants.CORE):
|
||||||
try:
|
# Some plugins support a count method for particular resources, using a
|
||||||
obj_count_getter = getattr(plugin, count_getter_name)
|
# DB's optimized counting features. We try to use that one if present.
|
||||||
return obj_count_getter(context, filters={'tenant_id': [tenant_id]})
|
# Otherwise just use regular getter to retrieve all objects and count
|
||||||
except (NotImplementedError, AttributeError):
|
# in python, allowing older plugins to still be supported
|
||||||
obj_getter = getattr(plugin, "get_%s" % collection_name)
|
try:
|
||||||
obj_list = obj_getter(context, filters={'tenant_id': [tenant_id]})
|
obj_count_getter = getattr(plugins[pname], count_getter_name)
|
||||||
return len(obj_list) if obj_list else 0
|
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):
|
class BaseResource(object):
|
||||||
|
@ -129,7 +143,8 @@ class CountableResource(BaseResource):
|
||||||
self._count_func = count
|
self._count_func = count
|
||||||
|
|
||||||
def count(self, context, plugin, tenant_id, **kwargs):
|
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):
|
class TrackedResource(BaseResource):
|
||||||
|
|
|
@ -29,7 +29,7 @@ from neutron.tests.unit import testlib_api
|
||||||
DB_PLUGIN_KLASS = 'neutron.db.db_base_plugin_v2.NeutronDbPluginV2'
|
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"""
|
"""A fake counting function to determine current used counts"""
|
||||||
if resource[-1] == 's':
|
if resource[-1] == 's':
|
||||||
resource = resource[:-1]
|
resource = resource[:-1]
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
from neutron_lib import context
|
from neutron_lib import context
|
||||||
|
from neutron_lib.plugins import constants
|
||||||
|
from neutron_lib.plugins import directory
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
import testtools
|
import testtools
|
||||||
|
@ -325,3 +327,45 @@ class TestTrackedResource(testlib_api.SqlTestCase):
|
||||||
self.assertNotIn(self.tenant_id, res._out_of_sync_tenants)
|
self.assertNotIn(self.tenant_id, res._out_of_sync_tenants)
|
||||||
mock_set_quota_usage.assert_called_once_with(
|
mock_set_quota_usage.assert_called_once_with(
|
||||||
self.context, self.resource, self.tenant_id, in_use=2)
|
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))
|
||||||
|
|
Loading…
Reference in New Issue