]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Set onlink routes for all subnets on an external network
authorCarl Baldwin <carl.baldwin@hp.com>
Thu, 24 Apr 2014 23:06:10 +0000 (23:06 +0000)
committerCarl Baldwin <carl.baldwin@hp.com>
Thu, 22 May 2014 21:47:39 +0000 (21:47 +0000)
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

neutron/agent/l3_agent.py
neutron/agent/linux/interface.py
neutron/agent/linux/ip_lib.py
neutron/db/l3_db.py
neutron/tests/unit/test_l3_agent.py
neutron/tests/unit/test_linux_interface.py

index aa185e430674b9723a345aafd4fca25605ab5526..d81deea8388e9fee91aadd9abd1a1a087cfbf0ec 100644 (file)
@@ -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)
index 84c0f3bb9ec327914a95e4c6221687d28e85a7fe..a31250ee7e0238e27006fae3b87df9cc92b411b2 100644 (file)
@@ -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)
index 40acb4c1840cbadb2e9ec3b2781b2cfbb20ae928..bc32139f276026a0550c22711d8865eea5a2d3c1 100644 (file)
@@ -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 = []
index beebaee878f664ed2fb7ec272ba1a1d91b593758..9eea54fce55b4fe8840ee3ec0c560dda7c401e40 100644 (file)
@@ -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 = {}
index 1b3f231ed1cbc233bbacf4a0685eee97957f84ac..222290d62611c6ba771fcaec67be3f3a1121ad26 100644 (file)
@@ -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)
index 010409731ab1a13fa3f7ea8a77a55431093d4837..749f786b49ab1fcf129fa0f7d1d1d3e3e3c43f62 100644 (file)
@@ -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)
+        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().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().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',