diff --git a/neutron/agent/l3_agent.py b/neutron/agent/l3_agent.py index aa185e43067..d81deea8388 100644 --- a/neutron/agent/l3_agent.py +++ b/neutron/agent/l3_agent.py @@ -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) diff --git a/neutron/agent/linux/interface.py b/neutron/agent/linux/interface.py index 84c0f3bb9ec..a31250ee7e0 100644 --- a/neutron/agent/linux/interface.py +++ b/neutron/agent/linux/interface.py @@ -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) diff --git a/neutron/agent/linux/ip_lib.py b/neutron/agent/linux/ip_lib.py index 40acb4c1840..bc32139f276 100644 --- a/neutron/agent/linux/ip_lib.py +++ b/neutron/agent/linux/ip_lib.py @@ -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 = [] diff --git a/neutron/db/l3_db.py b/neutron/db/l3_db.py index beebaee878f..9eea54fce55 100644 --- a/neutron/db/l3_db.py +++ b/neutron/db/l3_db.py @@ -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 = {} diff --git a/neutron/tests/unit/test_l3_agent.py b/neutron/tests/unit/test_l3_agent.py index 1b3f231ed1c..222290d6261 100644 --- a/neutron/tests/unit/test_l3_agent.py +++ b/neutron/tests/unit/test_l3_agent.py @@ -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) diff --git a/neutron/tests/unit/test_linux_interface.py b/neutron/tests/unit/test_linux_interface.py index 010409731ab..749f786b49a 100644 --- a/neutron/tests/unit/test_linux_interface.py +++ b/neutron/tests/unit/test_linux_interface.py @@ -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',