diff --git a/os_vif/exception.py b/os_vif/exception.py index 1a21cf57..cc956f5e 100644 --- a/os_vif/exception.py +++ b/os_vif/exception.py @@ -94,3 +94,7 @@ class ExternalImport(ExceptionBase): msg_fmt = _("Use of this module outside of os_vif is not allowed. It must " "not be imported in os-vif plugins that are out of tree as it " "is not a public interface of os-vif.") + + +class NotImplementedForOS(ExceptionBase): + msg_fmt = _("Function %(function)s for %(os)s operating system") diff --git a/os_vif/internal/command/ip/__init__.py b/os_vif/internal/command/ip/__init__.py index d533d222..ad0339d9 100644 --- a/os_vif/internal/command/ip/__init__.py +++ b/os_vif/internal/command/ip/__init__.py @@ -32,3 +32,8 @@ def add(device, dev_type, check_exit_code=None, peer=None, link=None, def delete(device, check_exit_code=None): """Method to delete an interface.""" return api._get_impl().delete(device, check_exit_code=check_exit_code) + + +def exists(device): + """Method to check if an interface exists.""" + return api._get_impl().exists(device) diff --git a/os_vif/internal/command/ip/api.py b/os_vif/internal/command/ip/api.py index aa75387d..a7f2792f 100644 --- a/os_vif/internal/command/ip/api.py +++ b/os_vif/internal/command/ip/api.py @@ -11,11 +11,14 @@ # under the License. import abc +import os import six from oslo_log import log as logging from oslo_utils import importutils +from os_vif.internal.command.ip.windows import impl_netifaces as win_ip_lib + LOG = logging.getLogger(__name__) @@ -27,12 +30,16 @@ impl_map = { def _get_impl(): - # NOTE(sean-k-mooney): currently pyroute2 has a file handle leak. An - # iptools driver has been added as a workaround but No config options are - # exposed to the user. The iptools driver is considered deprecated and - # will be removed when a new release of pyroute2 is available. - driver = 'IPTools' - return importutils.import_object(impl_map[driver]) + if os.name == 'nt': + return win_ip_lib + else: + # NOTE(sean-k-mooney): currently pyroute2 has a file handle leak. An + # iptools driver has been added as a workaround but No config options + # are # exposed to the user. The iptools driver is considered + # deprecated and # will be removed when a new release of pyroute2 is + # available. + driver = 'IPTools' + return importutils.import_object(impl_map[driver]) @six.add_metaclass(abc.ABCMeta) diff --git a/os_vif/internal/command/ip/linux/__init__.py b/os_vif/internal/command/ip/linux/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/os_vif/internal/command/ip/impl_pyroute2.py b/os_vif/internal/command/ip/linux/impl_pyroute2.py similarity index 94% rename from os_vif/internal/command/ip/impl_pyroute2.py rename to os_vif/internal/command/ip/linux/impl_pyroute2.py index a229b917..d1e446a5 100644 --- a/os_vif/internal/command/ip/impl_pyroute2.py +++ b/os_vif/internal/command/ip/linux/impl_pyroute2.py @@ -92,3 +92,9 @@ class PyRoute2(api.IpCommand): idx = idx[0] return self._ip_link(ip, 'del', check_exit_code, **{'index': idx}) + + def exists(self, device): + """Return True if the device exists.""" + ip = iproute.IPRoute() + idx = ip.link_lookup(ifname=device) + return True if idx else False diff --git a/os_vif/internal/command/ip/windows/__init__.py b/os_vif/internal/command/ip/windows/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/os_vif/internal/command/ip/windows/impl_netifaces.py b/os_vif/internal/command/ip/windows/impl_netifaces.py new file mode 100644 index 00000000..eda20962 --- /dev/null +++ b/os_vif/internal/command/ip/windows/impl_netifaces.py @@ -0,0 +1,45 @@ +# Derived from: neutron/agent/windows/ip_lib.py +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import netifaces + +from oslo_log import log as logging + +from os_vif import exception + + +LOG = logging.getLogger(__name__) + + +def exists(device): + """Return True if the device exists in the namespace.""" + try: + return bool(netifaces.ifaddresses(device)) + except ValueError: + LOG.warning("The device does not exist on the system: %s", device) + except OSError: + LOG.error("Failed to get interface addresses: %s", device) + return False + + +def set(*args): + exception.NotImplementedForOS(function='ip.set', os='Windows') + + +def add(*args): + exception.NotImplementedForOS(function='ip.add', os='Windows') + + +def delete(*args): + exception.NotImplementedForOS(function='ip.delete', os='Windows') diff --git a/os_vif/tests/functional/internal/command/ip/test_impl_pyroute2.py b/os_vif/tests/functional/internal/command/ip/test_impl_pyroute2.py index 16353c7c..f891cb7c 100644 --- a/os_vif/tests/functional/internal/command/ip/test_impl_pyroute2.py +++ b/os_vif/tests/functional/internal/command/ip/test_impl_pyroute2.py @@ -15,7 +15,7 @@ import re from oslo_concurrency import processutils from oslo_utils import excutils -from os_vif.internal.command.ip import impl_pyroute2 +from os_vif.internal.command.ip.linux import impl_pyroute2 from os_vif.tests.functional import base from os_vif.tests.functional import privsep diff --git a/os_vif/tests/unit/internal/command/ip/linux/__init__.py b/os_vif/tests/unit/internal/command/ip/linux/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/os_vif/tests/unit/internal/command/ip/test_impl_pyroute2.py b/os_vif/tests/unit/internal/command/ip/linux/test_impl_pyroute2.py similarity index 99% rename from os_vif/tests/unit/internal/command/ip/test_impl_pyroute2.py rename to os_vif/tests/unit/internal/command/ip/linux/test_impl_pyroute2.py index 43adeb04..b28b0594 100644 --- a/os_vif/tests/unit/internal/command/ip/test_impl_pyroute2.py +++ b/os_vif/tests/unit/internal/command/ip/linux/test_impl_pyroute2.py @@ -16,7 +16,7 @@ from pyroute2.netlink import exceptions as ipexc from pyroute2.netlink.rtnl import ifinfmsg from os_vif import exception -from os_vif.internal.command.ip import impl_pyroute2 +from os_vif.internal.command.ip.linux import impl_pyroute2 from os_vif.tests.unit import base diff --git a/os_vif/tests/unit/internal/command/ip/windows/__init__.py b/os_vif/tests/unit/internal/command/ip/windows/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/os_vif/tests/unit/internal/command/ip/windows/test_impl_netifaces.py b/os_vif/tests/unit/internal/command/ip/windows/test_impl_netifaces.py new file mode 100644 index 00000000..71ddbec7 --- /dev/null +++ b/os_vif/tests/unit/internal/command/ip/windows/test_impl_netifaces.py @@ -0,0 +1,44 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +import netifaces + +from os_vif.internal.command.ip.windows import impl_netifaces as ip_lib +from os_vif.tests.unit import base + + +class TestIPDevice(base.TestCase): + + def setUp(self): + super(TestIPDevice, self).setUp() + self.device_name = 'test_device' + self.mock_log = mock.patch.object(ip_lib, "LOG").start() + + @mock.patch.object(netifaces, 'ifaddresses', return_value=True) + def test_exists(self, mock_ifaddresses): + self.assertTrue(ip_lib.exists(self.device_name)) + mock_ifaddresses.assert_called_once_with(self.device_name) + + @mock.patch.object(netifaces, 'ifaddresses', side_effect=ValueError()) + def test_exists_not_found(self, mock_ifaddresses): + self.assertFalse(ip_lib.exists(self.device_name)) + mock_ifaddresses.assert_called_once_with(self.device_name) + self.mock_log.warning.assert_called_once_with( + "The device does not exist on the system: %s", self.device_name) + + @mock.patch.object(netifaces, 'ifaddresses', side_effect=OSError()) + def test_exists_os_error_exception(self, mock_ifaddresses): + self.assertFalse(ip_lib.exists(self.device_name)) + mock_ifaddresses.assert_called_once_with(self.device_name) + self.mock_log.error.assert_called_once_with( + "Failed to get interface addresses: %s", self.device_name) diff --git a/vif_plug_linux_bridge/linux_net.py b/vif_plug_linux_bridge/linux_net.py index 596b571d..5b3a3d32 100644 --- a/vif_plug_linux_bridge/linux_net.py +++ b/vif_plug_linux_bridge/linux_net.py @@ -34,11 +34,6 @@ LOG = logging.getLogger(__name__) _IPTABLES_MANAGER = None -def device_exists(device): - """Check if ethernet device exists.""" - return os.path.exists('/sys/class/net/%s' % device) - - def _set_device_mtu(dev, mtu): """Set the device MTU.""" if mtu: @@ -76,7 +71,7 @@ def _ensure_vlan_privileged(vlan_num, bridge_interface, mac_address, mtu): with elevated privileges. """ interface = 'vlan%s' % vlan_num - if not device_exists(interface): + if not ip_lib.exists(interface): LOG.debug('Starting VLAN interface %s', interface) ip_lib.add(interface, 'vlan', link=bridge_interface, vlan_id=vlan_num, check_exit_code=[0, 2, 254]) @@ -121,13 +116,13 @@ def _ensure_bridge_privileged(bridge, interface, net_attrs, gateway, interface onto the bridge and reset the default gateway if necessary. """ - if not device_exists(bridge): + if not ip_lib.exists(bridge): LOG.debug('Starting Bridge %s', bridge) try: processutils.execute('brctl', 'addbr', bridge) except Exception: with excutils.save_and_reraise_exception() as ectx: - ectx.reraise = not device_exists(bridge) + ectx.reraise = not ip_lib.exists(bridge) processutils.execute('brctl', 'setfd', bridge, 0) # processutils.execute('brctl setageing %s 10' % bridge) processutils.execute('brctl', 'stp', bridge, 'off') diff --git a/vif_plug_linux_bridge/tests/unit/test_linux_net.py b/vif_plug_linux_bridge/tests/unit/test_linux_net.py index dabb2354..214153b8 100644 --- a/vif_plug_linux_bridge/tests/unit/test_linux_net.py +++ b/vif_plug_linux_bridge/tests/unit/test_linux_net.py @@ -54,7 +54,7 @@ class LinuxNetTest(testtools.TestCase): @mock.patch.object(ip_lib, "add") @mock.patch.object(ip_lib, "set") - @mock.patch.object(linux_net, "device_exists", return_value=False) + @mock.patch.object(ip_lib, "exists", return_value=False) @mock.patch.object(linux_net, "_set_device_mtu") def test_ensure_vlan(self, mock_set_mtu, mock_dev_exists, mock_ip_set, mock_ip_add): @@ -73,23 +73,23 @@ class LinuxNetTest(testtools.TestCase): mock_set_mtu.assert_called_once_with('vlan123', 1500) @mock.patch.object(processutils, "execute") - @mock.patch.object(linux_net, "device_exists", return_value=True) + @mock.patch.object(ip_lib, "exists", return_value=True) def test_ensure_bridge_exists(self, mock_dev_exists, mock_exec): linux_net.ensure_bridge("br0", None, filtering=False) mock_exec.assert_not_called() mock_dev_exists.assert_called_once_with("br0") + @mock.patch.object(ip_lib, "exists", return_value=False) @mock.patch.object(processutils, "execute") - @mock.patch.object(linux_net, "device_exists", return_value=False) - def test_ensure_bridge_addbr_exception(self, mock_dev_exists, mock_exec): + def test_ensure_bridge_addbr_exception(self, mock_exec, mock_dev_exists): mock_exec.side_effect = ValueError() with testtools.ExpectedException(ValueError): linux_net.ensure_bridge("br0", None, filtering=False) @mock.patch.object(ip_lib, "set") @mock.patch.object(processutils, "execute") - @mock.patch.object(linux_net, "device_exists", side_effect=[False, True]) + @mock.patch.object(ip_lib, "exists", side_effect=[False, True]) def test_ensure_bridge_concurrent_add(self, mock_dev_exists, mock_exec, mock_ip_set): mock_exec.side_effect = [ValueError(), 0, 0, 0] @@ -106,7 +106,7 @@ class LinuxNetTest(testtools.TestCase): @mock.patch.object(linux_net, "_set_device_mtu") @mock.patch.object(os.path, "exists", return_value=False) @mock.patch.object(processutils, "execute") - @mock.patch.object(linux_net, "device_exists", return_value=False) + @mock.patch.object(ip_lib, "exists", return_value=False) def test_ensure_bridge_mtu_not_called(self, mock_dev_exists, mock_exec, mock_path_exists, mock_set_mtu, mock_ip_set): """This test validates that mtus are updated only if an interface @@ -121,7 +121,7 @@ class LinuxNetTest(testtools.TestCase): @mock.patch.object(linux_net, "_set_device_mtu") @mock.patch.object(os.path, "exists", return_value=False) @mock.patch.object(processutils, "execute", return_value=("", "")) - @mock.patch.object(linux_net, "device_exists", return_value=False) + @mock.patch.object(ip_lib, "exists", return_value=False) def test_ensure_bridge_mtu_order(self, mock_dev_exists, mock_exec, mock_path_exists, mock_set_mtu, mock_ip_set): """This test validates that when adding an interface @@ -141,7 +141,7 @@ class LinuxNetTest(testtools.TestCase): @mock.patch.object(ip_lib, "set") @mock.patch.object(os.path, "exists", return_value=False) @mock.patch.object(processutils, "execute") - @mock.patch.object(linux_net, "device_exists", return_value=False) + @mock.patch.object(ip_lib, "exists", return_value=False) def test_ensure_bridge_new_ipv4(self, mock_dev_exists, mock_exec, mock_path_exists, mock_ip_set): linux_net.ensure_bridge("br0", None, filtering=False) @@ -156,7 +156,7 @@ class LinuxNetTest(testtools.TestCase): @mock.patch.object(ip_lib, "set") @mock.patch.object(os.path, "exists", return_value=True) @mock.patch.object(processutils, "execute") - @mock.patch.object(linux_net, "device_exists", return_value=False) + @mock.patch.object(ip_lib, "exists", return_value=False) def test_ensure_bridge_new_ipv6(self, mock_dev_exists, mock_exec, mock_path_exists, mock_ip_set): linux_net.ensure_bridge("br0", None, filtering=False) diff --git a/vif_plug_ovs/linux_net.py b/vif_plug_ovs/linux_net.py index 6a0dbc58..9bef005f 100644 --- a/vif_plug_ovs/linux_net.py +++ b/vif_plug_ovs/linux_net.py @@ -113,11 +113,6 @@ def delete_ovs_vif_port(bridge, dev, timeout=None, _delete_net_dev(dev) -def device_exists(device): - """Check if ethernet device exists.""" - return os.path.exists('/sys/class/net/%s' % device) - - def interface_in_bridge(bridge, device): """Check if an ethernet device belongs to a Linux Bridge.""" return os.path.exists('/sys/class/net/%(bridge)s/brif/%(device)s' % @@ -126,7 +121,7 @@ def interface_in_bridge(bridge, device): def _delete_net_dev(dev): """Delete a network device only if it exists.""" - if device_exists(dev): + if ip_lib.exists(dev): try: ip_lib.delete(dev, check_exit_code=[0, 2, 254]) LOG.debug("Net device removed: '%s'", dev) @@ -166,7 +161,7 @@ def ensure_ovs_bridge(bridge, datapath_type, timeout=None, @privsep.vif_plug.entrypoint def ensure_bridge(bridge): - if not device_exists(bridge): + if not ip_lib.exists(bridge): processutils.execute('brctl', 'addbr', bridge) processutils.execute('brctl', 'setfd', bridge, 0) processutils.execute('brctl', 'stp', bridge, 'off') @@ -188,7 +183,7 @@ def ensure_bridge(bridge): @privsep.vif_plug.entrypoint def delete_bridge(bridge, dev): - if device_exists(bridge): + if ip_lib.exists(bridge): if interface_in_bridge(bridge, dev): processutils.execute('brctl', 'delif', bridge, dev) diff --git a/vif_plug_ovs/ovs.py b/vif_plug_ovs/ovs.py index 53c9618e..0cfe8b87 100644 --- a/vif_plug_ovs/ovs.py +++ b/vif_plug_ovs/ovs.py @@ -19,6 +19,7 @@ import sys +from os_vif.internal.command import ip as ip_lib from os_vif import objects from os_vif import plugin from oslo_config import cfg @@ -172,7 +173,7 @@ class OvsPlugin(plugin.PluginBase): linux_net.ensure_bridge(vif.bridge_name) mtu = self._get_mtu(vif) - if not linux_net.device_exists(v2_name): + if not ip_lib.exists(v2_name): linux_net.create_veth_pair(v1_name, v2_name, mtu) linux_net.add_bridge_port(vif.bridge_name, v1_name) linux_net.ensure_ovs_bridge(vif.network.bridge, @@ -187,7 +188,7 @@ class OvsPlugin(plugin.PluginBase): def _plug_vif_windows(self, vif, instance_info): """Create a per-VIF OVS port.""" - if not linux_net.device_exists(vif.id): + if not ip_lib.exists(vif.id): linux_net.ensure_ovs_bridge(vif.network.bridge, self._get_vif_datapath_type(vif)) self._create_vif_port(vif, vif.id, instance_info) diff --git a/vif_plug_ovs/tests/unit/test_linux_net.py b/vif_plug_ovs/tests/unit/test_linux_net.py index b0f71ee0..47ed9c08 100644 --- a/vif_plug_ovs/tests/unit/test_linux_net.py +++ b/vif_plug_ovs/tests/unit/test_linux_net.py @@ -32,7 +32,7 @@ class LinuxNetTest(testtools.TestCase): privsep.vif_plug.set_client_mode(False) @mock.patch.object(ip_lib, "set") - @mock.patch.object(linux_net, "device_exists", return_value=True) + @mock.patch.object(ip_lib, "exists", return_value=True) def test_ensure_bridge_exists(self, mock_dev_exists, mock_ip_set): linux_net.ensure_bridge("br0") @@ -43,7 +43,7 @@ class LinuxNetTest(testtools.TestCase): @mock.patch.object(ip_lib, "set") @mock.patch.object(os.path, "exists", return_value=False) @mock.patch.object(processutils, "execute") - @mock.patch.object(linux_net, "device_exists", return_value=False) + @mock.patch.object(ip_lib, "exists", return_value=False) def test_ensure_bridge_new_ipv4(self, mock_dev_exists, mock_execute, mock_path_exists, mock_ip_set): linux_net.ensure_bridge("br0") @@ -62,11 +62,11 @@ class LinuxNetTest(testtools.TestCase): check_exit_code=[0, 2, 254]) @mock.patch.object(ip_lib, "set") + @mock.patch.object(ip_lib, "exists", return_value=False) @mock.patch.object(os.path, "exists", return_value=True) @mock.patch.object(processutils, "execute") - @mock.patch.object(linux_net, "device_exists", return_value=False) - def test_ensure_bridge_new_ipv6(self, mock_dev_exists, mock_execute, - mock_path_exists, mock_ip_set): + def test_ensure_bridge_new_ipv6(self, mock_execute, mock_path_exists, + mock_dev_exists, mock_ip_set): linux_net.ensure_bridge("br0") calls = [ @@ -85,7 +85,7 @@ class LinuxNetTest(testtools.TestCase): check_exit_code=[0, 2, 254]) @mock.patch.object(processutils, "execute") - @mock.patch.object(linux_net, "device_exists", return_value=False) + @mock.patch.object(ip_lib, "exists", return_value=False) @mock.patch.object(linux_net, "interface_in_bridge", return_value=False) def test_delete_bridge_none(self, mock_interface_br, mock_dev_exists, mock_execute,): @@ -97,7 +97,7 @@ class LinuxNetTest(testtools.TestCase): @mock.patch.object(ip_lib, "set") @mock.patch.object(processutils, "execute") - @mock.patch.object(linux_net, "device_exists", return_value=True) + @mock.patch.object(ip_lib, "exists", return_value=True) @mock.patch.object(linux_net, "interface_in_bridge", return_value=True) def test_delete_bridge_exists(self, mock_interface_br, mock_dev_exists, mock_execute, mock_ip_set): @@ -113,7 +113,7 @@ class LinuxNetTest(testtools.TestCase): @mock.patch.object(ip_lib, "set") @mock.patch.object(processutils, "execute") - @mock.patch.object(linux_net, "device_exists", return_value=True) + @mock.patch.object(ip_lib, "exists", return_value=True) @mock.patch.object(linux_net, "interface_in_bridge", return_value=False) def test_delete_interface_not_present(self, mock_interface_br, mock_dev_exists, mock_execute, mock_ip_set): diff --git a/vif_plug_ovs/tests/unit/test_plugin.py b/vif_plug_ovs/tests/unit/test_plugin.py index c1754b2d..dc6cff04 100644 --- a/vif_plug_ovs/tests/unit/test_plugin.py +++ b/vif_plug_ovs/tests/unit/test_plugin.py @@ -13,6 +13,7 @@ import mock import testtools +from os_vif.internal.command import ip as ip_lib from os_vif import objects from os_vif.objects import fields @@ -167,7 +168,7 @@ class PluginTest(testtools.TestCase): @mock.patch.object(linux_net, 'add_bridge_port') @mock.patch.object(linux_net, 'update_veth_pair') @mock.patch.object(linux_net, 'create_veth_pair') - @mock.patch.object(linux_net, 'device_exists') + @mock.patch.object(ip_lib, 'exists') @mock.patch.object(linux_net, 'ensure_bridge') @mock.patch.object(ovs, 'sys') def test_plug_ovs_bridge(self, mock_sys, ensure_bridge, device_exists, @@ -222,7 +223,6 @@ class PluginTest(testtools.TestCase): # plugging existing devices should result in devices being updated device_exists.return_value = True - self.assertTrue(linux_net.device_exists('test')) plugin.plug(self.vif_ovs_hybrid, self.instance) create_veth_pair.assert_not_called() _create_vif_port.assert_not_called() @@ -231,13 +231,13 @@ class PluginTest(testtools.TestCase): @mock.patch.object(linux_net, 'ensure_ovs_bridge') @mock.patch.object(ovs.OvsPlugin, '_create_vif_port') - @mock.patch.object(linux_net, 'device_exists', return_value=False) + @mock.patch.object(ip_lib, 'exists', return_value=False) @mock.patch.object(ovs, 'sys') - def _check_plug_ovs_windows(self, vif, mock_sys, device_exists, + def _check_plug_ovs_windows(self, vif, mock_sys, mock_exists, _create_vif_port, ensure_ovs_bridge): dp_type = ovs.OvsPlugin._get_vif_datapath_type(vif) calls = { - 'device_exists': [mock.call(vif.id)], + 'exists': [mock.call(vif.id)], '_create_vif_port': [mock.call(vif, vif.id, self.instance)], 'ensure_ovs_bridge': [mock.call('br0', dp_type)] } @@ -245,7 +245,7 @@ class PluginTest(testtools.TestCase): mock_sys.platform = constants.PLATFORM_WIN32 plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME) plugin.plug(vif, self.instance) - device_exists.assert_has_calls(calls['device_exists']) + mock_exists.assert_has_calls(calls['exists']) _create_vif_port.assert_has_calls(calls['_create_vif_port']) ensure_ovs_bridge.assert_has_calls(calls['ensure_ovs_bridge'])