a1735c46d8
Only reschedule gateways/update segments when things have changed that would require those actions. Co-Authored-By: Terry Wilson <twilson@redhat.com> Change-Id: I62f53dbd862c0f38af4a1434d453e97c18777eb4 Closes-bug: #1861510 Closes-bug: #1861509
553 lines
26 KiB
Python
553 lines
26 KiB
Python
# Copyright 2020 Red Hat, Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import mock
|
|
|
|
from neutron.common.ovn import constants as ovn_const
|
|
from neutron.common.ovn import utils as ovn_utils
|
|
from neutron.common import utils as n_utils
|
|
from neutron.scheduler import l3_ovn_scheduler as l3_sched
|
|
from neutron.tests.functional import base
|
|
from neutron.tests.functional.resources.ovsdb import events
|
|
from neutron_lib.api.definitions import external_net
|
|
from neutron_lib.api.definitions import l3 as l3_apidef
|
|
from neutron_lib.api.definitions import portbindings
|
|
from neutron_lib.api.definitions import provider_net as pnet
|
|
from neutron_lib import constants as n_consts
|
|
from neutron_lib.plugins import directory
|
|
from ovsdbapp.backend.ovs_idl import idlutils
|
|
|
|
|
|
class TestRouter(base.TestOVNFunctionalBase):
|
|
def setUp(self):
|
|
super(TestRouter, self).setUp()
|
|
self.chassis1 = self.add_fake_chassis(
|
|
'ovs-host1', physical_nets=['physnet1', 'physnet3'])
|
|
self.chassis2 = self.add_fake_chassis(
|
|
'ovs-host2', physical_nets=['physnet2', 'physnet3'])
|
|
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):
|
|
router = {'router':
|
|
{'name': name,
|
|
'admin_state_up': True,
|
|
'tenant_id': self._tenant_id}}
|
|
if gw_info:
|
|
router['router']['external_gateway_info'] = gw_info
|
|
return self.l3_plugin.create_router(self.context, router)
|
|
|
|
def _create_ext_network(self, name, net_type, physnet, seg,
|
|
gateway, cidr):
|
|
arg_list = (pnet.NETWORK_TYPE, external_net.EXTERNAL,)
|
|
net_arg = {pnet.NETWORK_TYPE: net_type,
|
|
external_net.EXTERNAL: True}
|
|
if seg:
|
|
arg_list = arg_list + (pnet.SEGMENTATION_ID,)
|
|
net_arg[pnet.SEGMENTATION_ID] = seg
|
|
if physnet:
|
|
arg_list = arg_list + (pnet.PHYSICAL_NETWORK,)
|
|
net_arg[pnet.PHYSICAL_NETWORK] = physnet
|
|
network = self._make_network(self.fmt, name, True,
|
|
arg_list=arg_list, **net_arg)
|
|
if cidr:
|
|
self._make_subnet(self.fmt, network, gateway, cidr,
|
|
ip_version=n_consts.IP_VERSION_4)
|
|
return network
|
|
|
|
def _set_redirect_chassis_to_invalid_chassis(self, ovn_client):
|
|
with ovn_client._nb_idl.transaction(check_error=True) as txn:
|
|
for lrp in self.nb_api.tables[
|
|
'Logical_Router_Port'].rows.values():
|
|
txn.add(ovn_client._nb_idl.update_lrouter_port(
|
|
lrp.name,
|
|
gateway_chassis=[ovn_const.OVN_GATEWAY_INVALID_CHASSIS]))
|
|
|
|
def test_gateway_chassis_on_router_gateway_port(self):
|
|
ext2 = self._create_ext_network(
|
|
'ext2', 'flat', 'physnet3', None, "20.0.0.1", "20.0.0.0/24")
|
|
gw_info = {'network_id': ext2['network']['id']}
|
|
self._create_router('router1', gw_info=gw_info)
|
|
expected = [row.name for row in
|
|
self.sb_api.tables['Chassis'].rows.values()]
|
|
for row in self.nb_api.tables[
|
|
'Logical_Router_Port'].rows.values():
|
|
if self._l3_ha_supported():
|
|
chassis = [gwc.chassis_name for gwc in row.gateway_chassis]
|
|
self.assertItemsEqual(expected, chassis)
|
|
else:
|
|
rc = row.options.get(ovn_const.OVN_GATEWAY_CHASSIS_KEY)
|
|
self.assertIn(rc, expected)
|
|
|
|
def _check_gateway_chassis_candidates(self, candidates):
|
|
# In this test, fake_select() is called once from _create_router()
|
|
# and later from schedule_unhosted_gateways()
|
|
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")
|
|
# mock select function and check if it is called with expected
|
|
# candidates.
|
|
|
|
def fake_select(*args, **kwargs):
|
|
self.assertItemsEqual(candidates, kwargs['candidates'])
|
|
# We are not interested in further processing, let us return
|
|
# INVALID_CHASSIS to avoid erros
|
|
return [ovn_const.OVN_GATEWAY_INVALID_CHASSIS]
|
|
|
|
with mock.patch.object(ovn_client._ovn_scheduler, 'select',
|
|
side_effect=fake_select) as client_select,\
|
|
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.assertFalse(plugin_select.called)
|
|
self.assertTrue(client_select.called)
|
|
client_select.reset_mock()
|
|
plugin_select.reset_mock()
|
|
|
|
# set redirect-chassis to neutron-ovn-invalid-chassis, so
|
|
# that schedule_unhosted_gateways will try to schedule it
|
|
self._set_redirect_chassis_to_invalid_chassis(ovn_client)
|
|
self.l3_plugin.schedule_unhosted_gateways()
|
|
self.assertFalse(client_select.called)
|
|
self.assertTrue(plugin_select.called)
|
|
|
|
def test_gateway_chassis_with_cms_and_bridge_mappings(self):
|
|
# Both chassis1 and chassis3 are having proper bridge mappings,
|
|
# but only chassis3 is having enable-chassis-as-gw.
|
|
# 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'})
|
|
self._check_gateway_chassis_candidates([self.chassis3])
|
|
|
|
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.
|
|
self.chassis3 = self.add_fake_chassis(
|
|
'ovs-host3',
|
|
external_ids={'ovn-cms-options': 'enable-chassis-as-gw'})
|
|
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,
|
|
# but none of the chassis having enable-chassis-as-gw.
|
|
# Test if chassis1 is selected as candidate or not.
|
|
self._check_gateway_chassis_candidates([self.chassis1])
|
|
|
|
def _l3_ha_supported(self):
|
|
# If the Gateway_Chassis table exists in SB database, then it
|
|
# means that L3 HA is supported.
|
|
return self.nb_api.tables.get('Gateway_Chassis')
|
|
|
|
def test_gateway_chassis_least_loaded_scheduler(self):
|
|
# This test will create 4 routers each with its own gateway.
|
|
# Using the least loaded policy for scheduling gateway ports, we
|
|
# expect that they are equally distributed across the two available
|
|
# chassis.
|
|
ovn_client = self.l3_plugin._ovn_client
|
|
ovn_client._ovn_scheduler = l3_sched.OVNGatewayLeastLoadedScheduler()
|
|
ext1 = self._create_ext_network(
|
|
'ext1', 'flat', 'physnet3', None, "20.0.0.1", "20.0.0.0/24")
|
|
gw_info = {'network_id': ext1['network']['id']}
|
|
|
|
# Create 4 routers with a gateway. Since we're using physnet3, the
|
|
# chassis candidates will be chassis1 and chassis2.
|
|
for i in range(1, 5):
|
|
self._create_router('router%d' % i, gw_info=gw_info)
|
|
|
|
# At this point we expect two gateways to be present in chassis1
|
|
# and two in chassis2. If schema supports L3 HA, we expect each
|
|
# chassis to host 2 priority 2 gateways and 2 priority 1 ones.
|
|
if self._l3_ha_supported():
|
|
# Each chassis contains a dict of (priority, # of ports hosted).
|
|
# {1: 2, 2: 2} means that this chassis hosts 2 ports of prio 1
|
|
# and two ports of prio 2.
|
|
expected = {self.chassis1: {1: 2, 2: 2},
|
|
self.chassis2: {1: 2, 2: 2}}
|
|
else:
|
|
# For non L3 HA, each chassis should contain two gateway ports.
|
|
expected = {self.chassis1: 2,
|
|
self.chassis2: 2}
|
|
sched_info = {}
|
|
for row in self.nb_api.tables[
|
|
'Logical_Router_Port'].rows.values():
|
|
if self._l3_ha_supported():
|
|
for gwc in row.gateway_chassis:
|
|
chassis = sched_info.setdefault(gwc.chassis_name, {})
|
|
chassis[gwc.priority] = chassis.get(gwc.priority, 0) + 1
|
|
else:
|
|
rc = row.options.get(ovn_const.OVN_GATEWAY_CHASSIS_KEY)
|
|
sched_info[rc] = sched_info.get(rc, 0) + 1
|
|
self.assertEqual(expected, sched_info)
|
|
|
|
def _get_gw_port(self, router_id):
|
|
router = self.l3_plugin._get_router(self.context, router_id)
|
|
gw_port_id = router.get('gw_port_id', '')
|
|
for row in self.nb_api.tables['Logical_Router_Port'].rows.values():
|
|
if row.name == 'lrp-%s' % gw_port_id:
|
|
return row
|
|
|
|
def test_gateway_chassis_with_subnet_changes(self):
|
|
"""Launchpad bug #1843485: logical router port is getting lost
|
|
|
|
Test cases when subnets are added to an external network after router
|
|
has been configured to use that network via "set --external-gateway"
|
|
"""
|
|
|
|
ovn_client = self.l3_plugin._ovn_client
|
|
|
|
with mock.patch.object(
|
|
ovn_client._ovn_scheduler, 'select',
|
|
return_value=[ovn_const.OVN_GATEWAY_INVALID_CHASSIS]) as \
|
|
client_select:
|
|
router1 = self._create_router('router1', gw_info=None)
|
|
router_id = router1['id']
|
|
self.assertIsNone(self._get_gw_port(router_id),
|
|
"router logical port unexpected before ext net")
|
|
|
|
# Create external network with no subnets and assign it to router
|
|
ext1 = self._create_ext_network(
|
|
'ext1', 'flat', 'physnet3', None, gateway=None, cidr=None)
|
|
net_id = ext1['network']['id']
|
|
|
|
gw_info = {'network_id': ext1['network']['id']}
|
|
self.l3_plugin.update_router(
|
|
self.context, router_id,
|
|
{'router': {l3_apidef.EXTERNAL_GW_INFO: gw_info}})
|
|
self.assertIsNotNone(self._get_gw_port(router_id),
|
|
"router logical port must exist after gw add")
|
|
|
|
# Add subnets to external network. This should percolate
|
|
# into l3_plugin.update_router()
|
|
kwargs = {'ip_version': n_consts.IP_VERSION_4,
|
|
'gateway_ip': '10.0.0.1', 'cidr': '10.0.0.0/24'}
|
|
subnet4_res = self._create_subnet(
|
|
self.fmt, net_id, **kwargs)
|
|
subnet4 = self.deserialize(self.fmt, subnet4_res).get('subnet')
|
|
self.assertIsNotNone(self._get_gw_port(router_id),
|
|
"router logical port must exist after v4 add")
|
|
|
|
kwargs = {'ip_version': n_consts.IP_VERSION_6,
|
|
'gateway_ip': 'fe81::1', 'cidr': 'fe81::/64',
|
|
'ipv6_ra_mode': n_consts.IPV6_SLAAC,
|
|
'ipv6_address_mode': n_consts.IPV6_SLAAC}
|
|
subnet6_res = self._create_subnet(
|
|
self.fmt, net_id, **kwargs)
|
|
subnet6 = self.deserialize(self.fmt, subnet6_res).get('subnet')
|
|
self.assertIsNotNone(self._get_gw_port(router_id),
|
|
"router logical port must exist after v6 add")
|
|
|
|
self.assertGreaterEqual(client_select.call_count, 3)
|
|
|
|
# Verify that ports have had the subnets created
|
|
kwargs = {'device_owner': n_consts.DEVICE_OWNER_ROUTER_GW}
|
|
ports_res = self._list_ports(self.fmt, net_id=net_id, **kwargs)
|
|
ports = self.deserialize(self.fmt, ports_res).get('ports')
|
|
subnet4_ip = None
|
|
subnet6_ip = None
|
|
for port in ports:
|
|
for fixed_ip in port.get('fixed_ips', []):
|
|
if fixed_ip.get('subnet_id') == subnet4['id']:
|
|
subnet4_ip = fixed_ip.get('ip_address')
|
|
if fixed_ip.get('subnet_id') == subnet6['id']:
|
|
subnet6_ip = fixed_ip.get('ip_address')
|
|
self.assertIsNotNone(subnet4_ip)
|
|
self.assertIsNotNone(subnet6_ip)
|
|
|
|
# Verify that logical router port is properly configured
|
|
gw_port = self._get_gw_port(router_id)
|
|
self.assertIsNotNone(gw_port)
|
|
|
|
expected_networks = ['%s/24' % subnet4_ip, '%s/64' % subnet6_ip]
|
|
self.assertItemsEqual(
|
|
expected_networks, gw_port.networks,
|
|
'networks in ovn port must match fixed_ips in neutron')
|
|
|
|
def test_logical_router_port_creation(self):
|
|
"""Launchpad bug #1844652: Verify creation and removal of lrp
|
|
|
|
This test verifies that logical router port is created and removed
|
|
based on attaching and detaching the external network to a router.
|
|
"""
|
|
router = self._create_router('router1', gw_info=None)
|
|
router_id = router['id']
|
|
self.assertIsNone(self._get_gw_port(router_id),
|
|
"router logical port unexpected before ext net")
|
|
|
|
# Create external network and assign it to router
|
|
ext1 = self._create_ext_network(
|
|
'ext1', 'flat', 'physnet3', None, gateway=None, cidr=None)
|
|
gw_info = {'network_id': ext1['network']['id']}
|
|
self.l3_plugin.update_router(
|
|
self.context, router_id,
|
|
{'router': {l3_apidef.EXTERNAL_GW_INFO: gw_info}})
|
|
self.assertIsNotNone(self._get_gw_port(router_id),
|
|
"router logical port missing after ext net add")
|
|
|
|
# Un-assign external network from router
|
|
self.l3_plugin.update_router(
|
|
self.context, router_id,
|
|
{'router': {l3_apidef.EXTERNAL_GW_INFO: None}})
|
|
self.assertIsNone(self._get_gw_port(router_id),
|
|
"router logical port exists after ext net removal")
|
|
|
|
def test_gateway_chassis_with_bridge_mappings(self):
|
|
"""Check selected ovn chassis based on external network
|
|
|
|
This test sets different gateway values to ensure that the proper
|
|
chassis are candidates, based on the physical network mappings.
|
|
"""
|
|
|
|
ovn_client = self.l3_plugin._ovn_client
|
|
# Create external networks with vlan, flat and geneve network types
|
|
ext1 = self._create_ext_network(
|
|
'ext1', 'vlan', 'physnet1', 1, "10.0.0.1", "10.0.0.0/24")
|
|
ext2 = self._create_ext_network(
|
|
'ext2', 'flat', 'physnet3', None, "20.0.0.1", "20.0.0.0/24")
|
|
ext3 = self._create_ext_network(
|
|
'ext3', 'geneve', None, 10, "30.0.0.1", "30.0.0.0/24")
|
|
# mock select function and check if it is called with expected
|
|
# candidates.
|
|
self.candidates = []
|
|
|
|
def fake_select(*args, **kwargs):
|
|
self.assertItemsEqual(self.candidates, kwargs['candidates'])
|
|
# We are not interested in further processing, let us return
|
|
# INVALID_CHASSIS to avoid erros
|
|
return [ovn_const.OVN_GATEWAY_INVALID_CHASSIS]
|
|
|
|
with mock.patch.object(ovn_client._ovn_scheduler, 'select',
|
|
side_effect=fake_select) as client_select,\
|
|
mock.patch.object(self.l3_plugin.scheduler, 'select',
|
|
side_effect=fake_select) as plugin_select:
|
|
self.candidates = [self.chassis1]
|
|
gw_info = {'network_id': ext1['network']['id']}
|
|
router1 = self._create_router('router1', gw_info=gw_info)
|
|
|
|
# set redirect-chassis to neutron-ovn-invalid-chassis, so
|
|
# that schedule_unhosted_gateways will try to schedule it
|
|
self._set_redirect_chassis_to_invalid_chassis(ovn_client)
|
|
self.l3_plugin.schedule_unhosted_gateways()
|
|
|
|
self.candidates = [self.chassis1, self.chassis2]
|
|
gw_info = {'network_id': ext2['network']['id']}
|
|
self.l3_plugin.update_router(
|
|
self.context, router1['id'],
|
|
{'router': {l3_apidef.EXTERNAL_GW_INFO: gw_info}})
|
|
self._set_redirect_chassis_to_invalid_chassis(ovn_client)
|
|
self.l3_plugin.schedule_unhosted_gateways()
|
|
|
|
self.candidates = []
|
|
gw_info = {'network_id': ext3['network']['id']}
|
|
self.l3_plugin.update_router(
|
|
self.context, router1['id'],
|
|
{'router': {l3_apidef.EXTERNAL_GW_INFO: gw_info}})
|
|
self._set_redirect_chassis_to_invalid_chassis(ovn_client)
|
|
self.l3_plugin.schedule_unhosted_gateways()
|
|
|
|
# We can't test call_count for these mocks, as we have disabled
|
|
# maintenance_worker which will trigger chassis events
|
|
# and eventually calling schedule_unhosted_gateways.
|
|
# However, we know for sure that these mocks must have been
|
|
# called at least 3 times because that is the number of times
|
|
# this test invokes them: 1x create_router + 2x update_router
|
|
# for client_select mock; and 3x schedule_unhosted_gateways for
|
|
# plugin_select mock.
|
|
self.assertGreaterEqual(client_select.call_count, 3)
|
|
self.assertGreaterEqual(plugin_select.call_count, 3)
|
|
|
|
def test_router_gateway_port_binding_host_id(self):
|
|
# Test setting chassis on chassisredirect port in Port_Binding table,
|
|
# will update host_id of corresponding router gateway port
|
|
# with this chassis.
|
|
chassis = idlutils.row_by_value(self.sb_api.idl, 'Chassis',
|
|
'name', self.chassis1)
|
|
host_id = chassis.hostname
|
|
ext = self._create_ext_network(
|
|
'ext1', 'vlan', 'physnet1', 1, "10.0.0.1", "10.0.0.0/24")
|
|
gw_info = {'network_id': ext['network']['id']}
|
|
router = self._create_router('router1', gw_info=gw_info)
|
|
core_plugin = directory.get_plugin()
|
|
gw_port_id = router.get('gw_port_id')
|
|
|
|
# Set chassis on chassisredirect port in Port_Binding table
|
|
logical_port = 'cr-lrp-%s' % gw_port_id
|
|
self.assertTrue(self.cr_lrp_pb_event.wait(logical_port))
|
|
self.sb_api.lsp_bind(logical_port, self.chassis1,
|
|
may_exist=True).execute(check_error=True)
|
|
|
|
def check_port_binding_host_id(port_id):
|
|
port = core_plugin.get_ports(
|
|
self.context, filters={'id': [port_id]})[0]
|
|
return port[portbindings.HOST_ID] == host_id
|
|
|
|
# Test if router gateway port updated with this chassis
|
|
n_utils.wait_until_true(lambda: check_port_binding_host_id(
|
|
gw_port_id))
|
|
|
|
def _validate_router_ipv6_ra_configs(self, lrp_name, expected_ra_confs):
|
|
lrp = idlutils.row_by_value(self.nb_api.idl,
|
|
'Logical_Router_Port', 'name', lrp_name)
|
|
self.assertEqual(expected_ra_confs, lrp.ipv6_ra_configs)
|
|
|
|
def _test_router_port_ipv6_ra_configs_helper(
|
|
self, cidr='aef0::/64', ip_version=6,
|
|
address_mode=n_consts.IPV6_SLAAC,):
|
|
router1 = self._create_router('router1')
|
|
n1 = self._make_network(self.fmt, 'n1', True)
|
|
if ip_version == 6:
|
|
kwargs = {'ip_version': 6, 'cidr': 'aef0::/64',
|
|
'ipv6_address_mode': address_mode,
|
|
'ipv6_ra_mode': address_mode}
|
|
else:
|
|
kwargs = {'ip_version': 4, 'cidr': '10.0.0.0/24'}
|
|
|
|
res = self._create_subnet(self.fmt, n1['network']['id'],
|
|
**kwargs)
|
|
|
|
n1_s1 = self.deserialize(self.fmt, res)
|
|
n1_s1_id = n1_s1['subnet']['id']
|
|
router_iface_info = self.l3_plugin.add_router_interface(
|
|
self.context, router1['id'], {'subnet_id': n1_s1_id})
|
|
|
|
lrp_name = ovn_utils.ovn_lrouter_port_name(
|
|
router_iface_info['port_id'])
|
|
if ip_version == 6:
|
|
expected_ra_configs = {
|
|
'address_mode': ovn_utils.get_ovn_ipv6_address_mode(
|
|
address_mode),
|
|
'send_periodic': 'true',
|
|
'mtu': '1450'}
|
|
else:
|
|
expected_ra_configs = {}
|
|
self._validate_router_ipv6_ra_configs(lrp_name, expected_ra_configs)
|
|
|
|
def test_router_port_ipv6_ra_configs_addr_mode_slaac(self):
|
|
self._test_router_port_ipv6_ra_configs_helper()
|
|
|
|
def test_router_port_ipv6_ra_configs_addr_mode_dhcpv6_stateful(self):
|
|
self._test_router_port_ipv6_ra_configs_helper(
|
|
address_mode=n_consts.DHCPV6_STATEFUL)
|
|
|
|
def test_router_port_ipv6_ra_configs_addr_mode_dhcpv6_stateless(self):
|
|
self._test_router_port_ipv6_ra_configs_helper(
|
|
address_mode=n_consts.DHCPV6_STATELESS)
|
|
|
|
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)
|
|
gw_port_id = router.get('gw_port_id')
|
|
logical_port = 'cr-lrp-%s' % gw_port_id
|
|
self.assertTrue(self.cr_lrp_pb_event.wait(logical_port))
|
|
self.sb_api.lsp_bind(logical_port, chassis4,
|
|
may_exist=True).execute(check_error=True)
|
|
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)
|
|
gw_port_id = router.get('gw_port_id')
|
|
logical_port = 'cr-lrp-%s' % gw_port_id
|
|
self.assertTrue(self.cr_lrp_pb_event.wait(logical_port))
|
|
self.sb_api.lsp_bind(logical_port, chassis_list[0],
|
|
may_exist=True).execute(check_error=True)
|
|
|
|
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))
|