LOG = log.getLogger(__name__)
-def _count_resource(context, plugin, resources, tenant_id):
- count_getter_name = "get_%s_count" % resources
+def _count_resource(context, plugin, collection_name, tenant_id):
+ count_getter_name = "get_%s_count" % collection_name
# Some plugins support a count method for particular resources,
# using a DB's optimized counting features. We try to use that one
meh = obj_count_getter(context, filters={'tenant_id': [tenant_id]})
return meh
except (NotImplementedError, AttributeError):
- obj_getter = getattr(plugin, "get_%s" % resources)
+ 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
class BaseResource(object):
"""Describe a single resource for quota checking."""
- def __init__(self, name, flag):
+ def __init__(self, name, flag, plural_name=None):
"""Initializes a resource.
:param name: The name of the resource, i.e., "instances".
:param flag: The name of the flag or configuration option
+ :param plural_name: Plural form of the resource name. If not
+ specified, it is generated automatically by
+ appending an 's' to the resource name, unless
+ it ends with a 'y'. In that case the last
+ letter is removed, and 'ies' is appended.
+ Dashes are always converted to underscores.
"""
self.name = name
+ # If a plural name is not supplied, default to adding an 's' to
+ # the resource name, unless the resource name ends in 'y', in which
+ # case remove the 'y' and add 'ies'. Even if the code should not fiddle
+ # too much with English grammar, this is a rather common and easy to
+ # implement rule.
+ if plural_name:
+ self.plural_name = plural_name
+ elif self.name[-1] == 'y':
+ self.plural_name = "%sies" % self.name[:-1]
+ else:
+ self.plural_name = "%ss" % self.name
+ # always convert dashes to underscores
+ self.plural_name = self.plural_name.replace('-', '_')
self.flag = flag
@property
class CountableResource(BaseResource):
"""Describe a resource where the counts are determined by a function."""
- def __init__(self, name, count, flag=None):
+ def __init__(self, name, count, flag=None, plural_name=None):
"""Initializes a CountableResource.
Countable resources are those resources which directly
:param flag: The name of the flag or configuration option
which specifies the default value of the quota
for this resource.
+ :param plural_name: Plural form of the resource name. If not
+ specified, it is generated automatically by
+ appending an 's' to the resource name, unless
+ it ends with a 'y'. In that case the last
+ letter is removed, and 'ies' is appended.
+ Dashes are always converted to underscores.
"""
- super(CountableResource, self).__init__(name, flag=flag)
- self.count = count
+ super(CountableResource, self).__init__(
+ name, flag=flag, plural_name=plural_name)
+ self._count_func = count
+
+ def count(self, context, plugin, tenant_id):
+ return self._count_func(context, plugin, self.plural_name, tenant_id)
class TrackedResource(BaseResource):
"""Resource which keeps track of its usage data."""
- def __init__(self, name, model_class, flag):
+ def __init__(self, name, model_class, flag, plural_name=None):
"""Initializes an instance for a given resource.
TrackedResource are directly mapped to data model classes.
:param flag: The name of the flag or configuration option
which specifies the default value of the quota
for this resource.
+ :param plural_name: Plural form of the resource name. If not
+ specified, it is generated automatically by
+ appending an 's' to the resource name, unless
+ it ends with a 'y'. In that case the last
+ letter is removed, and 'ies' is appended.
+ Dashes are always converted to underscores.
+
"""
- super(TrackedResource, self).__init__(name, flag)
+ super(TrackedResource, self).__init__(
+ name, flag=flag, plural_name=plural_name)
# Register events for addition/removal of records in the model class
# As tenant_id is immutable for all Neutron objects there is no need
# to register a listener for update events
# Update quota usage
return self._resync(context, tenant_id, in_use)
- def count(self, context, _plugin, _resources, tenant_id,
- resync_usage=False):
+ def count(self, context, _plugin, tenant_id, resync_usage=False):
"""Return the current usage count for the resource."""
# Load current usage data
usage_info = quota_api.get_quota_usage_by_resource_and_tenant(
ResourceRegistry.get_instance().register_resource(resource)
-def register_resource_by_name(resource_name):
- ResourceRegistry.get_instance().register_resource_by_name(resource_name)
+def register_resource_by_name(resource_name, plural_name=None):
+ ResourceRegistry.get_instance().register_resource_by_name(
+ resource_name, plural_name)
def get_all_resources():
def __contains__(self, resource):
return resource in self._resources
- def _create_resource_instance(self, resource_name):
+ def _create_resource_instance(self, resource_name, plural_name):
"""Factory function for quota Resource.
This routine returns a resource instance of the appropriate type
for res in resources:
self.register_resource(res)
- def register_resource_by_name(self, resource_name):
+ def register_resource_by_name(self, resource_name,
+ plural_name=None):
"""Register a resource by name."""
- resource = self._create_resource_instance(resource_name)
+ resource = self._create_resource_instance(
+ resource_name, plural_name)
self.register_resource(resource)
def unregister_resources(self):
random.seed()
+class TestResource(base.DietTestCase):
+ """Unit tests for neutron.quota.resource.BaseResource"""
+
+ def test_create_resource_without_plural_name(self):
+ res = resource.BaseResource('foo', None)
+ self.assertEqual('foos', res.plural_name)
+ res = resource.BaseResource('foy', None)
+ self.assertEqual('foies', res.plural_name)
+
+ def test_create_resource_with_plural_name(self):
+ res = resource.BaseResource('foo', None,
+ plural_name='foopsies')
+ self.assertEqual('foopsies', res.plural_name)
+
+ def test_resource_default_value(self):
+ res = resource.BaseResource('foo', 'foo_quota')
+ with mock.patch('oslo_config.cfg.CONF') as mock_cfg:
+ mock_cfg.QUOTAS.foo_quota = 99
+ self.assertEqual(99, res.default)
+
+ def test_resource_negative_default_value(self):
+ res = resource.BaseResource('foo', 'foo_quota')
+ with mock.patch('oslo_config.cfg.CONF') as mock_cfg:
+ mock_cfg.QUOTAS.foo_quota = -99
+ self.assertEqual(-1, res.default)
+
+
class TestTrackedResource(testlib_api.SqlTestCaseLight):
def _add_data(self, tenant_id=None):
self.context, self.resource, dirty=False)
# Expect correct count to be returned anyway since the first call to
# count() always resyncs with the db
- self.assertEqual(2, res.count(self.context,
- None, None,
- self.tenant_id))
+ self.assertEqual(2, res.count(self.context, None, self.tenant_id))
def _test_count(self):
res = self._create_resource()
def test_count_with_dirty_false(self):
res = self._test_count()
- res.count(self.context, None, None, self.tenant_id)
+ res.count(self.context, None, self.tenant_id)
# At this stage count has been invoked, and the dirty flag should be
# false. Another invocation of count should not query the model class
set_quota = 'neutron.db.quota.api.set_quota_usage'
with mock.patch(set_quota) as mock_set_quota:
self.assertEqual(0, mock_set_quota.call_count)
self.assertEqual(2, res.count(self.context,
- None, None,
+ None,
self.tenant_id))
def test_count_with_dirty_true_resync(self):
# Expect correct count to be returned, which also implies
# set_quota_usage has been invoked with the correct parameters
self.assertEqual(2, res.count(self.context,
- None, None,
+ None,
self.tenant_id,
resync_usage=True))
quota_api.set_quota_usage_dirty(self.context,
self.resource,
self.tenant_id)
- res.count(self.context, None, None, self.tenant_id,
+ res.count(self.context, None, self.tenant_id,
resync_usage=True)
mock_set_quota_usage.assert_called_once_with(
self.context, self.resource, self.tenant_id, in_use=2)
self._add_data()
# Invoke count without having usage info in DB - Expect correct
# count to be returned
- self.assertEqual(2, res.count(self.context,
- None, None,
- self.tenant_id))
+ self.assertEqual(2, res.count(self.context, None, self.tenant_id))
def test_count_with_dirty_true_no_usage_info_calls_set_quota_usage(self):
res = self._create_resource()
quota_api.set_quota_usage_dirty(self.context,
self.resource,
self.tenant_id)
- res.count(self.context, None, None, self.tenant_id,
- resync_usage=True)
+ res.count(self.context, None, self.tenant_id, resync_usage=True)
mock_set_quota_usage.assert_called_once_with(
self.context, self.resource, self.tenant_id, in_use=2)