commit 6f757dea1d5d126bd31fc7009821fcb188f6f043 Author: Michael Still Date: Sat Feb 23 07:24:36 2013 +1100 Add quotas for fixed ips. Change-Id: I970d540cfa6a61b7e903703f845a6453ff55f225 diff --git a/nova/db/api.py b/nova/db/api.py index bb69558..9f2ff73 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -507,6 +507,12 @@ def fixed_ip_update(context, address, values): """Create a fixed ip from the values dictionary.""" return IMPL.fixed_ip_update(context, address, values) + +def fixed_ip_count_by_project(context, project_id, session=None): + """Count fixed ips used by project.""" + return IMPL.fixed_ip_count_by_project(context, project_id, + session=session) + #################### diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 4bdab49..2598d3e 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1273,6 +1273,16 @@ def fixed_ip_update(context, address, values): fixed_ip_ref.save(session=session) +@require_context +def fixed_ip_count_by_project(context, project_id, session=None): + authorize_project_context(context, project_id) + return model_query(context, models.FixedIp, read_deleted="no", + session=session).\ + options(joinedload_all('instances')).\ + filter_by(project_id=project_id).\ + count() + + ################### diff --git a/nova/exception.py b/nova/exception.py index 2eeef04..2da5e6c 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -998,6 +998,10 @@ class FloatingIpLimitExceeded(QuotaError): message = _("Maximum number of floating ips exceeded") +class FixedIpLimitExceeded(QuotaError): + message = _("Maximum number of fixed ips exceeded") + + class MetadataLimitExceeded(QuotaError): message = _("Maximum number of metadata items exceeds %(allowed)d") diff --git a/nova/network/manager.py b/nova/network/manager.py index 00a6e58..b183553 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -1294,6 +1294,16 @@ class NetworkManager(manager.SchedulerDependentManager): address = None instance_ref = self.db.instance_get(context, instance_id) + # Check the quota; can't put this in the API because we get + # called into from other places + try: + reservations = QUOTAS.reserve(context, fixed_ips=1) + except exception.OverQuota: + pid = context.project_id + LOG.warn(_("Quota exceeded for %(pid)s, tried to allocate " + "fixed IP") % locals()) + raise exception.FixedIpLimitExceeded() + if network['cidr']: address = kwargs.get('address', None) if address: @@ -1324,6 +1334,8 @@ class NetworkManager(manager.SchedulerDependentManager): "A", self.instance_dns_domain) self._setup_network_on_host(context, network) + + QUOTAS.commit(context, reservations) return address def deallocate_fixed_ip(self, context, address, host=None, teardown=True): @@ -1334,6 +1346,13 @@ class NetworkManager(manager.SchedulerDependentManager): context.elevated(read_deleted='yes'), fixed_ip_ref['instance_uuid']) + try: + reservations = QUOTAS.reserve(context, floating_ips=-1) + except Exception: + reservations = None + LOG.exception(_("Failed to update usages deallocating " + "floating IP")) + self._do_trigger_security_group_members_refresh_for_instance( instance['uuid']) @@ -1373,6 +1392,10 @@ class NetworkManager(manager.SchedulerDependentManager): # callback will get called by nova-dhcpbridge. self.driver.release_dhcp(dev, address, vif['address']) + # Commit the reservations + if reservations: + QUOTAS.commit(context, reservations) + def lease_fixed_ip(self, context, address): """Called by dhcp-bridge when ip is leased.""" LOG.debug(_('Leased IP |%(address)s|'), locals(), context=context) diff --git a/nova/quota.py b/nova/quota.py index d3ba0aa..31e2794 100644 --- a/nova/quota.py +++ b/nova/quota.py @@ -50,6 +50,10 @@ quota_opts = [ cfg.IntOpt('quota_floating_ips', default=10, help='number of floating ips allowed per project'), + cfg.IntOpt('quota_fixed_ips', + default=10, + help=('number of fixed ips allowed per project (this should be ' + 'at least the number of instances allowed)')), cfg.IntOpt('quota_metadata_items', default=128, help='number of metadata items allowed per instance'), @@ -778,6 +782,11 @@ def _sync_floating_ips(context, project_id, session): context, project_id, session=session)) +def _sync_fixed_ips(context, project_id, session): + return dict(fixed_ips=db.fixed_ip_count_by_project( + context, project_id, session=session)) + + def _sync_security_groups(context, project_id, session): return dict(security_groups=db.security_group_count_by_project( context, project_id, session=session)) @@ -794,6 +803,7 @@ resources = [ ReservableResource('gigabytes', _sync_volumes, 'quota_gigabytes'), ReservableResource('floating_ips', _sync_floating_ips, 'quota_floating_ips'), + ReservableResource('fixed_ips', _sync_fixed_ips, 'quota_fixed_ips'), AbsoluteResource('metadata_items', 'quota_metadata_items'), AbsoluteResource('injected_files', 'quota_injected_files'), AbsoluteResource('injected_file_content_bytes', diff --git a/nova/tests/api/openstack/compute/contrib/test_quota_classes.py b/nova/tests/api/openstack/compute/contrib/test_quota_classes.py index b732f88..5bee208 100644 --- a/nova/tests/api/openstack/compute/contrib/test_quota_classes.py +++ b/nova/tests/api/openstack/compute/contrib/test_quota_classes.py @@ -25,10 +25,11 @@ from nova.tests.api.openstack import fakes def quota_set(class_name): return {'quota_class_set': {'id': class_name, 'metadata_items': 128, 'volumes': 10, 'gigabytes': 1000, 'ram': 51200, - 'floating_ips': 10, 'instances': 10, 'injected_files': 5, - 'cores': 20, 'injected_file_content_bytes': 10240, - 'security_groups': 10, 'security_group_rules': 20, - 'key_pairs': 100, 'injected_file_path_bytes': 255}} + 'floating_ips': 10, 'fixed_ips': 10, 'instances': 10, + 'injected_files': 5, 'cores': 20, + 'injected_file_content_bytes': 10240, 'security_groups': 10, + 'security_group_rules': 20, 'key_pairs': 100, + 'injected_file_path_bytes': 255}} class QuotaClassSetsTest(test.TestCase): @@ -44,6 +45,7 @@ class QuotaClassSetsTest(test.TestCase): 'ram': 51200, 'volumes': 10, 'floating_ips': 10, + 'fixed_ips': 10, 'metadata_items': 128, 'gigabytes': 1000, 'injected_files': 5, @@ -91,7 +93,8 @@ class QuotaClassSetsTest(test.TestCase): body = {'quota_class_set': {'instances': 50, 'cores': 50, 'ram': 51200, 'volumes': 10, 'gigabytes': 1000, 'floating_ips': 10, - 'metadata_items': 128, 'injected_files': 5, + 'fixed_ips': 10, 'metadata_items': 128, + 'injected_files': 5, 'injected_file_content_bytes': 10240, 'injected_file_path_bytes': 255, 'security_groups': 10, @@ -139,6 +142,7 @@ class QuotaTemplateXMLSerializerTest(test.TestCase): gigabytes=40, ram=50, floating_ips=60, + fixed_ips=10, instances=70, injected_files=80, security_groups=10, diff --git a/nova/tests/api/openstack/compute/contrib/test_quotas.py b/nova/tests/api/openstack/compute/contrib/test_quotas.py index f628535..c36b4d7 100644 --- a/nova/tests/api/openstack/compute/contrib/test_quotas.py +++ b/nova/tests/api/openstack/compute/contrib/test_quotas.py @@ -26,7 +26,7 @@ from nova.tests.api.openstack import fakes def quota_set(id): return {'quota_set': {'id': id, 'metadata_items': 128, 'volumes': 10, - 'gigabytes': 1000, 'ram': 51200, 'floating_ips': 10, + 'gigabytes': 1000, 'ram': 51200, 'floating_ips': 10, 'fixed_ips': 10, 'instances': 10, 'injected_files': 5, 'cores': 20, 'injected_file_content_bytes': 10240, 'security_groups': 10, 'security_group_rules': 20, @@ -46,6 +46,7 @@ class QuotaSetsTest(test.TestCase): 'ram': 51200, 'volumes': 10, 'floating_ips': 10, + 'fixed_ips': 10, 'metadata_items': 128, 'gigabytes': 1000, 'injected_files': 5, @@ -88,6 +89,7 @@ class QuotaSetsTest(test.TestCase): 'volumes': 10, 'gigabytes': 1000, 'floating_ips': 10, + 'fixed_ips': 10, 'metadata_items': 128, 'injected_files': 5, 'injected_file_path_bytes': 255, @@ -120,7 +122,7 @@ class QuotaSetsTest(test.TestCase): 'injected_file_path_bytes': 255, 'security_groups': 10, 'security_group_rules': 20, - 'key_pairs': 100}} + 'key_pairs': 100, 'fixed_ips': 10}} req = fakes.HTTPRequest.blank('/v2/fake4/os-quota-sets/update_me', use_admin_context=True) @@ -171,6 +173,7 @@ class QuotaXMLSerializerTest(test.TestCase): gigabytes=40, ram=50, floating_ips=60, + fixed_ips=10, instances=70, injected_files=80, security_groups=10, diff --git a/nova/tests/network/test_manager.py b/nova/tests/network/test_manager.py index e983ad6..f27a176 100644 --- a/nova/tests/network/test_manager.py +++ b/nova/tests/network/test_manager.py @@ -30,6 +30,7 @@ from nova.openstack.common import importutils from nova.openstack.common import log as logging from nova.openstack.common import rpc import nova.policy +from nova import quota from nova import test from nova.tests import fake_network from nova import utils @@ -278,6 +279,7 @@ class FlatNetworkTestCase(test.TestCase): self.mox.StubOutWithMock(db, 'virtual_interface_get_by_instance_and_network') self.mox.StubOutWithMock(db, 'fixed_ip_update') + self.mox.StubOutWithMock(quota.QUOTAS, 'reserve') db.fixed_ip_update(mox.IgnoreArg(), mox.IgnoreArg(), @@ -291,6 +293,10 @@ class FlatNetworkTestCase(test.TestCase): db.instance_get(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn({'security_groups': [{'id': 0}]}) + + quota.QUOTAS.reserve(mox.IgnoreArg(), + fixed_ips=mox.IgnoreArg()).AndReturn(None) + db.fixed_ip_associate_pool(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()).AndReturn('192.168.0.101') @@ -310,6 +316,7 @@ class FlatNetworkTestCase(test.TestCase): self.mox.StubOutWithMock(db, 'virtual_interface_get_by_instance_and_network') self.mox.StubOutWithMock(db, 'fixed_ip_update') + self.mox.StubOutWithMock(quota.QUOTAS, 'reserve') db.fixed_ip_update(mox.IgnoreArg(), mox.IgnoreArg(), @@ -323,6 +330,10 @@ class FlatNetworkTestCase(test.TestCase): db.instance_get(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn({'security_groups': [{'id': 0}]}) + + quota.QUOTAS.reserve(mox.IgnoreArg(), + fixed_ips=mox.IgnoreArg()).AndReturn(None) + db.fixed_ip_associate_pool(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()).AndReturn('192.168.0.101') @@ -376,6 +387,7 @@ class FlatNetworkTestCase(test.TestCase): self.mox.StubOutWithMock(db, 'virtual_interface_get_by_instance_and_network') self.mox.StubOutWithMock(db, 'fixed_ip_update') + self.mox.StubOutWithMock(quota.QUOTAS, 'reserve') db.fixed_ip_update(mox.IgnoreArg(), mox.IgnoreArg(), @@ -390,6 +402,9 @@ class FlatNetworkTestCase(test.TestCase): mox.IgnoreArg()).AndReturn({'security_groups': [{'id': 0}]}) + quota.QUOTAS.reserve(mox.IgnoreArg(), + fixed_ips=mox.IgnoreArg()).AndReturn(None) + db.fixed_ip_associate_pool(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(fixedip) diff --git a/nova/tests/test_quota.py b/nova/tests/test_quota.py index dd86c7c..5baf966 100644 --- a/nova/tests/test_quota.py +++ b/nova/tests/test_quota.py @@ -723,6 +723,7 @@ class DbQuotaDriverTestCase(test.TestCase): quota_volumes=10, quota_gigabytes=1000, quota_floating_ips=10, + quota_fixed_ips=10, quota_metadata_items=128, quota_injected_files=5, quota_injected_file_content_bytes=10 * 1024, @@ -755,6 +756,7 @@ class DbQuotaDriverTestCase(test.TestCase): volumes=10, gigabytes=1000, floating_ips=10, + fixed_ips=10, metadata_items=128, injected_files=5, injected_file_content_bytes=10 * 1024, @@ -791,6 +793,7 @@ class DbQuotaDriverTestCase(test.TestCase): volumes=10, gigabytes=500, floating_ips=10, + fixed_ips=10, metadata_items=64, injected_files=5, injected_file_content_bytes=5 * 1024, @@ -847,6 +850,7 @@ class DbQuotaDriverTestCase(test.TestCase): self._stub_quota_class_get_all_by_name() def test_get_project_quotas(self): + self.maxDiff = None self._stub_get_by_project() result = self.driver.get_project_quotas( FakeContext('test_project', 'test_class'), @@ -888,6 +892,11 @@ class DbQuotaDriverTestCase(test.TestCase): in_use=2, reserved=0, ), + fixed_ips=dict( + limit=10, + in_use=0, + reserved=0, + ), metadata_items=dict( limit=64, in_use=0, @@ -926,6 +935,7 @@ class DbQuotaDriverTestCase(test.TestCase): )) def test_get_project_quotas_alt_context_no_class(self): + self.maxDiff = None self._stub_get_by_project() result = self.driver.get_project_quotas( FakeContext('other_project', 'other_class'), @@ -966,6 +976,11 @@ class DbQuotaDriverTestCase(test.TestCase): in_use=2, reserved=0, ), + fixed_ips=dict( + limit=10, + in_use=0, + reserved=0, + ), metadata_items=dict( limit=128, in_use=0, @@ -1004,6 +1019,7 @@ class DbQuotaDriverTestCase(test.TestCase): )) def test_get_project_quotas_alt_context_with_class(self): + self.maxDiff = None self._stub_get_by_project() result = self.driver.get_project_quotas( FakeContext('other_project', 'other_class'), @@ -1045,6 +1061,11 @@ class DbQuotaDriverTestCase(test.TestCase): in_use=2, reserved=0, ), + fixed_ips=dict( + limit=10, + in_use=0, + reserved=0, + ), metadata_items=dict( limit=64, in_use=0, @@ -1145,6 +1166,9 @@ class DbQuotaDriverTestCase(test.TestCase): floating_ips=dict( limit=10, ), + fixed_ips=dict( + limit=10, + ), metadata_items=dict( limit=64, ),