]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Add extra subnet route to ha router
authorgong yong sheng <gong.yongsheng@99cloud.net>
Thu, 25 Jun 2015 04:38:40 +0000 (12:38 +0800)
committergong yong sheng <gong.yongsheng@99cloud.net>
Wed, 8 Jul 2015 08:01:04 +0000 (16:01 +0800)
Add scope in HA virtual route mode to
represent on link scope route from extra subnet.

Co-Authored-By: Assaf Muller <amuller@redhat.com>
Change-Id: I6ce000a7aa8e4b9e61a35a86d3dc8c0b7beaa3a9
Closes-bug: 1414640

neutron/agent/l3/ha_router.py
neutron/agent/linux/ip_lib.py
neutron/agent/linux/keepalived.py
neutron/tests/common/l3_test_common.py
neutron/tests/functional/agent/linux/test_ip_lib.py
neutron/tests/functional/agent/test_l3_agent.py
neutron/tests/unit/agent/linux/test_keepalived.py

index 31a2c18dc583aa06226113b5ea7656ec5cee1a39..cb9e7f3d6b4669bde2c631c76ec4369d89b81dab 100644 (file)
@@ -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']
index 36d2b09523b5dc1458a1d582a3d93866d60a64f9..4c039591a07c8661d2f846438c1eb978a47df8c7 100644 (file)
@@ -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
 
 
index f856def6d46304f11a63d909fda34624c4db91fb..d39f015f944c54bff5b2b4d38c8c3568c407d3ce 100644 (file)
@@ -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)
index 2cb66d4deea12c9e3a4a2781264d6beaf41e6adb..6045f56bb441224e63c780c97bc0dd51d35948a4 100644 (file)
@@ -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:
index 68cecc19d8f752b41b0ba5dfc7c31acd860d1d5d..8804599ec5bad97dfca6ff649eef32a01c2337aa 100644 (file)
@@ -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)
index e9a3f877a0c43d44275f2e00650af4c35c0c15ea..383bd2888048697c07c15a7ec7157f9eea24d3a7 100644 (file)
@@ -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
index 7d6e9806f9ec422b8ccd8eceba11951cb267e8b9..ef8b8491331023691b29df32dc5415df2f12546e 100644 (file)
@@ -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()))