Merge "Nested Quota: Set default values to subproject"
This commit is contained in:
commit
47d0b24d88
|
@ -76,8 +76,9 @@ class QuotaSetsController(wsgi.Controller):
|
|||
|
||||
return limit
|
||||
|
||||
def _get_quotas(self, context, id, usages=False):
|
||||
values = QUOTAS.get_project_quotas(context, id, usages=usages)
|
||||
def _get_quotas(self, context, id, usages=False, parent_project_id=None):
|
||||
values = QUOTAS.get_project_quotas(context, id, usages=usages,
|
||||
parent_project_id=parent_project_id)
|
||||
|
||||
if usages:
|
||||
return values
|
||||
|
@ -150,7 +151,10 @@ class QuotaSetsController(wsgi.Controller):
|
|||
def defaults(self, req, id):
|
||||
context = req.environ['cinder.context']
|
||||
authorize_show(context)
|
||||
return self._format_quota_set(id, QUOTAS.get_defaults(context))
|
||||
return self._format_quota_set(id,
|
||||
QUOTAS.get_defaults(context,
|
||||
parent_project_id=
|
||||
None))
|
||||
|
||||
@wsgi.serializers(xml=QuotaTemplate)
|
||||
def delete(self, req, id):
|
||||
|
|
|
@ -97,13 +97,18 @@ class DbQuotaDriver(object):
|
|||
|
||||
return db.quota_class_get(context, quota_class, resource_name)
|
||||
|
||||
def get_default(self, context, resource):
|
||||
"""Get a specific default quota for a resource."""
|
||||
def get_default(self, context, resource, parent_project_id=None):
|
||||
"""Get a specific default quota for a resource.
|
||||
|
||||
:param parent_project_id: The id of the current project's parent,
|
||||
if any.
|
||||
"""
|
||||
|
||||
default_quotas = db.quota_class_get_default(context)
|
||||
return default_quotas.get(resource.name, resource.default)
|
||||
default_quota_value = 0 if parent_project_id else resource.default
|
||||
return default_quotas.get(resource.name, default_quota_value)
|
||||
|
||||
def get_defaults(self, context, resources):
|
||||
def get_defaults(self, context, resources, parent_project_id=None):
|
||||
"""Given a list of resources, retrieve the default quotas.
|
||||
|
||||
Use the class quotas named `_DEFAULT_QUOTA_NAME` as default quotas,
|
||||
|
@ -111,6 +116,8 @@ class DbQuotaDriver(object):
|
|||
|
||||
:param context: The request context, for access checks.
|
||||
:param resources: A dictionary of the registered resources.
|
||||
:param parent_project_id: The id of the current project's parent,
|
||||
if any.
|
||||
"""
|
||||
|
||||
quotas = {}
|
||||
|
@ -127,8 +134,8 @@ class DbQuotaDriver(object):
|
|||
"default quota class for default "
|
||||
"quota.") % {'res': resource.name})
|
||||
quotas[resource.name] = default_quotas.get(resource.name,
|
||||
resource.default)
|
||||
|
||||
(0 if parent_project_id
|
||||
else resource.default))
|
||||
return quotas
|
||||
|
||||
def get_class_quotas(self, context, resources, quota_class,
|
||||
|
@ -162,7 +169,7 @@ class DbQuotaDriver(object):
|
|||
|
||||
def get_project_quotas(self, context, resources, project_id,
|
||||
quota_class=None, defaults=True,
|
||||
usages=True):
|
||||
usages=True, parent_project_id=None):
|
||||
"""Given a list of resources, retrieve the quotas for the given
|
||||
project.
|
||||
|
||||
|
@ -180,6 +187,8 @@ class DbQuotaDriver(object):
|
|||
specific value for the resource.
|
||||
:param usages: If True, the current in_use and reserved counts
|
||||
will also be returned.
|
||||
:param parent_project_id: The id of the current project's parent,
|
||||
if any.
|
||||
"""
|
||||
|
||||
quotas = {}
|
||||
|
@ -199,7 +208,8 @@ class DbQuotaDriver(object):
|
|||
else:
|
||||
class_quotas = {}
|
||||
|
||||
default_quotas = self.get_defaults(context, resources)
|
||||
default_quotas = self.get_defaults(context, resources,
|
||||
parent_project_id=parent_project_id)
|
||||
|
||||
for resource in resources.values():
|
||||
# Omit default/quota class values
|
||||
|
@ -224,7 +234,8 @@ class DbQuotaDriver(object):
|
|||
|
||||
return quotas
|
||||
|
||||
def _get_quotas(self, context, resources, keys, has_sync, project_id=None):
|
||||
def _get_quotas(self, context, resources, keys, has_sync, project_id=None,
|
||||
parent_project_id=None):
|
||||
"""A helper method which retrieves the quotas for specific resources.
|
||||
|
||||
This specific resource is identified by keys, and which apply to the
|
||||
|
@ -240,6 +251,8 @@ class DbQuotaDriver(object):
|
|||
:param project_id: Specify the project_id if current context
|
||||
is admin and admin wants to impact on
|
||||
common user's tenant.
|
||||
:param parent_project_id: The id of the current project's parent,
|
||||
if any.
|
||||
"""
|
||||
|
||||
# Filter resources
|
||||
|
@ -259,7 +272,8 @@ class DbQuotaDriver(object):
|
|||
# Grab and return the quotas (without usages)
|
||||
quotas = self.get_project_quotas(context, sub_resources,
|
||||
project_id,
|
||||
context.quota_class, usages=False)
|
||||
context.quota_class, usages=False,
|
||||
parent_project_id=parent_project_id)
|
||||
|
||||
return {k: v['limit'] for k, v in quotas.items()}
|
||||
|
||||
|
@ -431,17 +445,20 @@ class DbQuotaDriver(object):
|
|||
class BaseResource(object):
|
||||
"""Describe a single resource for quota checking."""
|
||||
|
||||
def __init__(self, name, flag=None):
|
||||
def __init__(self, name, flag=None, parent_project_id=None):
|
||||
"""Initializes a Resource.
|
||||
|
||||
:param name: The name of the resource, i.e., "volumes".
|
||||
:param flag: The name of the flag or configuration option
|
||||
which specifies the default value of the quota
|
||||
for this resource.
|
||||
:param parent_project_id: The id of the current project's parent,
|
||||
if any.
|
||||
"""
|
||||
|
||||
self.name = name
|
||||
self.flag = flag
|
||||
self.parent_project_id = parent_project_id
|
||||
|
||||
def quota(self, driver, context, **kwargs):
|
||||
"""Given a driver and context, obtain the quota for this resource.
|
||||
|
@ -486,12 +503,16 @@ class BaseResource(object):
|
|||
pass
|
||||
|
||||
# OK, return the default
|
||||
return driver.get_default(context, self)
|
||||
return driver.get_default(context, self,
|
||||
parent_project_id=self.parent_project_id)
|
||||
|
||||
@property
|
||||
def default(self):
|
||||
"""Return the default value of the quota."""
|
||||
|
||||
if self.parent_project_id:
|
||||
return 0
|
||||
|
||||
return CONF[self.flag] if self.flag else -1
|
||||
|
||||
|
||||
|
@ -628,18 +649,26 @@ class QuotaEngine(object):
|
|||
|
||||
return self._driver.get_by_class(context, quota_class, resource_name)
|
||||
|
||||
def get_default(self, context, resource):
|
||||
"""Get a specific default quota for a resource."""
|
||||
def get_default(self, context, resource, parent_project_id=None):
|
||||
"""Get a specific default quota for a resource.
|
||||
|
||||
return self._driver.get_default(context, resource)
|
||||
:param parent_project_id: The id of the current project's parent,
|
||||
if any.
|
||||
"""
|
||||
|
||||
def get_defaults(self, context):
|
||||
return self._driver.get_default(context, resource,
|
||||
parent_project_id=parent_project_id)
|
||||
|
||||
def get_defaults(self, context, parent_project_id=None):
|
||||
"""Retrieve the default quotas.
|
||||
|
||||
:param context: The request context, for access checks.
|
||||
:param parent_project_id: The id of the current project's parent,
|
||||
if any.
|
||||
"""
|
||||
|
||||
return self._driver.get_defaults(context, self.resources)
|
||||
return self._driver.get_defaults(context, self.resources,
|
||||
parent_project_id)
|
||||
|
||||
def get_class_quotas(self, context, quota_class, defaults=True):
|
||||
"""Retrieve the quotas for the given quota class.
|
||||
|
@ -656,7 +685,7 @@ class QuotaEngine(object):
|
|||
quota_class, defaults=defaults)
|
||||
|
||||
def get_project_quotas(self, context, project_id, quota_class=None,
|
||||
defaults=True, usages=True):
|
||||
defaults=True, usages=True, parent_project_id=None):
|
||||
"""Retrieve the quotas for the given project.
|
||||
|
||||
:param context: The request context, for access checks.
|
||||
|
@ -670,13 +699,17 @@ class QuotaEngine(object):
|
|||
specific value for the resource.
|
||||
:param usages: If True, the current in_use and reserved counts
|
||||
will also be returned.
|
||||
:param parent_project_id: The id of the current project's parent,
|
||||
if any.
|
||||
"""
|
||||
|
||||
return self._driver.get_project_quotas(context, self.resources,
|
||||
project_id,
|
||||
quota_class=quota_class,
|
||||
defaults=defaults,
|
||||
usages=usages)
|
||||
usages=usages,
|
||||
parent_project_id=
|
||||
parent_project_id)
|
||||
|
||||
def count(self, context, resource, *args, **kwargs):
|
||||
"""Count a resource.
|
||||
|
|
|
@ -325,12 +325,14 @@ class FakeDriver(object):
|
|||
except KeyError:
|
||||
raise exception.QuotaClassNotFound(class_name=quota_class)
|
||||
|
||||
def get_default(self, context, resource):
|
||||
self.called.append(('get_default', context, resource))
|
||||
def get_default(self, context, resource, parent_project_id=None):
|
||||
self.called.append(('get_default', context, resource,
|
||||
parent_project_id))
|
||||
return resource.default
|
||||
|
||||
def get_defaults(self, context, resources):
|
||||
self.called.append(('get_defaults', context, resources))
|
||||
def get_defaults(self, context, resources, parent_project_id=None):
|
||||
self.called.append(('get_defaults', context, resources,
|
||||
parent_project_id))
|
||||
return resources
|
||||
|
||||
def get_class_quotas(self, context, resources, quota_class,
|
||||
|
@ -340,9 +342,11 @@ class FakeDriver(object):
|
|||
return resources
|
||||
|
||||
def get_project_quotas(self, context, resources, project_id,
|
||||
quota_class=None, defaults=True, usages=True):
|
||||
quota_class=None, defaults=True, usages=True,
|
||||
parent_project_id=None):
|
||||
self.called.append(('get_project_quotas', context, resources,
|
||||
project_id, quota_class, defaults, usages))
|
||||
project_id, quota_class, defaults, usages,
|
||||
parent_project_id))
|
||||
return resources
|
||||
|
||||
def limit_check(self, context, resources, values, project_id=None):
|
||||
|
@ -447,6 +451,16 @@ class BaseResourceTestCase(test.TestCase):
|
|||
|
||||
self.assertEqual(quota_value, 20)
|
||||
|
||||
def test_quota_override_subproject_no_class(self):
|
||||
self.flags(quota_volumes=10)
|
||||
resource = quota.BaseResource('test_resource', 'quota_volumes',
|
||||
parent_project_id='test_parent_project')
|
||||
driver = FakeDriver()
|
||||
context = FakeContext('test_project', None)
|
||||
quota_value = resource.quota(driver, context)
|
||||
|
||||
self.assertEqual(quota_value, 0)
|
||||
|
||||
def test_quota_with_project_override_class(self):
|
||||
self.flags(quota_volumes=10)
|
||||
resource = quota.BaseResource('test_resource', 'quota_volumes')
|
||||
|
@ -554,13 +568,15 @@ class QuotaEngineTestCase(test.TestCase):
|
|||
|
||||
def test_get_defaults(self):
|
||||
context = FakeContext(None, None)
|
||||
parent_project_id = None
|
||||
driver = FakeDriver()
|
||||
quota_obj = self._make_quota_obj(driver)
|
||||
result = quota_obj.get_defaults(context)
|
||||
|
||||
self.assertEqual(driver.called, [('get_defaults',
|
||||
context,
|
||||
quota_obj.resources), ])
|
||||
quota_obj.resources,
|
||||
parent_project_id), ])
|
||||
self.assertEqual(result, quota_obj.resources)
|
||||
|
||||
def test_get_class_quotas(self):
|
||||
|
@ -584,6 +600,7 @@ class QuotaEngineTestCase(test.TestCase):
|
|||
def test_get_project_quotas(self):
|
||||
context = FakeContext(None, None)
|
||||
driver = FakeDriver()
|
||||
parent_project_id = None
|
||||
quota_obj = self._make_quota_obj(driver)
|
||||
result1 = quota_obj.get_project_quotas(context, 'test_project')
|
||||
result2 = quota_obj.get_project_quotas(context, 'test_project',
|
||||
|
@ -598,14 +615,51 @@ class QuotaEngineTestCase(test.TestCase):
|
|||
'test_project',
|
||||
None,
|
||||
True,
|
||||
True),
|
||||
True,
|
||||
parent_project_id),
|
||||
('get_project_quotas',
|
||||
context,
|
||||
quota_obj.resources,
|
||||
'test_project',
|
||||
'test_class',
|
||||
False,
|
||||
False), ])
|
||||
False,
|
||||
parent_project_id), ])
|
||||
self.assertEqual(result1, quota_obj.resources)
|
||||
self.assertEqual(result2, quota_obj.resources)
|
||||
|
||||
def test_get_subproject_quotas(self):
|
||||
context = FakeContext(None, None)
|
||||
driver = FakeDriver()
|
||||
parent_project_id = 'test_parent_project_id'
|
||||
quota_obj = self._make_quota_obj(driver)
|
||||
result1 = quota_obj.get_project_quotas(context, 'test_project',
|
||||
parent_project_id=
|
||||
parent_project_id)
|
||||
result2 = quota_obj.get_project_quotas(context, 'test_project',
|
||||
quota_class='test_class',
|
||||
defaults=False,
|
||||
usages=False,
|
||||
parent_project_id=
|
||||
parent_project_id)
|
||||
|
||||
self.assertEqual(driver.called, [
|
||||
('get_project_quotas',
|
||||
context,
|
||||
quota_obj.resources,
|
||||
'test_project',
|
||||
None,
|
||||
True,
|
||||
True,
|
||||
parent_project_id),
|
||||
('get_project_quotas',
|
||||
context,
|
||||
quota_obj.resources,
|
||||
'test_project',
|
||||
'test_class',
|
||||
False,
|
||||
False,
|
||||
parent_project_id), ])
|
||||
self.assertEqual(result1, quota_obj.resources)
|
||||
self.assertEqual(result2, quota_obj.resources)
|
||||
|
||||
|
@ -857,6 +911,25 @@ class DbQuotaDriverTestCase(test.TestCase):
|
|||
backup_gigabytes=1000,
|
||||
per_volume_gigabytes=-1))
|
||||
|
||||
def test_subproject_get_defaults(self):
|
||||
# Test subproject default values.
|
||||
self._stub_quota_class_get_default_subproject()
|
||||
self._stub_volume_type_get_all()
|
||||
parent_project_id = 'test_parent_project_id'
|
||||
result = self.driver.get_defaults(None,
|
||||
quota.QUOTAS.resources,
|
||||
parent_project_id)
|
||||
|
||||
self.assertEqual(
|
||||
result,
|
||||
dict(
|
||||
volumes=0,
|
||||
snapshots=0,
|
||||
gigabytes=0,
|
||||
backups=0,
|
||||
backup_gigabytes=0,
|
||||
per_volume_gigabytes=0))
|
||||
|
||||
def _stub_quota_class_get_default(self):
|
||||
# Stub out quota_class_get_default
|
||||
def fake_qcgd(context):
|
||||
|
@ -869,6 +942,13 @@ class DbQuotaDriverTestCase(test.TestCase):
|
|||
)
|
||||
self.stubs.Set(db, 'quota_class_get_default', fake_qcgd)
|
||||
|
||||
def _stub_quota_class_get_default_subproject(self):
|
||||
# Stub out quota_class_get_default for subprojects
|
||||
def fake_qcgd(context):
|
||||
self.calls.append('quota_class_get_default')
|
||||
return {}
|
||||
self.stubs.Set(db, 'quota_class_get_default', fake_qcgd)
|
||||
|
||||
def _stub_volume_type_get_all(self):
|
||||
def fake_vtga(context, inactive=False, filters=None):
|
||||
return {}
|
||||
|
@ -933,6 +1013,24 @@ class DbQuotaDriverTestCase(test.TestCase):
|
|||
self._stub_quota_class_get_all_by_name()
|
||||
self._stub_quota_class_get_default()
|
||||
|
||||
def _stub_get_by_subproject(self):
|
||||
def fake_qgabp(context, project_id):
|
||||
self.calls.append('quota_get_all_by_project')
|
||||
self.assertEqual(project_id, 'test_project')
|
||||
return dict(volumes=10, gigabytes=50, reserved=0)
|
||||
|
||||
def fake_qugabp(context, project_id):
|
||||
self.calls.append('quota_usage_get_all_by_project')
|
||||
self.assertEqual(project_id, 'test_project')
|
||||
return dict(volumes=dict(in_use=2, reserved=0),
|
||||
gigabytes=dict(in_use=10, reserved=0))
|
||||
|
||||
self.stubs.Set(db, 'quota_get_all_by_project', fake_qgabp)
|
||||
self.stubs.Set(db, 'quota_usage_get_all_by_project', fake_qugabp)
|
||||
|
||||
self._stub_quota_class_get_all_by_name()
|
||||
self._stub_quota_class_get_default_subproject()
|
||||
|
||||
def test_get_project_quotas(self):
|
||||
self._stub_get_by_project()
|
||||
self._stub_volume_type_get_all()
|
||||
|
@ -964,6 +1062,38 @@ class DbQuotaDriverTestCase(test.TestCase):
|
|||
reserved= 0)
|
||||
))
|
||||
|
||||
def test_get_subproject_quotas(self):
|
||||
self._stub_get_by_subproject()
|
||||
self._stub_volume_type_get_all()
|
||||
parent_project_id = 'test_parent_project_id'
|
||||
result = self.driver.get_project_quotas(
|
||||
FakeContext('test_project', None),
|
||||
quota.QUOTAS.resources, 'test_project',
|
||||
parent_project_id=parent_project_id)
|
||||
|
||||
self.assertEqual(self.calls, ['quota_get_all_by_project',
|
||||
'quota_usage_get_all_by_project',
|
||||
'quota_class_get_default', ])
|
||||
self.assertEqual(result, dict(volumes=dict(limit=10,
|
||||
in_use=2,
|
||||
reserved=0, ),
|
||||
snapshots=dict(limit=0,
|
||||
in_use=0,
|
||||
reserved=0, ),
|
||||
gigabytes=dict(limit=50,
|
||||
in_use=10,
|
||||
reserved=0, ),
|
||||
backups=dict(limit=0,
|
||||
in_use=0,
|
||||
reserved=0, ),
|
||||
backup_gigabytes=dict(limit=0,
|
||||
in_use=0,
|
||||
reserved=0, ),
|
||||
per_volume_gigabytes=dict(in_use=0,
|
||||
limit=0,
|
||||
reserved= 0)
|
||||
))
|
||||
|
||||
def test_get_project_quotas_alt_context_no_class(self):
|
||||
self._stub_get_by_project()
|
||||
self._stub_volume_type_get_all()
|
||||
|
@ -1077,7 +1207,7 @@ class DbQuotaDriverTestCase(test.TestCase):
|
|||
def _stub_get_project_quotas(self):
|
||||
def fake_get_project_quotas(context, resources, project_id,
|
||||
quota_class=None, defaults=True,
|
||||
usages=True):
|
||||
usages=True, parent_project_id=None):
|
||||
self.calls.append('get_project_quotas')
|
||||
return {k: dict(limit=v.default) for k, v in resources.items()}
|
||||
|
||||
|
|
Loading…
Reference in New Issue