Add check for subnet update with conflict gateway and allocation_pools

Fixes: bug 1062061

The patch will raise exception 'GatewayConflictWithAllocationPools' when
subnet update with conflict gateway and allocation_pools.

Because before validate gateway ip with conflict allocation pools, we need
validate allocation pools first. Move the validation of allocation pools
into _validate_subnet. Then_allocate_pools_for_subnet is only responsible
for pools allocation, and_validate_subnet is responsible for most validate
for subnet.

Change-Id: I8dffe45ce5c02e8e6c317bad470054564538bcf2
This commit is contained in:
mathieu-rohon 2013-02-07 16:05:22 +01:00
parent 7e892502f1
commit 21bf28bc86
3 changed files with 78 additions and 43 deletions

View File

@ -249,3 +249,8 @@ class TooManyExternalNetworks(QuantumException):
class InvalidConfigurationOption(QuantumException): class InvalidConfigurationOption(QuantumException):
message = _("An invalid value was provided for %(opt_name)s: " message = _("An invalid value was provided for %(opt_name)s: "
"%(opt_value)s") "%(opt_value)s")
class GatewayConflictWithAllocationPools(InUse):
message = _("Gateway ip %(ip_address)s conflicts with "
"allocation pool %(pool)s")

View File

@ -706,14 +706,13 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
'cidr': subnet.cidr}) 'cidr': subnet.cidr})
raise q_exc.InvalidInput(error_message=err_msg) raise q_exc.InvalidInput(error_message=err_msg)
def _validate_allocation_pools(self, ip_pools, gateway_ip, subnet_cidr): def _validate_allocation_pools(self, ip_pools, subnet_cidr):
"""Validate IP allocation pools. """Validate IP allocation pools.
Verify start and end address for each allocation pool are valid, Verify start and end address for each allocation pool are valid,
ie: constituted by valid and appropriately ordered IP addresses. ie: constituted by valid and appropriately ordered IP addresses.
Also, verify pools do not overlap among themselves and with the Also, verify pools do not overlap among themselves.
gateway IP. Finally, verify that each range, and the gateway IP, Finally, verify that each range fall within the subnet's CIDR.
fall within the subnet's CIDR.
""" """
@ -760,10 +759,7 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
LOG.debug(_("Checking for overlaps among allocation pools " LOG.debug(_("Checking for overlaps among allocation pools "
"and gateway ip")) "and gateway ip"))
ip_ranges = ip_pools[:] ip_ranges = ip_pools[:]
# Treat gw as IPset as well
if gateway_ip:
ip_ranges.append(gateway_ip)
ip_sets.append(netaddr.IPSet([gateway_ip]))
# Use integer cursors as an efficient way for implementing # Use integer cursors as an efficient way for implementing
# comparison and avoiding comparing the same pair twice # comparison and avoiding comparing the same pair twice
for l_cursor in range(len(ip_sets)): for l_cursor in range(len(ip_sets)):
@ -803,30 +799,23 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
""" """
pools = [] pools = []
if subnet['allocation_pools'] is attributes.ATTR_NOT_SPECIFIED: # Auto allocate the pool around gateway_ip
# Auto allocate the pool around gateway_ip net = netaddr.IPNetwork(subnet['cidr'])
net = netaddr.IPNetwork(subnet['cidr']) first_ip = net.first + 1
first_ip = net.first + 1 last_ip = net.last - 1
last_ip = net.last - 1 gw_ip = int(netaddr.IPAddress(subnet['gateway_ip'] or net.last))
gw_ip = int(netaddr.IPAddress(subnet['gateway_ip'] or net.last)) # Use the gw_ip to find a point for splitting allocation pools
# Use the gw_ip to find a point for splitting allocation pools # for this subnet
# for this subnet split_ip = min(max(gw_ip, net.first), net.last)
split_ip = min(max(gw_ip, net.first), net.last) if split_ip > first_ip:
if split_ip > first_ip: pools.append({'start': str(netaddr.IPAddress(first_ip)),
pools.append({'start': str(netaddr.IPAddress(first_ip)), 'end': str(netaddr.IPAddress(split_ip - 1))})
'end': str(netaddr.IPAddress(split_ip - 1))}) if split_ip < last_ip:
if split_ip < last_ip: pools.append({'start': str(netaddr.IPAddress(split_ip + 1)),
pools.append({'start': str(netaddr.IPAddress(split_ip + 1)), 'end': str(netaddr.IPAddress(last_ip))})
'end': str(netaddr.IPAddress(last_ip))}) # return auto-generated pools
# return auto-generated pools # no need to check for their validity
# no need to check for their validity return pools
return pools
else:
pools = subnet['allocation_pools']
self._validate_allocation_pools(pools,
subnet['gateway_ip'],
subnet['cidr'])
return pools
def _validate_shared_update(self, context, id, original, updated): def _validate_shared_update(self, context, id, original, updated):
# The only case that needs to be validated is when 'shared' # The only case that needs to be validated is when 'shared'
@ -997,14 +986,18 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
def _validate_subnet(self, s): def _validate_subnet(self, s):
"""Validate a subnet spec""" """Validate a subnet spec"""
# This method will validate attributes which may change during
# create_subnet() and update_subnet().
# The method requires the subnet spec 's' has 'ip_version' field. # The method requires the subnet spec 's' has 'ip_version' field.
# If 's' dict does not have 'ip_version' field in an API call # If 's' dict does not have 'ip_version' field in an API call
# (e.g., update_subnet()), you need to set 'ip_version' field # (e.g., update_subnet()), you need to set 'ip_version' field
# before calling this method. # before calling this method.
ip_ver = s['ip_version'] ip_ver = s['ip_version']
if 'cidr' in s: if 'cidr' in s:
self._validate_ip_version(ip_ver, s['cidr'], 'cidr') self._validate_ip_version(ip_ver, s['cidr'], 'cidr')
if attributes.is_attr_set(s.get('gateway_ip')): if attributes.is_attr_set(s.get('gateway_ip')):
self._validate_ip_version(ip_ver, s['gateway_ip'], 'gateway_ip') self._validate_ip_version(ip_ver, s['gateway_ip'], 'gateway_ip')
if (cfg.CONF.force_gateway_on_subnet and if (cfg.CONF.force_gateway_on_subnet and
@ -1036,14 +1029,34 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
for rt in s['host_routes']: for rt in s['host_routes']:
self._validate_host_route(rt, ip_ver) self._validate_host_route(rt, ip_ver)
def create_subnet(self, context, subnet): def _validate_gw_out_of_pools(self, gateway_ip, pools):
s = subnet['subnet'] for allocation_pool in pools:
self._validate_subnet(s) pool_range = netaddr.IPRange(
allocation_pool['start'],
allocation_pool['end'])
if netaddr.IPAddress(gateway_ip) in pool_range:
raise q_exc.GatewayConflictWithAllocationPools(
pool=pool_range,
ip_address=gateway_ip)
def create_subnet(self, context, subnet):
s = subnet['subnet']
net = netaddr.IPNetwork(s['cidr']) net = netaddr.IPNetwork(s['cidr'])
if s['gateway_ip'] is attributes.ATTR_NOT_SPECIFIED: if s['gateway_ip'] is attributes.ATTR_NOT_SPECIFIED:
s['gateway_ip'] = str(netaddr.IPAddress(net.first + 1)) s['gateway_ip'] = str(netaddr.IPAddress(net.first + 1))
if s['allocation_pools'] == attributes.ATTR_NOT_SPECIFIED:
s['allocation_pools'] = self._allocate_pools_for_subnet(context, s)
else:
self._validate_allocation_pools(s['allocation_pools'], s['cidr'])
if s['gateway_ip'] is not None:
self._validate_gw_out_of_pools(s['gateway_ip'],
s['allocation_pools'])
self._validate_subnet(s)
tenant_id = self._get_tenant_id_for_create(context, s) tenant_id = self._get_tenant_id_for_create(context, s)
with context.session.begin(subtransactions=True): with context.session.begin(subtransactions=True):
network = self._get_network(context, s["network_id"]) network = self._get_network(context, s["network_id"])
@ -1061,9 +1074,6 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
'shared': network.shared} 'shared': network.shared}
subnet = models_v2.Subnet(**args) subnet = models_v2.Subnet(**args)
# perform allocate pools first, since it might raise an error
pools = self._allocate_pools_for_subnet(context, s)
context.session.add(subnet) context.session.add(subnet)
if s['dns_nameservers'] is not attributes.ATTR_NOT_SPECIFIED: if s['dns_nameservers'] is not attributes.ATTR_NOT_SPECIFIED:
for addr in s['dns_nameservers']: for addr in s['dns_nameservers']:
@ -1077,7 +1087,8 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
destination=rt['destination'], destination=rt['destination'],
nexthop=rt['nexthop']) nexthop=rt['nexthop'])
context.session.add(route) context.session.add(route)
for pool in pools:
for pool in s['allocation_pools']:
ip_pool = models_v2.IPAllocationPool(subnet=subnet, ip_pool = models_v2.IPAllocationPool(subnet=subnet,
first_ip=pool['start'], first_ip=pool['start'],
last_ip=pool['end']) last_ip=pool['end'])
@ -1096,15 +1107,22 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
gratuitous DHCP offers""" gratuitous DHCP offers"""
s = subnet['subnet'] s = subnet['subnet']
# Fill 'ip_version' field with the current value since db_subnet = self._get_subnet(context, id)
# _validate_subnet() expects subnet spec has 'ip_version' field. # Fill 'ip_version' and 'allocation_pools' fields with the current
s['ip_version'] = self._get_subnet(context, id).ip_version # value since _validate_subnet() expects subnet spec has 'ip_version'
# and 'allocation_pools' fields.
s['ip_version'] = db_subnet.ip_version
s['cidr'] = db_subnet.cidr
self._validate_subnet(s) self._validate_subnet(s)
if 'gateway_ip' in s:
allocation_pools = [{'start': p['first_ip'], 'end': p['last_ip']}
for p in db_subnet.allocation_pools]
self._validate_gw_out_of_pools(s["gateway_ip"], allocation_pools)
with context.session.begin(subtransactions=True): with context.session.begin(subtransactions=True):
if "dns_nameservers" in s: if "dns_nameservers" in s:
old_dns_list = self._get_dns_by_subnet(context, id) old_dns_list = self._get_dns_by_subnet(context, id)
new_dns_addr_set = set(s["dns_nameservers"]) new_dns_addr_set = set(s["dns_nameservers"])
old_dns_addr_set = set([dns['address'] old_dns_addr_set = set([dns['address']
for dns in old_dns_list]) for dns in old_dns_list])

View File

@ -2540,6 +2540,18 @@ class TestSubnetsV2(QuantumDbPluginV2TestCase):
res = req.get_response(self.api) res = req.get_response(self.api)
self.assertEqual(res.status_int, 400) self.assertEqual(res.status_int, 400)
def test_update_subnet_gateway_in_allocation_pool_returns_409(self):
allocation_pools = [{'start': '10.0.0.2', 'end': '10.0.0.254'}]
with self.network() as network:
with self.subnet(network=network,
allocation_pools=allocation_pools,
cidr='10.0.0.0/24') as subnet:
data = {'subnet': {'gateway_ip': '10.0.0.50'}}
req = self.new_update_request('subnets', data,
subnet['subnet']['id'])
res = req.get_response(self.api)
self.assertEquals(res.status_int, 409)
def test_show_subnet(self): def test_show_subnet(self):
with self.network() as network: with self.network() as network:
with self.subnet(network=network) as subnet: with self.subnet(network=network) as subnet: