bug 1057844: improve floating-ip association checks

allow multiple floating ips to be associated with the same internal port
as long as they map to different external nets (not yet supported in
Folsom) or different internal fixed IPs.  With Quantum, there is no
need to disallow either scenario.

Also improve check for a valid external network to router to internal
subnet path when a floating IP is bound.

Change-Id: Iced675e1f064172ee8a5bb6b9e37032e83af5711
This commit is contained in:
Dan Wendlandt 2012-11-20 14:09:19 -08:00
parent b23940c319
commit 86436a661b
3 changed files with 49 additions and 41 deletions

View File

@ -416,34 +416,35 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
'fixed_ip_address': floatingip['fixed_ip_address']}
return self._fields(res, fields)
def _get_router_for_internal_subnet(self, context, internal_port,
internal_subnet_id):
def _get_router_for_floatingip(self, context, internal_port,
internal_subnet_id,
external_network_id):
subnet_db = self._get_subnet(context, internal_subnet_id)
if not subnet_db['gateway_ip']:
msg = ('Cannot add floating IP to port on subnet %s '
'which has no gateway_ip' % internal_subnet_id)
raise q_exc.BadRequest(resource='floatingip', msg=msg)
#FIXME(danwent): can do join, but cannot use standard F-K syntax?
# just do it inefficiently for now
port_qry = context.session.query(models_v2.Port)
ports = port_qry.filter_by(network_id=internal_port['network_id'])
for p in ports:
ips = [ip['ip_address'] for ip in p['fixed_ips']]
if len(ips) != 1:
continue
fixed = p['fixed_ips'][0]
if (fixed['ip_address'] == subnet_db['gateway_ip'] and
fixed['subnet_id'] == internal_subnet_id):
router_qry = context.session.query(Router)
try:
router = router_qry.filter_by(id=p['device_id']).one()
return router['id']
except exc.NoResultFound:
pass
# find router interface ports on this network
router_intf_qry = context.session.query(models_v2.Port)
router_intf_ports = router_intf_qry.filter_by(
network_id=internal_port['network_id'],
device_owner=DEVICE_OWNER_ROUTER_INTF)
for intf_p in router_intf_ports:
if intf_p['fixed_ips'][0]['subnet_id'] == internal_subnet_id:
router_id = intf_p['device_id']
router_gw_qry = context.session.query(models_v2.Port)
has_gw_port = router_gw_qry.filter_by(
network_id=external_network_id,
device_id=router_id,
device_owner=DEVICE_OWNER_ROUTER_GW).count()
if has_gw_port:
return router_id
raise l3.ExternalGatewayForFloatingIPNotFound(
subnet_id=internal_subnet_id,
external_network_id=external_network_id,
port_id=internal_port['id'])
def get_assoc_data(self, context, fip, floating_network_id):
@ -491,9 +492,10 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
internal_ip_address = internal_port['fixed_ips'][0]['ip_address']
internal_subnet_id = internal_port['fixed_ips'][0]['subnet_id']
router_id = self._get_router_for_internal_subnet(context,
internal_port,
internal_subnet_id)
router_id = self._get_router_for_floatingip(context,
internal_port,
internal_subnet_id,
floating_network_id)
# confirm that this router has a floating
# ip enabled gateway with support for this floating IP network
try:
@ -516,17 +518,24 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
msg = "fixed_ip_address cannot be specified without a port_id"
raise q_exc.BadRequest(resource='floatingip', msg=msg)
if 'port_id' in fip and fip['port_id']:
port_qry = context.session.query(FloatingIP)
try:
port_qry.filter_by(fixed_port_id=fip['port_id']).one()
raise l3.FloatingIPPortAlreadyAssociated(
port_id=fip['port_id'])
except exc.NoResultFound:
pass
port_id, internal_ip_address, router_id = self.get_assoc_data(
context,
fip,
floatingip_db['floating_network_id'])
fip_qry = context.session.query(FloatingIP)
try:
fip_qry.filter_by(
fixed_port_id=fip['port_id'],
floating_network_id=floatingip_db['floating_network_id'],
fixed_ip_address=internal_ip_address).one()
raise l3.FloatingIPPortAlreadyAssociated(
port_id=fip['port_id'],
fip_id=floatingip_db['id'],
floating_ip_address=floatingip_db['floating_ip_address'],
fixed_ip=internal_ip_address,
net_id=floatingip_db['floating_network_id'])
except exc.NoResultFound:
pass
floatingip_db.update({'fixed_ip_address': internal_ip_address,
'fixed_port_id': port_id,
'router_id': router_id})

View File

@ -44,14 +44,16 @@ class FloatingIPNotFound(qexception.NotFound):
class ExternalGatewayForFloatingIPNotFound(qexception.NotFound):
message = _("Could not find an external network gateway reachable "
message = _("External network %(external_network_id)s is not reachable "
"from subnet %(subnet_id)s. Therefore, cannot associate "
"Port %(port_id)s with a Floating IP.")
class FloatingIPPortAlreadyAssociated(qexception.InUse):
message = _("Port %(port_id)s already has a floating IP"
" associated with it")
message = _("Cannot associate floating IP %(floating_ip_address)s "
"(%(fip_id)s) with port %(port_id)s "
"using fixed IP %(fixed_ip)s, as that fixed IP already "
"has a floating IP on external network %(net_id)s.")
class L3PortInUse(qexception.InUse):

View File

@ -949,16 +949,13 @@ class L3NatDBTestCase(test_db_plugin.QuantumDbPluginV2TestCase):
self.assertEquals(body['floatingip']['fixed_ip_address'], None)
self.assertEquals(body['floatingip']['router_id'], None)
def test_double_floating_assoc(self):
def test_two_fips_one_port_invalid_return_409(self):
with self.floatingip_with_assoc() as fip1:
with self.subnet() as s:
with self.floatingip_no_assoc(s) as fip2:
port_id = fip1['floatingip']['port_id']
body = self._update('floatingips',
fip2['floatingip']['id'],
{'floatingip':
{'port_id': port_id}},
expected_code=exc.HTTPConflict.code)
res = self._create_floatingip(
'json',
fip1['floatingip']['floating_network_id'],
fip1['floatingip']['port_id'])
self.assertEqual(res.status_int, exc.HTTPConflict.code)
def test_floating_ip_direct_port_delete_returns_409(self):
found = False