[ovn] Implement support for external-gateway-multihoming extension
The general approach is to update the ovn_client and ovn_db_sync code to handle multiple gateway ports wherever a single gateway port is handled today. In this patch set multiple static routes for default gateway will be added by default when multiple gw ports are present. Support for the `enable_default_route_ecmp` attribute to control this behavior will be added in subsequent patch set to avoid making this change too large. Partial-Bug: #2002687 Change-Id: I00b1f29172be5a0034b921b11af3a8d502273766
This commit is contained in:
parent
3ef02cc2fb
commit
0bc9a71387
neutron
common/ovn
plugins/ml2/drivers/ovn/mech_driver/ovsdb
tests
@ -41,6 +41,7 @@ from neutron_lib.api.definitions import floating_ip_port_forwarding
|
||||
from neutron_lib.api.definitions import floatingip_pools
|
||||
from neutron_lib.api.definitions import l3
|
||||
from neutron_lib.api.definitions import l3_ext_gw_mode
|
||||
from neutron_lib.api.definitions import l3_ext_gw_multihoming
|
||||
from neutron_lib.api.definitions import l3_flavors
|
||||
from neutron_lib.api.definitions import logging
|
||||
from neutron_lib.api.definitions import multiprovidernet
|
||||
@ -118,6 +119,7 @@ ML2_SUPPORTED_API_EXTENSIONS_OVN_L3 = [
|
||||
raz_def.ALIAS,
|
||||
flavors.ALIAS,
|
||||
l3_flavors.ALIAS,
|
||||
l3_ext_gw_multihoming.ALIAS,
|
||||
]
|
||||
ML2_SUPPORTED_API_EXTENSIONS = [
|
||||
address_group.ALIAS,
|
||||
|
@ -19,6 +19,7 @@ import datetime
|
||||
|
||||
import netaddr
|
||||
from neutron_lib.api.definitions import l3
|
||||
from neutron_lib.api.definitions import l3_ext_gw_multihoming
|
||||
from neutron_lib.api.definitions import port_security as psec
|
||||
from neutron_lib.api.definitions import portbindings
|
||||
from neutron_lib.api.definitions import provider_net as pnet
|
||||
@ -1180,17 +1181,21 @@ class OVNClient(object):
|
||||
LOG.error('Unable to disassociate floating ip in gateway '
|
||||
'router. Error: %s', e)
|
||||
|
||||
def _get_gw_info(self, context, router):
|
||||
def _get_gw_info(self, context, port_dict):
|
||||
gateways_info = []
|
||||
ext_gw_info = router.get(l3.EXTERNAL_GW_INFO, {})
|
||||
network_id = ext_gw_info.get('network_id', '')
|
||||
for ext_fixed_ip in ext_gw_info.get('external_fixed_ips', []):
|
||||
subnet_id = ext_fixed_ip['subnet_id']
|
||||
subnet = self._plugin.get_subnet(context, subnet_id)
|
||||
network_id = port_dict.get('network_id')
|
||||
subnet_by_id = {
|
||||
subnet['id']: subnet
|
||||
for subnet in self._plugin.get_subnets_by_network(
|
||||
context, network_id)}
|
||||
for fixed_ip in port_dict.get('fixed_ips'):
|
||||
subnet_id = fixed_ip.get('subnet_id')
|
||||
subnet = subnet_by_id.get(subnet_id)
|
||||
ip_version = subnet.get('ip_version')
|
||||
gateways_info.append(GW_INFO(
|
||||
network_id, subnet_id, ext_fixed_ip['ip_address'],
|
||||
subnet.get('gateway_ip'), subnet['ip_version'],
|
||||
const.IPv4_ANY if subnet['ip_version'] == const.IP_VERSION_4
|
||||
network_id, subnet_id, fixed_ip.get('ip_address'),
|
||||
subnet.get('gateway_ip'), ip_version,
|
||||
const.IPv4_ANY if ip_version == const.IP_VERSION_4
|
||||
else const.IPv6_ANY))
|
||||
return gateways_info
|
||||
|
||||
@ -1199,21 +1204,23 @@ class OVNClient(object):
|
||||
if not networks:
|
||||
networks = []
|
||||
router_id = router['id']
|
||||
gw_port_id = router['gw_port_id']
|
||||
gw_lrouter_name = utils.ovn_name(router_id)
|
||||
gateways = self._get_gw_info(context, router)
|
||||
for gw_info in gateways:
|
||||
if gw_info.ip_version == const.IP_VERSION_4:
|
||||
for network in networks:
|
||||
txn.add(self._nb_idl.delete_nat_rule_in_lrouter(
|
||||
gw_lrouter_name, type='snat', logical_ip=network,
|
||||
external_ip=gw_info.router_ip))
|
||||
txn.add(self._nb_idl.delete_static_route(
|
||||
gw_lrouter_name, ip_prefix=gw_info.ip_prefix,
|
||||
nexthop=gw_info.gateway_ip))
|
||||
txn.add(self._nb_idl.delete_lrouter_port(
|
||||
utils.ovn_lrouter_port_name(gw_port_id),
|
||||
gw_lrouter_name))
|
||||
deleted_ports = []
|
||||
for gw_port in self._get_router_gw_ports(context, router_id):
|
||||
for gw_info in self._get_gw_info(context, gw_port):
|
||||
if gw_info.ip_version == const.IP_VERSION_4:
|
||||
for network in networks:
|
||||
txn.add(self._nb_idl.delete_nat_rule_in_lrouter(
|
||||
gw_lrouter_name, type='snat', logical_ip=network,
|
||||
external_ip=gw_info.router_ip))
|
||||
txn.add(self._nb_idl.delete_static_route(
|
||||
gw_lrouter_name, ip_prefix=gw_info.ip_prefix,
|
||||
nexthop=gw_info.gateway_ip))
|
||||
txn.add(self._nb_idl.delete_lrouter_port(
|
||||
utils.ovn_lrouter_port_name(gw_port['id']),
|
||||
gw_lrouter_name))
|
||||
deleted_ports.append(gw_port['id'])
|
||||
return deleted_ports
|
||||
|
||||
def _get_nets_and_ipv6_ra_confs_for_router_port(self, context, port):
|
||||
port_fixed_ips = port['fixed_ips']
|
||||
@ -1249,34 +1256,35 @@ class OVNClient(object):
|
||||
return list(networks), ipv6_ra_configs
|
||||
|
||||
def _add_router_ext_gw(self, context, router, networks, txn):
|
||||
lrouter_name = utils.ovn_name(router['id'])
|
||||
|
||||
# 1. Add the external gateway router port.
|
||||
admin_context = context.elevated()
|
||||
gateways = self._get_gw_info(admin_context, router)
|
||||
gw_port_id = router['gw_port_id']
|
||||
port = self._plugin.get_port(admin_context, gw_port_id)
|
||||
self._create_lrouter_port(admin_context, router, port, txn=txn)
|
||||
added_ports = []
|
||||
for gw_port in self._get_router_gw_ports(admin_context, router['id']):
|
||||
port = self._plugin.get_port(admin_context, gw_port['id'])
|
||||
self._create_lrouter_port(admin_context, router, port, txn=txn)
|
||||
added_ports.append(port)
|
||||
|
||||
# 2. Add default route with nexthop as gateway ip
|
||||
lrouter_name = utils.ovn_name(router['id'])
|
||||
for gw_info in gateways:
|
||||
if gw_info.gateway_ip is None:
|
||||
continue
|
||||
columns = {'external_ids': {
|
||||
ovn_const.OVN_ROUTER_IS_EXT_GW: 'true',
|
||||
ovn_const.OVN_SUBNET_EXT_ID_KEY: gw_info.subnet_id}}
|
||||
txn.add(self._nb_idl.add_static_route(
|
||||
lrouter_name, ip_prefix=gw_info.ip_prefix,
|
||||
nexthop=gw_info.gateway_ip, **columns))
|
||||
# 2. Add default route with nexthop as gateway ip
|
||||
for gw_info in self._get_gw_info(admin_context, gw_port):
|
||||
if gw_info.gateway_ip is None:
|
||||
continue
|
||||
columns = {'external_ids': {
|
||||
ovn_const.OVN_ROUTER_IS_EXT_GW: 'true',
|
||||
ovn_const.OVN_SUBNET_EXT_ID_KEY: gw_info.subnet_id}}
|
||||
txn.add(self._nb_idl.add_static_route(
|
||||
lrouter_name, ip_prefix=gw_info.ip_prefix,
|
||||
nexthop=gw_info.gateway_ip, **columns))
|
||||
|
||||
# 3. Add snat rules for tenant networks in lrouter if snat is enabled
|
||||
if utils.is_snat_enabled(router) and networks:
|
||||
self.update_nat_rules(router, networks, enable_snat=True, txn=txn)
|
||||
return port
|
||||
return added_ports
|
||||
|
||||
def _check_external_ips_changed(self, ovn_snats,
|
||||
ovn_static_routes, router):
|
||||
context = n_context.get_admin_context()
|
||||
gateways = self._get_gw_info(context, router)
|
||||
ovn_gw_subnets = None
|
||||
if self._nb_idl.is_col_present('Logical_Router_Static_Route',
|
||||
'external_ids'):
|
||||
@ -1285,15 +1293,29 @@ class OVNClient(object):
|
||||
ovn_const.OVN_SUBNET_EXT_ID_KEY) for route in
|
||||
ovn_static_routes]
|
||||
|
||||
for gw_info in gateways:
|
||||
if ovn_gw_subnets and gw_info.subnet_id not in ovn_gw_subnets:
|
||||
return True
|
||||
if gw_info.ip_version == const.IP_VERSION_6:
|
||||
continue
|
||||
for snat in ovn_snats:
|
||||
if snat.external_ip != gw_info.router_ip:
|
||||
for gw_port in self._get_router_gw_ports(context, router['id']):
|
||||
gw_infos = self._get_gw_info(context, gw_port)
|
||||
if not gw_infos:
|
||||
# The router is attached to a external network without a subnet
|
||||
lrp = self._nb_idl.get_lrouter_port(
|
||||
utils.ovn_lrouter_port_name(gw_port['id']))
|
||||
if not lrp:
|
||||
continue
|
||||
lrp_ext_ids = getattr(lrp, 'external_ids', {})
|
||||
if (ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY in lrp_ext_ids and
|
||||
lrp_ext_ids[ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY] != (
|
||||
utils.ovn_name(gw_port['network_id']))):
|
||||
return True
|
||||
|
||||
for gw_info in gw_infos:
|
||||
if ovn_gw_subnets and gw_info.subnet_id not in ovn_gw_subnets:
|
||||
return True
|
||||
if gw_info.ip_version == const.IP_VERSION_6:
|
||||
continue
|
||||
for snat in ovn_snats:
|
||||
if snat.external_ip != gw_info.router_ip:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def update_router_routes(self, context, router_id, add, remove,
|
||||
@ -1315,7 +1337,9 @@ class OVNClient(object):
|
||||
self._transaction(commands, txn=txn)
|
||||
|
||||
def _get_router_gw_ports(self, context, router_id):
|
||||
return self._plugin.get_ports(context, filters={
|
||||
# NOTE(fnordahl): an elevated context is required here to ensure we
|
||||
# have access to the data.
|
||||
return self._plugin.get_ports(context.elevated(), filters={
|
||||
'device_owner': [const.DEVICE_OWNER_ROUTER_GW],
|
||||
'device_id': [router_id]})
|
||||
|
||||
@ -1367,7 +1391,7 @@ class OVNClient(object):
|
||||
external_ids = self._gen_router_ext_ids(router)
|
||||
enabled = router.get('admin_state_up')
|
||||
lrouter_name = utils.ovn_name(router['id'])
|
||||
added_gw_port = None
|
||||
added_gw_ports = []
|
||||
options = {'always_learn_from_arp_request': 'false',
|
||||
'dynamic_neigh_routers': 'true',
|
||||
ovn_const.LR_OPTIONS_MAC_AGE_LIMIT:
|
||||
@ -1383,14 +1407,15 @@ class OVNClient(object):
|
||||
if add_external_gateway:
|
||||
networks = self._get_v4_network_of_all_router_ports(
|
||||
context, router['id'])
|
||||
if router.get(l3.EXTERNAL_GW_INFO) and networks is not None:
|
||||
added_gw_port = self._add_router_ext_gw(
|
||||
if (router.get(l3_ext_gw_multihoming.EXTERNAL_GATEWAYS) and
|
||||
networks is not None):
|
||||
added_gw_ports = self._add_router_ext_gw(
|
||||
context, router, networks, txn)
|
||||
|
||||
self._qos_driver.create_router(txn, router)
|
||||
|
||||
if added_gw_port:
|
||||
db_rev.bump_revision(context, added_gw_port,
|
||||
for gw_port in added_gw_ports:
|
||||
db_rev.bump_revision(context, gw_port,
|
||||
ovn_const.TYPE_ROUTER_PORTS)
|
||||
db_rev.bump_revision(context, router, ovn_const.TYPE_ROUTERS)
|
||||
|
||||
@ -1402,13 +1427,16 @@ class OVNClient(object):
|
||||
router_id = new_router['id']
|
||||
router_name = utils.ovn_name(router_id)
|
||||
ovn_router = self._nb_idl.get_lrouter(router_name)
|
||||
gateway_new = new_router.get(l3.EXTERNAL_GW_INFO)
|
||||
# Note that this needs to be retrieved from the request
|
||||
gateway_new = new_router.get(l3_ext_gw_multihoming.EXTERNAL_GATEWAYS)
|
||||
gateway_old = utils.get_lrouter_ext_gw_static_route(ovn_router)
|
||||
added_gw_port = None
|
||||
deleted_gw_port_id = None
|
||||
added_gw_ports = []
|
||||
deleted_gw_port_ids = []
|
||||
|
||||
if router_object:
|
||||
gateway_old = gateway_old or router_object.get(l3.EXTERNAL_GW_INFO)
|
||||
gateway_old = gateway_old or router_object.get(
|
||||
l3_ext_gw_multihoming.EXTERNAL_GATEWAYS)
|
||||
|
||||
ovn_snats = utils.get_lrouter_snats(ovn_router)
|
||||
networks = self._get_v4_network_of_all_router_ports(context, router_id)
|
||||
try:
|
||||
@ -1418,15 +1446,14 @@ class OVNClient(object):
|
||||
txn.add(check_rev_cmd)
|
||||
if gateway_new and not gateway_old:
|
||||
# Route gateway is set
|
||||
added_gw_port = self._add_router_ext_gw(
|
||||
added_gw_ports = self._add_router_ext_gw(
|
||||
context, new_router, networks, txn)
|
||||
elif gateway_old and not gateway_new:
|
||||
# router gateway is removed
|
||||
txn.add(self._nb_idl.delete_lrouter_ext_gw(router_name))
|
||||
if router_object:
|
||||
self._delete_router_ext_gw(
|
||||
deleted_gw_port_ids = self._delete_router_ext_gw(
|
||||
router_object, networks, txn)
|
||||
deleted_gw_port_id = router_object['gw_port_id']
|
||||
elif gateway_new and gateway_old:
|
||||
# Check if external gateway has changed, if yes, delete
|
||||
# the old gateway and add the new gateway
|
||||
@ -1435,14 +1462,13 @@ class OVNClient(object):
|
||||
txn.add(self._nb_idl.delete_lrouter_ext_gw(
|
||||
router_name))
|
||||
if router_object:
|
||||
self._delete_router_ext_gw(
|
||||
deleted_gw_port_ids = self._delete_router_ext_gw(
|
||||
router_object, networks, txn)
|
||||
deleted_gw_port_id = router_object['gw_port_id']
|
||||
added_gw_port = self._add_router_ext_gw(
|
||||
added_gw_ports = self._add_router_ext_gw(
|
||||
context, new_router, networks, txn)
|
||||
else:
|
||||
# Check if snat has been enabled/disabled and update
|
||||
new_snat_state = gateway_new.get('enable_snat', True)
|
||||
new_snat_state = utils.is_snat_enabled(new_router)
|
||||
if bool(ovn_snats) != new_snat_state and networks:
|
||||
self.update_nat_rules(
|
||||
new_router, networks,
|
||||
@ -1465,12 +1491,12 @@ class OVNClient(object):
|
||||
db_rev.bump_revision(context, new_router,
|
||||
ovn_const.TYPE_ROUTERS)
|
||||
|
||||
if added_gw_port:
|
||||
db_rev.bump_revision(context, added_gw_port,
|
||||
for gw_port in added_gw_ports:
|
||||
db_rev.bump_revision(context, gw_port,
|
||||
ovn_const.TYPE_ROUTER_PORTS)
|
||||
|
||||
if deleted_gw_port_id:
|
||||
db_rev.delete_revision(context, deleted_gw_port_id,
|
||||
for gw_port in deleted_gw_port_ids:
|
||||
db_rev.delete_revision(context, gw_port,
|
||||
ovn_const.TYPE_ROUTER_PORTS)
|
||||
|
||||
except Exception as e:
|
||||
@ -1692,7 +1718,8 @@ class OVNClient(object):
|
||||
else:
|
||||
self._create_lrouter_port(context, router, port, txn=txn)
|
||||
|
||||
if router.get(l3.EXTERNAL_GW_INFO):
|
||||
gw_ports = self._get_router_gw_ports(context, router_id)
|
||||
if gw_ports:
|
||||
cidr = None
|
||||
for fixed_ip in port['fixed_ips']:
|
||||
subnet = self._plugin.get_subnet(context,
|
||||
@ -1705,9 +1732,10 @@ class OVNClient(object):
|
||||
cidr = subnet['cidr']
|
||||
|
||||
if ovn_conf.is_ovn_emit_need_to_frag_enabled():
|
||||
provider_net = self._plugin.get_network(
|
||||
context, router[l3.EXTERNAL_GW_INFO]['network_id'])
|
||||
self.set_gateway_mtu(context, provider_net)
|
||||
for gw_port in gw_ports:
|
||||
provider_net = self._plugin.get_network(
|
||||
context, gw_port['network_id'])
|
||||
self.set_gateway_mtu(context, provider_net)
|
||||
|
||||
if utils.is_snat_enabled(router) and cidr:
|
||||
self.update_nat_rules(router, networks=[cidr],
|
||||
@ -1816,14 +1844,16 @@ class OVNClient(object):
|
||||
router_id = port.get('device_id')
|
||||
|
||||
router = None
|
||||
gw_ports = []
|
||||
if router_id:
|
||||
try:
|
||||
router = self._l3_plugin.get_router(context, router_id)
|
||||
gw_ports = self._get_router_gw_ports(context, router_id)
|
||||
except l3_exc.RouterNotFound:
|
||||
# If the router is gone, the router port is also gone
|
||||
port_removed = True
|
||||
|
||||
if not router or not router.get(l3.EXTERNAL_GW_INFO):
|
||||
if not router or not gw_ports:
|
||||
if port_removed:
|
||||
self._delete_lrouter_port(context, port_id, router_id,
|
||||
txn=txn)
|
||||
@ -1838,11 +1868,11 @@ class OVNClient(object):
|
||||
elif port:
|
||||
subnet_ids = utils.get_port_subnet_ids(port)
|
||||
|
||||
if (ovn_conf.is_ovn_emit_need_to_frag_enabled() and
|
||||
router.get('gw_port_id')):
|
||||
provider_net = self._plugin.get_network(
|
||||
context, router[l3.EXTERNAL_GW_INFO]['network_id'])
|
||||
self.set_gateway_mtu(context, provider_net, txn=txn)
|
||||
if ovn_conf.is_ovn_emit_need_to_frag_enabled():
|
||||
for gw_port in gw_ports:
|
||||
provider_net = self._plugin.get_network(
|
||||
context, gw_port['network_id'])
|
||||
self.set_gateway_mtu(context, provider_net, txn=txn)
|
||||
|
||||
cidr = None
|
||||
for sid in subnet_ids:
|
||||
@ -1881,10 +1911,12 @@ class OVNClient(object):
|
||||
func = (self._nb_idl.add_nat_rule_in_lrouter if enable_snat else
|
||||
self._nb_idl.delete_nat_rule_in_lrouter)
|
||||
gw_lrouter_name = utils.ovn_name(router['id'])
|
||||
gateways = self._get_gw_info(context, router)
|
||||
# Update NAT rules only for IPv4 subnets
|
||||
commands = [func(gw_lrouter_name, type='snat', logical_ip=network,
|
||||
external_ip=gw_info.router_ip) for gw_info in gateways
|
||||
external_ip=gw_info.router_ip)
|
||||
for gw_port in self._get_router_gw_ports(context,
|
||||
router['id'])
|
||||
for gw_info in self._get_gw_info(context, gw_port)
|
||||
if gw_info.ip_version != const.IP_VERSION_6
|
||||
for network in networks]
|
||||
self._transaction(commands, txn=txn)
|
||||
|
@ -15,7 +15,6 @@ from datetime import datetime
|
||||
import itertools
|
||||
|
||||
from eventlet import greenthread
|
||||
from neutron_lib.api.definitions import l3
|
||||
from neutron_lib.api.definitions import segment as segment_def
|
||||
from neutron_lib import constants
|
||||
from neutron_lib import context
|
||||
@ -527,32 +526,28 @@ class OvnNbSynchronizer(OvnDbSynchronizer):
|
||||
db_extends[router['id']]['snats'] = []
|
||||
db_extends[router['id']]['fips'] = []
|
||||
db_extends[router['id']]['fips_pfs'] = []
|
||||
if not router.get(l3.EXTERNAL_GW_INFO):
|
||||
continue
|
||||
gateways = self._ovn_client._get_gw_info(ctx, router)
|
||||
for gw_info in gateways:
|
||||
prefix = (constants.IPv4_ANY if
|
||||
gw_info.ip_version == constants.IP_VERSION_4 else
|
||||
constants.IPv6_ANY)
|
||||
if gw_info.gateway_ip:
|
||||
db_extends[router['id']]['routes'].append(
|
||||
{'destination': prefix,
|
||||
'nexthop': gw_info.gateway_ip,
|
||||
'external_ids': {
|
||||
ovn_const.OVN_ROUTER_IS_EXT_GW: 'true',
|
||||
ovn_const.OVN_SUBNET_EXT_ID_KEY:
|
||||
gw_info.subnet_id}})
|
||||
if gw_info.ip_version == constants.IP_VERSION_6:
|
||||
continue
|
||||
if gw_info.router_ip and utils.is_snat_enabled(router):
|
||||
networks = (
|
||||
self._ovn_client._get_v4_network_of_all_router_ports(
|
||||
ctx, router['id']))
|
||||
for network in networks:
|
||||
db_extends[router['id']]['snats'].append({
|
||||
'logical_ip': network,
|
||||
'external_ip': gw_info.router_ip,
|
||||
'type': 'snat'})
|
||||
for gw_port in self._ovn_client._get_router_gw_ports(ctx,
|
||||
router['id']):
|
||||
for gw_info in self._ovn_client._get_gw_info(ctx, gw_port):
|
||||
if gw_info.gateway_ip:
|
||||
db_extends[router['id']]['routes'].append(
|
||||
{'destination': gw_info.ip_prefix,
|
||||
'nexthop': gw_info.gateway_ip,
|
||||
'external_ids': {
|
||||
ovn_const.OVN_ROUTER_IS_EXT_GW: 'true',
|
||||
ovn_const.OVN_SUBNET_EXT_ID_KEY:
|
||||
gw_info.subnet_id}})
|
||||
if gw_info.ip_version == constants.IP_VERSION_6:
|
||||
continue
|
||||
if gw_info.router_ip and utils.is_snat_enabled(router):
|
||||
networks = self._ovn_client.\
|
||||
_get_v4_network_of_all_router_ports(
|
||||
ctx, router['id'])
|
||||
for network in networks:
|
||||
db_extends[router['id']]['snats'].append({
|
||||
'logical_ip': network,
|
||||
'external_ip': gw_info.router_ip,
|
||||
'type': 'snat'})
|
||||
|
||||
fips = self.l3_plugin.get_floatingips(
|
||||
ctx, {'router_id': list(db_routers.keys())})
|
||||
|
@ -139,7 +139,7 @@ class TestOVNFunctionalBase(test_plugin.Ml2PluginV2TestCase,
|
||||
OVN_SCHEMA_FILES = ['ovn-nb.ovsschema', 'ovn-sb.ovsschema']
|
||||
|
||||
_mechanism_drivers = ['logger', 'ovn']
|
||||
_extension_drivers = ['port_security']
|
||||
_extension_drivers = ['port_security', 'external-gateway-multihoming']
|
||||
_counter = 0
|
||||
l3_plugin = 'neutron.services.ovn_l3.plugin.OVNL3RouterPlugin'
|
||||
|
||||
|
@ -19,7 +19,6 @@ from neutron_lib.api.definitions import dns as dns_apidef
|
||||
from neutron_lib.api.definitions import fip_pf_description as ext_pf_def
|
||||
from neutron_lib.api.definitions import fip_pf_port_range as ranges_pf_def
|
||||
from neutron_lib.api.definitions import floating_ip_port_forwarding as pf_def
|
||||
from neutron_lib.api.definitions import l3
|
||||
from neutron_lib.api.definitions import port_security as ps
|
||||
from neutron_lib import constants
|
||||
from neutron_lib import context
|
||||
@ -1192,10 +1191,10 @@ class TestOvnNbSync(base.TestOVNFunctionalBase):
|
||||
db_route['nexthop']
|
||||
for db_route in db_router['routes']]
|
||||
db_nats[db_router['id']] = []
|
||||
if db_router.get(l3.EXTERNAL_GW_INFO):
|
||||
gateways = self.l3_plugin._ovn_client._get_gw_info(
|
||||
self.context, db_router)
|
||||
for gw_info in gateways:
|
||||
for gw_port in self.l3_plugin._ovn_client._get_router_gw_ports(
|
||||
self.context, db_router['id']):
|
||||
for gw_info in self.l3_plugin._ovn_client._get_gw_info(
|
||||
self.context, gw_port):
|
||||
# Add gateway default route and snats
|
||||
if gw_info.gateway_ip:
|
||||
db_routes[db_router['id']].append(gw_info.ip_prefix +
|
||||
|
@ -53,6 +53,16 @@ class TestRouter(base.TestOVNFunctionalBase):
|
||||
router['router']['external_gateway_info'] = gw_info
|
||||
return self.l3_plugin.create_router(self.context, router)
|
||||
|
||||
def _add_external_gateways(self, router_id, external_gateways):
|
||||
router = {'router': {'external_gateways': external_gateways}}
|
||||
return self.l3_plugin.add_external_gateways(
|
||||
self.context, router_id, body=router)
|
||||
|
||||
def _remove_external_gateways(self, router_id, external_gateways):
|
||||
router = {'router': {'external_gateways': external_gateways}}
|
||||
return self.l3_plugin.remove_external_gateways(
|
||||
self.context, router_id, body=router)
|
||||
|
||||
def _create_ext_network(self, name, net_type, physnet, seg,
|
||||
gateway, cidr):
|
||||
arg_list = (pnet.NETWORK_TYPE, external_net.EXTERNAL,)
|
||||
@ -504,6 +514,27 @@ class TestRouter(base.TestOVNFunctionalBase):
|
||||
self._test_router_port_ipv6_ra_configs_helper(
|
||||
ip_version=4)
|
||||
|
||||
def test_create_delete_router_multiple_gw_ports(self):
|
||||
ext4 = self._create_ext_network(
|
||||
'ext4', 'flat', 'physnet4', None, "40.0.0.1", "40.0.0.0/24")
|
||||
router = self._create_router('router4')
|
||||
gws = self._add_external_gateways(
|
||||
router['id'],
|
||||
[
|
||||
{'network_id': ext4['network']['id']},
|
||||
{'network_id': ext4['network']['id']},
|
||||
]
|
||||
)
|
||||
lr = self.nb_api.lookup('Logical_Router',
|
||||
ovn_utils.ovn_name(router['id']))
|
||||
self.assertEqual(
|
||||
len(lr.ports),
|
||||
len(gws['router']['external_gateways']))
|
||||
|
||||
self.l3_plugin.delete_router(self.context, id=router['id'])
|
||||
self.assertRaises(idlutils.RowNotFound, self.nb_api.lookup,
|
||||
'Logical_Router', ovn_utils.ovn_name(router['id']))
|
||||
|
||||
def test_gateway_chassis_rebalance(self):
|
||||
def _get_result_dict():
|
||||
sched_info = {}
|
||||
|
@ -158,6 +158,7 @@ class FakeOvsdbNbOvnIdl(object):
|
||||
self.ha_chassis_group_del = mock.Mock()
|
||||
self.ha_chassis_group_add_chassis = mock.Mock()
|
||||
self.ha_chassis_group_del_chassis = mock.Mock()
|
||||
self.get_lrouter_port = mock.Mock()
|
||||
self.get_lrouter_gw_ports = mock.Mock()
|
||||
self.lrp_get = mock.Mock()
|
||||
self.get_schema_version = mock.Mock(return_value='3.6.0')
|
||||
|
@ -21,6 +21,7 @@ from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
|
||||
from neutron.plugins.ml2 import db as ml2_db
|
||||
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_client
|
||||
from neutron.tests import base
|
||||
from neutron.tests.unit import fake_resources as fakes
|
||||
from neutron.tests.unit.services.logapi.drivers.ovn \
|
||||
import test_driver as test_log_driver
|
||||
from neutron_lib.api.definitions import l3
|
||||
@ -55,24 +56,28 @@ class TestOVNClient(TestOVNClientBase):
|
||||
plugin = mock.MagicMock()
|
||||
self.get_plugin.return_value = plugin
|
||||
subnet = {
|
||||
'subnet_id': 'fake-subnet-id',
|
||||
'id': 'fake-subnet-id',
|
||||
'gateway_ip': '10.42.0.1',
|
||||
'ip_version': const.IP_VERSION_4,
|
||||
}
|
||||
plugin.get_subnet.return_value = subnet
|
||||
plugin.get_subnets_by_network.return_value = [subnet]
|
||||
router = {
|
||||
'id': 'fake-router-id',
|
||||
l3.EXTERNAL_GW_INFO: {
|
||||
'external_fixed_ips': [{
|
||||
'subnet_id': subnet.get('subnet_id'),
|
||||
'ip_address': '10.42.0.42'}],
|
||||
},
|
||||
'gw_port_id': 'fake-port-id',
|
||||
}
|
||||
networks = mock.MagicMock()
|
||||
txn = mock.MagicMock()
|
||||
self.ovn_client._get_router_gw_ports = mock.MagicMock()
|
||||
gw_port = fakes.FakePort().create_one_port(
|
||||
attrs={
|
||||
'fixed_ips': [{
|
||||
'subnet_id': subnet.get('id'),
|
||||
'ip_address': '10.42.0.42'}]
|
||||
})
|
||||
self.ovn_client._get_router_gw_ports.return_value = [gw_port]
|
||||
self.assertEqual(
|
||||
self.get_plugin().get_port(),
|
||||
[self.get_plugin().get_port()],
|
||||
self.ovn_client._add_router_ext_gw(mock.Mock(), router, networks,
|
||||
txn))
|
||||
self.nb_idl.add_static_route.assert_called_once_with(
|
||||
@ -81,30 +86,39 @@ class TestOVNClient(TestOVNClientBase):
|
||||
nexthop='10.42.0.1',
|
||||
external_ids={
|
||||
'neutron:is_ext_gw': 'true',
|
||||
'neutron:subnet_id': subnet['subnet_id']})
|
||||
'neutron:subnet_id': subnet['id']})
|
||||
|
||||
def test__add_router_ext_gw_no_default_route(self):
|
||||
plugin = mock.MagicMock()
|
||||
self.get_plugin.return_value = plugin
|
||||
subnet = {
|
||||
'subnet_id': 'fake-subnet-id',
|
||||
'id': 'fake-subnet-id',
|
||||
'gateway_ip': None,
|
||||
'ip_version': const.IP_VERSION_4
|
||||
}
|
||||
plugin.get_subnet.return_value = subnet
|
||||
plugin.get_subnets_by_network.return_value = [subnet]
|
||||
router = {
|
||||
'id': 'fake-router-id',
|
||||
l3.EXTERNAL_GW_INFO: {
|
||||
'external_fixed_ips': [{
|
||||
'subnet_id': subnet.get('subnet_id'),
|
||||
'subnet_id': subnet.get('id'),
|
||||
'ip_address': '10.42.0.42'}],
|
||||
},
|
||||
'gw_port_id': 'fake-port-id',
|
||||
}
|
||||
networks = mock.MagicMock()
|
||||
txn = mock.MagicMock()
|
||||
self.ovn_client._get_router_gw_ports = mock.MagicMock()
|
||||
gw_port = fakes.FakePort().create_one_port(
|
||||
attrs={
|
||||
'fixed_ips': [{
|
||||
'subnet_id': subnet.get('id'),
|
||||
'ip_address': '10.42.0.42'}]
|
||||
})
|
||||
self.ovn_client._get_router_gw_ports.return_value = [gw_port]
|
||||
self.assertEqual(
|
||||
self.get_plugin().get_port(),
|
||||
[self.get_plugin().get_port()],
|
||||
self.ovn_client._add_router_ext_gw(mock.Mock(), router, networks,
|
||||
txn))
|
||||
self.nb_idl.add_static_route.assert_not_called()
|
||||
|
@ -353,19 +353,19 @@ class TestOvnNbSyncML2(test_mech_driver.OVNMechanismDriverTestCase):
|
||||
'external_ids': {'subnet_id': 'n1-s1'}}
|
||||
return {'cidr': '', 'options': '', 'external_ids': {}}
|
||||
|
||||
def _fake_get_gw_info(self, ctx, router):
|
||||
def _fake_get_gw_info(self, ctx, port):
|
||||
return {
|
||||
'r1': [ovn_client.GW_INFO(router_ip='90.0.0.2',
|
||||
gateway_ip='90.0.0.1',
|
||||
network_id='', subnet_id='ext-subnet',
|
||||
ip_version=4,
|
||||
ip_prefix=const.IPv4_ANY)],
|
||||
'r2': [ovn_client.GW_INFO(router_ip='100.0.0.2',
|
||||
gateway_ip='100.0.0.1',
|
||||
network_id='', subnet_id='ext-subnet',
|
||||
ip_version=4,
|
||||
ip_prefix=const.IPv4_ANY)]
|
||||
}.get(router['id'], [])
|
||||
'p1r1': [ovn_client.GW_INFO(router_ip='90.0.0.2',
|
||||
gateway_ip='90.0.0.1',
|
||||
network_id='', subnet_id='ext-subnet',
|
||||
ip_version=4,
|
||||
ip_prefix=const.IPv4_ANY)],
|
||||
'p1r2': [ovn_client.GW_INFO(router_ip='100.0.0.2',
|
||||
gateway_ip='100.0.0.1',
|
||||
network_id='', subnet_id='ext-subnet',
|
||||
ip_version=4,
|
||||
ip_prefix=const.IPv4_ANY)]
|
||||
}.get(port['id'], [])
|
||||
|
||||
def _fake_get_v4_network_of_all_router_ports(self, ctx, router_id):
|
||||
return {'r1': ['172.16.0.0/24', '172.16.2.0/24'],
|
||||
@ -532,6 +532,11 @@ class TestOvnNbSyncML2(test_mech_driver.OVNMechanismDriverTestCase):
|
||||
ovn_api.delete_dhcp_options = mock.Mock()
|
||||
ovn_nb_synchronizer._ovn_client.get_port_dns_records = mock.Mock()
|
||||
ovn_nb_synchronizer._ovn_client.get_port_dns_records.return_value = {}
|
||||
ovn_nb_synchronizer._ovn_client._get_router_gw_ports.side_effect = (
|
||||
[self.get_sync_router_ports[0]],
|
||||
[self.get_sync_router_ports[1]],
|
||||
[self.get_sync_router_ports[2]],
|
||||
)
|
||||
|
||||
def _test_ovn_nb_sync_helper(self, ovn_nb_synchronizer,
|
||||
networks, ports,
|
||||
|
@ -58,9 +58,11 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
_mechanism_drivers = ['ovn']
|
||||
l3_plugin = 'neutron.services.ovn_l3.plugin.OVNL3RouterPlugin'
|
||||
|
||||
def _start_mock(self, path, return_value, new_callable=None):
|
||||
def _start_mock(self, path, return_value=None, new_callable=None,
|
||||
side_effect=None):
|
||||
patcher = mock.patch(path, return_value=return_value,
|
||||
new_callable=new_callable)
|
||||
new_callable=new_callable,
|
||||
side_effect=side_effect)
|
||||
patch = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
return patch
|
||||
@ -128,6 +130,7 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
'admin_state_up': True,
|
||||
'flavor_id': None,
|
||||
'external_gateway_info': self.fake_external_fixed_ips,
|
||||
'external_gateways': [self.fake_external_fixed_ips],
|
||||
'gw_port_id': 'gw-port-id'
|
||||
}
|
||||
self.fake_router_without_ext_gw = {
|
||||
@ -144,8 +147,9 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
'fixed_ips': [{'ip_address': '192.168.1.1',
|
||||
'subnet_id': 'ext-subnet-id'}],
|
||||
'mac_address': '00:00:00:02:04:06',
|
||||
'network_id': self.fake_network['id'],
|
||||
'network_id': 'ext-network-id',
|
||||
'id': 'gw-port-id'}
|
||||
self.fake_ext_gw_ports = [self.fake_ext_gw_port]
|
||||
self.fake_ext_gw_port_assert = {
|
||||
'lrouter': 'neutron-router-id',
|
||||
'mac': '00:00:00:02:04:06',
|
||||
@ -156,7 +160,7 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
ovn_const.OVN_SUBNET_EXT_IDS_KEY: 'ext-subnet-id',
|
||||
ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
|
||||
ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY:
|
||||
utils.ovn_name(self.fake_network['id'])},
|
||||
utils.ovn_name('ext-network-id')},
|
||||
'gateway_chassis': ['hv1'],
|
||||
'options': {}}
|
||||
self.fake_floating_ip_attrs = {'floating_ip_address': '192.168.0.10',
|
||||
@ -250,6 +254,13 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
self.get_subnet = self._start_mock(
|
||||
'neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_subnet',
|
||||
return_value=self.fake_subnet)
|
||||
self.get_subnets_by_network = self._start_mock(
|
||||
'neutron.db.db_base_plugin_v2.NeutronDbPluginV2.'
|
||||
'get_subnets_by_network',
|
||||
side_effect=lambda _, x: {
|
||||
self.fake_network['id']: [self.fake_subnet],
|
||||
'ext-network-id': [self.fake_ext_subnet],
|
||||
}[x])
|
||||
self.get_router = self._start_mock(
|
||||
'neutron.db.l3_db.L3_NAT_dbonly_mixin.get_router',
|
||||
return_value=self.fake_router)
|
||||
@ -318,6 +329,10 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
self._start_mock(
|
||||
'neutron.common.ovn.utils.is_nat_gateway_port_supported',
|
||||
return_value=False)
|
||||
self._get_router_gw_ports = self._start_mock(
|
||||
'neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.ovn_client.'
|
||||
'OVNClient._get_router_gw_ports',
|
||||
return_value=self.fake_ext_gw_ports)
|
||||
|
||||
def test__plugin_driver(self):
|
||||
# No valid mech drivers should raise an exception.
|
||||
@ -818,6 +833,9 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
self.fake_subnet)
|
||||
self.get_port.return_value = self.fake_ext_gw_port
|
||||
grps.return_value = self.fake_router_ports
|
||||
fake_old_ext_gw_port = self.fake_ext_gw_port.copy()
|
||||
fake_old_ext_gw_port['id'] = 'old-gw-port-id'
|
||||
self._get_router_gw_ports.return_value = [fake_old_ext_gw_port]
|
||||
|
||||
payload = self._create_payload_for_router_update(
|
||||
self.get_router.return_value, self.fake_router_with_ext_gw)
|
||||
|
Loading…
x
Reference in New Issue
Block a user