[OVN] "Logical_Router" pinned to chassis, OVN L3 scheduler

Pin a "Logical_Router" to a chassis when the gateway network (external
network) is tunnelled. When the external network is tunnelled, the
"Logical_Router_Port" acting as gateway port is not bound to any
chassis (the network has no physical provider network defined).

In that case, the router is pinned to a chassis instead. A
"HA_Chassis_Group" is created per router. The highest "HA_Chassis" of
this group is assigned to the router. If the gateway port is deleted,
the pinned chassis is removed from the "options" field. If the
router is deleted, the "HA_Chassis_Group" is deleted too.

NOTE: in the a chassis belonging to the router "HA_Chassis_Group"
changes, the list of "HA_Chassis" will be updated in
``ChassisEvent.handle_ha_chassis_group_changes``. However, a
"HA_Chassis_Group" change is handled by OVN, when assiged.

But in this case we are using this artifact, as commented before,
to "manually assign" (from core OVN point of view) the highest
priority "HA_Chassis" to the router (this upcoming funcionality
will be implemented in core OVN). A new follow-up patch will be
pushed to provide HA functionality and update the "HA_Chassis"
assigned to the "Logical_Router" when the chassis list changes.

Partial-Bug: #2052821
Change-Id: I33555fc8a8441149b683ae68f1f10548ffb662a6
This commit is contained in:
Rodolfo Alonso Hernandez 2024-02-11 18:50:55 +00:00
parent 4cad0eda59
commit 25a1809964
15 changed files with 413 additions and 163 deletions

View File

@ -28,6 +28,7 @@ from neutron_lib.api import validators
from neutron_lib import constants as const
from neutron_lib import context as n_context
from neutron_lib import exceptions as n_exc
from neutron_lib.plugins import constants as plugin_constants
from neutron_lib.plugins import directory
from neutron_lib.utils import net as n_utils
from oslo_concurrency import processutils
@ -36,7 +37,7 @@ from oslo_log import log
from oslo_serialization import jsonutils
from oslo_utils import netutils
from oslo_utils import strutils
from ovsdbapp.backend.ovs_idl import idlutils
from ovsdbapp.backend.ovs_idl import rowview
from ovsdbapp import constants as ovsdbapp_const
from pecan import util as pecan_util
import tenacity
@ -66,7 +67,7 @@ BPInfo = collections.namedtuple(
HAChassisGroupInfo = collections.namedtuple(
'HAChassisGroupInfo', ['group_name', 'chassis_list', 'az_hints',
'ignore_chassis'])
'ignore_chassis', 'external_ids'])
class OvsdbClientCommand(object):
@ -1047,44 +1048,6 @@ def get_subnets_address_scopes(context, subnets_by_id, fixed_ips, ml2_plugin):
return address4_scope_id, address6_scope_id
def _get_info_for_ha_chassis_group(context, port_id, network_id, sb_idl):
"""Get the common required information to create a HA Chassis Group.
:param context: Neutron API context
:param port_id: The port ID
:param network_id: The network ID
:param sb_idl: OVN SB IDL
:returns: An instance of HAChassisGroupInfo
"""
ignore_chassis = set()
# If there are Chassis marked for hosting external ports create a HA
# Chassis Group per external port, otherwise do it at the network level
chassis_list = sb_idl.get_extport_chassis_from_cms_options()
if chassis_list:
group_name = ovn_extport_chassis_group_name(port_id)
# Check if the port is bound to a chassis and if so, ignore that
# chassis when building the HA Chassis Group to ensure the
# external port is bound to a different chassis than the VM
ignore_chassis = sb_idl.get_chassis_host_for_port(port_id)
LOG.debug('HA Chassis Group %s is based on external port %s '
'(network %s)', group_name, port_id, network_id)
else:
chassis_list = sb_idl.get_gateway_chassis_from_cms_options(
name_only=False)
group_name = ovn_name(network_id)
LOG.debug('HA Chassis Group %s is based on network %s',
group_name, network_id)
# Get the Availability Zones hints
plugin = directory.get_plugin()
az_hints = common_utils.get_az_hints(
plugin.get_network(context, network_id))
return HAChassisGroupInfo(
group_name=group_name, chassis_list=chassis_list, az_hints=az_hints,
ignore_chassis=ignore_chassis)
def _filter_candidates_for_ha_chassis_group(hcg_info):
"""Filter a list of chassis candidates for a given HA Chassis Group.
@ -1112,40 +1075,39 @@ def _filter_candidates_for_ha_chassis_group(hcg_info):
return candidates
def sync_ha_chassis_group(context, port_id, network_id, nb_idl, sb_idl, txn):
def _sync_ha_chassis_group(nb_idl, hcg_info, txn):
"""Return the UUID of the HA Chassis Group or the HA Chassis Group cmd.
Given the Neutron Network ID, this method will return (or create
and then return) the appropriate HA Chassis Group the external
port (in that network) needs to be associated with.
This method will return (or create and then return) the appropriate HA
Chassis Group for the resource specified in ``HAChassisGroupInfo``,
which can be generated from a network or a router.
:param context: Neutron API context
:param port_id: The port ID
:param network_id: The network ID
:param nb_idl: OVN NB IDL
:param sb_idl: OVN SB IDL
:param hcg_info: HA Chassis Group information named tuple
(``HAChassisGroupInfo``)
:param txn: The ovsdbapp transaction object
:returns: The HA Chassis Group UUID or the HA Chassis Group command object
:returns: The HA Chassis Group UUID or the HA Chassis Group command object,
The name of the Chassis with the highest priority (could be None)
"""
# If there are Chassis marked for hosting external ports create a HA
# Chassis Group per external port, otherwise do it at the network level
hcg_info = _get_info_for_ha_chassis_group(context, port_id, network_id,
sb_idl)
candidates = _filter_candidates_for_ha_chassis_group(hcg_info)
# Try to get the HA Chassis Group or create if it doesn't exist
ha_ch_grp = ha_ch_grp_cmd = None
try:
ha_ch_grp = nb_idl.ha_chassis_group_get(
hcg_info.group_name).execute(check_error=True)
except idlutils.RowNotFound:
ext_ids = {constants.OVN_AZ_HINTS_EXT_ID_KEY: ','.join(
hcg_info.az_hints)}
ha_ch_grp_cmd = txn.add(nb_idl.ha_chassis_group_add(
hcg_info.group_name, may_exist=True, external_ids=ext_ids))
ha_ch_grp_cmd = txn.add(nb_idl.ha_chassis_group_add(
hcg_info.group_name, may_exist=True,
external_ids=hcg_info.external_ids))
if isinstance(ha_ch_grp_cmd.result, rowview.RowView):
# The HA chassis group existed before this transaction.
ha_ch_grp = ha_ch_grp_cmd.result
else:
# The HA chassis group is being created in this transaction.
ha_ch_grp = None
max_chassis_number = constants.MAX_CHASSIS_IN_HA_GROUP
priority = constants.HA_CHASSIS_GROUP_HIGHEST_PRIORITY
high_prio_ch_name = None
# Check if the HA Chassis Group existed before. If so, re-calculate
# the canditates in case something changed and keep the highest priority
@ -1166,6 +1128,7 @@ def sync_ha_chassis_group(context, port_id, network_id, nb_idl, sb_idl, txn):
if (high_prio_ch and
high_prio_ch.chassis_name in candidates):
# If found, keep it as the highest priority chassis in the group
high_prio_ch_name = high_prio_ch.chassis_name
txn.add(nb_idl.ha_chassis_group_add_chassis(
hcg_info.group_name, high_prio_ch.chassis_name,
priority=priority))
@ -1186,11 +1149,68 @@ def sync_ha_chassis_group(context, port_id, network_id, nb_idl, sb_idl, txn):
txn.add(nb_idl.ha_chassis_group_add_chassis(
hcg_info.group_name, ch, priority=priority))
priority -= 1
if not high_prio_ch_name:
high_prio_ch_name = ch
LOG.info('HA Chassis Group %s synchronized', hcg_info.group_name)
LOG.info('HA Chassis Group %s synchronized; highest priority chassis %s',
hcg_info.group_name, high_prio_ch_name)
# Return the existing register UUID or the HA chassis group creation
# command (see ovsdbapp ``HAChassisGroupAddChassisCommand`` class).
return ha_ch_grp.uuid if ha_ch_grp else ha_ch_grp_cmd
return ha_ch_grp.uuid if ha_ch_grp else ha_ch_grp_cmd, high_prio_ch_name
@ovn_context(idl_var_name='nb_idl')
def sync_ha_chassis_group_router(context, nb_idl, sb_idl, router_id, txn):
"""Syncs the HA Chassis Group for a given router"""
chassis_list = sb_idl.get_gateway_chassis_from_cms_options(
name_only=False)
group_name = ovn_name(router_id)
LOG.debug('HA Chassis Group %s is based on router %s',
group_name, router_id)
plugin = directory.get_plugin(plugin_constants.L3)
resource = plugin.get_router(context, router_id,
fields=['availability_zone_hints'])
az_hints = common_utils.get_az_hints(resource)
external_ids = {constants.OVN_AZ_HINTS_EXT_ID_KEY: ','.join(az_hints),
constants.OVN_ROUTER_ID_EXT_ID_KEY: router_id}
hcg_info = HAChassisGroupInfo(
group_name=group_name, chassis_list=chassis_list, az_hints=az_hints,
ignore_chassis=set(), external_ids=external_ids)
return _sync_ha_chassis_group(nb_idl, hcg_info, txn)
@ovn_context(idl_var_name='nb_idl')
def sync_ha_chassis_group_network(context, nb_idl, sb_idl, port_id,
network_id, txn):
"""Syncs the HA Chassis Group for a given network"""
# If there are Chassis marked for hosting external ports create a HA
# Chassis Group per external port, otherwise do it at the network
# level
chassis_list = sb_idl.get_extport_chassis_from_cms_options()
if chassis_list:
group_name = ovn_extport_chassis_group_name(port_id)
# Check if the port is bound to a chassis and if so, ignore that
# chassis when building the HA Chassis Group to ensure the
# external port is bound to a different chassis than the VM
ignore_chassis = sb_idl.get_chassis_host_for_port(port_id)
LOG.debug('HA Chassis Group %s is based on external port %s '
'(network %s)', group_name, port_id, network_id)
else:
chassis_list = sb_idl.get_gateway_chassis_from_cms_options(
name_only=False)
group_name = ovn_name(network_id)
ignore_chassis = set()
LOG.debug('HA Chassis Group %s is based on network %s',
group_name, network_id)
plugin = directory.get_plugin()
resource = plugin.get_network(context, network_id)
az_hints = common_utils.get_az_hints(resource)
external_ids = {constants.OVN_AZ_HINTS_EXT_ID_KEY: ','.join(az_hints)}
hcg_info = HAChassisGroupInfo(
group_name=group_name, chassis_list=chassis_list, az_hints=az_hints,
ignore_chassis=ignore_chassis, external_ids=external_ids)
return _sync_ha_chassis_group(nb_idl, hcg_info, txn)
def get_port_type_virtual_and_parents(subnets_by_id, fixed_ips, network_id,

View File

@ -1195,9 +1195,9 @@ class OVNMechanismDriver(api.MechanismDriver):
self.sb_ovn.get_extport_chassis_from_cms_options()):
try:
with self.nb_ovn.transaction(check_error=True) as txn:
ovn_utils.sync_ha_chassis_group(
admin_context, db_port['id'], db_port['network_id'],
self.nb_ovn, self.sb_ovn, txn)
ovn_utils.sync_ha_chassis_group_network(
admin_context, self.nb_ovn, self.sb_ovn,
db_port['id'], db_port['network_id'], txn)
except Exception as e:
LOG.error('Error while syncing the HA Chassis Group for the '
'external port %s during set port status up. '

View File

@ -418,6 +418,17 @@ class ScheduleNewGatewayCommand(command.BaseCommand):
*_add_gateway_chassis(self.api, txn, self.g_name, chassis))
class LrDelCommand(ovn_nb_commands.LrDelCommand):
def run_idl(self, txn):
super().run_idl(txn)
try:
hcg = self.api.lookup('HA_Chassis_Group', self.router)
hcg.delete()
except idlutils.RowNotFound:
pass
class AddLRouterPortCommand(command.BaseCommand):
def __init__(self, api, name, lrouter, may_exist, **columns):
super(AddLRouterPortCommand, self).__init__(api)
@ -975,6 +986,15 @@ class DeleteLRouterExtGwCommand(command.BaseCommand):
lrouter.delvalue('nat', nat)
nat.delete()
# Remove the router pinning to a chassis (if any).
lrouter.delkey('options', 'chassis')
# Remove the HA_Chassis_Group of the router (if any).
hcg = self.api.lookup('HA_Chassis_Group',
lrouter.name, default=None)
if hcg:
hcg.delete()
for gw_port in self.api.get_lrouter_gw_ports(lrouter.name):
lrouter.delvalue('ports', gw_port)

View File

@ -428,6 +428,11 @@ class OvsdbNbOvnIdl(nb_impl_idl.OvnNbApiIdlImpl, Backend):
return cmd.UpdateLRouterCommand(self, name,
if_exists, **columns)
# This method overrides the parent class ``nb_impl_idl.OvnNbApiIdlImpl``
# implementation.
def lr_del(self, router, if_exists=False):
return cmd.LrDelCommand(self, router, if_exists=if_exists)
def add_lrouter_port(self, name, lrouter, may_exist=False, **columns):
return cmd.AddLRouterPortCommand(self, name, lrouter,
may_exist, **columns)

View File

@ -558,9 +558,9 @@ class DBInconsistenciesPeriodics(SchemaAwarePeriodicsBase):
network_id = port.external_ids[
ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY].replace(
ovn_const.OVN_NAME_PREFIX, '')
ha_ch_grp = utils.sync_ha_chassis_group(
context, port.name, network_id, self._nb_idl,
self._sb_idl, txn)
ha_ch_grp, high_prio_ch = utils.sync_ha_chassis_group_network(
context, self._nb_idl, self._sb_idl, port.name,
network_id, txn)
txn.add(self._nb_idl.set_lswitch_port(
port.name, ha_chassis_group=ha_ch_grp))

View File

@ -564,9 +564,10 @@ class OVNClient(object):
if (self.is_external_ports_supported() and
port_info.type == ovn_const.LSP_TYPE_EXTERNAL):
kwargs['ha_chassis_group'] = utils.sync_ha_chassis_group(
context, port['id'], port['network_id'], self._nb_idl,
self._sb_idl, txn)
kwargs['ha_chassis_group'], _ = (
utils.sync_ha_chassis_group_network(
context, self._nb_idl, self._sb_idl, port['id'],
port['network_id'], txn))
# NOTE(mjozefcz): Do not set addresses if the port is not
# bound, has no device_owner and it is OVN LB VIP port.
@ -689,10 +690,10 @@ class OVNClient(object):
if self.is_external_ports_supported():
if port_info.type == ovn_const.LSP_TYPE_EXTERNAL:
columns_dict['ha_chassis_group'] = (
utils.sync_ha_chassis_group(
context, port['id'], port['network_id'],
self._nb_idl, self._sb_idl, txn))
columns_dict['ha_chassis_group'], _ = (
utils.sync_ha_chassis_group_network(
context, self._nb_idl, self._sb_idl, port['id'],
port['network_id'], txn))
else:
# Clear the ha_chassis_group field
columns_dict['ha_chassis_group'] = []
@ -1714,8 +1715,7 @@ class OVNClient(object):
networks, ipv6_ra_configs = (
self._get_nets_and_ipv6_ra_confs_for_router_port(context, port))
lrouter_port_name = utils.ovn_lrouter_port_name(port['id'])
is_gw_port = const.DEVICE_OWNER_ROUTER_GW == port.get(
'device_owner')
is_gw_port = const.DEVICE_OWNER_ROUTER_GW == port.get('device_owner')
columns = {}
columns['options'] = self._gen_router_port_options(port)
@ -1738,17 +1738,32 @@ class OVNClient(object):
port_net = self._plugin.get_network(
n_context.get_admin_context(), port['network_id'])
physnet = self._get_physnet(port_net)
az_hints = common_utils.get_az_hints(router)
commands.append(
self._nb_idl.schedule_new_gateway(lrouter_port_name,
self._sb_idl,
lrouter, self._l3_plugin,
physnet, az_hints))
if physnet is None:
# The external network is tunnelled, pin the router to a
# chassis.
_, selected_chassis = utils.sync_ha_chassis_group_router(
context, self._nb_idl, self._sb_idl, router['id'], txn)
if selected_chassis:
options = {'chassis': selected_chassis}
commands.append(self._nb_idl.db_set(
'Logical_Router', lrouter, ('options', options)))
else:
LOG.info('Router %s is not pinned to any gateway chassis',
router['id'])
else:
# VLAN/flat network with a physical network, bind the LRP to
# a chassis using the OVN L3 scheduler.
az_hints = common_utils.get_az_hints(router)
commands.append(
self._nb_idl.schedule_new_gateway(lrouter_port_name,
self._sb_idl,
lrouter, self._l3_plugin,
physnet, az_hints))
commands.append(
self._nb_idl.set_lrouter_port_in_lswitch_port(
port['id'], lrouter_port_name, is_gw_port=is_gw_port,
lsp_address=lsp_address))
self._transaction(commands, txn=txn)
def create_router_port(self, context, router_id, router_interface):
@ -2098,9 +2113,9 @@ class OVNClient(object):
network_id = extport.external_ids[
ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY].replace(
ovn_const.OVN_NAME_PREFIX, '')
utils.sync_ha_chassis_group(
context, port_id, network_id, self._nb_idl,
self._sb_idl, txn)
utils.sync_ha_chassis_group_network(
context, self._nb_idl, self._sb_idl, port_id, network_id,
txn)
elif extport_list:
# If there's no dedicated chassis for external ports, there will
# be 1 HA Chassis Group per network, so the sync is at the network
@ -2110,8 +2125,8 @@ class OVNClient(object):
network_id = extport_list[0].external_ids[
ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY].replace(
ovn_const.OVN_NAME_PREFIX, '')
utils.sync_ha_chassis_group(
context, port_id, network_id, self._nb_idl, self._sb_idl, txn)
utils.sync_ha_chassis_group_network(
context, self._nb_idl, self._sb_idl, port_id, network_id, txn)
def update_network(self, context, network, original_network=None):
lswitch_name = utils.ovn_name(network['id'])

View File

@ -67,7 +67,7 @@ class TestCreateNeutronPgDrop(base.TestOVNFunctionalBase):
class TestSyncHaChassisGroup(base.TestOVNFunctionalBase):
def test_sync_ha_chassis_group(self):
def test_sync_ha_chassis_group_network(self):
net = self._make_network(self.fmt, 'n1', True)['network']
port_id = 'fake-port-id'
hcg_name = utils.ovn_name(net['id'])
@ -78,9 +78,9 @@ class TestSyncHaChassisGroup(base.TestOVNFunctionalBase):
self.add_fake_chassis('host3')
with self.nb_api.transaction(check_error=True) as txn:
utils.sync_ha_chassis_group(
self.context, port_id, net['id'], self.nb_api,
self.sb_api, txn)
utils.sync_ha_chassis_group_network(
self.context, self.nb_api, self.sb_api,
port_id, net['id'], txn)
ha_chassis = self.nb_api.db_find('HA_Chassis').execute(
check_error=True)
@ -101,9 +101,9 @@ class TestSyncHaChassisGroup(base.TestOVNFunctionalBase):
# HA Chassis Group register but will update the "ha_chassis" list.
self.del_fake_chassis(chassis2)
with self.nb_api.transaction(check_error=True) as txn:
utils.sync_ha_chassis_group(
self.context, port_id, net['id'], self.nb_api,
self.sb_api, txn)
utils.sync_ha_chassis_group_network(
self.context, self.nb_api, self.sb_api, port_id,
net['id'], txn)
ha_chassis = self.nb_api.db_find('HA_Chassis').execute(
check_error=True)
@ -118,7 +118,7 @@ class TestSyncHaChassisGroup(base.TestOVNFunctionalBase):
ha_chassis_ret = str(hcg.ha_chassis[0].uuid)
self.assertEqual(ha_chassis_exp, ha_chassis_ret)
def test_sync_ha_chassis_group_extport(self):
def test_sync_ha_chassis_group_network_extport(self):
# Create a network and an external port
net = self._make_network(self.fmt, 'n1', True)['network']
port_data = {
@ -138,9 +138,9 @@ class TestSyncHaChassisGroup(base.TestOVNFunctionalBase):
# Invoke the sync method
with self.nb_api.transaction(check_error=True) as txn:
utils.sync_ha_chassis_group(
self.context, port['id'], net['id'], self.nb_api,
self.sb_api, txn)
utils.sync_ha_chassis_group_network(
self.context, self.nb_api, self.sb_api, port['id'],
net['id'], txn)
# Assert only the eligible chassis are present in HA Chassis
ha_chassis = self.nb_api.db_find('HA_Chassis').execute(
@ -165,9 +165,9 @@ class TestSyncHaChassisGroup(base.TestOVNFunctionalBase):
# the existing HA Chassis Group but only update the "ha_chassis" list
self.del_fake_chassis(chassis2)
with self.nb_api.transaction(check_error=True) as txn:
utils.sync_ha_chassis_group(
self.context, port['id'], net['id'], self.nb_api,
self.sb_api, txn)
utils.sync_ha_chassis_group_network(
self.context, self.nb_api, self.sb_api, port['id'],
net['id'], txn)
# Assert the chassis deletion reflects in the HA Chassis and
# HA Chassis Group

View File

@ -28,6 +28,7 @@ from ovsdbapp.tests.functional import base
from ovsdbapp.tests import utils
from neutron.common.ovn import constants as ovn_const
from neutron.common.ovn import utils as ovn_utils
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb \
import impl_idl_ovn as impl
@ -608,6 +609,23 @@ class TestNbApi(BaseOvnIdlTest):
lsp = self.nbapi.lookup('Logical_Switch_Port', lsp_name)
self.assertEqual(hcg.result.uuid, lsp.ha_chassis_group[0].uuid)
def test_delete_lrouter(self):
router_name = ovn_utils.ovn_name(uuidutils.generate_uuid())
with self.nbapi.transaction(check_error=True) as txn:
txn.add(self.nbapi.lr_add(router_name))
txn.add(self.nbapi.ha_chassis_group_add(router_name))
r = self.nbapi.lookup('Logical_Router', router_name)
self.assertEqual(router_name, r.name)
hcg = self.nbapi.lookup('HA_Chassis_Group', router_name)
self.assertEqual(router_name, hcg.name)
self.nbapi.lr_del(router_name).execute(check_error=True)
self.assertIsNone(
self.nbapi.lookup('Logical_Router', router_name, default=None))
self.assertIsNone(
self.nbapi.lookup('HA_Chassis_Group', router_name, default=None))
class TestIgnoreConnectionTimeout(BaseOvnIdlTest):
@classmethod

View File

@ -12,13 +12,26 @@
# License for the specific language governing permissions and limitations
# under the License.
from neutron_lib.api.definitions import external_net
from neutron_lib.api.definitions import provider_net
from neutron_lib import constants
from oslo_utils import strutils
from neutron.common.ovn import constants as ovn_const
from neutron.common.ovn import utils as ovn_utils
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf as ovn_config
from neutron.tests.functional import base
from neutron.tests.unit.api import test_extensions
from neutron.tests.unit.extensions import test_l3
class TestOVNClient(base.TestOVNFunctionalBase):
class TestOVNClient(base.TestOVNFunctionalBase,
test_l3.L3NatTestCaseMixin):
def setUp(self, **kwargs):
super().setUp(**kwargs)
ext_mgr = test_l3.L3TestExtensionManager()
self.ext_api = test_extensions.setup_extensions_middleware(ext_mgr)
def test_create_metadata_port(self):
def check_metadata_port(enable_dhcp):
@ -84,3 +97,37 @@ class TestOVNClient(base.TestOVNFunctionalBase):
# command automatically checks for existing logical
# switch ports
ovn_client.create_port(self.context, port_data)
def test_create_router(self):
ch = self.add_fake_chassis('host1', enable_chassis_as_gw=True,
azs=[])
net_arg = {provider_net.NETWORK_TYPE: 'geneve',
external_net.EXTERNAL: True}
with self.network('test-ovn-client', as_admin=True,
arg_list=tuple(net_arg.keys()), **net_arg) as net:
with self.subnet(net):
ext_gw = {'network_id': net['network']['id']}
with self.router(external_gateway_info=ext_gw) as router:
router_id = router['router']['id']
lr = self.nb_api.lookup('Logical_Router',
ovn_utils.ovn_name(router_id))
self.assertEqual(ch, lr.options['chassis'])
lrp = lr.ports[0]
self.assertTrue(strutils.bool_from_string(
lrp.external_ids[ovn_const.OVN_ROUTER_IS_EXT_GW]))
hcg = self.nb_api.lookup('HA_Chassis_Group',
ovn_utils.ovn_name(router_id))
self.assertIsNotNone(hcg)
# Remove the external GW port.
self._update('routers', router_id,
{'router': {'external_gateway_info': {}}},
as_admin=True)
lr = self.nb_api.lookup('Logical_Router',
ovn_utils.ovn_name(router_id))
self.assertEqual([], lr.ports)
self.assertNotIn('chassis', lr.options)
hcg = self.nb_api.lookup('HA_Chassis_Group',
ovn_utils.ovn_name(router_id),
default=None)
self.assertIsNone(hcg)

View File

@ -175,7 +175,9 @@ class TestRouter(base.TestOVNFunctionalBase):
gw_info = {'network_id': ext1['network']['id']}
self._create_router('router1', gw_info=gw_info,
az_hints=router_az_hints)
self.assertTrue(plugin_select.called)
# If the network is tunnelled, the scheduler is not called.
check = self.assertTrue if physnet else self.assertFalse
check(plugin_select.called)
plugin_select.reset_mock()
# Unset the redirect-chassis so that schedule_unhosted_gateways

View File

@ -184,7 +184,7 @@ class FakeOvsdbSbOvnIdl(object):
self._get_chassis_physnets = mock.Mock()
self._get_chassis_physnets.return_value = ['fake-physnet']
self.get_chassis_and_physnets = mock.Mock()
self.get_gateway_chassis_from_cms_options = mock.Mock()
self.get_gateway_chassis_from_cms_options = mock.Mock(return_value=[])
self.get_extport_chassis_from_cms_options = mock.Mock(return_value=[])
self.is_col_present = mock.Mock()
self.is_col_present.return_value = False
@ -420,6 +420,7 @@ class FakeOvsdbRow(FakeResource):
'delvalue': None,
'verify': None,
'setkey': None,
'delkey': None,
}
# Overwrite default attributes and methods.
@ -741,6 +742,43 @@ class FakeFloatingIp(object):
loaded=True)
class FakeRouter(object):
"""Fake one or more Neutron routers."""
@staticmethod
def create_one_router(attrs=None):
"""Create a fake router.
:param Dictionary attrs:
A dictionary with all attributes
:return:
A FakeResource object faking the router
"""
attrs = attrs or {}
# Set default attributes.
router_id = uuidutils.generate_uuid()
router_attrs = {
'id': 'router-' + router_id,
'name': 'router-' + router_id,
'tenant_id': '',
'project_id': '',
'admin_state_up': True,
'status': 'ACTIVE',
'gw_port_id': {},
'enable_snat': True,
'flavor_id': None,
'extra_attributes': {},
'qos_policy_id': None,
'availability_zones': [],
'availability_zone_hints': [],
}
# Overwrite default attributes.
router_attrs.update(attrs)
return FakeResource(info=copy.deepcopy(router_attrs), loaded=True)
class FakeOVNPort(object):
"""Fake one or more ports."""

View File

@ -463,6 +463,37 @@ class TestUpdateLRouterCommand(TestBaseCommand):
self.assertEqual(new_ext_ids, fake_lrouter.external_ids)
class TestLrDelCommand(TestBaseCommand):
def _test_lrouter_del_no_exist(self, if_exists=True):
with mock.patch.object(self.ovn_api, 'lookup',
side_effect=idlutils.RowNotFound):
cmd = commands.LrDelCommand(
self.ovn_api, 'fake-lrouter', if_exists=if_exists)
if if_exists:
cmd.run_idl(self.transaction)
else:
self.assertRaises(RuntimeError, cmd.run_idl, self.transaction)
def test_lrouter_no_exist_ignore(self):
self._test_lrouter_del_no_exist(if_exists=True)
def test_lrouter_no_exist_fail(self):
self._test_lrouter_del_no_exist(if_exists=False)
def test_lrouter_del(self):
fake_lrouter = fakes.FakeOvsdbRow.create_one_ovsdb_row()
fake_hcg = fakes.FakeOvsdbRow.create_one_ovsdb_row()
self.ovn_api._tables['Logical_Router'].rows[fake_lrouter.uuid] = \
fake_lrouter
with mock.patch.object(self.ovn_api, 'lookup',
side_effect=[fake_lrouter, fake_hcg]):
cmd = commands.LrDelCommand(
self.ovn_api, fake_lrouter.name, if_exists=True)
cmd.run_idl(self.transaction)
fake_lrouter.delete.assert_called_once_with()
class TestAddLRouterPortCommand(TestBaseCommand):
def test_lrouter_not_found(self):

View File

@ -339,18 +339,19 @@ class TestDBInconsistenciesPeriodics(testlib_api.SqlTestCaseLight,
self.assertFalse(
self.fake_ovn_client._nb_idl.ha_chassis_group_add.called)
@mock.patch.object(utils, 'sync_ha_chassis_group')
@mock.patch.object(utils, 'sync_ha_chassis_group_network')
def test_check_for_ha_chassis_group_no_external_ports(
self, mock_sync_ha_chassis_group):
self, mock_sync_ha_chassis_group_network):
self.fake_ovn_client.is_external_ports_supported.return_value = True
nb_idl = self.fake_ovn_client._nb_idl
nb_idl.db_find_rows.return_value.execute.return_value = []
self.assertRaises(periodics.NeverAgain,
self.periodic.check_for_ha_chassis_group)
self.assertFalse(mock_sync_ha_chassis_group.called)
self.assertFalse(mock_sync_ha_chassis_group_network.called)
@mock.patch.object(utils, 'sync_ha_chassis_group')
def test_check_for_ha_chassis_group(self, mock_sync_ha_chassis_group):
@mock.patch.object(utils, 'sync_ha_chassis_group_network')
def test_check_for_ha_chassis_group(self,
mock_sync_ha_chassis_group_network):
self.fake_ovn_client.is_external_ports_supported.return_value = True
nb_idl = self.fake_ovn_client._nb_idl
@ -374,24 +375,22 @@ class TestDBInconsistenciesPeriodics(testlib_api.SqlTestCaseLight,
constants.OVN_NETWORK_NAME_EXT_ID_KEY: 'neutron-net1'}})
nb_idl.db_find_rows.return_value.execute.return_value = [p0, p1]
mock_sync_ha_chassis_group.return_value = hcg0.uuid
mock_sync_ha_chassis_group_network.return_value = hcg0.uuid, mock.ANY
# Invoke the periodic method, it meant to run only once at startup
# so NeverAgain will be raised at the end
self.assertRaises(periodics.NeverAgain,
self.periodic.check_for_ha_chassis_group)
# Assert sync_ha_chassis_group() is called for both networks
# Assert sync_ha_chassis_group_network() is called for both networks
expected_calls = [
mock.call(mock.ANY, 'p0', 'net0',
self.fake_ovn_client._nb_idl,
self.fake_ovn_client._sb_idl, mock.ANY),
mock.call(mock.ANY, 'p1', 'net1',
self.fake_ovn_client._nb_idl,
self.fake_ovn_client._sb_idl, mock.ANY),
mock.call(mock.ANY, self.fake_ovn_client._nb_idl,
self.fake_ovn_client._sb_idl, 'p0', 'net0', mock.ANY),
mock.call(mock.ANY, self.fake_ovn_client._nb_idl,
self.fake_ovn_client._sb_idl, 'p1', 'net1', mock.ANY),
]
mock_sync_ha_chassis_group.assert_has_calls(expected_calls,
any_order=True)
mock_sync_ha_chassis_group_network.assert_has_calls(expected_calls,
any_order=True)
expected_calls = [
mock.call('p0', ha_chassis_group=hcg0.uuid),

View File

@ -44,6 +44,7 @@ from oslo_serialization import jsonutils
from oslo_utils import timeutils
from oslo_utils import uuidutils
from ovsdbapp.backend.ovs_idl import idlutils
from ovsdbapp.backend.ovs_idl import rowview
from webob import exc
from neutron.common import _constants as n_const
@ -65,6 +66,7 @@ from neutron.plugins.ml2.drivers.ovn.mech_driver import mech_driver
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import impl_idl_ovn
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_client
from neutron.plugins.ml2.drivers import type_geneve # noqa
from neutron.plugins.ml2 import plugin as ml2_plugin
from neutron.services.revisions import revision_plugin
from neutron.tests.unit.extensions import test_segment
from neutron.tests.unit import fake_resources as fakes
@ -1080,7 +1082,7 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase):
port['port']['id'],
ovn_const.TYPE_PORTS)
@mock.patch.object(ovn_utils, 'sync_ha_chassis_group')
@mock.patch.object(ovn_utils, 'sync_ha_chassis_group_network')
@mock.patch.object(ovn_utils, 'is_port_external')
def _test_set_port_status_up(self, mock_is_ext, mock_sync,
is_compute_port=False,
@ -1125,8 +1127,8 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase):
if is_extport_present:
mock_sync.assert_called_once_with(
mock.ANY, port1['port']['id'], port1['port']['network_id'],
self.nb_ovn, self.sb_ovn, mock.ANY)
mock.ANY, self.nb_ovn, self.sb_ovn, port1['port']['id'],
port1['port']['network_id'], mock.ANY)
else:
mock_sync.assert_not_called()
@ -2675,7 +2677,8 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase):
self.assertEqual(sorted(result['expected_candidates']),
sorted(candidates))
def test__get_info_for_ha_chassis_group_as_extport(self):
@mock.patch.object(ovn_utils, '_sync_ha_chassis_group')
def test_sync_ha_chassis_group_network_as_extport(self, mock_sync_hcg):
net_attrs = {az_def.AZ_HINTS: ['az0', 'az1', 'az2']}
fake_net = (
fakes.FakeNetwork.create_one_network(attrs=net_attrs).info())
@ -2701,9 +2704,12 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase):
self.sb_ovn.get_chassis_host_for_port.return_value = {
ch4.name, ch5.name}
hcg_info = ovn_utils._get_info_for_ha_chassis_group(
self.context, fake_port['id'], fake_net['id'], self.sb_ovn)
ovn_utils.sync_ha_chassis_group_network(
self.context, self.nb_ovn, self.sb_ovn, fake_port['id'],
fake_net['id'], None)
mock_sync_hcg.assert_called_once()
hcg_info = mock_sync_hcg.call_args.args[1]
expected_group_name = ovn_utils.ovn_extport_chassis_group_name(
fake_port['id'])
expected_ch_list = [ch0, ch1, ch2, ch3, ch4, ch5]
@ -2716,7 +2722,8 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase):
self.assertEqual(sorted(expected_ignore_chassis),
sorted(hcg_info.ignore_chassis))
def test__get_info_for_ha_chassis_group_as_gw(self):
@mock.patch.object(ovn_utils, '_sync_ha_chassis_group')
def test_sync_ha_chassis_group_network_as_gw(self, mock_sync_hcg):
net_attrs = {az_def.AZ_HINTS: ['az0', 'az1', 'az2']}
fake_net = (
fakes.FakeNetwork.create_one_network(attrs=net_attrs).info())
@ -2740,9 +2747,12 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase):
self.sb_ovn.get_gateway_chassis_from_cms_options.return_value = [
ch0, ch1, ch2, ch3, ch4, ch5]
hcg_info = ovn_utils._get_info_for_ha_chassis_group(
self.context, fake_port['id'], fake_net['id'], self.sb_ovn)
ovn_utils.sync_ha_chassis_group_network(
self.context, self.nb_ovn, self.sb_ovn, fake_port['id'],
fake_net['id'], None)
mock_sync_hcg.assert_called_once()
hcg_info = mock_sync_hcg.call_args.args[1]
expected_group_name = ovn_utils.ovn_name(fake_net['id'])
expected_ch_list = [ch0, ch1, ch2, ch3, ch4, ch5]
expected_az_hints = ['az0', 'az1', 'az2']
@ -2752,7 +2762,29 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase):
self.assertEqual(expected_az_hints, hcg_info.az_hints)
self.assertEqual(set(), hcg_info.ignore_chassis)
def _build_hcg_info(self, with_az=False, with_ignore_chassis=False):
@mock.patch.object(ovn_utils, '_sync_ha_chassis_group')
def test_sync_ha_chassis_group_router(self, mock_sync_hcg):
fake_router = fakes.FakeRouter.create_one_router().info()
l3_plugin = mock.patch.object(directory, 'get_plugin').start()
l3_plugin.get_router.return_value = fake_router
chassis_list = []
for _ in range(5):
chassis_list.append(fakes.FakeChassis.create(chassis_as_gw=True))
self.sb_ovn.get_gateway_chassis_from_cms_options.return_value = (
chassis_list)
ovn_utils.sync_ha_chassis_group_router(
self.context, self.nb_ovn, self.sb_ovn, fake_router['id'], None)
mock_sync_hcg.assert_called_once()
hcg_info = mock_sync_hcg.call_args.args[1]
expected_group_name = ovn_utils.ovn_name(fake_router['id'])
self.assertEqual(expected_group_name, hcg_info.group_name)
self.assertEqual(chassis_list, hcg_info.chassis_list)
self.assertEqual(set(), hcg_info.ignore_chassis)
def _build_hcg_info(self, with_az=False, with_ignore_chassis=False,
network_id=None):
az_hints = []
if with_az:
az_hints = ['az0', 'az1']
@ -2775,10 +2807,12 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase):
ignore_chassis = set()
if with_ignore_chassis:
ignore_chassis = {ch1.name, ch2.name}
group_name = (ovn_utils.ovn_name(network_id) if network_id else
'fake-hcg-name')
return ovn_utils.HAChassisGroupInfo(
group_name='fake-hcg-name', chassis_list=chassis_list,
az_hints=az_hints, ignore_chassis=ignore_chassis)
group_name=group_name, chassis_list=chassis_list,
az_hints=az_hints, ignore_chassis=ignore_chassis, external_ids={})
def test__filter_candidates_for_ha_chassis_group(self):
fake_hcg_info = self._build_hcg_info()
@ -2805,18 +2839,18 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase):
fake_hcg_info)
self.assertEqual(['ch0', 'ch3'], sorted(candidates))
@mock.patch.object(ovn_utils, '_get_info_for_ha_chassis_group')
def test_sync_ha_chassis_group(self, mock_hcg_info):
@mock.patch.object(ml2_plugin.Ml2Plugin, 'get_network', return_value={})
@mock.patch.object(ovn_utils, '_filter_candidates_for_ha_chassis_group')
def test_sync_ha_chassis_group_network(self, mock_candidates, *args):
self.nb_ovn.ha_chassis_group_get.side_effect = idlutils.RowNotFound
fake_txn = mock.Mock()
hcg_info = self._build_hcg_info()
mock_hcg_info.return_value = hcg_info
hcg_info = self._build_hcg_info(network_id='fake-net-id')
mock_candidates.return_value = {'ch0', 'ch1', 'ch2', 'ch3'}
# Invoke the method
ovn_utils.sync_ha_chassis_group(
self.context, 'fake-port-id', 'fake-net-id',
self.nb_ovn, self.sb_ovn, fake_txn)
ovn_utils.sync_ha_chassis_group_network(
self.context, self.nb_ovn, self.sb_ovn, 'fake-port-id',
'fake-net-id', fake_txn)
# Assert it creates the HA Chassis Group
ext_ids = {ovn_const.OVN_AZ_HINTS_EXT_ID_KEY:
@ -2832,11 +2866,12 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase):
self.nb_ovn.ha_chassis_group_add_chassis.assert_has_calls(
expected_calls, any_order=True)
@mock.patch.object(ovn_utils, '_get_info_for_ha_chassis_group')
def test_sync_ha_chassis_group_existing_group(self, mock_hcg_info):
@mock.patch.object(ml2_plugin.Ml2Plugin, 'get_network', return_value={})
@mock.patch.object(ovn_utils, '_filter_candidates_for_ha_chassis_group')
def test_sync_ha_chassis_group_network_existing_group(
self, mock_candidates, *args):
fake_txn = mock.Mock()
hcg_info = self._build_hcg_info()
mock_hcg_info.return_value = hcg_info
hcg_info = self._build_hcg_info(network_id='fake-net-id')
hc0 = fakes.FakeOvsdbRow.create_one_ovsdb_row(
attrs={'chassis_name': 'ch0', 'priority': 1})
@ -2852,18 +2887,20 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase):
hcg_attrs = {
'name': hcg_info.group_name,
'ha_chassis': [hc0, hc1, hc2, hc3]}
fake_ha_chassis_group = fakes.FakeOvsdbRow.create_one_ovsdb_row(
attrs=hcg_attrs)
self.nb_ovn.ha_chassis_group_get().execute.return_value = (
fake_ha_chassis_group)
fake_txn.add.return_value.result = mock.Mock(
spec=rowview.RowView, uuid=uuidutils.generate_uuid(), **hcg_attrs)
mock_candidates.return_value = {'ch0', 'ch1', 'ch2', 'ch3'}
# Invoke the method
ovn_utils.sync_ha_chassis_group(
self.context, 'fake-port-id', 'fake-net-id',
self.nb_ovn, self.sb_ovn, fake_txn)
ovn_utils.sync_ha_chassis_group_network(
self.context, self.nb_ovn, self.sb_ovn, 'fake-port-id',
'fake-net-id', fake_txn)
# Assert the group was not re-created
self.nb_ovn.ha_chassis_group_add.assert_not_called()
self.nb_ovn.ha_chassis_group_add.assert_has_calls(
[mock.call(hcg_info.group_name, may_exist=True,
external_ids={'neutron:availability_zone_hints': ''})]
)
self.nb_ovn.ha_chassis_group_add.reset_mock()
# Assert the chassis that are no longer part of the candidates list
# are removed from group
@ -4168,10 +4205,10 @@ class TestOVNMechanismDriverSecurityGroup(MechDriverSetupBase,
@mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.'
'ovn_client.OVNClient.is_external_ports_supported',
lambda *_: True)
@mock.patch.object(ovn_utils, 'sync_ha_chassis_group')
@mock.patch.object(ovn_utils, 'sync_ha_chassis_group_network')
def _test_create_port_with_vnic_type(self, vnic_type, sync_mock):
fake_grp = 'fake-default-ha-group-uuid'
sync_mock.return_value = fake_grp
sync_mock.return_value = fake_grp, mock.ANY
with self.network() as n, self.subnet(n):
self._create_port(
@ -4188,8 +4225,8 @@ class TestOVNMechanismDriverSecurityGroup(MechDriverSetupBase,
self.assertEqual(ovn_const.LSP_TYPE_EXTERNAL, kwargs['type'])
self.assertEqual(fake_grp, kwargs['ha_chassis_group'])
sync_mock.assert_called_once_with(
mock.ANY, mock.ANY, n['network']['id'],
self.mech_driver.nb_ovn, self.mech_driver.sb_ovn, mock.ANY)
mock.ANY, self.mech_driver.nb_ovn, self.mech_driver.sb_ovn,
mock.ANY, n['network']['id'], mock.ANY)
def test_create_port_with_vnic_direct(self):
self._test_create_port_with_vnic_type(portbindings.VNIC_DIRECT)

View File

@ -80,6 +80,11 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
self.fake_network = \
fake_resources.FakeNetwork.create_one_network(
attrs=network_attrs).info()
network_attrs.update({'provider:network_type': 'vlan',
'provider:physical_network': 'physnet1'})
self.fake_ext_network = fake_resources.FakeNetwork.create_one_network(
attrs=network_attrs).info()
self.fake_router_port = {'device_id': '',
'network_id': self.fake_network['id'],
'tenant_id': 'tenant-id',
@ -127,7 +132,7 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
self.fake_external_fixed_ips = {
'network_id': 'ext-network-id',
'external_fixed_ips': [{'ip_address': '192.168.1.1',
'subnet_id': 'ext-subnet-id'}]}
'subnet_id': 'ext-subnet-id'}],}
self.fake_router_with_ext_gw = {
'id': 'router-id',
'name': 'router',
@ -251,7 +256,7 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
'neutron.services.ovn_l3.plugin.OVNL3RouterPlugin._sb_ovn',
new_callable=mock.PropertyMock,
return_value=fake_resources.FakeOvsdbSbOvnIdl())
self._start_mock(
self._get_network = self._start_mock(
'neutron.plugins.ml2.plugin.Ml2Plugin.get_network',
return_value=self.fake_network)
self.get_port = self._start_mock(
@ -623,6 +628,7 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
@mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.'
'ovn_client.OVNClient._get_v4_network_of_all_router_ports')
def test_create_router_with_ext_gw(self, get_rps):
self._get_network.return_value = self.fake_ext_network
self.l3_inst._nb_ovn.is_col_present.return_value = True
self.get_subnet.return_value = self.fake_ext_subnet
self.get_port.return_value = self.fake_ext_gw_port
@ -807,6 +813,7 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
@mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.'
'update_router')
def test_update_router_with_ext_gw(self, ur, grps):
self._get_network.return_value = self.fake_ext_network
self.l3_inst._nb_ovn.is_col_present.return_value = True
ur.return_value = self.fake_router_with_ext_gw
self.get_subnet.side_effect = lambda ctx, sid: {
@ -846,6 +853,7 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
'update_router')
def test_update_router_ext_gw_change_subnet(self, ur,
grps, mock_get_gw):
self._get_network.return_value = self.fake_ext_network
self.l3_inst._nb_ovn.is_col_present.return_value = True
mock_get_gw.return_value = [mock.sentinel.GwRoute]
fake_old_ext_subnet = {'id': 'old-ext-subnet-id',
@ -919,6 +927,7 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
'update_router')
def test_update_router_ext_gw_change_ip_address(self, ur,
grps, mock_get_gw):
self._get_network.return_value = self.fake_ext_network
self.l3_inst._nb_ovn.is_col_present.return_value = True
mock_get_gw.return_value = [mock.sentinel.GwRoute]
# Old gateway info with same subnet and different ip address
@ -994,9 +1003,13 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
'ext-subnet-id': self.fake_ext_subnet}.get(sid, self.fake_subnet)
self.get_port.return_value = self.fake_ext_gw_port
grps.return_value = self.fake_router_ports
chassis = mock.Mock(name='chassis1', other_config={})
self.sb_idl().get_gateway_chassis_from_cms_options.return_value = (
[chassis])
payload = self._create_payload_for_router_update(
self.fake_router_without_ext_gw, self.fake_router_with_ext_gw)
self.ovn_drv._process_router_update(resources.ROUTER,
events.AFTER_UPDATE,
self, payload)
@ -1904,7 +1917,9 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
grps.return_value = [interface_info]
self.get_router.return_value = self.fake_router_with_ext_gw
mtu = 1200
network_attrs = {'id': 'prov-net', 'mtu': mtu}
network_attrs = {'id': 'prov-net', 'mtu': 1200,
'provider:network_type': 'vlan',
'provider:physical_network': 'physnet1'}
prov_net = fake_resources.FakeNetwork.create_one_network(
attrs=network_attrs).info()
self.fake_router_port['device_owner'] = (
@ -1927,7 +1942,8 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
fake_router_port_assert = self.fake_router_port_assert
fake_router_port_assert['options'] = {
ovn_const.OVN_ROUTER_PORT_GW_MTU_OPTION:
str(prov_net['mtu'])}
str(prov_net['mtu']),
}
fake_router_port_assert['external_ids'][
ovn_const.OVN_ROUTER_IS_EXT_GW] = 'True'
@ -1960,7 +1976,9 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
# _get_routers_ports will be RouterNotFound
grps.side_effect = l3_exc.RouterNotFound(router_id=router_id)
self.get_router.return_value = self.fake_router_with_ext_gw
network_attrs = {'id': 'prov-net', 'mtu': 1200}
network_attrs = {'id': 'prov-net', 'mtu': 1200,
'provider:network_type': 'vlan',
'provider:physical_network': 'physnet1'}
prov_net = fake_resources.FakeNetwork.create_one_network(
attrs=network_attrs).info()
self.fake_router_port['device_owner'] = (