diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index b10fa287df65..1e06a7a1200f 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -1311,13 +1311,22 @@ def delete_ivs_vif_port(dev): run_as_root=True) -def create_tap_dev(dev, mac_address=None): +def create_tap_dev(dev, mac_address=None, multiqueue=False): if not device_exists(dev): try: # First, try with 'ip' - utils.execute('ip', 'tuntap', 'add', dev, 'mode', 'tap', - run_as_root=True, check_exit_code=[0, 2, 254]) + cmd = ('ip', 'tuntap', 'add', dev, 'mode', 'tap') + if multiqueue: + cmd = cmd + ('multi_queue', ) + utils.execute(*cmd, run_as_root=True, check_exit_code=[0, 2, 254]) except processutils.ProcessExecutionError: + if multiqueue: + LOG.warning( + _LW('Failed to create a tap device with ip tuntap. ' + 'tunctl does not support creation of multi-queue ' + 'enabled devices, skipping fallback.')) + raise + # Second option: tunctl utils.execute('tunctl', '-b', '-t', dev, run_as_root=True) if mac_address: diff --git a/nova/tests/unit/network/test_linux_net.py b/nova/tests/unit/network/test_linux_net.py index 4b90da50f62d..6e65a86669fa 100644 --- a/nova/tests/unit/network/test_linux_net.py +++ b/nova/tests/unit/network/test_linux_net.py @@ -1418,3 +1418,74 @@ class LinuxNetworkTestCase(test.NoDBTestCase): self.assertRaises(processutils.ProcessExecutionError, linux_net.LinuxBridgeInterfaceDriver.remove_bridge, 'fake-bridge') + + @mock.patch('nova.utils.execute') + def test_create_tap_dev(self, mock_execute): + linux_net.create_tap_dev('tap42') + + mock_execute.assert_has_calls([ + mock.call('ip', 'tuntap', 'add', 'tap42', 'mode', 'tap', + run_as_root=True, check_exit_code=[0, 2, 254]), + mock.call('ip', 'link', 'set', 'tap42', 'up', + run_as_root=True, check_exit_code=[0, 2, 254]) + ]) + + @mock.patch('os.path.exists', return_value=True) + @mock.patch('nova.utils.execute') + def test_create_tap_skipped_when_exists(self, mock_execute, mock_exists): + linux_net.create_tap_dev('tap42') + + mock_exists.assert_called_once_with('/sys/class/net/tap42') + mock_execute.assert_not_called() + + @mock.patch('nova.utils.execute') + def test_create_tap_dev_mac(self, mock_execute): + linux_net.create_tap_dev('tap42', '00:11:22:33:44:55') + + mock_execute.assert_has_calls([ + mock.call('ip', 'tuntap', 'add', 'tap42', 'mode', 'tap', + run_as_root=True, check_exit_code=[0, 2, 254]), + mock.call('ip', 'link', 'set', 'tap42', + 'address', '00:11:22:33:44:55', + run_as_root=True, check_exit_code=[0, 2, 254]), + mock.call('ip', 'link', 'set', 'tap42', 'up', + run_as_root=True, check_exit_code=[0, 2, 254]) + ]) + + @mock.patch('nova.utils.execute') + def test_create_tap_dev_fallback_to_tunctl(self, mock_execute): + # ip failed, fall back to tunctl + mock_execute.side_effect = [processutils.ProcessExecutionError, 0, 0] + + linux_net.create_tap_dev('tap42') + + mock_execute.assert_has_calls([ + mock.call('ip', 'tuntap', 'add', 'tap42', 'mode', 'tap', + run_as_root=True, check_exit_code=[0, 2, 254]), + mock.call('tunctl', '-b', '-t', 'tap42', + run_as_root=True), + mock.call('ip', 'link', 'set', 'tap42', 'up', + run_as_root=True, check_exit_code=[0, 2, 254]) + ]) + + @mock.patch('nova.utils.execute') + def test_create_tap_dev_multiqueue(self, mock_execute): + linux_net.create_tap_dev('tap42', multiqueue=True) + + mock_execute.assert_has_calls([ + mock.call('ip', 'tuntap', 'add', 'tap42', 'mode', 'tap', + 'multi_queue', + run_as_root=True, check_exit_code=[0, 2, 254]), + mock.call('ip', 'link', 'set', 'tap42', 'up', + run_as_root=True, check_exit_code=[0, 2, 254]) + ]) + + @mock.patch('nova.utils.execute') + def test_create_tap_dev_multiqueue_tunctl_raises(self, mock_execute): + # if creation of a tap by the means of ip command fails, + # create_tap_dev() will try to do that by the means of tunctl + mock_execute.side_effect = processutils.ProcessExecutionError + # but tunctl can't create multiqueue taps, so the failure is expected + self.assertRaises(processutils.ProcessExecutionError, + linux_net.create_tap_dev, + 'tap42', multiqueue=True)