Merge "Improve "sync_ha_chassis_group" method"

This commit is contained in:
Zuul 2023-05-30 10:07:42 +00:00 committed by Gerrit Code Review
commit 940a1b6cea
10 changed files with 265 additions and 145 deletions

View File

@ -14,6 +14,7 @@ import collections
import copy import copy
import inspect import inspect
import os import os
import random
import netaddr import netaddr
from neutron_lib.api.definitions import external_net from neutron_lib.api.definitions import external_net
@ -34,6 +35,7 @@ from oslo_log import log
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
from oslo_utils import netutils from oslo_utils import netutils
from oslo_utils import strutils from oslo_utils import strutils
from ovsdbapp.backend.ovs_idl import rowview
from ovsdbapp import constants as ovsdbapp_const from ovsdbapp import constants as ovsdbapp_const
import tenacity import tenacity
@ -853,3 +855,76 @@ def get_ovn_chassis_other_config(chassis):
return chassis.other_config return chassis.other_config
except AttributeError: except AttributeError:
return chassis.external_ids return chassis.external_ids
def sync_ha_chassis_group(context, network_id, nb_idl, sb_idl, 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.
:param context: Neutron API context.
:param network_id: The Neutron network ID.
:param nb_idl: OVN NB IDL
:param sb_idl: OVN SB IDL
:param txn: The ovsdbapp transaction object.
:returns: The HA Chassis Group UUID or the HA Chassis Group command object.
"""
plugin = directory.get_plugin()
az_hints = common_utils.get_az_hints(
plugin.get_network(context, network_id))
ha_ch_grp_name = ovn_name(network_id)
ext_ids = {constants.OVN_AZ_HINTS_EXT_ID_KEY: ','.join(az_hints)}
hcg_cmd = txn.add(nb_idl.ha_chassis_group_add(
ha_ch_grp_name, may_exist=True, external_ids=ext_ids))
if isinstance(hcg_cmd.result, rowview.RowView):
# The HA chassis group existed before this transaction.
ha_ch_grp = hcg_cmd.result
else:
# The HA chassis group is being created in this transaction.
ha_ch_grp = None
# Get the chassis belonging to the AZ hints
ch_list = sb_idl.get_gateway_chassis_from_cms_options(name_only=False)
if not az_hints:
az_chassis = get_gateway_chassis_without_azs(ch_list)
else:
az_chassis = get_chassis_in_azs(ch_list, az_hints)
priority = constants.HA_CHASSIS_GROUP_HIGHEST_PRIORITY
if ha_ch_grp:
# Remove any chassis that no longer belongs to the AZ hints
all_ch = {ch.chassis_name for ch in ha_ch_grp.ha_chassis}
ch_to_del = all_ch - az_chassis
for ch in ch_to_del:
txn.add(nb_idl.ha_chassis_group_del_chassis(
ha_ch_grp_name, ch, if_exists=True))
# Find the highest priority chassis in the HA Chassis Group. If
# it exists and still belongs to the same AZ, keep it as the
# highest priority in the group to avoid ports already bond to it
# from moving to another chassis.
high_prio_ch = max(ha_ch_grp.ha_chassis, key=lambda x: x.priority,
default=None)
priority = constants.HA_CHASSIS_GROUP_HIGHEST_PRIORITY
if high_prio_ch and high_prio_ch.chassis_name in az_chassis:
txn.add(nb_idl.ha_chassis_group_add_chassis(
ha_ch_grp_name, high_prio_ch.chassis_name,
priority=priority))
az_chassis.remove(high_prio_ch.chassis_name)
priority -= 1
# Randomize the order so that networks belonging to the same
# availability zones do not necessarily end up with the same
# Chassis as the highest priority one.
for ch in random.sample(list(az_chassis), len(az_chassis)):
txn.add(nb_idl.ha_chassis_group_add_chassis(
hcg_cmd, ch, priority=priority))
priority -= 1
# 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 hcg_cmd

View File

@ -15,6 +15,7 @@
from oslo_utils import timeutils from oslo_utils import timeutils
from ovsdbapp.backend.ovs_idl import command from ovsdbapp.backend.ovs_idl import command
from ovsdbapp.backend.ovs_idl import idlutils from ovsdbapp.backend.ovs_idl import idlutils
from ovsdbapp import utils as ovsdbapp_utils
from neutron._i18n import _ from neutron._i18n import _
from neutron.common.ovn import constants as ovn_const from neutron.common.ovn import constants as ovn_const
@ -137,6 +138,14 @@ class AddLSwitchPortCommand(command.BaseCommand):
port.dhcpv6_options = dhcpv6_options port.dhcpv6_options = dhcpv6_options
else: else:
port.dhcpv6_options = [dhcpv6_options.result] port.dhcpv6_options = [dhcpv6_options.result]
# NOTE(ralonsoh): HA chassis group is created by Neutron, there is no
# need to create it in this command.
ha_chassis_group = self.columns.pop('ha_chassis_group', None)
if ha_chassis_group:
hcg_uuid = ovsdbapp_utils.get_uuid(ha_chassis_group)
port.ha_chassis_group = hcg_uuid
for col, val in self.columns.items(): for col, val in self.columns.items():
setattr(port, col, val) setattr(port, col, val)
# add the newly created port to existing lswitch # add the newly created port to existing lswitch
@ -202,6 +211,21 @@ class SetLSwitchPortCommand(command.BaseCommand):
external_ids[k] = v external_ids[k] = v
port.external_ids = external_ids port.external_ids = external_ids
# NOTE(ralonsoh): HA chassis group is created by Neutron, there is no
# need to create it in this command. The register is also deleted when
# the network to which the HA chassis group is associated is deleted.
ha_chassis_group = self.columns.pop('ha_chassis_group', None)
if ha_chassis_group:
hcg_uuid = ovsdbapp_utils.get_uuid(ha_chassis_group)
try:
port_hcg_uuid = port.ha_chassis_group[0].uuid
except IndexError:
port_hcg_uuid = None
if port_hcg_uuid != hcg_uuid:
port.ha_chassis_group = hcg_uuid
elif ha_chassis_group == []:
port.ha_chassis_group = []
for col, val in self.columns.items(): for col, val in self.columns.items():
setattr(port, col, val) setattr(port, col, val)

View File

@ -603,15 +603,10 @@ class DBInconsistenciesPeriodics(SchemaAwarePeriodicsBase):
network_id = port.external_ids[ network_id = port.external_ids[
ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY].replace( ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY].replace(
ovn_const.OVN_NAME_PREFIX, '') ovn_const.OVN_NAME_PREFIX, '')
ha_ch_grp = self._ovn_client.sync_ha_chassis_group( ha_ch_grp = utils.sync_ha_chassis_group(
context, network_id, txn) context, network_id, self._nb_idl, self._sb_idl, txn)
try: txn.add(self._nb_idl.set_lswitch_port(
port_ha_ch_uuid = port.ha_chassis_group[0].uuid port.name, ha_chassis_group=ha_ch_grp))
except IndexError:
port_ha_ch_uuid = None
if port_ha_ch_uuid != ha_ch_grp:
txn.add(self._nb_idl.set_lswitch_port(
port.name, ha_chassis_group=ha_ch_grp))
self._delete_default_ha_chassis_group(txn) self._delete_default_ha_chassis_group(txn)

View File

@ -16,7 +16,6 @@
import collections import collections
import copy import copy
import datetime import datetime
import random
import netaddr import netaddr
from neutron_lib.api.definitions import l3 from neutron_lib.api.definitions import l3
@ -447,76 +446,6 @@ class OVNClient(object):
bp_info.vnic_type, bp_info.capabilities bp_info.vnic_type, bp_info.capabilities
) )
def sync_ha_chassis_group(self, context, network_id, txn):
"""Return the UUID of the HA Chassis Group.
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.
:param context: Neutron API context.
:param network_id: The Neutron network ID.
:param txn: The ovsdbapp transaction object.
:returns: An HA Chassis Group UUID.
"""
az_hints = common_utils.get_az_hints(
self._plugin.get_network(context, network_id))
ha_ch_grp_name = utils.ovn_name(network_id)
# FIXME(lucasagomes): Couldn't find a better way of doing this
# without a sub-transaction. This shouldn't be a problem since
# the HA Chassis Group associated with a network will be deleted
# as part of the network delete method (if present)
with self._nb_idl.create_transaction(check_error=True) as sub_txn:
sub_txn.add(self._nb_idl.ha_chassis_group_add(
ha_ch_grp_name, may_exist=True))
ha_ch_grp = self._nb_idl.ha_chassis_group_get(
ha_ch_grp_name).execute(check_error=True)
txn.add(self._nb_idl.db_set(
'HA_Chassis_Group', ha_ch_grp_name,
('external_ids',
{ovn_const.OVN_AZ_HINTS_EXT_ID_KEY: ','.join(az_hints)})))
# Get the chassis belonging to the AZ hints
ch_list = self._sb_idl.get_gateway_chassis_from_cms_options(
name_only=False)
if not az_hints:
az_chassis = utils.get_gateway_chassis_without_azs(ch_list)
else:
az_chassis = utils.get_chassis_in_azs(ch_list, az_hints)
# Remove any chassis that no longer belongs to the AZ hints
all_ch = {ch.chassis_name for ch in ha_ch_grp.ha_chassis}
ch_to_del = all_ch - az_chassis
for ch in ch_to_del:
txn.add(self._nb_idl.ha_chassis_group_del_chassis(
ha_ch_grp_name, ch, if_exists=True))
# Find the highest priority chassis in the HA Chassis Group. If
# it exists and still belongs to the same AZ, keep it as the highest
# priority in the group to avoid ports already bond to it from
# moving to another chassis.
high_prio_ch = max(ha_ch_grp.ha_chassis, key=lambda x: x.priority,
default=None)
priority = ovn_const.HA_CHASSIS_GROUP_HIGHEST_PRIORITY
if high_prio_ch and high_prio_ch.chassis_name in az_chassis:
txn.add(self._nb_idl.ha_chassis_group_add_chassis(
ha_ch_grp_name, high_prio_ch.chassis_name,
priority=priority))
az_chassis.remove(high_prio_ch.chassis_name)
priority -= 1
# Randomize the order so that networks belonging to the same
# availability zones do not necessarily end up with the same
# Chassis as the highest priority one.
for ch in random.sample(list(az_chassis), len(az_chassis)):
txn.add(self._nb_idl.ha_chassis_group_add_chassis(
ha_ch_grp_name, ch, priority=priority))
priority -= 1
return ha_ch_grp.uuid
def update_port_dhcp_options(self, port_info, txn): def update_port_dhcp_options(self, port_info, txn):
dhcpv4_options = [] dhcpv4_options = []
dhcpv6_options = [] dhcpv6_options = []
@ -598,9 +527,9 @@ class OVNClient(object):
if (self.is_external_ports_supported() and if (self.is_external_ports_supported() and
port_info.type == ovn_const.LSP_TYPE_EXTERNAL): port_info.type == ovn_const.LSP_TYPE_EXTERNAL):
kwargs['ha_chassis_group'] = ( kwargs['ha_chassis_group'] = utils.sync_ha_chassis_group(
self.sync_ha_chassis_group( context, port['network_id'], self._nb_idl, self._sb_idl,
context, port['network_id'], txn)) txn)
# NOTE(mjozefcz): Do not set addresses if the port is not # NOTE(mjozefcz): Do not set addresses if the port is not
# bound, has no device_owner and it is OVN LB VIP port. # bound, has no device_owner and it is OVN LB VIP port.
@ -721,8 +650,9 @@ class OVNClient(object):
if self.is_external_ports_supported(): if self.is_external_ports_supported():
if port_info.type == ovn_const.LSP_TYPE_EXTERNAL: if port_info.type == ovn_const.LSP_TYPE_EXTERNAL:
columns_dict['ha_chassis_group'] = ( columns_dict['ha_chassis_group'] = (
self.sync_ha_chassis_group( utils.sync_ha_chassis_group(
context, port['network_id'], txn)) context, port['network_id'], self._nb_idl,
self._sb_idl, txn))
else: else:
# Clear the ha_chassis_group field # Clear the ha_chassis_group field
columns_dict['ha_chassis_group'] = [] columns_dict['ha_chassis_group'] = []
@ -2068,7 +1998,9 @@ class OVNClient(object):
neutron_net_azs = lswitch_params['external_ids'].get( neutron_net_azs = lswitch_params['external_ids'].get(
ovn_const.OVN_AZ_HINTS_EXT_ID_KEY, '') ovn_const.OVN_AZ_HINTS_EXT_ID_KEY, '')
if ovn_ls_azs != neutron_net_azs: if ovn_ls_azs != neutron_net_azs:
self.sync_ha_chassis_group(context, network['id'], txn) utils.sync_ha_chassis_group(
context, network['id'], self._nb_idl,
self._sb_idl, txn)
# Update the segment tags, if any # Update the segment tags, if any
segments = segments_db.get_network_segments(context, network['id']) segments = segments_db.get_network_segments(context, network['id'])

View File

@ -12,6 +12,10 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from unittest import mock
from oslo_utils import uuidutils
from neutron.common.ovn import constants as ovn_const from neutron.common.ovn import constants as ovn_const
from neutron.common.ovn import utils from neutron.common.ovn import utils
from neutron.tests.functional import base from neutron.tests.functional import base
@ -58,3 +62,56 @@ class TestCreateNeutronPgDrop(base.TestOVNFunctionalBase):
self.assertEqual(directions[0], acl2.direction) self.assertEqual(directions[0], acl2.direction)
self.assertEqual('drop', acl2.action) self.assertEqual('drop', acl2.action)
self.assertEqual(matches[0], acl2.match) self.assertEqual(matches[0], acl2.match)
class TestSyncHaChassisGroup(base.TestOVNFunctionalBase):
def test_sync_ha_chassis_group(self):
plugin = mock.Mock()
plugin.get_network.return_value = {}
network_id = uuidutils.generate_uuid()
hcg_name = utils.ovn_name(network_id)
chassis1 = self.add_fake_chassis('host1', azs=[],
enable_chassis_as_gw=True)
chassis2 = self.add_fake_chassis('host2', azs=[],
enable_chassis_as_gw=True)
self.add_fake_chassis('host3')
with self.nb_api.transaction(check_error=True) as txn:
utils.sync_ha_chassis_group(self.context, network_id, self.nb_api,
self.sb_api, txn)
ha_chassis = self.nb_api.db_find('HA_Chassis').execute(
check_error=True)
ha_chassis_names = [hc['chassis_name'] for hc in ha_chassis]
self.assertEqual(2, len(ha_chassis))
self.assertEqual(sorted([chassis1, chassis2]),
sorted(ha_chassis_names))
hcg = self.nb_api.ha_chassis_group_get(hcg_name).execute(
check_error=True)
self.assertEqual(hcg_name, hcg.name)
ha_chassis_exp = sorted([str(hc['_uuid']) for hc in ha_chassis])
ha_chassis_ret = sorted([str(hc.uuid) for hc in hcg.ha_chassis])
self.assertEqual(ha_chassis_exp, ha_chassis_ret)
# Delete one GW chassis and resync the HA chassis group associated to
# the same network. The method will now not create again the existing
# 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, network_id, self.nb_api,
self.sb_api, txn)
ha_chassis = self.nb_api.db_find('HA_Chassis').execute(
check_error=True)
ha_chassis_names = [hc['chassis_name'] for hc in ha_chassis]
self.assertEqual(1, len(ha_chassis))
self.assertEqual([chassis1], ha_chassis_names)
hcg = self.nb_api.ha_chassis_group_get(hcg_name).execute(
check_error=True)
self.assertEqual(hcg_name, hcg.name)
ha_chassis_exp = str(ha_chassis[0]['_uuid'])
ha_chassis_ret = str(hcg.ha_chassis[0].uuid)
self.assertEqual(ha_chassis_exp, ha_chassis_ret)

View File

@ -15,6 +15,7 @@
import copy import copy
import uuid import uuid
from oslo_utils import uuidutils
from ovsdbapp.backend.ovs_idl import connection from ovsdbapp.backend.ovs_idl import connection
from ovsdbapp import constants as const from ovsdbapp import constants as const
from ovsdbapp import event as ovsdb_event from ovsdbapp import event as ovsdb_event
@ -269,6 +270,43 @@ class TestNbApi(BaseOvnIdlTest):
self.assertIn((lb_match['name'], lb_match['external_ids']), self.assertIn((lb_match['name'], lb_match['external_ids']),
exp_values) exp_values)
def test_create_lswitch_port_ha_chassis_group(self):
ls_name = uuidutils.generate_uuid()
lsp_name = uuidutils.generate_uuid()
hcg_name = uuidutils.generate_uuid()
self.nbapi.ha_chassis_group_add(hcg_name).execute(check_error=True)
hcg = self.nbapi.lookup('HA_Chassis_Group', hcg_name)
self.nbapi.ls_add(ls_name).execute(check_error=True)
self.nbapi.create_lswitch_port(
lsp_name, ls_name, ha_chassis_group=hcg.uuid).execute(
check_error=True)
lsp = self.nbapi.lookup('Logical_Switch_Port', lsp_name)
self.assertEqual(hcg.uuid, lsp.ha_chassis_group[0].uuid)
def test_set_lswitch_port_ha_chassis_group(self):
ls_name = uuidutils.generate_uuid()
lsp_name = uuidutils.generate_uuid()
self.nbapi.ls_add(ls_name).execute(check_error=True)
self.nbapi.create_lswitch_port(lsp_name, ls_name).execute(
check_error=True)
lsp = self.nbapi.lookup('Logical_Switch_Port', lsp_name)
self.assertEqual([], lsp.ha_chassis_group)
# Create an HA Chassis Group register and assign to the LSP.
hcg_name = uuidutils.generate_uuid()
self.nbapi.ha_chassis_group_add(hcg_name).execute(check_error=True)
hcg = self.nbapi.lookup('HA_Chassis_Group', hcg_name)
self.nbapi.set_lswitch_port(
lsp_name, ha_chassis_group=hcg.uuid).execute(check_error=True)
lsp = self.nbapi.lookup('Logical_Switch_Port', lsp_name)
self.assertEqual(hcg.uuid, lsp.ha_chassis_group[0].uuid)
# Unassign the HA Chassis Group from the LSP.
self.nbapi.set_lswitch_port(
lsp_name, ha_chassis_group=[]).execute(check_error=True)
lsp = self.nbapi.lookup('Logical_Switch_Port', lsp_name)
self.assertEqual([], lsp.ha_chassis_group)
class TestIgnoreConnectionTimeout(BaseOvnIdlTest): class TestIgnoreConnectionTimeout(BaseOvnIdlTest):
@classmethod @classmethod

View File

@ -74,7 +74,7 @@ class _TestMaintenanceHelper(base.TestOVNFunctionalBase):
return self.deserialize(self.fmt, res)['network'] return self.deserialize(self.fmt, res)['network']
def _create_port(self, name, net_id, security_groups=None, def _create_port(self, name, net_id, security_groups=None,
device_owner=None): device_owner=None, vnic_type=None):
data = {'port': {'name': name, data = {'port': {'name': name,
'network_id': net_id}} 'network_id': net_id}}
@ -84,6 +84,9 @@ class _TestMaintenanceHelper(base.TestOVNFunctionalBase):
if device_owner is not None: if device_owner is not None:
data['port']['device_owner'] = device_owner data['port']['device_owner'] = device_owner
if vnic_type is not None:
data['port']['binding:vnic_type'] = vnic_type
req = self.new_create_request('ports', data, self.fmt) req = self.new_create_request('ports', data, self.fmt)
res = req.get_response(self.api) res = req.get_response(self.api)
return self.deserialize(self.fmt, res)['port'] return self.deserialize(self.fmt, res)['port']
@ -949,6 +952,26 @@ class TestMaintenance(_TestMaintenanceHelper):
# Assert load balancer for port forwarding is gone # Assert load balancer for port forwarding is gone
self.assertFalse(self._find_pf_lb(router_id, fip_id)) self.assertFalse(self._find_pf_lb(router_id, fip_id))
def test_check_for_ha_chassis_group(self):
net1 = self._create_network('network1test', external=False)
self._create_subnet('subnet1test', net1['id'])
p1 = self._create_port('testp1', net1['id'], vnic_type='direct')
# Remove the HA Chassis Group register, created during the port
# creation.
self.nb_api.set_lswitch_port(p1['id'], ha_chassis_group=[]).execute(
check_error=True)
hcg_uuid = next(iter(self.nb_api._tables['HA_Chassis_Group'].rows))
self.nb_api.ha_chassis_group_del(hcg_uuid).execute(check_error=True)
lsp = self.nb_api.lookup('Logical_Switch_Port', p1['id'])
self.assertEqual([], lsp.ha_chassis_group)
self.assertRaises(periodics.NeverAgain,
self.maint.check_for_ha_chassis_group)
hcg_uuid = next(iter(self.nb_api._tables['HA_Chassis_Group'].rows))
lsp = self.nb_api.lookup('Logical_Switch_Port', p1['id'])
self.assertEqual(hcg_uuid, lsp.ha_chassis_group[0].uuid)
class TestLogMaintenance(_TestMaintenanceHelper, class TestLogMaintenance(_TestMaintenanceHelper,
test_log_driver.LogApiTestCaseBase): test_log_driver.LogApiTestCaseBase):

View File

@ -416,7 +416,8 @@ class TestDBInconsistenciesPeriodics(testlib_api.SqlTestCaseLight,
self.assertFalse( self.assertFalse(
self.fake_ovn_client.sync_ha_chassis_group.called) self.fake_ovn_client.sync_ha_chassis_group.called)
def test_check_for_ha_chassis_group(self): @mock.patch.object(utils, 'sync_ha_chassis_group')
def test_check_for_ha_chassis_group(self, mock_sync_ha_chassis_group):
self.fake_ovn_client.is_external_ports_supported.return_value = True self.fake_ovn_client.is_external_ports_supported.return_value = True
nb_idl = self.fake_ovn_client._nb_idl nb_idl = self.fake_ovn_client._nb_idl
@ -440,7 +441,7 @@ class TestDBInconsistenciesPeriodics(testlib_api.SqlTestCaseLight,
constants.OVN_NETWORK_NAME_EXT_ID_KEY: 'neutron-net1'}}) constants.OVN_NETWORK_NAME_EXT_ID_KEY: 'neutron-net1'}})
nb_idl.db_find_rows.return_value.execute.return_value = [p0, p1] nb_idl.db_find_rows.return_value.execute.return_value = [p0, p1]
self.fake_ovn_client.sync_ha_chassis_group.return_value = hcg0.uuid mock_sync_ha_chassis_group.return_value = hcg0.uuid
# Invoke the periodic method, it meant to run only once at startup # Invoke the periodic method, it meant to run only once at startup
# so NeverAgain will be raised at the end # so NeverAgain will be raised at the end
@ -449,16 +450,21 @@ class TestDBInconsistenciesPeriodics(testlib_api.SqlTestCaseLight,
# Assert sync_ha_chassis_group() is called for both networks # Assert sync_ha_chassis_group() is called for both networks
expected_calls = [ expected_calls = [
mock.call(mock.ANY, 'net0', mock.ANY), mock.call(mock.ANY, 'net0',
mock.call(mock.ANY, 'net1', mock.ANY)] self.fake_ovn_client._nb_idl,
self.fake_ovn_client.sync_ha_chassis_group.assert_has_calls( self.fake_ovn_client._sb_idl, mock.ANY),
expected_calls) mock.call(mock.ANY, 'net1',
self.fake_ovn_client._nb_idl,
self.fake_ovn_client._sb_idl, mock.ANY),
]
mock_sync_ha_chassis_group.assert_has_calls(expected_calls,
any_order=True)
# Assert set_lswitch_port() is only called for p1 because expected_calls = [
# the ha_chassis_group is different than what was returned mock.call('p0', ha_chassis_group=hcg0.uuid),
# by sync_ha_chassis_group() mock.call('p1', ha_chassis_group=hcg0.uuid)]
nb_idl.set_lswitch_port.assert_called_once_with( nb_idl.set_lswitch_port.assert_has_calls(expected_calls,
'p1', ha_chassis_group=hcg0.uuid) any_order=True)
def test_check_port_has_address_scope(self): def test_check_port_has_address_scope(self):
self.fake_ovn_client.is_external_ports_supported.return_value = True self.fake_ovn_client.is_external_ports_supported.return_value = True

View File

@ -2573,37 +2573,21 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase):
self.sb_ovn.get_gateway_chassis_from_cms_options.return_value = [ self.sb_ovn.get_gateway_chassis_from_cms_options.return_value = [
ch0, ch1, ch2, ch3, ch4] ch0, ch1, ch2, ch3, ch4]
fake_ha_ch = fakes.FakeOvsdbRow.create_one_ovsdb_row(
attrs={'chassis_name': ch2.name, 'priority': 1})
fake_ch_grp_uuid = 'fake-ha-ch-grp-uuid'
fake_ch_grp = fakes.FakeOvsdbRow.create_one_ovsdb_row(
attrs={'uuid': fake_ch_grp_uuid, 'ha_chassis': [fake_ha_ch]})
self.nb_ovn.ha_chassis_group_get.return_value.execute.return_value = (
fake_ch_grp)
# Invoke the method # Invoke the method
ret = self.mech_driver._ovn_client.sync_ha_chassis_group( hcg_cmd = ovn_utils.sync_ha_chassis_group(
self.context, fake_net['id'], fake_txn) self.context, fake_net['id'], self.nb_ovn, self.sb_ovn, fake_txn)
# Assert the UUID of the HA Chassis Group is returned
self.assertEqual(fake_ch_grp_uuid, ret)
# Assert it attempts to add the chassis group for that network # Assert it attempts to add the chassis group for that network
ha_ch_grp_name = ovn_utils.ovn_name(fake_net['id']) ha_ch_grp_name = ovn_utils.ovn_name(fake_net['id'])
ext_ids = {ovn_const.OVN_AZ_HINTS_EXT_ID_KEY: 'az0,az1,az2'}
self.nb_ovn.ha_chassis_group_add.assert_called_once_with( self.nb_ovn.ha_chassis_group_add.assert_called_once_with(
ha_ch_grp_name, may_exist=True) ha_ch_grp_name, may_exist=True, external_ids=ext_ids)
# Assert existing members that no longer belong to those
# AZs are removed
self.nb_ovn.ha_chassis_group_del_chassis.assert_called_once_with(
ha_ch_grp_name, ch2.name, if_exists=True)
# Assert that only Chassis belonging to the AZ hints are # Assert that only Chassis belonging to the AZ hints are
# added to the HA Chassis Group for that network # added to the HA Chassis Group for that network
expected_calls = [ expected_calls = [
mock.call(ha_ch_grp_name, ch0.name, priority=mock.ANY), mock.call(hcg_cmd, ch0.name, priority=mock.ANY),
mock.call(ha_ch_grp_name, ch1.name, priority=mock.ANY)] mock.call(hcg_cmd, ch1.name, priority=mock.ANY)]
self.nb_ovn.ha_chassis_group_add_chassis.assert_has_calls( self.nb_ovn.ha_chassis_group_add_chassis.assert_has_calls(
expected_calls, any_order=True) expected_calls, any_order=True)
@ -2625,36 +2609,21 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase):
self.sb_ovn.get_gateway_chassis_from_cms_options.return_value = [ self.sb_ovn.get_gateway_chassis_from_cms_options.return_value = [
ch0, ch1, ch2, ch3, ch4] ch0, ch1, ch2, ch3, ch4]
fake_ha_ch = fakes.FakeOvsdbRow.create_one_ovsdb_row(
attrs={'chassis_name': ch1.name, 'priority': 1})
fake_ch_grp_uuid = 'fake-ha-ch-grp-uuid'
fake_ch_grp = fakes.FakeOvsdbRow.create_one_ovsdb_row(
attrs={'uuid': fake_ch_grp_uuid, 'ha_chassis': [fake_ha_ch]})
self.nb_ovn.ha_chassis_group_get.return_value.execute.return_value = (
fake_ch_grp)
# Invoke the method # Invoke the method
ret = self.mech_driver._ovn_client.sync_ha_chassis_group( hcg_cmd = ovn_utils.sync_ha_chassis_group(
self.context, fake_net['id'], fake_txn) self.context, fake_net['id'], self.nb_ovn, self.sb_ovn, fake_txn)
# Assert the UUID of the HA Chassis Group is returned
self.assertEqual(fake_ch_grp_uuid, ret)
# Assert it attempts to add the chassis group for that network # Assert it attempts to add the chassis group for that network
ha_ch_grp_name = ovn_utils.ovn_name(fake_net['id']) ha_ch_grp_name = ovn_utils.ovn_name(fake_net['id'])
ext_ids = {ovn_const.OVN_AZ_HINTS_EXT_ID_KEY: ''}
self.nb_ovn.ha_chassis_group_add.assert_called_once_with( self.nb_ovn.ha_chassis_group_add.assert_called_once_with(
ha_ch_grp_name, may_exist=True) ha_ch_grp_name, may_exist=True, external_ids=ext_ids)
# Assert existing members that does belong to any AZ are removed
self.nb_ovn.ha_chassis_group_del_chassis.assert_called_once_with(
ha_ch_grp_name, ch1.name, if_exists=True)
# Assert that only Chassis that are gateways and DOES NOT # Assert that only Chassis that are gateways and DOES NOT
# belong to any AZs are added # belong to any AZs are added
expected_calls = [ expected_calls = [
mock.call(ha_ch_grp_name, ch2.name, priority=mock.ANY), mock.call(hcg_cmd, ch2.name, priority=mock.ANY),
mock.call(ha_ch_grp_name, ch3.name, priority=mock.ANY)] mock.call(hcg_cmd, ch3.name, priority=mock.ANY)]
self.nb_ovn.ha_chassis_group_add_chassis.assert_has_calls( self.nb_ovn.ha_chassis_group_add_chassis.assert_has_calls(
expected_calls, any_order=True) expected_calls, any_order=True)
@ -3967,8 +3936,7 @@ class TestOVNMechanismDriverSecurityGroup(MechDriverSetupBase,
@mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.' @mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.'
'ovn_client.OVNClient.is_external_ports_supported', 'ovn_client.OVNClient.is_external_ports_supported',
lambda *_: True) lambda *_: True)
@mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.' @mock.patch.object(ovn_utils, 'sync_ha_chassis_group')
'ovn_client.OVNClient.sync_ha_chassis_group')
def _test_create_port_with_vnic_type(self, vnic_type, sync_mock): def _test_create_port_with_vnic_type(self, vnic_type, sync_mock):
fake_grp = 'fake-default-ha-group-uuid' fake_grp = 'fake-default-ha-group-uuid'
sync_mock.return_value = fake_grp sync_mock.return_value = fake_grp
@ -3988,7 +3956,9 @@ class TestOVNMechanismDriverSecurityGroup(MechDriverSetupBase,
if vnic_type in ovn_const.EXTERNAL_PORT_TYPES: if vnic_type in ovn_const.EXTERNAL_PORT_TYPES:
self.assertEqual(ovn_const.LSP_TYPE_EXTERNAL, kwargs['type']) self.assertEqual(ovn_const.LSP_TYPE_EXTERNAL, kwargs['type'])
self.assertEqual(fake_grp, kwargs['ha_chassis_group']) self.assertEqual(fake_grp, kwargs['ha_chassis_group'])
sync_mock.assert_called_once_with(mock.ANY, net_id, mock.ANY) sync_mock.assert_called_once_with(
mock.ANY, net_id, self.mech_driver.nb_ovn,
self.mech_driver.sb_ovn, mock.ANY)
def test_create_port_with_vnic_direct(self): def test_create_port_with_vnic_direct(self):
self._test_create_port_with_vnic_type(portbindings.VNIC_DIRECT) self._test_create_port_with_vnic_type(portbindings.VNIC_DIRECT)

View File

@ -50,7 +50,7 @@ osprofiler>=2.3.0 # Apache-2.0
os-ken>=2.2.0 # Apache-2.0 os-ken>=2.2.0 # Apache-2.0
os-resource-classes>=1.1.0 # Apache-2.0 os-resource-classes>=1.1.0 # Apache-2.0
ovs>=2.10.0 # Apache-2.0 ovs>=2.10.0 # Apache-2.0
ovsdbapp>=1.16.0 # Apache-2.0 ovsdbapp>=2.2.1 # Apache-2.0
packaging>=20.4 # Apache-2.0 packaging>=20.4 # Apache-2.0
psutil>=5.3.0 # BSD psutil>=5.3.0 # BSD
pyroute2>=0.7.3;sys_platform!='win32' # Apache-2.0 (+ dual licensed GPL2) pyroute2>=0.7.3;sys_platform!='win32' # Apache-2.0 (+ dual licensed GPL2)