Browse Source

Merge "Support for Router Scheduling on addition/removal of chassis" into stable/queens

changes/61/699661/4
Zuul Gerrit Code Review 2 months ago
parent
commit
a104ae3387
12 changed files with 397 additions and 53 deletions
  1. +3
    -0
      networking_ovn/common/constants.py
  2. +26
    -0
      networking_ovn/common/utils.py
  3. +26
    -1
      networking_ovn/l3/l3_ovn.py
  4. +38
    -16
      networking_ovn/l3/l3_ovn_scheduler.py
  5. +6
    -8
      networking_ovn/ovsdb/impl_idl_ovn.py
  6. +4
    -0
      networking_ovn/tests/functional/base.py
  7. +80
    -0
      networking_ovn/tests/functional/test_router.py
  8. +62
    -0
      networking_ovn/tests/unit/common/test_utils.py
  9. +2
    -0
      networking_ovn/tests/unit/fakes.py
  10. +60
    -4
      networking_ovn/tests/unit/l3/test_l3_ovn.py
  11. +68
    -13
      networking_ovn/tests/unit/l3/test_l3_ovn_scheduler.py
  12. +22
    -11
      networking_ovn/tests/unit/ovsdb/test_impl_idl_ovn.py

+ 3
- 0
networking_ovn/common/constants.py View File

@@ -135,3 +135,6 @@ MAINTENANCE_DELETE_TYPE_ORDER = {
# The addresses field to set in the logical switch port which has a
# peer router port (connecting to the logical router).
DEFAULT_ADDR_FOR_LSP_WITH_PEER = 'router'

# Maximum chassis count where a gateway port can be hosted
MAX_GW_CHASSIS = 5

+ 26
- 0
networking_ovn/common/utils.py View File

@@ -365,3 +365,29 @@ def get_system_dns_resolvers(resolver_file=DNS_RESOLVER_FILE):
def get_port_subnet_ids(port):
fixed_ips = [ip for ip in port['fixed_ips']]
return [f['subnet_id'] for f in fixed_ips]


def is_gateway_chassis_invalid(chassis_name, gw_chassis,
physnet, chassis_physnets):
"""Check if gateway chassis is invalid

@param chassis_name: gateway chassis name
@type chassis_name: string
@param gw_chassis: List of gateway chassis in the system
@type gw_chassis: []
@param physnet: physical network associated to chassis_name
@type physnet: string
@param chassis_physnets: Dictionary linking chassis with their physnets
@type chassis_physnets: {}
@return Boolean
"""

if chassis_name == constants.OVN_GATEWAY_INVALID_CHASSIS:
return True
elif chassis_name not in chassis_physnets:
return True
elif physnet and physnet not in chassis_physnets.get(chassis_name):
return True
elif gw_chassis and chassis_name not in gw_chassis:
return True
return False

+ 26
- 1
networking_ovn/l3/l3_ovn.py View File

@@ -321,10 +321,35 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
port_physnet_dict, chassis_physnets, cms)
for g_name in unhosted_gateways:
physnet = port_physnet_dict.get(g_name[len('lrp-'):])
# 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,
existing_chassis=existing_chassis)
candidates = self._ovn_client.get_candidates_for_scheduling(
physnet, cms=cms, chassis_physnets=chassis_physnets)
chassis = self.scheduler.select(
self._ovn, self._sb_ovn, g_name, candidates=candidates)
self._ovn, self._sb_ovn, g_name, candidates=candidates,
existing_chassis=existing_chassis)
if master and master != chassis[0]:
if master not in chassis:
LOG.debug("Master gateway chassis %(old)s "
"has been removed from the system. Moving "
"gateway %(gw)s to other chassis %(new)s.",
{'gw': g_name,
'old': master,
'new': chassis[0]})
else:
LOG.debug("Gateway %s is hosted at %s.", g_name, master)
# NOTE(mjozefcz): It means scheduler moved master chassis
# to other gw based on scheduling method. But we don't
# want network flap - so moving actual master to be on
# the top.
index = chassis.index(master)
chassis[0], chassis[index] = chassis[index], chassis[0]
# NOTE(dalvarez): Let's commit the changes in separate transactions
# as we will rely on those for scheduling subsequent gateways.
with self._ovn.transaction(check_error=True) as txn:


+ 38
- 16
networking_ovn/l3/l3_ovn_scheduler.py View File

@@ -21,6 +21,7 @@ import six

from networking_ovn.common import config as ovn_config
from networking_ovn.common import constants as ovn_const
from networking_ovn.common import utils


LOG = log.getLogger(__name__)
@@ -28,8 +29,6 @@ LOG = log.getLogger(__name__)
OVN_SCHEDULER_CHANCE = 'chance'
OVN_SCHEDULER_LEAST_LOADED = 'leastloaded'

MAX_GW_CHASSIS = 5


@six.add_metaclass(abc.ABCMeta)
class OVNGatewayScheduler(object):
@@ -46,23 +45,42 @@ class OVNGatewayScheduler(object):
"""
pass

def _schedule_gateway(self, nb_idl, sb_idl, gateway_name, candidates):
existing_chassis = nb_idl.get_gateway_chassis_binding(gateway_name)
def filter_existing_chassis(self, nb_idl, gw_chassis,
physnet, chassis_physnets,
existing_chassis):
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):
LOG.debug("Chassis %(chassis)s is invalid for scheduling "
"router in physnet: %(physnet)s.",
{'chassis': chassis_name,
'physnet': physnet})
chassis_list.remove(chassis_name)
return chassis_list

def _schedule_gateway(self, nb_idl, sb_idl, gateway_name, candidates,
existing_chassis):
existing_chassis = existing_chassis or []
candidates = candidates or self._get_chassis_candidates(sb_idl)
# if no candidates or all chassis in existing_chassis also present
# in candidates, then return existing_chassis
# TODO(anilvenkata): If more candidates avaialable, then schedule
# on them also?
if existing_chassis and (not candidates or
not (set(existing_chassis) - set(candidates))):
return existing_chassis
candidates = list(set(candidates) - set(existing_chassis))
# If no candidates, or gateway scheduled on MAX_GATEWAY_CHASSIS nodes
# or all candidates in existing_chassis, return existing_chassis.
# Otherwise, if more candidates present, then schedule them.
if existing_chassis:
if not candidates or (
len(existing_chassis) == ovn_const.MAX_GW_CHASSIS):
return existing_chassis
if not candidates:
return [ovn_const.OVN_GATEWAY_INVALID_CHASSIS]
chassis_count = ovn_const.MAX_GW_CHASSIS - len(existing_chassis)
# The actual binding of the gateway to a chassis via the options
# column or gateway_chassis column in the OVN_Northbound is done
# by the caller
chassis = self._select_gateway_chassis(
nb_idl, candidates)[:MAX_GW_CHASSIS]
nb_idl, candidates)[:chassis_count]
# priority of existing chassis is higher than candidates
chassis = existing_chassis + chassis

LOG.debug("Gateway %s scheduled on chassis %s",
gateway_name, chassis)
@@ -84,8 +102,10 @@ class OVNGatewayScheduler(object):
class OVNGatewayChanceScheduler(OVNGatewayScheduler):
"""Randomly select an chassis for a gateway port of a router"""

def select(self, nb_idl, sb_idl, gateway_name, candidates=None):
return self._schedule_gateway(nb_idl, sb_idl, gateway_name, candidates)
def select(self, nb_idl, sb_idl, gateway_name, candidates=None,
existing_chassis=None):
return self._schedule_gateway(nb_idl, sb_idl, gateway_name,
candidates, existing_chassis)

def _select_gateway_chassis(self, nb_idl, candidates):
candidates = copy.deepcopy(candidates)
@@ -96,8 +116,10 @@ class OVNGatewayChanceScheduler(OVNGatewayScheduler):
class OVNGatewayLeastLoadedScheduler(OVNGatewayScheduler):
"""Select the least loaded chassis for a gateway port of a router"""

def select(self, nb_idl, sb_idl, gateway_name, candidates=None):
return self._schedule_gateway(nb_idl, sb_idl, gateway_name, candidates)
def select(self, nb_idl, sb_idl, gateway_name, candidates=None,
existing_chassis=None):
return self._schedule_gateway(nb_idl, sb_idl, gateway_name,
candidates, existing_chassis)

@staticmethod
def _get_chassis_load_by_prios(chassis_info):


+ 6
- 8
networking_ovn/ovsdb/impl_idl_ovn.py View File

@@ -351,7 +351,7 @@ class OvsdbNbOvnIdl(nb_impl_idl.OvnNbApiIdlImpl, Backend):

@param lrp: logical router port
@type lrp: Logical_Router_Port row
@return: List of tuples (chassis_name, priority)
@return: List of tuples (chassis_name, priority) sorted by priority
"""
# Try retrieving gateway_chassis with new schema. If new schema is not
# supported or user is using old schema, then use old schema for
@@ -364,7 +364,8 @@ class OvsdbNbOvnIdl(nb_impl_idl.OvnNbApiIdlImpl, Backend):
rc = lrp.options.get(ovn_const.OVN_GATEWAY_CHASSIS_KEY)
if rc:
chassis.append((rc, 0))
return chassis
# make sure that chassis are sorted by priority
return sorted(chassis, reverse=True, key=lambda x: x[1])

def get_all_chassis_gateway_bindings(self,
chassis_candidate_list=None):
@@ -395,21 +396,18 @@ class OvsdbNbOvnIdl(nb_impl_idl.OvnNbApiIdlImpl, Backend):
def get_unhosted_gateways(self, port_physnet_dict, chassis_physnets,
gw_chassis):
unhosted_gateways = []
valid_chassis_list = list(chassis_physnets)
for lrp in self._tables['Logical_Router_Port'].rows.values():
if not lrp.name.startswith('lrp-'):
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 (chassis_name == ovn_const.OVN_GATEWAY_INVALID_CHASSIS or
chassis_name not in valid_chassis_list or
(physnet and
physnet not in chassis_physnets.get(chassis_name)) or
(gw_chassis and chassis_name not in gw_chassis)):
if is_max_gw_reached or utils.is_gateway_chassis_invalid(
chassis_name, gw_chassis, physnet, chassis_physnets):
unhosted_gateways.append(lrp.name)
return unhosted_gateways



+ 4
- 0
networking_ovn/tests/functional/base.py View File

@@ -253,3 +253,7 @@ class TestOVNFunctionalBase(test_plugin.Ml2PluginV2TestCase):
name, ['geneve'], '172.24.4.%d' % self._counter,
external_ids=external_ids, hostname=host).execute(check_error=True)
return name

def del_fake_chassis(self, chassis, if_exists=True):
self.sb_api.chassis_del(
chassis, if_exists=if_exists).execute(check_error=True)

+ 80
- 0
networking_ovn/tests/functional/test_router.py View File

@@ -401,3 +401,83 @@ class TestRouter(base.TestOVNFunctionalBase):
def test_router_port_ipv6_ra_configs_ipv4(self):
self._test_router_port_ipv6_ra_configs_helper(
ip_version=4)

def test_gateway_chassis_rebalance(self):
def _get_result_dict():
sched_info = {}
for row in self.nb_api.tables[
'Logical_Router_Port'].rows.values():
for gwc in row.gateway_chassis:
chassis = sched_info.setdefault(gwc.chassis_name, {})
chassis[gwc.priority] = chassis.get(gwc.priority, 0) + 1
return sched_info

if not self._l3_ha_supported():
self.skipTest('L3 HA not supported')
ovn_client = self.l3_plugin._ovn_client
chassis4 = self.add_fake_chassis(
'ovs-host4', physical_nets=['physnet4'], external_ids={
'ovn-cms-options': 'enable-chassis-as-gw'})
ovn_client._ovn_scheduler = l3_sched.OVNGatewayLeastLoadedScheduler()
ext1 = self._create_ext_network(
'ext1', 'flat', 'physnet4', None, "30.0.0.1", "30.0.0.0/24")
gw_info = {'network_id': ext1['network']['id']}
# Create 20 routers with a gateway. Since we're using physnet4, the
# chassis candidates will be chassis4 initially.
for i in range(20):
router = self._create_router('router%d' % i, gw_info=gw_info)
self.l3_plugin.update_router_gateway_port_bindings(
router['id'],
chassis4)
self.l3_plugin.schedule_unhosted_gateways()
expected = {chassis4: {1: 20}}
self.assertEqual(expected, _get_result_dict())

# Add another chassis as a gateway chassis
chassis5 = self.add_fake_chassis(
'ovs-host5', physical_nets=['physnet4'], external_ids={
'ovn-cms-options': 'enable-chassis-as-gw'})
# Add a node as compute node. Compute node wont be
# used to schedule the router gateway ports therefore
# priority values wont be changed. Therefore chassis4 would
# still have priority 2
self.add_fake_chassis('ovs-host6', physical_nets=['physnet4'])

# Chassis4 should have all ports at Priority 2
self.l3_plugin.schedule_unhosted_gateways()
self.assertEqual({2: 20}, _get_result_dict()[chassis4])
# Chassis5 should have all ports at Priority 1
self.assertEqual({1: 20}, _get_result_dict()[chassis5])

# delete chassis that hosts all the gateways
self.del_fake_chassis(chassis4)
self.l3_plugin.schedule_unhosted_gateways()

# As Chassis4 has been removed so all gateways that were
# hosted there are now masters on chassis5 and have
# priority 1.
self.assertEqual({1: 20}, _get_result_dict()[chassis5])

def test_gateway_chassis_rebalance_max_chassis(self):
chassis_list = []
# spawn 6 chassis and check if port has MAX_CHASSIS candidates.
for i in range(0, ovn_const.MAX_GW_CHASSIS + 1):
chassis_list.append(
self.add_fake_chassis(
'ovs-host%s' % i, physical_nets=['physnet1'],
external_ids={
'ovn-cms-options': 'enable-chassis-as-gw'}))

ext1 = self._create_ext_network(
'ext1', 'vlan', 'physnet1', 1, "10.0.0.1", "10.0.0.0/24")
gw_info = {'network_id': ext1['network']['id']}
router = self._create_router('router', gw_info=gw_info)
self.l3_plugin.update_router_gateway_port_bindings(
router['id'],
chassis_list[0])

self.l3_plugin.schedule_unhosted_gateways()
for row in self.nb_api.tables[
'Logical_Router_Port'].rows.values():
self.assertEqual(ovn_const.MAX_GW_CHASSIS,
len(row.gateway_chassis))

+ 62
- 0
networking_ovn/tests/unit/common/test_utils.py View File

@@ -15,6 +15,7 @@

import fixtures

from networking_ovn.common import constants
from networking_ovn.common import utils
from networking_ovn.tests import base

@@ -41,3 +42,64 @@ class TestUtils(base.TestCase):
observed_dns_resolvers = utils.get_system_dns_resolvers(
resolver_file=resolver_file_name)
self.assertEqual(expected_dns_resolvers, observed_dns_resolvers)


class TestGateWayChassisValidity(base.TestCase):

def setUp(self):
super(TestGateWayChassisValidity, self).setUp()
self.gw_chassis = ['host1', 'host2']
self.chassis_name = self.gw_chassis[0]
self.physnet = 'physical-nw-1'
self.chassis_physnets = {self.chassis_name: [self.physnet]}

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))

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))

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))

def test_gateway_chassis_for_undefined_physnet(self):
# Return True since physnet is not defined
self.chassis_name = 'host-1'
self.physnet = None
self.assertTrue(utils.is_gateway_chassis_invalid(
self.chassis_name, self.gw_chassis, self.physnet,
self.chassis_physnets))

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))

def test_gateway_chassis_for_gw_chassis_empty(self):
# Return False if gw_chassis is []
# This condition states that the chassis is valid, has valid
# physnets and there are no gw_chassis present in the system.
self.gw_chassis = []
self.assertFalse(utils.is_gateway_chassis_invalid(
self.chassis_name, self.gw_chassis, self.physnet,
self.chassis_physnets))

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))

+ 2
- 0
networking_ovn/tests/unit/fakes.py View File

@@ -157,6 +157,8 @@ class FakeOvsdbSbOvnIdl(object):
self.get_logical_port_chassis_and_datapath = mock.Mock()
self.get_logical_port_chassis_and_datapath.return_value = \
('fake', 'fake-dp')
self.get_chassis_and_physnets = mock.Mock()
self.get_gateway_chassis_from_cms_options = mock.Mock()


class FakeOvsdbTransaction(object):


+ 60
- 4
networking_ovn/tests/unit/l3/test_l3_ovn.py View File

@@ -151,11 +151,11 @@ class OVNL3RouterPlugin(test_mech_driver.OVNMechanismDriverTestCase):
ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name(
self.fake_floating_ip['router_id'])}}
self.l3_inst = directory.get_plugin(plugin_constants.L3)
self._start_mock(
self.nb_idl = self._start_mock(
'networking_ovn.l3.l3_ovn.OVNL3RouterPlugin._ovn',
new_callable=mock.PropertyMock,
return_value=fakes.FakeOvsdbNbOvnIdl())
self._start_mock(
self.sb_idl = self._start_mock(
'networking_ovn.l3.l3_ovn.OVNL3RouterPlugin._sb_ovn',
new_callable=mock.PropertyMock,
return_value=fakes.FakeOvsdbSbOvnIdl())
@@ -183,11 +183,11 @@ class OVNL3RouterPlugin(test_mech_driver.OVNMechanismDriverTestCase):
self._start_mock(
'neutron.db.l3_db.L3_NAT_dbonly_mixin.delete_router',
return_value={})
self._start_mock(
self.mock_candidates = self._start_mock(
'networking_ovn.common.ovn_client.'
'OVNClient.get_candidates_for_scheduling',
return_value=[])
self._start_mock(
self.mock_schedule = self._start_mock(
'networking_ovn.l3.l3_ovn_scheduler.'
'OVNGatewayLeastLoadedScheduler._schedule_gateway',
return_value=['hv1'])
@@ -1134,6 +1134,62 @@ class OVNL3RouterPlugin(test_mech_driver.OVNMechanismDriverTestCase):
mock_updt_status.assert_called_once_with(
mock.ANY, fake_port_id, constants.PORT_STATUS_DOWN)

def test_schedule_unhosted_gateways_no_gateways(self):
self.nb_idl().get_unhosted_gateways.return_value = []
self.l3_inst.schedule_unhosted_gateways()
self.nb_idl().update_lrouter_port.assert_not_called()

def test_schedule_unhosted_gateways(self):
unhosted_gws = ['lrp-foo-1', 'lrp-foo-2', 'lrp-foo-3']
chassis_mappings = {
'chassis1': ['physnet1'],
'chassis2': ['physnet1'],
'chassis3': ['physnet1']}
chassis = ['chassis1', 'chassis2', 'chassis3']
self.sb_idl().get_chassis_and_physnets.return_value = (
chassis_mappings)
self.sb_idl().get_gateway_chassis_from_cms_options.return_value = (
chassis)
self.nb_idl().get_unhosted_gateways.return_value = unhosted_gws
# 1. port has 2 gateway chassis
# 2. port has only chassis2
# 3. port is not bound
existing_port_bindings = [
['chassis1', 'chassis2'],
['chassis2'],
[]]
self.nb_idl().get_gateway_chassis_binding.side_effect = (
existing_port_bindings)
# for 1. port schedule untouched, add only 3'rd chassis
# for 2. port master scheduler somewhere else
# for 3. port schedule all
self.mock_schedule.side_effect = [
['chassis1', 'chassis2', 'chassis3'],
['chassis1', 'chassis2', 'chassis3'],
['chassis3', 'chassis2', 'chassis1']]

self.l3_inst.schedule_unhosted_gateways()

self.mock_candidates.assert_has_calls([
mock.call(mock.ANY,
chassis_physnets=chassis_mappings,
cms=chassis)] * 3)
self.mock_schedule.assert_has_calls([
mock.call(self.nb_idl(), self.sb_idl(),
'lrp-foo-1', [], ['chassis1', 'chassis2']),
mock.call(self.nb_idl(), self.sb_idl(),
'lrp-foo-2', [], ['chassis2']),
mock.call(self.nb_idl(), self.sb_idl(),
'lrp-foo-3', [], [])])
# make sure that for second port master chassis stays untouched
self.nb_idl().update_lrouter_port.assert_has_calls([
mock.call('lrp-foo-1',
gateway_chassis=['chassis1', 'chassis2', 'chassis3']),
mock.call('lrp-foo-2',
gateway_chassis=['chassis2', 'chassis1', 'chassis3']),
mock.call('lrp-foo-3',
gateway_chassis=['chassis3', 'chassis2', 'chassis1'])])


class OVNL3ExtrarouteTests(test_l3_gw.ExtGwModeIntTestCase,
test_l3.L3NatDBIntTestCase,


+ 68
- 13
networking_ovn/tests/unit/l3/test_l3_ovn_scheduler.py View File

@@ -50,10 +50,11 @@ class TestOVNGatewayScheduler(base.BaseTestCase):
'None': {'Chassis': [],
'Gateways': {
'g1': [ovn_const.OVN_GATEWAY_INVALID_CHASSIS]}},
'Multiple1': {'Chassis': ['hv1', 'hv2'],
'Gateways': {'g1': ['hv1'],
'g2': ['hv2'],
'g3': ['hv1']}},
'Multiple1': {'Chassis': ['hv1', 'hv2', 'hv3', 'hv4', 'hv5'],
'Gateways': {'g1': ['hv1', 'hv2', 'hv3', 'hv4'],
'g2': ['hv1', 'hv2', 'hv3'],
'g3': ['hv1', 'hv2'],
'g4': ['hv1']}},
'Multiple2': {'Chassis': ['hv1', 'hv2', 'hv3'],
'Gateways': {'g1': ['hv1'],
'g2': ['hv1'],
@@ -61,7 +62,14 @@ class TestOVNGatewayScheduler(base.BaseTestCase):
'Multiple3': {'Chassis': ['hv1', 'hv2', 'hv3'],
'Gateways': {'g1': ['hv3'],
'g2': ['hv2'],
'g3': ['hv2']}}
'g3': ['hv2']}},
'Multiple4': {'Chassis': ['hv1', 'hv2'],
'Gateways': {'g1': ['hv1'],
'g2': ['hv1'],
'g3': ['hv1'],
'g4': ['hv1'],
'g5': ['hv1'],
'g6': ['hv1']}},
}

# Determine the chassis to gateway list bindings
@@ -79,7 +87,15 @@ class TestOVNGatewayScheduler(base.BaseTestCase):
nb_idl = FakeOVNGatewaySchedulerNbOvnIdl(chassis_gateway_mapping,
gateway_name)
sb_idl = FakeOVNGatewaySchedulerSbOvnIdl(chassis_gateway_mapping)
return self.l3_scheduler.select(nb_idl, sb_idl, gateway_name)
return self.l3_scheduler.select(nb_idl, sb_idl,
gateway_name)

def filter_existing_chassis(self, *args, **kwargs):
return self.l3_scheduler.filter_existing_chassis(
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'))


class OVNGatewayChanceScheduler(TestOVNGatewayScheduler):
@@ -106,11 +122,43 @@ class OVNGatewayChanceScheduler(TestOVNGatewayScheduler):
chassis = self.select(mapping, gateway_name)
self.assertItemsEqual(chassis, mapping.get('Chassis'))

def test_existing_chassis_available_for_existing_gateway(self):
mapping = self.fake_chassis_gateway_mappings['Multiple1']
gateway_name = random.choice(list(mapping['Gateways'].keys()))
chassis = self.select(mapping, gateway_name)
self.assertEqual(mapping['Gateways'][gateway_name], chassis)
def test_filter_existing_chassis(self):
# filter_existing_chassis is scheduler independent, but calling
# it from Base class didnt seem right. Also, there is no need to have
# another test in LeastLoadedScheduler.
chassis_physnets = {'temp': ['phys-network-0', 'phys-network-1']}
nb_idl = FakeOVNGatewaySchedulerNbOvnIdl(
self.fake_chassis_gateway_mappings['None'], 'g1')
# Check if invalid chassis is removed
self.assertEqual(
['temp'], self.filter_existing_chassis(
nb_idl=nb_idl, gw_chassis=["temp"],
physnet='phys-network-1',
chassis_physnets=chassis_physnets,
existing_chassis=['temp',
ovn_const.OVN_GATEWAY_INVALID_CHASSIS]))
# Check if invalid is removed -II
self.assertFalse(
self.filter_existing_chassis(
nb_idl=nb_idl, gw_chassis=["temp"],
physnet='phys-network-1',
chassis_physnets=chassis_physnets,
existing_chassis=[ovn_const.OVN_GATEWAY_INVALID_CHASSIS]))
# Check if chassis removed when physnet doesnt exist
self.assertFalse(
self.filter_existing_chassis(
nb_idl=nb_idl, gw_chassis=["temp"],
physnet='phys-network-2',
chassis_physnets=chassis_physnets,
existing_chassis=['temp']))
# Check if chassis removed when it doesnt exist in gw_chassis
# or in chassis_physnets
self.assertFalse(
self.filter_existing_chassis(
nb_idl=nb_idl, gw_chassis=["temp1"],
physnet='phys-network-2',
chassis_physnets=chassis_physnets,
existing_chassis=['temp']))


class OVNGatewayLeastLoadedScheduler(TestOVNGatewayScheduler):
@@ -138,7 +186,7 @@ class OVNGatewayLeastLoadedScheduler(TestOVNGatewayScheduler):
self.assertItemsEqual(chassis, mapping.get('Chassis'))
# least loaded will be the first one in the list,
# networking-ovn will assign highest priority to this first element
self.assertEqual(['hv2', 'hv1'], chassis)
self.assertEqual(['hv5', 'hv4', 'hv3', 'hv2', 'hv1'], chassis)

def test_least_loaded_chassis_available_for_new_gateway2(self):
mapping = self.fake_chassis_gateway_mappings['Multiple2']
@@ -154,11 +202,18 @@ class OVNGatewayLeastLoadedScheduler(TestOVNGatewayScheduler):
# least loaded chassis will be in the front of the list
self.assertEqual(['hv1', 'hv3', 'hv2'], chassis)

def test_least_loaded_chassis_with_rebalance(self):
mapping = self.fake_chassis_gateway_mappings['Multiple4']
gateway_name = self.new_gateway_name
chassis = self.select(mapping, gateway_name)
# least loaded chassis will be in the front of the list
self.assertEqual(['hv2', 'hv1'], chassis)

def test_existing_chassis_available_for_existing_gateway(self):
mapping = self.fake_chassis_gateway_mappings['Multiple1']
gateway_name = random.choice(list(mapping['Gateways'].keys()))
chassis = self.select(mapping, gateway_name)
self.assertEqual(mapping['Gateways'][gateway_name], chassis)
self.assertEqual(ovn_const.MAX_GW_CHASSIS, len(chassis))

def test__get_chassis_load_by_prios_several_ports(self):
# Adding 5 ports of prio 1 and 5 ports of prio 2


+ 22
- 11
networking_ovn/tests/unit/ovsdb/test_impl_idl_ovn.py View File

@@ -11,7 +11,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
import collections
import copy
import uuid

@@ -583,19 +583,12 @@ class TestNBImplIdlOvn(TestDBImplIdlOvn):
# Test only host-1 in the valid list
unhosted_gateways = self.nb_ovn_idl.get_unhosted_gateways(
{}, {'host-1': 'physnet1'}, [])
expected = {
utils.ovn_lrouter_port_name('orp-id-b2'): {
ovn_const.OVN_GATEWAY_CHASSIS_KEY: 'host-2'},
utils.ovn_lrouter_port_name('orp-id-a3'): {
ovn_const.OVN_GATEWAY_CHASSIS_KEY:
ovn_const.OVN_GATEWAY_INVALID_CHASSIS}}
expected = ['lrp-orp-id-a1', 'lrp-orp-id-a2',
'lrp-orp-id-a3', 'lrp-orp-id-b2']
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'}, [])
expected = {utils.ovn_lrouter_port_name('orp-id-a3'): {
ovn_const.OVN_GATEWAY_CHASSIS_KEY:
ovn_const.OVN_GATEWAY_INVALID_CHASSIS}}
self.assertItemsEqual(unhosted_gateways, expected)
# Schedule unhosted_gateways on host-2
for unhosted_gateway in unhosted_gateways:
@@ -605,7 +598,25 @@ class TestNBImplIdlOvn(TestDBImplIdlOvn):
ovn_const.OVN_GATEWAY_CHASSIS_KEY: 'host-2'})
unhosted_gateways = self.nb_ovn_idl.get_unhosted_gateways(
{}, {'host-1': 'physnet1', 'host-2': 'physnet2'}, [])
self.assertItemsEqual(unhosted_gateways, {})
self.assertItemsEqual(unhosted_gateways, expected)

def test_unhosted_gateway_max_chassis(self):
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'] = []
self._load_nb_db()
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'}, [])
expected = []
self.assertItemsEqual(unhosted_gateways, expected)

def test_get_subnet_dhcp_options(self):
self._load_nb_db()


Loading…
Cancel
Save