From 51656c7576874b9a6c85ffe6e711f17898e7190f Mon Sep 17 00:00:00 2001 From: liyingjun Date: Thu, 23 May 2013 16:24:26 +0800 Subject: [PATCH] Editable default quota support for cinder Implement blueprint edit-default-quota DocImpact Using the class quotas named `default` as the default editable quotas. We can use the following cinderclient command to update default quota: cinder quota-class-update default Change-Id: I9f97506b41157066026dd43163ec4b1827d5b9cf --- cinder/db/api.py | 5 +++++ cinder/db/sqlalchemy/api.py | 14 ++++++++++++++ cinder/quota.py | 29 ++++++++++++++++++++++++----- cinder/tests/test_quota.py | 26 +++++++++++++++++++++----- etc/cinder/cinder.conf.sample | 5 +++++ 5 files changed, 69 insertions(+), 10 deletions(-) diff --git a/cinder/db/api.py b/cinder/db/api.py index 099856642..15e4a3f78 100644 --- a/cinder/db/api.py +++ b/cinder/db/api.py @@ -617,6 +617,11 @@ def quota_class_get(context, class_name, resource): return IMPL.quota_class_get(context, class_name, resource) +def quota_class_get_default(context): + """Retrieve all default quotas.""" + return IMPL.quota_class_get_default(context) + + def quota_class_get_all_by_name(context, class_name): """Retrieve all quotas associated with a given quota class.""" return IMPL.quota_class_get_all_by_name(context, class_name) diff --git a/cinder/db/sqlalchemy/api.py b/cinder/db/sqlalchemy/api.py index c4a18b393..c5735fc85 100644 --- a/cinder/db/sqlalchemy/api.py +++ b/cinder/db/sqlalchemy/api.py @@ -52,6 +52,8 @@ db_session.set_defaults(sql_connection='sqlite:///$state_path/$sqlite_db', get_engine = db_session.get_engine get_session = db_session.get_session +_DEFAULT_QUOTA_NAME = 'default' + def get_backend(): """The backend is this module itself.""" @@ -499,6 +501,18 @@ def quota_class_get(context, class_name, resource, session=None): return result +def quota_class_get_default(context): + rows = model_query(context, models.QuotaClass, + read_deleted="no").\ + filter_by(class_name=_DEFAULT_QUOTA_NAME).all() + + result = {'class_name': _DEFAULT_QUOTA_NAME} + for row in rows: + result[row.resource] = row.hard_limit + + return result + + @require_context def quota_class_get_all_by_name(context, class_name): authorize_quota_class_context(context, class_name) diff --git a/cinder/quota.py b/cinder/quota.py index 3f9b174ec..9a27df190 100644 --- a/cinder/quota.py +++ b/cinder/quota.py @@ -54,7 +54,10 @@ quota_opts = [ help='number of seconds between subsequent usage refreshes'), cfg.StrOpt('quota_driver', default='cinder.quota.DbQuotaDriver', - help='default driver to use for quota checks'), ] + help='default driver to use for quota checks'), + cfg.BoolOpt('use_default_quota_class', + default='True', + help='whether to use default quota class for default quota'), ] CONF = cfg.CONF CONF.register_opts(quota_opts) @@ -79,14 +82,26 @@ class DbQuotaDriver(object): def get_defaults(self, context, resources): """Given a list of resources, retrieve the default quotas. + Use the class quotas named `_DEFAULT_QUOTA_NAME` as default quotas, + if it exists. :param context: The request context, for access checks. :param resources: A dictionary of the registered resources. """ quotas = {} + default_quotas = {} + if CONF.use_default_quota_class: + default_quotas = db.quota_class_get_default(context) for resource in resources.values(): - quotas[resource.name] = resource.default + if resource.name not in default_quotas: + LOG.deprecated(_("Default quota for resource: %(res)s is set " + "by the default quota flag: quota_%(res)s, " + "it is now deprecated. Please use the " + "the default quota class for default " + "quota.") % {'res': resource.name}) + quotas[resource.name] = default_quotas.get(resource.name, + resource.default) return quotas @@ -154,15 +169,19 @@ class DbQuotaDriver(object): else: class_quotas = {} + default_quotas = self.get_defaults(context, resources) + for resource in resources.values(): # Omit default/quota class values if not defaults and resource.name not in project_quotas: continue quotas[resource.name] = dict( - limit=project_quotas.get(resource.name, - class_quotas.get(resource.name, - resource.default)), ) + limit=project_quotas.get( + resource.name, + class_quotas.get(resource.name, + default_quotas[resource.name])), + ) # Include usages if desired. This is optional because one # internal consumer of this interface wants to access the diff --git a/cinder/tests/test_quota.py b/cinder/tests/test_quota.py index 6639cd378..6a5e1653c 100644 --- a/cinder/tests/test_quota.py +++ b/cinder/tests/test_quota.py @@ -666,6 +666,7 @@ class DbQuotaDriverTestCase(test.TestCase): def test_get_defaults(self): # Use our pre-defined resources + self._stub_quota_class_get_default() result = self.driver.get_defaults(None, quota.QUOTAS._resources) self.assertEqual( @@ -675,6 +676,15 @@ class DbQuotaDriverTestCase(test.TestCase): snapshots=10, gigabytes=1000, )) + def _stub_quota_class_get_default(self): + # Stub out quota_class_get_default + def fake_qcgd(context): + self.calls.append('quota_class_get_default') + return dict(volumes=10, + snapshots=10, + gigabytes=1000,) + self.stubs.Set(db, 'quota_class_get_default', fake_qcgd) + def _stub_quota_class_get_all_by_name(self): # Stub out quota_class_get_all_by_name def fake_qcgabn(context, quota_class): @@ -720,6 +730,7 @@ class DbQuotaDriverTestCase(test.TestCase): 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() def test_get_project_quotas(self): self._stub_get_by_project() @@ -729,7 +740,8 @@ class DbQuotaDriverTestCase(test.TestCase): self.assertEqual(self.calls, ['quota_get_all_by_project', 'quota_usage_get_all_by_project', - 'quota_class_get_all_by_name', ]) + 'quota_class_get_all_by_name', + 'quota_class_get_default', ]) self.assertEqual(result, dict(volumes=dict(limit=10, in_use=2, reserved=0, ), @@ -747,7 +759,8 @@ class DbQuotaDriverTestCase(test.TestCase): quota.QUOTAS._resources, 'test_project') self.assertEqual(self.calls, ['quota_get_all_by_project', - 'quota_usage_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, ), @@ -766,7 +779,8 @@ class DbQuotaDriverTestCase(test.TestCase): self.assertEqual(self.calls, ['quota_get_all_by_project', 'quota_usage_get_all_by_project', - 'quota_class_get_all_by_name', ]) + 'quota_class_get_all_by_name', + 'quota_class_get_default', ]) self.assertEqual(result, dict(volumes=dict(limit=10, in_use=2, reserved=0, ), @@ -785,7 +799,8 @@ class DbQuotaDriverTestCase(test.TestCase): self.assertEqual(self.calls, ['quota_get_all_by_project', 'quota_usage_get_all_by_project', - 'quota_class_get_all_by_name', ]) + 'quota_class_get_all_by_name', + 'quota_class_get_default', ]) self.assertEqual(result, dict(gigabytes=dict(limit=50, in_use=10, @@ -804,7 +819,8 @@ class DbQuotaDriverTestCase(test.TestCase): quota.QUOTAS._resources, 'test_project', usages=False) self.assertEqual(self.calls, ['quota_get_all_by_project', - 'quota_class_get_all_by_name', ]) + 'quota_class_get_all_by_name', + 'quota_class_get_default', ]) self.assertEqual(result, dict(volumes=dict(limit=10, ), snapshots=dict(limit=10, ), gigabytes=dict(limit=50, ), )) diff --git a/etc/cinder/cinder.conf.sample b/etc/cinder/cinder.conf.sample index 871818ca3..ba5bbeea7 100644 --- a/etc/cinder/cinder.conf.sample +++ b/etc/cinder/cinder.conf.sample @@ -210,6 +210,11 @@ # Options defined in cinder.quota # +# If True, uses the default quota class for default quota, the +# quota_* option will be deprecated if the default quota for +# the related resource is set by default quota class (boolean value) +#use_default_quota_class=true + # number of volumes allowed per project (integer value) #quota_volumes=10 -- 2.45.2