From baaf240ce3f7802fe1431cc13913b9d93fc7f742 Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Sat, 19 Aug 2023 05:11:55 +0000 Subject: [PATCH] [OVN] Add the bridge name and datapath type to the port VIF details Same as in ML2/OVS, the ML2/OVN mechanism driver adds to the port VIF details dictionary the OVS bridge the port is connected to and the integration bridge datapath type. Closes-Bug: #2045889 Change-Id: Ifda46c42b9506449a58fbaf312cc71c72d9cf2df --- neutron/agent/ovn/metadata/agent.py | 14 +++ neutron/common/_constants.py | 4 + neutron/common/ovn/constants.py | 3 + neutron/common/ovn/utils.py | 26 ++++ .../plugins/ml2/drivers/ovn/db_migration.py | 26 ++-- .../drivers/ovn/mech_driver/mech_driver.py | 17 ++- .../agent/ovn/metadata/test_metadata_agent.py | 5 + .../ovn/mech_driver/test_mech_driver.py | 31 +++-- neutron/tests/unit/fake_resources.py | 7 +- .../ovn/mech_driver/test_mech_driver.py | 111 +++++++++++++----- .../ml2/drivers/ovn/test_db_migration.py | 26 ++-- ...me-and-datapath-type-d2bd5b438118355f.yaml | 8 ++ 12 files changed, 206 insertions(+), 72 deletions(-) create mode 100644 releasenotes/notes/ovn-vif-details-bridge-name-and-datapath-type-d2bd5b438118355f.yaml diff --git a/neutron/agent/ovn/metadata/agent.py b/neutron/agent/ovn/metadata/agent.py index 7a091455bd8..4fd4aa8e40b 100644 --- a/neutron/agent/ovn/metadata/agent.py +++ b/neutron/agent/ovn/metadata/agent.py @@ -370,6 +370,18 @@ class MetadataAgent(object): LOG.debug("Loaded chassis name %s (UUID: %s) and ovn bridge %s.", self.chassis, self.chassis_id, self.ovn_bridge) + def _update_chassis_private_config(self): + """Update the Chassis_Private register information + + This method should be called once the Metadata Agent has been + registered (method ``register_metadata_agent`` has been called) and + the corresponding Chassis_Private register has been created/updated. + """ + external_ids = {ovn_const.OVN_AGENT_OVN_BRIDGE: self.ovn_bridge} + self.sb_idl.db_set( + 'Chassis_Private', self.chassis, + ('external_ids', external_ids)).execute(check_error=True) + @_sync_lock def resync(self): """Resync the agent. @@ -377,6 +389,7 @@ class MetadataAgent(object): Reload the configuration and sync the agent again. """ self._load_config() + self._update_chassis_private_config() self.sync() def start(self): @@ -409,6 +422,7 @@ class MetadataAgent(object): # Register the agent with its corresponding Chassis self.register_metadata_agent() + self._update_chassis_private_config() self._proxy.wait() diff --git a/neutron/common/_constants.py b/neutron/common/_constants.py index 40745ecc86a..97c12e1c397 100644 --- a/neutron/common/_constants.py +++ b/neutron/common/_constants.py @@ -92,3 +92,7 @@ METADATA_V6_CIDR = constants.METADATA_V6_IP + '/128' # TODO(haleyb): move this constant to neutron_lib.constants IPV4_MIN_MTU = 68 + + +# TODO(ralonsoh): move this constant to neutron_lib.plugins.ml2.ovs_constants +DEFAULT_BR_INT = 'br-int' diff --git a/neutron/common/ovn/constants.py b/neutron/common/ovn/constants.py index 99cb80c56e2..7c34d8d212f 100644 --- a/neutron/common/ovn/constants.py +++ b/neutron/common/ovn/constants.py @@ -72,6 +72,8 @@ OVN_NAME_PREFIX = 'neutron-' OVN_HA_CH_GROUP_EXTPORT_PREFIX = 'neutron-extport-' OVN_METADATA_PREFIX = 'ovnmeta-' +OVN_DATAPATH_TYPE = 'datapath-type' + # TODO(froyo): Move this to neutron-lib as soon as possible, and when a new # release is created and pointed to in the requirements remove this code OVN_LB_HM_PORT_DISTRIBUTED = 'ovn-lb-hm:distributed' @@ -84,6 +86,7 @@ OVN_AGENT_METADATA_ID_KEY = 'neutron:ovn-metadata-id' OVN_AGENT_NEUTRON_SB_CFG_KEY = 'neutron:ovn-neutron-agent-sb-cfg' OVN_AGENT_NEUTRON_DESC_KEY = 'neutron:description-neutron-agent' OVN_AGENT_NEUTRON_ID_KEY = 'neutron:ovn-neutron-agent-id' +OVN_AGENT_OVN_BRIDGE = 'neutron:ovn-bridge' OVN_CONTROLLER_AGENT = 'OVN Controller agent' OVN_CONTROLLER_GW_AGENT = 'OVN Controller Gateway agent' OVN_METADATA_AGENT = 'OVN Metadata agent' diff --git a/neutron/common/ovn/utils.py b/neutron/common/ovn/utils.py index 14ab1870875..4d48214f3c3 100644 --- a/neutron/common/ovn/utils.py +++ b/neutron/common/ovn/utils.py @@ -40,6 +40,7 @@ from ovsdbapp import constants as ovsdbapp_const import tenacity from neutron._i18n import _ +from neutron.common import _constants as n_const from neutron.common.ovn import constants from neutron.common.ovn import exceptions as ovn_exc from neutron.common import utils as common_utils @@ -707,6 +708,31 @@ def get_ovn_cms_options(chassis): constants.OVN_CMS_OPTIONS, '').split(',')] +def get_ovn_bridge_from_chassis_private(chassis_private): + """Return the OVN bridge used by the local OVN controller + + This information is stored in the Chassis_Private register by the OVN + Metadata agent. The default value returned, if not present, is "br-int". + NOTE: the default value is not reading the local ``OVS.integration_bridge`` + configuration knob, that could be different. + """ + return (chassis_private.external_ids.get(constants.OVN_AGENT_OVN_BRIDGE) or + n_const.DEFAULT_BR_INT) + + +def get_datapath_type(hostname, sb_idl): + """Return the local OVS integration bridge datapath type + + If the datapath type is not stored in the ``Chassis`` register or + the register is still not created, the default value returned is "". + """ + chassis = sb_idl.db_find( + 'Chassis', ('hostname', '=', hostname)).execute(check_error=True) + return ( + chassis[0].get('other_config', {}).get(constants.OVN_DATAPATH_TYPE, '') + if chassis else '') + + def is_gateway_chassis(chassis): """Check if the given chassis is a gateway chassis""" return constants.CMS_OPT_CHASSIS_AS_GW in get_ovn_cms_options(chassis) diff --git a/neutron/plugins/ml2/drivers/ovn/db_migration.py b/neutron/plugins/ml2/drivers/ovn/db_migration.py index d37434b5548..3b3ba5ee6c6 100644 --- a/neutron/plugins/ml2/drivers/ovn/db_migration.py +++ b/neutron/plugins/ml2/drivers/ovn/db_migration.py @@ -11,6 +11,7 @@ # 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 copy from neutron_lib.api.definitions import portbindings as pb_api from neutron_lib import constants @@ -21,6 +22,7 @@ from oslo_db import exception as db_exc from oslo_log import log as logging from sqlalchemy.orm import exc as sqla_exc +from neutron.common import _constants as n_const from neutron.db.models.plugins.ml2 import geneveallocation from neutron.db.models.plugins.ml2 import vxlanallocation from neutron.objects import network as network_obj @@ -30,10 +32,6 @@ from neutron.objects import trunk as trunk_obj LOG = logging.getLogger(__name__) -VIF_DETAILS_TO_REMOVE = ( - pb_api.VIF_DETAILS_BRIDGE_NAME, -) - def migrate_neutron_database_to_ovn(): """Change DB content from OVS to OVN mech driver. @@ -75,18 +73,22 @@ def migrate_neutron_database_to_ovn(): with db_api.CONTEXT_WRITER.using(ctx): pb = port_obj.PortBinding.get_object(ctx, port_id=port_id, host=host) - if not pb or not pb.vif_details: + if not pb: continue - vif_details = pb.vif_details.copy() - for detail in VIF_DETAILS_TO_REMOVE: - try: - del vif_details[detail] - except KeyError: - pass - if vif_details == pb.vif_details: + # Update the OVS bridge name in the VIF details: now all + # port are directly connected to the integration bridge. + # Because the name of each host integration bridge is not + # know by the Neutron API at this point, the default value + # "br-int" will be used. + # The OVS datapath type is unchanged. + vif_details = copy.deepcopy(pb.vif_details) or {} + if (vif_details.get(pb_api.VIF_DETAILS_BRIDGE_NAME) == + n_const.DEFAULT_BR_INT): continue + vif_details[pb_api.VIF_DETAILS_BRIDGE_NAME] = ( + n_const.DEFAULT_BR_INT) pb.vif_details = vif_details pb.update() except (exceptions.ObjectNotFound, diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py index 437918bad55..4d0d5c185d1 100644 --- a/neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py +++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py @@ -187,7 +187,7 @@ class OVNMechanismDriver(api.MechanismDriver): vif_types = set() for ch in self.sb_ovn.chassis_list().execute(check_error=True): other_config = ovn_utils.get_ovn_chassis_other_config(ch) - dp_type = other_config.get('datapath-type', '') + dp_type = other_config.get(ovn_const.OVN_DATAPATH_TYPE, '') if dp_type == ovn_const.CHASSIS_DATAPATH_NETDEV: vif_types.add(portbindings.VIF_TYPE_VHOST_USER) else: @@ -990,7 +990,7 @@ class OVNMechanismDriver(api.MechanismDriver): return chassis = agent.chassis other_config = ovn_utils.get_ovn_chassis_other_config(chassis) - datapath_type = other_config.get('datapath-type', '') + datapath_type = other_config.get(ovn_const.OVN_DATAPATH_TYPE, '') iface_types = other_config.get('iface-types', '') iface_types = iface_types.split(',') if iface_types else [] chassis_physnets = self.sb_ovn._get_chassis_physnets(chassis) @@ -1037,7 +1037,7 @@ class OVNMechanismDriver(api.MechanismDriver): vif_type = portbindings.VIF_TYPE_VHOST_USER port[portbindings.VIF_DETAILS].update({ portbindings.VHOST_USER_SOCKET: vhost_user_socket}) - vif_details = dict(self.vif_details[vif_type]) + vif_details = copy.deepcopy(self.vif_details[vif_type]) vif_details[portbindings.VHOST_USER_SOCKET] = ( vhost_user_socket) elif (vnic_type == portbindings.VNIC_VIRTIO_FORWARDER): @@ -1046,15 +1046,22 @@ class OVNMechanismDriver(api.MechanismDriver): vif_type = portbindings.VIF_TYPE_AGILIO_OVS port[portbindings.VIF_DETAILS].update({ portbindings.VHOST_USER_SOCKET: vhost_user_socket}) - vif_details = dict(self.vif_details[vif_type]) + vif_details = copy.deepcopy(self.vif_details[vif_type]) vif_details[portbindings.VHOST_USER_SOCKET] = ( vhost_user_socket) vif_details[portbindings.VHOST_USER_MODE] = ( portbindings.VHOST_USER_MODE_CLIENT) else: vif_type = portbindings.VIF_TYPE_OVS - vif_details = self.vif_details[vif_type] + vif_details = copy.deepcopy(self.vif_details[vif_type]) + ovn_bridge = ovn_utils.get_ovn_bridge_from_chassis_private( + agent.chassis_private) + dp_type = ovn_utils.get_datapath_type(bind_host, self.sb_ovn) + vif_details.update({ + portbindings.VIF_DETAILS_BRIDGE_NAME: ovn_bridge, + portbindings.OVS_DATAPATH_TYPE: dp_type, + }) context.set_binding(segment_to_bind[api.ID], vif_type, vif_details) break diff --git a/neutron/tests/functional/agent/ovn/metadata/test_metadata_agent.py b/neutron/tests/functional/agent/ovn/metadata/test_metadata_agent.py index bd6ace7392c..2f6a92fb578 100644 --- a/neutron/tests/functional/agent/ovn/metadata/test_metadata_agent.py +++ b/neutron/tests/functional/agent/ovn/metadata/test_metadata_agent.py @@ -114,6 +114,11 @@ class TestMetadataAgent(base.TestOVNFunctionalBase): with mock.patch.object(metadata_server.UnixDomainMetadataProxy, 'wait'): agt.start() + external_ids = agt.sb_idl.db_get( + 'Chassis_Private', agt.chassis, 'external_ids').execute( + check_error=True) + self.assertEqual(external_ids[ovn_const.OVN_AGENT_OVN_BRIDGE], + self.OVN_BRIDGE) # Metadata agent will open connections to OVS and SB databases. # Close connections to them when the test ends, diff --git a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py index 115a86f1373..787f7919660 100644 --- a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py +++ b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py @@ -43,6 +43,8 @@ OVS_VIF_DETAILS = { portbindings.CAP_PORT_FILTER: True, portbindings.VIF_DETAILS_CONNECTIVITY: portbindings.CONNECTIVITY_L2, portbindings.VIF_DETAILS_BOUND_DRIVERS: {'0': 'ovn'}, + portbindings.VIF_DETAILS_BRIDGE_NAME: 'br-int', + portbindings.OVS_DATAPATH_TYPE: 'system', } VHOSTUSER_VIF_DETAILS = { portbindings.CAP_PORT_FILTER: False, @@ -50,34 +52,39 @@ VHOSTUSER_VIF_DETAILS = { 'vhostuser_ovs_plug': True, portbindings.VIF_DETAILS_CONNECTIVITY: portbindings.CONNECTIVITY_L2, portbindings.VIF_DETAILS_BOUND_DRIVERS: {'0': 'ovn'}, + portbindings.VIF_DETAILS_BRIDGE_NAME: 'br-int', + portbindings.OVS_DATAPATH_TYPE: 'netdev', } class TestPortBinding(base.TestOVNFunctionalBase): - def setUp(self): - super(TestPortBinding, self).setUp() + def setUp(self, **kwargs): + super().setUp(**kwargs) self.ovs_host = 'ovs-host' self.dpdk_host = 'dpdk-host' self.invalid_dpdk_host = 'invalid-host' self.insecure_host = 'insecure-host' self.smartnic_dpu_host = 'smartnic-dpu-host' self.smartnic_dpu_serial = 'fake-smartnic-dpu-serial' - self.add_fake_chassis(self.ovs_host) + self.add_fake_chassis( + self.ovs_host, + other_config={ovn_const.OVN_DATAPATH_TYPE: 'system'}) self.add_fake_chassis( self.dpdk_host, - other_config={'datapath-type': 'netdev', + other_config={ovn_const.OVN_DATAPATH_TYPE: 'netdev', 'iface-types': 'dummy,dummy-internal,dpdkvhostuser'}) self.add_fake_chassis( self.invalid_dpdk_host, - other_config={'datapath-type': 'netdev', + other_config={ovn_const.OVN_DATAPATH_TYPE: 'netdev', 'iface-types': 'dummy,dummy-internal,geneve,vxlan'}) self.add_fake_chassis( self.smartnic_dpu_host, other_config={ovn_const.OVN_CMS_OPTIONS: '{}={}'.format( ovn_const.CMS_OPT_CARD_SERIAL_NUMBER, - self.smartnic_dpu_serial)}) + self.smartnic_dpu_serial), + ovn_const.OVN_DATAPATH_TYPE: 'system'}) self.n1 = self._make_network(self.fmt, 'n1', True) res = self._create_subnet(self.fmt, self.n1['network']['id'], '10.0.0.0/24') @@ -152,9 +159,13 @@ class TestPortBinding(base.TestOVNFunctionalBase): self._verify_vif_details(port_id, self.dpdk_host, 'vhostuser', expected_vif_details) + expected_vif_details = copy.deepcopy(VHOSTUSER_VIF_DETAILS) + expected_vif_details.pop('vhostuser_mode') + expected_vif_details.pop('vhostuser_ovs_plug') + expected_vif_details[portbindings.CAP_PORT_FILTER] = True port_id = self._create_or_update_port(hostname=self.invalid_dpdk_host) self._verify_vif_details(port_id, self.invalid_dpdk_host, 'ovs', - OVS_VIF_DETAILS) + expected_vif_details) def test_port_binding_create_remote_managed_port(self): pci_vendor_info = 'fake-pci-vendor-info' @@ -206,8 +217,12 @@ class TestPortBinding(base.TestOVNFunctionalBase): port_id = self._create_or_update_port(port_id=port_id, hostname=self.invalid_dpdk_host) + expected_vif_details = copy.deepcopy(VHOSTUSER_VIF_DETAILS) + expected_vif_details.pop('vhostuser_mode') + expected_vif_details.pop('vhostuser_ovs_plug') + expected_vif_details[portbindings.CAP_PORT_FILTER] = True self._verify_vif_details(port_id, self.invalid_dpdk_host, 'ovs', - OVS_VIF_DETAILS) + expected_vif_details) def test_port_binding_update_remote_managed_port(self): port_id = self._create_or_update_port( diff --git a/neutron/tests/unit/fake_resources.py b/neutron/tests/unit/fake_resources.py index c35719ca195..fb01a9fb5bf 100644 --- a/neutron/tests/unit/fake_resources.py +++ b/neutron/tests/unit/fake_resources.py @@ -181,6 +181,7 @@ class FakeOvsdbSbOvnIdl(object): self.get_extport_chassis_from_cms_options = mock.Mock(return_value=[]) self.is_col_present = mock.Mock() self.is_col_present.return_value = False + self.db_find = mock.MagicMock() self.db_set = mock.Mock() self.lookup = mock.MagicMock() self.chassis_list = mock.MagicMock() @@ -856,7 +857,8 @@ class FakeChassis(object): def create(attrs=None, az_list=None, chassis_as_gw=False, bridge_mappings=None, rp_bandwidths=None, rp_inventory_defaults=None, rp_hypervisors=None, - card_serial_number=None, chassis_as_extport=False): + card_serial_number=None, chassis_as_extport=False, + datapath_type=None): cms_opts = [] if az_list: cms_opts.append("%s=%s" % (ovn_const.CMS_OPT_AVAILABILITY_ZONES, @@ -901,6 +903,9 @@ class FakeChassis(object): if bridge_mappings: other_config['ovn-bridge-mappings'] = ','.join(bridge_mappings) + if datapath_type: + other_config[ovn_const.OVN_DATAPATH_TYPE] = datapath_type + chassis_attrs = { 'encaps': [], 'external_ids': '', diff --git a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py index 9ec5d336fdc..0c1a4ed0b5c 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py +++ b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py @@ -46,6 +46,7 @@ from oslo_utils import uuidutils from ovsdbapp.backend.ovs_idl import idlutils from webob import exc +from neutron.common import _constants as n_const from neutron.common import config from neutron.common.ovn import acl as ovn_acl from neutron.common.ovn import constants as ovn_const @@ -76,6 +77,7 @@ from neutron.tests.unit.plugins.ml2 import test_security_group OVN_PROFILE = ovn_const.OVN_PORT_BINDING_PROFILE CLASS_PLACEMENT_REPORT = ('neutron.services.placement_report.plugin.' 'PlacementReportPlugin') +DEFAULT_DP_TYPE = 'system' # For testing, we define "system" as default. OvnRevNumberRow = collections.namedtuple( 'OvnRevNumberRow', ['created_at']) @@ -101,7 +103,7 @@ class MechDriverSetupBase(abc.ABC): self.mock_vp_parents = mock.patch.object( ovn_utils, 'get_virtual_port_parents', return_value=None).start() - def _add_chassis(self, nb_cfg, name=None): + def _add_chassis_private(self, nb_cfg, name=None): chassis_private = mock.Mock() chassis_private.nb_cfg = nb_cfg chassis_private.uuid = uuid.uuid4() @@ -109,26 +111,40 @@ class MechDriverSetupBase(abc.ABC): chassis_private.nb_cfg_timestamp = timeutils.utcnow_ts() * 1000 return chassis_private - def _add_chassis_agent(self, nb_cfg, agent_type, chassis_private=None): - chassis_private = chassis_private or self._add_chassis(nb_cfg) + def _add_chassis(self, name, hostname, external_ids=None, + other_config=None): + external_ids = external_ids or {} + other_config = other_config or {} + return mock.Mock(name=name, hostname=hostname, + external_ids=external_ids, other_config=other_config) + + def _add_chassis_agent(self, nb_cfg, agent_type, chassis_private=None, + hostname=None): + chassis_private = chassis_private or self._add_chassis_private(nb_cfg) + hostname = hostname or chassis_private.name + '_host' if hasattr(chassis_private, 'nb_cfg_timestamp') and isinstance( chassis_private.nb_cfg_timestamp, mock.Mock): del chassis_private.nb_cfg_timestamp - chassis_private.external_ids = {} - chassis_private.other_config = {} + chassis_private.external_ids = { + ovn_const.OVN_AGENT_OVN_BRIDGE: n_const.DEFAULT_BR_INT, + ovn_const.OVN_DATAPATH_TYPE: DEFAULT_DP_TYPE, + } if agent_type == ovn_const.OVN_METADATA_AGENT: chassis_private.external_ids.update({ ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY: nb_cfg, ovn_const.OVN_AGENT_METADATA_ID_KEY: str(uuid.uuid4())}) - chassis_private.chassis = [chassis_private] + chassis_private.chassis = [self._add_chassis(chassis_private.name, + hostname)] return neutron_agent.AgentCache().update(agent_type, chassis_private) - def _add_agent(self, name, nb_cfg_offset=0): + def _add_agent(self, name, nb_cfg_offset=0, hostname=None): + hostname = hostname or name + '_host' nb_cfg = 5 self.mech_driver.nb_ovn.nb_global.nb_cfg = nb_cfg + nb_cfg_offset - chassis = self._add_chassis(nb_cfg, name=name) + chassis_private = self._add_chassis_private(nb_cfg, name=name) return self._add_chassis_agent( - nb_cfg, ovn_const.OVN_CONTROLLER_AGENT, chassis) + nb_cfg, ovn_const.OVN_CONTROLLER_AGENT, + chassis_private=chassis_private, hostname=hostname) class TestOVNMechanismDriverBase(MechDriverSetupBase, @@ -1242,6 +1258,9 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase): self._test_bind_port_failed([]) def _test_bind_port(self, fake_segments): + fake_chassis = fakes.FakeChassis.create(datapath_type=DEFAULT_DP_TYPE) + self.sb_ovn.db_find.return_value.execute.return_value = [ + {'other_config': fake_chassis.other_config}] fake_port = fakes.FakePort.create_one_port().info() fake_host = 'host' fake_port_context = fakes.FakePortContext( @@ -1250,12 +1269,18 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase): neutron_agent.AgentCache().get_agents.assert_called_once_with( {'host': fake_host, 'agent_type': ovn_const.OVN_CONTROLLER_TYPES}) - fake_port_context.set_binding.assert_called_once_with( - fake_segments[0]['id'], - portbindings.VIF_TYPE_OVS, + vif_details = copy.deepcopy( self.mech_driver.vif_details[portbindings.VIF_TYPE_OVS]) + vif_details[ + portbindings.VIF_DETAILS_BRIDGE_NAME] = n_const.DEFAULT_BR_INT + vif_details[portbindings.OVS_DATAPATH_TYPE] = DEFAULT_DP_TYPE + fake_port_context.set_binding.assert_called_once_with( + fake_segments[0]['id'], portbindings.VIF_TYPE_OVS, vif_details) def _test_bind_port_sriov(self, fake_segments): + fake_chassis = fakes.FakeChassis.create(datapath_type=DEFAULT_DP_TYPE) + self.sb_ovn.db_find.return_value.execute.return_value = [ + {'other_config': fake_chassis.other_config}] fake_port = fakes.FakePort.create_one_port( attrs={'binding:vnic_type': 'direct', 'binding:profile': { @@ -1268,12 +1293,18 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase): neutron_agent.AgentCache().get_agents.assert_called_once_with( {'host': fake_host, 'agent_type': ovn_const.OVN_CONTROLLER_TYPES}) - fake_port_context.set_binding.assert_called_once_with( - fake_segments[0]['id'], - portbindings.VIF_TYPE_OVS, + vif_details = copy.deepcopy( self.mech_driver.vif_details[portbindings.VIF_TYPE_OVS]) + vif_details[ + portbindings.VIF_DETAILS_BRIDGE_NAME] = n_const.DEFAULT_BR_INT + vif_details[portbindings.OVS_DATAPATH_TYPE] = DEFAULT_DP_TYPE + fake_port_context.set_binding.assert_called_once_with( + fake_segments[0]['id'], portbindings.VIF_TYPE_OVS, vif_details) def _test_bind_port_virtio_forwarder(self, fake_segments): + fake_chassis = fakes.FakeChassis.create(datapath_type=DEFAULT_DP_TYPE) + self.sb_ovn.db_find.return_value.execute.return_value = [ + {'other_config': fake_chassis.other_config}] fake_port = fakes.FakePort.create_one_port( attrs={'binding:vnic_type': 'virtio-forwarder'}).info() fake_host = 'host' @@ -1281,11 +1312,15 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase): fake_port, fake_host, fake_segments) self.mech_driver.bind_port(fake_port_context) - vif_details = self.mech_driver.\ - vif_details[portbindings.VIF_TYPE_AGILIO_OVS] - vif_details.update({"vhostuser_socket": ovn_utils.ovn_vhu_sockpath( - ovn_conf.get_ovn_vhost_sock_dir(), fake_port['id'])}) - vif_details.update({"vhostuser_mode": "client"}) + vif_details = copy.deepcopy( + self.mech_driver.vif_details[portbindings.VIF_TYPE_AGILIO_OVS]) + vif_details.update({ + 'vhostuser_socket': ovn_utils.ovn_vhu_sockpath( + ovn_conf.get_ovn_vhost_sock_dir(), fake_port['id']), + 'vhostuser_mode': 'client', + portbindings.VIF_DETAILS_BRIDGE_NAME: n_const.DEFAULT_BR_INT, + portbindings.OVS_DATAPATH_TYPE: DEFAULT_DP_TYPE, + }) neutron_agent.AgentCache().get_agents.assert_called_once_with( {'host': fake_host, @@ -1312,6 +1347,9 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase): attrs={'hostname': fake_smartnic_dpu}, card_serial_number=fake_serial) + fake_chassis = fakes.FakeChassis.create(datapath_type=DEFAULT_DP_TYPE) + self.sb_ovn.db_find.return_value.execute.return_value = [ + {'other_config': fake_chassis.other_config}] self.sb_ovn.get_chassis_by_card_serial_from_cms_options.\ return_value = ch_smartnic_dpu fake_host = 'host' @@ -1321,12 +1359,18 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase): neutron_agent.AgentCache().get_agents.assert_called_once_with( {'host': fake_smartnic_dpu, 'agent_type': ovn_const.OVN_CONTROLLER_TYPES}) - fake_port_context.set_binding.assert_called_once_with( - fake_segments[0]['id'], - portbindings.VIF_TYPE_OVS, + vif_details = copy.deepcopy( self.mech_driver.vif_details[portbindings.VIF_TYPE_OVS]) + vif_details[ + portbindings.VIF_DETAILS_BRIDGE_NAME] = n_const.DEFAULT_BR_INT + vif_details[portbindings.OVS_DATAPATH_TYPE] = DEFAULT_DP_TYPE + fake_port_context.set_binding.assert_called_once_with( + fake_segments[0]['id'], portbindings.VIF_TYPE_OVS, vif_details) def test_bind_port_vdpa(self): + fake_chassis = fakes.FakeChassis.create(datapath_type=DEFAULT_DP_TYPE) + self.sb_ovn.db_find.return_value.execute.return_value = [ + {'other_config': fake_chassis.other_config}] segment_attrs = {'network_type': 'geneve', 'physical_network': None, 'segmentation_id': 1023} @@ -1343,10 +1387,13 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase): neutron_agent.AgentCache().get_agents.assert_called_once_with( {'host': fake_host, 'agent_type': ovn_const.OVN_CONTROLLER_TYPES}) - fake_port_context.set_binding.assert_called_once_with( - fake_segments[0]['id'], - portbindings.VIF_TYPE_OVS, + vif_details = copy.deepcopy( self.mech_driver.vif_details[portbindings.VIF_TYPE_OVS]) + vif_details[ + portbindings.VIF_DETAILS_BRIDGE_NAME] = n_const.DEFAULT_BR_INT + vif_details[portbindings.OVS_DATAPATH_TYPE] = DEFAULT_DP_TYPE + fake_port_context.set_binding.assert_called_once_with( + fake_segments[0]['id'], portbindings.VIF_TYPE_OVS, vif_details) def test_bind_port_geneve(self): segment_attrs = {'network_type': 'geneve', @@ -2374,7 +2421,7 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase): mock_notify_dhcp.assert_called_with(fake_port['id']) def test_agent_alive_true(self): - chassis_private = self._add_chassis(5) + chassis_private = self._add_chassis_private(5) for agent_type in (ovn_const.OVN_CONTROLLER_AGENT, ovn_const.OVN_METADATA_AGENT): self.mech_driver.nb_ovn.nb_global.nb_cfg = 5 @@ -2386,7 +2433,7 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase): # Agent should be reported as alive when the nb_cfg delta is 1 # even if the last update time was old enough. nb_cfg = 5 - chassis_private = self._add_chassis(nb_cfg) + chassis_private = self._add_chassis_private(nb_cfg) for agent_type in (ovn_const.OVN_CONTROLLER_AGENT, ovn_const.OVN_METADATA_AGENT): self.mech_driver.nb_ovn.nb_global.nb_cfg = nb_cfg + 1 @@ -2401,7 +2448,7 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase): def test_agent_alive_not_timed_out(self): nb_cfg = 3 - chassis_private = self._add_chassis(nb_cfg) + chassis_private = self._add_chassis_private(nb_cfg) for agent_type in (ovn_const.OVN_CONTROLLER_AGENT, ovn_const.OVN_METADATA_AGENT): self.mech_driver.nb_ovn.nb_global.nb_cfg = nb_cfg + 2 @@ -2412,7 +2459,7 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase): def test_agent_alive_timed_out(self): nb_cfg = 3 - chassis_private = self._add_chassis(nb_cfg) + chassis_private = self._add_chassis_private(nb_cfg) for agent_type in (ovn_const.OVN_CONTROLLER_AGENT, ovn_const.OVN_METADATA_AGENT): self.mech_driver.nb_ovn.nb_global.nb_cfg = nb_cfg + 2 @@ -2427,7 +2474,7 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase): def test_agent_with_nb_cfg_timestamp_timeout(self): nb_cfg = 3 - chassis_private = self._add_chassis(nb_cfg) + chassis_private = self._add_chassis_private(nb_cfg) self.mech_driver.nb_ovn.nb_global.nb_cfg = nb_cfg + 2 updated_at = (timeutils.utcnow_ts() - cfg.CONF.agent_down_time - 1 @@ -2441,7 +2488,7 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase): def test_agent_with_nb_cfg_timestamp_not_timeout(self): nb_cfg = 3 - chassis_private = self._add_chassis(nb_cfg) + chassis_private = self._add_chassis_private(nb_cfg) self.mech_driver.nb_ovn.nb_global.nb_cfg = nb_cfg + 2 updated_at = timeutils.utcnow_ts() * 1000 diff --git a/neutron/tests/unit/plugins/ml2/drivers/ovn/test_db_migration.py b/neutron/tests/unit/plugins/ml2/drivers/ovn/test_db_migration.py index a14f3feb290..423e9337b6a 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/ovn/test_db_migration.py +++ b/neutron/tests/unit/plugins/ml2/drivers/ovn/test_db_migration.py @@ -11,7 +11,7 @@ # 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 copy from unittest import mock from neutron_lib.api.definitions import portbindings as pb @@ -21,6 +21,7 @@ from neutron_lib.db import api as db_api from neutron_lib import exceptions from oslo_utils import uuidutils +from neutron.common import _constants as n_const from neutron.db.models.plugins.ml2 import geneveallocation from neutron.db.models.plugins.ml2 import vxlanallocation from neutron.objects import ports as port_obj @@ -48,11 +49,12 @@ class TestMigrateNeutronDatabaseToOvn( for vif_details in vif_details_list: port = self._make_port(self.fmt, network_id)['port'] - port_o = port_obj.PortBinding.get_object( - ctx, port_id=port['id'], host='') - port_o.vif_type = 'ovs' - port_o.vif_details = vif_details - port_o.update() + with db_api.CONTEXT_WRITER.using(ctx): + port_o = port_obj.PortBinding.get_object( + ctx, port_id=port['id'], host='') + port_o.vif_type = 'ovs' + port_o.vif_details = vif_details + port_o.update() for i in range(1, 4): port = self._make_port(self.fmt, network_id)['port'] @@ -152,14 +154,10 @@ class TestMigrateNeutronDatabaseToOvn( {"foo": "bar"}, {}, ] - expected_vif_details = [ - {pb.CAP_PORT_FILTER: "true", - pb.OVS_HYBRID_PLUG: "true", - pb.VIF_DETAILS_CONNECTIVITY: pb.CONNECTIVITY_L2}, - {pb.CAP_PORT_FILTER: "true"}, - {"foo": "bar"}, - {}, - ] + expected_vif_details = copy.deepcopy(vif_details_list) + for vif_detail in expected_vif_details: + vif_detail[pb.VIF_DETAILS_BRIDGE_NAME] = n_const.DEFAULT_BR_INT + expected_vif_details.append({}) self._create_ml2_ovs_test_resources(vif_details_list) db_migration.migrate_neutron_database_to_ovn() diff --git a/releasenotes/notes/ovn-vif-details-bridge-name-and-datapath-type-d2bd5b438118355f.yaml b/releasenotes/notes/ovn-vif-details-bridge-name-and-datapath-type-d2bd5b438118355f.yaml new file mode 100644 index 00000000000..365c436477d --- /dev/null +++ b/releasenotes/notes/ovn-vif-details-bridge-name-and-datapath-type-d2bd5b438118355f.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + [`bug 2045889 `_] + The ports bound to ML2/OVN now contain the OVS bridge name and datapath + type in the VIF details dictionary. NOTE: in the ML2/OVS to ML2/OVN + migration, the local host OVN bridge (integration bridge) per port is not + known; "br-int" will be used by default (that value is rarely changed).