Adds quotas for fixed IPs on ports

RM11643

Implements quota checking the number of fixed IP addresses allowed on a
port on create and update port, as well as create and update IP Address.
This commit is contained in:
Matt Dietz
2015-03-13 09:10:12 +00:00
parent f361b92f73
commit 256285a9d8
5 changed files with 152 additions and 4 deletions

View File

@@ -54,7 +54,9 @@ quark_resources = [
quota.BaseResource('v4_subnets_per_network',
'quota_v4_subnets_per_network'),
quota.BaseResource('v6_subnets_per_network',
'quota_v6_subnets_per_network')
'quota_v6_subnets_per_network'),
quota.BaseResource('fixed_ips_per_port',
'quota_fixed_ips_per_port')
]
quark_quota_opts = [
@@ -81,7 +83,10 @@ quark_quota_opts = [
help=_('Maximum v4 subnets per network')),
cfg.IntOpt('quota_v6_subnets_per_network',
default=1,
help=_('Maximum v6 subnets per network'))
help=_('Maximum v6 subnets per network')),
cfg.IntOpt('quota_fixed_ips_per_port',
default=5,
help=_('Maximum number of fixed IPs per port'))
]

View File

@@ -14,6 +14,7 @@
# under the License.
from neutron.common import exceptions
from neutron import quota
from oslo.config import cfg
from oslo_log import log as logging
import webob
@@ -65,6 +66,13 @@ def validate_ports_on_network_and_same_segment(ports, network_id):
msg="Segment id's do not match.")
def validate_port_ip_quotas(context, ports):
for port in ports:
addresses = port.get("ip_addresses", [])
quota.QUOTAS.limit_check(context, context.tenant_id,
fixed_ips_per_port=len(addresses) + 1)
def _shared_ip_request(ip_address):
port_ids = ip_address.get('ip_address', {}).get('port_ids', [])
return len(port_ids) > 1
@@ -117,6 +125,7 @@ def create_ip_address(context, body):
net_id=network_id)
validate_ports_on_network_and_same_segment(ports, network_id)
validate_port_ip_quotas(context, ports)
# Shared Ips are only new IPs. Two use cases: if we got device_id
# or if we got port_ids. We should check the case where we got port_ids
@@ -185,6 +194,7 @@ def update_ip_address(context, id, ip_address):
validate_ports_on_network_and_same_segment(ports,
address["network_id"])
validate_port_ip_quotas(context, ports)
LOG.info("Updating IP address, %s, to only be used by the"
"following ports: %s" % (address.address_readable,

View File

@@ -91,6 +91,11 @@ def create_port(context, port):
resource="port", msg="This device is already connected to the "
"requested network via another port")
# Try to fail early on quotas and save ourselves some db overhead
if fixed_ips:
quota.QUOTAS.limit_check(context, context.tenant_id,
fixed_ips_per_port=len(fixed_ips))
if not STRATEGY.is_parent_network(net_id):
# We don't honor segmented networks when they aren't "shared"
segment_id = None
@@ -261,6 +266,13 @@ def update_port(context, id, port):
utils.filter_body(context, port_dict, admin_only=admin_only,
always_filter=always_filter)
# Pre-check the requested fixed_ips before making too many db trips.
# Note that this is the only check we need, since this call replaces
# the entirety of the IP addresses document if fixed_ips are provided.
if fixed_ips:
quota.QUOTAS.limit_check(context, context.tenant_id,
fixed_ips_per_port=len(fixed_ips))
# TODO(anyone): security groups are not currently supported on port create,
# nor on isolated networks today. Please see RM8615
new_security_groups = utils.pop_param(port_dict, "security_groups")

View File

@@ -146,6 +146,45 @@ class TestIpAddresses(test_quark_plugin.TestQuarkPlugin):
self.plugin.create_ip_address(self.context, ip_address)
class TestCreateIpAddressQuotaCheck(test_quark_plugin.TestQuarkPlugin):
@contextlib.contextmanager
def _stubs(self, port, addresses):
port_model = models.Port()
port_model.update(port)
for addr in addresses:
addr_model = models.IPAddress()
addr_model.update(addr)
port_model["ip_addresses"].append(addr_model)
with contextlib.nested(
mock.patch("quark.db.api.port_find"),
mock.patch("quark.plugin_modules.ip_addresses.ipam_driver"),
mock.patch("quark.plugin_modules.ip_addresses.db_api"
".port_associate_ip"),
mock.patch("quark.plugin_modules.ip_addresses"
".validate_ports_on_network_and_same_segment")
) as (port_find, mock_ipam, mock_port_associate_ip, validate):
port_find.return_value = port_model
yield
def test_create_ip_address_with_port_over_quota(self):
addresses = [{"id": ip, "address": ip} for ip in xrange(5)]
port = dict(id=1, network_id=2, ip_addresses=[])
ip = dict(id=1, address=3232235876, address_readable="192.168.1.100",
subnet_id=1, network_id=2, version=4)
with self._stubs(port=port, addresses=addresses):
ip_address = dict(port_ids=[port["id"]])
ip_address['version'] = 4
ip_address['network_id'] = 2
with self.assertRaises(exceptions.OverQuota):
self.plugin.create_ip_address(
self.context, dict(ip_address=ip_address))
@mock.patch("quark.plugin_modules.ip_addresses.v")
@mock.patch("quark.plugin_modules.ip_addresses"
".validate_ports_on_network_and_same_segment")
@@ -514,6 +553,55 @@ class TestQuarkUpdateIPAddress(test_quark_plugin.TestQuarkPlugin):
self.assertEqual(response['port_ids'], [])
class TestQuarkUpdateIPAddressQuotaCheck(test_quark_plugin.TestQuarkPlugin):
@contextlib.contextmanager
def _stubs(self, port, addresses):
port_models = []
addr_model = None
port_model = models.Port()
port_model.update(port)
port_models.append(port_model)
for addr in addresses:
addr_model = models.IPAddress()
addr_model.update(addr)
port_model["ip_addresses"].append(addr_model)
db_mod = "quark.db.api"
with contextlib.nested(
mock.patch("%s.port_find" % db_mod),
mock.patch("%s.ip_address_find" % db_mod),
mock.patch("%s.port_associate_ip" % db_mod),
mock.patch("%s.port_disassociate_ip" % db_mod),
mock.patch("quark.plugin_modules.ip_addresses"
".validate_ports_on_network_and_same_segment"),
mock.patch("quark.plugin_modules.ip_addresses.ipam_driver")
) as (port_find, ip_find, port_associate_ip, port_disassociate_ip, val,
mock_ipam):
port_find.return_value = port_models
ip_find.return_value = addr_model
port_associate_ip.side_effect = _port_associate_stub
port_disassociate_ip.side_effect = _port_disassociate_stub
mock_ipam.deallocate_ip_address.side_effect = (
_ip_deallocate_stub)
yield
def test_update_ip_address_port_over_quota(self):
addresses = [{"id": ip, "address": ip} for ip in xrange(5)]
port = dict(id=1, network_id=2)
ip = dict(id=1, address=3232235876, address_readable="192.168.1.100",
subnet_id=1, network_id=2, version=4, deallocated=1,
deallocated_at='2020-01-01 00:00:00')
with self._stubs(port=port, addresses=addresses):
ip_address = {'ip_address': {"port_ids": [port]}}
with self.assertRaises(exceptions.OverQuota):
self.plugin.update_ip_address(self.admin_context, ip['id'],
ip_address)
class TestQuarkGetIpAddress(test_quark_plugin.TestQuarkPlugin):
@contextlib.contextmanager
def _stubs(self, ips, ports):

View File

@@ -712,6 +712,26 @@ class TestQuarkPortCreateQuota(test_quark_plugin.TestQuarkPlugin):
self.plugin.create_port(self.context, port)
class TestQuarkPortCreateFixedIpsQuota(test_quark_plugin.TestQuarkPlugin):
@contextlib.contextmanager
def _stubs(self, network):
network["network_plugin"] = "BASE"
network["ipam_strategy"] = "ANY"
with mock.patch("quark.db.api.network_find") as net_find:
net_find.return_value = network
yield
def test_create_port_fixed_ips_over_quota(self):
network = {"id": 1, "tenant_id": self.context.tenant_id}
fixed_ips = [{"subnet_id": 1}, {"subnet_id": 1}, {"subnet_id": 1},
{"subnet_id": 1}, {"subnet_id": 1}, {"subnet_id": 1}]
port = {"port": {"network_id": 1, "tenant_id": self.context.tenant_id,
"device_id": 2, "fixed_ips": fixed_ips}}
with self._stubs(network=network):
with self.assertRaises(exceptions.OverQuota):
self.plugin.create_port(self.context, port)
class TestQuarkUpdatePort(test_quark_plugin.TestQuarkPlugin):
@contextlib.contextmanager
def _stubs(self, port, new_ips=None, parent_net=False):
@@ -728,8 +748,7 @@ class TestQuarkUpdatePort(test_quark_plugin.TestQuarkPlugin):
mock.patch("quark.db.api.port_update"),
mock.patch("quark.ipam.QuarkIpam.allocate_ip_address"),
mock.patch("quark.ipam.QuarkIpam.deallocate_ips_by_port"),
mock.patch("neutron.quota.QuotaEngine.limit_check"),
) as (port_find, port_update, alloc_ip, dealloc_ip, limit_check):
) as (port_find, port_update, alloc_ip, dealloc_ip):
port_find.return_value = port_model
port_update.return_value = port_model
if new_ips:
@@ -854,6 +873,20 @@ class TestQuarkUpdatePort(test_quark_plugin.TestQuarkPlugin):
self.assertEqual(port_res["fixed_ips"][1]["ip_address"],
str(ip2.ipv6()))
def test_update_port_goes_over_quota(self):
fixed_ips = {"fixed_ips": [{"subnet_id": 1},
{"subnet_id": 1},
{"subnet_id": 1},
{"subnet_id": 1},
{"subnet_id": 1},
{"subnet_id": 1}]}
with self._stubs(
port=dict(id=1, name="myport", mac_address="0:0:0:0:0:1")
) as (port_find, port_update, alloc_ip, dealloc_ip):
new_port = {"port": fixed_ips}
with self.assertRaises(exceptions.OverQuota):
self.plugin.update_port(self.context, 1, new_port)
class TestQuarkUpdatePortSecurityGroups(test_quark_plugin.TestQuarkPlugin):
@contextlib.contextmanager