Add support for Windows network commands

Added a network library dependant on the operating system. Windows
and Linux OS are supported. The library is choosen automatically
depending on Python ``os.name``.

Linux library is based on ``ip`` commands and still relying on
``processutils`` library. Linux implementation can't use ``netifaces``
because this library doesn't provide the needed functionality. [1] will
migrate all ``ip`` commands to ``pyroute2`` library.

Windows library relies on Python's ``netifaces`` library because is
enough for the reduced set of commands used. The only functionality
implemented, to cover the bug resolution, is the device existence
check. New functionalities could be added following, for example,
the Neutron implementation.

[1] https://review.openstack.org/#/c/484386/

Closes-Bug: #1672812

Change-Id: I84e3582135ed02137366c8f55f1dd1e4c115f0b5
This commit is contained in:
Rodolfo Alonso Hernandez 2017-12-22 12:24:34 +00:00 committed by Rodolfo Alonso Hernandez
parent 3eded6ba37
commit e8d102b500
18 changed files with 151 additions and 49 deletions

View File

@ -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")

View File

@ -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)

View File

@ -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,10 +30,14 @@ impl_map = {
def _get_impl():
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.
# 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])

View File

@ -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

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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')

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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):

View File

@ -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'])