Queries for DVR-aware floating IP next-hop lookups
Introduce calls to collect host routes for floating IP's in a DVR-aware way. When a floating IP is associated through a distributed router, the next-hop for the floating IP is announced as the floating IP agent gateway port on the corresponding host. Change-Id: I654e4d2496e19c011653eedb87c9b6026f801e9f Implements: blueprint bgp-dynamic-routing DocImpact: Neutron BGP should have docs created for the feature
This commit is contained in:
parent
8bf46be67c
commit
2e11d85d6b
@ -34,6 +34,7 @@ from neutron.db import l3_db
|
||||
from neutron.db import model_base
|
||||
from neutron.db import models_v2
|
||||
from neutron.extensions import bgp as bgp_ext
|
||||
from neutron.plugins.ml2 import models as ml2_models
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -464,7 +465,10 @@ class BgpDbMixin(common_db.CommonDbMixin):
|
||||
fip_routes = self._get_central_fip_host_routes_by_bgp_speaker(
|
||||
context,
|
||||
bgp_speaker_id)
|
||||
return itertools.chain(fip_routes, net_routes)
|
||||
dvr_fip_routes = self._get_dvr_fip_host_routes_by_bgp_speaker(
|
||||
context,
|
||||
bgp_speaker_id)
|
||||
return itertools.chain(fip_routes, net_routes, dvr_fip_routes)
|
||||
|
||||
def get_routes_by_bgp_speaker_binding(self, context,
|
||||
bgp_speaker_id, network_id):
|
||||
@ -478,7 +482,11 @@ class BgpDbMixin(common_db.CommonDbMixin):
|
||||
context,
|
||||
network_id,
|
||||
bgp_speaker_id)
|
||||
return itertools.chain(fip_routes, net_routes)
|
||||
dvr_fip_routes = self._get_dvr_fip_host_routes_by_binding(
|
||||
context,
|
||||
network_id,
|
||||
bgp_speaker_id)
|
||||
return itertools.chain(fip_routes, net_routes, dvr_fip_routes)
|
||||
|
||||
def _get_routes_by_router(self, context, router_id):
|
||||
bgp_speaker_ids = self._get_bgp_speaker_ids_by_router(context,
|
||||
@ -493,8 +501,12 @@ class BgpDbMixin(common_db.CommonDbMixin):
|
||||
context,
|
||||
router_id,
|
||||
bgp_speaker_id)
|
||||
routes = list(itertools.chain(fip_routes, net_routes))
|
||||
route_dict[bgp_speaker_id] = routes
|
||||
dvr_fip_routes = self._get_dvr_fip_host_routes_by_router(
|
||||
context,
|
||||
router_id,
|
||||
bgp_speaker_id)
|
||||
routes = itertools.chain(fip_routes, net_routes, dvr_fip_routes)
|
||||
route_dict[bgp_speaker_id] = list(routes)
|
||||
return route_dict
|
||||
|
||||
def _get_central_fip_host_routes_by_router(self, context, router_id,
|
||||
@ -533,6 +545,21 @@ class BgpDbMixin(common_db.CommonDbMixin):
|
||||
query = query.filter(router_attrs.distributed != sa.sql.true())
|
||||
return self._host_route_list_from_tuples(query.all())
|
||||
|
||||
def _get_dvr_fip_host_routes_by_router(self, context, bgp_speaker_id,
|
||||
router_id):
|
||||
with context.session.begin(subtransactions=True):
|
||||
gw_query = self._get_gateway_query(context, bgp_speaker_id)
|
||||
|
||||
fip_query = self._get_fip_query(context, bgp_speaker_id)
|
||||
fip_query.filter(l3_db.FloatingIP.router_id == router_id)
|
||||
|
||||
#Create the join query
|
||||
join_query = self._join_fip_by_host_binding_to_agent_gateway(
|
||||
context,
|
||||
fip_query.subquery(),
|
||||
gw_query.subquery())
|
||||
return self._host_route_list_from_tuples(join_query.all())
|
||||
|
||||
def _get_central_fip_host_routes_by_binding(self, context,
|
||||
network_id, bgp_speaker_id):
|
||||
"""Get all floating IP host routes for the given network binding."""
|
||||
@ -572,6 +599,24 @@ class BgpDbMixin(common_db.CommonDbMixin):
|
||||
query = query.filter(router_attrs.distributed != sa.sql.true())
|
||||
return self._host_route_list_from_tuples(query.all())
|
||||
|
||||
def _get_dvr_fip_host_routes_by_binding(self, context, network_id,
|
||||
bgp_speaker_id):
|
||||
with context.session.begin(subtransactions=True):
|
||||
BgpBinding = BgpSpeakerNetworkBinding
|
||||
|
||||
gw_query = self._get_gateway_query(context, bgp_speaker_id)
|
||||
gw_query.filter(BgpBinding.network_id == network_id)
|
||||
|
||||
fip_query = self._get_fip_query(context, bgp_speaker_id)
|
||||
fip_query.filter(BgpBinding.network_id == network_id)
|
||||
|
||||
#Create the join query
|
||||
join_query = self._join_fip_by_host_binding_to_agent_gateway(
|
||||
context,
|
||||
fip_query.subquery(),
|
||||
gw_query.subquery())
|
||||
return self._host_route_list_from_tuples(join_query.all())
|
||||
|
||||
def _get_central_fip_host_routes_by_bgp_speaker(self, context,
|
||||
bgp_speaker_id):
|
||||
"""Get all the floating IP host routes advertised by a BgpSpeaker."""
|
||||
@ -613,6 +658,65 @@ class BgpDbMixin(common_db.CommonDbMixin):
|
||||
query = query.filter(router_attrs.distributed != sa.sql.true())
|
||||
return self._host_route_list_from_tuples(query.all())
|
||||
|
||||
def _get_gateway_query(self, context, bgp_speaker_id):
|
||||
BgpBinding = BgpSpeakerNetworkBinding
|
||||
ML2PortBinding = ml2_models.PortBinding
|
||||
IpAllocation = models_v2.IPAllocation
|
||||
Port = models_v2.Port
|
||||
gw_query = context.session.query(Port.network_id,
|
||||
ML2PortBinding.host,
|
||||
IpAllocation.ip_address)
|
||||
|
||||
#Subquery for FIP agent gateway ports
|
||||
gw_query = gw_query.filter(
|
||||
ML2PortBinding.port_id == Port.id,
|
||||
IpAllocation.port_id == Port.id,
|
||||
IpAllocation.subnet_id == models_v2.Subnet.id,
|
||||
models_v2.Subnet.ip_version == 4,
|
||||
Port.device_owner == lib_consts.DEVICE_OWNER_AGENT_GW,
|
||||
Port.network_id == BgpBinding.network_id,
|
||||
BgpBinding.bgp_speaker_id == bgp_speaker_id,
|
||||
BgpBinding.ip_version == 4)
|
||||
return gw_query
|
||||
|
||||
def _get_fip_query(self, context, bgp_speaker_id):
|
||||
BgpBinding = BgpSpeakerNetworkBinding
|
||||
ML2PortBinding = ml2_models.PortBinding
|
||||
|
||||
#Subquery for floating IP's
|
||||
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_network_id == BgpBinding.network_id,
|
||||
BgpBinding.bgp_speaker_id == bgp_speaker_id)
|
||||
return fip_query
|
||||
|
||||
def _get_dvr_fip_host_routes_by_bgp_speaker(self, context,
|
||||
bgp_speaker_id):
|
||||
with context.session.begin(subtransactions=True):
|
||||
gw_query = self._get_gateway_query(context, bgp_speaker_id)
|
||||
fip_query = self._get_fip_query(context, bgp_speaker_id)
|
||||
|
||||
#Create the join query
|
||||
join_query = self._join_fip_by_host_binding_to_agent_gateway(
|
||||
context,
|
||||
fip_query.subquery(),
|
||||
gw_query.subquery())
|
||||
return self._host_route_list_from_tuples(join_query.all())
|
||||
|
||||
def _join_fip_by_host_binding_to_agent_gateway(self, context,
|
||||
fip_subq, gw_subq):
|
||||
join_query = context.session.query(fip_subq.c.floating_ip_address,
|
||||
gw_subq.c.ip_address)
|
||||
and_cond = and_(
|
||||
gw_subq.c.host == fip_subq.c.host,
|
||||
gw_subq.c.network_id == fip_subq.c.floating_network_id)
|
||||
|
||||
return join_query.join(gw_subq, and_cond)
|
||||
|
||||
def _get_tenant_network_routes_by_binding(self, context,
|
||||
network_id, bgp_speaker_id):
|
||||
"""Get all tenant network routes for the given network."""
|
||||
|
@ -20,6 +20,7 @@ from neutron.api.v2 import attributes as attrs
|
||||
from neutron.common import exceptions as n_exc
|
||||
from neutron.extensions import bgp
|
||||
from neutron.extensions import external_net
|
||||
from neutron.extensions import portbindings
|
||||
from neutron import manager
|
||||
from neutron.plugins.common import constants as p_const
|
||||
from neutron.services.bgp import bgp_plugin
|
||||
@ -145,6 +146,11 @@ class BgpTests(test_plugin.Ml2PluginV2TestCase,
|
||||
BgpEntityCreationMixin):
|
||||
fmt = 'json'
|
||||
|
||||
def setup_parent(self):
|
||||
self.l3_plugin = ('neutron.tests.unit.extensions.test_l3.'
|
||||
'TestL3NatAgentSchedulingServicePlugin')
|
||||
super(BgpTests, self).setup_parent()
|
||||
|
||||
def setUp(self):
|
||||
super(BgpTests, self).setUp()
|
||||
self.l3plugin = manager.NeutronManager.get_service_plugins().get(
|
||||
@ -775,6 +781,184 @@ class BgpTests(test_plugin.Ml2PluginV2TestCase,
|
||||
self.assertTrue(tenant_prefix_found)
|
||||
self.assertTrue(fip_prefix_found)
|
||||
|
||||
def test_get_routes_by_bgp_speaker_id_with_fip_dvr(self):
|
||||
gw_prefix = '172.16.10.0/24'
|
||||
tenant_prefix = '10.10.10.0/24'
|
||||
tenant_id = _uuid()
|
||||
scope_data = {'tenant_id': tenant_id, 'ip_version': 4,
|
||||
'shared': True, 'name': 'bgp-scope'}
|
||||
scope = self.plugin.create_address_scope(
|
||||
self.context,
|
||||
{'address_scope': scope_data})
|
||||
with self.router_with_external_and_tenant_networks(
|
||||
tenant_id=tenant_id,
|
||||
gw_prefix=gw_prefix,
|
||||
tenant_prefix=tenant_prefix,
|
||||
address_scope=scope,
|
||||
distributed=True) as res:
|
||||
router, ext_net, int_net = res
|
||||
ext_gw_info = router['external_gateway_info']
|
||||
gw_net_id = ext_net['network']['id']
|
||||
tenant_net_id = int_net['network']['id']
|
||||
fixed_port_data = {'port':
|
||||
{'name': 'test',
|
||||
'network_id': tenant_net_id,
|
||||
'tenant_id': tenant_id,
|
||||
'admin_state_up': True,
|
||||
'device_id': _uuid(),
|
||||
'device_owner': 'compute:nova',
|
||||
'mac_address': attrs.ATTR_NOT_SPECIFIED,
|
||||
'fixed_ips': attrs.ATTR_NOT_SPECIFIED,
|
||||
portbindings.HOST_ID: 'test-host'}}
|
||||
fixed_port = self.plugin.create_port(self.context,
|
||||
fixed_port_data)
|
||||
self.plugin._create_or_update_agent(self.context,
|
||||
{'agent_type': 'L3 agent',
|
||||
'host': 'test-host',
|
||||
'binary': 'neutron-l3-agent',
|
||||
'topic': 'test'})
|
||||
fip_gw = self.l3plugin.create_fip_agent_gw_port_if_not_exists(
|
||||
self.context,
|
||||
gw_net_id,
|
||||
'test-host')
|
||||
fip_data = {'floatingip': {'floating_network_id': gw_net_id,
|
||||
'tenant_id': tenant_id,
|
||||
'port_id': fixed_port['id']}}
|
||||
fip = self.l3plugin.create_floatingip(self.context, fip_data)
|
||||
fip_prefix = fip['floating_ip_address'] + '/32'
|
||||
with self.bgp_speaker(4, 1234, networks=[gw_net_id]) as speaker:
|
||||
bgp_speaker_id = speaker['id']
|
||||
routes = self.bgp_plugin.get_routes_by_bgp_speaker_id(
|
||||
self.context,
|
||||
bgp_speaker_id)
|
||||
routes = list(routes)
|
||||
cvr_gw_ip = ext_gw_info['external_fixed_ips'][0]['ip_address']
|
||||
dvr_gw_ip = fip_gw['fixed_ips'][0]['ip_address']
|
||||
self.assertEqual(2, len(routes))
|
||||
tenant_route_verified = False
|
||||
fip_route_verified = False
|
||||
for route in routes:
|
||||
if route['destination'] == tenant_prefix:
|
||||
self.assertEqual(cvr_gw_ip, route['next_hop'])
|
||||
tenant_route_verified = True
|
||||
if route['destination'] == fip_prefix:
|
||||
self.assertEqual(dvr_gw_ip, route['next_hop'])
|
||||
fip_route_verified = True
|
||||
self.assertTrue(tenant_route_verified)
|
||||
self.assertTrue(fip_route_verified)
|
||||
|
||||
def test__get_dvr_fip_host_routes_by_binding(self):
|
||||
gw_prefix = '172.16.10.0/24'
|
||||
tenant_prefix = '10.10.10.0/24'
|
||||
tenant_id = _uuid()
|
||||
scope_data = {'tenant_id': tenant_id, 'ip_version': 4,
|
||||
'shared': True, 'name': 'bgp-scope'}
|
||||
scope = self.plugin.create_address_scope(
|
||||
self.context,
|
||||
{'address_scope': scope_data})
|
||||
with self.router_with_external_and_tenant_networks(
|
||||
tenant_id=tenant_id,
|
||||
gw_prefix=gw_prefix,
|
||||
tenant_prefix=tenant_prefix,
|
||||
address_scope=scope,
|
||||
distributed=True) as res:
|
||||
router, ext_net, int_net = res
|
||||
gw_net_id = ext_net['network']['id']
|
||||
tenant_net_id = int_net['network']['id']
|
||||
fixed_port_data = {'port':
|
||||
{'name': 'test',
|
||||
'network_id': tenant_net_id,
|
||||
'tenant_id': tenant_id,
|
||||
'admin_state_up': True,
|
||||
'device_id': _uuid(),
|
||||
'device_owner': 'compute:nova',
|
||||
'mac_address': attrs.ATTR_NOT_SPECIFIED,
|
||||
'fixed_ips': attrs.ATTR_NOT_SPECIFIED,
|
||||
portbindings.HOST_ID: 'test-host'}}
|
||||
fixed_port = self.plugin.create_port(self.context,
|
||||
fixed_port_data)
|
||||
self.plugin._create_or_update_agent(self.context,
|
||||
{'agent_type': 'L3 agent',
|
||||
'host': 'test-host',
|
||||
'binary': 'neutron-l3-agent',
|
||||
'topic': 'test'})
|
||||
fip_gw = self.l3plugin.create_fip_agent_gw_port_if_not_exists(
|
||||
self.context,
|
||||
gw_net_id,
|
||||
'test-host')
|
||||
fip_data = {'floatingip': {'floating_network_id': gw_net_id,
|
||||
'tenant_id': tenant_id,
|
||||
'port_id': fixed_port['id']}}
|
||||
fip = self.l3plugin.create_floatingip(self.context, fip_data)
|
||||
fip_prefix = fip['floating_ip_address'] + '/32'
|
||||
with self.bgp_speaker(4, 1234, networks=[gw_net_id]) as speaker:
|
||||
bgp_speaker_id = speaker['id']
|
||||
routes = self.bgp_plugin._get_dvr_fip_host_routes_by_binding(
|
||||
self.context,
|
||||
gw_net_id,
|
||||
bgp_speaker_id)
|
||||
routes = list(routes)
|
||||
dvr_gw_ip = fip_gw['fixed_ips'][0]['ip_address']
|
||||
self.assertEqual(1, len(routes))
|
||||
self.assertEqual(dvr_gw_ip, routes[0]['next_hop'])
|
||||
self.assertEqual(fip_prefix, routes[0]['destination'])
|
||||
|
||||
def test__get_dvr_fip_host_routes_by_router(self):
|
||||
gw_prefix = '172.16.10.0/24'
|
||||
tenant_prefix = '10.10.10.0/24'
|
||||
tenant_id = _uuid()
|
||||
scope_data = {'tenant_id': tenant_id, 'ip_version': 4,
|
||||
'shared': True, 'name': 'bgp-scope'}
|
||||
scope = self.plugin.create_address_scope(
|
||||
self.context,
|
||||
{'address_scope': scope_data})
|
||||
with self.router_with_external_and_tenant_networks(
|
||||
tenant_id=tenant_id,
|
||||
gw_prefix=gw_prefix,
|
||||
tenant_prefix=tenant_prefix,
|
||||
address_scope=scope,
|
||||
distributed=True) as res:
|
||||
router, ext_net, int_net = res
|
||||
gw_net_id = ext_net['network']['id']
|
||||
tenant_net_id = int_net['network']['id']
|
||||
fixed_port_data = {'port':
|
||||
{'name': 'test',
|
||||
'network_id': tenant_net_id,
|
||||
'tenant_id': tenant_id,
|
||||
'admin_state_up': True,
|
||||
'device_id': _uuid(),
|
||||
'device_owner': 'compute:nova',
|
||||
'mac_address': attrs.ATTR_NOT_SPECIFIED,
|
||||
'fixed_ips': attrs.ATTR_NOT_SPECIFIED,
|
||||
portbindings.HOST_ID: 'test-host'}}
|
||||
fixed_port = self.plugin.create_port(self.context,
|
||||
fixed_port_data)
|
||||
self.plugin._create_or_update_agent(self.context,
|
||||
{'agent_type': 'L3 agent',
|
||||
'host': 'test-host',
|
||||
'binary': 'neutron-l3-agent',
|
||||
'topic': 'test'})
|
||||
fip_gw = self.l3plugin.create_fip_agent_gw_port_if_not_exists(
|
||||
self.context,
|
||||
gw_net_id,
|
||||
'test-host')
|
||||
fip_data = {'floatingip': {'floating_network_id': gw_net_id,
|
||||
'tenant_id': tenant_id,
|
||||
'port_id': fixed_port['id']}}
|
||||
fip = self.l3plugin.create_floatingip(self.context, fip_data)
|
||||
fip_prefix = fip['floating_ip_address'] + '/32'
|
||||
with self.bgp_speaker(4, 1234, networks=[gw_net_id]) as speaker:
|
||||
bgp_speaker_id = speaker['id']
|
||||
routes = self.bgp_plugin._get_dvr_fip_host_routes_by_router(
|
||||
self.context,
|
||||
bgp_speaker_id,
|
||||
router['id'])
|
||||
routes = list(routes)
|
||||
dvr_gw_ip = fip_gw['fixed_ips'][0]['ip_address']
|
||||
self.assertEqual(1, len(routes))
|
||||
self.assertEqual(dvr_gw_ip, routes[0]['next_hop'])
|
||||
self.assertEqual(fip_prefix, routes[0]['destination'])
|
||||
|
||||
def test_get_routes_by_bgp_speaker_binding_with_fip(self):
|
||||
gw_prefix = '172.16.10.0/24'
|
||||
tenant_prefix = '10.10.10.0/24'
|
||||
|
23
releasenotes/notes/bgp-support-ef361825ca63f28b.yaml
Normal file
23
releasenotes/notes/bgp-support-ef361825ca63f28b.yaml
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
prelude: >
|
||||
Announcement of tenant prefixes and host routes for floating
|
||||
IP's via BGP is supported
|
||||
features:
|
||||
- Announcement of tenant subnets via BGP using centralized Neutron
|
||||
router gateway port as the next-hop
|
||||
- Announcement of floating IP host routes via BGP using the centralized
|
||||
Neutron router gateway port as the next-hop
|
||||
- Announcement of floating IP host routes via BGP using the floating
|
||||
IP agent gateway as the next-hop when the floating IP is associated
|
||||
through a distributed router
|
||||
issues:
|
||||
- When using DVR, if a floating IP is associated to a fixed IP direct
|
||||
access to the fixed IP is not possible when traffic is sent from
|
||||
outside of a Neutron tenant network (north-south traffic). Traffic
|
||||
sent between tenant networks (east-west traffic) is not affected.
|
||||
When using a distributed router, the floating IP will mask the fixed
|
||||
IP making it inaccessible, even though the tenant subnet is being
|
||||
announced as accessible through the centralized SNAT router. In such
|
||||
a case, traffic sent to the instance should be directed to the
|
||||
floating IP. This is a limitation of the Neutron L3 agent when using
|
||||
DVR and will be addressed in a future release.
|
Loading…
Reference in New Issue
Block a user