Set onlink routes for all subnets on an external network
The addition of the on-link routes gives us some freedom to allocate a router's IP address from any one of multiple subnets on one external network. Different routers can get their IPs from different subnets and they still have direct on-link connectivity to each other. For example, one router with its primary IP from 10.0.0.0/24 and another from 192.168.0.0/24 can communicate directly. It is important that each router has on-link routes to *all* of the subnets. Any router can host floating ips from any of the subnets regardless of which subnet the primary IP address comes from. This is an alternative to the "Multiple floating IP pools" section in the administration guide. It is a simpler alternative that avoids having to create multiple external networks. It is also more flexible because routers will no longer be restricted to getting floating IPs from the pool to which they happen to be connected. DocImpact Document the procedure for adding subnets to the external network. Potentially remove the existing procedure for "Multiple floating IP pools" from the docs. Change-Id: I2c283f5be0cbb6b5d350cafc1b636c300b796a7b Closes-Bug: #1312467
This commit is contained in:
parent
8807461ba7
commit
cb30a284f2
@ -458,7 +458,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
|
||||
interface_name = None
|
||||
if ex_gw_port_id:
|
||||
interface_name = self.get_external_device_name(ex_gw_port_id)
|
||||
if ex_gw_port and not ri.ex_gw_port:
|
||||
if ex_gw_port and ex_gw_port != ri.ex_gw_port:
|
||||
self._set_subnet_info(ex_gw_port)
|
||||
self.external_gateway_added(ri, ex_gw_port,
|
||||
interface_name, internal_cidrs)
|
||||
@ -646,6 +646,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
|
||||
self.driver.init_l3(interface_name, [ex_gw_port['ip_cidr']],
|
||||
namespace=ri.ns_name,
|
||||
gateway=ex_gw_port['subnet'].get('gateway_ip'),
|
||||
extra_subnets=ex_gw_port.get('extra_subnets', []),
|
||||
preserve_ips=preserve_ips)
|
||||
ip_address = ex_gw_port['ip_cidr'].split('/')[0]
|
||||
self._send_gratuitous_arp_packet(ri, interface_name, ip_address)
|
||||
|
@ -72,7 +72,7 @@ class LinuxInterfaceDriver(object):
|
||||
self.root_helper = config.get_root_helper(conf)
|
||||
|
||||
def init_l3(self, device_name, ip_cidrs, namespace=None,
|
||||
preserve_ips=[], gateway=None):
|
||||
preserve_ips=[], gateway=None, extra_subnets=[]):
|
||||
"""Set the L3 settings for the interface using data from the port.
|
||||
|
||||
ip_cidrs: list of 'X.X.X.X/YY' strings
|
||||
@ -108,6 +108,13 @@ class LinuxInterfaceDriver(object):
|
||||
if gateway:
|
||||
device.route.add_gateway(gateway)
|
||||
|
||||
new_onlink_routes = set(s['cidr'] for s in extra_subnets)
|
||||
existing_onlink_routes = set(device.route.list_onlink_routes())
|
||||
for route in new_onlink_routes - existing_onlink_routes:
|
||||
device.route.add_onlink_route(route)
|
||||
for route in existing_onlink_routes - new_onlink_routes:
|
||||
device.route.delete_onlink_route(route)
|
||||
|
||||
def check_bridge_exists(self, bridge):
|
||||
if not ip_lib.device_exists(bridge):
|
||||
raise exceptions.BridgeDoesNotExist(bridge=bridge)
|
||||
|
@ -376,6 +376,22 @@ class IpRouteCommand(IpDeviceCommandBase):
|
||||
'dev',
|
||||
self.name)
|
||||
|
||||
def list_onlink_routes(self):
|
||||
def iterate_routes():
|
||||
output = self._run('list', 'dev', self.name, 'scope', 'link')
|
||||
for line in output.split('\n'):
|
||||
line = line.strip()
|
||||
if line and not line.count('src'):
|
||||
yield line
|
||||
|
||||
return [x for x in iterate_routes()]
|
||||
|
||||
def add_onlink_route(self, cidr):
|
||||
self._as_root('replace', cidr, 'dev', self.name, 'scope', 'link')
|
||||
|
||||
def delete_onlink_route(self, cidr):
|
||||
self._as_root('del', cidr, 'dev', self.name, 'scope', 'link')
|
||||
|
||||
def get_gateway(self, scope=None, filters=None):
|
||||
if filters is None:
|
||||
filters = []
|
||||
|
@ -924,36 +924,41 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
||||
"""
|
||||
if not ports:
|
||||
return
|
||||
subnet_id_ports_dict = {}
|
||||
for port in ports:
|
||||
fixed_ips = port.get('fixed_ips', [])
|
||||
if len(fixed_ips) > 1:
|
||||
LOG.info(_("Ignoring multiple IPs on router port %s"),
|
||||
port['id'])
|
||||
continue
|
||||
elif not fixed_ips:
|
||||
# Skip ports without IPs, which can occur if a subnet
|
||||
# attached to a router is deleted
|
||||
LOG.info(_("Skipping port %s as no IP is configure on it"),
|
||||
port['id'])
|
||||
continue
|
||||
fixed_ip = fixed_ips[0]
|
||||
my_ports = subnet_id_ports_dict.get(fixed_ip['subnet_id'], [])
|
||||
my_ports.append(port)
|
||||
subnet_id_ports_dict[fixed_ip['subnet_id']] = my_ports
|
||||
if not subnet_id_ports_dict:
|
||||
return
|
||||
filters = {'id': subnet_id_ports_dict.keys()}
|
||||
fields = ['id', 'cidr', 'gateway_ip']
|
||||
subnet_dicts = self._core_plugin.get_subnets(context, filters, fields)
|
||||
for subnet_dict in subnet_dicts:
|
||||
ports = subnet_id_ports_dict.get(subnet_dict['id'], [])
|
||||
|
||||
def each_port_with_ip():
|
||||
for port in ports:
|
||||
# TODO(gongysh) stash the subnet into fixed_ips
|
||||
# to make the payload smaller.
|
||||
port['subnet'] = {'id': subnet_dict['id'],
|
||||
'cidr': subnet_dict['cidr'],
|
||||
'gateway_ip': subnet_dict['gateway_ip']}
|
||||
fixed_ips = port.get('fixed_ips', [])
|
||||
if len(fixed_ips) > 1:
|
||||
LOG.info(_("Ignoring multiple IPs on router port %s"),
|
||||
port['id'])
|
||||
continue
|
||||
elif not fixed_ips:
|
||||
# Skip ports without IPs, which can occur if a subnet
|
||||
# attached to a router is deleted
|
||||
LOG.info(_("Skipping port %s as no IP is configure on it"),
|
||||
port['id'])
|
||||
continue
|
||||
yield (port, fixed_ips[0])
|
||||
|
||||
network_ids = set(p['network_id'] for p, _ in each_port_with_ip())
|
||||
filters = {'network_id': [id for id in network_ids]}
|
||||
fields = ['id', 'cidr', 'gateway_ip', 'network_id']
|
||||
|
||||
subnets_by_network = dict((id, []) for id in network_ids)
|
||||
for subnet in self._core_plugin.get_subnets(context, filters, fields):
|
||||
subnets_by_network[subnet['network_id']].append(subnet)
|
||||
|
||||
for port, fixed_ip in each_port_with_ip():
|
||||
port['extra_subnets'] = []
|
||||
for subnet in subnets_by_network[port['network_id']]:
|
||||
subnet_info = {'id': subnet['id'],
|
||||
'cidr': subnet['cidr'],
|
||||
'gateway_ip': subnet['gateway_ip']}
|
||||
|
||||
if subnet['id'] == fixed_ip['subnet_id']:
|
||||
port['subnet'] = subnet_info
|
||||
else:
|
||||
port['extra_subnets'].append(subnet_info)
|
||||
|
||||
def _process_sync_data(self, routers, interfaces, floating_ips):
|
||||
routers_dict = {}
|
||||
|
@ -160,6 +160,7 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
||||
ex_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30',
|
||||
'subnet_id': _uuid()}],
|
||||
'subnet': {'gateway_ip': '20.0.0.1'},
|
||||
'extra_subnets': [{'cidr': '172.16.0.0/24'}],
|
||||
'id': _uuid(),
|
||||
'network_id': _uuid(),
|
||||
'mac_address': 'ca:fe:de:ad:be:ef',
|
||||
@ -179,7 +180,8 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
||||
'20.0.0.30')
|
||||
kwargs = {'preserve_ips': ['192.168.1.34/32'],
|
||||
'namespace': 'qrouter-' + router_id,
|
||||
'gateway': '20.0.0.1'}
|
||||
'gateway': '20.0.0.1',
|
||||
'extra_subnets': [{'cidr': '172.16.0.0/24'}]}
|
||||
self.mock_driver.init_l3.assert_called_with(interface_name,
|
||||
['20.0.0.30/24'],
|
||||
**kwargs)
|
||||
|
@ -81,15 +81,32 @@ class TestABCDriver(TestBase):
|
||||
addresses = [dict(ip_version=4, scope='global',
|
||||
dynamic=False, cidr='172.16.77.240/24')]
|
||||
self.ip_dev().addr.list = mock.Mock(return_value=addresses)
|
||||
self.ip_dev().route.list_onlink_routes.return_value = []
|
||||
|
||||
bc = BaseChild(self.conf)
|
||||
ns = '12345678-1234-5678-90ab-ba0987654321'
|
||||
bc.init_l3('tap0', ['192.168.1.2/24'], namespace=ns,
|
||||
extra_subnets=[{'cidr': '172.20.0.0/24'}])
|
||||
self.ip_dev.assert_has_calls(
|
||||
[mock.call('tap0', 'sudo', namespace=ns),
|
||||
mock.call().addr.list(scope='global', filters=['permanent']),
|
||||
mock.call().addr.add(4, '192.168.1.2/24', '192.168.1.255'),
|
||||
mock.call().addr.delete(4, '172.16.77.240/24'),
|
||||
mock.call().route.list_onlink_routes(),
|
||||
mock.call().route.add_onlink_route('172.20.0.0/24')])
|
||||
|
||||
def test_l3_init_delete_onlink_routes(self):
|
||||
addresses = [dict(ip_version=4, scope='global',
|
||||
dynamic=False, cidr='172.16.77.240/24')]
|
||||
self.ip_dev().addr.list = mock.Mock(return_value=addresses)
|
||||
self.ip_dev().route.list_onlink_routes.return_value = ['172.20.0.0/24']
|
||||
|
||||
bc = BaseChild(self.conf)
|
||||
ns = '12345678-1234-5678-90ab-ba0987654321'
|
||||
bc.init_l3('tap0', ['192.168.1.2/24'], namespace=ns)
|
||||
self.ip_dev.assert_has_calls(
|
||||
[mock.call('tap0', 'sudo', namespace=ns),
|
||||
mock.call().addr.list(scope='global', filters=['permanent']),
|
||||
mock.call().addr.add(4, '192.168.1.2/24', '192.168.1.255'),
|
||||
mock.call().addr.delete(4, '172.16.77.240/24')])
|
||||
[mock.call().route.list_onlink_routes(),
|
||||
mock.call().route.delete_onlink_route('172.20.0.0/24')])
|
||||
|
||||
def test_l3_init_with_preserve(self):
|
||||
addresses = [dict(ip_version=4, scope='global',
|
||||
|
Loading…
Reference in New Issue
Block a user