From b4905b40fd20fd01927ab13420a34a7135f3f565 Mon Sep 17 00:00:00 2001 From: liyingjun Date: Mon, 5 Aug 2013 17:57:15 +0800 Subject: [PATCH] User quota update should not exceed project quota Move the default user quota setting code from db to quota.py, because it is necessary to check if user quota is set in db to get the settable quota for the user under a project. Fixes bug 1208400 Change-Id: I83a4a7fcd8bf80b20e4e700bad7dd31294f8ed0b --- nova/db/sqlalchemy/api.py | 9 -- nova/quota.py | 5 + nova/tests/db/test_db_api.py | 17 +-- nova/tests/test_quota.py | 193 +++++++++++++++++++++++++++++++++-- 4 files changed, 192 insertions(+), 32 deletions(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 36ef817bf8ea..ec5d98ca6f3a 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -2640,16 +2640,7 @@ def quota_get_all_by_project_and_user(context, project_id, user_id): filter_by(user_id=user_id).\ all() - proj_quotas = model_query(context, models.Quota.resource, - models.Quota.hard_limit, - base_model=models.Quota).\ - filter_by(project_id=project_id).\ - all() - result = {'project_id': project_id, 'user_id': user_id} - # Use the project quota for default user quota. - for quota in proj_quotas: - result[quota.resource] = quota.hard_limit for quota in user_quotas: result[quota.resource] = quota.hard_limit diff --git a/nova/quota.py b/nova/quota.py index 615b4142cacc..96eb5444849b 100644 --- a/nova/quota.py +++ b/nova/quota.py @@ -222,6 +222,11 @@ class DbQuotaDriver(object): """ user_quotas = db.quota_get_all_by_project_and_user(context, project_id, user_id) + # Use the project quota for default user quota. + proj_quotas = db.quota_get_all_by_project(context, project_id) + for key, value in proj_quotas.iteritems(): + if key not in user_quotas.keys(): + user_quotas[key] = value user_usages = None if usages: user_usages = db.quota_usage_get_all_by_project_and_user(context, diff --git a/nova/tests/db/test_db_api.py b/nova/tests/db/test_db_api.py index 7a95936c0f6a..d766181863a9 100644 --- a/nova/tests/db/test_db_api.py +++ b/nova/tests/db/test_db_api.py @@ -4764,18 +4764,6 @@ class QuotaTestCase(test.TestCase, ModelsObjectComparatorMixin): 'resource2': 2}) def test_quota_get_all_by_project_and_user(self): - for i in range(3): - for j in range(3): - db.quota_create(self.ctxt, 'proj%d' % i, 'resource%d' % j, j) - for i in range(3): - quotas_db = db.quota_get_all_by_project_and_user(self.ctxt, - 'proj%d' % i, - 'user%d' % i) - self.assertEqual(quotas_db, {'project_id': 'proj%d' % i, - 'user_id': 'user%d' % i, - 'resource0': 0, - 'resource1': 1, - 'resource2': 2}) for i in range(3): for j in range(3): db.quota_create(self.ctxt, 'proj%d' % i, 'resource%d' % j, @@ -4876,10 +4864,7 @@ class QuotaTestCase(test.TestCase, ModelsObjectComparatorMixin): self.assertEqual(db.quota_get_all_by_project_and_user(self.ctxt, 'project1', 'user1'), {'project_id': 'project1', - 'user_id': 'user1', - 'resource0': 0, - 'resource1': 1, - 'resource2': 2}) + 'user_id': 'user1'}) self.assertEqual(db.quota_usage_get_all_by_project_and_user( self.ctxt, 'project1', 'user1'), {'project_id': 'project1', diff --git a/nova/tests/test_quota.py b/nova/tests/test_quota.py index 996c79d2ee5b..c88dbef6ebeb 100644 --- a/nova/tests/test_quota.py +++ b/nova/tests/test_quota.py @@ -864,7 +864,7 @@ class DbQuotaDriverTestCase(test.TestCase): )) def _stub_get_by_project_and_user(self): - def fake_qgabp(context, project_id, user_id): + def fake_qgabpau(context, project_id, user_id): self.calls.append('quota_get_all_by_project_and_user') self.assertEqual(project_id, 'test_project') self.assertEqual(user_id, 'fake_user') @@ -874,7 +874,16 @@ class DbQuotaDriverTestCase(test.TestCase): injected_file_path_bytes=127, ) - def fake_qugabp(context, project_id, user_id): + def fake_qgabp(context, project_id): + self.calls.append('quota_get_all_by_project') + self.assertEqual(project_id, 'test_project') + return { + 'cores': 10, + 'injected_files': 2, + 'injected_file_path_bytes': 127, + } + + def fake_qugabpau(context, project_id, user_id): self.calls.append('quota_usage_get_all_by_project_and_user') self.assertEqual(project_id, 'test_project') self.assertEqual(user_id, 'fake_user') @@ -889,9 +898,10 @@ class DbQuotaDriverTestCase(test.TestCase): injected_file_path_bytes=dict(in_use=0, reserved=0), ) - self.stubs.Set(db, 'quota_get_all_by_project_and_user', fake_qgabp) + self.stubs.Set(db, 'quota_get_all_by_project_and_user', fake_qgabpau) + self.stubs.Set(db, 'quota_get_all_by_project', fake_qgabp) self.stubs.Set(db, 'quota_usage_get_all_by_project_and_user', - fake_qugabp) + fake_qugabpau) self._stub_quota_class_get_all_by_name() @@ -904,6 +914,7 @@ class DbQuotaDriverTestCase(test.TestCase): self.assertEqual(self.calls, [ 'quota_get_all_by_project_and_user', + 'quota_get_all_by_project', 'quota_usage_get_all_by_project_and_user', 'quota_class_get_all_by_name', ]) @@ -1080,11 +1091,12 @@ class DbQuotaDriverTestCase(test.TestCase): self.maxDiff = None self._stub_get_by_project_and_user() result = self.driver.get_user_quotas( - FakeContext('other_project', 'other_class'), + FakeContext('test_project', None), quota.QUOTAS._resources, 'test_project', 'fake_user') self.assertEqual(self.calls, [ 'quota_get_all_by_project_and_user', + 'quota_get_all_by_project', 'quota_usage_get_all_by_project_and_user', ]) self.assertEqual(result, dict( @@ -1103,7 +1115,7 @@ class DbQuotaDriverTestCase(test.TestCase): in_use=10 * 1024, reserved=0, ), - floating_ips=dict( + floating_ips=dict( limit=10, in_use=2, reserved=0, @@ -1229,12 +1241,13 @@ class DbQuotaDriverTestCase(test.TestCase): self.maxDiff = None self._stub_get_by_project_and_user() result = self.driver.get_user_quotas( - FakeContext('other_project', 'other_class'), + FakeContext('test_project', 'test_class'), quota.QUOTAS._resources, 'test_project', 'fake_user', quota_class='test_class') self.assertEqual(self.calls, [ 'quota_get_all_by_project_and_user', + 'quota_get_all_by_project', 'quota_usage_get_all_by_project_and_user', 'quota_class_get_all_by_name', ]) @@ -1386,6 +1399,7 @@ class DbQuotaDriverTestCase(test.TestCase): self.assertEqual(self.calls, [ 'quota_get_all_by_project_and_user', + 'quota_get_all_by_project', 'quota_usage_get_all_by_project_and_user', 'quota_class_get_all_by_name', ]) @@ -1445,6 +1459,7 @@ class DbQuotaDriverTestCase(test.TestCase): self.assertEqual(self.calls, [ 'quota_get_all_by_project_and_user', + 'quota_get_all_by_project', 'quota_class_get_all_by_name', ]) self.assertEqual(result, dict( @@ -1536,6 +1551,170 @@ class DbQuotaDriverTestCase(test.TestCase): ), )) + def _stub_get_settable_quotas(self): + def fake_get_project_quotas(context, resources, project_id, + quota_class=None, defaults=True, + usages=True, remains=False): + self.calls.append('get_project_quotas') + result = {} + for k, v in resources.items(): + if k == 'instances': + remains = v.default - 5 + in_use = 1 + else: + remains = v.default + in_use = 0 + result[k] = {'limit': v.default, 'in_use': in_use, + 'reserved': 0, 'remains': remains} + return result + + def fake_get_user_quotas(context, resources, project_id, user_id, + quota_class=None, defaults=True, + usages=True): + self.calls.append('get_user_quotas') + result = {} + for k, v in resources.items(): + if k == 'instances': + in_use = 1 + else: + in_use = 0 + result[k] = {'limit': v.default, + 'in_use': in_use, 'reserved': 0} + return result + + def fake_qgabpau(context, project_id, user_id): + self.calls.append('quota_get_all_by_project_and_user') + return {'instances': 2} + + self.stubs.Set(self.driver, 'get_project_quotas', + fake_get_project_quotas) + self.stubs.Set(self.driver, 'get_user_quotas', + fake_get_user_quotas) + self.stubs.Set(db, 'quota_get_all_by_project_and_user', + fake_qgabpau) + + def test_get_settable_quotas_with_user(self): + self._stub_get_settable_quotas() + result = self.driver.get_settable_quotas( + FakeContext('test_project', 'test_class'), + quota.QUOTAS._resources, 'test_project', user_id='test_user') + + self.assertEqual(self.calls, [ + 'get_project_quotas', + 'get_user_quotas', + 'quota_get_all_by_project_and_user', + ]) + self.assertEqual(result, { + 'instances': { + 'minimum': 1, + 'maximum': 7, + }, + 'cores': { + 'minimum': 0, + 'maximum': 20, + }, + 'ram': { + 'minimum': 0, + 'maximum': 50 * 1024, + }, + 'floating_ips': { + 'minimum': 0, + 'maximum': 10, + }, + 'fixed_ips': { + 'minimum': 0, + 'maximum': 10, + }, + 'metadata_items': { + 'minimum': 0, + 'maximum': 128, + }, + 'injected_files': { + 'minimum': 0, + 'maximum': 5, + }, + 'injected_file_content_bytes': { + 'minimum': 0, + 'maximum': 10 * 1024, + }, + 'injected_file_path_bytes': { + 'minimum': 0, + 'maximum': 255, + }, + 'security_groups': { + 'minimum': 0, + 'maximum': 10, + }, + 'security_group_rules': { + 'minimum': 0, + 'maximum': 20, + }, + 'key_pairs': { + 'minimum': 0, + 'maximum': 100, + }, + }) + + def test_get_settable_quotas_without_user(self): + self._stub_get_settable_quotas() + result = self.driver.get_settable_quotas( + FakeContext('test_project', 'test_class'), + quota.QUOTAS._resources, 'test_project') + + self.assertEqual(self.calls, [ + 'get_project_quotas', + ]) + self.assertEqual(result, { + 'instances': { + 'minimum': 5, + 'maximum': -1, + }, + 'cores': { + 'minimum': 0, + 'maximum': -1, + }, + 'ram': { + 'minimum': 0, + 'maximum': -1, + }, + 'floating_ips': { + 'minimum': 0, + 'maximum': -1, + }, + 'fixed_ips': { + 'minimum': 0, + 'maximum': -1, + }, + 'metadata_items': { + 'minimum': 0, + 'maximum': -1, + }, + 'injected_files': { + 'minimum': 0, + 'maximum': -1, + }, + 'injected_file_content_bytes': { + 'minimum': 0, + 'maximum': -1, + }, + 'injected_file_path_bytes': { + 'minimum': 0, + 'maximum': -1, + }, + 'security_groups': { + 'minimum': 0, + 'maximum': -1, + }, + 'security_group_rules': { + 'minimum': 0, + 'maximum': -1, + }, + 'key_pairs': { + 'minimum': 0, + 'maximum': -1, + }, + }) + def _stub_get_project_quotas(self): def fake_get_project_quotas(context, resources, project_id, quota_class=None, defaults=True,