import openvswitch plugin implementation
While most of the vendor plugins will be in separate repositories, the os-vif library will include the openvswitch plugin as one of the reference implementations. Change-Id: Icd9e452a4f0726823c0b1e07a29bb53a05fd1651
This commit is contained in:
parent
5888af0815
commit
f874a4652c
@ -26,6 +26,7 @@ setup-hooks =
|
|||||||
packages =
|
packages =
|
||||||
os_vif
|
os_vif
|
||||||
vif_plug_linux_bridge
|
vif_plug_linux_bridge
|
||||||
|
vif_plug_ovs
|
||||||
|
|
||||||
[egg_info]
|
[egg_info]
|
||||||
tag_build =
|
tag_build =
|
||||||
@ -57,3 +58,5 @@ output_file = os_vif/locale/os-vif.pot
|
|||||||
[entry_points]
|
[entry_points]
|
||||||
os_vif =
|
os_vif =
|
||||||
linux_bridge = vif_plug_linux_bridge.linux_bridge:LinuxBridgePlugin
|
linux_bridge = vif_plug_linux_bridge.linux_bridge:LinuxBridgePlugin
|
||||||
|
ovs = vif_plug_ovs.ovs:OvsBridgePlugin
|
||||||
|
ovs_hybrid = vif_plug_ovs.ovs_hybrid:OvsHybridPlugin
|
||||||
|
0
vif_plug_ovs/__init__.py
Normal file
0
vif_plug_ovs/__init__.py
Normal file
28
vif_plug_ovs/exception.py
Normal file
28
vif_plug_ovs/exception.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# 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.i18n import _
|
||||||
|
|
||||||
|
from os_vif import exception as osv_exception
|
||||||
|
|
||||||
|
|
||||||
|
class AgentError(osv_exception.ExceptionBase):
|
||||||
|
msg_fmt = _('Error during following call to agent: %(method)s')
|
||||||
|
|
||||||
|
|
||||||
|
class MissingPortProfile(osv_exception.ExceptionBase):
|
||||||
|
msg_fmt = _('A port profile is mandatory for the OpenVSwitch plugin')
|
||||||
|
|
||||||
|
|
||||||
|
class WrongPortProfile(osv_exception.ExceptionBase):
|
||||||
|
msg_fmt = _('Port profile %(profile)s is not a subclass '
|
||||||
|
'of VIFPortProfileOpenVSwitch')
|
49
vif_plug_ovs/i18n.py
Normal file
49
vif_plug_ovs/i18n.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# Copyright 2014 IBM Corp.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""oslo.i18n integration module.
|
||||||
|
|
||||||
|
See http://docs.openstack.org/developer/oslo.i18n/usage.html .
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import oslo_i18n
|
||||||
|
|
||||||
|
# Normally this would be the plugin specific name
|
||||||
|
# eg 'vif_plug_ovs', but since the OVS plugin is
|
||||||
|
# in-tree, this is a special case
|
||||||
|
DOMAIN = 'os_vif'
|
||||||
|
|
||||||
|
_translators = oslo_i18n.TranslatorFactory(domain=DOMAIN)
|
||||||
|
|
||||||
|
# The primary translation function using the well-known name "_"
|
||||||
|
_ = _translators.primary
|
||||||
|
|
||||||
|
# Translators for log levels.
|
||||||
|
#
|
||||||
|
# The abbreviated names are meant to reflect the usual use of a short
|
||||||
|
# name like '_'. The "L" is for "log" and the other letter comes from
|
||||||
|
# the level.
|
||||||
|
_LI = _translators.log_info
|
||||||
|
_LW = _translators.log_warning
|
||||||
|
_LE = _translators.log_error
|
||||||
|
_LC = _translators.log_critical
|
||||||
|
|
||||||
|
|
||||||
|
def translate(value, user_locale):
|
||||||
|
return oslo_i18n.translate(value, user_locale)
|
||||||
|
|
||||||
|
|
||||||
|
def get_available_languages():
|
||||||
|
return oslo_i18n.get_available_languages(DOMAIN)
|
104
vif_plug_ovs/linux_net.py
Normal file
104
vif_plug_ovs/linux_net.py
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
# Derived from nova/network/linux_net.py
|
||||||
|
#
|
||||||
|
# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
|
||||||
|
# Copyright 2010 United States Government as represented by the
|
||||||
|
# Administrator of the National Aeronautics and Space Administration.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Implements vlans, bridges using linux utilities."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from oslo_concurrency import processutils
|
||||||
|
from oslo_log import log as logging
|
||||||
|
from oslo_utils import excutils
|
||||||
|
|
||||||
|
from vif_plug_ovs import exception
|
||||||
|
from vif_plug_ovs.i18n import _LE
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _ovs_vsctl(args, timeout=None):
|
||||||
|
full_args = ['ovs-vsctl']
|
||||||
|
if timeout is not None:
|
||||||
|
full_args += ['--timeout=%s' % timeout]
|
||||||
|
full_args += args
|
||||||
|
try:
|
||||||
|
return processutils.execute(*full_args, run_as_root=True)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(_LE("Unable to execute %(cmd)s. Exception: %(exception)s"),
|
||||||
|
{'cmd': full_args, 'exception': e})
|
||||||
|
raise exception.AgentError(method=full_args)
|
||||||
|
|
||||||
|
|
||||||
|
def create_ovs_vif_port(bridge, dev, iface_id, mac, instance_id, mtu,
|
||||||
|
timeout=None):
|
||||||
|
_ovs_vsctl(['--', '--if-exists', 'del-port', dev, '--',
|
||||||
|
'add-port', bridge, dev,
|
||||||
|
'--', 'set', 'Interface', dev,
|
||||||
|
'external-ids:iface-id=%s' % iface_id,
|
||||||
|
'external-ids:iface-status=active',
|
||||||
|
'external-ids:attached-mac=%s' % mac,
|
||||||
|
'external-ids:vm-uuid=%s' % instance_id],
|
||||||
|
timeout=timeout)
|
||||||
|
_set_device_mtu(dev, mtu)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_ovs_vif_port(bridge, dev, timeout=None):
|
||||||
|
_ovs_vsctl(['--', '--if-exists', 'del-port', bridge, dev],
|
||||||
|
timeout=timeout)
|
||||||
|
delete_net_dev(dev)
|
||||||
|
|
||||||
|
|
||||||
|
def device_exists(device):
|
||||||
|
"""Check if ethernet device exists."""
|
||||||
|
return os.path.exists('/sys/class/net/%s' % device)
|
||||||
|
|
||||||
|
|
||||||
|
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],
|
||||||
|
run_as_root=True)
|
||||||
|
LOG.debug("Net device removed: '%s'", dev)
|
||||||
|
except processutils.ProcessExecutionError:
|
||||||
|
with excutils.save_and_reraise_exception():
|
||||||
|
LOG.error(_LE("Failed removing net device: '%s'"), dev)
|
||||||
|
|
||||||
|
|
||||||
|
def create_veth_pair(dev1_name, dev2_name, mtu):
|
||||||
|
"""Create a pair of veth devices with the specified names,
|
||||||
|
deleting any previous devices with those names.
|
||||||
|
"""
|
||||||
|
for dev in [dev1_name, dev2_name]:
|
||||||
|
delete_net_dev(dev)
|
||||||
|
|
||||||
|
processutils.execute('ip', 'link', 'add', dev1_name,
|
||||||
|
'type', 'veth', 'peer', 'name', dev2_name,
|
||||||
|
run_as_root=True)
|
||||||
|
for dev in [dev1_name, dev2_name]:
|
||||||
|
processutils.execute('ip', 'link', 'set', dev, 'up',
|
||||||
|
run_as_root=True)
|
||||||
|
processutils.execute('ip', 'link', 'set', dev, 'promisc', 'on')
|
||||||
|
_set_device_mtu(dev, mtu)
|
||||||
|
|
||||||
|
|
||||||
|
def _set_device_mtu(dev, mtu):
|
||||||
|
"""Set the device MTU."""
|
||||||
|
processutils.execute('ip', 'link', 'set', dev, 'mtu', mtu,
|
||||||
|
check_exit_code=[0, 2, 254])
|
41
vif_plug_ovs/ovs.py
Normal file
41
vif_plug_ovs/ovs.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# Derived from nova/virt/libvirt/vif.py
|
||||||
|
#
|
||||||
|
# Copyright (C) 2011 Midokura KK
|
||||||
|
# Copyright (C) 2011 Nicira, Inc
|
||||||
|
# Copyright 2011 OpenStack Foundation
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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 import objects
|
||||||
|
from os_vif import plugin
|
||||||
|
|
||||||
|
|
||||||
|
class OvsBridgePlugin(plugin.PluginBase):
|
||||||
|
"""An OVS VIF type that uses a standard Linux bridge for integration."""
|
||||||
|
|
||||||
|
def describe(self):
|
||||||
|
return plugin.PluginInfo(
|
||||||
|
[
|
||||||
|
plugin.PluginVIFInfo(
|
||||||
|
objects.vif.VIFOpenVSwitch,
|
||||||
|
"1.0", "1.0")
|
||||||
|
])
|
||||||
|
|
||||||
|
def plug(self, vif, instance_info):
|
||||||
|
# Nothing required to plug an OVS port...
|
||||||
|
pass
|
||||||
|
|
||||||
|
def unplug(self, vif, instance_info):
|
||||||
|
# Nothing required to unplug an OVS port...
|
||||||
|
pass
|
135
vif_plug_ovs/ovs_hybrid.py
Normal file
135
vif_plug_ovs/ovs_hybrid.py
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
# Derived from nova/virt/libvirt/vif.py
|
||||||
|
#
|
||||||
|
# Copyright (C) 2011 Midokura KK
|
||||||
|
# Copyright (C) 2011 Nicira, Inc
|
||||||
|
# Copyright 2011 OpenStack Foundation
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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 import objects
|
||||||
|
from os_vif import plugin
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from oslo_concurrency import processutils
|
||||||
|
|
||||||
|
from vif_plug_ovs import exception
|
||||||
|
from vif_plug_ovs import linux_net
|
||||||
|
|
||||||
|
|
||||||
|
class OvsHybridPlugin(plugin.PluginBase):
|
||||||
|
"""
|
||||||
|
An OVS VIF type that uses a pair of devices in order to allow
|
||||||
|
security group rules to be applied to traffic coming in or out of
|
||||||
|
a virtual machine.
|
||||||
|
"""
|
||||||
|
|
||||||
|
NIC_NAME_LEN = 14
|
||||||
|
|
||||||
|
CONFIG_OPTS = (
|
||||||
|
cfg.IntOpt('network_device_mtu',
|
||||||
|
default=1500,
|
||||||
|
help='MTU setting for network interface.',
|
||||||
|
deprecated_group="DEFAULT"),
|
||||||
|
cfg.IntOpt('ovs_vsctl_timeout',
|
||||||
|
default=120,
|
||||||
|
help='Amount of time, in seconds, that ovs_vsctl should '
|
||||||
|
'wait for a response from the database. 0 is to wait '
|
||||||
|
'forever.',
|
||||||
|
deprecated_group="DEFAULT"),
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_veth_pair_names(vif):
|
||||||
|
iface_id = vif.id
|
||||||
|
return (("qvb%s" % iface_id)[:OvsHybridPlugin.NIC_NAME_LEN],
|
||||||
|
("qvo%s" % iface_id)[:OvsHybridPlugin.NIC_NAME_LEN])
|
||||||
|
|
||||||
|
def describe(self):
|
||||||
|
return plugin.PluginInfo(
|
||||||
|
[
|
||||||
|
plugin.PluginVIFInfo(
|
||||||
|
objects.vif.VIFBridge,
|
||||||
|
"1.0", "1.0")
|
||||||
|
])
|
||||||
|
|
||||||
|
def plug(self, vif, instance_info):
|
||||||
|
"""Plug using hybrid strategy
|
||||||
|
|
||||||
|
Create a per-VIF linux bridge, then link that bridge to the OVS
|
||||||
|
integration bridge via a veth device, setting up the other end
|
||||||
|
of the veth device just like a normal OVS port. Then boot the
|
||||||
|
VIF on the linux bridge using standard libvirt mechanisms.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not hasattr(vif, "port_profile"):
|
||||||
|
raise exception.MissingPortProfile()
|
||||||
|
if not isinstance(vif.port_profile,
|
||||||
|
objects.vif.VIFPortProfileOpenVSwitch):
|
||||||
|
raise exception.WrongPortProfile(
|
||||||
|
profile=vif.port_profile.__class__.__name__)
|
||||||
|
|
||||||
|
v1_name, v2_name = self.get_veth_pair_names(vif)
|
||||||
|
|
||||||
|
if not linux_net.device_exists(vif.bridge_name):
|
||||||
|
processutils.execute('brctl', 'addbr', vif.bridge_name,
|
||||||
|
run_as_root=True)
|
||||||
|
processutils.execute('brctl', 'setfd', vif.bridge_name, 0,
|
||||||
|
run_as_root=True)
|
||||||
|
processutils.execute('brctl', 'stp', vif.bridge_name, 'off',
|
||||||
|
run_as_root=True)
|
||||||
|
syspath = '/sys/class/net/%s/bridge/multicast_snooping'
|
||||||
|
syspath = syspath % vif.bridge_name
|
||||||
|
processutils.execute('tee', syspath, process_input='0',
|
||||||
|
check_exit_code=[0, 1],
|
||||||
|
run_as_root=True)
|
||||||
|
|
||||||
|
if not linux_net.device_exists(v2_name):
|
||||||
|
linux_net.create_veth_pair(v1_name, v2_name,
|
||||||
|
self.config.network_device_mtu)
|
||||||
|
processutils.execute('ip', 'link', 'set', vif.bridge_name, 'up',
|
||||||
|
run_as_root=True)
|
||||||
|
processutils.execute('brctl', 'addif', vif.bridge_name, v1_name,
|
||||||
|
run_as_root=True)
|
||||||
|
linux_net.create_ovs_vif_port(
|
||||||
|
vif.network.bridge,
|
||||||
|
v2_name,
|
||||||
|
vif.port_profile.interface_id,
|
||||||
|
vif.address, instance_info.uuid,
|
||||||
|
timeout=self.config.ovs_vsctl_timeout)
|
||||||
|
|
||||||
|
def unplug(self, vif, instance_info):
|
||||||
|
"""UnPlug using hybrid strategy
|
||||||
|
|
||||||
|
Unhook port from OVS, unhook port from bridge, delete
|
||||||
|
bridge, and delete both veth devices.
|
||||||
|
"""
|
||||||
|
if not hasattr(vif, "port_profile"):
|
||||||
|
raise exception.MissingPortProfile()
|
||||||
|
if not isinstance(vif.port_profile,
|
||||||
|
objects.vif.VIFPortProfileOpenVSwitch):
|
||||||
|
raise exception.WrongPortProfile(
|
||||||
|
profile=vif.port_profile.__class__.__name__)
|
||||||
|
|
||||||
|
v1_name, v2_name = self.get_veth_pair_names(vif)
|
||||||
|
|
||||||
|
if linux_net.device_exists(vif.bridge_name):
|
||||||
|
processutils.execute('brctl', 'delif', vif.bridge_name, v1_name,
|
||||||
|
run_as_root=True)
|
||||||
|
processutils.execute('ip', 'link', 'set', vif.bridge_name, 'down',
|
||||||
|
run_as_root=True)
|
||||||
|
processutils.execute('brctl', 'delbr', vif.bridge_name,
|
||||||
|
run_as_root=True)
|
||||||
|
|
||||||
|
linux_net.delete_ovs_vif_port(vif.network.bridge, v2_name,
|
||||||
|
timeout=self.config.ovs_vsctl_timeout)
|
0
vif_plug_ovs/tests/__init__.py
Normal file
0
vif_plug_ovs/tests/__init__.py
Normal file
155
vif_plug_ovs/tests/test_plugin.py
Normal file
155
vif_plug_ovs/tests/test_plugin.py
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
# 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 contextlib
|
||||||
|
import mock
|
||||||
|
import six
|
||||||
|
import testtools
|
||||||
|
|
||||||
|
from os_vif import objects
|
||||||
|
|
||||||
|
from oslo_concurrency import processutils
|
||||||
|
|
||||||
|
from vif_plug_ovs import linux_net
|
||||||
|
from vif_plug_ovs import ovs_hybrid
|
||||||
|
|
||||||
|
|
||||||
|
if six.PY2:
|
||||||
|
nested = contextlib.nested
|
||||||
|
else:
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def nested(*contexts):
|
||||||
|
with contextlib.ExitStack() as stack:
|
||||||
|
yield [stack.enter_context(c) for c in contexts]
|
||||||
|
|
||||||
|
|
||||||
|
class PluginTest(testtools.TestCase):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(PluginTest, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
objects.register_all()
|
||||||
|
|
||||||
|
self.subnet_bridge_4 = objects.subnet.Subnet(
|
||||||
|
cidr='101.168.1.0/24',
|
||||||
|
dns=['8.8.8.8'],
|
||||||
|
gateway='101.168.1.1',
|
||||||
|
dhcp_server='191.168.1.1')
|
||||||
|
|
||||||
|
self.subnet_bridge_6 = objects.subnet.Subnet(
|
||||||
|
cidr='101:1db9::/64',
|
||||||
|
gateway='101:1db9::1')
|
||||||
|
|
||||||
|
self.subnets = objects.subnet.SubnetList(
|
||||||
|
objects=[self.subnet_bridge_4,
|
||||||
|
self.subnet_bridge_6])
|
||||||
|
|
||||||
|
self.network_ovs = objects.network.Network(
|
||||||
|
id='network-id-xxx-yyy-zzz',
|
||||||
|
bridge='br0',
|
||||||
|
subnets=self.subnets,
|
||||||
|
vlan=99)
|
||||||
|
|
||||||
|
self.profile_ovs = objects.vif.VIFPortProfileOpenVSwitch(
|
||||||
|
interface_id='aaa-bbb-ccc')
|
||||||
|
self.vif_ovs = objects.vif.VIFBridge(
|
||||||
|
id='vif-xxx-yyy-zzz',
|
||||||
|
address='ca:fe:de:ad:be:ef',
|
||||||
|
network=self.network_ovs,
|
||||||
|
dev_name='tap-xxx-yyy-zzz',
|
||||||
|
bridge_name="qbrvif-xxx-yyy",
|
||||||
|
port_profile=self.profile_ovs)
|
||||||
|
|
||||||
|
self.instance = objects.instance_info.InstanceInfo(
|
||||||
|
name='demo',
|
||||||
|
uuid='f0000000-0000-0000-0000-000000000001')
|
||||||
|
|
||||||
|
def test_plug_ovs_hybrid(self):
|
||||||
|
calls = {
|
||||||
|
'device_exists': [mock.call('qbrvif-xxx-yyy'),
|
||||||
|
mock.call('qvovif-xxx-yyy')],
|
||||||
|
'_create_veth_pair': [mock.call('qvbvif-xxx-yyy',
|
||||||
|
'qvovif-xxx-yyy',
|
||||||
|
1500)],
|
||||||
|
'execute': [mock.call('brctl', 'addbr', 'qbrvif-xxx-yyy',
|
||||||
|
run_as_root=True),
|
||||||
|
mock.call('brctl', 'setfd', 'qbrvif-xxx-yyy', 0,
|
||||||
|
run_as_root=True),
|
||||||
|
mock.call('brctl', 'stp', 'qbrvif-xxx-yyy', 'off',
|
||||||
|
run_as_root=True),
|
||||||
|
mock.call('tee', ('/sys/class/net/qbrvif-xxx-yyy'
|
||||||
|
'/bridge/multicast_snooping'),
|
||||||
|
process_input='0', run_as_root=True,
|
||||||
|
check_exit_code=[0, 1]),
|
||||||
|
mock.call('ip', 'link', 'set', 'qbrvif-xxx-yyy', 'up',
|
||||||
|
run_as_root=True),
|
||||||
|
mock.call('brctl', 'addif', 'qbrvif-xxx-yyy',
|
||||||
|
'qvbvif-xxx-yyy', run_as_root=True)],
|
||||||
|
'create_ovs_vif_port': [mock.call(
|
||||||
|
'br0', 'qvovif-xxx-yyy', 'aaa-bbb-ccc',
|
||||||
|
'ca:fe:de:ad:be:ef',
|
||||||
|
'f0000000-0000-0000-0000-000000000001',
|
||||||
|
timeout=120)]
|
||||||
|
}
|
||||||
|
with nested(
|
||||||
|
mock.patch.object(linux_net, 'device_exists',
|
||||||
|
return_value=False),
|
||||||
|
mock.patch.object(processutils, 'execute'),
|
||||||
|
mock.patch.object(linux_net, 'create_veth_pair'),
|
||||||
|
mock.patch.object(linux_net, 'create_ovs_vif_port')
|
||||||
|
) as (device_exists, execute, _create_veth_pair, create_ovs_vif_port):
|
||||||
|
plugin = ovs_hybrid.OvsHybridPlugin.load("ovs_hybrid")
|
||||||
|
plugin.plug(self.vif_ovs, self.instance)
|
||||||
|
device_exists.assert_has_calls(calls['device_exists'])
|
||||||
|
_create_veth_pair.assert_has_calls(calls['_create_veth_pair'])
|
||||||
|
execute.assert_has_calls(calls['execute'])
|
||||||
|
create_ovs_vif_port.assert_has_calls(calls['create_ovs_vif_port'])
|
||||||
|
|
||||||
|
def test_unplug_ovs_hybrid(self):
|
||||||
|
calls = {
|
||||||
|
'device_exists': [mock.call('qbrvif-xxx-yyy')],
|
||||||
|
'execute': [mock.call('brctl', 'delif', 'qbrvif-xxx-yyy',
|
||||||
|
'qvbvif-xxx-yyy', run_as_root=True),
|
||||||
|
mock.call('ip', 'link', 'set',
|
||||||
|
'qbrvif-xxx-yyy', 'down', run_as_root=True),
|
||||||
|
mock.call('brctl', 'delbr',
|
||||||
|
'qbrvif-xxx-yyy', run_as_root=True)],
|
||||||
|
'delete_ovs_vif_port': [mock.call('br0', 'qvovif-xxx-yyy',
|
||||||
|
timeout=120)]
|
||||||
|
}
|
||||||
|
with nested(
|
||||||
|
mock.patch.object(linux_net, 'device_exists',
|
||||||
|
return_value=True),
|
||||||
|
mock.patch.object(processutils, 'execute'),
|
||||||
|
mock.patch.object(linux_net, 'delete_ovs_vif_port')
|
||||||
|
) as (device_exists, execute, delete_ovs_vif_port):
|
||||||
|
plugin = ovs_hybrid.OvsHybridPlugin.load("ovs_hybrid")
|
||||||
|
plugin.unplug(self.vif_ovs, self.instance)
|
||||||
|
device_exists.assert_has_calls(calls['device_exists'])
|
||||||
|
execute.assert_has_calls(calls['execute'])
|
||||||
|
delete_ovs_vif_port.assert_has_calls(calls['delete_ovs_vif_port'])
|
||||||
|
|
||||||
|
def test_unplug_ovs_hybrid_bridge_does_not_exist(self):
|
||||||
|
calls = {
|
||||||
|
'device_exists': [mock.call('qbrvif-xxx-yyy')],
|
||||||
|
'delete_ovs_vif_port': [mock.call('br0', 'qvovif-xxx-yyy',
|
||||||
|
timeout=120)]
|
||||||
|
}
|
||||||
|
with nested(
|
||||||
|
mock.patch.object(linux_net, 'device_exists',
|
||||||
|
return_value=False),
|
||||||
|
mock.patch.object(linux_net, 'delete_ovs_vif_port')
|
||||||
|
) as (device_exists, delete_ovs_vif_port):
|
||||||
|
plugin = ovs_hybrid.OvsHybridPlugin.load("ovs_hybrid")
|
||||||
|
plugin.unplug(self.vif_ovs, self.instance)
|
||||||
|
device_exists.assert_has_calls(calls['device_exists'])
|
||||||
|
delete_ovs_vif_port.assert_has_calls(calls['delete_ovs_vif_port'])
|
Loading…
x
Reference in New Issue
Block a user