Let the bgp_plugin to query floating IP bgp next_hop

When a dvr floating IP is associated to a port, the bgp plugin
`floatingip_update_callback` will immediately send a notification
`start_route_advertisements` to DR agent, there is a new floating
IP bgp route.
But this bgp route is not correct, its next_hop is set to dvr router
snat gateway IP address. And then after `periodic_interval` seconds,
the DR agent will resync that DVR fip route with new next_hop to the
correct FIP namespace fg-device IP address.

This patch will let the bgp_plugin to handle the floating IP bgp route
next_hop query.

Change-Id: Ic6bb7f4263c6e2da315178be2ed041eb7020c905
Closes-bug: #1615919
This commit is contained in:
LIU Yulong 2016-09-19 11:29:04 +08:00 committed by Ryan Tidwell
parent 130861b86b
commit 0980985b2f
3 changed files with 116 additions and 3 deletions

View File

@ -13,6 +13,7 @@
# under the License.
import itertools
import netaddr
from oslo_db import exception as oslo_db_exc
from oslo_utils import uuidutils
@ -30,8 +31,10 @@ from neutron_lib import exceptions as n_exc
from neutron.db import common_db_mixin as common_db
from neutron.db import l3_attrs_db
from neutron.db import l3_db
from neutron.db import l3_dvr_db
from neutron.db.models import address_scope as address_scope_db
from neutron.db import models_v2
from neutron.extensions import l3 as l3_ext
from neutron.plugins.ml2 import models as ml2_models
from neutron_dynamic_routing._i18n import _
@ -1016,3 +1019,66 @@ class BgpDbMixin(common_db.CommonDbMixin):
"""Return the list of host routes given a list of (IP, nexthop)"""
return ({'destination': x + '/32',
'next_hop': y} for x, y in ip_next_hop_tuples)
def _get_router(self, context, router_id):
try:
router = self._get_by_id(context, l3_db.Router, router_id)
except sa_exc.NoResultFound:
raise l3_ext.RouterNotFound(router_id=router_id)
return router
def _get_fip_next_hop(self, context, router_id, ip_address=None):
router = self._get_router(context, router_id)
gw_port = router.gw_port
if not gw_port:
return
if l3_dvr_db.is_distributed_router(router) and ip_address:
return self._get_dvr_fip_next_hop(context, ip_address)
for fixed_ip in gw_port.fixed_ips:
addr = netaddr.IPAddress(fixed_ip.ip_address)
if addr.version == 4:
return fixed_ip.ip_address
def _get_dvr_fip_agent_gateway_query(self, context):
ML2PortBinding = ml2_models.PortBinding
IpAllocation = models_v2.IPAllocation
Port = models_v2.Port
base_query = context.session.query(Port.network_id,
ML2PortBinding.host,
IpAllocation.ip_address)
gw_query = base_query.filter(
ML2PortBinding.port_id == Port.id,
IpAllocation.port_id == Port.id,
Port.device_owner == lib_consts.DEVICE_OWNER_AGENT_GW)
return gw_query
def _get_fip_fixed_port_host_query(self, context, fip_address):
ML2PortBinding = ml2_models.PortBinding
fip_query = context.session.query(
l3_db.FloatingIP.floating_network_id,
ML2PortBinding.host,
l3_db.FloatingIP.floating_ip_address)
fip_query = fip_query.filter(
l3_db.FloatingIP.fixed_port_id == ML2PortBinding.port_id,
l3_db.FloatingIP.floating_ip_address == fip_address)
return fip_query
def _get_dvr_fip_next_hop(self, context, fip_address):
try:
dvr_agent_gw_query = self._get_dvr_fip_agent_gateway_query(
context)
fip_fix_port_query = self._get_fip_fixed_port_host_query(
context, fip_address)
q = self._join_fip_by_host_binding_to_agent_gateway(
context,
fip_fix_port_query.subquery(),
dvr_agent_gw_query.subquery()).one()
return q[1]
except sa_exc.NoResultFound:
return
except sa_exc.MultipleResultsFound:
return

View File

@ -223,8 +223,8 @@ class BgpPlugin(service_base.ServicePluginBase,
ctx = context.get_admin_context()
new_router_id = kwargs['router_id']
last_router_id = kwargs['last_known_router_id']
next_hop = kwargs['next_hop']
dest = kwargs['floating_ip_address'] + '/32'
floating_ip_address = kwargs['floating_ip_address']
dest = floating_ip_address + '/32'
bgp_speakers = self._bgp_speakers_for_gw_network_by_family(
ctx,
kwargs['floating_network_id'],
@ -235,7 +235,9 @@ class BgpPlugin(service_base.ServicePluginBase,
self.stop_route_advertisements(ctx, self._bgp_rpc,
bgp_speaker.id, [dest])
if next_hop and new_router_id != last_router_id:
if new_router_id and new_router_id != last_router_id:
next_hop = self._get_fip_next_hop(
ctx, new_router_id, floating_ip_address)
new_host_route = {'destination': dest, 'next_hop': next_hop}
for bgp_speaker in bgp_speakers:
self.start_route_advertisements(ctx, self._bgp_rpc,

View File

@ -1213,3 +1213,48 @@ class BgpTests(test_plugin.Ml2PluginV2TestCase,
def test_ha_router_fips_has_no_next_hop_to_fip_agent_gateway(self):
self._test_legacy_router_fips_next_hop(router_ha=True)
def _test__get_fip_next_hop(self, distributed=False):
gw_prefix = '172.16.10.0/24'
tenant_prefix = '10.10.10.0/24'
tenant_id = _uuid()
agent_confs = [{"host": "compute1", "mode": "dvr"},
{"host": "network1", "mode": "dvr_snat"}]
self._create_scenario_test_l3_agents(agent_confs)
with self.router_with_external_and_tenant_networks(
tenant_id=tenant_id,
gw_prefix=gw_prefix,
tenant_prefix=tenant_prefix,
distributed=distributed) as res:
router, ext_net, int_net = res
ext_gw_info = router['external_gateway_info']
legacy_gw_ip = ext_gw_info[
'external_fixed_ips'][0]['ip_address']
gw_net_id = ext_net['network']['id']
# Whatever the router type is, we always create such dvr fip agent
# gateway port, in order to make sure that legacy router fip bgp
# route next_hop is not the dvr_fip_agent_gw.
fip_gw = self.l3plugin.create_fip_agent_gw_port_if_not_exists(
self.context, gw_net_id, 'compute1')
port_configs = [{'net_id': int_net['network']['id'],
'host': 'compute1'}]
ports = self._create_scenario_test_ports(tenant_id, port_configs)
dvr_gw_ip = fip_gw['fixed_ips'][0]['ip_address']
fip_data = {'floatingip': {'floating_network_id': gw_net_id,
'tenant_id': tenant_id,
'port_id': ports[0]['id']}}
fip = self.l3plugin.create_floatingip(self.context, fip_data)
fip_address = fip['floating_ip_address']
with self.bgp_speaker(4, 1234, networks=[gw_net_id]):
next_hop = self.bgp_plugin._get_fip_next_hop(
self.context, router['id'], fip_address)
if distributed:
self.assertEqual(dvr_gw_ip, next_hop)
else:
self.assertEqual(legacy_gw_ip, next_hop)
def test__get_fip_next_hop_legacy(self):
self._test__get_fip_next_hop()
def test__get_fip_next_hop_dvr(self):
self._test__get_fip_next_hop(distributed=True)