]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Add netns to support overlapping address ranges
authorMark McClain <mark.mcclain@dreamhost.com>
Fri, 20 Jul 2012 01:37:24 +0000 (21:37 -0400)
committerMark McClain <mark.mcclain@dreamhost.com>
Fri, 10 Aug 2012 16:23:40 +0000 (12:23 -0400)
blueprint: dhcp-overlapping-ips

This change uses linux network namespaces to isolate dhcp interfaces so
that tenant network IP address ranges can properly overlap.

Change-Id: Iaa07e7c38d0813d07c3405884e513276e43e2afd

quantum/agent/dhcp_agent.py
quantum/agent/linux/dhcp.py
quantum/agent/linux/interface.py
quantum/tests/unit/test_dhcp_agent.py
quantum/tests/unit/test_linux_dhcp.py
quantum/tests/unit/test_linux_interface.py

index 8ab0c925404d791fe191057e1a2007bd0456e9f7..b8e203434d6fe3cdf62a1b5765ba63093b2c0f68 100644 (file)
@@ -231,7 +231,9 @@ class DeviceManager(object):
         port = self._get_or_create_port(network)
         interface_name = self.get_interface_name(network, port)
 
-        if ip_lib.device_exists(interface_name):
+        if ip_lib.device_exists(interface_name,
+                                self.conf.root_helper,
+                                network.id):
             if not reuse_existing:
                 raise exceptions.PreexistingDeviceFailure(
                     dev_name=interface_name)
index 5ed015867b9d4d2fa06c982054cb418aa8bc000a..5792f9a71f4aea18c2760a3eaba530a8e3ed5260 100644 (file)
@@ -24,6 +24,7 @@ import tempfile
 
 import netaddr
 
+from quantum.agent.linux import ip_lib
 from quantum.agent.linux import utils
 from quantum.openstack.common import cfg
 from quantum.openstack.common import importutils
@@ -110,7 +111,10 @@ class DhcpLocalProcess(DhcpBase):
         pid = self.pid
 
         if self.active:
-            utils.execute(['kill', '-9', pid], self.root_helper)
+            cmd = ['kill', '-9', pid]
+            ip_wrapper = ip_lib.IPWrapper(self.root_helper,
+                                          namespace=self.network.id)
+            ip_wrapper.netns.execute(cmd)
             self.device_delegate.destroy(self.network)
         elif pid:
             LOG.debug(_('DHCP for %s pid %d is stale, ignoring command') %
@@ -178,7 +182,6 @@ class Dnsmasq(DhcpLocalProcess):
         """Spawns a Dnsmasq process for the network."""
         interface_name = self.device_delegate.get_interface_name(self.network)
         cmd = [
-            'NETWORK_ID=%s' % self.network.id,
             # TODO (mark): this is dhcpbridge script we'll need to know
             # when an IP address has been released
             'dnsmasq',
@@ -219,7 +222,9 @@ class Dnsmasq(DhcpLocalProcess):
         if self.conf.dnsmasq_dns_server:
             cmd.append('--server=%s' % self.conf.dnsmasq_dns_server)
 
-        utils.execute(cmd, self.root_helper)
+        ip_wrapper = ip_lib.IPWrapper(self.root_helper,
+                                      namespace=self.network.id)
+        ip_wrapper.netns.execute(cmd)
 
     def reload_allocations(self):
         """If all subnets turn off dhcp, kill the process."""
@@ -232,7 +237,10 @@ class Dnsmasq(DhcpLocalProcess):
         """Rebuilds the dnsmasq config and signal the dnsmasq to reload."""
         self._output_hosts_file()
         self._output_opts_file()
-        utils.execute(['kill', '-HUP', self.pid], self.root_helper)
+        cmd = ['kill', '-HUP', self.pid]
+        ip_wrapper = ip_lib.IPWrapper(self.root_helper,
+                                      namespace=self.network.id)
+        ip_wrapper.netns.execute(cmd)
         LOG.debug(_('Reloading allocations for network: %s') % self.network.id)
 
     def _output_hosts_file(self):
index 4f7d9bec63d17971e888a01ac4c737a67809113d..03ed071dfe545c59df61c299626a383fac215e6f 100644 (file)
@@ -52,7 +52,9 @@ class LinuxInterfaceDriver(object):
 
     def init_l3(self, port, device_name):
         """Set the L3 settings for the interface using data from the port."""
-        device = ip_lib.IPDevice(device_name, self.conf.root_helper)
+        device = ip_lib.IPDevice(device_name,
+                                 self.conf.root_helper,
+                                 port.network.id)
 
         previous = {}
         for address in device.addr.list(scope='global', filters=['permanent']):
@@ -107,7 +109,10 @@ class OVSInterfaceDriver(LinuxInterfaceDriver):
 
         self.check_bridge_exists(bridge)
 
-        if not ip_lib.device_exists(device_name):
+        if not ip_lib.device_exists(device_name,
+                                    self.conf.root_helper,
+                                    namespace=network_id):
+
             utils.execute(['ovs-vsctl',
                            '--', '--may-exist', 'add-port', bridge,
                            device_name,
@@ -127,6 +132,9 @@ class OVSInterfaceDriver(LinuxInterfaceDriver):
             device.link.set_address(mac_address)
             if self.conf.network_device_mtu:
                 device.link.set_mtu(self.conf.network_device_mtu)
+
+            namespace = ip.ensure_namespace(network_id)
+            namespace.add_device_to_namespace(device)
             device.link.set_up()
         else:
             LOG.error(_('Device %s already exists') % device)
@@ -147,14 +155,21 @@ class BridgeInterfaceDriver(LinuxInterfaceDriver):
 
     def plug(self, network_id, port_id, device_name, mac_address):
         """Plugin the interface."""
-        if not ip_lib.device_exists(device_name):
+        if not ip_lib.device_exists(device_name,
+                                    self.conf.root_helper,
+                                    namespace=network_id):
             ip = ip_lib.IPWrapper(self.conf.root_helper)
 
             tap_name = device_name.replace(self.DEV_NAME_PREFIX, 'tap')
             root_veth, dhcp_veth = ip.add_veth(tap_name, device_name)
             root_veth.link.set_address(mac_address)
+
+            namespace = ip.ensure_namespace(network_id)
+            namespace.add_device_to_namespace(root_veth)
+
             root_veth.link.set_up()
             dhcp_veth.link.set_up()
+
         else:
             LOG.warn(_("Device %s already exists") % device_name)
 
index 0eeb7eb75cc5cf44802bbe768f9803aec68ea9b2..3e2312ea831c56888c45dc5f9c00abfc9a4272dd 100644 (file)
@@ -360,6 +360,7 @@ class TestDeviceManager(unittest.TestCase):
         self.conf.register_opts(dhcp_agent.DeviceManager.OPTS)
         self.conf.set_override('interface_driver',
                                'quantum.agent.linux.interface.NullDriver')
+        self.conf.root_helper = 'sudo'
 
         self.client_cls_p = mock.patch('quantumclient.v2_0.client.Client')
         client_cls = self.client_cls_p.start()
index e20e3a392cfb2db254b1f7d9d7a70544d7c53faa..2686253812fb0810ee72b97baaa3355b739ab75c 100644 (file)
@@ -280,7 +280,9 @@ class TestDhcpLocalProcess(TestBase):
             lp.disable()
 
         delegate.assert_has_calls([mock.call.destroy(network)])
-        self.execute.assert_called_once_with(['kill', '-9', 5], 'sudo')
+        exp_args = ['ip', 'netns', 'exec',
+                    'cccccccc-cccc-cccc-cccc-cccccccccccc', 'kill', '-9', 5]
+        self.execute.assert_called_once_with(exp_args, root_helper='sudo')
 
     def test_pid(self):
         with mock.patch('__builtin__.open') as mock_open:
@@ -311,7 +313,10 @@ class TestDnsmasq(TestBase):
             return '/dhcp/cccccccc-cccc-cccc-cccc-cccccccccccc/%s' % kind
 
         expected = [
-            'NETWORK_ID=cccccccc-cccc-cccc-cccc-cccccccccccc',
+            'ip',
+            'netns',
+            'exec',
+            'cccccccc-cccc-cccc-cccc-cccccccccccc',
             'dnsmasq',
             '--no-hosts',
             '--no-resolv',
@@ -347,7 +352,7 @@ class TestDnsmasq(TestBase):
                               device_delegate=delegate)
             dm.spawn_process()
             self.assertTrue(mocks['_output_opts_file'].called)
-            self.execute.assert_called_once_with(expected, 'sudo')
+            self.execute.assert_called_once_with(expected, root_helper='sudo')
 
     def test_spawn(self):
         self._test_spawn([])
@@ -388,6 +393,8 @@ class TestDnsmasq(TestBase):
 """.lstrip()
         exp_opt_name = '/dhcp/cccccccc-cccc-cccc-cccc-cccccccccccc/opts'
         exp_opt_data = "tag:tag0,option:router,192.168.0.1"
+        exp_args = ['ip', 'netns', 'exec',
+                    'cccccccc-cccc-cccc-cccc-cccccccccccc', 'kill', '-HUP', 5]
 
         with mock.patch('os.path.isdir') as isdir:
             isdir.return_value = True
@@ -398,4 +405,4 @@ class TestDnsmasq(TestBase):
 
         self.safe.assert_has_calls([mock.call(exp_host_name, exp_host_data),
                                     mock.call(exp_opt_name, exp_opt_data)])
-        self.execute.assert_called_once_with(['kill', '-HUP', 5], 'sudo')
+        self.execute.assert_called_once_with(exp_args, root_helper='sudo')
index 5b70b0f942bfeaf436e447c256f0c4b04815341e..a9d9cd63878b82a99164b5cf02e2279e5438ce1e 100644 (file)
@@ -94,7 +94,7 @@ class TestABCDriver(TestBase):
         bc = BaseChild(self.conf)
         bc.init_l3(FakePort(), 'tap0')
         self.ip_dev.assert_has_calls(
-            [mock.call('tap0', 'sudo'),
+            [mock.call('tap0', 'sudo', '12345678-1234-5678-90ab-ba0987654321'),
              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')])
@@ -127,7 +127,10 @@ class TestOVSInterfaceDriver(TestBase):
                     mock.call().device('tap0'),
                     mock.call().device().link.set_address('aa:bb:cc:dd:ee:ff')]
         expected.extend(additional_expectation)
-        expected.extend([mock.call().device().link.set_up()])
+        expected.extend(
+            [mock.call().ensure_namespace('01234567-1234-1234-99'),
+             mock.call().ensure_namespace().add_device_to_namespace(mock.ANY),
+             mock.call().device().link.set_up()])
 
         self.ip.assert_has_calls(expected)
 
@@ -172,9 +175,10 @@ class TestBridgeInterfaceDriver(TestBase):
                 'aa:bb:cc:dd:ee:ff')
 
         self.ip.assert_has_calls(
-            [mock.call(),
-             mock.call('sudo'),
-             mock.call().add_veth('tap0', 'dhc0')])
+            [mock.call('sudo'),
+             mock.call().add_veth('tap0', 'dhc0'),
+             mock.call().ensure_namespace('01234567-1234-1234-99'),
+             mock.call().ensure_namespace().add_device_to_namespace(mock.ANY)])
 
         root_veth.assert_has_calls([mock.call.link.set_up()])
         ns_veth.assert_has_calls([mock.call.link.set_up()])
@@ -237,7 +241,7 @@ class TestRyuInterfaceDriver(TestBase):
         super(TestRyuInterfaceDriver, self).tearDown()
 
     @staticmethod
-    def _device_exists(dev, root_helper=None):
+    def _device_exists(dev, root_helper=None, namespace=None):
         return dev == 'br-int'
 
     _vsctl_cmd_init = ['ovs-vsctl', '--timeout=2',
@@ -281,8 +285,12 @@ class TestRyuInterfaceDriver(TestBase):
 
         self.ryu_app_client.OFPClient.assert_called_once_with('127.0.0.1:8080')
 
-        expected = [mock.call('sudo'),
-                    mock.call().device('tap0'),
-                    mock.call().device().link.set_address('aa:bb:cc:dd:ee:ff'),
-                    mock.call().device().link.set_up()]
+        expected = [
+            mock.call('sudo'),
+            mock.call().device('tap0'),
+            mock.call().device().link.set_address('aa:bb:cc:dd:ee:ff'),
+            mock.call().ensure_namespace('01234567-1234-1234-99'),
+            mock.call().ensure_namespace().add_device_to_namespace(mock.ANY),
+            mock.call().device().link.set_up()]
+
         self.ip.assert_has_calls(expected)