[OVN] Reschedule router GW chassis when AZ updated
when chassis's available zone updated, triger rescheduler router gateway chassis. Closes-bug: #1958225 Change-Id: If7cf55f2c12b388fc34fa942b6b0bf70cb9eb866
This commit is contained in:
parent
3f0b82f84a
commit
b5253b224b
|
@ -498,7 +498,8 @@ def ovn_metadata_name(id_):
|
|||
|
||||
|
||||
def is_gateway_chassis_invalid(chassis_name, gw_chassis,
|
||||
physnet, chassis_physnets):
|
||||
physnet, chassis_physnets,
|
||||
az_hints, chassis_with_azs):
|
||||
"""Check if gateway chassis is invalid
|
||||
|
||||
@param chassis_name: gateway chassis name
|
||||
|
@ -509,6 +510,10 @@ def is_gateway_chassis_invalid(chassis_name, gw_chassis,
|
|||
@type physnet: string
|
||||
@param chassis_physnets: Dictionary linking chassis with their physnets
|
||||
@type chassis_physnets: {}
|
||||
@param az_hints: available zone hints associated to chassis_name
|
||||
@type az_hints: []
|
||||
@param chassis_with_azs: Dictionary linking chassis with their azs
|
||||
@type chassis_with_azs: {}
|
||||
@return Boolean
|
||||
"""
|
||||
|
||||
|
@ -520,6 +525,9 @@ def is_gateway_chassis_invalid(chassis_name, gw_chassis,
|
|||
return True
|
||||
elif gw_chassis and chassis_name not in gw_chassis:
|
||||
return True
|
||||
elif az_hints and not set(az_hints) & set(chassis_with_azs.get(
|
||||
chassis_name, [])):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
|
|
|
@ -293,13 +293,14 @@ class API(api.API, metaclass=abc.ABCMeta):
|
|||
|
||||
@abc.abstractmethod
|
||||
def get_unhosted_gateways(self, port_physnet_dict, chassis_physnets,
|
||||
gw_chassis):
|
||||
gw_chassis, chassis_with_azs):
|
||||
"""Return a list of gateways not hosted on chassis
|
||||
|
||||
:param port_physnet_dict: Dictionary of gateway ports and their physnet
|
||||
:param chassis_physnets: Dictionary of chassis and physnets
|
||||
:param gw_chassis: List of gateway chassis provided by admin
|
||||
through ovn-cms-options
|
||||
:param chassis_with_azs: Dictionary of chassis and available zones
|
||||
:returns: List of gateways not hosted on a valid
|
||||
chassis
|
||||
"""
|
||||
|
|
|
@ -513,24 +513,42 @@ class OvsdbNbOvnIdl(nb_impl_idl.OvnNbApiIdlImpl, Backend):
|
|||
except idlutils.RowNotFound:
|
||||
return []
|
||||
|
||||
def get_gateway_chassis_az_hints(self, gateway_name):
|
||||
lrp = self.lookup('Logical_Router_Port', gateway_name,
|
||||
default=None)
|
||||
if not lrp:
|
||||
return []
|
||||
router_id = lrp.external_ids.get(
|
||||
ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY, "")
|
||||
lrouter = self.lookup('Logical_Router', utils.ovn_name(router_id),
|
||||
default=None)
|
||||
if not lrouter:
|
||||
return []
|
||||
az_string = lrouter.external_ids.get(
|
||||
ovn_const.OVN_AZ_HINTS_EXT_ID_KEY, "")
|
||||
if not az_string:
|
||||
return []
|
||||
return az_string.split(",")
|
||||
|
||||
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):
|
||||
all_gw_chassis, chassis_with_azs):
|
||||
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)
|
||||
|
||||
az_hints = self.get_gateway_chassis_az_hints(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)]
|
||||
chassis, all_gw_chassis, physnet, chassis_with_physnets,
|
||||
az_hints, chassis_with_azs)]
|
||||
|
||||
# Check if gw ports are fully scheduled.
|
||||
if len(actual_gw_chassis) >= ovn_const.MAX_GW_CHASSIS:
|
||||
|
@ -542,7 +560,8 @@ class OvsdbNbOvnIdl(nb_impl_idl.OvnNbApiIdlImpl, Backend):
|
|||
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)}
|
||||
c, all_gw_chassis, physnet, chassis_with_physnets,
|
||||
az_hints, chassis_with_azs)}
|
||||
|
||||
if available_chassis == set(original_state):
|
||||
# The same situation as was before. Nothing
|
||||
|
@ -853,6 +872,12 @@ class OvsdbSbOvnIdl(sb_impl_idl.OvnSbApiIdlImpl, Backend):
|
|||
chassis_info_dict[ch.name] = self._get_chassis_physnets(ch)
|
||||
return chassis_info_dict
|
||||
|
||||
def get_chassis_and_azs(self):
|
||||
chassis_azs = {}
|
||||
for ch in self.chassis_list().execute(check_error=True):
|
||||
chassis_azs[ch.name] = utils.get_chassis_availability_zones(ch)
|
||||
return chassis_azs
|
||||
|
||||
def get_all_chassis(self, chassis_type=None):
|
||||
# TODO(azbiswas): Use chassis_type as input once the compute type
|
||||
# preference patch (as part of external ids) merges.
|
||||
|
|
|
@ -44,11 +44,12 @@ class OVNGatewayScheduler(object, metaclass=abc.ABCMeta):
|
|||
|
||||
def filter_existing_chassis(self, nb_idl, gw_chassis,
|
||||
physnet, chassis_physnets,
|
||||
existing_chassis):
|
||||
existing_chassis, az_hints, chassis_with_azs):
|
||||
chassis_list = copy.copy(existing_chassis)
|
||||
for chassis_name in existing_chassis:
|
||||
if utils.is_gateway_chassis_invalid(chassis_name, gw_chassis,
|
||||
physnet, chassis_physnets):
|
||||
physnet, chassis_physnets,
|
||||
az_hints, chassis_with_azs):
|
||||
LOG.debug("Chassis %(chassis)s is invalid for scheduling "
|
||||
"router in physnet: %(physnet)s.",
|
||||
{'chassis': chassis_name,
|
||||
|
|
|
@ -394,20 +394,23 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
|
|||
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()
|
||||
chassis_with_azs = self._sb_ovn.get_chassis_and_azs()
|
||||
unhosted_gateways = self._nb_ovn.get_unhosted_gateways(
|
||||
port_physnet_dict, chassis_with_physnets,
|
||||
all_gw_chassis)
|
||||
all_gw_chassis, chassis_with_azs)
|
||||
for g_name in unhosted_gateways:
|
||||
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._nb_ovn.get_gateway_chassis_binding(g_name)
|
||||
primary = existing_chassis[0] if existing_chassis else None
|
||||
az_hints = self._nb_ovn.get_gateway_chassis_az_hints(g_name)
|
||||
existing_chassis = self.scheduler.filter_existing_chassis(
|
||||
nb_idl=self._nb_ovn, gw_chassis=all_gw_chassis,
|
||||
physnet=physnet, chassis_physnets=chassis_with_physnets,
|
||||
existing_chassis=existing_chassis)
|
||||
az_hints = self._get_availability_zones_from_router_port(g_name)
|
||||
existing_chassis=existing_chassis, az_hints=az_hints,
|
||||
chassis_with_azs=chassis_with_azs)
|
||||
|
||||
candidates = self._ovn_client.get_candidates_for_scheduling(
|
||||
physnet, cms=all_gw_chassis,
|
||||
chassis_physnets=chassis_with_physnets,
|
||||
|
|
|
@ -393,9 +393,17 @@ class TestOVNFunctionalBase(test_plugin.Ml2PluginV2TestCase,
|
|||
self._start_ovn_northd()
|
||||
|
||||
def add_fake_chassis(self, host, physical_nets=None, external_ids=None,
|
||||
name=None):
|
||||
name=None, azs=None):
|
||||
physical_nets = physical_nets or []
|
||||
external_ids = external_ids or {}
|
||||
if azs is None:
|
||||
azs = ['ovn']
|
||||
if azs:
|
||||
if 'ovn-cms-options' not in external_ids:
|
||||
external_ids['ovn-cms-options'] = 'availability-zones='
|
||||
else:
|
||||
external_ids['ovn-cms-options'] += ',availability-zones='
|
||||
external_ids['ovn-cms-options'] += ':'.join(azs)
|
||||
|
||||
bridge_mapping = ",".join(["%s:br-provider%s" % (phys_net, i)
|
||||
for i, phys_net in enumerate(physical_nets)])
|
||||
|
|
|
@ -41,11 +41,13 @@ class TestRouter(base.TestOVNFunctionalBase):
|
|||
self.cr_lrp_pb_event = events.WaitForCrLrpPortBindingEvent()
|
||||
self.sb_api.idl.notify_handler.watch_event(self.cr_lrp_pb_event)
|
||||
|
||||
def _create_router(self, name, gw_info=None):
|
||||
def _create_router(self, name, gw_info=None, az_hints=None):
|
||||
router = {'router':
|
||||
{'name': name,
|
||||
'admin_state_up': True,
|
||||
'tenant_id': self._tenant_id}}
|
||||
if az_hints:
|
||||
router['router']['availability_zone_hints'] = az_hints
|
||||
if gw_info:
|
||||
router['router']['external_gateway_info'] = gw_info
|
||||
return self.l3_plugin.create_router(self.context, router)
|
||||
|
@ -92,7 +94,8 @@ class TestRouter(base.TestOVNFunctionalBase):
|
|||
rc = row.options.get(ovn_const.OVN_GATEWAY_CHASSIS_KEY)
|
||||
self.assertIn(rc, expected)
|
||||
|
||||
def _check_gateway_chassis_candidates(self, candidates):
|
||||
def _check_gateway_chassis_candidates(self, candidates,
|
||||
router_az_hints=None):
|
||||
# In this test, fake_select() is called once from _create_router()
|
||||
# and later from schedule_unhosted_gateways()
|
||||
ovn_client = self.l3_plugin._ovn_client
|
||||
|
@ -104,7 +107,7 @@ class TestRouter(base.TestOVNFunctionalBase):
|
|||
def fake_select(*args, **kwargs):
|
||||
self.assertCountEqual(candidates, kwargs['candidates'])
|
||||
# We are not interested in further processing, let us return
|
||||
# INVALID_CHASSIS to avoid erros
|
||||
# INVALID_CHASSIS to avoid errors
|
||||
return [ovn_const.OVN_GATEWAY_INVALID_CHASSIS]
|
||||
|
||||
with mock.patch.object(ovn_client._ovn_scheduler, 'select',
|
||||
|
@ -112,7 +115,8 @@ class TestRouter(base.TestOVNFunctionalBase):
|
|||
mock.patch.object(self.l3_plugin.scheduler, 'select',
|
||||
side_effect=fake_select) as plugin_select:
|
||||
gw_info = {'network_id': ext1['network']['id']}
|
||||
self._create_router('router1', gw_info=gw_info)
|
||||
self._create_router('router1', gw_info=gw_info,
|
||||
az_hints=router_az_hints)
|
||||
self.assertFalse(plugin_select.called)
|
||||
self.assertTrue(client_select.called)
|
||||
client_select.reset_mock()
|
||||
|
@ -160,6 +164,53 @@ class TestRouter(base.TestOVNFunctionalBase):
|
|||
# and do not updated the lrp port.
|
||||
ulrp.assert_not_called()
|
||||
|
||||
def test_gateway_chassis_with_cms_and_azs(self):
|
||||
# Both chassis3 and chassis4 are having azs,
|
||||
# but only chassis3's azs is ['ovn'].
|
||||
# Test if chassis3 is selected as candidate or not.
|
||||
self.chassis3 = self.add_fake_chassis(
|
||||
'ovs-host3', physical_nets=['physnet1'],
|
||||
external_ids={'ovn-cms-options': 'enable-chassis-as-gw'},
|
||||
azs=['ovn'])
|
||||
self.chassis4 = self.add_fake_chassis(
|
||||
'ovs-host4', physical_nets=['physnet1'],
|
||||
external_ids={'ovn-cms-options': 'enable-chassis-as-gw'},
|
||||
azs=['ovn2'])
|
||||
self._check_gateway_chassis_candidates([self.chassis3],
|
||||
router_az_hints=['ovn'])
|
||||
|
||||
def test_gateway_chassis_with_cms_and_not_match_azs(self):
|
||||
# add chassis3 is having azs [ovn], match router az_hints,
|
||||
# if not add chassis3, create router will fail with
|
||||
# AvailabilityZoneNotFound. after create will delete if.
|
||||
# add chassis4 is having azs [ovn2], not match routers az_hints [ovn]
|
||||
self.chassis3 = self.add_fake_chassis(
|
||||
'ovs-host3', physical_nets=['physnet1'],
|
||||
external_ids={'ovn-cms-options': 'enable-chassis-as-gw'})
|
||||
self.chassis4 = self.add_fake_chassis(
|
||||
'ovs-host4', physical_nets=['physnet1'],
|
||||
external_ids={'ovn-cms-options': 'enable-chassis-as-gw'},
|
||||
azs=['ovn2'])
|
||||
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, az_hints=['ovn'])
|
||||
self.del_fake_chassis(self.chassis3)
|
||||
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,
|
||||
# but none of the chassis having enable-chassis-as-gw.
|
||||
|
|
|
@ -209,26 +209,28 @@ class TestGateWayChassisValidity(base.BaseTestCase):
|
|||
self.chassis_name = self.gw_chassis[0]
|
||||
self.physnet = 'physical-nw-1'
|
||||
self.chassis_physnets = {self.chassis_name: [self.physnet]}
|
||||
self.az_hints = ['ovn', ]
|
||||
self.chassis_azs = {self.chassis_name: self.az_hints}
|
||||
|
||||
def test_gateway_chassis_valid(self):
|
||||
# Return False, since everything is valid
|
||||
self.assertFalse(utils.is_gateway_chassis_invalid(
|
||||
self.chassis_name, self.gw_chassis, self.physnet,
|
||||
self.chassis_physnets))
|
||||
self.chassis_physnets, self.az_hints, self.chassis_azs))
|
||||
|
||||
def test_gateway_chassis_due_to_invalid_chassis_name(self):
|
||||
# Return True since chassis is invalid
|
||||
self.chassis_name = constants.OVN_GATEWAY_INVALID_CHASSIS
|
||||
self.assertTrue(utils.is_gateway_chassis_invalid(
|
||||
self.chassis_name, self.gw_chassis, self.physnet,
|
||||
self.chassis_physnets))
|
||||
self.chassis_physnets, self.az_hints, self.chassis_azs))
|
||||
|
||||
def test_gateway_chassis_for_chassis_not_in_chassis_physnets(self):
|
||||
# Return True since chassis is not in chassis_physnets
|
||||
self.chassis_name = 'host-2'
|
||||
self.assertTrue(utils.is_gateway_chassis_invalid(
|
||||
self.chassis_name, self.gw_chassis, self.physnet,
|
||||
self.chassis_physnets))
|
||||
self.chassis_physnets, self.az_hints, self.chassis_azs))
|
||||
|
||||
def test_gateway_chassis_for_undefined_physnet(self):
|
||||
# Return True since physnet is not defined
|
||||
|
@ -236,14 +238,14 @@ class TestGateWayChassisValidity(base.BaseTestCase):
|
|||
self.physnet = None
|
||||
self.assertTrue(utils.is_gateway_chassis_invalid(
|
||||
self.chassis_name, self.gw_chassis, self.physnet,
|
||||
self.chassis_physnets))
|
||||
self.chassis_physnets, self.az_hints, self.chassis_azs))
|
||||
|
||||
def test_gateway_chassis_for_physnet_not_in_chassis_physnets(self):
|
||||
# Return True since physnet is not in chassis_physnets
|
||||
self.physnet = 'physical-nw-2'
|
||||
self.assertTrue(utils.is_gateway_chassis_invalid(
|
||||
self.chassis_name, self.gw_chassis, self.physnet,
|
||||
self.chassis_physnets))
|
||||
self.chassis_physnets, self.az_hints, self.chassis_azs))
|
||||
|
||||
def test_gateway_chassis_for_gw_chassis_empty(self):
|
||||
# Return False if gw_chassis is []
|
||||
|
@ -252,14 +254,28 @@ class TestGateWayChassisValidity(base.BaseTestCase):
|
|||
self.gw_chassis = []
|
||||
self.assertFalse(utils.is_gateway_chassis_invalid(
|
||||
self.chassis_name, self.gw_chassis, self.physnet,
|
||||
self.chassis_physnets))
|
||||
self.chassis_physnets, self.az_hints, self.chassis_azs))
|
||||
|
||||
def test_gateway_chassis_for_chassis_not_in_gw_chassis_list(self):
|
||||
# Return True since chassis_name not in gw_chassis
|
||||
self.gw_chassis = ['host-2']
|
||||
self.assertTrue(utils.is_gateway_chassis_invalid(
|
||||
self.chassis_name, self.gw_chassis, self.physnet,
|
||||
self.chassis_physnets))
|
||||
self.chassis_physnets, self.az_hints, self.chassis_azs))
|
||||
|
||||
def test_gateway_chassis_for_chassis_az_hints_empty(self):
|
||||
# Return False since az_hints is []
|
||||
az_hints = []
|
||||
self.assertFalse(utils.is_gateway_chassis_invalid(
|
||||
self.chassis_name, self.gw_chassis, self.physnet,
|
||||
self.chassis_physnets, az_hints, self.chassis_azs))
|
||||
|
||||
def test_gateway_chassis_for_chassis_no_in_az_hints(self):
|
||||
# Return True since az_hints not match chassis_azs
|
||||
az_hints = ['ovs']
|
||||
self.assertTrue(utils.is_gateway_chassis_invalid(
|
||||
self.chassis_name, self.gw_chassis, self.physnet,
|
||||
self.chassis_physnets, az_hints, self.chassis_azs))
|
||||
|
||||
|
||||
class TestDHCPUtils(base.BaseTestCase):
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
import collections
|
||||
import copy
|
||||
import sys
|
||||
from unittest import mock
|
||||
|
||||
from neutron_lib.api.definitions import l3
|
||||
|
@ -77,6 +78,8 @@ class FakeOvsdbNbOvnIdl(object):
|
|||
self.get_all_chassis_gateway_bindings = mock.Mock()
|
||||
self.get_chassis_gateways = mock.Mock()
|
||||
self.get_gateway_chassis_binding = mock.Mock()
|
||||
self.get_gateway_chassis_az_hints = mock.Mock()
|
||||
self.get_gateway_chassis_az_hints.return_value = []
|
||||
self.get_unhosted_gateways = mock.Mock()
|
||||
self.add_dhcp_options = mock.Mock()
|
||||
self.delete_dhcp_options = mock.Mock()
|
||||
|
@ -168,6 +171,8 @@ class FakeOvsdbSbOvnIdl(object):
|
|||
self.chassis_exists.return_value = True
|
||||
self.get_chassis_hostname_and_physnets = mock.Mock()
|
||||
self.get_chassis_hostname_and_physnets.return_value = {}
|
||||
self.get_chassis_and_azs = mock.Mock()
|
||||
self.get_chassis_and_azs.return_value = {}
|
||||
self.get_all_chassis = mock.Mock()
|
||||
self.get_chassis_data_for_ml2_bind_port = mock.Mock()
|
||||
self.get_chassis_data_for_ml2_bind_port.return_value = \
|
||||
|
@ -431,11 +436,13 @@ class FakeOvsdbTable(FakeResource):
|
|||
"""Fake one or more OVSDB tables."""
|
||||
|
||||
@staticmethod
|
||||
def create_one_ovsdb_table(attrs=None):
|
||||
def create_one_ovsdb_table(attrs=None, max_rows=sys.maxsize):
|
||||
"""Create a fake OVSDB table.
|
||||
|
||||
:param Dictionary attrs:
|
||||
A dictionary with all attributes
|
||||
:param Int max_rows:
|
||||
A num of max rows
|
||||
:return:
|
||||
A FakeResource object faking the OVSDB table
|
||||
"""
|
||||
|
@ -446,6 +453,7 @@ class FakeOvsdbTable(FakeResource):
|
|||
'rows': collections.UserDict(),
|
||||
'columns': {},
|
||||
'indexes': [],
|
||||
'max_rows': max_rows,
|
||||
}
|
||||
|
||||
# Overwrite default attributes.
|
||||
|
|
|
@ -147,10 +147,12 @@ class TestNBImplIdlOvn(TestDBImplIdlOvn):
|
|||
'lrouters': [
|
||||
{'name': utils.ovn_name('lr-id-a'),
|
||||
'external_ids': {ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY:
|
||||
'lr-name-a'}},
|
||||
'lr-name-a',
|
||||
ovn_const.OVN_AZ_HINTS_EXT_ID_KEY: 'az-a'}},
|
||||
{'name': utils.ovn_name('lr-id-b'),
|
||||
'external_ids': {ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY:
|
||||
'lr-name-b'}},
|
||||
'lr-name-b',
|
||||
ovn_const.OVN_AZ_HINTS_EXT_ID_KEY: 'az-b'}},
|
||||
{'name': utils.ovn_name('lr-id-c'),
|
||||
'external_ids': {ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY:
|
||||
'lr-name-c'}},
|
||||
|
@ -162,7 +164,9 @@ class TestNBImplIdlOvn(TestDBImplIdlOvn):
|
|||
'lr-name-e'}}],
|
||||
'lrouter_ports': [
|
||||
{'name': utils.ovn_lrouter_port_name('orp-id-a1'),
|
||||
'external_ids': {}, 'networks': ['10.0.1.0/24'],
|
||||
'external_ids': {ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY:
|
||||
'lr-id-a'},
|
||||
'networks': ['10.0.1.0/24'],
|
||||
'options': {ovn_const.OVN_GATEWAY_CHASSIS_KEY: 'host-1'}},
|
||||
{'name': utils.ovn_lrouter_port_name('orp-id-a2'),
|
||||
'external_ids': {}, 'networks': ['10.0.2.0/24'],
|
||||
|
@ -596,17 +600,29 @@ class TestNBImplIdlOvn(TestDBImplIdlOvn):
|
|||
# Test only that orp-id-a3 is to be scheduled.
|
||||
# Rest ports don't have required chassis (physnet2)
|
||||
# or are already scheduled.
|
||||
chassis_with_azs = {'host-1': ['az-a'], 'host-2': ['az-b']}
|
||||
unhosted_gateways = self.nb_ovn_idl.get_unhosted_gateways(
|
||||
port_physnet_dict, {'host-1': 'physnet1', 'host-2': 'physnet3'},
|
||||
['host-1', 'host-2'])
|
||||
['host-1', 'host-2'], chassis_with_azs)
|
||||
expected = ['lrp-orp-id-a3']
|
||||
self.assertCountEqual(unhosted_gateways, expected)
|
||||
# Test both host-1, host-2 in valid list
|
||||
unhosted_gateways = self.nb_ovn_idl.get_unhosted_gateways(
|
||||
port_physnet_dict, {'host-1': 'physnet1', 'host-2': 'physnet2'},
|
||||
['host-1', 'host-2'])
|
||||
['host-1', 'host-2'], chassis_with_azs)
|
||||
expected = ['lrp-orp-id-a3', 'lrp-orp-id-b6']
|
||||
self.assertCountEqual(unhosted_gateways, expected)
|
||||
# Test lrp-orp-id-a1 az_hints not in host-1's azs
|
||||
# lrp-orp-id-a2 not set az_hints, should schedule in host-1, host-3
|
||||
# lrp-orp-id-a3 not scheduled
|
||||
chassis_with_azs = {'host-1': ['az-b'], 'host-2': ['az-b'],
|
||||
'host-3': ['az-a']}
|
||||
unhosted_gateways = self.nb_ovn_idl.get_unhosted_gateways(
|
||||
port_physnet_dict, {'host-1': 'physnet1', 'host-2': 'physnet3',
|
||||
'host-3': 'physnet1'},
|
||||
['host-1', 'host-2', 'host-3'], chassis_with_azs)
|
||||
expected = ['lrp-orp-id-a1', 'lrp-orp-id-a2', 'lrp-orp-id-a3']
|
||||
self.assertCountEqual(unhosted_gateways, expected)
|
||||
|
||||
def test_get_unhosted_gateways_deleted_physnet(self):
|
||||
self._load_nb_db()
|
||||
|
@ -616,17 +632,18 @@ class TestNBImplIdlOvn(TestDBImplIdlOvn):
|
|||
setattr(router_row, 'options', {
|
||||
ovn_const.OVN_GATEWAY_CHASSIS_KEY: 'host-2'})
|
||||
port_physnet_dict = {'orp-id-a1': 'physnet1'}
|
||||
chassis_with_azs = {'host-1': ['az-a'], 'host-2': ['az-a']}
|
||||
# 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'])
|
||||
['host-1', 'host-2'], chassis_with_azs)
|
||||
# Make sure that lrp is rescheduled, because host-1 has physet1
|
||||
expected = ['lrp-orp-id-a1']
|
||||
self.assertCountEqual(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'])
|
||||
['host-1', 'host-2'], chassis_with_azs)
|
||||
self.assertCountEqual(unhosted_gateways, [])
|
||||
|
||||
def _test_get_unhosted_gateway_max_chassis(self, r):
|
||||
|
@ -647,7 +664,8 @@ class TestNBImplIdlOvn(TestDBImplIdlOvn):
|
|||
{'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)])
|
||||
['host-%s' % x for x in range(1, 7)],
|
||||
{'host-%s' % x: ['az-a'] for x in range(1, 7)})
|
||||
# We don't have required number of chassis
|
||||
expected = []
|
||||
self.assertCountEqual(unhosted_gateways, expected)
|
||||
|
@ -661,7 +679,8 @@ class TestNBImplIdlOvn(TestDBImplIdlOvn):
|
|||
{'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)])
|
||||
['host-%s' % x for x in range(1, 7)],
|
||||
{'host-%s' % x: ['az-a'] for x in range(1, 7)})
|
||||
expected = []
|
||||
self.assertCountEqual(unhosted_gateways, expected)
|
||||
|
||||
|
@ -674,7 +693,8 @@ class TestNBImplIdlOvn(TestDBImplIdlOvn):
|
|||
{'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)])
|
||||
['host-%s' % x for x in range(1, 7)],
|
||||
{'host-%s' % x: ['az-a'] for x in range(1, 7)})
|
||||
expected = ['lrp-orp-id-a1']
|
||||
self.assertCountEqual(unhosted_gateways, expected)
|
||||
|
||||
|
|
|
@ -94,7 +94,9 @@ class TestOVNGatewayScheduler(base.BaseTestCase):
|
|||
nb_idl=kwargs.pop('nb_idl'), gw_chassis=kwargs.pop('gw_chassis'),
|
||||
physnet=kwargs.pop('physnet'),
|
||||
chassis_physnets=kwargs.pop('chassis_physnets'),
|
||||
existing_chassis=kwargs.pop('existing_chassis'))
|
||||
existing_chassis=kwargs.pop('existing_chassis'),
|
||||
az_hints=kwargs.pop('az_hints', []),
|
||||
chassis_with_azs=kwargs.pop('chassis_with_azs', {}))
|
||||
|
||||
|
||||
class OVNGatewayChanceScheduler(TestOVNGatewayScheduler):
|
||||
|
|
|
@ -1571,7 +1571,7 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
|||
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)
|
||||
{'foo-1': 'physnet1'}, mock.ANY, mock.ANY, mock.ANY)
|
||||
|
||||
@mock.patch('neutron.plugins.ml2.plugin.Ml2Plugin.get_network')
|
||||
@mock.patch('neutron.plugins.ml2.plugin.Ml2Plugin.get_networks')
|
||||
|
|
Loading…
Reference in New Issue