Merge "Set onlink routes for all subnets on an external network"
This commit is contained in:
@@ -458,7 +458,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
|
|||||||
interface_name = None
|
interface_name = None
|
||||||
if ex_gw_port_id:
|
if ex_gw_port_id:
|
||||||
interface_name = self.get_external_device_name(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._set_subnet_info(ex_gw_port)
|
||||||
self.external_gateway_added(ri, ex_gw_port,
|
self.external_gateway_added(ri, ex_gw_port,
|
||||||
interface_name, internal_cidrs)
|
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']],
|
self.driver.init_l3(interface_name, [ex_gw_port['ip_cidr']],
|
||||||
namespace=ri.ns_name,
|
namespace=ri.ns_name,
|
||||||
gateway=ex_gw_port['subnet'].get('gateway_ip'),
|
gateway=ex_gw_port['subnet'].get('gateway_ip'),
|
||||||
|
extra_subnets=ex_gw_port.get('extra_subnets', []),
|
||||||
preserve_ips=preserve_ips)
|
preserve_ips=preserve_ips)
|
||||||
ip_address = ex_gw_port['ip_cidr'].split('/')[0]
|
ip_address = ex_gw_port['ip_cidr'].split('/')[0]
|
||||||
self._send_gratuitous_arp_packet(ri, interface_name, ip_address)
|
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)
|
self.root_helper = config.get_root_helper(conf)
|
||||||
|
|
||||||
def init_l3(self, device_name, ip_cidrs, namespace=None,
|
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.
|
"""Set the L3 settings for the interface using data from the port.
|
||||||
|
|
||||||
ip_cidrs: list of 'X.X.X.X/YY' strings
|
ip_cidrs: list of 'X.X.X.X/YY' strings
|
||||||
@@ -108,6 +108,13 @@ class LinuxInterfaceDriver(object):
|
|||||||
if gateway:
|
if gateway:
|
||||||
device.route.add_gateway(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):
|
def check_bridge_exists(self, bridge):
|
||||||
if not ip_lib.device_exists(bridge):
|
if not ip_lib.device_exists(bridge):
|
||||||
raise exceptions.BridgeDoesNotExist(bridge=bridge)
|
raise exceptions.BridgeDoesNotExist(bridge=bridge)
|
||||||
|
|||||||
@@ -376,6 +376,22 @@ class IpRouteCommand(IpDeviceCommandBase):
|
|||||||
'dev',
|
'dev',
|
||||||
self.name)
|
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):
|
def get_gateway(self, scope=None, filters=None):
|
||||||
if filters is None:
|
if filters is None:
|
||||||
filters = []
|
filters = []
|
||||||
|
|||||||
@@ -924,36 +924,41 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
|||||||
"""
|
"""
|
||||||
if not ports:
|
if not ports:
|
||||||
return
|
return
|
||||||
subnet_id_ports_dict = {}
|
|
||||||
for port in ports:
|
def each_port_with_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
|
|
||||||
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'], [])
|
|
||||||
for port in ports:
|
for port in ports:
|
||||||
# TODO(gongysh) stash the subnet into fixed_ips
|
fixed_ips = port.get('fixed_ips', [])
|
||||||
# to make the payload smaller.
|
if len(fixed_ips) > 1:
|
||||||
port['subnet'] = {'id': subnet_dict['id'],
|
LOG.info(_("Ignoring multiple IPs on router port %s"),
|
||||||
'cidr': subnet_dict['cidr'],
|
port['id'])
|
||||||
'gateway_ip': subnet_dict['gateway_ip']}
|
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):
|
def _process_sync_data(self, routers, interfaces, floating_ips):
|
||||||
routers_dict = {}
|
routers_dict = {}
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
|||||||
ex_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30',
|
ex_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30',
|
||||||
'subnet_id': _uuid()}],
|
'subnet_id': _uuid()}],
|
||||||
'subnet': {'gateway_ip': '20.0.0.1'},
|
'subnet': {'gateway_ip': '20.0.0.1'},
|
||||||
|
'extra_subnets': [{'cidr': '172.16.0.0/24'}],
|
||||||
'id': _uuid(),
|
'id': _uuid(),
|
||||||
'network_id': _uuid(),
|
'network_id': _uuid(),
|
||||||
'mac_address': 'ca:fe:de:ad:be:ef',
|
'mac_address': 'ca:fe:de:ad:be:ef',
|
||||||
@@ -178,7 +179,8 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
|||||||
'20.0.0.30')
|
'20.0.0.30')
|
||||||
kwargs = {'preserve_ips': ['192.168.1.34/32'],
|
kwargs = {'preserve_ips': ['192.168.1.34/32'],
|
||||||
'namespace': 'qrouter-' + router_id,
|
'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,
|
self.mock_driver.init_l3.assert_called_with(interface_name,
|
||||||
['20.0.0.30/24'],
|
['20.0.0.30/24'],
|
||||||
**kwargs)
|
**kwargs)
|
||||||
|
|||||||
@@ -81,15 +81,32 @@ class TestABCDriver(TestBase):
|
|||||||
addresses = [dict(ip_version=4, scope='global',
|
addresses = [dict(ip_version=4, scope='global',
|
||||||
dynamic=False, cidr='172.16.77.240/24')]
|
dynamic=False, cidr='172.16.77.240/24')]
|
||||||
self.ip_dev().addr.list = mock.Mock(return_value=addresses)
|
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)
|
bc = BaseChild(self.conf)
|
||||||
ns = '12345678-1234-5678-90ab-ba0987654321'
|
ns = '12345678-1234-5678-90ab-ba0987654321'
|
||||||
bc.init_l3('tap0', ['192.168.1.2/24'], namespace=ns)
|
bc.init_l3('tap0', ['192.168.1.2/24'], namespace=ns)
|
||||||
self.ip_dev.assert_has_calls(
|
self.ip_dev.assert_has_calls(
|
||||||
[mock.call('tap0', 'sudo', namespace=ns),
|
[mock.call().route.list_onlink_routes(),
|
||||||
mock.call().addr.list(scope='global', filters=['permanent']),
|
mock.call().route.delete_onlink_route('172.20.0.0/24')])
|
||||||
mock.call().addr.add(4, '192.168.1.2/24', '192.168.1.255'),
|
|
||||||
mock.call().addr.delete(4, '172.16.77.240/24')])
|
|
||||||
|
|
||||||
def test_l3_init_with_preserve(self):
|
def test_l3_init_with_preserve(self):
|
||||||
addresses = [dict(ip_version=4, scope='global',
|
addresses = [dict(ip_version=4, scope='global',
|
||||||
|
|||||||
Reference in New Issue
Block a user