Merge "[OVN] Add vnic_type and binding profile capabilities to LSP info"

This commit is contained in:
Zuul 2023-01-24 02:16:44 +00:00 committed by Gerrit Code Review
commit 7fa5346e05
10 changed files with 179 additions and 66 deletions

View File

@ -26,6 +26,8 @@ OVN_NETWORK_MTU_EXT_ID_KEY = 'neutron:mtu'
OVN_PORT_NAME_EXT_ID_KEY = 'neutron:port_name'
OVN_PORT_EXT_ID_KEY = 'neutron:port_id'
OVN_PORT_FIP_EXT_ID_KEY = 'neutron:port_fip'
OVN_PORT_BP_CAPABILITIES_KEY = 'neutron:port_capabilities'
OVN_PORT_VNIC_TYPE_KEY = 'neutron:vnic_type'
OVN_ROUTER_NAME_EXT_ID_KEY = 'neutron:router_name'
OVN_ROUTER_ID_EXT_ID_KEY = 'neutron:router_id'
OVN_AZ_HINTS_EXT_ID_KEY = 'neutron:availability_zone_hints'

View File

@ -55,6 +55,8 @@ AddrPairsDiff = collections.namedtuple(
PortExtraDHCPValidation = collections.namedtuple(
'PortExtraDHCPValidation', ['failed', 'invalid_ipv4', 'invalid_ipv6'])
BPInfo = collections.namedtuple(
'BPInfo', ['bp_param', 'vnic_type', 'capabilities'])
class OvsdbClientCommand(object):
@ -309,10 +311,17 @@ def is_security_groups_enabled(port):
def validate_and_get_data_from_binding_profile(port):
"""Validate the port binding profile
:param port: (dict) Neutron port dictionary.
:returns: (namedtuple BPInfo: dict, string, list) a tuple with the
dictionary of the port profile, the VNIC type and a list of port
capabilities.
"""
if (constants.OVN_PORT_BINDING_PROFILE not in port or
not validators.is_attr_set(
port[constants.OVN_PORT_BINDING_PROFILE])):
return {}
BPInfo({}, None, [])
param_set = {}
param_dict = {}
vnic_type = port.get(portbindings.VNIC_TYPE, portbindings.VNIC_NORMAL)
@ -354,7 +363,7 @@ def validate_and_get_data_from_binding_profile(port):
break
if not param_dict:
return {}
return BPInfo({}, vnic_type, capabilities)
# With this example param_set:
#
@ -406,7 +415,7 @@ def validate_and_get_data_from_binding_profile(port):
'an integer between 0 and 4095, inclusive') % tag
raise n_exc.InvalidInput(error_message=msg)
return param_dict
return BPInfo(param_dict, vnic_type, capabilities)
def is_dhcp_options_ignored(subnet):

View File

@ -120,6 +120,14 @@ class PortBinding(PortBindingBase):
cls.db_model.status == constants.INACTIVE)
return context.session.query(cls.db_model).filter(_filter).all()
@classmethod
@db_api.CONTEXT_READER
def get_port_binding_by_vnic_type(cls, context, vnic_type):
"""Returns the port binding filtering by VNIC type."""
query = context.session.query(cls.db_model)
query = query.filter(cls.db_model.vnic_type == vnic_type)
return query.all()
@base.NeutronObjectRegistry.register
class DistributedPortBinding(PortBindingBase):

View File

@ -28,6 +28,7 @@ from neutron_lib.db import api as db_api
from neutron_lib import exceptions as n_exc
from oslo_config import cfg
from oslo_log import log
from oslo_serialization import jsonutils
from oslo_utils import timeutils
from ovsdbapp.backend.ovs_idl import event as row_event
@ -918,6 +919,38 @@ class DBInconsistenciesPeriodics(SchemaAwarePeriodicsBase):
raise periodics.NeverAgain()
# TODO(ralonsoh): Remove this in the Antelope+4 cycle
@periodics.periodic(spacing=600, run_immediately=True)
def add_vnic_type_and_pb_capabilities_to_lsp(self):
"""Add the port VNIC type and port binding capabilities to the LSP.
This is needed to know if a port has hardware offload capabilities.
This method is only updating those ports with VNIC type direct, in
order to minimize the load impact of this method when updating the OVN
database. Within the patch that adds this maintenance method, it has
been added to the LSP the VNIC type and the port binding capabilities.
To implement LP#1998608, only direct ports are needed.
"""
if not self.has_lock:
return
port_bindings = ports_obj.PortBinding.get_port_binding_by_vnic_type(
n_context.get_admin_context(), portbindings.VNIC_DIRECT)
with self._nb_idl.transaction(check_error=True) as txn:
for pb in port_bindings:
profile = jsonutils.loads(pb.profile)
capabilities = profile.get(ovn_const.PORT_CAP_PARAM, [])
external_ids = {
ovn_const.OVN_PORT_VNIC_TYPE_KEY: portbindings.VNIC_DIRECT,
ovn_const.OVN_PORT_BP_CAPABILITIES_KEY:
';'.join(capabilities)
}
txn.add(self._nb_idl.set_lswitch_port(
lport_name=pb.port_id, if_exists=True,
external_ids=external_ids))
raise periodics.NeverAgain()
class HashRingHealthCheckPeriodics(object):

View File

@ -73,6 +73,8 @@ OvnPortInfo = collections.namedtuple(
"security_group_ids",
"address4_scope_id",
"address6_scope_id",
"vnic_type",
"capabilities",
],
)
@ -263,18 +265,18 @@ class OVNClient(object):
return port_context.host if port_context else port.get(
portbindings.HOST_ID, '')
binding_prof = utils.validate_and_get_data_from_binding_profile(port)
if ovn_const.VIF_DETAILS_CARD_SERIAL_NUMBER in binding_prof:
bp_info = (
utils.validate_and_get_data_from_binding_profile(port))
if ovn_const.VIF_DETAILS_CARD_SERIAL_NUMBER in bp_info.bp_param:
return self._sb_idl.get_chassis_by_card_serial_from_cms_options(
binding_prof[
bp_info.bp_param[
ovn_const.VIF_DETAILS_CARD_SERIAL_NUMBER]).hostname
return ''
def _get_port_options(self, port):
context = n_context.get_admin_context()
binding_prof = utils.validate_and_get_data_from_binding_profile(port)
vnic_type = port.get(portbindings.VNIC_TYPE, portbindings.VNIC_NORMAL)
vtep_physical_switch = binding_prof.get('vtep-physical-switch')
bp_info = utils.validate_and_get_data_from_binding_profile(port)
vtep_physical_switch = bp_info.bp_param.get('vtep-physical-switch')
port_type = ''
cidrs = ''
@ -283,7 +285,7 @@ class OVNClient(object):
dhcpv4_options = self._get_port_dhcp_options(port, const.IP_VERSION_4)
dhcpv6_options = self._get_port_dhcp_options(port, const.IP_VERSION_6)
if vtep_physical_switch:
vtep_logical_switch = binding_prof.get('vtep-logical-switch')
vtep_logical_switch = bp_info.bp_param.get('vtep-logical-switch')
port_type = 'vtep'
options = {'vtep-physical-switch': vtep_physical_switch,
'vtep-logical-switch': vtep_logical_switch}
@ -293,8 +295,8 @@ class OVNClient(object):
port_security = []
else:
options = {}
parent_name = binding_prof.get('parent_name', [])
tag = binding_prof.get('tag', [])
parent_name = bp_info.bp_param.get('parent_name', [])
tag = bp_info.bp_param.get('tag', [])
address = port['mac_address']
ip_subnets = port.get('fixed_ips', [])
@ -394,8 +396,8 @@ class OVNClient(object):
# HA Chassis Group will bind the port to the highest
# priority Chassis
if port_type != ovn_const.LSP_TYPE_EXTERNAL:
if (vnic_type == portbindings.VNIC_REMOTE_MANAGED and
ovn_const.VIF_DETAILS_PF_MAC_ADDRESS in binding_prof):
if (bp_info.vnic_type == portbindings.VNIC_REMOTE_MANAGED and
ovn_const.VIF_DETAILS_PF_MAC_ADDRESS in bp_info.bp_param):
port_net = self._plugin.get_network(
context, port['network_id'])
options.update({
@ -403,10 +405,10 @@ class OVNClient(object):
ovn_const.LSP_OPTIONS_VIF_PLUG_MTU_REQUEST_KEY: str(
port_net['mtu']),
ovn_const.LSP_OPTIONS_VIF_PLUG_REPRESENTOR_PF_MAC_KEY: (
binding_prof.get(
bp_info.bp_param.get(
ovn_const.VIF_DETAILS_PF_MAC_ADDRESS)),
ovn_const.LSP_OPTIONS_VIF_PLUG_REPRESENTOR_VF_NUM_KEY: str(
binding_prof.get(ovn_const.VIF_DETAILS_VF_NUM))})
bp_info.bp_param.get(ovn_const.VIF_DETAILS_VF_NUM))})
chassis = self.determine_bind_host(port)
if chassis:
# If OVN supports multi-chassis port bindings, use it for live
@ -440,7 +442,8 @@ class OVNClient(object):
return OvnPortInfo(port_type, options, addresses, port_security,
parent_name, tag, dhcpv4_options, dhcpv6_options,
cidrs.strip(), device_owner, sg_ids,
address4_scope_id, address6_scope_id
address4_scope_id, address6_scope_id,
bp_info.vnic_type, bp_info.capabilities
)
def sync_ha_chassis_group(self, context, network_id, txn):
@ -533,23 +536,25 @@ class OVNClient(object):
def get_external_ids_from_port(self, port):
port_info = self._get_port_options(port)
external_ids = {ovn_const.OVN_PORT_NAME_EXT_ID_KEY: port['name'],
ovn_const.OVN_DEVID_EXT_ID_KEY: port['device_id'],
ovn_const.OVN_PROJID_EXT_ID_KEY: port['project_id'],
ovn_const.OVN_CIDRS_EXT_ID_KEY: port_info.cidrs,
ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY:
port_info.device_owner,
ovn_const.OVN_SUBNET_POOL_EXT_ADDR_SCOPE4_KEY:
port_info.address4_scope_id,
ovn_const.OVN_SUBNET_POOL_EXT_ADDR_SCOPE6_KEY:
port_info.address6_scope_id,
ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY:
utils.ovn_name(port['network_id']),
ovn_const.OVN_SG_IDS_EXT_ID_KEY:
port_info.security_group_ids,
ovn_const.OVN_REV_NUM_EXT_ID_KEY: str(
utils.get_revision_number(
port, ovn_const.TYPE_PORTS))}
external_ids = {
ovn_const.OVN_PORT_NAME_EXT_ID_KEY: port['name'],
ovn_const.OVN_DEVID_EXT_ID_KEY: port['device_id'],
ovn_const.OVN_PROJID_EXT_ID_KEY: port['project_id'],
ovn_const.OVN_CIDRS_EXT_ID_KEY: port_info.cidrs,
ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY: port_info.device_owner,
ovn_const.OVN_SUBNET_POOL_EXT_ADDR_SCOPE4_KEY:
port_info.address4_scope_id,
ovn_const.OVN_SUBNET_POOL_EXT_ADDR_SCOPE6_KEY:
port_info.address6_scope_id,
ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY:
utils.ovn_name(port['network_id']),
ovn_const.OVN_SG_IDS_EXT_ID_KEY: port_info.security_group_ids,
ovn_const.OVN_REV_NUM_EXT_ID_KEY: str(utils.get_revision_number(
port, ovn_const.TYPE_PORTS)),
ovn_const.OVN_PORT_VNIC_TYPE_KEY: port_info.vnic_type,
ovn_const.OVN_PORT_BP_CAPABILITIES_KEY:
';'.join(port_info.capabilities),
}
return port_info, external_ids
def create_port(self, context, port):

View File

@ -588,8 +588,8 @@ class TestValidateAndGetDataFromBindingProfile(base.BaseTestCase):
'parent_name': 'fake-parent-port-uuid',
'tag': 42
}
self.assertDictEqual(
expect,
self.assertEqual(
utils.BPInfo(expect, portbindings.VNIC_NORMAL, []),
utils.validate_and_get_data_from_binding_profile(
{constants.OVN_PORT_BINDING_PROFILE: expect}))
@ -597,8 +597,8 @@ class TestValidateAndGetDataFromBindingProfile(base.BaseTestCase):
'vtep-physical-switch': 'fake-physical-switch-uuid',
'vtep-logical-switch': 'fake-logical-switch-uuid',
}
self.assertDictEqual(
expect,
self.assertEqual(
utils.BPInfo(expect, portbindings.VNIC_NORMAL, []),
utils.validate_and_get_data_from_binding_profile(
{constants.OVN_PORT_BINDING_PROFILE: expect}))
@ -611,8 +611,9 @@ class TestValidateAndGetDataFromBindingProfile(base.BaseTestCase):
}
expect = binding_profile.copy()
del(expect[constants.PORT_CAP_PARAM])
self.assertDictEqual(
expect,
self.assertEqual(
utils.BPInfo(expect, portbindings.VNIC_DIRECT,
[constants.PORT_CAP_SWITCHDEV]),
utils.validate_and_get_data_from_binding_profile(
{portbindings.VNIC_TYPE: portbindings.VNIC_DIRECT,
constants.OVN_PORT_BINDING_PROFILE: binding_profile}))
@ -626,8 +627,9 @@ class TestValidateAndGetDataFromBindingProfile(base.BaseTestCase):
}
expect = binding_profile.copy()
del(expect[constants.PORT_CAP_PARAM])
self.assertDictEqual(
expect,
self.assertEqual(
utils.BPInfo(expect, portbindings.VNIC_DIRECT,
[constants.PORT_CAP_SWITCHDEV]),
utils.validate_and_get_data_from_binding_profile(
{portbindings.VNIC_TYPE: portbindings.VNIC_DIRECT,
constants.OVN_PORT_BINDING_PROFILE: binding_profile}))
@ -640,11 +642,11 @@ class TestValidateAndGetDataFromBindingProfile(base.BaseTestCase):
'pf_mac_address': '00:53:00:00:00:42',
'vf_num': 42,
}
self.assertDictEqual(
self.assertEqual(
utils.BPInfo(expect, portbindings.VNIC_REMOTE_MANAGED, []),
utils.validate_and_get_data_from_binding_profile(
{portbindings.VNIC_TYPE: portbindings.VNIC_REMOTE_MANAGED,
constants.OVN_PORT_BINDING_PROFILE: expect}),
expect)
constants.OVN_PORT_BINDING_PROFILE: expect}))
def test_valid_input_surplus_keys(self):
# Confirm that extra keys are allowed
@ -658,8 +660,9 @@ class TestValidateAndGetDataFromBindingProfile(base.BaseTestCase):
expect = binding_profile.copy()
del(expect[constants.PORT_CAP_PARAM])
del(expect['optional_information_provided_by_nova'])
self.assertDictEqual(
expect,
self.assertEqual(
utils.BPInfo(expect, portbindings.VNIC_DIRECT,
[constants.PORT_CAP_SWITCHDEV]),
utils.validate_and_get_data_from_binding_profile(
{portbindings.VNIC_TYPE: portbindings.VNIC_DIRECT,
constants.OVN_PORT_BINDING_PROFILE: binding_profile}))
@ -667,7 +670,7 @@ class TestValidateAndGetDataFromBindingProfile(base.BaseTestCase):
def test_unknown_profile_items_pruned(self):
# Confirm that unknown profile items are pruned
self.assertEqual(
{},
utils.BPInfo({}, portbindings.VNIC_NORMAL, []),
utils.validate_and_get_data_from_binding_profile(
{constants.OVN_PORT_BINDING_PROFILE: {
'unknown-key': 'unknown-data'}}))
@ -676,16 +679,16 @@ class TestValidateAndGetDataFromBindingProfile(base.BaseTestCase):
expect = {
'key': 'value',
}
self.assertDictEqual(
expect,
self.assertEqual(
utils.BPInfo(expect, self.VNIC_FAKE_NORMAL, []),
utils.validate_and_get_data_from_binding_profile(
{portbindings.VNIC_TYPE: self.VNIC_FAKE_NORMAL,
constants.OVN_PORT_BINDING_PROFILE: expect}))
expect = {
'key': None,
}
self.assertDictEqual(
expect,
self.assertEqual(
utils.BPInfo(expect, self.VNIC_FAKE_NORMAL, []),
utils.validate_and_get_data_from_binding_profile(
{portbindings.VNIC_TYPE: self.VNIC_FAKE_NORMAL,
constants.OVN_PORT_BINDING_PROFILE: expect}))
@ -706,17 +709,17 @@ class TestValidateAndGetDataFromBindingProfile(base.BaseTestCase):
'other_key': 'value',
}
# This param set is valid for VNIC_FAKE_NORMAL with 'other_key' pruned.
expect = binding_profile.copy()
del(expect['other_key'])
self.assertDictEqual(
expect,
expected_bp = binding_profile.copy()
del(expected_bp['other_key'])
self.assertEqual(
utils.BPInfo(expected_bp, self.VNIC_FAKE_NORMAL, []),
utils.validate_and_get_data_from_binding_profile(
{portbindings.VNIC_TYPE: self.VNIC_FAKE_NORMAL,
constants.OVN_PORT_BINDING_PROFILE: binding_profile}))
# It is valid for VNIC_FAKE_OTHER
expect = binding_profile.copy()
self.assertDictEqual(
expect,
expected_bp = binding_profile.copy()
self.assertEqual(
utils.BPInfo(expected_bp, self.VNIC_FAKE_OTHER, []),
utils.validate_and_get_data_from_binding_profile(
{portbindings.VNIC_TYPE: self.VNIC_FAKE_OTHER,
constants.OVN_PORT_BINDING_PROFILE: binding_profile}))
@ -744,7 +747,7 @@ class TestValidateAndGetDataFromBindingProfile(base.BaseTestCase):
'third_key': 'value',
}
self.assertEqual(
{},
utils.BPInfo({}, self.VNIC_FAKE_OTHER, ['fake-capability']),
utils.validate_and_get_data_from_binding_profile(
{portbindings.VNIC_TYPE: self.VNIC_FAKE_OTHER,
constants.OVN_PORT_BINDING_PROFILE: binding_profile}))
@ -757,8 +760,9 @@ class TestValidateAndGetDataFromBindingProfile(base.BaseTestCase):
}
expect = binding_profile.copy()
del(expect[constants.PORT_CAP_PARAM])
self.assertDictEqual(
expect,
self.assertEqual(
utils.BPInfo(expect, self.VNIC_FAKE_OTHER,
[constants.PORT_CAP_SWITCHDEV]),
utils.validate_and_get_data_from_binding_profile(
{portbindings.VNIC_TYPE: self.VNIC_FAKE_OTHER,
constants.OVN_PORT_BINDING_PROFILE: binding_profile}))
@ -775,7 +779,8 @@ class TestValidateAndGetDataFromBindingProfile(base.BaseTestCase):
constants.PORT_CAP_PARAM: [constants.PORT_CAP_SWITCHDEV],
}
self.assertEqual(
{},
utils.BPInfo({}, self.VNIC_FAKE_OTHER,
[constants.PORT_CAP_SWITCHDEV]),
utils.validate_and_get_data_from_binding_profile(
{portbindings.VNIC_TYPE: self.VNIC_FAKE_OTHER,
constants.OVN_PORT_BINDING_PROFILE: binding_profile}))

View File

@ -71,6 +71,23 @@ class PortBindingDbObjectTestCase(obj_test_base.BaseDbObjectTestCase,
# The PB register returned is the INACTIVE one.
self.assertEqual(self.obj_fields[1]['host'], dup_pb[0].host)
def test_get_port_binding_by_vnic_type(self):
self.update_obj_fields({'vnic_type': portbindings.VNIC_NORMAL},
obj_fields=[self.obj_fields[0]])
self.update_obj_fields({'vnic_type': portbindings.VNIC_DIRECT},
obj_fields=[self.obj_fields[1],
self.obj_fields[2]])
for i in range(3):
_obj = self._make_object(self.obj_fields[i])
_obj.create()
for vnic_type, pb_num in [(portbindings.VNIC_NORMAL, 1),
(portbindings.VNIC_DIRECT, 2),
(portbindings.VNIC_MACVTAP, 0)]:
pb = ports.PortBinding.get_port_binding_by_vnic_type(self.context,
vnic_type)
self.assertEqual(pb_num, len(pb))
class DistributedPortBindingIfaceObjTestCase(
obj_test_base.BaseObjectIfaceTestCase):

View File

@ -16,10 +16,12 @@
from unittest import mock
from futurist import periodics
from neutron_lib.api.definitions import portbindings
from neutron_lib import constants as n_const
from neutron_lib import context
from neutron_lib.db import api as db_api
from oslo_config import cfg
from oslo_serialization import jsonutils
from oslo_utils import uuidutils
from neutron.common.ovn import constants
@ -27,6 +29,7 @@ from neutron.common.ovn import utils
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
from neutron.db.models import ovn as ovn_models
from neutron.db import ovn_revision_numbers_db
from neutron.objects import ports as ports_obj
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import maintenance
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_db_sync
from neutron.tests.unit import fake_resources as fakes
@ -831,3 +834,23 @@ class TestDBInconsistenciesPeriodics(testlib_api.SqlTestCaseLight,
self.assertEqual(
2,
nb_idl.delete_static_route.call_count)
@mock.patch.object(ports_obj.PortBinding, 'get_port_binding_by_vnic_type')
def test_add_vnic_type_and_pb_capabilities_to_lsp(self, mock_get_pb):
nb_idl = self.fake_ovn_client._nb_idl
profile = {'capabilities': ['switchdev']}
pb1 = mock.Mock(profile=jsonutils.dumps(profile), port_id='port1')
pb2 = mock.Mock(profile=jsonutils.dumps(profile), port_id='port2')
mock_get_pb.return_value = [pb1, pb2]
self.assertRaises(
periodics.NeverAgain,
self.periodic.add_vnic_type_and_pb_capabilities_to_lsp)
external_ids = {
constants.OVN_PORT_VNIC_TYPE_KEY: portbindings.VNIC_DIRECT,
constants.OVN_PORT_BP_CAPABILITIES_KEY: 'switchdev'}
expected_calls = [mock.call(lport_name='port1', if_exists=True,
external_ids=external_ids),
mock.call(lport_name='port2', if_exists=True,
external_ids=external_ids)]
nb_idl.set_lswitch_port.assert_has_calls(expected_calls)

View File

@ -16,6 +16,7 @@
from unittest import mock
from neutron.common.ovn import constants
from neutron.conf.plugins.ml2 import config as ml2_conf
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_client
from neutron.tests import base
@ -28,6 +29,7 @@ from neutron_lib import constants as const
class TestOVNClientBase(base.BaseTestCase):
def setUp(self):
ml2_conf.register_ml2_plugin_opts()
ovn_conf.register_opts()
super(TestOVNClientBase, self).setUp()
self.nb_idl = mock.MagicMock()
@ -133,6 +135,7 @@ class TestOVNClientDetermineBindHost(TestOVNClientBase):
def test_vnic_remote_managed_unbound_port_no_binding_profile(self):
port = {
portbindings.VNIC_TYPE: portbindings.VNIC_REMOTE_MANAGED,
constants.OVN_PORT_BINDING_PROFILE: {},
}
self.assertEqual(
'',

View File

@ -1800,7 +1800,9 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase):
'ip_address': '10.0.0.55'},
{'subnet_id': 'subnet-2',
'ip_address': '10.0.1.55'},
]}
],
portbindings.PROFILE: {},
}
subnet_ids = [
ip['subnet_id']
for ip in port.get('fixed_ips')
@ -1826,6 +1828,7 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase):
{"subnet_id": "subnet-1", "ip_address": "10.0.0.55"},
{"subnet_id": "subnet-2", "ip_address": "aef0::4"},
],
portbindings.PROFILE: {},
}
subnet_ids = [ip["subnet_id"] for ip in port.get("fixed_ips")]
@ -1910,6 +1913,7 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase):
'network_id': 'foo',
'fixed_ips': [],
portbindings.HOST_ID: 'fake-src',
portbindings.PROFILE: {},
}
with mock.patch.object(
self.mech_driver._ovn_client._sb_idl, 'is_col_present',
@ -1942,7 +1946,9 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase):
'ip_address': '10.0.0.55'},
{'subnet_id': 'subnet-2',
'ip_address': '10.0.1.55'},
]}
],
portbindings.PROFILE: {},
}
subnet_ids = [
ip['subnet_id']
for ip in port.get('fixed_ips')
@ -4292,7 +4298,9 @@ class TestOVNVVirtualPort(OVNMechanismDriverTestCase):
'device_owner': device_owner,
'network_id': self.net['id'],
'fixed_ips': [{'subnet_id': self.subnet['id'],
'ip_address': '10.0.0.55'}]}
'ip_address': '10.0.0.55'}],
portbindings.PROFILE: {},
}
port_info = self.mech_driver._ovn_client._get_port_options(port)
self.assertEqual(ovn_const.LSP_TYPE_VIRTUAL, port_info.type)
self.assertEqual(