From 0980985b2f3590f60b4726a5419680a1d70f9ead Mon Sep 17 00:00:00 2001 From: LIU Yulong Date: Mon, 19 Sep 2016 11:29:04 +0800 Subject: [PATCH] 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 --- neutron_dynamic_routing/db/bgp_db.py | 66 +++++++++++++++++++ .../services/bgp/bgp_plugin.py | 8 ++- .../tests/unit/db/test_bgp_db.py | 45 +++++++++++++ 3 files changed, 116 insertions(+), 3 deletions(-) diff --git a/neutron_dynamic_routing/db/bgp_db.py b/neutron_dynamic_routing/db/bgp_db.py index a63147be..44a87723 100644 --- a/neutron_dynamic_routing/db/bgp_db.py +++ b/neutron_dynamic_routing/db/bgp_db.py @@ -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 diff --git a/neutron_dynamic_routing/services/bgp/bgp_plugin.py b/neutron_dynamic_routing/services/bgp/bgp_plugin.py index 235429b3..920c0d3d 100644 --- a/neutron_dynamic_routing/services/bgp/bgp_plugin.py +++ b/neutron_dynamic_routing/services/bgp/bgp_plugin.py @@ -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, diff --git a/neutron_dynamic_routing/tests/unit/db/test_bgp_db.py b/neutron_dynamic_routing/tests/unit/db/test_bgp_db.py index 8a7d7b4a..dd2c5729 100644 --- a/neutron_dynamic_routing/tests/unit/db/test_bgp_db.py +++ b/neutron_dynamic_routing/tests/unit/db/test_bgp_db.py @@ -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)