diff options
| author | Kevin L. Mitchell <kevin.mitchell@rackspace.com> | 2012-05-04 19:27:43 -0500 |
|---|---|---|
| committer | Kevin L. Mitchell <kevin.mitchell@rackspace.com> | 2012-05-16 08:58:53 -0500 |
| commit | 406ff304bb09f144a59448e0e9d2d01160c7d553 (patch) | |
| tree | fc43d88a568100c552a20cda9b2c6168024ad057 /nova/tests | |
| parent | 823a114727e514f153b500a16c7cad98253300f5 (diff) | |
Rearchitect quota checking to partially fix bug 938317.
This is a rearchitecting/rewriting of quota handling to correct the
quota atomicity issues highlighted by bug 938317. Partially implements
blueprint quota-refactor as well.
This change is fairly substantial. To make it easier to review, it has been
broken up into 3 parts. This is the first part.
Change-Id: I805f5750c08de17487e59fe33fad0bed203188a6
Diffstat (limited to 'nova/tests')
| -rw-r--r-- | nova/tests/test_quota.py | 1634 |
1 files changed, 1628 insertions, 6 deletions
diff --git a/nova/tests/test_quota.py b/nova/tests/test_quota.py index 65ee7471a..88b3f7535 100644 --- a/nova/tests/test_quota.py +++ b/nova/tests/test_quota.py @@ -16,17 +16,22 @@ # License for the specific language governing permissions and limitations # under the License. +import datetime + from nova import compute +from nova.compute import instance_types from nova import context from nova import db +from nova.db.sqlalchemy import api as sqa_api +from nova.db.sqlalchemy import models as sqa_models +from nova import exception from nova import flags from nova import quota -from nova import exception from nova import rpc +from nova.scheduler import driver as scheduler_driver from nova import test +from nova import utils from nova import volume -from nova.compute import instance_types -from nova.scheduler import driver as scheduler_driver FLAGS = flags.FLAGS @@ -258,7 +263,7 @@ class GetQuotaTestCase(test.TestCase): )) -class QuotaTestCase(test.TestCase): +class OldQuotaTestCase(test.TestCase): class StubImageService(object): @@ -266,7 +271,7 @@ class QuotaTestCase(test.TestCase): return {"properties": {}} def setUp(self): - super(QuotaTestCase, self).setUp() + super(OldQuotaTestCase, self).setUp() self.flags(connection_type='fake', quota_instances=2, quota_cores=4, @@ -275,7 +280,9 @@ class QuotaTestCase(test.TestCase): quota_floating_ips=1, network_manager='nova.network.manager.FlatDHCPManager') - self.network = self.network = self.start_service('network') + # Apparently needed by the RPC tests... + self.network = self.start_service('network') + self.user_id = 'admin' self.project_id = 'admin' self.context = context.RequestContext(self.user_id, @@ -677,3 +684,1618 @@ class QuotaTestCase(test.TestCase): db.quota_class_create(self.context, 'foo', 'floating_ips', -1) items = quota.allowed_floating_ips(self.context, 100) self.assertEqual(items, 100) + + +class FakeContext(object): + def __init__(self, project_id, quota_class): + self.is_admin = False + self.user_id = 'fake_user' + self.project_id = project_id + self.quota_class = quota_class + + def elevated(self): + elevated = self.__class__(self.project_id, self.quota_class) + elevated.is_admin = True + return elevated + + +class FakeDriver(object): + def __init__(self, by_project=None, by_class=None, reservations=None): + self.called = [] + self.by_project = by_project or {} + self.by_class = by_class or {} + self.reservations = reservations or [] + + def get_by_project(self, context, project_id, resource): + self.called.append(('get_by_project', context, project_id, resource)) + try: + return self.by_project[project_id][resource] + except KeyError: + raise exception.ProjectQuotaNotFound(project_id=project_id) + + def get_by_class(self, context, quota_class, resource): + self.called.append(('get_by_class', context, quota_class, resource)) + try: + return self.by_class[quota_class][resource] + except KeyError: + raise exception.QuotaClassNotFound(class_name=quota_class) + + def get_defaults(self, context, resources): + self.called.append(('get_defaults', context, resources)) + return resources + + def get_class_quotas(self, context, resources, quota_class, + defaults=True): + self.called.append(('get_class_quotas', context, resources, + quota_class, defaults)) + return resources + + def get_project_quotas(self, context, resources, project_id, + quota_class=None, defaults=True, usages=True): + self.called.append(('get_project_quotas', context, resources, + project_id, quota_class, defaults, usages)) + return resources + + def limit_check(self, context, resources, values): + self.called.append(('limit_check', context, resources, values)) + + def reserve(self, context, resources, deltas, expire=None): + self.called.append(('reserve', context, resources, deltas, expire)) + return self.reservations + + def commit(self, context, reservations): + self.called.append(('commit', context, reservations)) + + def rollback(self, context, reservations): + self.called.append(('rollback', context, reservations)) + + def destroy_all_by_project(self, context, project_id): + self.called.append(('destroy_all_by_project', context, project_id)) + + def expire(self, context): + self.called.append(('expire', context)) + + +class BaseResourceTestCase(test.TestCase): + def test_no_flag(self): + resource = quota.BaseResource('test_resource') + + self.assertEqual(resource.name, 'test_resource') + self.assertEqual(resource.flag, None) + self.assertEqual(resource.default, -1) + + def test_with_flag(self): + # We know this flag exists, so use it... + self.flags(quota_instances=10) + resource = quota.BaseResource('test_resource', 'quota_instances') + + self.assertEqual(resource.name, 'test_resource') + self.assertEqual(resource.flag, 'quota_instances') + self.assertEqual(resource.default, 10) + + def test_with_flag_no_quota(self): + self.flags(quota_instances=-1) + resource = quota.BaseResource('test_resource', 'quota_instances') + + self.assertEqual(resource.name, 'test_resource') + self.assertEqual(resource.flag, 'quota_instances') + self.assertEqual(resource.default, -1) + + def test_quota_no_project_no_class(self): + self.flags(quota_instances=10) + resource = quota.BaseResource('test_resource', 'quota_instances') + driver = FakeDriver() + context = FakeContext(None, None) + quota_value = resource.quota(driver, context) + + self.assertEqual(quota_value, 10) + + def test_quota_with_project_no_class(self): + self.flags(quota_instances=10) + resource = quota.BaseResource('test_resource', 'quota_instances') + driver = FakeDriver(by_project=dict( + test_project=dict(test_resource=15), + )) + context = FakeContext('test_project', None) + quota_value = resource.quota(driver, context) + + self.assertEqual(quota_value, 15) + + def test_quota_no_project_with_class(self): + self.flags(quota_instances=10) + resource = quota.BaseResource('test_resource', 'quota_instances') + driver = FakeDriver(by_class=dict( + test_class=dict(test_resource=20), + )) + context = FakeContext(None, 'test_class') + quota_value = resource.quota(driver, context) + + self.assertEqual(quota_value, 20) + + def test_quota_with_project_with_class(self): + self.flags(quota_instances=10) + resource = quota.BaseResource('test_resource', 'quota_instances') + driver = FakeDriver(by_project=dict( + test_project=dict(test_resource=15), + ), + by_class=dict( + test_class=dict(test_resource=20), + )) + context = FakeContext('test_project', 'test_class') + quota_value = resource.quota(driver, context) + + self.assertEqual(quota_value, 15) + + def test_quota_override_project_with_class(self): + self.flags(quota_instances=10) + resource = quota.BaseResource('test_resource', 'quota_instances') + driver = FakeDriver(by_project=dict( + test_project=dict(test_resource=15), + override_project=dict(test_resource=20), + )) + context = FakeContext('test_project', 'test_class') + quota_value = resource.quota(driver, context, + project_id='override_project') + + self.assertEqual(quota_value, 20) + + def test_quota_with_project_override_class(self): + self.flags(quota_instances=10) + resource = quota.BaseResource('test_resource', 'quota_instances') + driver = FakeDriver(by_class=dict( + test_class=dict(test_resource=15), + override_class=dict(test_resource=20), + )) + context = FakeContext('test_project', 'test_class') + quota_value = resource.quota(driver, context, + quota_class='override_class') + + self.assertEqual(quota_value, 20) + + +class QuotaEngineTestCase(test.TestCase): + def test_init(self): + quota_obj = quota.QuotaEngine() + + self.assertEqual(quota_obj._resources, {}) + self.assertTrue(isinstance(quota_obj._driver, quota.DbQuotaDriver)) + + def test_init_override_string(self): + quota_obj = quota.QuotaEngine( + quota_driver_class='nova.tests.test_quota.FakeDriver') + + self.assertEqual(quota_obj._resources, {}) + self.assertTrue(isinstance(quota_obj._driver, FakeDriver)) + + def test_init_override_obj(self): + quota_obj = quota.QuotaEngine(quota_driver_class=FakeDriver) + + self.assertEqual(quota_obj._resources, {}) + self.assertEqual(quota_obj._driver, FakeDriver) + + def test_register_resource(self): + quota_obj = quota.QuotaEngine() + resource = quota.AbsoluteResource('test_resource') + quota_obj.register_resource(resource) + + self.assertEqual(quota_obj._resources, dict(test_resource=resource)) + + def test_register_resources(self): + quota_obj = quota.QuotaEngine() + resources = [ + quota.AbsoluteResource('test_resource1'), + quota.AbsoluteResource('test_resource2'), + quota.AbsoluteResource('test_resource3'), + ] + quota_obj.register_resources(resources) + + self.assertEqual(quota_obj._resources, dict( + test_resource1=resources[0], + test_resource2=resources[1], + test_resource3=resources[2], + )) + + def test_sync_predeclared(self): + quota_obj = quota.QuotaEngine() + + def spam(*args, **kwargs): + pass + + resource = quota.ReservableResource('test_resource', spam) + quota_obj.register_resource(resource) + + self.assertEqual(resource.sync, spam) + + def test_sync_multi(self): + quota_obj = quota.QuotaEngine() + + def spam(*args, **kwargs): + pass + + resources = [ + quota.ReservableResource('test_resource1', spam), + quota.ReservableResource('test_resource2', spam), + quota.ReservableResource('test_resource3', spam), + quota.ReservableResource('test_resource4', spam), + ] + quota_obj.register_resources(resources[:2]) + + self.assertEqual(resources[0].sync, spam) + self.assertEqual(resources[1].sync, spam) + self.assertEqual(resources[2].sync, spam) + self.assertEqual(resources[3].sync, spam) + + def test_get_by_project(self): + context = FakeContext('test_project', 'test_class') + driver = FakeDriver(by_project=dict( + test_project=dict(test_resource=42))) + quota_obj = quota.QuotaEngine(quota_driver_class=driver) + result = quota_obj.get_by_project(context, 'test_project', + 'test_resource') + + self.assertEqual(driver.called, [ + ('get_by_project', context, 'test_project', 'test_resource'), + ]) + self.assertEqual(result, 42) + + def test_get_by_class(self): + context = FakeContext('test_project', 'test_class') + driver = FakeDriver(by_class=dict( + test_class=dict(test_resource=42))) + quota_obj = quota.QuotaEngine(quota_driver_class=driver) + result = quota_obj.get_by_class(context, 'test_class', 'test_resource') + + self.assertEqual(driver.called, [ + ('get_by_class', context, 'test_class', 'test_resource'), + ]) + self.assertEqual(result, 42) + + def _make_quota_obj(self, driver): + quota_obj = quota.QuotaEngine(quota_driver_class=driver) + resources = [ + quota.AbsoluteResource('test_resource4'), + quota.AbsoluteResource('test_resource3'), + quota.AbsoluteResource('test_resource2'), + quota.AbsoluteResource('test_resource1'), + ] + quota_obj.register_resources(resources) + + return quota_obj + + def test_get_defaults(self): + context = FakeContext(None, 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), + ]) + self.assertEqual(result, quota_obj._resources) + + def test_get_class_quotas(self): + context = FakeContext(None, None) + driver = FakeDriver() + quota_obj = self._make_quota_obj(driver) + result1 = quota_obj.get_class_quotas(context, 'test_class') + result2 = quota_obj.get_class_quotas(context, 'test_class', False) + + self.assertEqual(driver.called, [ + ('get_class_quotas', context, quota_obj._resources, + 'test_class', True), + ('get_class_quotas', context, quota_obj._resources, + 'test_class', False), + ]) + self.assertEqual(result1, quota_obj._resources) + self.assertEqual(result2, quota_obj._resources) + + def test_get_project_quotas(self): + context = FakeContext(None, None) + driver = FakeDriver() + 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', + quota_class='test_class', + defaults=False, + usages=False) + + self.assertEqual(driver.called, [ + ('get_project_quotas', context, quota_obj._resources, + 'test_project', None, True, True), + ('get_project_quotas', context, quota_obj._resources, + 'test_project', 'test_class', False, False), + ]) + self.assertEqual(result1, quota_obj._resources) + self.assertEqual(result2, quota_obj._resources) + + def test_count_no_resource(self): + context = FakeContext(None, None) + driver = FakeDriver() + quota_obj = self._make_quota_obj(driver) + self.assertRaises(exception.QuotaResourceUnknown, + quota_obj.count, context, 'test_resource5', + True, foo='bar') + + def test_count_wrong_resource(self): + context = FakeContext(None, None) + driver = FakeDriver() + quota_obj = self._make_quota_obj(driver) + self.assertRaises(exception.QuotaResourceUnknown, + quota_obj.count, context, 'test_resource1', + True, foo='bar') + + def test_count(self): + def fake_count(context, *args, **kwargs): + self.assertEqual(args, (True,)) + self.assertEqual(kwargs, dict(foo='bar')) + return 5 + + context = FakeContext(None, None) + driver = FakeDriver() + quota_obj = self._make_quota_obj(driver) + quota_obj.register_resource(quota.CountableResource('test_resource5', + fake_count)) + result = quota_obj.count(context, 'test_resource5', True, foo='bar') + + self.assertEqual(result, 5) + + def test_limit_check(self): + context = FakeContext(None, None) + driver = FakeDriver() + quota_obj = self._make_quota_obj(driver) + quota_obj.limit_check(context, test_resource1=4, test_resource2=3, + test_resource3=2, test_resource4=1) + + self.assertEqual(driver.called, [ + ('limit_check', context, quota_obj._resources, dict( + test_resource1=4, + test_resource2=3, + test_resource3=2, + test_resource4=1, + )), + ]) + + def test_reserve(self): + context = FakeContext(None, None) + driver = FakeDriver(reservations=[ + 'resv-01', 'resv-02', 'resv-03', 'resv-04', + ]) + quota_obj = self._make_quota_obj(driver) + result1 = quota_obj.reserve(context, test_resource1=4, + test_resource2=3, test_resource3=2, + test_resource4=1) + result2 = quota_obj.reserve(context, expire=3600, + test_resource1=1, test_resource2=2, + test_resource3=3, test_resource4=4) + + self.assertEqual(driver.called, [ + ('reserve', context, quota_obj._resources, dict( + test_resource1=4, + test_resource2=3, + test_resource3=2, + test_resource4=1, + ), None), + ('reserve', context, quota_obj._resources, dict( + test_resource1=1, + test_resource2=2, + test_resource3=3, + test_resource4=4, + ), 3600), + ]) + self.assertEqual(result1, [ + 'resv-01', 'resv-02', 'resv-03', 'resv-04', + ]) + self.assertEqual(result2, [ + 'resv-01', 'resv-02', 'resv-03', 'resv-04', + ]) + + def test_commit(self): + context = FakeContext(None, None) + driver = FakeDriver() + quota_obj = self._make_quota_obj(driver) + quota_obj.commit(context, ['resv-01', 'resv-02', 'resv-03']) + + self.assertEqual(driver.called, [ + ('commit', context, ['resv-01', 'resv-02', 'resv-03']), + ]) + + def test_rollback(self): + context = FakeContext(None, None) + driver = FakeDriver() + quota_obj = self._make_quota_obj(driver) + quota_obj.rollback(context, ['resv-01', 'resv-02', 'resv-03']) + + self.assertEqual(driver.called, [ + ('rollback', context, ['resv-01', 'resv-02', 'resv-03']), + ]) + + def test_destroy_all_by_project(self): + context = FakeContext(None, None) + driver = FakeDriver() + quota_obj = self._make_quota_obj(driver) + quota_obj.destroy_all_by_project(context, 'test_project') + + self.assertEqual(driver.called, [ + ('destroy_all_by_project', context, 'test_project'), + ]) + + def test_expire(self): + context = FakeContext(None, None) + driver = FakeDriver() + quota_obj = self._make_quota_obj(driver) + quota_obj.expire(context) + + self.assertEqual(driver.called, [ + ('expire', context), + ]) + + def test_resources(self): + quota_obj = self._make_quota_obj(None) + + self.assertEqual(quota_obj.resources, + ['test_resource1', 'test_resource2', + 'test_resource3', 'test_resource4']) + + +class DbQuotaDriverTestCase(test.TestCase): + def setUp(self): + super(DbQuotaDriverTestCase, self).setUp() + + self.flags(quota_instances=10, + quota_cores=20, + quota_ram=50 * 1024, + quota_volumes=10, + quota_gigabytes=1000, + quota_floating_ips=10, + quota_metadata_items=128, + quota_injected_files=5, + quota_injected_file_content_bytes=10 * 1024, + quota_injected_file_path_bytes=255, + quota_security_groups=10, + quota_security_group_rules=20, + reservation_expire=86400, + until_refresh=0, + max_age=0, + ) + + self.driver = quota.DbQuotaDriver() + + self.calls = [] + + utils.set_time_override() + + def tearDown(self): + utils.clear_time_override() + super(DbQuotaDriverTestCase, self).tearDown() + + def test_get_defaults(self): + # Use our pre-defined resources + result = self.driver.get_defaults(None, quota.QUOTAS._resources) + + self.assertEqual(result, dict( + instances=10, + cores=20, + ram=50 * 1024, + volumes=10, + gigabytes=1000, + floating_ips=10, + metadata_items=128, + injected_files=5, + injected_file_content_bytes=10 * 1024, + injected_file_path_bytes=255, + security_groups=10, + security_group_rules=20, + key_pairs=100, + )) + + def _stub_quota_class_get_all_by_name(self): + # Stub out quota_class_get_all_by_name + def fake_qcgabn(context, quota_class): + self.calls.append('quota_class_get_all_by_name') + self.assertEqual(quota_class, 'test_class') + return dict( + instances=5, + ram=25 * 1024, + gigabytes=500, + metadata_items=64, + injected_file_content_bytes=5 * 1024, + ) + self.stubs.Set(db, 'quota_class_get_all_by_name', fake_qcgabn) + + def test_get_class_quotas(self): + self._stub_quota_class_get_all_by_name() + result = self.driver.get_class_quotas(None, quota.QUOTAS._resources, + 'test_class') + + self.assertEqual(self.calls, ['quota_class_get_all_by_name']) + self.assertEqual(result, dict( + instances=5, + cores=20, + ram=25 * 1024, + volumes=10, + gigabytes=500, + floating_ips=10, + metadata_items=64, + injected_files=5, + injected_file_content_bytes=5 * 1024, + injected_file_path_bytes=255, + security_groups=10, + security_group_rules=20, + key_pairs=100, + )) + + def test_get_class_quotas_no_defaults(self): + self._stub_quota_class_get_all_by_name() + result = self.driver.get_class_quotas(None, quota.QUOTAS._resources, + 'test_class', False) + + self.assertEqual(self.calls, ['quota_class_get_all_by_name']) + self.assertEqual(result, dict( + instances=5, + ram=25 * 1024, + gigabytes=500, + metadata_items=64, + injected_file_content_bytes=5 * 1024, + )) + + def _stub_get_by_project(self): + def fake_qgabp(context, project_id): + self.calls.append('quota_get_all_by_project') + self.assertEqual(project_id, 'test_project') + return dict( + cores=10, + gigabytes=50, + injected_files=2, + injected_file_path_bytes=127, + ) + + def fake_qugabp(context, project_id): + self.calls.append('quota_usage_get_all_by_project') + self.assertEqual(project_id, 'test_project') + return dict( + instances=dict(in_use=2, reserved=2), + cores=dict(in_use=4, reserved=4), + ram=dict(in_use=10 * 1024, reserved=0), + volumes=dict(in_use=2, reserved=0), + gigabytes=dict(in_use=10, reserved=0), + floating_ips=dict(in_use=2, reserved=0), + metadata_items=dict(in_use=0, reserved=0), + injected_files=dict(in_use=0, reserved=0), + injected_file_content_bytes=dict(in_use=0, reserved=0), + injected_file_path_bytes=dict(in_use=0, 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() + + def test_get_project_quotas(self): + self._stub_get_by_project() + result = self.driver.get_project_quotas( + FakeContext('test_project', 'test_class'), + quota.QUOTAS._resources, 'test_project') + + self.assertEqual(self.calls, [ + 'quota_get_all_by_project', + 'quota_usage_get_all_by_project', + 'quota_class_get_all_by_name', + ]) + self.assertEqual(result, dict( + instances=dict( + limit=5, + in_use=2, + reserved=2, + ), + cores=dict( + limit=10, + in_use=4, + reserved=4, + ), + ram=dict( + limit=25 * 1024, + in_use=10 * 1024, + reserved=0, + ), + volumes=dict( + limit=10, + in_use=2, + reserved=0, + ), + gigabytes=dict( + limit=50, + in_use=10, + reserved=0, + ), + floating_ips=dict( + limit=10, + in_use=2, + reserved=0, + ), + metadata_items=dict( + limit=64, + in_use=0, + reserved=0, + ), + injected_files=dict( + limit=2, + in_use=0, + reserved=0, + ), + injected_file_content_bytes=dict( + limit=5 * 1024, + in_use=0, + reserved=0, + ), + injected_file_path_bytes=dict( + limit=127, + in_use=0, + reserved=0, + ), + security_groups=dict( + limit=10, + in_use=0, + reserved=0, + ), + security_group_rules=dict( + limit=20, + in_use=0, + reserved=0, + ), + key_pairs=dict( + limit=100, + in_use=0, + reserved=0, + ), + )) + + def test_get_project_quotas_alt_context_no_class(self): + self._stub_get_by_project() + result = self.driver.get_project_quotas( + FakeContext('other_project', 'other_class'), + quota.QUOTAS._resources, 'test_project') + + self.assertEqual(self.calls, [ + 'quota_get_all_by_project', + 'quota_usage_get_all_by_project', + ]) + self.assertEqual(result, dict( + instances=dict( + limit=10, + in_use=2, + reserved=2, + ), + cores=dict( + limit=10, + in_use=4, + reserved=4, + ), + ram=dict( + limit=50 * 1024, + in_use=10 * 1024, + reserved=0, + ), + volumes=dict( + limit=10, + in_use=2, + reserved=0, + ), + gigabytes=dict( + limit=50, + in_use=10, + reserved=0, + ), + floating_ips=dict( + limit=10, + in_use=2, + reserved=0, + ), + metadata_items=dict( + limit=128, + in_use=0, + reserved=0, + ), + injected_files=dict( + limit=2, + in_use=0, + reserved=0, + ), + injected_file_content_bytes=dict( + limit=10 * 1024, + in_use=0, + reserved=0, + ), + injected_file_path_bytes=dict( + limit=127, + in_use=0, + reserved=0, + ), + security_groups=dict( + limit=10, + in_use=0, + reserved=0, + ), + security_group_rules=dict( + limit=20, + in_use=0, + reserved=0, + ), + key_pairs=dict( + limit=100, + in_use=0, + reserved=0, + ), + )) + + def test_get_project_quotas_alt_context_with_class(self): + self._stub_get_by_project() + result = self.driver.get_project_quotas( + FakeContext('other_project', 'other_class'), + quota.QUOTAS._resources, 'test_project', quota_class='test_class') + + self.assertEqual(self.calls, [ + 'quota_get_all_by_project', + 'quota_usage_get_all_by_project', + 'quota_class_get_all_by_name', + ]) + self.assertEqual(result, dict( + instances=dict( + limit=5, + in_use=2, + reserved=2, + ), + cores=dict( + limit=10, + in_use=4, + reserved=4, + ), + ram=dict( + limit=25 * 1024, + in_use=10 * 1024, + reserved=0, + ), + volumes=dict( + limit=10, + in_use=2, + reserved=0, + ), + gigabytes=dict( + limit=50, + in_use=10, + reserved=0, + ), + floating_ips=dict( + limit=10, + in_use=2, + reserved=0, + ), + metadata_items=dict( + limit=64, + in_use=0, + reserved=0, + ), + injected_files=dict( + limit=2, + in_use=0, + reserved=0, + ), + injected_file_content_bytes=dict( + limit=5 * 1024, + in_use=0, + reserved=0, + ), + injected_file_path_bytes=dict( + limit=127, + in_use=0, + reserved=0, + ), + security_groups=dict( + limit=10, + in_use=0, + reserved=0, + ), + security_group_rules=dict( + limit=20, + in_use=0, + reserved=0, + ), + key_pairs=dict( + limit=100, + in_use=0, + reserved=0, + ), + )) + + def test_get_project_quotas_no_defaults(self): + self._stub_get_by_project() + result = self.driver.get_project_quotas( + FakeContext('test_project', 'test_class'), + quota.QUOTAS._resources, 'test_project', defaults=False) + + self.assertEqual(self.calls, [ + 'quota_get_all_by_project', + 'quota_usage_get_all_by_project', + 'quota_class_get_all_by_name', + ]) + self.assertEqual(result, dict( + cores=dict( + limit=10, + in_use=4, + reserved=4, + ), + gigabytes=dict( + limit=50, + in_use=10, + reserved=0, + ), + injected_files=dict( + limit=2, + in_use=0, + reserved=0, + ), + injected_file_path_bytes=dict( + limit=127, + in_use=0, + reserved=0, + ), + )) + + def test_get_project_quotas_no_usages(self): + self._stub_get_by_project() + result = self.driver.get_project_quotas( + FakeContext('test_project', 'test_class'), + quota.QUOTAS._resources, 'test_project', usages=False) + + self.assertEqual(self.calls, [ + 'quota_get_all_by_project', + 'quota_class_get_all_by_name', + ]) + self.assertEqual(result, dict( + instances=dict( + limit=5, + ), + cores=dict( + limit=10, + ), + ram=dict( + limit=25 * 1024, + ), + volumes=dict( + limit=10, + ), + gigabytes=dict( + limit=50, + ), + floating_ips=dict( + limit=10, + ), + metadata_items=dict( + limit=64, + ), + injected_files=dict( + limit=2, + ), + injected_file_content_bytes=dict( + limit=5 * 1024, + ), + injected_file_path_bytes=dict( + limit=127, + ), + security_groups=dict( + limit=10, + ), + security_group_rules=dict( + limit=20, + ), + key_pairs=dict( + limit=100, + ), + )) + + def _stub_get_project_quotas(self): + def fake_get_project_quotas(context, resources, project_id, + quota_class=None, defaults=True, + usages=True): + self.calls.append('get_project_quotas') + return dict((k, dict(limit=v.default)) + for k, v in resources.items()) + + self.stubs.Set(self.driver, 'get_project_quotas', + fake_get_project_quotas) + + def test_get_quotas_has_sync_unknown(self): + self._stub_get_project_quotas() + self.assertRaises(exception.QuotaResourceUnknown, + self.driver._get_quotas, + None, quota.QUOTAS._resources, + ['unknown'], True) + self.assertEqual(self.calls, []) + + def test_get_quotas_no_sync_unknown(self): + self._stub_get_project_quotas() + self.assertRaises(exception.QuotaResourceUnknown, + self.driver._get_quotas, + None, quota.QUOTAS._resources, + ['unknown'], False) + self.assertEqual(self.calls, []) + + def test_get_quotas_has_sync_no_sync_resource(self): + self._stub_get_project_quotas() + self.assertRaises(exception.QuotaResourceUnknown, + self.driver._get_quotas, + None, quota.QUOTAS._resources, + ['metadata_items'], True) + self.assertEqual(self.calls, []) + + def test_get_quotas_no_sync_has_sync_resource(self): + self._stub_get_project_quotas() + self.assertRaises(exception.QuotaResourceUnknown, + self.driver._get_quotas, + None, quota.QUOTAS._resources, + ['instances'], False) + self.assertEqual(self.calls, []) + + def test_get_quotas_has_sync(self): + self._stub_get_project_quotas() + result = self.driver._get_quotas(FakeContext('test_project', + 'test_class'), + quota.QUOTAS._resources, + ['instances', 'cores', 'ram', + 'volumes', 'gigabytes', + 'floating_ips', 'security_groups'], + True) + + self.assertEqual(self.calls, ['get_project_quotas']) + self.assertEqual(result, dict( + instances=10, + cores=20, + ram=50 * 1024, + volumes=10, + gigabytes=1000, + floating_ips=10, + security_groups=10, + )) + + def test_get_quotas_no_sync(self): + self._stub_get_project_quotas() + result = self.driver._get_quotas(FakeContext('test_project', + 'test_class'), + quota.QUOTAS._resources, + ['metadata_items', 'injected_files', + 'injected_file_content_bytes', + 'injected_file_path_bytes', + 'security_group_rules'], False) + + self.assertEqual(self.calls, ['get_project_quotas']) + self.assertEqual(result, dict( + metadata_items=128, + injected_files=5, + injected_file_content_bytes=10 * 1024, + injected_file_path_bytes=255, + security_group_rules=20, + )) + + def test_limit_check_under(self): + self._stub_get_project_quotas() + self.assertRaises(exception.InvalidQuotaValue, + self.driver.limit_check, + FakeContext('test_project', 'test_class'), + quota.QUOTAS._resources, + dict(metadata_items=-1)) + + def test_limit_check_over(self): + self._stub_get_project_quotas() + self.assertRaises(exception.OverQuota, + self.driver.limit_check, + FakeContext('test_project', 'test_class'), + quota.QUOTAS._resources, + dict(metadata_items=129)) + + def test_limit_check_unlimited(self): + self.flags(quota_metadata_items=-1) + self._stub_get_project_quotas() + self.driver.limit_check(FakeContext('test_project', 'test_class'), + quota.QUOTAS._resources, + dict(metadata_items=32767)) + + def test_limit_check(self): + self._stub_get_project_quotas() + self.driver.limit_check(FakeContext('test_project', 'test_class'), + quota.QUOTAS._resources, + dict(metadata_items=128)) + + def _stub_quota_reserve(self): + def fake_quota_reserve(context, resources, quotas, deltas, expire, + until_refresh, max_age): + self.calls.append(('quota_reserve', expire, until_refresh, + max_age)) + return ['resv-1', 'resv-2', 'resv-3'] + self.stubs.Set(db, 'quota_reserve', fake_quota_reserve) + + def test_reserve_bad_expire(self): + self._stub_get_project_quotas() + self._stub_quota_reserve() + self.assertRaises(exception.InvalidReservationExpiration, + self.driver.reserve, + FakeContext('test_project', 'test_class'), + quota.QUOTAS._resources, + dict(instances=2), expire='invalid') + self.assertEqual(self.calls, []) + + def test_reserve_default_expire(self): + self._stub_get_project_quotas() + self._stub_quota_reserve() + result = self.driver.reserve(FakeContext('test_project', 'test_class'), + quota.QUOTAS._resources, + dict(instances=2)) + + expire = utils.utcnow() + datetime.timedelta(seconds=86400) + self.assertEqual(self.calls, [ + 'get_project_quotas', + ('quota_reserve', expire, 0, 0), + ]) + self.assertEqual(result, ['resv-1', 'resv-2', 'resv-3']) + + def test_reserve_int_expire(self): + self._stub_get_project_quotas() + self._stub_quota_reserve() + result = self.driver.reserve(FakeContext('test_project', 'test_class'), + quota.QUOTAS._resources, + dict(instances=2), expire=3600) + + expire = utils.utcnow() + datetime.timedelta(seconds=3600) + self.assertEqual(self.calls, [ + 'get_project_quotas', + ('quota_reserve', expire, 0, 0), + ]) + self.assertEqual(result, ['resv-1', 'resv-2', 'resv-3']) + + def test_reserve_timedelta_expire(self): + self._stub_get_project_quotas() + self._stub_quota_reserve() + expire_delta = datetime.timedelta(seconds=60) + result = self.driver.reserve(FakeContext('test_project', 'test_class'), + quota.QUOTAS._resources, + dict(instances=2), expire=expire_delta) + + expire = utils.utcnow() + expire_delta + self.assertEqual(self.calls, [ + 'get_project_quotas', + ('quota_reserve', expire, 0, 0), + ]) + self.assertEqual(result, ['resv-1', 'resv-2', 'resv-3']) + + def test_reserve_datetime_expire(self): + self._stub_get_project_quotas() + self._stub_quota_reserve() + expire = utils.utcnow() + datetime.timedelta(seconds=120) + result = self.driver.reserve(FakeContext('test_project', 'test_class'), + quota.QUOTAS._resources, + dict(instances=2), expire=expire) + + self.assertEqual(self.calls, [ + 'get_project_quotas', + ('quota_reserve', expire, 0, 0), + ]) + self.assertEqual(result, ['resv-1', 'resv-2', 'resv-3']) + + def test_reserve_until_refresh(self): + self._stub_get_project_quotas() + self._stub_quota_reserve() + self.flags(until_refresh=500) + expire = utils.utcnow() + datetime.timedelta(seconds=120) + result = self.driver.reserve(FakeContext('test_project', 'test_class'), + quota.QUOTAS._resources, + dict(instances=2), expire=expire) + + self.assertEqual(self.calls, [ + 'get_project_quotas', + ('quota_reserve', expire, 500, 0), + ]) + self.assertEqual(result, ['resv-1', 'resv-2', 'resv-3']) + + def test_reserve_max_age(self): + self._stub_get_project_quotas() + self._stub_quota_reserve() + self.flags(max_age=86400) + expire = utils.utcnow() + datetime.timedelta(seconds=120) + result = self.driver.reserve(FakeContext('test_project', 'test_class'), + quota.QUOTAS._resources, + dict(instances=2), expire=expire) + + self.assertEqual(self.calls, [ + 'get_project_quotas', + ('quota_reserve', expire, 0, 86400), + ]) + self.assertEqual(result, ['resv-1', 'resv-2', 'resv-3']) + + +class FakeSession(object): + def begin(self): + return self + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, exc_traceback): + return False + + +class FakeUsage(sqa_models.QuotaUsage): + def save(self, *args, **kwargs): + pass + + +class QuotaReserveSqlAlchemyTestCase(test.TestCase): + # nova.db.sqlalchemy.api.quota_reserve is so complex it needs its + # own test case, and since it's a quota manipulator, this is the + # best place to put it... + + def setUp(self): + super(QuotaReserveSqlAlchemyTestCase, self).setUp() + + self.sync_called = set() + + def make_sync(res_name): + def sync(context, project_id, session): + self.sync_called.add(res_name) + if res_name in self.usages: + return {res_name: self.usages[res_name].in_use - 1} + return {res_name: 0} + return sync + + self.resources = {} + for res_name in ('instances', 'cores', 'ram'): + res = quota.ReservableResource(res_name, make_sync(res_name)) + self.resources[res_name] = res + + self.expire = utils.utcnow() + datetime.timedelta(seconds=3600) + + self.usages = {} + self.usages_created = {} + self.reservations_created = {} + + def fake_get_session(): + return FakeSession() + + def fake_get_quota_usages(context, session, keys): + return self.usages.copy() + + def fake_quota_usage_create(context, project_id, resource, in_use, + reserved, until_refresh, session=None, + save=True): + quota_usage_ref = self._make_quota_usage( + project_id, resource, in_use, reserved, until_refresh, + utils.utcnow(), utils.utcnow()) + + self.usages_created[resource] = quota_usage_ref + + return quota_usage_ref + + def fake_reservation_create(context, uuid, usage_id, project_id, + resource, delta, expire, session=None): + reservation_ref = self._make_reservation( + uuid, usage_id, project_id, resource, delta, expire, + utils.utcnow(), utils.utcnow()) + + self.reservations_created[resource] = reservation_ref + + return reservation_ref + + self.stubs.Set(sqa_api, 'get_session', fake_get_session) + self.stubs.Set(sqa_api, '_get_quota_usages', fake_get_quota_usages) + self.stubs.Set(sqa_api, 'quota_usage_create', fake_quota_usage_create) + self.stubs.Set(sqa_api, 'reservation_create', fake_reservation_create) + + utils.set_time_override() + + def _make_quota_usage(self, project_id, resource, in_use, reserved, + until_refresh, created_at, updated_at): + quota_usage_ref = FakeUsage() + quota_usage_ref.id = len(self.usages) + len(self.usages_created) + quota_usage_ref.project_id = project_id + quota_usage_ref.resource = resource + quota_usage_ref.in_use = in_use + quota_usage_ref.reserved = reserved + quota_usage_ref.until_refresh = until_refresh + quota_usage_ref.created_at = created_at + quota_usage_ref.updated_at = updated_at + quota_usage_ref.deleted_at = None + quota_usage_ref.deleted = False + + return quota_usage_ref + + def init_usage(self, project_id, resource, in_use, reserved, + until_refresh=None, created_at=None, updated_at=None): + if created_at is None: + created_at = utils.utcnow() + if updated_at is None: + updated_at = utils.utcnow() + + quota_usage_ref = self._make_quota_usage(project_id, resource, in_use, + reserved, until_refresh, + created_at, updated_at) + + self.usages[resource] = quota_usage_ref + + def compare_usage(self, usage_dict, expected): + for usage in expected: + resource = usage['resource'] + for key, value in usage.items(): + actual = getattr(usage_dict[resource], key) + self.assertEqual(actual, value, + "%s != %s on usage for resource %s" % + (actual, value, resource)) + + def _make_reservation(self, uuid, usage_id, project_id, resource, + delta, expire, created_at, updated_at): + reservation_ref = sqa_models.Reservation() + reservation_ref.id = len(self.reservations_created) + reservation_ref.uuid = uuid + reservation_ref.usage_id = usage_id + reservation_ref.project_id = project_id + reservation_ref.resource = resource + reservation_ref.delta = delta + reservation_ref.expire = expire + reservation_ref.created_at = created_at + reservation_ref.updated_at = updated_at + reservation_ref.deleted_at = None + reservation_ref.deleted = False + + return reservation_ref + + def compare_reservation(self, reservations, expected): + reservations = set(reservations) + for resv in expected: + resource = resv['resource'] + resv_obj = self.reservations_created[resource] + + self.assertIn(resv_obj.uuid, reservations) + reservations.discard(resv_obj.uuid) + + for key, value in resv.items(): + actual = getattr(resv_obj, key) + self.assertEqual(actual, value, + "%s != %s on reservation for resource %s" % + (actual, value, resource)) + + self.assertEqual(len(reservations), 0) + + def test_quota_reserve_create_usages(self): + context = FakeContext('test_project', 'test_class') + quotas = dict( + instances=5, + cores=10, + ram=10 * 1024, + ) + deltas = dict( + instances=2, + cores=4, + ram=2 * 1024, + ) + result = sqa_api.quota_reserve(context, self.resources, quotas, + deltas, self.expire, 0, 0) + + self.assertEqual(self.sync_called, set(['instances', 'cores', 'ram'])) + self.compare_usage(self.usages_created, [ + dict(resource='instances', + project_id='test_project', + in_use=0, + reserved=2, + until_refresh=None), + dict(resource='cores', + project_id='test_project', + in_use=0, + reserved=4, + until_refresh=None), + dict(resource='ram', + project_id='test_project', + in_use=0, + reserved=2 * 1024, + until_refresh=None), + ]) + self.compare_reservation(result, [ + dict(resource='instances', + usage_id=self.usages_created['instances'], + project_id='test_project', + delta=2), + dict(resource='cores', + usage_id=self.usages_created['cores'], + project_id='test_project', + delta=4), + dict(resource='ram', + usage_id=self.usages_created['ram'], + delta=2 * 1024), + ]) + + def test_quota_reserve_until_refresh(self): + self.init_usage('test_project', 'instances', 3, 0, until_refresh=1) + self.init_usage('test_project', 'cores', 3, 0, until_refresh=1) + self.init_usage('test_project', 'ram', 3, 0, until_refresh=1) + context = FakeContext('test_project', 'test_class') + quotas = dict( + instances=5, + cores=10, + ram=10 * 1024, + ) + deltas = dict( + instances=2, + cores=4, + ram=2 * 1024, + ) + result = sqa_api.quota_reserve(context, self.resources, quotas, + deltas, self.expire, 5, 0) + + self.assertEqual(self.sync_called, set(['instances', 'cores', 'ram'])) + self.compare_usage(self.usages, [ + dict(resource='instances', + project_id='test_project', + in_use=2, + reserved=2, + until_refresh=5), + dict(resource='cores', + project_id='test_project', + in_use=2, + reserved=4, + until_refresh=5), + dict(resource='ram', + project_id='test_project', + in_use=2, + reserved=2 * 1024, + until_refresh=5), + ]) + self.assertEqual(self.usages_created, {}) + self.compare_reservation(result, [ + dict(resource='instances', + usage_id=self.usages['instances'], + project_id='test_project', + delta=2), + dict(resource='cores', + usage_id=self.usages['cores'], + project_id='test_project', + delta=4), + dict(resource='ram', + usage_id=self.usages['ram'], + delta=2 * 1024), + ]) + + def test_quota_reserve_max_age(self): + max_age = 3600 + record_created = utils.utcnow() - datetime.timedelta(seconds=max_age) + self.init_usage('test_project', 'instances', 3, 0, + created_at=record_created, updated_at=record_created) + self.init_usage('test_project', 'cores', 3, 0, + created_at=record_created, updated_at=record_created) + self.init_usage('test_project', 'ram', 3, 0, + created_at=record_created, updated_at=record_created) + context = FakeContext('test_project', 'test_class') + quotas = dict( + instances=5, + cores=10, + ram=10 * 1024, + ) + deltas = dict( + instances=2, + cores=4, + ram=2 * 1024, + ) + result = sqa_api.quota_reserve(context, self.resources, quotas, + deltas, self.expire, 0, max_age) + + self.assertEqual(self.sync_called, set(['instances', 'cores', 'ram'])) + self.compare_usage(self.usages, [ + dict(resource='instances', + project_id='test_project', + in_use=2, + reserved=2, + until_refresh=None), + dict(resource='cores', + project_id='test_project', + in_use=2, + reserved=4, + until_refresh=None), + dict(resource='ram', + project_id='test_project', + in_use=2, + reserved=2 * 1024, + until_refresh=None), + ]) + self.assertEqual(self.usages_created, {}) + self.compare_reservation(result, [ + dict(resource='instances', + usage_id=self.usages['instances'], + project_id='test_project', + delta=2), + dict(resource='cores', + usage_id=self.usages['cores'], + project_id='test_project', + delta=4), + dict(resource='ram', + usage_id=self.usages['ram'], + delta=2 * 1024), + ]) + + def test_quota_reserve_no_refresh(self): + self.init_usage('test_project', 'instances', 3, 0) + self.init_usage('test_project', 'cores', 3, 0) + self.init_usage('test_project', 'ram', 3, 0) + context = FakeContext('test_project', 'test_class') + quotas = dict( + instances=5, + cores=10, + ram=10 * 1024, + ) + deltas = dict( + instances=2, + cores=4, + ram=2 * 1024, + ) + result = sqa_api.quota_reserve(context, self.resources, quotas, + deltas, self.expire, 0, 0) + + self.assertEqual(self.sync_called, set([])) + self.compare_usage(self.usages, [ + dict(resource='instances', + project_id='test_project', + in_use=3, + reserved=2, + until_refresh=None), + dict(resource='cores', + project_id='test_project', + in_use=3, + reserved=4, + until_refresh=None), + dict(resource='ram', + project_id='test_project', + in_use=3, + reserved=2 * 1024, + until_refresh=None), + ]) + self.assertEqual(self.usages_created, {}) + self.compare_reservation(result, [ + dict(resource='instances', + usage_id=self.usages['instances'], + project_id='test_project', + delta=2), + dict(resource='cores', + usage_id=self.usages['cores'], + project_id='test_project', + delta=4), + dict(resource='ram', + usage_id=self.usages['ram'], + delta=2 * 1024), + ]) + + def test_quota_reserve_unders(self): + self.init_usage('test_project', 'instances', 1, 0) + self.init_usage('test_project', 'cores', 3, 0) + self.init_usage('test_project', 'ram', 1 * 1024, 0) + context = FakeContext('test_project', 'test_class') + quotas = dict( + instances=5, + cores=10, + ram=10 * 1024, + ) + deltas = dict( + instances=-2, + cores=-4, + ram=-2 * 1024, + ) + self.assertRaises(exception.InvalidQuotaValue, + sqa_api.quota_reserve, + context, self.resources, quotas, + deltas, self.expire, 0, 0) + + self.assertEqual(self.sync_called, set([])) + self.compare_usage(self.usages, [ + dict(resource='instances', + project_id='test_project', + in_use=1, + reserved=0, + until_refresh=None), + dict(resource='cores', + project_id='test_project', + in_use=3, + reserved=0, + until_refresh=None), + dict(resource='ram', + project_id='test_project', + in_use=1 * 1024, + reserved=0, + until_refresh=None), + ]) + self.assertEqual(self.usages_created, {}) + self.assertEqual(self.reservations_created, {}) + + def test_quota_reserve_overs(self): + self.init_usage('test_project', 'instances', 4, 0) + self.init_usage('test_project', 'cores', 8, 0) + self.init_usage('test_project', 'ram', 10 * 1024, 0) + context = FakeContext('test_project', 'test_class') + quotas = dict( + instances=5, + cores=10, + ram=10 * 1024, + ) + deltas = dict( + instances=2, + cores=4, + ram=2 * 1024, + ) + self.assertRaises(exception.OverQuota, + sqa_api.quota_reserve, + context, self.resources, quotas, + deltas, self.expire, 0, 0) + + self.assertEqual(self.sync_called, set([])) + self.compare_usage(self.usages, [ + dict(resource='instances', + project_id='test_project', + in_use=4, + reserved=0, + until_refresh=None), + dict(resource='cores', + project_id='test_project', + in_use=8, + reserved=0, + until_refresh=None), + dict(resource='ram', + project_id='test_project', + in_use=10 * 1024, + reserved=0, + until_refresh=None), + ]) + self.assertEqual(self.usages_created, {}) + self.assertEqual(self.reservations_created, {}) + + def test_quota_reserve_reduction(self): + self.init_usage('test_project', 'instances', 10, 0) + self.init_usage('test_project', 'cores', 20, 0) + self.init_usage('test_project', 'ram', 20 * 1024, 0) + context = FakeContext('test_project', 'test_class') + quotas = dict( + instances=5, + cores=10, + ram=10 * 1024, + ) + deltas = dict( + instances=-2, + cores=-4, + ram=-2 * 1024, + ) + result = sqa_api.quota_reserve(context, self.resources, quotas, + deltas, self.expire, 0, 0) + + self.assertEqual(self.sync_called, set([])) + self.compare_usage(self.usages, [ + dict(resource='instances', + project_id='test_project', + in_use=10, + reserved=0, + until_refresh=None), + dict(resource='cores', + project_id='test_project', + in_use=20, + reserved=0, + until_refresh=None), + dict(resource='ram', + project_id='test_project', + in_use=20 * 1024, + reserved=0, + until_refresh=None), + ]) + self.assertEqual(self.usages_created, {}) + self.compare_reservation(result, [ + dict(resource='instances', + usage_id=self.usages['instances'], + project_id='test_project', + delta=-2), + dict(resource='cores', + usage_id=self.usages['cores'], + project_id='test_project', + delta=-4), + dict(resource='ram', + usage_id=self.usages['ram'], + project_id='test_project', + delta=-2 * 1024), + ]) |
