add configurable per port bridges

This patch add a new configuration option to use
per port bridge when hybrid_plug is false.
This can be used with OVN to reduce packet loss
during a live migration.

OVN can only install openflow rules when a port both has
external_ids set and an ofport-id assigned.
Since the ofport-id is only assigned when a netdev matching
the port name exists connected to the dataplane, OVN cannot
install the flows until libvirt create the tap on the destination
host during a live migration.

On loaded systems this can result in multiple seconds of packet loss.
To address this we introduce per port bridges which are connencted
to the integration brige by a patch port pair. Since the patch port
will exist on the dataplane during pre live migration OVN can install
the flows on the integration bridge before we begin the migration reducing
or avoiding packet loss.

Change-Id: I0d55ccbef5b585330b5512e67e442b80304a2e73
Depends-On: https://review.opendev.org/c/openstack/nova/+/797428
Closes-Bug: #1933517
This commit is contained in:
Sean Mooney 2021-06-25 07:50:26 +00:00
parent b75af3ae13
commit b837c1a74f
6 changed files with 309 additions and 46 deletions

View File

@ -112,6 +112,12 @@
parent: os-vif-tempest-base
description: |
os-vif ovn job (tests hybrid-plug=false)
vars:
devstack_local_conf:
post-config:
$NOVA_CONF:
os_vif_ovs:
per_port_bridge: true
- job:
name: os-vif-linuxbridge

View File

@ -0,0 +1,25 @@
---
fixes:
- |
The os-vif OVS plugin now supports using per-port OVS bridges when hybrid plug
is not used. This is disabled by default and can be enabled by defining
``[os_vif_ovs]/per_port_bridge=True`` in the compute service nova.conf.
This capability should only be enabled if you are deploying with ml2/ovn
and experience packet loss during live migrations. This is not supported
on windows or when using ironic smartnic ports. This option was introduced
to address bug: #1933517. When using OVN as a network backend OVN
requires the OVS interface to both have an ofport-id and the neutron port
uuid defined in the external_ids field. When the port is plugged if
``[os_vif_ovs]/per_port_bridge`` is not enabled then the OVS port will not
be assigned an openflow port id until the tap device is created on the host.
On loaded system with many flows and ports it can take a few second for OVN
to detect the creation of the tap device and install the correct flows.
During that interval packets can be dropped.
When ``[os_vif_ovs]/per_port_bridge`` is enabled, os-vif will add the VM tap
device to a new bridge that is connected to the integration bridge via a
patch port. This enables OVN to install the openflow rules on the
integration bridge before the tap is created reducing the possibility for
packet loss during a live migration. By default per port bridges are disabled
and this feature is considered experimental, however it will likely be enabled
by default in the future after we gain experience with how this bridge topology
scales in larger deployments.

View File

@ -19,10 +19,14 @@
import sys
from oslo_config import cfg
from oslo_log import log as logging
from os_vif import exception as osv_exception
from os_vif.internal.ip.api import ip as ip_lib
from os_vif import objects
from os_vif import plugin
from oslo_config import cfg
from vif_plug_ovs import constants
from vif_plug_ovs import exception
@ -30,6 +34,8 @@ from vif_plug_ovs import linux_net
from vif_plug_ovs.ovsdb import api as ovsdb_api
from vif_plug_ovs.ovsdb import ovsdb_lib
LOG = logging.getLogger(__name__)
class OvsPlugin(plugin.PluginBase):
"""An OVS plugin that can setup VIFs in many ways
@ -90,7 +96,13 @@ class OvsPlugin(plugin.PluginBase):
cfg.BoolOpt('isolate_vif', default=False,
help='Controls if VIF should be isolated when plugged '
'to the ovs bridge. This should only be set to True '
'when using the neutron ovs ml2 agent.')
'when using the neutron ovs ml2 agent.'),
cfg.BoolOpt('per_port_bridge', default=False,
help='Controls if VIF should be plugged into a per-port '
'bridge. This is experimental and controls the plugging '
'behavior when not using hybrid-plug.'
'This is only used on linux and should be set to false '
'in all other cases such as ironic smartnic ports.')
)
def __init__(self, config):
@ -98,8 +110,8 @@ class OvsPlugin(plugin.PluginBase):
self.ovsdb = ovsdb_lib.BaseOVS(self.config)
@staticmethod
def gen_port_name(prefix, id):
return ("%s%s" % (prefix, id))[:OvsPlugin.NIC_NAME_LEN]
def gen_port_name(prefix, vif_id, max_length=NIC_NAME_LEN):
return ("%s%s" % (prefix, vif_id))[:max_length]
@staticmethod
def get_veth_pair_names(vif):
@ -163,12 +175,13 @@ class OvsPlugin(plugin.PluginBase):
# can be enabled automatically in the future.
if self.config.isolate_vif:
kwargs['tag'] = constants.DEAD_VLAN
bridge = kwargs.pop('bridge', vif.network.bridge)
self.ovsdb.create_ovs_vif_port(
vif.network.bridge,
bridge,
vif_name,
vif.port_profile.interface_id,
vif.address, instance_info.uuid,
mtu,
mtu=mtu,
**kwargs)
def _update_vif_port(self, vif, vif_name):
@ -232,6 +245,37 @@ class OvsPlugin(plugin.PluginBase):
self._get_vif_datapath_type(vif))
self._create_vif_port(vif, vif.id, instance_info)
def _plug_port_bridge(self, vif, instance_info):
"""Create a per-VIF OVS bridge and patch pair."""
# NOTE(sean-k-mooney): the port name prefix should not be
# changed to avoid losing ports on upgrade.
port_bridge_name = self.gen_port_name('pb', vif.id)
port_bridge_patch = self.gen_port_name('pbp', vif.id, max_length=64)
int_bridge_name = vif.network.bridge
int_bridge_patch = self.gen_port_name('ibp', vif.id, max_length=64)
self.ovsdb.ensure_ovs_bridge(
int_bridge_name, self._get_vif_datapath_type(vif))
self.ovsdb.ensure_ovs_bridge(
port_bridge_name, self._get_vif_datapath_type(vif))
self._create_vif_port(
vif, vif.vif_name, instance_info, bridge=port_bridge_name,
set_ids=False
)
tag = constants.DEAD_VLAN if self.config.isolate_vif else None
iface_id = vif.id
mac = vif.address
instance_id = instance_info.uuid
LOG.debug(
'creating patch port pair \n'
f'{port_bridge_name}:({port_bridge_patch}) -> '
f'{int_bridge_name}:({int_bridge_patch})'
)
self.ovsdb.create_patch_port_pair(
port_bridge_name, port_bridge_patch, int_bridge_name,
int_bridge_patch, iface_id, mac, instance_id, tag=tag)
def _plug_vif_generic(self, vif, instance_info):
"""Create a per-VIF OVS port."""
self.ovsdb.ensure_ovs_bridge(vif.network.bridge,
@ -289,20 +333,30 @@ class OvsPlugin(plugin.PluginBase):
raise exception.WrongPortProfile(
profile=vif.port_profile.__class__.__name__)
if isinstance(vif, objects.vif.VIFOpenVSwitch):
if sys.platform != constants.PLATFORM_WIN32:
if sys.platform == constants.PLATFORM_WIN32:
if type(vif) not in (
objects.vif.VIFOpenVSwitch, objects.vif.VIFBridge
):
raise osv_exception.PlugException(
vif=vif, err="This vif type is not supported on Windows")
self._plug_vif_windows(vif, instance_info)
elif isinstance(vif, objects.vif.VIFOpenVSwitch):
if self.config.per_port_bridge:
self._plug_port_bridge(vif, instance_info)
else:
self._plug_vif_generic(vif, instance_info)
else:
self._plug_vif_windows(vif, instance_info)
elif isinstance(vif, objects.vif.VIFBridge):
if sys.platform != constants.PLATFORM_WIN32:
self._plug_bridge(vif, instance_info)
else:
self._plug_vif_windows(vif, instance_info)
self._plug_bridge(vif, instance_info)
elif isinstance(vif, objects.vif.VIFVHostUser):
self._plug_vhostuser(vif, instance_info)
elif isinstance(vif, objects.vif.VIFHostDevice):
self._plug_vf(vif, instance_info)
else:
# This should never be raised.
raise osv_exception.PlugException(
vif=vif,
err="This vif type is not supported by this plugin")
def _unplug_vhostuser(self, vif, instance_info):
self.ovsdb.delete_ovs_vif_port(vif.network.bridge,
@ -328,6 +382,18 @@ class OvsPlugin(plugin.PluginBase):
self.ovsdb.delete_ovs_vif_port(vif.network.bridge, vif.id,
delete_netdev=False)
def _unplug_port_bridge(self, vif, instance_info):
"""Create a per-VIF OVS bridge and patch pair."""
# NOTE(sean-k-mooney): the port name prefix should not be
# changed to avoid loosing ports on upgrade.
port_bridge_name = self.gen_port_name('pb', vif.id)
port_bridge_patch = self.gen_port_name('pbp', vif.id, max_length=64)
int_bridge_patch = self.gen_port_name('ibp', vif.id, max_length=64)
self.ovsdb.delete_ovs_vif_port(vif.network.bridge, int_bridge_patch)
self.ovsdb.delete_ovs_vif_port(port_bridge_name, port_bridge_patch)
self.ovsdb.delete_ovs_vif_port(port_bridge_name, vif.vif_name)
self.ovsdb.delete_ovs_bridge(port_bridge_name)
def _unplug_vif_generic(self, vif, instance_info):
"""Remove port from OVS."""
# NOTE(sean-k-mooney): even with the partial revert of change
@ -363,18 +429,26 @@ class OvsPlugin(plugin.PluginBase):
objects.vif.VIFPortProfileOpenVSwitch):
raise exception.WrongPortProfile(
profile=vif.port_profile.__class__.__name__)
if isinstance(vif, objects.vif.VIFOpenVSwitch):
if sys.platform != constants.PLATFORM_WIN32:
if sys.platform == constants.PLATFORM_WIN32:
if type(vif) not in (
objects.vif.VIFOpenVSwitch, objects.vif.VIFBridge
):
raise osv_exception.UnplugException(
vif=vif, err="This vif type is not supported on windows.")
self._unplug_vif_windows(vif, instance_info)
elif isinstance(vif, objects.vif.VIFOpenVSwitch):
if self.config.per_port_bridge:
self._unplug_port_bridge(vif, instance_info)
else:
self._unplug_vif_generic(vif, instance_info)
else:
self._unplug_vif_windows(vif, instance_info)
elif isinstance(vif, objects.vif.VIFBridge):
if sys.platform != constants.PLATFORM_WIN32:
self._unplug_bridge(vif, instance_info)
else:
self._unplug_vif_windows(vif, instance_info)
self._unplug_bridge(vif, instance_info)
elif isinstance(vif, objects.vif.VIFVHostUser):
self._unplug_vhostuser(vif, instance_info)
elif isinstance(vif, objects.vif.VIFHostDevice):
self._unplug_vf(vif)
else:
# this should never be raised.
raise osv_exception.UnplugException(
vif=vif,
err="This vif type is not supported by this plugin")

View File

@ -71,10 +71,75 @@ class BaseOVS(object):
return self.ovsdb.add_br(bridge, may_exist=True,
datapath_type=datapath_type).execute()
def create_ovs_vif_port(self, bridge, dev, iface_id, mac, instance_id,
mtu=None, interface_type=None,
vhost_server_path=None, tag=None,
pf_pci=None, vf_num=None):
def delete_ovs_bridge(self, bridge):
"""Delete ovs brdige by name
:param bridge: bridge name as a string
.. note:: Do Not call with br-int !!!
"""
# TODO(sean-k-mooney): when we fix bug: #1914886
# add a guard against deleting the integration bridge
# after adding a config option to store its name.
return self.ovsdb.del_br(bridge).execute()
def create_patch_port_pair(
self, port_bridge, port_bridge_port, int_bridge, int_bridge_port,
iface_id, mac, instance_id, tag=None
):
"""Create a patch port pair between any two bridges.
:param port_bridge: the source bridge name for the patch port pair.
:param port_bridge_port: the name of the patch port on the
source bridge.
:param int_bridge: the target bridge name, typically br-int.
:param int_bridge_port: the name of the patch port on the
target bridge.
:param iface_id: neutron port ID.
:param mac: port MAC.
:param instance_id: instance uuid.
:param mtu: port MTU.
:param tag: OVS interface tag used for vlan isolation.
"""
# NOTE(sean-k-mooney): we use a transaction here for 2 reasons:
# 1.) if using the vsctl client its faster
# 2.) in all cases we either want to fully create the patch port
# pair or not create it atomicly. By using a transaction we know
# that we will never be in a mixed state where it was partly created.
with self.ovsdb.transaction() as txn:
# create integration bridge patch peer
external_ids = {
'iface-id': iface_id, 'iface-status': 'active',
'attached-mac': mac, 'vm-uuid': instance_id
}
col_values = [
('external_ids', external_ids),
('type', 'patch'),
('options', {'peer': port_bridge_port})
]
txn.add(self.ovsdb.add_port(int_bridge, int_bridge_port))
if tag:
txn.add(
self.ovsdb.db_set('Port', int_bridge_port, ('tag', tag)))
txn.add(
self.ovsdb.db_set('Interface', int_bridge_port, *col_values))
# create port bidge patch peer
col_values = [
('type', 'patch'),
('options', {'peer': int_bridge_port})
]
txn.add(self.ovsdb.add_port(port_bridge, port_bridge_port))
txn.add(
self.ovsdb.db_set('Interface', port_bridge_port, *col_values))
def create_ovs_vif_port(
self, bridge, dev, iface_id, mac, instance_id,
mtu=None, interface_type=None, vhost_server_path=None,
tag=None, pf_pci=None, vf_num=None, set_ids=True
):
"""Create OVS port
:param bridge: bridge name to create the port on.
@ -88,6 +153,7 @@ class BaseOVS(object):
:param tag: OVS interface tag.
:param pf_pci: PCI address of PF for dpdk representor port.
:param vf_num: VF number of PF for dpdk representor port.
:param set_ids: set external ids on port (bool).
.. note:: create DPDK representor port by setting all three values:
`interface_type`, `pf_pci` and `vf_num`. if interface type is
@ -98,7 +164,7 @@ class BaseOVS(object):
'iface-status': 'active',
'attached-mac': mac,
'vm-uuid': instance_id}
col_values = [('external_ids', external_ids)]
col_values = [('external_ids', external_ids)] if set_ids else []
if interface_type:
col_values.append(('type', interface_type))
if vhost_server_path:
@ -114,7 +180,8 @@ class BaseOVS(object):
txn.add(self.ovsdb.add_port(bridge, dev))
if tag:
txn.add(self.ovsdb.db_set('Port', dev, ('tag', tag)))
txn.add(self.ovsdb.db_set('Interface', dev, *col_values))
if col_values:
txn.add(self.ovsdb.db_set('Interface', dev, *col_values))
self.update_device_mtu(dev, mtu, interface_type=interface_type)
def update_ovs_vif_port(self, dev, mtu=None, interface_type=None):

View File

@ -152,3 +152,40 @@ class TestOVSDBLib(testscenarios.WithScenarios,
self.ovs.ensure_ovs_bridge(bridge_name, constants.OVS_DATAPATH_SYSTEM)
self.assertTrue(self._check_bridge(bridge_name))
self.addCleanup(self._del_bridge, bridge_name)
def test_create_patch_port_pair(self):
port_bridge = 'fake-pb'
port_bridge_port = 'fake-pbp'
int_bridge = 'pb-int'
int_bridge_port = 'fake-ibp'
iface_id = 'iface_id'
mac = 'ca:fe:ca:fe:ca:fe'
instance_id = uuidutils.generate_uuid()
# deleting a bridge deletes all ports on bridges so we register the
# bridge cleanup first so if we fail anywhere it runs.
self.addCleanup(self._del_bridge, port_bridge)
self.addCleanup(self._del_bridge, int_bridge)
self.ovs.ensure_ovs_bridge(port_bridge, constants.OVS_DATAPATH_SYSTEM)
self.ovs.ensure_ovs_bridge(int_bridge, constants.OVS_DATAPATH_SYSTEM)
self.ovs.create_patch_port_pair(
port_bridge, port_bridge_port, int_bridge, int_bridge_port,
iface_id, mac, instance_id, tag=2000)
self.assertTrue(self._check_bridge(port_bridge))
self.assertTrue(self._check_bridge(int_bridge))
expected_external_ids = {'iface-status': 'active',
'iface-id': iface_id,
'attached-mac': mac,
'vm-uuid': instance_id}
self._check_parameter(
'Interface', int_bridge_port, 'external_ids',
expected_external_ids)
self._check_parameter('Interface', int_bridge_port, 'type', 'patch')
port_opts = {'peer': port_bridge_port}
self._check_parameter(
'Interface', int_bridge_port, 'options', port_opts)
self._check_parameter('Port', int_bridge_port, 'tag', 2000)
port_opts = {'peer': int_bridge_port}
self._check_parameter(
'Interface', port_bridge_port, 'options', port_opts)

View File

@ -66,6 +66,7 @@ class PluginTest(testtools.TestCase):
interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa',
datapath_type='system')
# This is used for ironic with vif_type=smartnic
self.profile_ovs_smart_nic = objects.vif.VIFPortProfileOpenVSwitch(
interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa',
create_port=True)
@ -89,6 +90,7 @@ class PluginTest(testtools.TestCase):
vif_name='tap-xxx-yyy-zzz',
port_profile=self.profile_ovs)
# This is used for ironic with vif_type=smartnic
self.vif_ovs_smart_nic = objects.vif.VIFOpenVSwitch(
id='b679325f-ca89-4ee0-a8be-6db1409b69ea',
address='ca:fe:de:ad:be:ef',
@ -153,7 +155,7 @@ class PluginTest(testtools.TestCase):
self.vif_ovs.network.bridge, mock.sentinel.vif_name,
self.vif_ovs.port_profile.interface_id,
self.vif_ovs.address, self.instance.uuid,
plugin.config.network_device_mtu,
mtu=plugin.config.network_device_mtu,
interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE)
@mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port')
@ -167,7 +169,7 @@ class PluginTest(testtools.TestCase):
self.vif_ovs.network.bridge, mock.sentinel.vif_name,
self.vif_ovs.port_profile.interface_id,
self.vif_ovs.address, self.instance.uuid,
self.network_ovs_mtu.mtu,
mtu=self.network_ovs_mtu.mtu,
interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE)
@mock.patch.object(ovsdb_lib.BaseOVS, 'create_ovs_vif_port')
@ -181,18 +183,28 @@ class PluginTest(testtools.TestCase):
self.vif_ovs.network.bridge, mock.sentinel.vif_name,
self.vif_ovs.port_profile.interface_id,
self.vif_ovs.address, self.instance.uuid,
plugin.config.network_device_mtu,
mtu=plugin.config.network_device_mtu,
interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE,
tag=constants.DEAD_VLAN)
@mock.patch.object(ovs, 'sys')
@mock.patch.object(ovs.OvsPlugin, '_plug_vif_generic')
def test_plug_ovs(self, plug_vif_generic, mock_sys):
def test_plug_ovs_port_bridge_false(self, plug_vif_generic, mock_sys):
mock_sys.platform = 'linux'
plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME)
plugin.plug(self.vif_ovs, self.instance)
plug_vif_generic.assert_called_once_with(self.vif_ovs,
self.instance)
with mock.patch.object(plugin.config, 'per_port_bridge', False):
plugin.plug(self.vif_ovs, self.instance)
plug_vif_generic.assert_called_once_with(
self.vif_ovs, self.instance)
@mock.patch.object(ovs, 'sys')
@mock.patch.object(ovs.OvsPlugin, '_plug_port_bridge')
def test_plug_ovs_port_bridge_true(self, plug_vif, mock_sys):
mock_sys.platform = 'linux'
plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME)
with mock.patch.object(plugin.config, 'per_port_bridge', True):
plugin.plug(self.vif_ovs, self.instance)
plug_vif.assert_called_once_with(self.vif_ovs, self.instance)
@mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge')
@mock.patch.object(ovs.OvsPlugin, "_create_vif_port")
@ -298,11 +310,21 @@ class PluginTest(testtools.TestCase):
@mock.patch.object(ovs, 'sys')
@mock.patch.object(ovs.OvsPlugin, '_unplug_vif_generic')
def test_unplug_ovs(self, unplug, mock_sys):
def test_unplug_ovs_port_bridge_false(self, unplug, mock_sys):
mock_sys.platform = 'linux'
plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME)
plugin.unplug(self.vif_ovs, self.instance)
unplug.assert_called_once_with(self.vif_ovs, self.instance)
with mock.patch.object(plugin.config, 'per_port_bridge', False):
plugin.unplug(self.vif_ovs, self.instance)
unplug.assert_called_once_with(self.vif_ovs, self.instance)
@mock.patch.object(ovs, 'sys')
@mock.patch.object(ovs.OvsPlugin, '_unplug_port_bridge')
def test_unplug_ovs_port_bridge_true(self, unplug, mock_sys):
mock_sys.platform = 'linux'
plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME)
with mock.patch.object(plugin.config, 'per_port_bridge', True):
plugin.unplug(self.vif_ovs, self.instance)
unplug.assert_called_once_with(self.vif_ovs, self.instance)
@mock.patch.object(ovs.OvsPlugin, '_unplug_vif_generic')
def test_unplug_vif_generic(self, delete_port):
@ -371,7 +393,7 @@ class PluginTest(testtools.TestCase):
'e65867e0-9340-4a7f-a256-09af6eb7a3aa',
'ca:fe:de:ad:be:ef',
'f0000000-0000-0000-0000-000000000001',
1500, interface_type='dpdkvhostuserclient',
mtu=1500, interface_type='dpdkvhostuserclient',
vhost_server_path='/var/run/openvswitch/vhub679325f-ca'
)],
'ensure_ovs_bridge': [mock.call('br0', dp_type)]
@ -472,17 +494,19 @@ class PluginTest(testtools.TestCase):
@mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge')
@mock.patch.object(ovs.OvsPlugin, "_create_vif_port")
def test_plug_vif_ovs_smart_nic(self, create_port, ensure_bridge):
def test_plug_vif_ovs_ironic_smart_nic(self, create_port, ensure_bridge):
plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME)
plugin.plug(self.vif_ovs_smart_nic, self.instance)
ensure_bridge.assert_called_once()
create_port.assert_called_once()
with mock.patch.object(plugin.config, 'per_port_bridge', False):
plugin.plug(self.vif_ovs_smart_nic, self.instance)
ensure_bridge.assert_called_once()
create_port.assert_called_once()
@mock.patch.object(ovs.OvsPlugin, '_unplug_vif_generic')
def test_unplug_vif_ovs_smart_nic(self, delete_port):
plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME)
plugin.unplug(self.vif_ovs_smart_nic, self.instance)
delete_port.assert_called_once()
with mock.patch.object(plugin.config, 'per_port_bridge', False):
plugin.unplug(self.vif_ovs_smart_nic, self.instance)
delete_port.assert_called_once()
@mock.patch.object(linux_net, 'get_dpdk_representor_port_name')
@mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge')
@ -544,3 +568,33 @@ class PluginTest(testtools.TestCase):
get_dpdk_representor_port_name.assert_has_calls(
calls['get_dpdk_representor_port_name'])
delete_ovs_vif_port.assert_has_calls(calls['delete_ovs_vif_port'])
@mock.patch.object(ovsdb_lib.BaseOVS, 'create_patch_port_pair')
@mock.patch.object(ovsdb_lib.BaseOVS, 'ensure_ovs_bridge')
@mock.patch.object(ovs.OvsPlugin, "_create_vif_port")
def test_plug_port_bridge(
self, create_port, ensure_bridge, create_patch_port_pair):
plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME)
plugin._plug_port_bridge(self.vif_ovs, self.instance)
calls = [
mock.call('br0', 'netdev'),
mock.call('pbb679325f-ca8', 'netdev')
]
ensure_bridge.assert_has_calls(calls)
create_port.assert_called_once()
create_patch_port_pair.assert_called_once()
@mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_vif_port')
@mock.patch.object(ovsdb_lib.BaseOVS, 'delete_ovs_bridge')
def test_unplug_port_bridge(
self, delete_ovs_bridge, delete_ovs_vif_port):
plugin = ovs.OvsPlugin.load(constants.PLUGIN_NAME)
plugin._unplug_port_bridge(self.vif_ovs, self.instance)
calls = [
mock.call('br0', 'ibpb679325f-ca89-4ee0-a8be-6db1409b69ea'),
mock.call(
'pbb679325f-ca8', 'pbpb679325f-ca89-4ee0-a8be-6db1409b69ea'),
mock.call('pbb679325f-ca8', 'tap-xxx-yyy-zzz')
]
delete_ovs_vif_port.assert_has_calls(calls)
delete_ovs_bridge.assert_called_once_with('pbb679325f-ca8')