Merge "Don't reschedule hosts unless we need to"
This commit is contained in:
commit
8770aa5133
@ -10,6 +10,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import re
|
||||
|
||||
from neutron_lib.api.definitions import portbindings
|
||||
from neutron_lib import constants as const
|
||||
import six
|
||||
@ -163,6 +165,7 @@ FIP_ACTION_DISASSOCIATE = 'fip_disassociate'
|
||||
|
||||
# Loadbalancer constants
|
||||
LRP_PREFIX = "lrp-"
|
||||
RE_PORT_FROM_GWC = re.compile(r'(%s)([\w-]+)_([\w-]+)' % LRP_PREFIX)
|
||||
LB_VIP_PORT_PREFIX = "ovn-lb-vip-"
|
||||
LB_EXT_IDS_LS_REFS_KEY = 'ls_refs'
|
||||
LB_EXT_IDS_LR_REF_KEY = 'lr_ref'
|
||||
|
@ -458,3 +458,17 @@ def is_gateway_chassis(chassis):
|
||||
def get_port_capabilities(port):
|
||||
"""Return a list of port's capabilities"""
|
||||
return port.get(portbindings.PROFILE, {}).get('capabilities', [])
|
||||
|
||||
|
||||
def get_port_id_from_gwc_row(row):
|
||||
"""Return a port_id from gwc row
|
||||
|
||||
The Gateway_Chassis row stores router port_id in
|
||||
the row name attribute:
|
||||
|
||||
<prefix>-<port_id>_<chassis_id>
|
||||
|
||||
:param row: A Gateway_Chassis table row.
|
||||
:returns: String containing router port_id.
|
||||
"""
|
||||
return constants.RE_PORT_FROM_GWC.search(row.name).group(2)
|
||||
|
@ -434,22 +434,46 @@ class OvsdbNbOvnIdl(nb_impl_idl.OvnNbApiIdlImpl, Backend):
|
||||
except idlutils.RowNotFound:
|
||||
return []
|
||||
|
||||
def get_unhosted_gateways(self, port_physnet_dict, chassis_physnets,
|
||||
gw_chassis):
|
||||
unhosted_gateways = []
|
||||
for lrp in self._tables['Logical_Router_Port'].rows.values():
|
||||
if not lrp.name.startswith('lrp-'):
|
||||
def get_chassis_gateways(self, chassis_name):
|
||||
gw_chassis = self.db_find_rows(
|
||||
'Gateway_Chassis', ('chassis_name', '=', chassis_name))
|
||||
return gw_chassis.execute(check_error=True)
|
||||
|
||||
def get_unhosted_gateways(self, port_physnet_dict, chassis_with_physnets,
|
||||
all_gw_chassis):
|
||||
unhosted_gateways = set()
|
||||
for port, physnet in port_physnet_dict.items():
|
||||
lrp_name = '%s%s' % (ovn_const.LRP_PREFIX, port)
|
||||
original_state = self.get_gateway_chassis_binding(lrp_name)
|
||||
|
||||
# Filter out chassis that lost physnet, the cms option,
|
||||
# or has been deleted.
|
||||
actual_gw_chassis = [
|
||||
chassis for chassis in original_state
|
||||
if not utils.is_gateway_chassis_invalid(
|
||||
chassis, all_gw_chassis, physnet, chassis_with_physnets)]
|
||||
|
||||
# Check if gw ports are fully scheduled.
|
||||
if len(actual_gw_chassis) >= ovn_const.MAX_GW_CHASSIS:
|
||||
continue
|
||||
physnet = port_physnet_dict.get(lrp.name[len('lrp-'):])
|
||||
chassis_list = self._get_logical_router_port_gateway_chassis(lrp)
|
||||
is_max_gw_reached = len(chassis_list) < ovn_const.MAX_GW_CHASSIS
|
||||
for chassis_name, prio in chassis_list:
|
||||
# TODO(azbiswas): Handle the case when a chassis is no
|
||||
# longer valid. This may involve moving conntrack states,
|
||||
# so it needs to discussed in the OVN community first.
|
||||
if is_max_gw_reached or utils.is_gateway_chassis_invalid(
|
||||
chassis_name, gw_chassis, physnet, chassis_physnets):
|
||||
unhosted_gateways.append(lrp.name)
|
||||
|
||||
# If there are no gateways with 'enable-chassis-as-gw' cms option
|
||||
# then try to schedule on all gateways with physnets connected,
|
||||
# and filter required physnet.
|
||||
available_chassis = {
|
||||
c for c in all_gw_chassis or chassis_with_physnets.keys()
|
||||
if not utils.is_gateway_chassis_invalid(
|
||||
c, all_gw_chassis, physnet, chassis_with_physnets)}
|
||||
|
||||
if available_chassis == set(original_state):
|
||||
# The same situation as was before. Nothing
|
||||
# to be rescheduled.
|
||||
continue
|
||||
if not available_chassis:
|
||||
# There is no chassis that could host
|
||||
# this gateway.
|
||||
continue
|
||||
unhosted_gateways.add(lrp_name)
|
||||
return unhosted_gateways
|
||||
|
||||
def add_dhcp_options(self, subnet_id, port_id=None, may_exist=True,
|
||||
|
@ -82,11 +82,6 @@ class ChassisEvent(row_event.RowEvent):
|
||||
if not self.driver._ovn_client.is_external_ports_supported():
|
||||
return
|
||||
|
||||
# NOTE(lucasgomes): If the external_ids column wasn't updated
|
||||
# (meaning, Chassis "gateway" status didn't change) just returns
|
||||
if not hasattr(old, 'external_ids') and event == self.ROW_UPDATE:
|
||||
return
|
||||
|
||||
is_gw_chassis = utils.is_gateway_chassis(row)
|
||||
# If the Chassis being created is not a gateway, ignore it
|
||||
if not is_gw_chassis and event == self.ROW_CREATE:
|
||||
@ -123,6 +118,19 @@ class ChassisEvent(row_event.RowEvent):
|
||||
ovn_const.HA_CHASSIS_GROUP_DEFAULT_NAME,
|
||||
row.name, if_exists=True).execute(check_error=True)
|
||||
|
||||
def match_fn(self, event, row, old):
|
||||
if event != self.ROW_UPDATE:
|
||||
return True
|
||||
# NOTE(lucasgomes): If the external_ids column wasn't updated
|
||||
# (meaning, Chassis "gateway" status didn't change) just returns
|
||||
if not hasattr(old, 'external_ids') and event == self.ROW_UPDATE:
|
||||
return
|
||||
if (old.external_ids.get('ovn-bridge-mappings') !=
|
||||
row.external_ids.get('ovn-bridge-mappings')):
|
||||
return True
|
||||
f = utils.is_gateway_chassis
|
||||
return f(old) != f(row)
|
||||
|
||||
def run(self, event, row, old):
|
||||
host = row.hostname
|
||||
phy_nets = []
|
||||
@ -133,8 +141,33 @@ class ChassisEvent(row_event.RowEvent):
|
||||
phy_nets = list(mapping_dict)
|
||||
|
||||
self.driver.update_segment_host_mapping(host, phy_nets)
|
||||
|
||||
if utils.is_ovn_l3(self.l3_plugin):
|
||||
self.l3_plugin.schedule_unhosted_gateways()
|
||||
# If chassis lost physnet or has been
|
||||
# deleted we can limit the scope and
|
||||
# reschedule only ports from this chassis.
|
||||
# In other cases we need to reschedule all gw ports.
|
||||
kwargs = {'event_from_chassis': None}
|
||||
if event == self.ROW_DELETE:
|
||||
kwargs['event_from_chassis'] = row.name
|
||||
elif event == self.ROW_UPDATE:
|
||||
old_mappings = old.external_ids.get('ovn-bridge-mappings',
|
||||
set()) or set()
|
||||
new_mappings = row.external_ids.get('ovn-bridge-mappings',
|
||||
set()) or set()
|
||||
if old_mappings:
|
||||
old_mappings = set(old_mappings.split(','))
|
||||
if new_mappings:
|
||||
new_mappings = set(new_mappings.split(','))
|
||||
|
||||
mappings_removed = old_mappings - new_mappings
|
||||
mappings_added = new_mappings - old_mappings
|
||||
if mappings_removed and not mappings_added:
|
||||
# Mapping has been only removed. So we can
|
||||
# limit scope of rescheduling only to impacted
|
||||
# gateway chassis.
|
||||
kwargs['event_from_chassis'] = row.name
|
||||
self.l3_plugin.schedule_unhosted_gateways(**kwargs)
|
||||
|
||||
self.handle_ha_chassis_group_changes(event, row, old)
|
||||
|
||||
|
@ -314,24 +314,48 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
|
||||
if port['status'] != status:
|
||||
self._plugin.update_port_status(context, port['id'], status)
|
||||
|
||||
def schedule_unhosted_gateways(self):
|
||||
def schedule_unhosted_gateways(self, event_from_chassis=None):
|
||||
# GW ports and its physnets.
|
||||
port_physnet_dict = self._get_gateway_port_physnet_mapping()
|
||||
chassis_physnets = self._sb_ovn.get_chassis_and_physnets()
|
||||
cms = self._sb_ovn.get_gateway_chassis_from_cms_options()
|
||||
# Filter out unwanted ports in case of event.
|
||||
if event_from_chassis:
|
||||
gw_chassis = self._ovn.get_chassis_gateways(
|
||||
chassis_name=event_from_chassis)
|
||||
if not gw_chassis:
|
||||
return
|
||||
ports_impacted = []
|
||||
for gwc in gw_chassis:
|
||||
try:
|
||||
ports_impacted.append(utils.get_port_id_from_gwc_row(gwc))
|
||||
except AttributeError:
|
||||
# Malformed GWC format.
|
||||
pass
|
||||
port_physnet_dict = {
|
||||
k: v
|
||||
for k, v in port_physnet_dict.items()
|
||||
if k in ports_impacted}
|
||||
if not port_physnet_dict:
|
||||
return
|
||||
# All chassis with physnets configured.
|
||||
chassis_with_physnets = self._sb_ovn.get_chassis_and_physnets()
|
||||
# All chassis with enable_as_gw_chassis set
|
||||
all_gw_chassis = self._sb_ovn.get_gateway_chassis_from_cms_options()
|
||||
unhosted_gateways = self._ovn.get_unhosted_gateways(
|
||||
port_physnet_dict, chassis_physnets, cms)
|
||||
port_physnet_dict, chassis_with_physnets,
|
||||
all_gw_chassis)
|
||||
for g_name in unhosted_gateways:
|
||||
physnet = port_physnet_dict.get(g_name[len('lrp-'):])
|
||||
physnet = port_physnet_dict.get(g_name[len(ovn_const.LRP_PREFIX):])
|
||||
# Remove any invalid gateway chassis from the list, otherwise
|
||||
# we can have a situation where all existing_chassis are invalid
|
||||
existing_chassis = self._ovn.get_gateway_chassis_binding(g_name)
|
||||
master = existing_chassis[0] if existing_chassis else None
|
||||
existing_chassis = self.scheduler.filter_existing_chassis(
|
||||
nb_idl=self._ovn, gw_chassis=cms,
|
||||
physnet=physnet, chassis_physnets=chassis_physnets,
|
||||
nb_idl=self._ovn, gw_chassis=all_gw_chassis,
|
||||
physnet=physnet, chassis_physnets=chassis_with_physnets,
|
||||
existing_chassis=existing_chassis)
|
||||
candidates = self._ovn_client.get_candidates_for_scheduling(
|
||||
physnet, cms=cms, chassis_physnets=chassis_physnets)
|
||||
physnet, cms=all_gw_chassis,
|
||||
chassis_physnets=chassis_with_physnets)
|
||||
chassis = self.scheduler.select(
|
||||
self._ovn, self._sb_ovn, g_name, candidates=candidates,
|
||||
existing_chassis=existing_chassis)
|
||||
|
@ -135,11 +135,28 @@ class TestRouter(base.TestOVNFunctionalBase):
|
||||
def test_gateway_chassis_with_cms_and_no_bridge_mappings(self):
|
||||
# chassis1 is having proper bridge mappings.
|
||||
# chassis3 is having enable-chassis-as-gw, but no bridge mappings.
|
||||
# Test if chassis1 is selected as candidate or not.
|
||||
self.chassis3 = self.add_fake_chassis(
|
||||
'ovs-host3',
|
||||
external_ids={'ovn-cms-options': 'enable-chassis-as-gw'})
|
||||
self._check_gateway_chassis_candidates([self.chassis1])
|
||||
ovn_client = self.l3_plugin._ovn_client
|
||||
ext1 = self._create_ext_network(
|
||||
'ext1', 'vlan', 'physnet1', 1, "10.0.0.1", "10.0.0.0/24")
|
||||
# As we have 'gateways' in the system, but without required
|
||||
# chassis we should not schedule gw in that case at all.
|
||||
self._set_redirect_chassis_to_invalid_chassis(ovn_client)
|
||||
with mock.patch.object(ovn_client._ovn_scheduler, 'select',
|
||||
return_value=[self.chassis1]), \
|
||||
mock.patch.object(self.l3_plugin.scheduler, 'select',
|
||||
side_effect=[self.chassis1]):
|
||||
gw_info = {'network_id': ext1['network']['id']}
|
||||
self._create_router('router1', gw_info=gw_info)
|
||||
|
||||
with mock.patch.object(
|
||||
ovn_client._nb_idl, 'update_lrouter_port') as ulrp:
|
||||
self.l3_plugin.schedule_unhosted_gateways()
|
||||
# Make sure that we don't schedule on chassis3
|
||||
# and do not updated the lrp port.
|
||||
ulrp.assert_not_called()
|
||||
|
||||
def test_gateway_chassis_with_bridge_mappings_and_no_cms(self):
|
||||
# chassis1 is configured with proper bridge mappings,
|
||||
|
@ -76,6 +76,7 @@ class FakeOvsdbNbOvnIdl(object):
|
||||
self.delete_address_set = mock.Mock()
|
||||
self.update_address_set = mock.Mock()
|
||||
self.get_all_chassis_gateway_bindings = mock.Mock()
|
||||
self.get_chassis_gateways = mock.Mock()
|
||||
self.get_gateway_chassis_binding = mock.Mock()
|
||||
self.get_unhosted_gateways = mock.Mock()
|
||||
self.add_dhcp_options = mock.Mock()
|
||||
|
@ -580,44 +580,97 @@ class TestNBImplIdlOvn(TestDBImplIdlOvn):
|
||||
|
||||
def test_get_unhosted_gateways(self):
|
||||
self._load_nb_db()
|
||||
# Test only host-1 in the valid list
|
||||
# Port physnet-dict
|
||||
port_physnet_dict = {
|
||||
'orp-id-a1': 'physnet1', # scheduled
|
||||
'orp-id-a2': 'physnet1', # scheduled
|
||||
'orp-id-a3': 'physnet1', # not scheduled
|
||||
'orp-id-b6': 'physnet2'} # not scheduled
|
||||
# Test only that orp-id-a3 is to be scheduled.
|
||||
# Rest ports don't have required chassis (physnet2)
|
||||
# or are already scheduled.
|
||||
unhosted_gateways = self.nb_ovn_idl.get_unhosted_gateways(
|
||||
{}, {'host-1': 'physnet1'}, [])
|
||||
expected = ['lrp-orp-id-a1', 'lrp-orp-id-a2',
|
||||
'lrp-orp-id-a3', 'lrp-orp-id-b2']
|
||||
port_physnet_dict, {'host-1': 'physnet1', 'host-2': 'physnet3'},
|
||||
['host-1', 'host-2'])
|
||||
expected = ['lrp-orp-id-a3']
|
||||
self.assertItemsEqual(unhosted_gateways, expected)
|
||||
# Test both host-1, host-2 in valid list
|
||||
unhosted_gateways = self.nb_ovn_idl.get_unhosted_gateways(
|
||||
{}, {'host-1': 'physnet1', 'host-2': 'physnet2'}, [])
|
||||
self.assertItemsEqual(unhosted_gateways, expected)
|
||||
# Schedule unhosted_gateways on host-2
|
||||
for unhosted_gateway in unhosted_gateways:
|
||||
router_row = self._find_ovsdb_fake_row(self.lrp_table,
|
||||
'name', unhosted_gateway)
|
||||
setattr(router_row, 'options', {
|
||||
ovn_const.OVN_GATEWAY_CHASSIS_KEY: 'host-2'})
|
||||
unhosted_gateways = self.nb_ovn_idl.get_unhosted_gateways(
|
||||
{}, {'host-1': 'physnet1', 'host-2': 'physnet2'}, [])
|
||||
port_physnet_dict, {'host-1': 'physnet1', 'host-2': 'physnet2'},
|
||||
['host-1', 'host-2'])
|
||||
expected = ['lrp-orp-id-a3', 'lrp-orp-id-b6']
|
||||
self.assertItemsEqual(unhosted_gateways, expected)
|
||||
|
||||
def test_unhosted_gateway_max_chassis(self):
|
||||
def test_get_unhosted_gateways_deleted_physnet(self):
|
||||
self._load_nb_db()
|
||||
# The LRP is on host-2 now
|
||||
router_row = self._find_ovsdb_fake_row(self.lrp_table,
|
||||
'name', 'lrp-orp-id-a1')
|
||||
setattr(router_row, 'options', {
|
||||
ovn_const.OVN_GATEWAY_CHASSIS_KEY: 'host-2'})
|
||||
port_physnet_dict = {'orp-id-a1': 'physnet1'}
|
||||
# Lets spoof that physnet1 is deleted from host-2.
|
||||
unhosted_gateways = self.nb_ovn_idl.get_unhosted_gateways(
|
||||
port_physnet_dict, {'host-1': 'physnet1', 'host-2': 'physnet3'},
|
||||
['host-1', 'host-2'])
|
||||
# Make sure that lrp is rescheduled, because host-1 has physet1
|
||||
expected = ['lrp-orp-id-a1']
|
||||
self.assertItemsEqual(unhosted_gateways, expected)
|
||||
# Spoof that there is no valid host with required physnet.
|
||||
unhosted_gateways = self.nb_ovn_idl.get_unhosted_gateways(
|
||||
port_physnet_dict, {'host-1': 'physnet4', 'host-2': 'physnet3'},
|
||||
['host-1', 'host-2'])
|
||||
self.assertItemsEqual(unhosted_gateways, [])
|
||||
|
||||
def _test_get_unhosted_gateway_max_chassis(self, r):
|
||||
gw_chassis_table = fakes.FakeOvsdbTable.create_one_ovsdb_table()
|
||||
self._tables['Gateway_Chassis'] = gw_chassis_table
|
||||
gw_chassis = collections.namedtuple('gw_chassis',
|
||||
'chassis_name priority')
|
||||
TestNBImplIdlOvn.fake_set['lrouter_ports'][0]['gateway_chassis'] = [
|
||||
gw_chassis(chassis_name='host-%s' % x,
|
||||
priority=x) for x in range(1, 6)]
|
||||
for port in TestNBImplIdlOvn.fake_set['lrouter_ports'][1:]:
|
||||
port['gateway_chassis'] = []
|
||||
priority=x) for x in r]
|
||||
self._load_nb_db()
|
||||
self.port_physnet_dict = {'orp-id-a1': 'physnet1'}
|
||||
|
||||
def test_get_unhosted_gateway_max_chassis_lack_of_chassis(self):
|
||||
self._test_get_unhosted_gateway_max_chassis(r=(1, 3, 5))
|
||||
unhosted_gateways = self.nb_ovn_idl.get_unhosted_gateways(
|
||||
{}, {'host-1': 'physnet1', 'host-2': 'physnet2',
|
||||
'host-3': 'physnet1', 'host-4': 'physnet2',
|
||||
'host-5': 'physnet1', 'host-6': 'physnet2'}, [])
|
||||
self.port_physnet_dict,
|
||||
{'host-1': 'physnet1', 'host-2': 'physnet2',
|
||||
'host-3': 'physnet1', 'host-4': 'physnet2',
|
||||
'host-5': 'physnet1', 'host-6': 'physnet2'},
|
||||
['host-%s' % x for x in range(1, 7)])
|
||||
# We don't have required number of chassis
|
||||
expected = []
|
||||
self.assertItemsEqual(unhosted_gateways, expected)
|
||||
|
||||
def test_get_unhosted_gateway_max_chassis(self):
|
||||
# We have required number of chassis, and lrp
|
||||
# is hosted everywhere.
|
||||
self._test_get_unhosted_gateway_max_chassis(r=range(1, 6))
|
||||
unhosted_gateways = self.nb_ovn_idl.get_unhosted_gateways(
|
||||
self.port_physnet_dict,
|
||||
{'host-1': 'physnet1', 'host-2': 'physnet1',
|
||||
'host-3': 'physnet1', 'host-4': 'physnet1',
|
||||
'host-5': 'physnet1', 'host-6': 'physnet1'},
|
||||
['host-%s' % x for x in range(1, 7)])
|
||||
expected = []
|
||||
self.assertItemsEqual(unhosted_gateways, expected)
|
||||
|
||||
def test_get_unhosed_gateway_schedule_to_max(self):
|
||||
# The LRP is not yet scheduled on all chassis
|
||||
# but we can schedule on new chassis now.
|
||||
self._test_get_unhosted_gateway_max_chassis(r=range(1, 4))
|
||||
unhosted_gateways = self.nb_ovn_idl.get_unhosted_gateways(
|
||||
self.port_physnet_dict,
|
||||
{'host-1': 'physnet1', 'host-2': 'physnet1',
|
||||
'host-3': 'physnet1', 'host-4': 'physnet1',
|
||||
'host-5': 'physnet1', 'host-6': 'physnet1'},
|
||||
['host-%s' % x for x in range(1, 7)])
|
||||
expected = ['lrp-orp-id-a1']
|
||||
self.assertItemsEqual(unhosted_gateways, expected)
|
||||
|
||||
def test_get_subnet_dhcp_options(self):
|
||||
self._load_nb_db()
|
||||
subnet_options = self.nb_ovn_idl.get_subnet_dhcp_options(
|
||||
|
@ -459,17 +459,15 @@ class TestOvnSbIdlNotifyHandler(test_mech_driver.OVNMechanismDriverTestCase):
|
||||
self._test_chassis_helper('create', self.row_json)
|
||||
self.driver.update_segment_host_mapping.assert_called_once_with(
|
||||
'fake-hostname', ['fake-phynet1'])
|
||||
self.assertEqual(
|
||||
1,
|
||||
self.l3_plugin.schedule_unhosted_gateways.call_count)
|
||||
self.l3_plugin.schedule_unhosted_gateways.assert_called_once_with(
|
||||
event_from_chassis=None)
|
||||
|
||||
def test_chassis_delete_event(self):
|
||||
self._test_chassis_helper('delete', self.row_json)
|
||||
self.driver.update_segment_host_mapping.assert_called_once_with(
|
||||
'fake-hostname', [])
|
||||
self.assertEqual(
|
||||
1,
|
||||
self.l3_plugin.schedule_unhosted_gateways.call_count)
|
||||
self.l3_plugin.schedule_unhosted_gateways.assert_called_once_with(
|
||||
event_from_chassis='fake-name')
|
||||
|
||||
def test_chassis_update_event(self):
|
||||
old_row_json = copy.deepcopy(self.row_json)
|
||||
@ -478,9 +476,54 @@ class TestOvnSbIdlNotifyHandler(test_mech_driver.OVNMechanismDriverTestCase):
|
||||
self._test_chassis_helper('update', self.row_json, old_row_json)
|
||||
self.driver.update_segment_host_mapping.assert_called_once_with(
|
||||
'fake-hostname', ['fake-phynet1'])
|
||||
self.assertEqual(
|
||||
1,
|
||||
self.l3_plugin.schedule_unhosted_gateways.call_count)
|
||||
self.l3_plugin.schedule_unhosted_gateways.assert_called_once_with(
|
||||
event_from_chassis=None)
|
||||
|
||||
def test_chassis_update_event_reschedule_not_needed(self):
|
||||
self.row_json['external_ids'][1].append(['foo_field', 'foo_value_new'])
|
||||
old_row_json = copy.deepcopy(self.row_json)
|
||||
old_row_json['external_ids'][1][1][1] = (
|
||||
"foo_value")
|
||||
self._test_chassis_helper('update', self.row_json, old_row_json)
|
||||
self.driver.update_segment_host_mapping.assert_not_called()
|
||||
self.l3_plugin.schedule_unhosted_gateways.assert_not_called()
|
||||
|
||||
def test_chassis_update_event_reschedule_lost_physnet(self):
|
||||
old_row_json = copy.deepcopy(self.row_json)
|
||||
self.row_json['external_ids'][1][0][1] = ''
|
||||
self._test_chassis_helper('update', self.row_json, old_row_json)
|
||||
self.l3_plugin.schedule_unhosted_gateways.assert_called_once_with(
|
||||
event_from_chassis='fake-name')
|
||||
|
||||
def test_chassis_update_event_reschedule_add_physnet(self):
|
||||
old_row_json = copy.deepcopy(self.row_json)
|
||||
self.row_json['external_ids'][1][0][1] += ',foo_physnet:foo_br'
|
||||
self._test_chassis_helper('update', self.row_json, old_row_json)
|
||||
self.driver.update_segment_host_mapping.assert_called_once_with(
|
||||
'fake-hostname', ['fake-phynet1', 'foo_physnet'])
|
||||
self.l3_plugin.schedule_unhosted_gateways.assert_called_once_with(
|
||||
event_from_chassis=None)
|
||||
|
||||
def test_chassis_update_event_reschedule_add_and_remove_physnet(self):
|
||||
old_row_json = copy.deepcopy(self.row_json)
|
||||
self.row_json['external_ids'][1][0][1] = 'foo_physnet:foo_br'
|
||||
self._test_chassis_helper('update', self.row_json, old_row_json)
|
||||
self.driver.update_segment_host_mapping.assert_called_once_with(
|
||||
'fake-hostname', ['foo_physnet'])
|
||||
self.l3_plugin.schedule_unhosted_gateways.assert_called_once_with(
|
||||
event_from_chassis=None)
|
||||
|
||||
def test_chassis_update_empty_no_external_ids(self):
|
||||
old_row_json = copy.deepcopy(self.row_json)
|
||||
old_row_json.pop('external_ids')
|
||||
with mock.patch(
|
||||
'neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.'
|
||||
'ovsdb_monitor.ChassisEvent.'
|
||||
'handle_ha_chassis_group_changes') as mock_ha:
|
||||
self._test_chassis_helper('update', self.row_json, old_row_json)
|
||||
self.driver.update_segment_host_mapping.assert_not_called()
|
||||
self.l3_plugin.schedule_unhosted_gateways.assert_not_called()
|
||||
mock_ha.assert_not_called()
|
||||
|
||||
|
||||
class TestChassisEvent(base.BaseTestCase):
|
||||
@ -558,12 +601,3 @@ class TestChassisEvent(base.BaseTestCase):
|
||||
# after it became a Gateway chassis
|
||||
self._test_handle_ha_chassis_group_changes_create(
|
||||
self.event.ROW_UPDATE)
|
||||
|
||||
def test_handle_ha_chassis_group_changes_update_ext_id_not_found(self):
|
||||
self.is_gw_ch_mock.side_effect = (True, False)
|
||||
old = fakes.FakeOvsdbTable.create_one_ovsdb_table(
|
||||
attrs={'name': 'SpongeBob'})
|
||||
self.assertIsNone(self.event.handle_ha_chassis_group_changes(
|
||||
self.event.ROW_UPDATE, mock.Mock(), old))
|
||||
self.assertFalse(self.nb_ovn.ha_chassis_group_add_chassis.called)
|
||||
self.assertFalse(self.nb_ovn.ha_chassis_group_del_chassis.called)
|
||||
|
@ -1354,16 +1354,14 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
@mock.patch('neutron.services.ovn_l3.plugin.OVNL3RouterPlugin.'
|
||||
'_get_gateway_port_physnet_mapping')
|
||||
def test_schedule_unhosted_gateways(self, get_gppm):
|
||||
physnet_dict = {'foo-1': 'physnet1',
|
||||
'foo-2': 'physnet1',
|
||||
'foo-3': 'physnet1'}
|
||||
unhosted_gws = ['lrp-foo-1', 'lrp-foo-2', 'lrp-foo-3']
|
||||
get_gppm.return_value = {k[len(ovn_const.LRP_PREFIX):]: 'physnet1'
|
||||
for k in unhosted_gws}
|
||||
chassis_mappings = {
|
||||
'chassis1': ['physnet1'],
|
||||
'chassis2': ['physnet1'],
|
||||
'chassis3': ['physnet1']}
|
||||
chassis = ['chassis1', 'chassis2', 'chassis3']
|
||||
get_gppm.return_value = physnet_dict
|
||||
self.sb_idl().get_chassis_and_physnets.return_value = (
|
||||
chassis_mappings)
|
||||
self.sb_idl().get_gateway_chassis_from_cms_options.return_value = (
|
||||
@ -1408,6 +1406,34 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
mock.call('lrp-foo-3',
|
||||
gateway_chassis=['chassis3', 'chassis2', 'chassis1'])])
|
||||
|
||||
@mock.patch('neutron.services.ovn_l3.plugin.OVNL3RouterPlugin.'
|
||||
'_get_gateway_port_physnet_mapping')
|
||||
def test_schedule_unhosted_gateways_on_event_no_gw_chassis(self, get_gppm):
|
||||
unhosted_gws = ['lrp-foo-1', 'lrp-foo-2', 'lrp-foo-3']
|
||||
get_gppm.return_value = {k[len(ovn_const.LRP_PREFIX):]: 'physnet1'
|
||||
for k in unhosted_gws}
|
||||
self.nb_idl().get_chassis_gateways.return_value = []
|
||||
self.l3_inst.schedule_unhosted_gateways(event_from_chassis='chassis4')
|
||||
self.nb_idl().get_unhosted_gateways.assert_not_called()
|
||||
|
||||
@mock.patch('neutron.services.ovn_l3.plugin.OVNL3RouterPlugin.'
|
||||
'_get_gateway_port_physnet_mapping')
|
||||
def test_schedule_unhosted_gateways_on_event(self, get_gppm):
|
||||
unhosted_gws = ['lrp-foo-1', 'lrp-foo-2', 'lrp-foo-3']
|
||||
get_gppm.return_value = {k[len(ovn_const.LRP_PREFIX):]: 'physnet1'
|
||||
for k in unhosted_gws}
|
||||
foo_gw = fake_resources.FakeOvsdbRow.create_one_ovsdb_row(
|
||||
attrs={'name': 'lrp-foo-1_chassis1',
|
||||
'chassis_name': 'chassis1'})
|
||||
self.nb_idl().get_chassis_gateways.return_value = [
|
||||
foo_gw]
|
||||
self.nb_idl().get_unhosted_gateways.return_value = []
|
||||
# Fake that rescheduling is executed on chassis event
|
||||
self.l3_inst.schedule_unhosted_gateways(event_from_chassis='chassis1')
|
||||
# Validate that only foo-1 port is beign rescheduled.
|
||||
self.nb_idl().get_unhosted_gateways.assert_called_once_with(
|
||||
{'foo-1': 'physnet1'}, mock.ANY, mock.ANY)
|
||||
|
||||
@mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_network')
|
||||
@mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.'
|
||||
'ovn_client.OVNClient._get_router_ports')
|
||||
|
Loading…
Reference in New Issue
Block a user