From 3b0fe59745b2b78daf40a51892c83b0a69e33a21 Mon Sep 17 00:00:00 2001 From: gong yong sheng Date: Thu, 25 Jun 2015 12:38:40 +0800 Subject: [PATCH] Add extra subnet route to ha router Add scope in HA virtual route mode to represent on link scope route from extra subnet. Co-Authored-By: Assaf Muller Change-Id: I6ce000a7aa8e4b9e61a35a86d3dc8c0b7beaa3a9 Closes-bug: 1414640 --- neutron/agent/l3/ha_router.py | 10 +++++++++ neutron/agent/linux/ip_lib.py | 15 +++++++++---- neutron/agent/linux/keepalived.py | 15 ++++++++++--- neutron/tests/common/l3_test_common.py | 7 ++++++- .../functional/agent/linux/test_ip_lib.py | 8 ++++--- .../tests/functional/agent/test_l3_agent.py | 21 ++++++++++++++++--- .../tests/unit/agent/linux/test_keepalived.py | 7 ++++++- 7 files changed, 68 insertions(+), 15 deletions(-) diff --git a/neutron/agent/l3/ha_router.py b/neutron/agent/l3/ha_router.py index 31a2c18dc..cb9e7f3d6 100644 --- a/neutron/agent/l3/ha_router.py +++ b/neutron/agent/l3/ha_router.py @@ -200,6 +200,15 @@ class HaRouter(router.RouterInfo): if enable_ra_on_gw: self.driver.configure_ipv6_ra(self.ns_name, interface_name) + def _add_extra_subnet_onlink_routes(self, ex_gw_port, interface_name): + extra_subnets = ex_gw_port.get('extra_subnets', []) + instance = self._get_keepalived_instance() + onlink_route_cidrs = set(s['cidr'] for s in extra_subnets) + instance.virtual_routes.extra_subnets = [ + keepalived.KeepalivedVirtualRoute( + onlink_route_cidr, None, interface_name, scope='link') for + onlink_route_cidr in onlink_route_cidrs] + def _should_delete_ipv6_lladdr(self, ipv6_lladdr): """Only the master should have any IP addresses configured. Let keepalived manage IPv6 link local addresses, the same way we let @@ -235,6 +244,7 @@ class HaRouter(router.RouterInfo): for ip_cidr in common_utils.fixed_ip_cidrs(ex_gw_port['fixed_ips']): self._add_vip(ip_cidr, interface_name) self._add_default_gw_virtual_route(ex_gw_port, interface_name) + self._add_extra_subnet_onlink_routes(ex_gw_port, interface_name) def add_floating_ip(self, fip, interface_name, device): fip_ip = fip['floating_ip_address'] diff --git a/neutron/agent/linux/ip_lib.py b/neutron/agent/linux/ip_lib.py index 36d2b0952..4c039591a 100644 --- a/neutron/agent/linux/ip_lib.py +++ b/neutron/agent/linux/ip_lib.py @@ -739,16 +739,22 @@ def device_exists_with_ips_and_mac(device_name, ip_cidrs, mac, namespace=None): return True -def get_routing_table(namespace=None): +def get_routing_table(ip_version, namespace=None): """Return a list of dictionaries, each representing a route. + @param ip_version: the routes of version to return, for example 4 + @param namespace + @return: a list of dictionaries, each representing a route. The dictionary format is: {'destination': cidr, 'nexthop': ip, - 'device': device_name} + 'device': device_name, + 'scope': scope} """ ip_wrapper = IPWrapper(namespace=namespace) - table = ip_wrapper.netns.execute(['ip', 'route'], check_exit_code=True) + table = ip_wrapper.netns.execute( + ['ip', '-%s' % ip_version, 'route'], + check_exit_code=True) routes = [] # Example for route_lines: @@ -765,7 +771,8 @@ def get_routing_table(namespace=None): data = dict(route[i:i + 2] for i in range(1, len(route), 2)) routes.append({'destination': network, 'nexthop': data.get('via'), - 'device': data.get('dev')}) + 'device': data.get('dev'), + 'scope': data.get('scope')}) return routes diff --git a/neutron/agent/linux/keepalived.py b/neutron/agent/linux/keepalived.py index f856def6d..d39f015f9 100644 --- a/neutron/agent/linux/keepalived.py +++ b/neutron/agent/linux/keepalived.py @@ -95,15 +95,21 @@ class KeepalivedVipAddress(object): class KeepalivedVirtualRoute(object): """A virtual route entry of a keepalived configuration.""" - def __init__(self, destination, nexthop, interface_name=None): + def __init__(self, destination, nexthop, interface_name=None, + scope=None): self.destination = destination self.nexthop = nexthop self.interface_name = interface_name + self.scope = scope def build_config(self): - output = '%s via %s' % (self.destination, self.nexthop) + output = self.destination + if self.nexthop: + output += ' via %s' % self.nexthop if self.interface_name: output += ' dev %s' % self.interface_name + if self.scope: + output += ' scope %s' % self.scope return output @@ -111,6 +117,7 @@ class KeepalivedInstanceRoutes(object): def __init__(self): self.gateway_routes = [] self.extra_routes = [] + self.extra_subnets = [] def remove_routes_on_interface(self, interface_name): self.gateway_routes = [gw_rt for gw_rt in self.gateway_routes @@ -118,10 +125,12 @@ class KeepalivedInstanceRoutes(object): # NOTE(amuller): extra_routes are initialized from the router's # 'routes' attribute. These routes do not have an interface # parameter and so cannot be removed via an interface_name lookup. + self.extra_subnets = [route for route in self.extra_subnets if + route.interface_name != interface_name] @property def routes(self): - return self.gateway_routes + self.extra_routes + return self.gateway_routes + self.extra_routes + self.extra_subnets def __len__(self): return len(self.routes) diff --git a/neutron/tests/common/l3_test_common.py b/neutron/tests/common/l3_test_common.py index 2cb66d4de..6045f56bb 100644 --- a/neutron/tests/common/l3_test_common.py +++ b/neutron/tests/common/l3_test_common.py @@ -56,18 +56,21 @@ def prepare_router_data(ip_version=4, enable_snat=None, num_internal_ports=1, fixed_ips = [] subnets = [] gateway_mac = kwargs.get('gateway_mac', 'ca:fe:de:ad:be:ee') + extra_subnets = [] for loop_version in (4, 6): if loop_version == 4 and (ip_version == 4 or dual_stack): ip_address = kwargs.get('ip_address', '19.4.4.4') prefixlen = 24 subnet_cidr = kwargs.get('subnet_cidr', '19.4.4.0/24') gateway_ip = kwargs.get('gateway_ip', '19.4.4.1') + _extra_subnet = {'cidr': '9.4.5.0/24'} elif (loop_version == 6 and (ip_version == 6 or dual_stack) and v6_ext_gw_with_sub): ip_address = kwargs.get('ip_address', 'fd00::4') prefixlen = 64 subnet_cidr = kwargs.get('subnet_cidr', 'fd00::/64') gateway_ip = kwargs.get('gateway_ip', 'fd00::1') + _extra_subnet = {'cidr': 'fd01::/64'} else: continue subnet_id = _uuid() @@ -77,6 +80,7 @@ def prepare_router_data(ip_version=4, enable_snat=None, num_internal_ports=1, subnets.append({'id': subnet_id, 'cidr': subnet_cidr, 'gateway_ip': gateway_ip}) + extra_subnets.append(_extra_subnet) if not fixed_ips and v6_ext_gw_with_sub: raise ValueError("Invalid ip_version: %s" % ip_version) @@ -85,7 +89,8 @@ def prepare_router_data(ip_version=4, enable_snat=None, num_internal_ports=1, 'mac_address': gateway_mac, 'network_id': _uuid(), 'fixed_ips': fixed_ips, - 'subnets': subnets} + 'subnets': subnets, + 'extra_subnets': extra_subnets} routes = [] if extra_routes: diff --git a/neutron/tests/functional/agent/linux/test_ip_lib.py b/neutron/tests/functional/agent/linux/test_ip_lib.py index 68cecc19d..8804599ec 100644 --- a/neutron/tests/functional/agent/linux/test_ip_lib.py +++ b/neutron/tests/functional/agent/linux/test_ip_lib.py @@ -141,11 +141,13 @@ class IpLibTestCase(IpLibTestFramework): expected_routes = [{'nexthop': device_ip, 'device': attr.name, - 'destination': destination}, + 'destination': destination, + 'scope': None}, {'nexthop': None, 'device': attr.name, 'destination': str( - netaddr.IPNetwork(attr.ip_cidrs[0]).cidr)}] + netaddr.IPNetwork(attr.ip_cidrs[0]).cidr), + 'scope': 'link'}] - routes = ip_lib.get_routing_table(namespace=attr.namespace) + routes = ip_lib.get_routing_table(4, namespace=attr.namespace) self.assertEqual(expected_routes, routes) diff --git a/neutron/tests/functional/agent/test_l3_agent.py b/neutron/tests/functional/agent/test_l3_agent.py index e9a3f877a..383bd2888 100644 --- a/neutron/tests/functional/agent/test_l3_agent.py +++ b/neutron/tests/functional/agent/test_l3_agent.py @@ -192,7 +192,7 @@ class L3AgentTestFramework(base.BaseSudoTestCase): floating_ip_cidr = common_utils.ip_to_cidr( router.get_floating_ips()[0]['floating_ip_address']) default_gateway_ip = external_port['subnets'][0].get('gateway_ip') - + extra_subnet_cidr = external_port['extra_subnets'][0].get('cidr') return """vrrp_instance VR_1 { state BACKUP interface %(ha_device_name)s @@ -216,6 +216,7 @@ class L3AgentTestFramework(base.BaseSudoTestCase): virtual_routes { 0.0.0.0/0 via %(default_gateway_ip)s dev %(external_device_name)s 8.8.8.0/24 via 19.4.4.4 + %(extra_subnet_cidr)s dev %(external_device_name)s scope link } }""" % { 'ha_device_name': ha_device_name, @@ -226,7 +227,8 @@ class L3AgentTestFramework(base.BaseSudoTestCase): 'floating_ip_cidr': floating_ip_cidr, 'default_gateway_ip': default_gateway_ip, 'int_port_ipv6': int_port_ipv6, - 'ex_port_ipv6': ex_port_ipv6 + 'ex_port_ipv6': ex_port_ipv6, + 'extra_subnet_cidr': extra_subnet_cidr, } def _get_rule(self, iptables_manager, table, chain, predicate): @@ -272,13 +274,24 @@ class L3AgentTestFramework(base.BaseSudoTestCase): device, router.get_internal_device_name, router.ns_name)) def _assert_extra_routes(self, router): - routes = ip_lib.get_routing_table(namespace=router.ns_name) + routes = ip_lib.get_routing_table(4, namespace=router.ns_name) routes = [{'nexthop': route['nexthop'], 'destination': route['destination']} for route in routes] for extra_route in router.router['routes']: self.assertIn(extra_route, routes) + def _assert_onlink_subnet_routes(self, router, ip_versions): + routes = [] + for ip_version in ip_versions: + _routes = ip_lib.get_routing_table(ip_version, + namespace=router.ns_name) + routes.extend(_routes) + routes = set(route['destination'] for route in routes) + extra_subnets = router.get_ex_gw_port()['extra_subnets'] + for extra_subnet in (route['cidr'] for route in extra_subnets): + self.assertIn(extra_subnet, routes) + def _assert_interfaces_deleted_from_ovs(self): def assert_ovs_bridge_empty(bridge_name): bridge = ovs_lib.OVSBridge(bridge_name) @@ -635,6 +648,8 @@ class L3AgentTestCase(L3AgentTestFramework): self._assert_snat_chains(router) self._assert_floating_ip_chains(router) self._assert_extra_routes(router) + ip_versions = [4, 6] if (ip_version == 6 or dual_stack) else [4] + self._assert_onlink_subnet_routes(router, ip_versions) self._assert_metadata_chains(router) # Verify router gateway interface is configured to receive Router Advts diff --git a/neutron/tests/unit/agent/linux/test_keepalived.py b/neutron/tests/unit/agent/linux/test_keepalived.py index 7d6e9806f..ef8b84913 100644 --- a/neutron/tests/unit/agent/linux/test_keepalived.py +++ b/neutron/tests/unit/agent/linux/test_keepalived.py @@ -202,11 +202,15 @@ class KeepalivedInstanceRoutesTestCase(base.BaseTestCase): keepalived.KeepalivedVirtualRoute('10.0.0.0/8', '1.0.0.1'), keepalived.KeepalivedVirtualRoute('20.0.0.0/8', '2.0.0.2')] routes.extra_routes = extra_routes + extra_subnets = [ + keepalived.KeepalivedVirtualRoute( + '30.0.0.0/8', None, 'eth0', scope='link')] + routes.extra_subnets = extra_subnets return routes def test_routes(self): routes = self._get_instance_routes() - self.assertEqual(len(routes.routes), 4) + self.assertEqual(len(routes.routes), 5) def test_remove_routes_on_interface(self): routes = self._get_instance_routes() @@ -221,6 +225,7 @@ class KeepalivedInstanceRoutesTestCase(base.BaseTestCase): ::/0 via fe80::3e97:eff:fe26:3bfa/64 dev eth1 10.0.0.0/8 via 1.0.0.1 20.0.0.0/8 via 2.0.0.2 + 30.0.0.0/8 dev eth0 scope link }""" routes = self._get_instance_routes() self.assertEqual(expected, '\n'.join(routes.build_config())) -- 2.45.2