Migrate from 'ip' commands to 'pyroute2'
This patch migrates the use of command line 'ip' commands to pyroute2 library. A new class, 'IpCommand', is created to wrap the use of the library, implementing the functionalities needed in this project. The new wrapper class is defined in 'os_vif' and is used in 'vif_plug_linux_bridge' and 'vif_plug_ovs'. This patch also adds functional tests in 'os_vif'. The aim of these functional tests is to check 'pyroute2' implementation works correctly, by creating, modifying and deleting network interfaces. 'ip' commands are used to execute additional actions, not relying on the tested library to check its own results. Co-Authored-By: Stephen Finucane <stephenfin@redhat.com> Closes-Bug: #1677238 Change-Id: I18f7b3424a6c447ee89df1f0326ece75f2333bf2
This commit is contained in:
parent
72b27d0e86
commit
dff9093ab6
3
.stestr.conf
Normal file
3
.stestr.conf
Normal file
@ -0,0 +1,3 @@
|
||||
[DEFAULT]
|
||||
test_path=${OS_TEST_PATH:-.}
|
||||
top_dir=./
|
@ -80,3 +80,17 @@ class UnplugException(ExceptionBase):
|
||||
|
||||
class NetworkMissingPhysicalNetwork(ExceptionBase):
|
||||
msg_fmt = _("Physical network is missing for network %(network_uuid)s")
|
||||
|
||||
|
||||
class NetworkInterfaceNotFound(ExceptionBase):
|
||||
msg_fmt = _("Network interface %(interface)s not found")
|
||||
|
||||
|
||||
class NetworkInterfaceTypeNotDefined(ExceptionBase):
|
||||
msg_fmt = _("Network interface type %(type)s not defined")
|
||||
|
||||
|
||||
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.")
|
||||
|
25
os_vif/internal/__init__.py
Normal file
25
os_vif/internal/__init__.py
Normal file
@ -0,0 +1,25 @@
|
||||
# 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 inspect
|
||||
from os import path
|
||||
|
||||
from os_vif import exception
|
||||
|
||||
os_vif_root = path.dirname(path.dirname(path.dirname(__file__)))
|
||||
frames_info = inspect.getouterframes(inspect.currentframe())
|
||||
for frame_info in frames_info[1:]:
|
||||
importer_filename = inspect.getframeinfo(frame_info[0]).filename
|
||||
if os_vif_root in importer_filename:
|
||||
break
|
||||
else:
|
||||
raise exception.ExternalImport()
|
34
os_vif/internal/command/ip/__init__.py
Normal file
34
os_vif/internal/command/ip/__init__.py
Normal file
@ -0,0 +1,34 @@
|
||||
# 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.
|
||||
|
||||
from os_vif.internal.command.ip import api
|
||||
|
||||
|
||||
def set(device, check_exit_code=None, state=None, mtu=None, address=None,
|
||||
promisc=None):
|
||||
"""Method to set a parameter in an interface."""
|
||||
return api._get_impl().set(device, check_exit_code=check_exit_code,
|
||||
state=state, mtu=mtu, address=address,
|
||||
promisc=promisc)
|
||||
|
||||
|
||||
def add(device, dev_type, check_exit_code=None, peer=None, link=None,
|
||||
vlan_id=None):
|
||||
"""Method to add an interface."""
|
||||
return api._get_impl().add(device, dev_type,
|
||||
check_exit_code=check_exit_code, peer=peer,
|
||||
link=link, vlan_id=vlan_id)
|
||||
|
||||
|
||||
def delete(device, check_exit_code=None):
|
||||
"""Method to delete an interface."""
|
||||
return api._get_impl().delete(device, check_exit_code=check_exit_code)
|
79
os_vif/internal/command/ip/api.py
Normal file
79
os_vif/internal/command/ip/api.py
Normal file
@ -0,0 +1,79 @@
|
||||
# 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 abc
|
||||
import six
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import importutils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
impl_map = {
|
||||
'pyroute2': 'os_vif.internal.command.ip.impl_pyroute2',
|
||||
}
|
||||
|
||||
|
||||
def _get_impl():
|
||||
# NOTE(ralonsoh): currently there is only one implementation. No config
|
||||
# options are exposed to the user.
|
||||
pyroute2 = importutils.import_module(impl_map['pyroute2'])
|
||||
return pyroute2.PyRoute2()
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class IpCommand(object):
|
||||
|
||||
TYPE_VETH = 'veth'
|
||||
TYPE_VLAN = 'vlan'
|
||||
|
||||
@abc.abstractmethod
|
||||
def set(self, device, check_exit_code=None, state=None, mtu=None,
|
||||
address=None, promisc=None):
|
||||
"""Method to set a parameter in an interface.
|
||||
|
||||
:param device: A network device (string)
|
||||
:param check_exit_code: List of integers of allowed execution exit
|
||||
codes
|
||||
:param state: String network device state
|
||||
:param mtu: Integer MTU value
|
||||
:param address: String MAC address
|
||||
:param promisc: Boolean promiscuous mode
|
||||
:return: status of the command execution
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def add(self, device, dev_type, check_exit_code=None, peer=None, link=None,
|
||||
vlan_id=None):
|
||||
"""Method to add an interface.
|
||||
|
||||
:param device: A network device (string)
|
||||
:param dev_type: String network device type (TYPE_VETH, TYPE_VLAN)
|
||||
:param check_exit_code: List of integers of allowed execution exit
|
||||
codes
|
||||
:param peer: String peer name, for veth interfaces
|
||||
:param link: String root network interface name, 'device' will be a
|
||||
VLAN tagged virtual interface
|
||||
:param vlan_id: Integer VLAN ID for VLAN devices
|
||||
:return: status of the command execution
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete(self, device, check_exit_code=None):
|
||||
"""Method to delete an interface.
|
||||
|
||||
:param device: A network device (string)
|
||||
:param dev_type: String network device type (TYPE_VETH, TYPE_VLAN)
|
||||
:return: status of the command execution
|
||||
"""
|
94
os_vif/internal/command/ip/impl_pyroute2.py
Normal file
94
os_vif/internal/command/ip/impl_pyroute2.py
Normal file
@ -0,0 +1,94 @@
|
||||
# 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.
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
from pyroute2 import iproute
|
||||
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 api
|
||||
from os_vif import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PyRoute2(api.IpCommand):
|
||||
|
||||
def _ip_link(self, ip, command, check_exit_code, **kwargs):
|
||||
try:
|
||||
LOG.debug('pyroute2 command %(command)s, arguments %(args)s' %
|
||||
{'command': command, 'args': kwargs})
|
||||
return ip.link(command, **kwargs)
|
||||
except ipexc.NetlinkError as e:
|
||||
with excutils.save_and_reraise_exception() as ctx:
|
||||
if e.code in check_exit_code:
|
||||
LOG.error('NetlinkError was raised, code %s, message: %s' %
|
||||
(e.code, str(e)))
|
||||
ctx.reraise = False
|
||||
|
||||
def set(self, device, check_exit_code=None, state=None, mtu=None,
|
||||
address=None, promisc=None):
|
||||
check_exit_code = check_exit_code or []
|
||||
ip = iproute.IPRoute()
|
||||
idx = ip.link_lookup(ifname=device)
|
||||
if not idx:
|
||||
raise exception.NetworkInterfaceNotFound(interface=device)
|
||||
idx = idx[0]
|
||||
|
||||
args = {'index': idx}
|
||||
if state:
|
||||
args['state'] = state
|
||||
if mtu:
|
||||
args['mtu'] = mtu
|
||||
if address:
|
||||
args['address'] = address
|
||||
if promisc is not None:
|
||||
flags = ip.link('get', index=idx)[0]['flags']
|
||||
args['flags'] = (utils.set_mask(flags, ifinfmsg.IFF_PROMISC)
|
||||
if promisc is True else
|
||||
utils.unset_mask(flags, ifinfmsg.IFF_PROMISC))
|
||||
|
||||
if isinstance(check_exit_code, int):
|
||||
check_exit_code = [check_exit_code]
|
||||
|
||||
return self._ip_link(ip, 'set', check_exit_code, **args)
|
||||
|
||||
def add(self, device, dev_type, check_exit_code=None, peer=None, link=None,
|
||||
vlan_id=None):
|
||||
check_exit_code = check_exit_code or []
|
||||
ip = iproute.IPRoute()
|
||||
args = {'ifname': device,
|
||||
'kind': dev_type}
|
||||
if self.TYPE_VLAN == dev_type:
|
||||
args['vlan_id'] = vlan_id
|
||||
idx = ip.link_lookup(ifname=link)
|
||||
if 0 == len(idx):
|
||||
raise exception.NetworkInterfaceNotFound(interface=link)
|
||||
args['link'] = idx[0]
|
||||
elif self.TYPE_VETH == dev_type:
|
||||
args['peer'] = peer
|
||||
else:
|
||||
raise exception.NetworkInterfaceTypeNotDefined(type=dev_type)
|
||||
|
||||
return self._ip_link(ip, 'add', check_exit_code, **args)
|
||||
|
||||
def delete(self, device, check_exit_code=None):
|
||||
check_exit_code = check_exit_code or []
|
||||
ip = iproute.IPRoute()
|
||||
idx = ip.link_lookup(ifname=device)
|
||||
if len(idx) == 0:
|
||||
raise exception.NetworkInterfaceNotFound(interface=device)
|
||||
idx = idx[0]
|
||||
|
||||
return self._ip_link(ip, 'del', check_exit_code, **{'index': idx})
|
0
os_vif/tests/functional/__init__.py
Normal file
0
os_vif/tests/functional/__init__.py
Normal file
112
os_vif/tests/functional/base.py
Normal file
112
os_vif/tests/functional/base.py
Normal file
@ -0,0 +1,112 @@
|
||||
# Derived from: neutron/tests/functional/base.py
|
||||
# neutron/tests/base.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 abc
|
||||
import functools
|
||||
import inspect
|
||||
import os
|
||||
import six
|
||||
import string
|
||||
import sys
|
||||
|
||||
import eventlet.timeout
|
||||
from os_vif import version as osvif_version
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import fileutils
|
||||
from oslotest import base
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _get_test_log_path():
|
||||
return os.environ.get('OS_LOG_PATH', '/tmp')
|
||||
|
||||
|
||||
# This is the directory from which infra fetches log files for functional tests
|
||||
DEFAULT_LOG_DIR = os.path.join(_get_test_log_path(), 'osvif-functional-logs')
|
||||
|
||||
|
||||
def _catch_timeout(f):
|
||||
@functools.wraps(f)
|
||||
def func(self, *args, **kwargs):
|
||||
try:
|
||||
return f(self, *args, **kwargs)
|
||||
except eventlet.Timeout as e:
|
||||
self.fail('Execution of this test timed out: %s' % e)
|
||||
return func
|
||||
|
||||
|
||||
class _CatchTimeoutMetaclass(abc.ABCMeta):
|
||||
def __init__(cls, name, bases, dct):
|
||||
super(_CatchTimeoutMetaclass, cls).__init__(name, bases, dct)
|
||||
for name, method in inspect.getmembers(
|
||||
# NOTE(ihrachys): we should use isroutine because it will catch
|
||||
# both unbound methods (python2) and functions (python3)
|
||||
cls, predicate=inspect.isroutine):
|
||||
if name.startswith('test_'):
|
||||
setattr(cls, name, _catch_timeout(method))
|
||||
|
||||
|
||||
def setup_logging():
|
||||
"""Sets up the logging options for a log with supplied name."""
|
||||
product_name = "os_vif"
|
||||
logging.setup(cfg.CONF, product_name)
|
||||
LOG.info("Logging enabled!")
|
||||
LOG.info("%(prog)s version %(version)s",
|
||||
{'prog': sys.argv[0], 'version': osvif_version.__version__})
|
||||
LOG.debug("command line: %s", " ".join(sys.argv))
|
||||
|
||||
|
||||
def sanitize_log_path(path):
|
||||
"""Sanitize the string so that its log path is shell friendly"""
|
||||
replace_map = string.maketrans(' ()', '-__')
|
||||
return path.translate(replace_map)
|
||||
|
||||
|
||||
# Test worker cannot survive eventlet's Timeout exception, which effectively
|
||||
# kills the whole worker, with all test cases scheduled to it. This metaclass
|
||||
# makes all test cases convert Timeout exceptions into unittest friendly
|
||||
# failure mode (self.fail).
|
||||
@six.add_metaclass(_CatchTimeoutMetaclass)
|
||||
class BaseFunctionalTestCase(base.BaseTestCase):
|
||||
"""Base class for functional tests."""
|
||||
|
||||
def setUp(self):
|
||||
super(BaseFunctionalTestCase, self).setUp()
|
||||
logging.register_options(CONF)
|
||||
setup_logging()
|
||||
fileutils.ensure_tree(DEFAULT_LOG_DIR, mode=0o755)
|
||||
log_file = sanitize_log_path(
|
||||
os.path.join(DEFAULT_LOG_DIR, "%s.txt" % self.id()))
|
||||
self.config(log_file=log_file)
|
||||
|
||||
def config(self, **kw):
|
||||
"""Override some configuration values.
|
||||
|
||||
The keyword arguments are the names of configuration options to
|
||||
override and their values.
|
||||
|
||||
If a group argument is supplied, the overrides are applied to
|
||||
the specified configuration option group.
|
||||
|
||||
All overrides are automatically cleared at the end of the current
|
||||
test by the fixtures cleanup process.
|
||||
"""
|
||||
group = kw.pop('group', None)
|
||||
for k, v in kw.items():
|
||||
CONF.set_override(k, v, group)
|
0
os_vif/tests/functional/internal/__init__.py
Normal file
0
os_vif/tests/functional/internal/__init__.py
Normal file
@ -0,0 +1,183 @@
|
||||
# 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 re
|
||||
|
||||
from oslo_concurrency import processutils
|
||||
from oslo_utils import excutils
|
||||
|
||||
from os_vif.internal.command.ip import impl_pyroute2
|
||||
from os_vif.tests.functional import base
|
||||
from os_vif.tests.functional import privsep
|
||||
|
||||
|
||||
@privsep.os_vif_pctxt.entrypoint
|
||||
def _execute_command(*args):
|
||||
return processutils.execute(*args)
|
||||
|
||||
|
||||
class ShellIpCommands(object):
|
||||
|
||||
def add_device(self, device, dev_type, peer=None, link=None,
|
||||
vlan_id=None):
|
||||
if 'vlan' == dev_type:
|
||||
_execute_command('ip', 'link', 'add', 'link', link,
|
||||
'name', device, 'type', dev_type, 'vlan', 'id',
|
||||
vlan_id)
|
||||
elif 'veth' == dev_type:
|
||||
_execute_command('ip', 'link', 'add', device, 'type', dev_type,
|
||||
'peer', 'name', peer)
|
||||
elif 'dummy' == dev_type:
|
||||
_execute_command('ip', 'link', 'add', device, 'type', dev_type)
|
||||
|
||||
def del_device(self, device):
|
||||
if self.exist_device(device):
|
||||
_execute_command('ip', 'link', 'del', device)
|
||||
|
||||
def set_status_up(self, device):
|
||||
_execute_command('ip', 'link', 'set', device, 'up')
|
||||
|
||||
def set_status_down(self, device):
|
||||
_execute_command('ip', 'link', 'set', device, 'down')
|
||||
|
||||
def set_device_mtu(self, device, mtu):
|
||||
_execute_command('ip', 'link', 'set', device, 'mtu', mtu)
|
||||
|
||||
def show_device(self, device):
|
||||
val, err = _execute_command('ip', 'link', 'show', device)
|
||||
return val.splitlines()
|
||||
|
||||
def exist_device(self, device):
|
||||
try:
|
||||
_execute_command('ip', 'link', 'show', device)
|
||||
return True
|
||||
except processutils.ProcessExecutionError as e:
|
||||
with excutils.save_and_reraise_exception() as saved_exception:
|
||||
if e.exit_code == 1:
|
||||
saved_exception.reraise = False
|
||||
return False
|
||||
|
||||
def show_state(self, device):
|
||||
regex = re.compile(r".*state (?P<state>\w+)")
|
||||
match = regex.match(self.show_device(device)[0])
|
||||
if match is None:
|
||||
return
|
||||
return match.group('state')
|
||||
|
||||
def show_promisc(self, device):
|
||||
regex = re.compile(r".*(PROMISC)")
|
||||
match = regex.match(self.show_device(device)[0])
|
||||
return True if match else False
|
||||
|
||||
def show_mac(self, device):
|
||||
exp = r".*link/ether (?P<mac>([0-9A-Fa-f]{2}[:]){5}[0-9A-Fa-f]{2})"
|
||||
regex = re.compile(exp)
|
||||
match = regex.match(self.show_device(device)[1])
|
||||
if match is None:
|
||||
return
|
||||
return match.group('mac')
|
||||
|
||||
def show_mtu(self, device):
|
||||
regex = re.compile(r".*mtu (?P<mtu>\d+)")
|
||||
match = regex.match(self.show_device(device)[0])
|
||||
if match is None:
|
||||
return
|
||||
return int(match.group('mtu'))
|
||||
|
||||
|
||||
@privsep.os_vif_pctxt.entrypoint
|
||||
def _ip_cmd_set(*args, **kwargs):
|
||||
impl_pyroute2.PyRoute2().set(*args, **kwargs)
|
||||
|
||||
|
||||
@privsep.os_vif_pctxt.entrypoint
|
||||
def _ip_cmd_add(*args, **kwargs):
|
||||
impl_pyroute2.PyRoute2().add(*args, **kwargs)
|
||||
|
||||
|
||||
@privsep.os_vif_pctxt.entrypoint
|
||||
def _ip_cmd_delete(*args, **kwargs):
|
||||
impl_pyroute2.PyRoute2().delete(*args, **kwargs)
|
||||
|
||||
|
||||
class TestIpCommand(ShellIpCommands, base.BaseFunctionalTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestIpCommand, self).setUp()
|
||||
|
||||
def test_set_state(self):
|
||||
device1 = "test_dev_1"
|
||||
device2 = "test_dev_2"
|
||||
self.addCleanup(self.del_device, device1)
|
||||
self.add_device(device1, 'veth', peer=device2)
|
||||
_ip_cmd_set(device1, state='up')
|
||||
_ip_cmd_set(device2, state='up')
|
||||
self.assertEqual('UP', self.show_state(device1))
|
||||
self.assertEqual('UP', self.show_state(device2))
|
||||
_ip_cmd_set(device1, state='down')
|
||||
_ip_cmd_set(device2, state='down')
|
||||
self.assertEqual('DOWN', self.show_state(device1))
|
||||
self.assertEqual('DOWN', self.show_state(device2))
|
||||
|
||||
def test_set_mtu(self):
|
||||
device = "test_dev_3"
|
||||
self.addCleanup(self.del_device, device)
|
||||
self.add_device(device, 'dummy')
|
||||
_ip_cmd_set(device, mtu=1200)
|
||||
self.assertEqual(1200, self.show_mtu(device))
|
||||
_ip_cmd_set(device, mtu=900)
|
||||
self.assertEqual(900, self.show_mtu(device))
|
||||
|
||||
def test_set_address(self):
|
||||
device = "test_dev_4"
|
||||
address1 = "36:a7:e4:f9:01:01"
|
||||
address2 = "36:a7:e4:f9:01:01"
|
||||
self.addCleanup(self.del_device, device)
|
||||
self.add_device(device, 'dummy')
|
||||
_ip_cmd_set(device, address=address1)
|
||||
self.assertEqual(address1, self.show_mac(device))
|
||||
_ip_cmd_set(device, address=address2)
|
||||
self.assertEqual(address2, self.show_mac(device))
|
||||
|
||||
def test_set_promisc(self):
|
||||
device = "test_dev_5"
|
||||
self.addCleanup(self.del_device, device)
|
||||
self.add_device(device, 'dummy')
|
||||
_ip_cmd_set(device, promisc=True)
|
||||
self.assertTrue(self.show_promisc(device))
|
||||
_ip_cmd_set(device, promisc=False)
|
||||
self.assertFalse(self.show_promisc(device))
|
||||
|
||||
def test_add_vlan(self):
|
||||
device = "test_dev_6"
|
||||
link = "test_devlink"
|
||||
self.addCleanup(self.del_device, device)
|
||||
self.addCleanup(self.del_device, link)
|
||||
self.add_device(link, 'dummy')
|
||||
_ip_cmd_add(device, 'vlan', link=link, vlan_id=100)
|
||||
self.assertTrue(self.exist_device(device))
|
||||
|
||||
def test_add_veth(self):
|
||||
device = "test_dev_7"
|
||||
peer = "test_devpeer"
|
||||
self.addCleanup(self.del_device, device)
|
||||
_ip_cmd_add(device, 'veth', peer=peer)
|
||||
self.assertTrue(self.exist_device(device))
|
||||
self.assertTrue(self.exist_device(peer))
|
||||
|
||||
def test_delete(self):
|
||||
device = "test_dev_8"
|
||||
self.addCleanup(self.del_device, device)
|
||||
self.add_device(device, 'dummy')
|
||||
self.assertTrue(self.exist_device(device))
|
||||
_ip_cmd_delete(device)
|
||||
self.assertFalse(self.exist_device(device))
|
21
os_vif/tests/functional/privsep.py
Normal file
21
os_vif/tests/functional/privsep.py
Normal file
@ -0,0 +1,21 @@
|
||||
# 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.
|
||||
|
||||
from oslo_privsep import capabilities as c
|
||||
from oslo_privsep import priv_context
|
||||
|
||||
os_vif_pctxt = priv_context.PrivContext(
|
||||
'os_vif',
|
||||
cfg_section='os_vif_privileged',
|
||||
pypath=__name__ + '.os_vif_pctxt',
|
||||
capabilities=[c.CAP_NET_ADMIN],
|
||||
)
|
0
os_vif/tests/unit/internal/__init__.py
Normal file
0
os_vif/tests/unit/internal/__init__.py
Normal file
0
os_vif/tests/unit/internal/command/__init__.py
Normal file
0
os_vif/tests/unit/internal/command/__init__.py
Normal file
0
os_vif/tests/unit/internal/command/ip/__init__.py
Normal file
0
os_vif/tests/unit/internal/command/ip/__init__.py
Normal file
145
os_vif/tests/unit/internal/command/ip/test_impl_pyroute2.py
Normal file
145
os_vif/tests/unit/internal/command/ip/test_impl_pyroute2.py
Normal file
@ -0,0 +1,145 @@
|
||||
# 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
|
||||
from pyroute2 import iproute
|
||||
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 api as ip_api
|
||||
from os_vif.tests.unit import base
|
||||
|
||||
|
||||
class TestIpCommand(base.TestCase):
|
||||
|
||||
ERROR_CODE = 40
|
||||
OTHER_ERROR_CODE = 50
|
||||
DEVICE = 'device'
|
||||
MTU = 1500
|
||||
MAC = 'ca:fe:ca:fe:ca:fe'
|
||||
UP = 'up'
|
||||
TYPE_VETH = 'veth'
|
||||
TYPE_VLAN = 'vlan'
|
||||
LINK = 'device2'
|
||||
VLAN_ID = 14
|
||||
|
||||
def setUp(self):
|
||||
super(TestIpCommand, self).setUp()
|
||||
self.ip = ip_api._get_impl()
|
||||
self.ip_link_p = mock.patch.object(iproute.IPRoute, 'link')
|
||||
self.ip_link = self.ip_link_p.start()
|
||||
|
||||
def test_set(self):
|
||||
with mock.patch.object(iproute.IPRoute, 'link_lookup',
|
||||
return_value=[1]) as mock_link_lookup:
|
||||
self.ip_link.return_value = [{'flags': 0x4000}]
|
||||
self.ip.set(self.DEVICE, state=self.UP, mtu=self.MTU,
|
||||
address=self.MAC, promisc=True)
|
||||
mock_link_lookup.assert_called_once_with(ifname=self.DEVICE)
|
||||
args = {'state': self.UP,
|
||||
'mtu': self.MTU,
|
||||
'address': self.MAC,
|
||||
'flags': 0x4000 | ifinfmsg.IFF_PROMISC}
|
||||
calls = [mock.call('get', index=1),
|
||||
mock.call('set', index=1, **args)]
|
||||
self.ip_link.assert_has_calls(calls)
|
||||
|
||||
def test_set_exit_code(self):
|
||||
with mock.patch.object(iproute.IPRoute, 'link_lookup',
|
||||
return_value=[1]) as mock_link_lookup:
|
||||
self.ip_link.side_effect = ipexc.NetlinkError(self.ERROR_CODE,
|
||||
msg="Error message")
|
||||
|
||||
self.ip.set(self.DEVICE, check_exit_code=[self.ERROR_CODE])
|
||||
mock_link_lookup.assert_called_once_with(ifname=self.DEVICE)
|
||||
self.ip_link.assert_called_once_with('set', index=1)
|
||||
|
||||
self.assertRaises(ipexc.NetlinkError, self.ip.set, self.DEVICE,
|
||||
check_exit_code=[self.OTHER_ERROR_CODE])
|
||||
|
||||
def test_set_no_interface_found(self):
|
||||
with mock.patch.object(iproute.IPRoute, 'link_lookup',
|
||||
return_value=[]) as mock_link_lookup:
|
||||
self.assertRaises(exception.NetworkInterfaceNotFound, self.ip.set,
|
||||
self.DEVICE)
|
||||
mock_link_lookup.assert_called_once_with(ifname=self.DEVICE)
|
||||
self.ip_link.assert_not_called()
|
||||
|
||||
def test_add_veth(self):
|
||||
self.ip.add(self.DEVICE, self.TYPE_VETH, peer='peer')
|
||||
self.ip_link.assert_called_once_with(
|
||||
'add', ifname=self.DEVICE, kind=self.TYPE_VETH, peer='peer')
|
||||
|
||||
def test_add_vlan(self):
|
||||
with mock.patch.object(iproute.IPRoute, 'link_lookup',
|
||||
return_value=[1]) as mock_link_lookup:
|
||||
self.ip.add(self.DEVICE, self.TYPE_VLAN, link=self.LINK,
|
||||
vlan_id=self.VLAN_ID)
|
||||
mock_link_lookup.assert_called_once_with(ifname=self.LINK)
|
||||
args = {'ifname': self.DEVICE,
|
||||
'kind': self.TYPE_VLAN,
|
||||
'vlan_id': self.VLAN_ID,
|
||||
'link': 1}
|
||||
self.ip_link.assert_called_once_with('add', **args)
|
||||
|
||||
def test_add_vlan_no_interface_found(self):
|
||||
with mock.patch.object(iproute.IPRoute, 'link_lookup',
|
||||
return_value=[]) as mock_link_lookup:
|
||||
self.assertRaises(exception.NetworkInterfaceNotFound, self.ip.add,
|
||||
self.DEVICE, self.TYPE_VLAN, link=self.LINK)
|
||||
mock_link_lookup.assert_called_once_with(ifname=self.LINK)
|
||||
self.ip_link.assert_not_called()
|
||||
|
||||
def test_add_other_type(self):
|
||||
self.assertRaises(exception.NetworkInterfaceTypeNotDefined,
|
||||
self.ip.add, self.DEVICE, 'type_not_defined')
|
||||
|
||||
def test_add_exit_code(self):
|
||||
self.ip_link.side_effect = ipexc.NetlinkError(self.ERROR_CODE,
|
||||
msg="Error message")
|
||||
|
||||
self.ip.add(self.DEVICE, self.TYPE_VETH, peer='peer',
|
||||
check_exit_code=[self.ERROR_CODE])
|
||||
self.ip_link.assert_called_once_with(
|
||||
'add', ifname=self.DEVICE, kind=self.TYPE_VETH, peer='peer')
|
||||
|
||||
self.assertRaises(ipexc.NetlinkError, self.ip.add, self.DEVICE,
|
||||
self.TYPE_VLAN, peer='peer',
|
||||
check_exit_code=[self.OTHER_ERROR_CODE])
|
||||
|
||||
def test_delete(self):
|
||||
with mock.patch.object(iproute.IPRoute, 'link_lookup',
|
||||
return_value=[1]) as mock_link_lookup:
|
||||
self.ip.delete(self.DEVICE)
|
||||
mock_link_lookup.assert_called_once_with(ifname=self.DEVICE)
|
||||
self.ip_link.assert_called_once_with('del', index=1)
|
||||
|
||||
def test_delete_no_interface_found(self):
|
||||
with mock.patch.object(iproute.IPRoute, 'link_lookup',
|
||||
return_value=[]) as mock_link_lookup:
|
||||
self.assertRaises(exception.NetworkInterfaceNotFound,
|
||||
self.ip.delete, self.DEVICE)
|
||||
mock_link_lookup.assert_called_once_with(ifname=self.DEVICE)
|
||||
|
||||
def test_delete_exit_code(self):
|
||||
with mock.patch.object(iproute.IPRoute, 'link_lookup',
|
||||
return_value=[1]) as mock_link_lookup:
|
||||
self.ip_link.side_effect = ipexc.NetlinkError(self.ERROR_CODE,
|
||||
msg="Error message")
|
||||
|
||||
self.ip.delete(self.DEVICE, check_exit_code=[self.ERROR_CODE])
|
||||
mock_link_lookup.assert_called_once_with(ifname=self.DEVICE)
|
||||
self.ip_link.assert_called_once_with('del', index=1)
|
||||
|
||||
self.assertRaises(ipexc.NetlinkError, self.ip.delete, self.DEVICE,
|
||||
check_exit_code=[self.OTHER_ERROR_CODE])
|
19
os_vif/utils.py
Normal file
19
os_vif/utils.py
Normal file
@ -0,0 +1,19 @@
|
||||
# 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.
|
||||
|
||||
|
||||
def set_mask(data, mask):
|
||||
return data | mask
|
||||
|
||||
|
||||
def unset_mask(data, mask, bit_size=32):
|
||||
return data & ((2 ** bit_size - 1) ^ mask)
|
@ -10,5 +10,6 @@ oslo.log>=3.30.0 # Apache-2.0
|
||||
oslo.i18n>=3.15.3 # Apache-2.0
|
||||
oslo.privsep>=1.23.0 # Apache-2.0
|
||||
oslo.versionedobjects>=1.28.0 # Apache-2.0
|
||||
pyroute2>=0.4.21;sys_platform!='win32' # Apache-2.0 (+ dual licensed GPL2)
|
||||
six>=1.10.0 # MIT
|
||||
stevedore>=1.20.0 # Apache-2.0
|
||||
|
@ -9,6 +9,7 @@ reno>=2.5.0 # Apache-2.0
|
||||
sphinx>=1.6.2 # BSD
|
||||
openstackdocstheme>=1.17.0 # Apache-2.0
|
||||
oslotest>=1.10.0 # Apache-2.0
|
||||
stestr>=1.0.0 # Apache-2.0
|
||||
testrepository>=0.0.18 # Apache-2.0/BSD
|
||||
testscenarios>=0.4 # Apache-2.0/BSD
|
||||
testtools>=2.2.0 # MIT
|
||||
|
16
tox.ini
16
tox.ini
@ -10,7 +10,6 @@ setenv =
|
||||
VIRTUAL_ENV={envdir}
|
||||
deps = -r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
commands = python setup.py testr --slowest --testr-args='{posargs}'
|
||||
whitelist_externals = bash
|
||||
|
||||
[tox:jenkins]
|
||||
@ -22,6 +21,21 @@ commands = flake8
|
||||
[testenv:venv]
|
||||
commands = {posargs}
|
||||
|
||||
[testenv:py27]
|
||||
commands =
|
||||
stestr run --black-regex ".tests.functional" --test-path="os_vif/tests" '{posargs}'
|
||||
|
||||
[testenv:py35]
|
||||
commands =
|
||||
stestr run --black-regex ".tests.functional" '{posargs}'
|
||||
|
||||
[testenv:functional]
|
||||
basepython = python2.7
|
||||
setenv =
|
||||
{[testenv]setenv}
|
||||
commands =
|
||||
stestr run --black-regex ".tests.unit" '{posargs}'
|
||||
|
||||
[testenv:cover]
|
||||
commands =
|
||||
coverage erase
|
||||
|
@ -21,6 +21,7 @@
|
||||
|
||||
import os
|
||||
|
||||
from os_vif.internal.command import ip as ip_lib
|
||||
from oslo_concurrency import lockutils
|
||||
from oslo_concurrency import processutils
|
||||
from oslo_log import log as logging
|
||||
@ -28,6 +29,7 @@ from oslo_utils import excutils
|
||||
|
||||
from vif_plug_linux_bridge import privsep
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
_IPTABLES_MANAGER = None
|
||||
|
||||
@ -40,8 +42,7 @@ def device_exists(device):
|
||||
def _set_device_mtu(dev, mtu):
|
||||
"""Set the device MTU."""
|
||||
if mtu:
|
||||
processutils.execute('ip', 'link', 'set', dev, 'mtu', mtu,
|
||||
check_exit_code=[0, 2, 254])
|
||||
ip_lib.set(dev, mtu=mtu, check_exit_code=[0, 2, 254])
|
||||
else:
|
||||
LOG.debug("MTU not set on %(interface_name)s interface",
|
||||
{'interface_name': dev})
|
||||
@ -77,18 +78,14 @@ def _ensure_vlan_privileged(vlan_num, bridge_interface, mac_address, mtu):
|
||||
interface = 'vlan%s' % vlan_num
|
||||
if not device_exists(interface):
|
||||
LOG.debug('Starting VLAN interface %s', interface)
|
||||
processutils.execute('ip', 'link', 'add', 'link',
|
||||
bridge_interface, 'name', interface, 'type',
|
||||
'vlan', 'id', vlan_num,
|
||||
check_exit_code=[0, 2, 254])
|
||||
ip_lib.add(interface, 'vlan', link=bridge_interface,
|
||||
vlan_id=vlan_num, check_exit_code=[0, 2, 254])
|
||||
# (danwent) the bridge will inherit this address, so we want to
|
||||
# make sure it is the value set from the NetworkManager
|
||||
if mac_address:
|
||||
processutils.execute('ip', 'link', 'set', interface,
|
||||
'address', mac_address,
|
||||
check_exit_code=[0, 2, 254])
|
||||
processutils.execute('ip', 'link', 'set', interface, 'up',
|
||||
check_exit_code=[0, 2, 254])
|
||||
ip_lib.set(interface, address=mac_address,
|
||||
check_exit_code=[0, 2, 254])
|
||||
ip_lib.set(interface, state='up', check_exit_code=[0, 2, 254])
|
||||
# NOTE(vish): set mtu every time to ensure that changes to mtu get
|
||||
# propogated
|
||||
_set_device_mtu(interface, mtu)
|
||||
@ -144,7 +141,7 @@ def _ensure_bridge_privileged(bridge, interface, net_attrs, gateway,
|
||||
# instead it inherits the MAC address of the first device on the
|
||||
# bridge, which will either be the vlan interface, or a
|
||||
# physical NIC.
|
||||
processutils.execute('ip', 'link', 'set', bridge, 'up')
|
||||
ip_lib.set(bridge, state='up')
|
||||
|
||||
if interface:
|
||||
LOG.debug('Adding interface %(interface)s to bridge %(bridge)s',
|
||||
@ -156,8 +153,7 @@ def _ensure_bridge_privileged(bridge, interface, net_attrs, gateway,
|
||||
msg = _('Failed to add interface: %s') % err
|
||||
raise Exception(msg)
|
||||
|
||||
out, err = processutils.execute('ip', 'link', 'set',
|
||||
interface, 'up', check_exit_code=False)
|
||||
ip_lib.set(interface, state='up')
|
||||
|
||||
_set_device_mtu(interface, mtu)
|
||||
|
||||
|
@ -15,6 +15,7 @@ import os.path
|
||||
import testtools
|
||||
|
||||
import fixtures
|
||||
from os_vif.internal.command import ip as ip_lib
|
||||
from oslo_concurrency import lockutils
|
||||
from oslo_concurrency import processutils
|
||||
from oslo_config import cfg
|
||||
@ -40,36 +41,35 @@ class LinuxNetTest(testtools.TestCase):
|
||||
group='oslo_concurrency')
|
||||
self.useFixture(log_fixture.get_logging_handle_error_fixture())
|
||||
|
||||
@mock.patch.object(processutils, "execute")
|
||||
def test_set_device_mtu(self, execute):
|
||||
@mock.patch.object(ip_lib, "set")
|
||||
def test_set_device_mtu(self, mock_ip_set):
|
||||
linux_net._set_device_mtu(dev='fakedev', mtu=1500)
|
||||
expected = ['ip', 'link', 'set', 'fakedev', 'mtu', 1500]
|
||||
execute.assert_called_with(*expected, check_exit_code=mock.ANY)
|
||||
mock_ip_set.assert_called_once_with('fakedev', mtu=1500,
|
||||
check_exit_code=[0, 2, 254])
|
||||
|
||||
@mock.patch.object(processutils, "execute")
|
||||
def test_set_device_invalid_mtu(self, mock_exec):
|
||||
linux_net._set_device_mtu(dev='fakedev', mtu=None)
|
||||
mock_exec.assert_not_called()
|
||||
|
||||
@mock.patch.object(processutils, "execute")
|
||||
@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(linux_net, "_set_device_mtu")
|
||||
def test_ensure_vlan(self, mock_set_mtu,
|
||||
mock_dev_exists, mock_exec):
|
||||
def test_ensure_vlan(self, mock_set_mtu, mock_dev_exists, mock_ip_set,
|
||||
mock_ip_add):
|
||||
linux_net._ensure_vlan_privileged(123, 'fake-bridge',
|
||||
mac_address='fake-mac',
|
||||
mtu=1500)
|
||||
self.assertTrue(mock_dev_exists.called)
|
||||
calls = [mock.call('ip', 'link', 'add', 'link',
|
||||
'fake-bridge', 'name', 'vlan123', 'type',
|
||||
'vlan', 'id', 123,
|
||||
check_exit_code=[0, 2, 254]),
|
||||
mock.call('ip', 'link', 'set', 'vlan123',
|
||||
'address', 'fake-mac',
|
||||
check_exit_code=[0, 2, 254]),
|
||||
mock.call('ip', 'link', 'set', 'vlan123', 'up',
|
||||
check_exit_code=[0, 2, 254])]
|
||||
mock_exec.assert_has_calls(calls)
|
||||
set_calls = [mock.call('vlan123', address='fake-mac',
|
||||
check_exit_code=[0, 2, 254]),
|
||||
mock.call('vlan123', state='up',
|
||||
check_exit_code=[0, 2, 254])]
|
||||
mock_ip_add.assert_called_once_with(
|
||||
'vlan123', 'vlan', link='fake-bridge', vlan_id=123,
|
||||
check_exit_code=[0, 2, 254])
|
||||
mock_ip_set.assert_has_calls(set_calls)
|
||||
mock_set_mtu.assert_called_once_with('vlan123', 1500)
|
||||
|
||||
@mock.patch.object(processutils, "execute")
|
||||
@ -87,38 +87,43 @@ class LinuxNetTest(testtools.TestCase):
|
||||
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])
|
||||
def test_ensure_bridge_concurrent_add(self, mock_dev_exists, mock_exec):
|
||||
def test_ensure_bridge_concurrent_add(self, mock_dev_exists, mock_exec,
|
||||
mock_ip_set):
|
||||
mock_exec.side_effect = [ValueError(), 0, 0, 0]
|
||||
linux_net.ensure_bridge("br0", None, filtering=False)
|
||||
|
||||
calls = [mock.call('brctl', 'addbr', 'br0'),
|
||||
mock.call('brctl', 'setfd', 'br0', 0),
|
||||
mock.call('brctl', 'stp', 'br0', "off"),
|
||||
mock.call('ip', 'link', 'set', 'br0', "up")]
|
||||
mock.call('brctl', 'stp', 'br0', "off")]
|
||||
mock_exec.assert_has_calls(calls)
|
||||
mock_dev_exists.assert_has_calls([mock.call("br0"), mock.call("br0")])
|
||||
mock_ip_set.assert_called_once_with('br0', state='up')
|
||||
|
||||
@mock.patch.object(ip_lib, "set")
|
||||
@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)
|
||||
def test_ensure_bridge_mtu_not_called(self, mock_dev_exists, mock_exec,
|
||||
mock_path_exists, mock_set_mtu):
|
||||
mock_path_exists, mock_set_mtu, mock_ip_set):
|
||||
"""This test validates that mtus are updated only if an interface
|
||||
is added to the bridge
|
||||
"""
|
||||
linux_net._ensure_bridge_privileged("fake-bridge", None,
|
||||
None, False, mtu=1500)
|
||||
mock_set_mtu.assert_not_called()
|
||||
mock_ip_set.assert_called_once_with('fake-bridge', state='up')
|
||||
|
||||
@mock.patch.object(ip_lib, "set")
|
||||
@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)
|
||||
def test_ensure_bridge_mtu_order(self, mock_dev_exists, mock_exec,
|
||||
mock_path_exists, mock_set_mtu):
|
||||
mock_path_exists, mock_set_mtu, mock_ip_set):
|
||||
"""This test validates that when adding an interface
|
||||
to a bridge, the interface mtu is updated first
|
||||
followed by the bridge. This is required to work around
|
||||
@ -129,33 +134,38 @@ class LinuxNetTest(testtools.TestCase):
|
||||
calls = [mock.call('fake-interface', 1500),
|
||||
mock.call('fake-bridge', 1500)]
|
||||
mock_set_mtu.assert_has_calls(calls)
|
||||
calls = [mock.call('fake-bridge', state = 'up'),
|
||||
mock.call('fake-interface', state='up')]
|
||||
mock_ip_set.assert_has_calls(calls)
|
||||
|
||||
@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)
|
||||
def test_ensure_bridge_new_ipv4(self, mock_dev_exists, mock_exec,
|
||||
mock_path_exists):
|
||||
mock_path_exists, mock_ip_set):
|
||||
linux_net.ensure_bridge("br0", None, filtering=False)
|
||||
|
||||
calls = [mock.call('brctl', 'addbr', 'br0'),
|
||||
mock.call('brctl', 'setfd', 'br0', 0),
|
||||
mock.call('brctl', 'stp', 'br0', "off"),
|
||||
mock.call('ip', 'link', 'set', 'br0', "up")]
|
||||
mock.call('brctl', 'stp', 'br0', "off")]
|
||||
mock_exec.assert_has_calls(calls)
|
||||
mock_dev_exists.assert_called_once_with("br0")
|
||||
mock_ip_set.assert_called_once_with('br0', state='up')
|
||||
|
||||
@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)
|
||||
def test_ensure_bridge_new_ipv6(self, mock_dev_exists, mock_exec,
|
||||
mock_path_exists):
|
||||
mock_path_exists, mock_ip_set):
|
||||
linux_net.ensure_bridge("br0", None, filtering=False)
|
||||
|
||||
calls = [mock.call('brctl', 'addbr', 'br0'),
|
||||
mock.call('brctl', 'setfd', 'br0', 0),
|
||||
mock.call('brctl', 'stp', 'br0', "off"),
|
||||
mock.call('tee', '/proc/sys/net/ipv6/conf/br0/disable_ipv6',
|
||||
check_exit_code=[0, 1], process_input='1'),
|
||||
mock.call('ip', 'link', 'set', 'br0', "up")]
|
||||
check_exit_code=[0, 1], process_input='1')]
|
||||
mock_exec.assert_has_calls(calls)
|
||||
mock_dev_exists.assert_called_once_with("br0")
|
||||
mock_ip_set.assert_called_once_with('br0', state='up')
|
||||
|
@ -24,6 +24,7 @@ import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from os_vif.internal.command import ip as ip_lib
|
||||
from oslo_concurrency import processutils
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
@ -117,8 +118,7 @@ def _delete_net_dev(dev):
|
||||
"""Delete a network device only if it exists."""
|
||||
if device_exists(dev):
|
||||
try:
|
||||
processutils.execute('ip', 'link', 'delete', dev,
|
||||
check_exit_code=[0, 2, 254])
|
||||
ip_lib.delete(dev, check_exit_code=[0, 2, 254])
|
||||
LOG.debug("Net device removed: '%s'", dev)
|
||||
except processutils.ProcessExecutionError:
|
||||
with excutils.save_and_reraise_exception():
|
||||
@ -133,11 +133,10 @@ def create_veth_pair(dev1_name, dev2_name, mtu):
|
||||
for dev in [dev1_name, dev2_name]:
|
||||
_delete_net_dev(dev)
|
||||
|
||||
processutils.execute('ip', 'link', 'add', dev1_name,
|
||||
'type', 'veth', 'peer', 'name', dev2_name)
|
||||
ip_lib.add(dev1_name, 'veth', peer=dev2_name)
|
||||
for dev in [dev1_name, dev2_name]:
|
||||
processutils.execute('ip', 'link', 'set', dev, 'up')
|
||||
processutils.execute('ip', 'link', 'set', dev, 'promisc', 'on')
|
||||
ip_lib.set(dev, state='up')
|
||||
ip_lib.set(dev, promisc='on')
|
||||
_update_device_mtu(dev, mtu)
|
||||
|
||||
|
||||
@ -180,7 +179,8 @@ def delete_bridge(bridge, dev):
|
||||
if device_exists(bridge):
|
||||
if interface_in_bridge(bridge, dev):
|
||||
processutils.execute('brctl', 'delif', bridge, dev)
|
||||
processutils.execute('ip', 'link', 'set', bridge, 'down')
|
||||
|
||||
ip_lib.set(bridge, state='down')
|
||||
processutils.execute('brctl', 'delbr', bridge)
|
||||
|
||||
|
||||
@ -213,14 +213,12 @@ def _update_device_mtu(dev, mtu, interface_type=None, timeout=120):
|
||||
@privsep.vif_plug.entrypoint
|
||||
def _set_device_mtu(dev, mtu):
|
||||
"""Set the device MTU."""
|
||||
processutils.execute('ip', 'link', 'set', dev, 'mtu', mtu,
|
||||
check_exit_code=[0, 2, 254])
|
||||
ip_lib.set(dev, mtu=mtu, check_exit_code=[0, 2, 254])
|
||||
|
||||
|
||||
@privsep.vif_plug.entrypoint
|
||||
def set_interface_state(interface_name, port_state):
|
||||
processutils.execute('ip', 'link', 'set', interface_name, port_state,
|
||||
check_exit_code=[0, 2, 254])
|
||||
ip_lib.set(interface_name, state=port_state, check_exit_code=[0, 2, 254])
|
||||
|
||||
|
||||
@privsep.vif_plug.entrypoint
|
||||
|
@ -15,6 +15,7 @@ import mock
|
||||
import os.path
|
||||
import testtools
|
||||
|
||||
from os_vif.internal.command import ip as ip_lib
|
||||
from oslo_concurrency import processutils
|
||||
|
||||
from vif_plug_ovs import constants
|
||||
@ -30,21 +31,21 @@ class LinuxNetTest(testtools.TestCase):
|
||||
|
||||
privsep.vif_plug.set_client_mode(False)
|
||||
|
||||
@mock.patch.object(processutils, "execute")
|
||||
@mock.patch.object(ip_lib, "set")
|
||||
@mock.patch.object(linux_net, "device_exists", return_value=True)
|
||||
def test_ensure_bridge_exists(self, mock_dev_exists, mock_execute):
|
||||
def test_ensure_bridge_exists(self, mock_dev_exists, mock_ip_set):
|
||||
linux_net.ensure_bridge("br0")
|
||||
|
||||
mock_execute.assert_has_calls([
|
||||
mock.call('ip', 'link', 'set', 'br0', 'up',
|
||||
check_exit_code=[0, 2, 254])])
|
||||
mock_ip_set.assert_called_once_with('br0', state='up',
|
||||
check_exit_code=[0, 2, 254])
|
||||
mock_dev_exists.assert_has_calls([mock.call("br0")])
|
||||
|
||||
@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)
|
||||
def test_ensure_bridge_new_ipv4(self, mock_dev_exists, mock_execute,
|
||||
mock_path_exists):
|
||||
mock_path_exists, mock_ip_set):
|
||||
linux_net.ensure_bridge("br0")
|
||||
|
||||
calls = [
|
||||
@ -54,17 +55,18 @@ class LinuxNetTest(testtools.TestCase):
|
||||
mock.call('brctl', 'setageing', 'br0', 0),
|
||||
mock.call('tee', '/sys/class/net/br0/bridge/multicast_snooping',
|
||||
check_exit_code=[0, 1], process_input='0'),
|
||||
mock.call('ip', 'link', 'set', 'br0', 'up',
|
||||
check_exit_code=[0, 2, 254])
|
||||
]
|
||||
mock_execute.assert_has_calls(calls)
|
||||
mock_dev_exists.assert_has_calls([mock.call("br0")])
|
||||
mock_ip_set.assert_called_once_with('br0', state='up',
|
||||
check_exit_code=[0, 2, 254])
|
||||
|
||||
@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)
|
||||
def test_ensure_bridge_new_ipv6(self, mock_dev_exists, mock_execute,
|
||||
mock_path_exists):
|
||||
mock_path_exists, mock_ip_set):
|
||||
linux_net.ensure_bridge("br0")
|
||||
|
||||
calls = [
|
||||
@ -76,11 +78,11 @@ class LinuxNetTest(testtools.TestCase):
|
||||
check_exit_code=[0, 1], process_input='0'),
|
||||
mock.call('tee', '/proc/sys/net/ipv6/conf/br0/disable_ipv6',
|
||||
check_exit_code=[0, 1], process_input='1'),
|
||||
mock.call('ip', 'link', 'set', 'br0', 'up',
|
||||
check_exit_code=[0, 2, 254])
|
||||
]
|
||||
mock_execute.assert_has_calls(calls)
|
||||
mock_dev_exists.assert_has_calls([mock.call("br0")])
|
||||
mock_ip_set.assert_called_once_with('br0', state='up',
|
||||
check_exit_code=[0, 2, 254])
|
||||
|
||||
@mock.patch.object(processutils, "execute")
|
||||
@mock.patch.object(linux_net, "device_exists", return_value=False)
|
||||
@ -93,34 +95,34 @@ class LinuxNetTest(testtools.TestCase):
|
||||
mock_dev_exists.assert_has_calls([mock.call("br0")])
|
||||
mock_interface_br.assert_not_called()
|
||||
|
||||
@mock.patch.object(ip_lib, "set")
|
||||
@mock.patch.object(processutils, "execute")
|
||||
@mock.patch.object(linux_net, "device_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_execute, mock_ip_set):
|
||||
linux_net.delete_bridge("br0", "vnet1")
|
||||
|
||||
calls = [
|
||||
mock.call('brctl', 'delif', 'br0', 'vnet1'),
|
||||
mock.call('ip', 'link', 'set', 'br0', 'down'),
|
||||
mock.call('brctl', 'delbr', 'br0')]
|
||||
mock_execute.assert_has_calls(calls)
|
||||
mock_dev_exists.assert_has_calls([mock.call("br0")])
|
||||
mock_interface_br.assert_called_once_with("br0", "vnet1")
|
||||
mock_ip_set.assert_called_once_with('br0', state='down')
|
||||
|
||||
@mock.patch.object(ip_lib, "set")
|
||||
@mock.patch.object(processutils, "execute")
|
||||
@mock.patch.object(linux_net, "device_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):
|
||||
def test_delete_interface_not_present(self,
|
||||
mock_interface_br, mock_dev_exists, mock_execute, mock_ip_set):
|
||||
linux_net.delete_bridge("br0", "vnet1")
|
||||
|
||||
calls = [
|
||||
mock.call('ip', 'link', 'set', 'br0', 'down'),
|
||||
mock.call('brctl', 'delbr', 'br0')]
|
||||
mock_execute.assert_has_calls(calls)
|
||||
mock_execute.assert_called_once_with('brctl', 'delbr', 'br0')
|
||||
mock_dev_exists.assert_has_calls([mock.call("br0")])
|
||||
mock_interface_br.assert_called_once_with("br0", "vnet1")
|
||||
mock_ip_set.assert_called_once_with('br0', state='down')
|
||||
|
||||
@mock.patch.object(processutils, "execute")
|
||||
def test_add_bridge_port(self, mock_execute):
|
||||
|
Loading…
x
Reference in New Issue
Block a user