[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:
parent
4cad0eda59
commit
25a1809964
|
@ -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,
|
||||
|
|
|
@ -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. '
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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'])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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."""
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'] = (
|
||||
|
|
Loading…
Reference in New Issue