High severityNVD Advisory· Published Mar 22, 2013· Updated Apr 29, 2026
CVE-2013-1838
CVE-2013-1838
Description
OpenStack Compute (Nova) Grizzly, Folsom (2012.2), and Essex (2012.1) does not properly implement a quota for fixed IPs, which allows remote authenticated users to cause a denial of service (resource exhaustion and failure to spawn new instances) via a large number of calls to the addFixedIp function.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
novaPyPI | < 12.0.0a0 | 12.0.0a0 |
Affected products
6cpe:2.3:o:canonical:ubuntu_linux:11.10:*:*:*:*:*:*:*+ 2 more
- cpe:2.3:o:canonical:ubuntu_linux:11.10:*:*:*:*:*:*:*
- cpe:2.3:o:canonical:ubuntu_linux:12.04:-:lts:*:*:*:*:*
- cpe:2.3:o:canonical:ubuntu_linux:12.10:*:*:*:*:*:*:*
Patches
3efaacdaee116Add quotas for fixed ips.
7 files changed · +67 −6
nova/api/openstack/compute/contrib/quotas.py+3 −2 modified@@ -30,8 +30,9 @@ quota_resources = ['metadata_items', 'injected_file_content_bytes', - 'volumes', 'gigabytes', 'ram', 'floating_ips', 'instances', - 'injected_files', 'cores', 'security_groups', 'security_group_rules'] + 'volumes', 'gigabytes', 'ram', 'floating_ips', 'fixed_ips', + 'instances', 'injected_files', 'cores', 'security_groups', + 'security_group_rules'] class QuotaTemplate(xmlutil.TemplateBuilder):
nova/db/api.py+6 −0 modified@@ -461,6 +461,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) + ####################
nova/db/sqlalchemy/api.py+21 −0 modified@@ -1114,6 +1114,27 @@ 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) + + # NOTE(mikal): Yes I know this is horrible, but I couldn't + # get a query using a join working, mainly because of a failure + # to be able to express the where clause sensibly. Patches + # welcome. + session = get_session() + with session.begin(): + instance_id_query = model_query(context, models.Instance.id, + read_deleted="no", session=session).\ + filter(models.Instance.project_id == \ + project_id) + id_filter = models.FixedIp.instance_id.in_(instance_id_query) + return model_query(context, models.FixedIp, read_deleted="no", + session=session).\ + filter(id_filter).\ + count() + + ###################
nova/network/manager.py+6 −0 modified@@ -1152,6 +1152,12 @@ def _validate_instance_zone_for_dns_domain(self, context, instance_id): def allocate_fixed_ip(self, context, instance_id, network, **kwargs): """Gets a fixed ip from the pool.""" + LOG.debug("QUOTA: %s" % quota.allowed_fixed_ips(context, 1)) + if quota.allowed_fixed_ips(context, 1) < 1: + LOG.warn(_('Quota exceeded for %s, tried to allocate address'), + context.project_id) + raise exception.QuotaError(code='FixedAddressLimitExceeded') + # TODO(vish): when this is called by compute, we can associate compute # with a network, or a cluster of computes with a network # and use that network here with a method like
nova/quota.py+17 −0 modified@@ -42,6 +42,10 @@ 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'), @@ -74,6 +78,7 @@ def _get_default_quotas(): 'volumes': FLAGS.quota_volumes, 'gigabytes': FLAGS.quota_gigabytes, 'floating_ips': FLAGS.quota_floating_ips, + 'fixed_ips': FLAGS.quota_fixed_ips, 'metadata_items': FLAGS.quota_metadata_items, 'injected_files': FLAGS.quota_max_injected_files, 'injected_file_content_bytes': @@ -173,6 +178,18 @@ def allowed_floating_ips(context, requested_floating_ips): return min(requested_floating_ips, allowed_floating_ips) +def allowed_fixed_ips(context, requested_fixed_ips): + """Check quota and return min(requested, allowed) fixed ips.""" + project_id = context.project_id + context = context.elevated() + used_fixed_ips = db.fixed_ip_count_by_project(context, project_id) + quota = get_project_quotas(context, project_id) + allowed_fixed_ips = _get_request_allotment(requested_fixed_ips, + used_fixed_ips, + quota['fixed_ips']) + return min(requested_fixed_ips, allowed_fixed_ips) + + def allowed_security_groups(context, requested_security_groups): """Check quota and return min(requested, allowed) security groups.""" project_id = context.project_id
nova/tests/api/openstack/compute/contrib/test_quotas.py+13 −4 modified@@ -27,8 +27,8 @@ def quota_set(id): return {'quota_set': {'id': id, 'metadata_items': 128, 'volumes': 10, 'gigabytes': 1000, 'ram': 51200, 'floating_ips': 10, - 'instances': 10, 'injected_files': 5, 'cores': 20, - 'injected_file_content_bytes': 10240, + 'fixed_ips': 10, 'instances': 10, 'injected_files': 5, + 'cores': 20, 'injected_file_content_bytes': 10240, 'security_groups': 10, 'security_group_rules': 20}} @@ -50,6 +50,8 @@ def test_format_quota_set(self): 'ram': 51200, 'volumes': 10, 'floating_ips': 10, + 'fixed_ips': 10, + 'fixed_ips': 10, 'metadata_items': 128, 'gigabytes': 1000, 'injected_files': 5, @@ -69,6 +71,7 @@ def test_format_quota_set(self): self.assertEqual(qs['volumes'], 10) self.assertEqual(qs['gigabytes'], 1000) self.assertEqual(qs['floating_ips'], 10) + self.assertEqual(qs['fixed_ips'], 10) self.assertEqual(qs['metadata_items'], 128) self.assertEqual(qs['injected_files'], 5) self.assertEqual(qs['injected_file_content_bytes'], 10240) @@ -89,6 +92,7 @@ def test_quotas_defaults(self): 'volumes': 10, 'gigabytes': 1000, 'floating_ips': 10, + 'fixed_ips': 10, 'metadata_items': 128, 'injected_files': 5, 'injected_file_content_bytes': 10240, @@ -114,7 +118,8 @@ def test_quotas_update_as_admin(self): body = {'quota_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, 'security_groups': 10, 'security_group_rules': 20}} @@ -129,7 +134,8 @@ def test_quotas_update_as_user(self): body = {'quota_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, 'security_groups': 10, 'security_group_rules': 20}} @@ -154,6 +160,7 @@ def test_serializer(self): gigabytes=40, ram=50, floating_ips=60, + fixed_ips=10, instances=70, injected_files=80, security_groups=10, @@ -179,6 +186,7 @@ def test_deserializer(self): gigabytes='40', ram='50', floating_ips='60', + fixed_ips='10', instances='70', injected_files='80', security_groups='10', @@ -193,6 +201,7 @@ def test_deserializer(self): '<gigabytes>40</gigabytes>' '<ram>50</ram>' '<floating_ips>60</floating_ips>' + '<fixed_ips>10</fixed_ips>' '<instances>70</instances>' '<injected_files>80</injected_files>' '<security_groups>10</security_groups>'
nova/tests/network/test_manager.py+1 −0 modified@@ -432,6 +432,7 @@ def test_allocate_fixed_ip(self): db.instance_get(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn({'security_groups': [{'id': 0}]}) + db.fixed_ip_associate_pool(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()).AndReturn('192.168.0.1')
9561484166f2Add quotas for fixed ips.
9 files changed · +157 −42
nova/db/api.py+6 −0 modified@@ -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) + ####################
nova/db/sqlalchemy/api.py+21 −0 modified@@ -1273,6 +1273,27 @@ 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) + + # NOTE(mikal): Yes I know this is horrible, but I couldn't + # get a query using a join working, mainly because of a failure + # to be able to express the where clause sensibly. Patches + # welcome. + session = get_session() + with session.begin(): + instance_uuid_query = model_query(context, models.Instance.uuid, + read_deleted="no", session=session).\ + filter(models.Instance.project_id == \ + project_id) + uuid_filter = models.FixedIp.instance_uuid.in_(instance_uuid_query) + return model_query(context, models.FixedIp, read_deleted="no", + session=session).\ + filter(uuid_filter).\ + count() + + ###################
nova/exception.py+4 −0 modified@@ -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")
nova/network/manager.py+58 −31 modified@@ -1294,37 +1294,53 @@ def allocate_fixed_ip(self, context, instance_id, network, **kwargs): address = None instance_ref = self.db.instance_get(context, instance_id) - if network['cidr']: - address = kwargs.get('address', None) - if address: - address = self.db.fixed_ip_associate(context, - address, - instance_ref['uuid'], - network['id']) - else: - address = self.db.fixed_ip_associate_pool(context.elevated(), - network['id'], - instance_ref['uuid']) - self._do_trigger_security_group_members_refresh_for_instance( - instance_id) - get_vif = self.db.virtual_interface_get_by_instance_and_network - vif = get_vif(context, instance_ref['uuid'], network['id']) - values = {'allocated': True, - 'virtual_interface_id': vif['id']} - self.db.fixed_ip_update(context, address, values) - - name = instance_ref['display_name'] - - if self._validate_instance_zone_for_dns_domain(context, instance_ref): - uuid = instance_ref['uuid'] - self.instance_dns_manager.create_entry(name, address, - "A", - self.instance_dns_domain) - self.instance_dns_manager.create_entry(uuid, address, - "A", - self.instance_dns_domain) - self._setup_network_on_host(context, network) - return address + # 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() + + try: + if network['cidr']: + address = kwargs.get('address', None) + if address: + address = self.db.fixed_ip_associate(context, + address, + instance_ref['uuid'], + network['id']) + else: + address = self.db.fixed_ip_associate_pool( + context.elevated(), network['id'], + instance_ref['uuid']) + self._do_trigger_security_group_members_refresh_for_instance( + instance_id) + get_vif = self.db.virtual_interface_get_by_instance_and_network + vif = get_vif(context, instance_ref['uuid'], network['id']) + values = {'allocated': True, + 'virtual_interface_id': vif['id']} + self.db.fixed_ip_update(context, address, values) + + name = instance_ref['display_name'] + + if self._validate_instance_zone_for_dns_domain(context, + instance_ref): + uuid = instance_ref['uuid'] + self.instance_dns_manager.create_entry( + name, address, "A", self.instance_dns_domain) + self.instance_dns_manager.create_entry( + uuid, address, "A", self.instance_dns_domain) + self._setup_network_on_host(context, network) + + QUOTAS.commit(context, reservations) + return address + + except Exception: + with excutils.save_and_reraise_exception(): + QUOTAS.rollback(context, reservations) def deallocate_fixed_ip(self, context, address, host=None, teardown=True): """Returns a fixed ip to the pool.""" @@ -1334,6 +1350,13 @@ def deallocate_fixed_ip(self, context, address, host=None, teardown=True): context.elevated(read_deleted='yes'), fixed_ip_ref['instance_uuid']) + try: + reservations = QUOTAS.reserve(context, fixed_ips=-1) + except Exception: + reservations = None + LOG.exception(_("Failed to update usages deallocating " + "fixed IP")) + self._do_trigger_security_group_members_refresh_for_instance( instance['uuid']) @@ -1373,6 +1396,10 @@ def deallocate_fixed_ip(self, context, address, host=None, teardown=True): # 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)
nova/quota.py+10 −0 modified@@ -50,6 +50,10 @@ 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 @@ def _sync_security_groups(context, project_id, session): 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',
nova/tests/api/openstack/compute/contrib/test_quota_classes.py+9 −5 modified@@ -25,10 +25,11 @@ 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 @@ def test_format_quota_set(self): 'ram': 51200, 'volumes': 10, 'floating_ips': 10, + 'fixed_ips': 10, 'metadata_items': 128, 'gigabytes': 1000, 'injected_files': 5, @@ -91,7 +93,8 @@ def test_quotas_update_as_admin(self): 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 @@ def test_serializer(self): gigabytes=40, ram=50, floating_ips=60, + fixed_ips=10, instances=70, injected_files=80, security_groups=10,
nova/tests/api/openstack/compute/contrib/test_quotas.py+10 −6 modified@@ -26,11 +26,12 @@ def quota_set(id): return {'quota_set': {'id': id, '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}} + '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, + 'key_pairs': 100, 'injected_file_path_bytes': 255}} class QuotaSetsTest(test.TestCase): @@ -46,6 +47,7 @@ def test_format_quota_set(self): 'ram': 51200, 'volumes': 10, 'floating_ips': 10, + 'fixed_ips': 10, 'metadata_items': 128, 'gigabytes': 1000, 'injected_files': 5, @@ -88,6 +90,7 @@ def test_quotas_defaults(self): 'volumes': 10, 'gigabytes': 1000, 'floating_ips': 10, + 'fixed_ips': 10, 'metadata_items': 128, 'injected_files': 5, 'injected_file_path_bytes': 255, @@ -120,7 +123,7 @@ def test_quotas_update_as_admin(self): '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 +174,7 @@ def test_serializer(self): gigabytes=40, ram=50, floating_ips=60, + fixed_ips=10, instances=70, injected_files=80, security_groups=10,
nova/tests/network/test_manager.py+15 −0 modified@@ -30,6 +30,7 @@ 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 @@ def test_add_fixed_ip_instance_using_id_without_vpn(self): 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 @@ def test_add_fixed_ip_instance_using_id_without_vpn(self): 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 @@ def test_add_fixed_ip_instance_using_uuid_without_vpn(self): 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 @@ def test_add_fixed_ip_instance_using_uuid_without_vpn(self): 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 @@ def test_instance_dns(self): 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 @@ def test_instance_dns(self): 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)
nova/tests/test_quota.py+24 −0 modified@@ -723,6 +723,7 @@ def setUp(self): 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 @@ def test_get_defaults(self): 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 @@ def test_get_class_quotas(self): 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 @@ def fake_qugabp(context, project_id): 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 @@ def test_get_project_quotas(self): 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 @@ def test_get_project_quotas(self): )) 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 @@ def test_get_project_quotas_alt_context_no_class(self): 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 @@ def test_get_project_quotas_alt_context_no_class(self): )) 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 @@ def test_get_project_quotas_alt_context_with_class(self): 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 @@ def test_get_project_quotas_no_usages(self): floating_ips=dict( limit=10, ), + fixed_ips=dict( + limit=10, + ), metadata_items=dict( limit=64, ),
99429214d4ddAdd quotas for fixed ips.
31 files changed · +198 −54
doc/api_samples/os-quota-class-sets/quota-classes-show-get-resp.json+1 −0 modified@@ -1,6 +1,7 @@ { "quota_class_set": { "cores": 20, + "fixed_ips": 10, "floating_ips": 10, "id": "test_class", "injected_file_content_bytes": 10240,
doc/api_samples/os-quota-class-sets/quota-classes-show-get-resp.xml+1 −0 modified@@ -1,6 +1,7 @@ <?xml version='1.0' encoding='UTF-8'?> <quota_class_set id="test_class"> <cores>20</cores> + <fixed_ips>10</fixed_ips> <floating_ips>10</floating_ips> <injected_file_content_bytes>10240</injected_file_content_bytes> <injected_file_path_bytes>255</injected_file_path_bytes>
doc/api_samples/os-quota-class-sets/quota-classes-update-post-resp.json+1 −0 modified@@ -1,6 +1,7 @@ { "quota_class_set": { "cores": 50, + "fixed_ips": 10, "floating_ips": 10, "injected_file_content_bytes": 10240, "injected_file_path_bytes": 255,
doc/api_samples/os-quota-class-sets/quota-classes-update-post-resp.xml+1 −0 modified@@ -1,6 +1,7 @@ <?xml version='1.0' encoding='UTF-8'?> <quota_class_set> <cores>50</cores> + <fixed_ips>10</fixed_ips> <floating_ips>10</floating_ips> <injected_file_content_bytes>10240</injected_file_content_bytes> <injected_file_path_bytes>255</injected_file_path_bytes>
doc/api_samples/os-quota-sets/quotas-show-defaults-get-resp.json+2 −1 modified@@ -1,6 +1,7 @@ { "quota_set": { "cores": 20, + "fixed_ips": 10, "floating_ips": 10, "id": "fake_tenant", "injected_file_content_bytes": 10240, @@ -13,4 +14,4 @@ "security_group_rules": 20, "security_groups": 10 } -} +} \ No newline at end of file
doc/api_samples/os-quota-sets/quotas-show-defaults-get-resp.xml+2 −1 modified@@ -1,6 +1,7 @@ <?xml version='1.0' encoding='UTF-8'?> <quota_set id="fake_tenant"> <cores>20</cores> + <fixed_ips>10</fixed_ips> <floating_ips>10</floating_ips> <injected_file_content_bytes>10240</injected_file_content_bytes> <injected_file_path_bytes>255</injected_file_path_bytes> @@ -11,4 +12,4 @@ <ram>51200</ram> <security_group_rules>20</security_group_rules> <security_groups>10</security_groups> -</quota_set> +</quota_set> \ No newline at end of file
doc/api_samples/os-quota-sets/quotas-show-get-resp.json+2 −1 modified@@ -1,6 +1,7 @@ { "quota_set": { "cores": 20, + "fixed_ips": 10, "floating_ips": 10, "id": "fake_tenant", "injected_file_content_bytes": 10240, @@ -13,4 +14,4 @@ "security_group_rules": 20, "security_groups": 10 } -} +} \ No newline at end of file
doc/api_samples/os-quota-sets/quotas-show-get-resp.xml+2 −1 modified@@ -1,6 +1,7 @@ <?xml version='1.0' encoding='UTF-8'?> <quota_set id="fake_tenant"> <cores>20</cores> + <fixed_ips>10</fixed_ips> <floating_ips>10</floating_ips> <injected_file_content_bytes>10240</injected_file_content_bytes> <injected_file_path_bytes>255</injected_file_path_bytes> @@ -11,4 +12,4 @@ <ram>51200</ram> <security_group_rules>20</security_group_rules> <security_groups>10</security_groups> -</quota_set> +</quota_set> \ No newline at end of file
doc/api_samples/os-quota-sets/quotas-update-post-resp.json+2 −1 modified@@ -1,6 +1,7 @@ { "quota_set": { "cores": 20, + "fixed_ips": 10, "floating_ips": 10, "injected_file_content_bytes": 10240, "injected_file_path_bytes": 255, @@ -12,4 +13,4 @@ "security_group_rules": 20, "security_groups": 45 } -} +} \ No newline at end of file
doc/api_samples/os-quota-sets/quotas-update-post-resp.xml+2 −1 modified@@ -1,6 +1,7 @@ <?xml version='1.0' encoding='UTF-8'?> <quota_set> <cores>20</cores> + <fixed_ips>10</fixed_ips> <floating_ips>10</floating_ips> <injected_file_content_bytes>10240</injected_file_content_bytes> <injected_file_path_bytes>255</injected_file_path_bytes> @@ -11,4 +12,4 @@ <ram>51200</ram> <security_group_rules>20</security_group_rules> <security_groups>45</security_groups> -</quota_set> +</quota_set> \ No newline at end of file
nova/db/api.py+6 −0 modified@@ -510,6 +510,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) + ####################
nova/db/sqlalchemy/api.py+12 −0 modified@@ -1245,6 +1245,18 @@ 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): + nova.context.authorize_project_context(context, project_id) + return model_query(context, models.FixedIp.id, + base_model=models.FixedIp, read_deleted="no", + session=session).\ + join((models.Instance, + models.Instance.uuid == models.FixedIp.instance_uuid)).\ + filter(models.Instance.uuid == project_id).\ + count() + + ###################
nova/exception.py+4 −0 modified@@ -1008,6 +1008,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")
nova/network/manager.py+62 −33 modified@@ -69,11 +69,14 @@ from nova.openstack.common import log as logging from nova.openstack.common import timeutils from nova.openstack.common import uuidutils +from nova import quota from nova import servicegroup from nova import utils LOG = logging.getLogger(__name__) +QUOTAS = quota.QUOTAS + network_opts = [ cfg.StrOpt('flat_network_bridge', @@ -823,47 +826,69 @@ def allocate_fixed_ip(self, context, instance_id, network, **kwargs): # network_get_by_compute_host address = None - if network['cidr']: - address = kwargs.get('address', None) - if address: - address = self.db.fixed_ip_associate(context, - address, - instance_id, - network['id']) - else: - address = self.db.fixed_ip_associate_pool(context.elevated(), - network['id'], - instance_id) - self._do_trigger_security_group_members_refresh_for_instance( - instance_id) - self._do_trigger_security_group_handler( - 'instance_add_security_group', instance_id) - get_vif = self.db.virtual_interface_get_by_instance_and_network - vif = get_vif(context, instance_id, network['id']) - values = {'allocated': True, - 'virtual_interface_id': vif['id']} - self.db.fixed_ip_update(context, address, values) + # 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() - # NOTE(vish) This db query could be removed if we pass az and name - # (or the whole instance object). - instance = self.db.instance_get_by_uuid(context, instance_id) - name = instance['display_name'] + try: + if network['cidr']: + address = kwargs.get('address', None) + if address: + address = self.db.fixed_ip_associate(context, + address, + instance_id, + network['id']) + else: + address = self.db.fixed_ip_associate_pool( + context.elevated(), network['id'], instance_id) + self._do_trigger_security_group_members_refresh_for_instance( + instance_id) + self._do_trigger_security_group_handler( + 'instance_add_security_group', instance_id) + get_vif = self.db.virtual_interface_get_by_instance_and_network + vif = get_vif(context, instance_id, network['id']) + values = {'allocated': True, + 'virtual_interface_id': vif['id']} + self.db.fixed_ip_update(context, address, values) + + # NOTE(vish) This db query could be removed if we pass az and name + # (or the whole instance object). + instance = self.db.instance_get_by_uuid(context, instance_id) + name = instance['display_name'] - if self._validate_instance_zone_for_dns_domain(context, instance): - self.instance_dns_manager.create_entry(name, address, - "A", - self.instance_dns_domain) - self.instance_dns_manager.create_entry(instance_id, address, - "A", - self.instance_dns_domain) - self._setup_network_on_host(context, network) - return address + if self._validate_instance_zone_for_dns_domain(context, instance): + self.instance_dns_manager.create_entry( + name, address, "A", self.instance_dns_domain) + self.instance_dns_manager.create_entry( + instance_id, address, "A", self.instance_dns_domain) + self._setup_network_on_host(context, network) + + QUOTAS.commit(context, reservations) + return address + + except Exception: + with excutils.save_and_reraise_exception(): + QUOTAS.rollback(context, reservations) def deallocate_fixed_ip(self, context, address, host=None, teardown=True): """Returns a fixed ip to the pool.""" fixed_ip_ref = self.db.fixed_ip_get_by_address(context, address) instance_uuid = fixed_ip_ref['instance_uuid'] vif_id = fixed_ip_ref['virtual_interface_id'] + + try: + reservations = QUOTAS.reserve(context, fixed_ips=-1) + except Exception: + reservations = None + LOG.exception(_("Failed to update usages deallocating " + "fixed IP")) + self._do_trigger_security_group_members_refresh_for_instance( instance_uuid) self._do_trigger_security_group_handler( @@ -912,6 +937,10 @@ def deallocate_fixed_ip(self, context, address, host=None, teardown=True): self._teardown_network_on_host(context, network) + # 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)
nova/quota.py+10 −0 modified@@ -43,6 +43,10 @@ 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'), @@ -1062,6 +1066,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)) @@ -1076,6 +1085,7 @@ def _sync_security_groups(context, project_id, session): ReservableResource('ram', _sync_instances, 'quota_ram'), 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',
nova/tests/api/openstack/compute/contrib/test_quota_classes.py+16 −7 modified@@ -24,11 +24,13 @@ def quota_set(class_name): return {'quota_class_set': {'id': class_name, 'metadata_items': 128, - '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}} + '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, 'key_pairs': 100, + 'injected_file_path_bytes': 255}} class QuotaClassSetsTest(test.TestCase): @@ -43,6 +45,7 @@ def test_format_quota_set(self): 'cores': 20, 'ram': 51200, 'floating_ips': 10, + 'fixed_ips': 10, 'metadata_items': 128, 'injected_files': 5, 'injected_file_path_bytes': 255, @@ -61,6 +64,7 @@ def test_format_quota_set(self): self.assertEqual(qs['cores'], 20) self.assertEqual(qs['ram'], 51200) self.assertEqual(qs['floating_ips'], 10) + self.assertEqual(qs['fixed_ips'], 10) self.assertEqual(qs['metadata_items'], 128) self.assertEqual(qs['injected_files'], 5) self.assertEqual(qs['injected_file_path_bytes'], 255) @@ -86,7 +90,8 @@ def test_quotas_show_as_unauthorized_user(self): def test_quotas_update_as_admin(self): body = {'quota_class_set': {'instances': 50, 'cores': 50, 'ram': 51200, '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, @@ -103,7 +108,8 @@ def test_quotas_update_as_admin(self): def test_quotas_update_as_user(self): body = {'quota_class_set': {'instances': 50, 'cores': 50, 'ram': 51200, 'floating_ips': 10, - 'metadata_items': 128, 'injected_files': 5, + 'fixed_ips': 10, 'metadata_items': 128, + 'injected_files': 5, 'injected_file_content_bytes': 10240, 'security_groups': 10, 'security_group_rules': 20, @@ -130,6 +136,7 @@ def test_serializer(self): injected_file_content_bytes=20, ram=50, floating_ips=60, + fixed_ips=10, instances=70, injected_files=80, security_groups=10, @@ -154,6 +161,7 @@ def test_deserializer(self): injected_file_content_bytes='20', ram='50', floating_ips='60', + fixed_ips='10', instances='70', injected_files='80', security_groups='10', @@ -167,6 +175,7 @@ def test_deserializer(self): '</injected_file_content_bytes>' '<ram>50</ram>' '<floating_ips>60</floating_ips>' + '<fixed_ips>10</fixed_ips>' '<instances>70</instances>' '<injected_files>80</injected_files>' '<cores>90</cores>'
nova/tests/api/openstack/compute/contrib/test_quotas.py+19 −8 modified@@ -26,7 +26,7 @@ def quota_set(id): return {'quota_set': {'id': id, 'metadata_items': 128, - 'ram': 51200, 'floating_ips': 10, + '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, @@ -45,6 +45,7 @@ def test_format_quota_set(self): 'cores': 20, 'ram': 51200, 'floating_ips': 10, + 'fixed_ips': 10, 'metadata_items': 128, 'injected_files': 5, 'injected_file_path_bytes': 255, @@ -61,6 +62,7 @@ def test_format_quota_set(self): self.assertEqual(qs['cores'], 20) self.assertEqual(qs['ram'], 51200) self.assertEqual(qs['floating_ips'], 10) + self.assertEqual(qs['fixed_ips'], 10) self.assertEqual(qs['metadata_items'], 128) self.assertEqual(qs['injected_files'], 5) self.assertEqual(qs['injected_file_path_bytes'], 255) @@ -81,6 +83,7 @@ def test_quotas_defaults(self): 'cores': 20, 'ram': 51200, 'floating_ips': 10, + 'fixed_ips': 10, 'metadata_items': 128, 'injected_files': 5, 'injected_file_path_bytes': 255, @@ -106,12 +109,13 @@ def test_quotas_show_as_unauthorized_user(self): def test_quotas_update_as_admin(self): body = {'quota_set': {'instances': 50, 'cores': 50, 'ram': 51200, '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, '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) @@ -122,7 +126,8 @@ def test_quotas_update_as_admin(self): def test_quotas_update_as_user(self): body = {'quota_set': {'instances': 50, 'cores': 50, 'ram': 51200, 'floating_ips': 10, - 'metadata_items': 128, 'injected_files': 5, + 'fixed_ips': 10, 'metadata_items': 128, + 'injected_files': 5, 'injected_file_content_bytes': 10240, 'security_groups': 10, 'security_group_rules': 20, @@ -134,7 +139,7 @@ def test_quotas_update_as_user(self): def test_quotas_update_invalid_limit(self): body = {'quota_set': {'instances': -2, 'cores': -2, - 'ram': -2, 'floating_ips': -2, + 'ram': -2, 'floating_ips': -2, 'fixed_ips': -2, 'metadata_items': -2, 'injected_files': -2, 'injected_file_content_bytes': -2}} @@ -147,7 +152,8 @@ def test_quotas_update_invalid_value(self): expected_resp = {'quota_set': { 'instances': 50, 'cores': 50, 'ram': 51200, '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, @@ -157,7 +163,8 @@ def test_quotas_update_invalid_value(self): # when PUT JSON format with empty string for quota body = {'quota_set': {'instances': 50, 'cores': 50, 'ram': '', '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, @@ -171,7 +178,8 @@ def test_quotas_update_invalid_value(self): # when PUT XML format with empty string for quota body = {'quota_set': {'instances': 50, 'cores': 50, 'ram': {}, '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, @@ -197,6 +205,7 @@ def test_serializer(self): injected_file_content_bytes=20, ram=50, floating_ips=60, + fixed_ips=10, instances=70, injected_files=80, security_groups=10, @@ -220,6 +229,7 @@ def test_deserializer(self): injected_file_content_bytes='20', ram='50', floating_ips='60', + fixed_ips='10', instances='70', injected_files='80', security_groups='10', @@ -233,6 +243,7 @@ def test_deserializer(self): '</injected_file_content_bytes>' '<ram>50</ram>' '<floating_ips>60</floating_ips>' + '<fixed_ips>10</fixed_ips>' '<instances>70</instances>' '<injected_files>80</injected_files>' '<security_groups>10</security_groups>'
nova/tests/integrated/api_samples/os-quota-class-sets/quota-classes-show-get-resp.json.tpl+1 −0 modified@@ -2,6 +2,7 @@ "quota_class_set": { "cores": 20, "floating_ips": 10, + "fixed_ips": 10, "id": "%(set_id)s", "injected_file_content_bytes": 10240, "injected_file_path_bytes": 255,
nova/tests/integrated/api_samples/os-quota-class-sets/quota-classes-show-get-resp.xml.tpl+1 −0 modified@@ -2,6 +2,7 @@ <quota_class_set id="%(set_id)s"> <cores>20</cores> <floating_ips>10</floating_ips> + <fixed_ips>10</fixed_ips> <injected_file_content_bytes>10240</injected_file_content_bytes> <injected_file_path_bytes>255</injected_file_path_bytes> <injected_files>5</injected_files>
nova/tests/integrated/api_samples/os-quota-class-sets/quota-classes-update-post-req.json.tpl+1 −0 modified@@ -4,6 +4,7 @@ "cores": 50, "ram": 51200, "floating_ips": 10, + "fixed_ips": 10, "metadata_items": 128, "injected_files": 5, "injected_file_content_bytes": 10240,
nova/tests/integrated/api_samples/os-quota-class-sets/quota-classes-update-post-req.xml.tpl+1 −0 modified@@ -2,6 +2,7 @@ <quota_class_set> <cores>50</cores> <floating_ips>10</floating_ips> + <fixed_ips>10</fixed_ips> <injected_file_content_bytes>10240</injected_file_content_bytes> <injected_file_path_bytes>255</injected_file_path_bytes> <injected_files>5</injected_files>
nova/tests/integrated/api_samples/os-quota-class-sets/quota-classes-update-post-resp.json.tpl+1 −0 modified@@ -2,6 +2,7 @@ "quota_class_set": { "cores": 50, "floating_ips": 10, + "fixed_ips": 10, "injected_file_content_bytes": 10240, "injected_file_path_bytes": 255, "injected_files": 5,
nova/tests/integrated/api_samples/os-quota-class-sets/quota-classes-update-post-resp.xml.tpl+1 −0 modified@@ -2,6 +2,7 @@ <quota_class_set> <cores>50</cores> <floating_ips>10</floating_ips> + <fixed_ips>10</fixed_ips> <injected_file_content_bytes>10240</injected_file_content_bytes> <injected_file_path_bytes>255</injected_file_path_bytes> <injected_files>5</injected_files>
nova/tests/integrated/api_samples/os-quota-sets/quotas-show-defaults-get-resp.json.tpl+1 −0 modified@@ -2,6 +2,7 @@ "quota_set": { "cores": 20, "floating_ips": 10, + "fixed_ips": 10, "id": "fake_tenant", "injected_file_content_bytes": 10240, "injected_file_path_bytes": 255,
nova/tests/integrated/api_samples/os-quota-sets/quotas-show-defaults-get-resp.xml.tpl+1 −0 modified@@ -2,6 +2,7 @@ <quota_set id="fake_tenant"> <cores>20</cores> <floating_ips>10</floating_ips> + <fixed_ips>10</fixed_ips> <injected_file_content_bytes>10240</injected_file_content_bytes> <injected_file_path_bytes>255</injected_file_path_bytes> <injected_files>5</injected_files>
nova/tests/integrated/api_samples/os-quota-sets/quotas-show-get-resp.json.tpl+1 −0 modified@@ -2,6 +2,7 @@ "quota_set": { "cores": 20, "floating_ips": 10, + "fixed_ips": 10, "id": "fake_tenant", "injected_file_content_bytes": 10240, "injected_file_path_bytes": 255,
nova/tests/integrated/api_samples/os-quota-sets/quotas-show-get-resp.xml.tpl+1 −0 modified@@ -2,6 +2,7 @@ <quota_set id="fake_tenant"> <cores>20</cores> <floating_ips>10</floating_ips> + <fixed_ips>10</fixed_ips> <injected_file_content_bytes>10240</injected_file_content_bytes> <injected_file_path_bytes>255</injected_file_path_bytes> <injected_files>5</injected_files>
nova/tests/integrated/api_samples/os-quota-sets/quotas-update-post-resp.json.tpl+1 −0 modified@@ -2,6 +2,7 @@ "quota_set": { "cores": 20, "floating_ips": 10, + "fixed_ips": 10, "injected_file_content_bytes": 10240, "injected_file_path_bytes": 255, "injected_files": 5,
nova/tests/integrated/api_samples/os-quota-sets/quotas-update-post-resp.xml.tpl+1 −0 modified@@ -2,6 +2,7 @@ <quota_set> <cores>20</cores> <floating_ips>10</floating_ips> + <fixed_ips>10</fixed_ips> <injected_file_content_bytes>10240</injected_file_content_bytes> <injected_file_path_bytes>255</injected_file_path_bytes> <injected_files>5</injected_files>
nova/tests/network/test_manager.py+17 −0 modified@@ -34,6 +34,7 @@ from nova.openstack.common import log as logging from nova.openstack.common import rpc from nova.openstack.common.rpc import common as rpc_common +from nova import quota from nova import test from nova.tests import fake_ldap from nova.tests import fake_network @@ -286,6 +287,7 @@ def test_add_fixed_ip_instance_using_id_without_vpn(self): self.mox.StubOutWithMock(db, 'virtual_interface_get_by_instance_and_network') self.mox.StubOutWithMock(db, 'fixed_ip_update') + self.mox.StubOutWithMock(quota.QUOTAS, 'reserve') self.mox.StubOutWithMock(db, 'instance_get_by_uuid') self.mox.StubOutWithMock(self.network, 'get_instance_nw_info') @@ -306,6 +308,10 @@ def test_add_fixed_ip_instance_using_id_without_vpn(self): db.fixed_ip_update(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()) + + quota.QUOTAS.reserve(mox.IgnoreArg(), + fixed_ips=mox.IgnoreArg()).AndReturn(None) + db.instance_get_by_uuid(self.context, mox.IgnoreArg()).AndReturn({'display_name': HOST}) @@ -327,6 +333,7 @@ def test_add_fixed_ip_instance_using_uuid_without_vpn(self): self.mox.StubOutWithMock(db, 'virtual_interface_get_by_instance_and_network') self.mox.StubOutWithMock(db, 'fixed_ip_update') + self.mox.StubOutWithMock(quota.QUOTAS, 'reserve') self.mox.StubOutWithMock(db, 'instance_get_by_uuid') self.mox.StubOutWithMock(self.network, 'get_instance_nw_info') @@ -347,6 +354,10 @@ def test_add_fixed_ip_instance_using_uuid_without_vpn(self): db.fixed_ip_update(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()) + + quota.QUOTAS.reserve(mox.IgnoreArg(), + fixed_ips=mox.IgnoreArg()).AndReturn(None) + db.instance_get_by_uuid(self.context, mox.IgnoreArg()).AndReturn({'display_name': HOST}) @@ -414,6 +425,7 @@ def test_instance_dns(self): self.mox.StubOutWithMock(db, 'fixed_ip_update') self.mox.StubOutWithMock(db, 'instance_get_by_uuid') self.mox.StubOutWithMock(self.network, 'get_instance_nw_info') + self.mox.StubOutWithMock(quota.QUOTAS, 'reserve') db.fixed_ip_associate_pool(mox.IgnoreArg(), mox.IgnoreArg(), @@ -432,6 +444,10 @@ def test_instance_dns(self): db.fixed_ip_update(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()) + + quota.QUOTAS.reserve(mox.IgnoreArg(), + fixed_ips=mox.IgnoreArg()).AndReturn(None) + db.instance_get_by_uuid(self.context, mox.IgnoreArg()).AndReturn({'display_name': HOST}) @@ -531,6 +547,7 @@ def test_allocate_fixed_ip(self): db.instance_get_by_uuid(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn({'security_groups': [{'id': 0}]}) + db.fixed_ip_associate_pool(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()).AndReturn('192.168.0.1')
nova/tests/test_quota.py+24 −0 modified@@ -719,6 +719,7 @@ def setUp(self): quota_cores=20, quota_ram=50 * 1024, quota_floating_ips=10, + quota_fixed_ips=10, quota_metadata_items=128, quota_injected_files=5, quota_injected_file_content_bytes=10 * 1024, @@ -745,6 +746,7 @@ def test_get_defaults(self): cores=20, ram=50 * 1024, floating_ips=10, + fixed_ips=10, metadata_items=128, injected_files=5, injected_file_content_bytes=10 * 1024, @@ -778,6 +780,7 @@ def test_get_class_quotas(self): cores=20, ram=25 * 1024, floating_ips=10, + fixed_ips=10, metadata_items=64, injected_files=5, injected_file_content_bytes=5 * 1024, @@ -830,6 +833,7 @@ def fake_qugabp(context, project_id): 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'), @@ -861,6 +865,11 @@ def test_get_project_quotas(self): in_use=2, reserved=0, ), + fixed_ips=dict( + limit=10, + in_use=0, + reserved=0, + ), metadata_items=dict( limit=64, in_use=0, @@ -899,6 +908,7 @@ def test_get_project_quotas(self): )) 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'), @@ -929,6 +939,11 @@ def test_get_project_quotas_alt_context_no_class(self): in_use=2, reserved=0, ), + fixed_ips=dict( + limit=10, + in_use=0, + reserved=0, + ), metadata_items=dict( limit=128, in_use=0, @@ -967,6 +982,7 @@ def test_get_project_quotas_alt_context_no_class(self): )) 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'), @@ -998,6 +1014,11 @@ def test_get_project_quotas_alt_context_with_class(self): in_use=2, reserved=0, ), + fixed_ips=dict( + limit=10, + in_use=0, + reserved=0, + ), metadata_items=dict( limit=64, in_use=0, @@ -1087,6 +1108,9 @@ def test_get_project_quotas_no_usages(self): floating_ips=dict( limit=10, ), + fixed_ips=dict( + limit=10, + ), metadata_items=dict( limit=64, ),
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
23- secunia.com/advisories/52580nvdVendor Advisory
- secunia.com/advisories/52728nvdVendor Advisory
- github.com/advisories/GHSA-63fq-8fp9-vhwqghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2013-1838ghsaADVISORY
- rhn.redhat.com/errata/RHSA-2013-0709.htmlnvdWEB
- ubuntu.com/usn/usn-1771-1nvdWEB
- www.openwall.com/lists/oss-security/2013/03/14/18nvdWEB
- bugs.launchpad.net/nova/+bug/1125468nvdWEB
- bugzilla.redhat.com/show_bug.cginvdWEB
- exchange.xforce.ibmcloud.com/vulnerabilities/82877nvdWEB
- github.com/openstack/nova/commit/9561484166f245d0e4602a36351d6cac72dd9426ghsaWEB
- github.com/openstack/nova/commit/99429214d4ddb5bdc7de185693b8a53ad50df3c6ghsaWEB
- github.com/openstack/nova/commit/efaacdaee116388234558e2682b647d41fe5b149ghsaWEB
- github.com/pypa/advisory-database/tree/main/vulns/nova/PYSEC-2013-44.yamlghsaWEB
- lists.launchpad.net/openstack/msg21892.htmlnvdWEB
- review.openstack.orgghsaWEB
- review.openstack.orgghsaWEB
- review.openstack.orgghsaWEB
- osvdb.org/91303nvd
- www.securityfocus.com/bid/58492nvd
- review.openstack.orgnvd
- review.openstack.orgnvd
- review.openstack.orgnvd
News mentions
0No linked articles in our index yet.