From 362e998e89a1f1d9dce3379c4610b205ebeec7c0 Mon Sep 17 00:00:00 2001 From: gseverina Date: Fri, 14 Mar 2014 13:06:45 -0300 Subject: [PATCH] Handling unlimited values when updating quota Changes: - Adding a general approach to handle unlimited values when performing calculations with quota values. - New test that fails without the fix. - Updated test cases to support the fix. Change-Id: I762a8e67261c0e0286307832cd13ad12e6d96504 Closes-Bug: #1292494 --- nova/quota.py | 46 ++++++++++++++++----- nova/tests/test_quota.py | 86 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 116 insertions(+), 16 deletions(-) diff --git a/nova/quota.py b/nova/quota.py index 10ba042cc3..d88a417f12 100644 --- a/nova/quota.py +++ b/nova/quota.py @@ -92,6 +92,8 @@ class DbQuotaDriver(object): quota information. The default driver utilizes the local database. """ + UNLIMITED_VALUE = -1 + def get_by_project_and_user(self, context, project_id, user_id, resource): """Get a specific quota by project and user.""" @@ -273,6 +275,30 @@ class DbQuotaDriver(object): defaults=defaults, usages=project_usages, remains=remains) + def _is_unlimited_value(self, v): + """A helper method to check for unlimited value. + """ + + return v <= self.UNLIMITED_VALUE + + def _sum_quota_values(self, v1, v2): + """A helper method that handles unlimited values when performing + sum operation. + """ + + if self._is_unlimited_value(v1) or self._is_unlimited_value(v2): + return self.UNLIMITED_VALUE + return v1 + v2 + + def _sub_quota_values(self, v1, v2): + """A helper method that handles unlimited values when performing + subtraction operation. + """ + + if self._is_unlimited_value(v1) or self._is_unlimited_value(v2): + return self.UNLIMITED_VALUE + return v1 - v2 + def get_settable_quotas(self, context, resources, project_id, user_id=None): """Given a list of resources, retrieve the range of settable quotas for @@ -283,6 +309,7 @@ class DbQuotaDriver(object): :param project_id: The ID of the project to return quotas for. :param user_id: The ID of the user to return quotas for. """ + settable_quotas = {} db_proj_quotas = db.quota_get_all_by_project(context, project_id) project_quotas = self.get_project_quotas(context, resources, @@ -297,17 +324,18 @@ class DbQuotaDriver(object): project_quotas=db_proj_quotas, user_quotas=setted_quotas) for key, value in user_quotas.items(): - maximum = project_quotas[key]['remains'] +\ - setted_quotas.get(key, 0) - settable_quotas[key] = dict( - minimum=value['in_use'] + value['reserved'], - maximum=maximum - ) + maximum = \ + self._sum_quota_values(project_quotas[key]['remains'], + setted_quotas.get(key, 0)) + minimum = value['in_use'] + value['reserved'] + settable_quotas[key] = {'minimum': minimum, 'maximum': maximum} else: for key, value in project_quotas.items(): - minimum = max(int(value['limit'] - value['remains']), - int(value['in_use'] + value['reserved'])) - settable_quotas[key] = dict(minimum=minimum, maximum=-1) + minimum = \ + max(int(self._sub_quota_values(value['limit'], + value['remains'])), + int(value['in_use'] + value['reserved'])) + settable_quotas[key] = {'minimum': minimum, 'maximum': -1} return settable_quotas def _get_quotas(self, context, resources, keys, has_sync, project_id=None, diff --git a/nova/tests/test_quota.py b/nova/tests/test_quota.py index e9c8ee7170..00eef479b5 100644 --- a/nova/tests/test_quota.py +++ b/nova/tests/test_quota.py @@ -1590,14 +1590,20 @@ class DbQuotaDriverTestCase(test.TestCase): self.calls.append('get_project_quotas') result = {} for k, v in resources.items(): + limit = v.default + reserved = 0 if k == 'instances': remains = v.default - 5 in_use = 1 + elif k == 'cores': + remains = -1 + in_use = 5 + limit = -1 else: remains = v.default in_use = 0 - result[k] = {'limit': v.default, 'in_use': in_use, - 'reserved': 0, 'remains': remains} + result[k] = {'limit': limit, 'in_use': in_use, + 'reserved': reserved, 'remains': remains} return result def fake_get_user_quotas(context, resources, project_id, user_id, @@ -1607,17 +1613,21 @@ class DbQuotaDriverTestCase(test.TestCase): self.calls.append('get_user_quotas') result = {} for k, v in resources.items(): + reserved = 0 if k == 'instances': in_use = 1 + elif k == 'cores': + in_use = 5 + reserved = 10 else: in_use = 0 result[k] = {'limit': v.default, - 'in_use': in_use, 'reserved': 0} + 'in_use': in_use, 'reserved': reserved} return result def fake_qgabpau(context, project_id, user_id): self.calls.append('quota_get_all_by_project_and_user') - return {'instances': 2} + return {'instances': 2, 'cores': -1} self.stubs.Set(self.driver, 'get_project_quotas', fake_get_project_quotas) @@ -1643,8 +1653,8 @@ class DbQuotaDriverTestCase(test.TestCase): 'maximum': 7, }, 'cores': { - 'minimum': 0, - 'maximum': 20, + 'minimum': 15, + 'maximum': -1, }, 'ram': { 'minimum': 0, @@ -1703,7 +1713,7 @@ class DbQuotaDriverTestCase(test.TestCase): 'maximum': -1, }, 'cores': { - 'minimum': 0, + 'minimum': 5, 'maximum': -1, }, 'ram': { @@ -1748,6 +1758,68 @@ class DbQuotaDriverTestCase(test.TestCase): }, }) + def test_get_settable_quotas_by_user_with_unlimited_value(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', + 'quota_get_all_by_project_and_user', + 'get_user_quotas', + ]) + self.assertEqual(result, { + 'instances': { + 'minimum': 1, + 'maximum': 7, + }, + 'cores': { + 'minimum': 15, + 'maximum': -1, + }, + '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 _stub_get_project_quotas(self): def fake_get_project_quotas(context, resources, project_id, quota_class=None, defaults=True,