From 796157a3a1150c0f69cddafdc986d9e8edd4a4b8 Mon Sep 17 00:00:00 2001 From: Mark McClain Date: Thu, 26 Jul 2012 14:42:55 -0400 Subject: [PATCH] update DHCP agent to work with linuxbridge plug-in Fixes bug: 1027194 Update the interface driver to use veths instead of tap devices. This change is compatible with the netns work. Change-Id: Ic236f5fdeb79eb36791434999df2b731856de092 --- quantum/agent/dhcp_agent.py | 2 +- quantum/agent/linux/interface.py | 30 +-- quantum/agent/linux/ip_lib.py | 192 +++++++++---- quantum/tests/unit/test_dhcp_agent.py | 18 +- quantum/tests/unit/test_linux_interface.py | 85 +++--- quantum/tests/unit/test_linux_ip_lib.py | 298 +++++++++++++++++---- 6 files changed, 450 insertions(+), 175 deletions(-) diff --git a/quantum/agent/dhcp_agent.py b/quantum/agent/dhcp_agent.py index 29b8a9d14d..3e4c4ed564 100644 --- a/quantum/agent/dhcp_agent.py +++ b/quantum/agent/dhcp_agent.py @@ -209,7 +209,7 @@ class DeviceManager(object): def get_interface_name(self, network, port=None): if not port: port = self._get_or_create_port(network) - return ('tap' + port.id)[:self.driver.DEV_NAME_LEN] + return self.driver.get_device_name(port) def get_device_id(self, network): # There could be more than one dhcp server per network, so create diff --git a/quantum/agent/linux/interface.py b/quantum/agent/linux/interface.py index 9a4c6531d5..3ff13d8311 100644 --- a/quantum/agent/linux/interface.py +++ b/quantum/agent/linux/interface.py @@ -42,6 +42,7 @@ class LinuxInterfaceDriver(object): # from linux IF_NAMESIZE DEV_NAME_LEN = 14 + DEV_NAME_PREFIX = 'tap' def __init__(self, conf): self.conf = conf @@ -74,6 +75,9 @@ class LinuxInterfaceDriver(object): if not ip_lib.device_exists(bridge): raise exception.BridgeDoesNotExist(bridge=bridge) + def get_device_name(self, port): + return (self.DEV_NAME_PREFIX + port.id)[:self.DEV_NAME_LEN] + @abc.abstractmethod def plug(self, network_id, port_id, device_name, mac_address): """Plug in the interface.""" @@ -115,7 +119,8 @@ class OVSInterfaceDriver(LinuxInterfaceDriver): mac_address], self.conf.root_helper) - device = ip_lib.IPDevice(device_name, self.conf.root_helper) + ip = ip_lib.IPWrapper(self.conf.root_helper) + device = ip.device(device_name) device.link.set_address(mac_address) if self.conf.network_device_mtu: device.link.set_mtu(self.conf.network_device_mtu) @@ -135,22 +140,18 @@ class OVSInterfaceDriver(LinuxInterfaceDriver): class BridgeInterfaceDriver(LinuxInterfaceDriver): """Driver for creating bridge interfaces.""" - BRIDGE_NAME_PREFIX = 'brq' + DEV_NAME_PREFIX = 'dhc' def plug(self, network_id, port_id, device_name, mac_address): """Plugin the interface.""" if not ip_lib.device_exists(device_name): - device = ip_lib.IPDevice(device_name, self.conf.root_helper) - try: - # First, try with 'ip' - device.tuntap.add() - except RuntimeError, e: - # Second option: tunctl - utils.execute(['tunctl', '-b', '-t', device_name], - self.conf.root_helper) + ip = ip_lib.IPWrapper(self.conf.root_helper) - device.link.set_address(mac_address) - device.link.set_up() + 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) + root_veth.link.set_up() + dhcp_veth.link.set_up() else: LOG.warn(_("Device %s already exists") % device_name) @@ -163,8 +164,3 @@ class BridgeInterfaceDriver(LinuxInterfaceDriver): except RuntimeError: LOG.error(_("Failed unplugging interface '%s'") % device_name) - - def get_bridge(self, network_id): - """Returns the name of the bridge interface.""" - bridge = self.BRIDGE_NAME_PREFIX + network_id[0:11] - return bridge diff --git a/quantum/agent/linux/ip_lib.py b/quantum/agent/linux/ip_lib.py index 099ae78795..26ee281ec9 100644 --- a/quantum/agent/linux/ip_lib.py +++ b/quantum/agent/linux/ip_lib.py @@ -15,36 +15,108 @@ # under the License. from quantum.agent.linux import utils +from quantum.common import exceptions -class IPDevice(object): - def __init__(self, name, root_helper=None): - self.name = name +class SubProcessBase(object): + def __init__(self, root_helper=None, namespace=None): self.root_helper = root_helper - self._commands = {} + self.namespace = namespace + def _run(self, options, command, args): + if self.namespace: + return self._as_root(options, command, args) + else: + return self._execute(options, command, args) + + def _as_root(self, options, command, args): + if not self.root_helper: + raise exceptions.SudoRequired() + return self._execute(options, + command, + args, + self.root_helper, + self.namespace) + + @classmethod + def _execute(cls, options, command, args, root_helper=None, + namespace=None): + opt_list = ['-%s' % o for o in options] + if namespace: + ip_cmd = ['ip', 'netns', 'exec', namespace, 'ip'] + else: + ip_cmd = ['ip'] + return utils.execute(ip_cmd + opt_list + [command] + list(args), + root_helper=root_helper) + + +class IPWrapper(SubProcessBase): + def __init__(self, root_helper=None, namespace=None): + super(IPWrapper, self).__init__(root_helper=root_helper, + namespace=namespace) + self.netns = IpNetnsCommand(self) + + def device(self, name): + return IPDevice(name, self.root_helper, self.namespace) + + def get_devices(self): + retval = [] + output = self._execute('o', 'link', ('list',), + self.root_helper, self.namespace) + for line in output.split('\n'): + if '<' not in line: + continue + tokens = line.split(':', 2) + if len(tokens) >= 3: + retval.append(IPDevice(tokens[1].strip(), + self.root_helper, + self.namespace)) + return retval + + def add_tuntap(self, name, mode='tap'): + self._as_root('', 'tuntap', ('add', name, 'mode', mode)) + return IPDevice(name, self.root_helper, self.namespace) + + def add_veth(self, name1, name2): + self._as_root('', 'link', + ('add', name1, 'type', 'veth', 'peer', 'name', name2)) + + return (IPDevice(name1, self.root_helper, self.namespace), + IPDevice(name2, self.root_helper, self.namespace)) + + def ensure_namespace(self, name): + if not self.netns.exists(name): + ip = self.netns.add(name) + lo = ip.device('lo') + lo.link.set_up() + else: + ip = IP(self.root_helper, name) + return ip + + def add_device_to_namespace(self, device): + if self.namespace: + device.link.set_netns(self.namespace) + + @classmethod + def get_namespaces(cls, root_helper): + output = cls._execute('netns', ('list',), root_helper=root_helper) + return [l.strip() for l in output.split('\n')] + + +class IPDevice(SubProcessBase): + def __init__(self, name, root_helper=None, namespace=None): + super(IPDevice, self).__init__(root_helper=root_helper, + namespace=namespace) + self.name = name self.link = IpLinkCommand(self) - self.tuntap = IpTuntapCommand(self) self.addr = IpAddrCommand(self) def __eq__(self, other): - return self.name == other.name + return (other is not None and self.name == other.name + and self.namespace == other.namespace) - @classmethod - def _execute(cls, options, command, args, root_helper=None): - opt_list = ['-%s' % o for o in options] - return utils.execute(['ip'] + opt_list + [command] + list(args), - root_helper=root_helper) - - @classmethod - def get_devices(cls): - retval = [] - for line in cls._execute('o', 'link', ('list',)).split('\n'): - if '<' not in line: - continue - index, name, attrs = line.split(':', 2) - retval.append(IPDevice(name.strip())) - return retval + def __str__(self): + return self.name class IpCommandBase(object): @@ -53,25 +125,22 @@ class IpCommandBase(object): def __init__(self, parent): self._parent = parent + def _run(self, *args, **kwargs): + return self._parent._run(kwargs.get('options', []), self.COMMAND, args) + + def _as_root(self, *args, **kwargs): + return self._parent._as_root(kwargs.get('options', []), + self.COMMAND, + args) + + +class IpDeviceCommandBase(IpCommandBase): @property def name(self): return self._parent.name - def _run(self, *args, **kwargs): - return self._parent._execute(kwargs.get('options', []), - self.COMMAND, - args) - def _as_root(self, *args, **kwargs): - if not self._parent.root_helper: - raise exceptions.SudoRequired() - return self._parent._execute(kwargs.get('options', []), - self.COMMAND, - args, - self._parent.root_helper) - - -class IpLinkCommand(IpCommandBase): +class IpLinkCommand(IpDeviceCommandBase): COMMAND = 'link' def set_address(self, mac_address): @@ -86,6 +155,14 @@ class IpLinkCommand(IpCommandBase): def set_down(self): self._as_root('set', self.name, 'down') + def set_netns(self, namespace): + self._as_root('set', self.name, 'netns', namespace) + self._parent.namespace = namespace + + def set_name(self, name): + self._as_root('set', self.name, 'name', name) + self._parent.name = name + def delete(self): self._as_root('delete', self.name) @@ -124,14 +201,7 @@ class IpLinkCommand(IpCommandBase): return retval -class IpTuntapCommand(IpCommandBase): - COMMAND = 'tuntap' - - def add(self): - self._as_root('add', self.name, 'mode', 'tap') - - -class IpAddrCommand(IpCommandBase): +class IpAddrCommand(IpDeviceCommandBase): COMMAND = 'addr' def add(self, ip_version, cidr, broadcast, scope='global'): @@ -182,10 +252,38 @@ class IpAddrCommand(IpCommandBase): return retval -def device_exists(device_name): - try: - address = IPDevice(device_name).link.address - except RuntimeError: +class IpNetnsCommand(IpCommandBase): + COMMAND = 'netns' + + def add(self, name): + self._as_root('add', name) + return IPWrapper(self._parent.root_helper, name) + + def delete(self, name): + self._as_root('delete', name) + + def execute(self, cmds): + if not self._parent.root_helper: + raise exceptions.SudoRequired() + elif not self._parent.namespace: + raise Exception(_('No namespace defined for parent')) + else: + return utils.execute( + ['ip', 'netns', 'exec', self._parent.namespace] + list(cmds), + root_helper=self._parent.root_helper) + + def exists(self, name): + output = self._as_root('list', options='o') + + for line in output.split('\n'): + if name == line.strip(): + return True return False + +def device_exists(device_name, root_helper=None, namespace=None): + try: + address = IPDevice(device_name, root_helper, namespace).link.address + except RuntimeError: + return False return True diff --git a/quantum/tests/unit/test_dhcp_agent.py b/quantum/tests/unit/test_dhcp_agent.py index 2c00e5210d..cac9c65108 100644 --- a/quantum/tests/unit/test_dhcp_agent.py +++ b/quantum/tests/unit/test_dhcp_agent.py @@ -52,11 +52,13 @@ class TestDhcpAgent(unittest.TestCase): def test_dhcp_agent_main(self): with mock.patch('quantum.agent.dhcp_agent.DeviceManager') as dev_mgr: with mock.patch('quantum.agent.dhcp_agent.DhcpAgent') as dhcp: - dhcp_agent.main() - dev_mgr.assert_called_once(mock.ANY, 'sudo') - dhcp.assert_has_calls([ - mock.call(mock.ANY), - mock.call().daemon_loop()]) + with mock.patch('quantum.agent.dhcp_agent.sys') as mock_sys: + mock_sys.argv = [] + dhcp_agent.main() + dev_mgr.assert_called_once(mock.ANY, 'sudo') + dhcp.assert_has_calls([ + mock.call(mock.ANY), + mock.call().daemon_loop()]) def test_daemon_loop_survives_get_network_state_delta_failure(self): def stop_loop(*args): @@ -269,6 +271,8 @@ class TestDeviceManager(unittest.TestCase): name='filter_by', side_effect=get_filter_results) + self.mock_driver.get_device_name.return_value = 'tap12345678-12' + dh = dhcp_agent.DeviceManager(self.conf, mock_db) dh.setup(fake_network) @@ -323,6 +327,7 @@ class TestDeviceManager(unittest.TestCase): mock_driver.DEV_NAME_LEN = ( interface.LinuxInterfaceDriver.DEV_NAME_LEN) mock_driver.port = fake_port + mock_driver.get_device_name.return_value = 'tap12345678-12' dvr_cls.return_value = mock_driver dh = dhcp_agent.DeviceManager(self.conf, mock_db) @@ -330,7 +335,8 @@ class TestDeviceManager(unittest.TestCase): dvr_cls.assert_called_once_with(self.conf) mock_driver.assert_has_calls( - [mock.call.unplug('tap12345678-12')]) + [mock.call.get_device_name(mock.ANY), + mock.call.unplug('tap12345678-12')]) class TestAugmentingWrapper(unittest.TestCase): diff --git a/quantum/tests/unit/test_linux_interface.py b/quantum/tests/unit/test_linux_interface.py index 07591bed9c..c85899c3ec 100644 --- a/quantum/tests/unit/test_linux_interface.py +++ b/quantum/tests/unit/test_linux_interface.py @@ -34,6 +34,10 @@ class BaseChild(interface.LinuxInterfaceDriver): pass +class FakeNetwork: + id = '12345678-1234-5678-90ab-ba0987654321' + + class FakeSubnet: cidr = '192.168.1.1/24' @@ -44,9 +48,11 @@ class FakeAllocation: ip_version = 4 -class FakePort(object): +class FakePort: + id = 'abcdef01-1234-5678-90ab-ba0987654321' fixed_ips = [FakeAllocation] device_id = 'cccccccc-cccc-cccc-cccc-cccccccccccc' + network = FakeNetwork() class TestBase(unittest.TestCase): @@ -59,6 +65,8 @@ class TestBase(unittest.TestCase): self.conf.register_opts(root_helper_opt) self.ip_dev_p = mock.patch.object(ip_lib, 'IPDevice') self.ip_dev = self.ip_dev_p.start() + self.ip_p = mock.patch.object(ip_lib, 'IPWrapper') + self.ip = self.ip_p.start() self.device_exists_p = mock.patch.object(ip_lib, 'device_exists') self.device_exists = self.device_exists_p.start() @@ -69,9 +77,15 @@ class TestBase(unittest.TestCase): except RuntimeError, e: pass self.ip_dev_p.stop() + self.ip_p.stop() class TestABCDriver(TestBase): + def test_get_device_name(self): + bc = BaseChild(self.conf) + device_name = bc.get_device_name(FakePort()) + self.assertEqual('tapabcdef01-12', device_name) + def test_l3_init(self): addresses = [dict(ip_version=4, scope='global', dynamic=False, cidr='172.16.77.240/24')] @@ -88,7 +102,7 @@ class TestABCDriver(TestBase): class TestOVSInterfaceDriver(TestBase): def test_plug(self, additional_expectation=[]): - def device_exists(dev, root_helper=None): + def device_exists(dev, root_helper=None, namespace=None): return dev == 'br-int' vsctl_cmd = ['ovs-vsctl', '--', '--may-exist', 'add-port', @@ -109,16 +123,17 @@ class TestOVSInterfaceDriver(TestBase): 'aa:bb:cc:dd:ee:ff') execute.assert_called_once_with(vsctl_cmd, 'sudo') - expected = [mock.call('tap0', 'sudo'), - mock.call().link.set_address('aa:bb:cc:dd:ee:ff')] - + expected = [mock.call('sudo'), + mock.call().device('tap0'), + mock.call().device().link.set_address('aa:bb:cc:dd:ee:ff')] expected.extend(additional_expectation) - expected.append(mock.call().link.set_up()) - self.ip_dev.assert_has_calls(expected) + expected.extend([mock.call().device().link.set_up()]) + + self.ip.assert_has_calls(expected) def test_plug_mtu(self): self.conf.set_override('network_device_mtu', 9000) - self.test_plug([mock.call().link.set_mtu(9000)]) + self.test_plug([mock.call().device().link.set_mtu(9000)]) def test_unplug(self): with mock.patch('quantum.agent.linux.ovs_lib.OVSBridge') as ovs_br: @@ -129,14 +144,20 @@ class TestOVSInterfaceDriver(TestBase): class TestBridgeInterfaceDriver(TestBase): - def test_get_bridge(self): + def test_get_device_name(self): br = interface.BridgeInterfaceDriver(self.conf) - self.assertEqual('brq12345678-11', br.get_bridge('12345678-1122-3344')) + device_name = br.get_device_name(FakePort()) + self.assertEqual('dhcabcdef01-12', device_name) def test_plug(self): - def device_exists(device, root_helper=None): + def device_exists(device, root_helper=None, namespace=None): return device.startswith('brq') + root_veth = mock.Mock() + ns_veth = mock.Mock() + + self.ip().add_veth = mock.Mock(return_value=(root_veth, ns_veth)) + expected = [mock.call(c, 'sudo') for c in [ ['ip', 'tuntap', 'add', 'tap0', 'mode', 'tap'], ['ip', 'link', 'set', 'tap0', 'address', 'aa:bb:cc:dd:ee:ff'], @@ -147,14 +168,16 @@ class TestBridgeInterfaceDriver(TestBase): br = interface.BridgeInterfaceDriver(self.conf) br.plug('01234567-1234-1234-99', 'port-1234', - 'tap0', + 'dhc0', 'aa:bb:cc:dd:ee:ff') - self.ip_dev.assert_has_calls( - [mock.call('tap0', 'sudo'), - mock.call().tuntap.add(), - mock.call().link.set_address('aa:bb:cc:dd:ee:ff'), - mock.call().link.set_up()]) + self.ip.assert_has_calls( + [mock.call(), + mock.call('sudo'), + mock.call().add_veth('tap0', 'dhc0')]) + + root_veth.assert_has_calls([mock.call.link.set_up()]) + ns_veth.assert_has_calls([mock.call.link.set_up()]) def test_plug_dev_exists(self): self.device_exists.return_value = True @@ -167,34 +190,6 @@ class TestBridgeInterfaceDriver(TestBase): self.ip_dev.assert_has_calls([]) self.assertEquals(log.call_count, 1) - def test_tunctl_failback(self): - def device_exists(dev, root_helper=None): - return dev.startswith('brq') - - expected = [mock.call(c, 'sudo') for c in [ - ['ip', 'tuntap', 'add', 'tap0', 'mode', 'tap'], - ['tunctl', '-b', '-t', 'tap0'], - ['ip', 'link', 'set', 'tap0', 'address', 'aa:bb:cc:dd:ee:ff'], - ['ip', 'link', 'set', 'tap0', 'up']] - ] - - self.device_exists.side_effect = device_exists - self.ip_dev().tuntap.add.side_effect = RuntimeError - self.ip_dev.reset_calls() - with mock.patch.object(utils, 'execute') as execute: - br = interface.BridgeInterfaceDriver(self.conf) - br.plug('01234567-1234-1234-99', - 'port-1234', - 'tap0', - 'aa:bb:cc:dd:ee:ff') - execute.assert_called_once_with(['tunctl', '-b', '-t', 'tap0'], - 'sudo') - self.ip_dev.assert_has_calls( - [mock.call('tap0', 'sudo'), - mock.call().tuntap.add(), - mock.call().link.set_address('aa:bb:cc:dd:ee:ff'), - mock.call().link.set_up()]) - def test_unplug(self): self.device_exists.return_value = True with mock.patch('quantum.agent.linux.interface.LOG.debug') as log: diff --git a/quantum/tests/unit/test_linux_ip_lib.py b/quantum/tests/unit/test_linux_ip_lib.py index 13617fd8ea..6b4fe086d0 100644 --- a/quantum/tests/unit/test_linux_ip_lib.py +++ b/quantum/tests/unit/test_linux_ip_lib.py @@ -21,7 +21,12 @@ import mock from quantum.agent.linux import ip_lib from quantum.agent.linux import utils +from quantum.common import exceptions +NETNS_SAMPLE = [ + '12345678-1234-5678-abcd-1234567890ab', + 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', + 'cccccccc-cccc-cccc-cccc-cccccccccccc'] LINK_SAMPLE = [ '1: lo: mtu 16436 qdisc noqueue state UNKNOWN \\' @@ -52,69 +57,203 @@ ADDR_SAMPLE = (""" """) -class TestIPDevice(unittest.TestCase): - def test_execute_wrapper(self): - with mock.patch('quantum.agent.linux.utils.execute') as execute: - ip_lib.IPDevice._execute('o', 'link', ('list',), 'sudo') +class TestSubProcessBase(unittest.TestCase): + def setUp(self): + self.execute_p = mock.patch('quantum.agent.linux.utils.execute') + self.execute = self.execute_p.start() - execute.assert_called_once_with(['ip', '-o', 'link', 'list'], - root_helper='sudo') + def tearDown(self): + self.execute_p.stop() + + def test_execute_wrapper(self): + ip_lib.SubProcessBase._execute('o', 'link', ('list',), 'sudo') + + self.execute.assert_called_once_with(['ip', '-o', 'link', 'list'], + root_helper='sudo') def test_execute_wrapper_int_options(self): - with mock.patch('quantum.agent.linux.utils.execute') as execute: - ip_lib.IPDevice._execute([4], 'link', ('list',)) + ip_lib.SubProcessBase._execute([4], 'link', ('list',)) - execute.assert_called_once_with(['ip', '-4', 'link', 'list'], - root_helper=None) + self.execute.assert_called_once_with(['ip', '-4', 'link', 'list'], + root_helper=None) def test_execute_wrapper_no_options(self): - with mock.patch('quantum.agent.linux.utils.execute') as execute: - ip_lib.IPDevice._execute([], 'link', ('list',)) + ip_lib.SubProcessBase._execute([], 'link', ('list',)) - execute.assert_called_once_with(['ip', 'link', 'list'], - root_helper=None) + self.execute.assert_called_once_with(['ip', 'link', 'list'], + root_helper=None) + + def test_run_no_namespace(self): + base = ip_lib.SubProcessBase('sudo') + base._run([], 'link', ('list',)) + self.execute.assert_called_once_with(['ip', 'link', 'list'], + root_helper=None) + + def test_run_namespace(self): + base = ip_lib.SubProcessBase('sudo', 'ns') + base._run([], 'link', ('list',)) + self.execute.assert_called_once_with(['ip', 'netns', 'exec', 'ns', + 'ip', 'link', 'list'], + root_helper='sudo') + + def test_as_root_namespace(self): + base = ip_lib.SubProcessBase('sudo', 'ns') + base._as_root([], 'link', ('list',)) + self.execute.assert_called_once_with(['ip', 'netns', 'exec', 'ns', + 'ip', 'link', 'list'], + root_helper='sudo') + + def test_as_root_no_root_helper(self): + base = ip_lib.SubProcessBase() + self.assertRaises(exceptions.SudoRequired, + base._as_root, + [], 'link', ('list',)) + + +class TestIpWrapper(unittest.TestCase): + def setUp(self): + self.execute_p = mock.patch.object(ip_lib.IPWrapper, '_execute') + self.execute = self.execute_p.start() + + def tearDown(self): + self.execute_p.stop() def test_get_devices(self): - with mock.patch.object(ip_lib.IPDevice, '_execute') as _execute: - _execute.return_value = '\n'.join(LINK_SAMPLE) - retval = ip_lib.IPDevice.get_devices() - self.assertEquals(retval, - [ip_lib.IPDevice('lo'), - ip_lib.IPDevice('eth0'), - ip_lib.IPDevice('br-int'), - ip_lib.IPDevice('gw-ddc717df-49')]) + self.execute.return_value = '\n'.join(LINK_SAMPLE) + retval = ip_lib.IPWrapper('sudo').get_devices() + self.assertEquals(retval, + [ip_lib.IPDevice('lo'), + ip_lib.IPDevice('eth0'), + ip_lib.IPDevice('br-int'), + ip_lib.IPDevice('gw-ddc717df-49')]) - _execute.assert_called_once_with('o', 'link', ('list',)) + self.execute.assert_called_once_with('o', 'link', ('list',), + 'sudo', None) + + def test_get_devices_malformed_line(self): + self.execute.return_value = '\n'.join(LINK_SAMPLE + ['gibberish']) + retval = ip_lib.IPWrapper('sudo').get_devices() + self.assertEquals(retval, + [ip_lib.IPDevice('lo'), + ip_lib.IPDevice('eth0'), + ip_lib.IPDevice('br-int'), + ip_lib.IPDevice('gw-ddc717df-49')]) + + self.execute.assert_called_once_with('o', 'link', ('list',), + 'sudo', None) + + def test_get_namespaces(self): + self.execute.return_value = '\n'.join(NETNS_SAMPLE) + retval = ip_lib.IPWrapper.get_namespaces('sudo') + self.assertEquals(retval, + ['12345678-1234-5678-abcd-1234567890ab', + 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', + 'cccccccc-cccc-cccc-cccc-cccccccccccc']) + + self.execute.assert_called_once_with('netns', ('list',), + root_helper='sudo') + + def test_add_tuntap(self): + ip_lib.IPWrapper('sudo').add_tuntap('tap0') + self.execute.assert_called_once_with('', 'tuntap', + ('add', 'tap0', 'mode', 'tap'), + 'sudo', None) + + def test_add_veth(self): + ip_lib.IPWrapper('sudo').add_veth('tap0', 'tap1') + self.execute.assert_called_once_with('', 'link', + ('add', 'tap0', 'type', 'veth', + 'peer', 'name', 'tap1'), + 'sudo', None) + + def test_get_device(self): + dev = ip_lib.IPWrapper('sudo', 'ns').device('eth0') + self.assertEqual(dev.root_helper, 'sudo') + self.assertEqual(dev.namespace, 'ns') + self.assertEqual(dev.name, 'eth0') + + def test_ensure_namespace(self): + with mock.patch.object(ip_lib, 'IPDevice') as ip_dev: + ns = ip_lib.IPWrapper('sudo').ensure_namespace('ns') + self.execute.assert_has_calls([mock.call('o', 'netns', ('list',), + 'sudo', None)]) + ip_dev.assert_has_calls([mock.call('lo', 'sudo', 'ns'), + mock.call().link.set_up()]) + + def test_add_device_to_namespace(self): + dev = mock.Mock() + ip_lib.IPWrapper('sudo', 'ns').add_device_to_namespace(dev) + dev.assert_has_calls([mock.call.link.set_netns('ns')]) + + def test_add_device_to_namespace_is_none(self): + dev = mock.Mock() + ip_lib.IPWrapper('sudo').add_device_to_namespace(dev) + self.assertEqual(dev.mock_calls, []) + + +class TestIPDevice(unittest.TestCase): + def test_eq_same_name(self): + dev1 = ip_lib.IPDevice('tap0') + dev2 = ip_lib.IPDevice('tap0') + self.assertEqual(dev1, dev2) + + def test_eq_diff_name(self): + dev1 = ip_lib.IPDevice('tap0') + dev2 = ip_lib.IPDevice('tap1') + self.assertNotEqual(dev1, dev2) + + def test_eq_same_namespace(self): + dev1 = ip_lib.IPDevice('tap0', 'ns1') + dev2 = ip_lib.IPDevice('tap0', 'ns1') + self.assertEqual(dev1, dev2) + + def test_eq_diff_namespace(self): + dev1 = ip_lib.IPDevice('tap0', 'sudo', 'ns1') + dev2 = ip_lib.IPDevice('tap0', 'sudo', 'ns2') + self.assertNotEqual(dev1, dev2) + + def test_eq_other_is_none(self): + dev1 = ip_lib.IPDevice('tap0', 'sudo', 'ns1') + self.assertNotEqual(dev1, None) + + def test_str(self): + self.assertEqual(str(ip_lib.IPDevice('tap0')), 'tap0') class TestIPCommandBase(unittest.TestCase): + def setUp(self): + self.ip = mock.Mock() + self.ip.root_helper = 'sudo' + self.ip.namespace = 'namespace' + self.ip_cmd = ip_lib.IpCommandBase(self.ip) + self.ip_cmd.COMMAND = 'foo' + + def test_run(self): + self.ip_cmd._run('link', 'show') + self.ip.assert_has_calls([mock.call._run([], 'foo', ('link', 'show'))]) + + def test_run_with_options(self): + self.ip_cmd._run('link', options='o') + self.ip.assert_has_calls([mock.call._run('o', 'foo', ('link', ))]) + + def test_as_root(self): + self.ip_cmd._as_root('link') + self.ip.assert_has_calls([mock.call._as_root([], 'foo', ('link', ))]) + + def test_as_root_with_options(self): + self.ip_cmd._as_root('link', options='o') + self.ip.assert_has_calls([mock.call._as_root('o', 'foo', ('link', ))]) + + +class TestIPDeviceCommandBase(unittest.TestCase): def setUp(self): self.ip_dev = mock.Mock() self.ip_dev.name = 'eth0' self.ip_dev.root_helper = 'sudo' self.ip_dev._execute = mock.Mock(return_value='executed') - self.ip_cmd = ip_lib.IpCommandBase(self.ip_dev) + self.ip_cmd = ip_lib.IpDeviceCommandBase(self.ip_dev) self.ip_cmd.COMMAND = 'foo' - def test_run(self): - self.assertEqual(self.ip_cmd._run('link', 'show'), 'executed') - self.ip_dev._execute.assert_called_once_with([], 'foo', - ('link', 'show')) - - def test_run_with_options(self): - self.assertEqual(self.ip_cmd._run('link', options='o'), 'executed') - self.ip_dev._execute.assert_called_once_with('o', 'foo', ('link',)) - - def test_as_root(self): - self.assertEqual(self.ip_cmd._as_root('link'), 'executed') - self.ip_dev._execute.assert_called_once_with([], 'foo', - ('link',), 'sudo') - - def test_as_root_with_options(self): - self.assertEqual(self.ip_cmd._as_root('link', options='o'), 'executed') - self.ip_dev._execute.assert_called_once_with('o', 'foo', - ('link',), 'sudo') - def test_name_property(self): self.assertEqual(self.ip_cmd.name, 'eth0') @@ -127,16 +266,17 @@ class TestIPCmdBase(unittest.TestCase): def _assert_call(self, options, args): self.parent.assert_has_calls([ - mock.call._execute(options, self.command, args)]) + mock.call._run(options, self.command, args)]) def _assert_sudo(self, options, args): self.parent.assert_has_calls([ - mock.call._execute(options, self.command, args, 'sudo')]) + mock.call._as_root(options, self.command, args)]) class TestIpLinkCommand(TestIPCmdBase): def setUp(self): super(TestIpLinkCommand, self).setUp() + self.parent._run.return_value = LINK_SAMPLE[1] self.command = 'link' self.link_cmd = ip_lib.IpLinkCommand(self.parent) @@ -156,6 +296,16 @@ class TestIpLinkCommand(TestIPCmdBase): self.link_cmd.set_down() self._assert_sudo([], ('set', 'eth0', 'down')) + def test_set_netns(self): + self.link_cmd.set_netns('foo') + self._assert_sudo([], ('set', 'eth0', 'netns', 'foo')) + self.assertEqual(self.parent.namespace, 'foo') + + def test_set_name(self): + self.link_cmd.set_name('tap1') + self._assert_sudo([], ('set', 'eth0', 'name', 'tap1')) + self.assertEqual(self.parent.name, 'tap1') + def test_delete(self): self.link_cmd.delete() self._assert_sudo([], ('delete', 'eth0')) @@ -176,6 +326,10 @@ class TestIpLinkCommand(TestIPCmdBase): self.parent._execute = mock.Mock(return_value=LINK_SAMPLE[1]) self.assertEqual(self.link_cmd.qlen, 1000) + def test_state_property(self): + self.parent._execute = mock.Mock(return_value=LINK_SAMPLE[1]) + self.assertEqual(self.link_cmd.state, 'UP') + def test_settings_property(self): expected = {'mtu': 1500, 'qlen': 1000, @@ -188,18 +342,6 @@ class TestIpLinkCommand(TestIPCmdBase): self._assert_call('o', ('show', 'eth0')) -class TestIpTuntapCommand(TestIPCmdBase): - def setUp(self): - super(TestIpTuntapCommand, self).setUp() - self.parent.name = 'tap0' - self.command = 'tuntap' - self.tuntap_cmd = ip_lib.IpTuntapCommand(self.parent) - - def test_add_tap(self): - self.tuntap_cmd.add() - self._assert_sudo([], ('add', 'tap0', 'mode', 'tap')) - - class TestIpAddrCommand(TestIPCmdBase): def setUp(self): super(TestIpAddrCommand, self).setUp() @@ -244,7 +386,7 @@ class TestIpAddrCommand(TestIPCmdBase): dict(ip_version=6, scope='link', dynamic=False, cidr='fe80::dfcc:aaff:feb9:76ce/64')] - self.parent._execute = mock.Mock(return_value=ADDR_SAMPLE) + self.parent._run = mock.Mock(return_value=ADDR_SAMPLE) self.assertEquals(self.addr_cmd.list(), expected) self._assert_call([], ('show', 'tap0')) @@ -254,12 +396,50 @@ class TestIpAddrCommand(TestIPCmdBase): dynamic=False, cidr='172.16.77.240/24')] output = '\n'.join(ADDR_SAMPLE.split('\n')[0:4]) - self.parent._execute = mock.Mock(return_value=output) + self.parent._run.return_value = output self.assertEquals(self.addr_cmd.list('global', filters=['permanent']), expected) self._assert_call([], ('show', 'tap0', 'permanent', 'scope', 'global')) +class TestIpNetnsCommand(TestIPCmdBase): + def setUp(self): + super(TestIpNetnsCommand, self).setUp() + self.command = 'netns' + self.netns_cmd = ip_lib.IpNetnsCommand(self.parent) + + def test_add_namespace(self): + ns = self.netns_cmd.add('ns') + self._assert_sudo([], ('add', 'ns')) + self.assertEqual(ns.namespace, 'ns') + + def test_delete_namespace(self): + self.netns_cmd.delete('ns') + self._assert_sudo([], ('delete', 'ns')) + + def test_namespace_exists(self): + retval = '\n'.join(NETNS_SAMPLE) + self.parent._as_root.return_value = retval + self.assertTrue( + self.netns_cmd.exists('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb')) + self._assert_sudo('o', ('list',)) + + def test_namespace_doest_not_exist(self): + retval = '\n'.join(NETNS_SAMPLE) + self.parent._as_root.return_value = retval + self.assertFalse( + self.netns_cmd.exists('bbbbbbbb-1111-2222-3333-bbbbbbbbbbbb')) + self._assert_sudo('o', ('list',)) + + def test_execute(self): + self.parent.namespace = 'ns' + with mock.patch('quantum.agent.linux.utils.execute') as execute: + self.netns_cmd.execute(['ip', 'link', 'list']) + execute.assert_called_once_with(['ip', 'netns', 'exec', 'ns', 'ip', + 'link', 'list'], + root_helper='sudo') + + class TestDeviceExists(unittest.TestCase): def test_device_exists(self): with mock.patch.object(ip_lib.IPDevice, '_execute') as _execute: