Merge "Allow the use of legacy routers within RPN segments"
This commit is contained in:
commit
8507e632b7
|
@ -524,3 +524,87 @@ one segment to a routed one.
|
|||
+------------+--------------------------------------+
|
||||
| segment_id | 81e5453d-4c9f-43a5-8ddf-feaf3937e8c7 |
|
||||
+------------+--------------------------------------+
|
||||
|
||||
|
||||
Routed provider networks as external networks for tenant routed networks
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. note::
|
||||
|
||||
This section applies only to legacy routers, not DVR nor HA routers. A
|
||||
legacy router has a single instance that is hosted in one single host.
|
||||
|
||||
One of the consequences of this feature is the externalization of any routing
|
||||
operation. The communication (routing) between segments is done using the
|
||||
underlying network infrastructure, not managed by Neutron.
|
||||
|
||||
Could be the case that the user needs to split the communication between
|
||||
several hosts. It is possible to create tenant networks and connect them using
|
||||
a router. To access to the router provider network, it should be connected
|
||||
as router gateway.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
Tenant net1 ┌─────────────────────┐
|
||||
─────────────┤ │
|
||||
│ │ Routed provided network
|
||||
│ GW port ├────────────────────────
|
||||
Tenant net2 │ │
|
||||
─────────────┤ │
|
||||
└─────────────────────┘
|
||||
|
||||
The routed provider network, acting as router gateway, contains all subnets
|
||||
associated to the segments. In a deployment without router provided networks,
|
||||
the gateway port has L2 connectivity to all subnet CIDRs. In this case, the
|
||||
gateway port has only connectivity to the attached segment subnets and its
|
||||
L2 broadcast domains.
|
||||
|
||||
The L3 agent will create, inside the router namespace, a default route in the
|
||||
gateway port fixed IP CIDR. For each other subnet no belonging to the port
|
||||
fixed IP address, a onlink route is created. These routes use the gateway port
|
||||
as routing device and allow to route any packet with destination on these
|
||||
CIDRs through this port.
|
||||
|
||||
The problem in the case of connecting the gatewat port to a routed provider
|
||||
network is that it will have broadcast connectivity only to those subnets
|
||||
that belong to the host segment:
|
||||
|
||||
* One of those subnets will provide the port IP address. The gateway IP address
|
||||
of this subnet will be the default route, through the gateway port.
|
||||
* Any other subnet belonging to this segment will create a onlink route, using
|
||||
the gateway port as route device.
|
||||
|
||||
For example, let's consider the following configuration:
|
||||
|
||||
* Two tenant networks with CIDRs 10.1.0.0/24 and 10.2.0.0/24.
|
||||
* A RPN with two segments; each segment with two subnets: segment 1 with
|
||||
10.51.0.0/24 and 10.52.0.0/24, segment 2 with 10.53.0.0/24 and 10.54.0.0/24.
|
||||
* The router is connected to the first segment and the gateway port has an IP
|
||||
address in the range of 10.51.0.0/24. This is why the default route uses
|
||||
an IP address in this range.
|
||||
|
||||
Without considering that the gateway network is a router provided network, this
|
||||
is the routing table set in the router namespace:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ ip netns exec $r ip r
|
||||
default via 10.51.0.1 dev qg-gwport proto static
|
||||
10.1.0.0/24 dev qr-tenant1 proto kernel scope link src 10.1.0.1
|
||||
10.2.0.0/24 dev qr-tenant2 proto kernel scope link src 10.2.0.1
|
||||
10.51.0.0/24 dev qg-gwport proto kernel scope link src 10.100.0.15
|
||||
10.52.0.0/24 dev qg-gwport proto static scope link
|
||||
10.53.0.0/24 dev qg-gwport proto static scope link <-- should be removed, belongs to segment 2
|
||||
10.54.0.0/24 dev qg-gwport proto static scope link <-- should be removed, belongs to segment 2
|
||||
|
||||
Those packets sent to 10.53.0.0/24 and 10.54.0.0/24 (the second RPN subnet
|
||||
CIDRs), don't have L2 connectivity and the ARP packets won't be replied. In the
|
||||
case of having a RPN as gateway network, all packets exiting the router through
|
||||
the gateway, must be sent to the gateway IP address, in this case 10.51.0.1.
|
||||
This is why the L3 plugin does not send the information of other segments
|
||||
subnets L3 agent when:
|
||||
|
||||
* The network is the router gateway.
|
||||
* The "segments" plugin is enabled; this plugin is needed for routed provided
|
||||
networks.
|
||||
* The network is connected to a segment.
|
||||
|
|
|
@ -51,6 +51,7 @@ from neutron.db import models_v2
|
|||
from neutron.db import standardattrdescription_db as st_attr
|
||||
from neutron.extensions import l3
|
||||
from neutron.extensions import qos_fip
|
||||
from neutron.extensions import segment as segment_ext
|
||||
from neutron.objects import base as base_obj
|
||||
from neutron.objects import port_forwarding
|
||||
from neutron.objects import ports as port_obj
|
||||
|
@ -1768,7 +1769,7 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
|
|||
query = query.filter(models_v2.Subnet.network_id.in_(network_ids))
|
||||
|
||||
fields = ['id', 'cidr', 'gateway_ip', 'dns_nameservers',
|
||||
'network_id', 'ipv6_ra_mode', 'subnetpool_id']
|
||||
'network_id', 'ipv6_ra_mode', 'subnetpool_id', 'segment_id']
|
||||
|
||||
def make_subnet_dict_with_scope(row):
|
||||
subnet_db, address_scope_id = row
|
||||
|
@ -1798,6 +1799,7 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
|
|||
|
||||
These ports already have fixed_ips populated.
|
||||
"""
|
||||
seg_plugin_loaded = segment_ext.SegmentPluginBase.is_loaded()
|
||||
network_ids = [p['network_id']
|
||||
for p in self._each_port_having_fixed_ips(ports)]
|
||||
|
||||
|
@ -1806,12 +1808,19 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
|
|||
context, network_ids)
|
||||
|
||||
for port in self._each_port_having_fixed_ips(ports):
|
||||
|
||||
is_gw = port['device_owner'] == constants.DEVICE_OWNER_ROUTER_GW
|
||||
port['subnets'] = []
|
||||
port['extra_subnets'] = []
|
||||
port['address_scopes'] = {constants.IP_VERSION_4: None,
|
||||
constants.IP_VERSION_6: None}
|
||||
|
||||
gw_port_segment = None
|
||||
if is_gw and seg_plugin_loaded:
|
||||
for subnet in subnets_by_network[port['network_id']]:
|
||||
if subnet['id'] == port['fixed_ips'][0]['subnet_id']:
|
||||
gw_port_segment = subnet['segment_id']
|
||||
break
|
||||
|
||||
scopes = {}
|
||||
for subnet in subnets_by_network[port['network_id']]:
|
||||
scope = subnet['address_scope_id']
|
||||
|
@ -1835,8 +1844,16 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
|
|||
fixed_ip['prefixlen'] = prefixlen
|
||||
break
|
||||
else:
|
||||
# This subnet is not used by the port.
|
||||
port['extra_subnets'].append(subnet_info)
|
||||
# NOTE(ralonsoh): if this is the gateway port and is
|
||||
# connected to a router provider network, it will have L2
|
||||
# connectivity only to the subnets in the same segment.
|
||||
# The router will add only the onlink routes to those
|
||||
# subnet CIDRs.
|
||||
if is_gw and seg_plugin_loaded:
|
||||
if subnet['segment_id'] == gw_port_segment:
|
||||
port['extra_subnets'].append(subnet_info)
|
||||
else:
|
||||
port['extra_subnets'].append(subnet_info)
|
||||
|
||||
port['address_scopes'].update(scopes)
|
||||
port['mtu'] = mtus_by_network.get(port['network_id'], 0)
|
||||
|
|
|
@ -248,3 +248,7 @@ class SegmentPluginBase(object, metaclass=abc.ABCMeta):
|
|||
@classmethod
|
||||
def get_plugin_type(cls):
|
||||
return SEGMENTS
|
||||
|
||||
@classmethod
|
||||
def is_loaded(cls):
|
||||
return cls.get_plugin_type() in directory.get_plugins()
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
from unittest import mock
|
||||
|
||||
import ddt
|
||||
import netaddr
|
||||
from neutron_lib.callbacks import events
|
||||
from neutron_lib.callbacks import registry
|
||||
|
@ -31,6 +32,7 @@ import testtools
|
|||
|
||||
from neutron.db import l3_db
|
||||
from neutron.db.models import l3 as l3_models
|
||||
from neutron.extensions import segment as segment_ext
|
||||
from neutron.objects import base as base_obj
|
||||
from neutron.objects import network as network_obj
|
||||
from neutron.objects import ports as port_obj
|
||||
|
@ -40,6 +42,7 @@ from neutron.tests import base
|
|||
from neutron.tests.unit.db import test_db_base_plugin_v2
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestL3_NAT_dbonly_mixin(
|
||||
test_db_base_plugin_v2.NeutronDbPluginV2TestCase):
|
||||
|
||||
|
@ -135,7 +138,8 @@ class TestL3_NAT_dbonly_mixin(
|
|||
|
||||
ports = [{'network_id': 'net_id',
|
||||
'id': 'port_id',
|
||||
'fixed_ips': [{'subnet_id': mock.sentinel.subnet_id}]}]
|
||||
'fixed_ips': [{'subnet_id': mock.sentinel.subnet_id}],
|
||||
'device_owner': 'compute:nova'}]
|
||||
with mock.patch.object(directory, 'get_plugin') as get_p:
|
||||
get_p().get_networks.return_value = [{'id': 'net_id', 'mtu': 1446}]
|
||||
self.db._populate_mtu_and_subnets_for_ports(mock.sentinel.context,
|
||||
|
@ -151,7 +155,73 @@ class TestL3_NAT_dbonly_mixin(
|
|||
'mtu': 1446,
|
||||
'network_id': 'net_id',
|
||||
'subnets': [{k: subnet[k] for k in keys}],
|
||||
'address_scopes': address_scopes}], ports)
|
||||
'address_scopes': address_scopes,
|
||||
'device_owner': 'compute:nova'}], ports)
|
||||
|
||||
@ddt.unpack
|
||||
@ddt.data({'plugin_loaded': False, 'seg1': None, 'seg2': None},
|
||||
{'plugin_loaded': True, 'seg1': None, 'seg2': None},
|
||||
{'plugin_loaded': True, 'seg1': 'seg1', 'seg2': 'seg2'})
|
||||
@mock.patch.object(l3_db.L3_NAT_dbonly_mixin,
|
||||
'_get_subnets_by_network_list')
|
||||
def test__populate_ports_for_subnets_gw_port(self, get_subnets_by_network,
|
||||
plugin_loaded, seg1, seg2):
|
||||
subnets = [
|
||||
{'id': uuidutils.generate_uuid(),
|
||||
'cidr': '10.1.0.0/24',
|
||||
'gateway_ip': mock.sentinel.gateway_ip,
|
||||
'dns_nameservers': mock.sentinel.dns_nameservers,
|
||||
'ipv6_ra_mode': mock.sentinel.ipv6_ra_mode,
|
||||
'subnetpool_id': mock.sentinel.subnetpool_id,
|
||||
'address_scope_id': mock.sentinel.address_scope_id,
|
||||
'segment_id': seg1},
|
||||
{'id': uuidutils.generate_uuid(),
|
||||
'cidr': '10.2.0.0/24',
|
||||
'gateway_ip': mock.sentinel.gateway_ip,
|
||||
'dns_nameservers': mock.sentinel.dns_nameservers,
|
||||
'ipv6_ra_mode': mock.sentinel.ipv6_ra_mode,
|
||||
'subnetpool_id': mock.sentinel.subnetpool_id,
|
||||
'address_scope_id': mock.sentinel.address_scope_id,
|
||||
'segment_id': seg1},
|
||||
{'id': uuidutils.generate_uuid(),
|
||||
'cidr': '10.3.0.0/24',
|
||||
'gateway_ip': mock.sentinel.gateway_ip,
|
||||
'dns_nameservers': mock.sentinel.dns_nameservers,
|
||||
'ipv6_ra_mode': mock.sentinel.ipv6_ra_mode,
|
||||
'subnetpool_id': mock.sentinel.subnetpool_id,
|
||||
'address_scope_id': mock.sentinel.address_scope_id,
|
||||
'segment_id': seg2}]
|
||||
get_subnets_by_network.return_value = {'net_id': subnets}
|
||||
|
||||
ports = [{'network_id': 'net_id',
|
||||
'id': 'port_id',
|
||||
'fixed_ips': [{'subnet_id': subnets[0]['id']}],
|
||||
'device_owner': n_const.DEVICE_OWNER_ROUTER_GW}]
|
||||
with mock.patch.object(directory, 'get_plugin') as get_p, \
|
||||
mock.patch.object(segment_ext.SegmentPluginBase,
|
||||
'is_loaded', return_value=plugin_loaded):
|
||||
get_p().get_networks.return_value = [{'id': 'net_id', 'mtu': 1446}]
|
||||
self.db._populate_mtu_and_subnets_for_ports(mock.sentinel.context,
|
||||
ports)
|
||||
keys = ('id', 'cidr', 'gateway_ip', 'ipv6_ra_mode',
|
||||
'subnetpool_id', 'dns_nameservers')
|
||||
address_scopes = {4: mock.sentinel.address_scope_id, 6: None}
|
||||
reference = {'fixed_ips': [{'subnet_id': subnets[0]['id'],
|
||||
'prefixlen': 24}],
|
||||
'id': 'port_id',
|
||||
'mtu': 1446,
|
||||
'network_id': 'net_id',
|
||||
'subnets': [{k: subnets[0][k] for k in keys}],
|
||||
'address_scopes': address_scopes,
|
||||
'device_owner': n_const.DEVICE_OWNER_ROUTER_GW,
|
||||
'extra_subnets': [{k: subnets[1][k] for k in keys}]}
|
||||
# If RPN plugin is not enabled or the network subnets do not have
|
||||
# associated segments (that means this is not a RPN), all subnets
|
||||
# should be passed in "subnets" + "extra_subnets".
|
||||
if not plugin_loaded or subnets[0]['segment_id'] is None:
|
||||
reference['extra_subnets'].append(
|
||||
{k: subnets[2][k] for k in keys})
|
||||
self.assertEqual([reference], ports)
|
||||
|
||||
def test__get_sync_floating_ips_no_query(self):
|
||||
"""Basic test that no query is performed if no router ids are passed"""
|
||||
|
|
Loading…
Reference in New Issue