diff -Nru python-os-vif-2.5.0/ChangeLog python-os-vif-2.6.0/ChangeLog --- python-os-vif-2.5.0/ChangeLog 2021-05-28 14:11:03.000000000 +0000 +++ python-os-vif-2.6.0/ChangeLog 2021-08-19 20:34:36.000000000 +0000 @@ -1,6 +1,12 @@ CHANGES ======= +2.6.0 +----- + +* add configurable per port bridges +* update os-vif ci to account for devstack default changes + 2.5.0 ----- diff -Nru python-os-vif-2.5.0/debian/changelog python-os-vif-2.6.0/debian/changelog --- python-os-vif-2.5.0/debian/changelog 2021-06-10 14:22:36.000000000 +0000 +++ python-os-vif-2.6.0/debian/changelog 2021-09-07 18:18:17.000000000 +0000 @@ -1,8 +1,8 @@ -python-os-vif (2.5.0-0ubuntu1) impish; urgency=medium +python-os-vif (2.6.0-0ubuntu1) impish; urgency=medium * New upstream release for OpenStack Xena. - -- Chris MacNaughton Thu, 10 Jun 2021 14:22:36 +0000 + -- Corey Bryant Tue, 07 Sep 2021 14:18:17 -0400 python-os-vif (2.4.0-0ubuntu1) hirsute; urgency=medium diff -Nru python-os-vif-2.5.0/os_vif.egg-info/pbr.json python-os-vif-2.6.0/os_vif.egg-info/pbr.json --- python-os-vif-2.5.0/os_vif.egg-info/pbr.json 2021-05-28 14:11:03.000000000 +0000 +++ python-os-vif-2.6.0/os_vif.egg-info/pbr.json 2021-08-19 20:34:36.000000000 +0000 @@ -1 +1 @@ -{"git_version": "e937367", "is_release": true} \ No newline at end of file +{"git_version": "b837c1a", "is_release": true} \ No newline at end of file diff -Nru python-os-vif-2.5.0/os_vif.egg-info/PKG-INFO python-os-vif-2.6.0/os_vif.egg-info/PKG-INFO --- python-os-vif-2.5.0/os_vif.egg-info/PKG-INFO 2021-05-28 14:11:03.000000000 +0000 +++ python-os-vif-2.6.0/os_vif.egg-info/PKG-INFO 2021-08-19 20:34:36.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.2 Name: os-vif -Version: 2.5.0 +Version: 2.6.0 Summary: A library for plugging and unplugging virtual interfaces in OpenStack. Home-page: https://docs.openstack.org/os-vif/latest/ Author: OpenStack diff -Nru python-os-vif-2.5.0/os_vif.egg-info/SOURCES.txt python-os-vif-2.6.0/os_vif.egg-info/SOURCES.txt --- python-os-vif-2.5.0/os_vif.egg-info/SOURCES.txt 2021-05-28 14:11:03.000000000 +0000 +++ python-os-vif-2.6.0/os_vif.egg-info/SOURCES.txt 2021-08-19 20:34:36.000000000 +0000 @@ -108,6 +108,7 @@ releasenotes/notes/generic-datapath-offloads-41cabb6842b41533.yaml releasenotes/notes/initial-release-2c71d6bbf55f763b.yaml releasenotes/notes/oslo-config-opts-entrypoints-e83f907b686d774a.yaml +releasenotes/notes/per-port-bridge-c6a50179a0256de3.yaml releasenotes/notes/port-profile-info-linux-bridge-4800f5a0b7328615.yaml releasenotes/notes/port-profile-info-ovs-63b46a3eafc11de2.yaml releasenotes/notes/prevent-lb-reply-arp-6459133bfb056069.yaml diff -Nru python-os-vif-2.5.0/PKG-INFO python-os-vif-2.6.0/PKG-INFO --- python-os-vif-2.5.0/PKG-INFO 2021-05-28 14:11:03.703331700 +0000 +++ python-os-vif-2.6.0/PKG-INFO 2021-08-19 20:34:36.495413300 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.2 Name: os_vif -Version: 2.5.0 +Version: 2.6.0 Summary: A library for plugging and unplugging virtual interfaces in OpenStack. Home-page: https://docs.openstack.org/os-vif/latest/ Author: OpenStack diff -Nru python-os-vif-2.5.0/releasenotes/notes/per-port-bridge-c6a50179a0256de3.yaml python-os-vif-2.6.0/releasenotes/notes/per-port-bridge-c6a50179a0256de3.yaml --- python-os-vif-2.5.0/releasenotes/notes/per-port-bridge-c6a50179a0256de3.yaml 1970-01-01 00:00:00.000000000 +0000 +++ python-os-vif-2.6.0/releasenotes/notes/per-port-bridge-c6a50179a0256de3.yaml 2021-08-19 20:34:01.000000000 +0000 @@ -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. diff -Nru python-os-vif-2.5.0/vif_plug_ovs/ovsdb/ovsdb_lib.py python-os-vif-2.6.0/vif_plug_ovs/ovsdb/ovsdb_lib.py --- python-os-vif-2.5.0/vif_plug_ovs/ovsdb/ovsdb_lib.py 2021-05-28 14:10:35.000000000 +0000 +++ python-os-vif-2.6.0/vif_plug_ovs/ovsdb/ovsdb_lib.py 2021-08-19 20:34:01.000000000 +0000 @@ -71,10 +71,75 @@ 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 @@ :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 @@ '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 @@ 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): diff -Nru python-os-vif-2.5.0/vif_plug_ovs/ovs.py python-os-vif-2.6.0/vif_plug_ovs/ovs.py --- python-os-vif-2.5.0/vif_plug_ovs/ovs.py 2021-05-28 14:10:35.000000000 +0000 +++ python-os-vif-2.6.0/vif_plug_ovs/ovs.py 2021-08-19 20:34:01.000000000 +0000 @@ -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.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 @@ 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 @@ 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 @@ # 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 @@ 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 @@ raise exception.WrongPortProfile( profile=vif.port_profile.__class__.__name__) - if isinstance(vif, objects.vif.VIFOpenVSwitch): - if sys.platform != constants.PLATFORM_WIN32: - self._plug_vif_generic(vif, instance_info) + 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_windows(vif, instance_info) + self._plug_vif_generic(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 @@ 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 @@ objects.vif.VIFPortProfileOpenVSwitch): raise exception.WrongPortProfile( profile=vif.port_profile.__class__.__name__) - - if isinstance(vif, objects.vif.VIFOpenVSwitch): - if sys.platform != constants.PLATFORM_WIN32: - self._unplug_vif_generic(vif, instance_info) + 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_windows(vif, instance_info) + self._unplug_vif_generic(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") diff -Nru python-os-vif-2.5.0/vif_plug_ovs/tests/functional/ovsdb/test_ovsdb_lib.py python-os-vif-2.6.0/vif_plug_ovs/tests/functional/ovsdb/test_ovsdb_lib.py --- python-os-vif-2.5.0/vif_plug_ovs/tests/functional/ovsdb/test_ovsdb_lib.py 2021-05-28 14:10:35.000000000 +0000 +++ python-os-vif-2.6.0/vif_plug_ovs/tests/functional/ovsdb/test_ovsdb_lib.py 2021-08-19 20:34:01.000000000 +0000 @@ -152,3 +152,40 @@ 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) diff -Nru python-os-vif-2.5.0/vif_plug_ovs/tests/unit/test_plugin.py python-os-vif-2.6.0/vif_plug_ovs/tests/unit/test_plugin.py --- python-os-vif-2.5.0/vif_plug_ovs/tests/unit/test_plugin.py 2021-05-28 14:10:35.000000000 +0000 +++ python-os-vif-2.6.0/vif_plug_ovs/tests/unit/test_plugin.py 2021-08-19 20:34:01.000000000 +0000 @@ -66,6 +66,7 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ @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 @@ '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 @@ @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 @@ 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') diff -Nru python-os-vif-2.5.0/.zuul.yaml python-os-vif-2.6.0/.zuul.yaml --- python-os-vif-2.5.0/.zuul.yaml 2021-05-28 14:10:35.000000000 +0000 +++ python-os-vif-2.6.0/.zuul.yaml 2021-08-19 20:34:01.000000000 +0000 @@ -9,12 +9,12 @@ - job: name: os-vif-tempest-base parent: devstack-tempest + timeout: 7800 description: | Base integration test with Neutron networking and py3. This is derived from tempest-full-py3 and adapted for use in os-vif required-projects: - - openstack/devstack-gate - openstack/nova - openstack/os-vif - openstack/neutron @@ -43,12 +43,35 @@ - job: name: os-vif-ovs-base parent: os-vif-tempest-base - timeout: 7800 description: | os-vif ovs base job, this should not be used directly. vars: + devstack_services: + # Disable OVN services + br-ex-tcpdump: false + br-int-flows: false + ovn-controller: false + ovn-northd: false + ovs-vswitchd: false + ovsdb-server: false + q-ovn-metadata-agent: false + # Neutron services + q-agt: true + q-dhcp: true + q-l3: true + q-meta: true + q-metering: true + devstack_localrc: + Q_AGENT: openvswitch + Q_ML2_PLUGIN_MECHANISM_DRIVERS: openvswitch + Q_DVR_MODE: dvr_snat + Q_ML2_TENANT_NETWORK_TYPE: vxlan devstack_local_conf: post-config: + $NEUTRON_CONF: + DEFAULT: + enable_dvr: yes + l3_ha: yes $NEUTRON_L3_CONF: agent: availability_zone: nova @@ -59,25 +82,19 @@ ml2_type_vlan: network_vlan_ranges: foo:1:10 agent: - enable_distributed_routing: true - l2_population: true - tunnel_types: vxlan,gre - test-config: - $TEMPEST_CONFIG: - neutron_plugin_options: - provider_vlans: foo, - agent_availability_zone: nova - image_is_advanced: true - available_type_drivers: flat,vlan,gre,local,vxlan + tunnel_types: vxlan - job: - name: os-vif-ovs + name: os-vif-ovs-iptables parent: os-vif-ovs-base description: | - os-vif ovs job with ovs firewall driver + os-vif ovs iptables job (tests hybrid-plug=true) vars: devstack_local_conf: post-config: + $NOVA_CONF: + os_vif_ovs: + isolate_vif: true # NOTE(sean-k-mooney): i do not believe that the devstack role # will merge the base /$NEUTRON_CORE_PLUGIN_CONF with the parent # job so we redefine the entire section @@ -85,42 +102,59 @@ ml2_type_vlan: network_vlan_ranges: foo:1:10 agent: - enable_distributed_routing: true - l2_population: true - tunnel_types: vxlan,gre + tunnel_types: vxlan securitygroup: - firewall_driver: openvswitch + firewall_driver: iptables_hybrid + enable_ipset: false - job: - name: os-vif-ovs-iptables - parent: os-vif-ovs-base + name: os-vif-ovn + parent: os-vif-tempest-base description: | - os-vif iptables job derived from neutron-tempest-iptables_hybrid + os-vif ovn job (tests hybrid-plug=false) vars: - tox_envlist: full devstack_local_conf: post-config: - # NOTE(sean-k-mooney): i do not believe that the devstack role - # will merge the base /$NEUTRON_CORE_PLUGIN_CONF with the parent - # job so we redefine the entire section - "/$NEUTRON_CORE_PLUGIN_CONF": - ml2_type_vlan: - network_vlan_ranges: foo:1:10 - agent: - enable_distributed_routing: true - l2_population: true - tunnel_types: vxlan,gre - securitygroup: - firewall_driver: iptables_hybrid + $NOVA_CONF: + os_vif_ovs: + per_port_bridge: true + - job: name: os-vif-linuxbridge parent: os-vif-tempest-base - timeout: 10800 description: | os-vif linux bridge job derived from neutron-tempest-linuxbridge vars: + devstack_services: + # Disable OVN services + br-ex-tcpdump: false + br-int-flows: false + ovn-controller: false + ovn-northd: false + ovs-vswitchd: false + ovsdb-server: false + q-ovn-metadata-agent: false + # Neutron services + q-agt: true + q-dhcp: true + q-l3: true + q-meta: true + q-metering: true devstack_localrc: Q_AGENT: linuxbridge + Q_ML2_PLUGIN_MECHANISM_DRIVERS: linuxbridge + Q_ML2_TENANT_NETWORK_TYPE: vxlan + devstack_local_conf: + post-config: + "/$NEUTRON_CORE_PLUGIN_CONF": + ml2: + type_drivers: flat,vlan,local,vxlan + ml2_type_vlan: + network_vlan_ranges: foo:1:10 + agent: + tunnel_types: vxlan + securitygroup: + firewall_driver: iptables - project: templates: @@ -135,12 +169,12 @@ - kuryr-kubernetes-tempest: voting: false - openstack-tox-functional-ovs-with-sudo - - os-vif-ovs + - os-vif-ovn - os-vif-ovs-iptables - os-vif-linuxbridge gate: jobs: - openstack-tox-functional-ovs-with-sudo - - os-vif-ovs + - os-vif-ovn - os-vif-ovs-iptables - os-vif-linuxbridge