diff --git a/neutron/common/ovn/utils.py b/neutron/common/ovn/utils.py index 684227d4068..d7aa1369836 100644 --- a/neutron/common/ovn/utils.py +++ b/neutron/common/ovn/utils.py @@ -14,6 +14,7 @@ import collections import copy import inspect import os +import random import netaddr from neutron_lib.api.definitions import external_net @@ -34,6 +35,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 rowview from ovsdbapp import constants as ovsdbapp_const import tenacity @@ -853,3 +855,76 @@ def get_ovn_chassis_other_config(chassis): return chassis.other_config except AttributeError: 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 diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/commands.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/commands.py index b3a7bdca807..3266de09fd4 100644 --- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/commands.py +++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/commands.py @@ -15,6 +15,7 @@ from oslo_utils import timeutils from ovsdbapp.backend.ovs_idl import command from ovsdbapp.backend.ovs_idl import idlutils +from ovsdbapp import utils as ovsdbapp_utils from neutron._i18n import _ from neutron.common.ovn import constants as ovn_const @@ -137,6 +138,14 @@ class AddLSwitchPortCommand(command.BaseCommand): port.dhcpv6_options = dhcpv6_options else: 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(): setattr(port, col, val) # add the newly created port to existing lswitch @@ -202,6 +211,21 @@ class SetLSwitchPortCommand(command.BaseCommand): external_ids[k] = v 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(): setattr(port, col, val) diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py index 7f4b15c0eee..9927767997c 100644 --- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py +++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py @@ -603,15 +603,10 @@ 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 = self._ovn_client.sync_ha_chassis_group( - context, network_id, txn) - try: - port_ha_ch_uuid = port.ha_chassis_group[0].uuid - 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)) + ha_ch_grp = utils.sync_ha_chassis_group( + context, network_id, self._nb_idl, self._sb_idl, txn) + txn.add(self._nb_idl.set_lswitch_port( + port.name, ha_chassis_group=ha_ch_grp)) self._delete_default_ha_chassis_group(txn) diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py index b34ca51dcee..506d5482461 100644 --- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py +++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py @@ -16,7 +16,6 @@ import collections import copy import datetime -import random import netaddr from neutron_lib.api.definitions import l3 @@ -447,76 +446,6 @@ class OVNClient(object): 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): dhcpv4_options = [] dhcpv6_options = [] @@ -598,9 +527,9 @@ class OVNClient(object): if (self.is_external_ports_supported() and port_info.type == ovn_const.LSP_TYPE_EXTERNAL): - kwargs['ha_chassis_group'] = ( - self.sync_ha_chassis_group( - context, port['network_id'], txn)) + kwargs['ha_chassis_group'] = utils.sync_ha_chassis_group( + context, port['network_id'], self._nb_idl, self._sb_idl, + txn) # NOTE(mjozefcz): Do not set addresses if the port is not # 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 port_info.type == ovn_const.LSP_TYPE_EXTERNAL: columns_dict['ha_chassis_group'] = ( - self.sync_ha_chassis_group( - context, port['network_id'], txn)) + utils.sync_ha_chassis_group( + context, port['network_id'], self._nb_idl, + self._sb_idl, txn)) else: # Clear the ha_chassis_group field columns_dict['ha_chassis_group'] = [] @@ -2068,7 +1998,9 @@ class OVNClient(object): neutron_net_azs = lswitch_params['external_ids'].get( ovn_const.OVN_AZ_HINTS_EXT_ID_KEY, '') 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 segments = segments_db.get_network_segments(context, network['id']) diff --git a/neutron/tests/functional/common/ovn/test_utils.py b/neutron/tests/functional/common/ovn/test_utils.py index 95f8e889617..ffdd6869fab 100644 --- a/neutron/tests/functional/common/ovn/test_utils.py +++ b/neutron/tests/functional/common/ovn/test_utils.py @@ -12,6 +12,10 @@ # License for the specific language governing permissions and limitations # 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 utils from neutron.tests.functional import base @@ -58,3 +62,56 @@ class TestCreateNeutronPgDrop(base.TestOVNFunctionalBase): self.assertEqual(directions[0], acl2.direction) self.assertEqual('drop', acl2.action) 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) diff --git a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl.py b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl.py index 8ae4f969f72..d75be57282d 100644 --- a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl.py +++ b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl.py @@ -15,6 +15,7 @@ import copy import uuid +from oslo_utils import uuidutils from ovsdbapp.backend.ovs_idl import connection from ovsdbapp import constants as const from ovsdbapp import event as ovsdb_event @@ -269,6 +270,43 @@ class TestNbApi(BaseOvnIdlTest): self.assertIn((lb_match['name'], lb_match['external_ids']), 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): @classmethod diff --git a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py index 6cd025045f4..256e47a5c06 100644 --- a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py +++ b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py @@ -74,7 +74,7 @@ class _TestMaintenanceHelper(base.TestOVNFunctionalBase): return self.deserialize(self.fmt, res)['network'] def _create_port(self, name, net_id, security_groups=None, - device_owner=None): + device_owner=None, vnic_type=None): data = {'port': {'name': name, 'network_id': net_id}} @@ -84,6 +84,9 @@ class _TestMaintenanceHelper(base.TestOVNFunctionalBase): if device_owner is not None: 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) res = req.get_response(self.api) return self.deserialize(self.fmt, res)['port'] @@ -949,6 +952,26 @@ class TestMaintenance(_TestMaintenanceHelper): # Assert load balancer for port forwarding is gone 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, test_log_driver.LogApiTestCaseBase): diff --git a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py index e05f324deb4..dbc9f274e9c 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py +++ b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py @@ -416,7 +416,8 @@ class TestDBInconsistenciesPeriodics(testlib_api.SqlTestCaseLight, self.assertFalse( 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 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'}}) 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 # 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 expected_calls = [ - mock.call(mock.ANY, 'net0', mock.ANY), - mock.call(mock.ANY, 'net1', mock.ANY)] - self.fake_ovn_client.sync_ha_chassis_group.assert_has_calls( - expected_calls) + mock.call(mock.ANY, 'net0', + self.fake_ovn_client._nb_idl, + self.fake_ovn_client._sb_idl, mock.ANY), + 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 - # the ha_chassis_group is different than what was returned - # by sync_ha_chassis_group() - nb_idl.set_lswitch_port.assert_called_once_with( - 'p1', ha_chassis_group=hcg0.uuid) + expected_calls = [ + mock.call('p0', ha_chassis_group=hcg0.uuid), + mock.call('p1', ha_chassis_group=hcg0.uuid)] + nb_idl.set_lswitch_port.assert_has_calls(expected_calls, + any_order=True) def test_check_port_has_address_scope(self): self.fake_ovn_client.is_external_ports_supported.return_value = True diff --git a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py index fb8a747919b..9ebd918ca95 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py +++ b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py @@ -2572,37 +2572,21 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase): self.sb_ovn.get_gateway_chassis_from_cms_options.return_value = [ 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 - ret = self.mech_driver._ovn_client.sync_ha_chassis_group( - self.context, fake_net['id'], fake_txn) - - # Assert the UUID of the HA Chassis Group is returned - self.assertEqual(fake_ch_grp_uuid, ret) + hcg_cmd = ovn_utils.sync_ha_chassis_group( + self.context, fake_net['id'], self.nb_ovn, self.sb_ovn, fake_txn) # Assert it attempts to add the chassis group for that network 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( - ha_ch_grp_name, may_exist=True) - - # 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) + ha_ch_grp_name, may_exist=True, external_ids=ext_ids) # Assert that only Chassis belonging to the AZ hints are # added to the HA Chassis Group for that network expected_calls = [ - mock.call(ha_ch_grp_name, ch0.name, priority=mock.ANY), - mock.call(ha_ch_grp_name, ch1.name, priority=mock.ANY)] + mock.call(hcg_cmd, ch0.name, priority=mock.ANY), + mock.call(hcg_cmd, ch1.name, priority=mock.ANY)] self.nb_ovn.ha_chassis_group_add_chassis.assert_has_calls( expected_calls, any_order=True) @@ -2624,36 +2608,21 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase): self.sb_ovn.get_gateway_chassis_from_cms_options.return_value = [ 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 - ret = self.mech_driver._ovn_client.sync_ha_chassis_group( - self.context, fake_net['id'], fake_txn) - - # Assert the UUID of the HA Chassis Group is returned - self.assertEqual(fake_ch_grp_uuid, ret) + hcg_cmd = ovn_utils.sync_ha_chassis_group( + self.context, fake_net['id'], self.nb_ovn, self.sb_ovn, fake_txn) # Assert it attempts to add the chassis group for that network 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( - ha_ch_grp_name, may_exist=True) - - # 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) + ha_ch_grp_name, may_exist=True, external_ids=ext_ids) # Assert that only Chassis that are gateways and DOES NOT # belong to any AZs are added expected_calls = [ - mock.call(ha_ch_grp_name, ch2.name, priority=mock.ANY), - mock.call(ha_ch_grp_name, ch3.name, priority=mock.ANY)] + mock.call(hcg_cmd, ch2.name, priority=mock.ANY), + mock.call(hcg_cmd, ch3.name, priority=mock.ANY)] self.nb_ovn.ha_chassis_group_add_chassis.assert_has_calls( expected_calls, any_order=True) @@ -3966,8 +3935,7 @@ class TestOVNMechanismDriverSecurityGroup(MechDriverSetupBase, @mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.' 'ovn_client.OVNClient.is_external_ports_supported', lambda *_: True) - @mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.' - 'ovn_client.OVNClient.sync_ha_chassis_group') + @mock.patch.object(ovn_utils, 'sync_ha_chassis_group') 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 @@ -3987,7 +3955,9 @@ class TestOVNMechanismDriverSecurityGroup(MechDriverSetupBase, if vnic_type in ovn_const.EXTERNAL_PORT_TYPES: 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, 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): self._test_create_port_with_vnic_type(portbindings.VNIC_DIRECT) diff --git a/requirements.txt b/requirements.txt index 59c16d4be51..fe0bbf0b0f0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -50,7 +50,7 @@ osprofiler>=2.3.0 # Apache-2.0 os-ken>=2.2.0 # Apache-2.0 os-resource-classes>=1.1.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 psutil>=5.3.0 # BSD pyroute2>=0.7.3;sys_platform!='win32' # Apache-2.0 (+ dual licensed GPL2)