Merge "[OVN] "Logical_Router" pinned to chassis, OVN L3 scheduler"
This commit is contained in:
commit
4e9f00078b
@ -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…
x
Reference in New Issue
Block a user