[ovn] Add support for enable_default_route_bfd attribute
When ``enable_default_route_bfd`` is set, maintain BFD records along with default route records. Default route records will also be fitted with the `output_port` key, which is a requirement for the OVN BFD support. Partial-Bug: #2002687 Signed-off-by: Frode Nordahl <frode.nordahl@canonical.com> Change-Id: I34e2453ab206c13c3ca40c4181970c320bdd8e67
This commit is contained in:
parent
743bd1ccef
commit
cc1ff09b9e
@ -40,6 +40,7 @@ from neutron_lib.api.definitions import flavors
|
|||||||
from neutron_lib.api.definitions import floating_ip_port_forwarding
|
from neutron_lib.api.definitions import floating_ip_port_forwarding
|
||||||
from neutron_lib.api.definitions import floatingip_pools
|
from neutron_lib.api.definitions import floatingip_pools
|
||||||
from neutron_lib.api.definitions import l3
|
from neutron_lib.api.definitions import l3
|
||||||
|
from neutron_lib.api.definitions import l3_enable_default_route_bfd
|
||||||
from neutron_lib.api.definitions import l3_enable_default_route_ecmp
|
from neutron_lib.api.definitions import l3_enable_default_route_ecmp
|
||||||
from neutron_lib.api.definitions import l3_ext_gw_mode
|
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_ext_gw_multihoming
|
||||||
@ -122,6 +123,7 @@ ML2_SUPPORTED_API_EXTENSIONS_OVN_L3 = [
|
|||||||
flavors.ALIAS,
|
flavors.ALIAS,
|
||||||
l3_flavors.ALIAS,
|
l3_flavors.ALIAS,
|
||||||
l3_ext_gw_multihoming.ALIAS,
|
l3_ext_gw_multihoming.ALIAS,
|
||||||
|
l3_enable_default_route_bfd.ALIAS,
|
||||||
l3_enable_default_route_ecmp.ALIAS,
|
l3_enable_default_route_ecmp.ALIAS,
|
||||||
]
|
]
|
||||||
ML2_SUPPORTED_API_EXTENSIONS = [
|
ML2_SUPPORTED_API_EXTENSIONS = [
|
||||||
|
@ -261,11 +261,13 @@ class API(api.API, metaclass=abc.ABCMeta):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def add_static_route(self, lrouter, **columns):
|
def add_static_route(self, lrouter, maintain_bfd=False, **columns):
|
||||||
"""Add static route to logical router.
|
"""Add static route to logical router.
|
||||||
|
|
||||||
:param lrouter: The unique name of the lrouter
|
:param lrouter: The unique name of the lrouter
|
||||||
:type lrouter: string
|
:type lrouter: string
|
||||||
|
:param maintain_bfd: Ensure a BFD record exists for the static route.
|
||||||
|
:type maintain_bfd: bool
|
||||||
:param columns: Dictionary of static columns
|
:param columns: Dictionary of static columns
|
||||||
Supported columns: prefix, nexthop, valid
|
Supported columns: prefix, nexthop, valid
|
||||||
:type columns: dictionary
|
:type columns: dictionary
|
||||||
@ -533,11 +535,13 @@ class API(api.API, metaclass=abc.ABCMeta):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def delete_lrouter_ext_gw(self, lrouter_name):
|
def delete_lrouter_ext_gw(self, lrouter_name, maintain_bfd=True):
|
||||||
"""Delete Logical Router external gateway.
|
"""Delete Logical Router external gateway.
|
||||||
|
|
||||||
:param lrouter_name: The name of the logical router
|
:param lrouter_name: The name of the logical router
|
||||||
:type lrouter_name: string
|
:type lrouter_name: string
|
||||||
|
:param maintain_bfd: Ensure any existing BFD record is removed.
|
||||||
|
:type maintain_bfd: bool
|
||||||
:returns: :class:`Command` with no result
|
:returns: :class:`Command` with no result
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
from ovsdbapp.backend.ovs_idl import command
|
from ovsdbapp.backend.ovs_idl import command
|
||||||
from ovsdbapp.backend.ovs_idl import idlutils
|
from ovsdbapp.backend.ovs_idl import idlutils
|
||||||
|
from ovsdbapp.schema.ovn_northbound import commands as ovn_nb_commands
|
||||||
from ovsdbapp import utils as ovsdbapp_utils
|
from ovsdbapp import utils as ovsdbapp_utils
|
||||||
|
|
||||||
from neutron._i18n import _
|
from neutron._i18n import _
|
||||||
@ -624,9 +625,10 @@ class DelACLCommand(command.BaseCommand):
|
|||||||
|
|
||||||
|
|
||||||
class AddStaticRouteCommand(command.BaseCommand):
|
class AddStaticRouteCommand(command.BaseCommand):
|
||||||
def __init__(self, api, lrouter, **columns):
|
def __init__(self, api, lrouter, maintain_bfd=False, **columns):
|
||||||
super(AddStaticRouteCommand, self).__init__(api)
|
super(AddStaticRouteCommand, self).__init__(api)
|
||||||
self.lrouter = lrouter
|
self.lrouter = lrouter
|
||||||
|
self.maintain_bfd = maintain_bfd
|
||||||
self.columns = columns
|
self.columns = columns
|
||||||
|
|
||||||
def run_idl(self, txn):
|
def run_idl(self, txn):
|
||||||
@ -637,9 +639,29 @@ class AddStaticRouteCommand(command.BaseCommand):
|
|||||||
msg = _("Logical Router %s does not exist") % self.lrouter
|
msg = _("Logical Router %s does not exist") % self.lrouter
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
|
bfd_uuid = None
|
||||||
|
if (self.maintain_bfd and
|
||||||
|
'nexthop' in self.columns and
|
||||||
|
'output_port' in self.columns):
|
||||||
|
cmd = ovn_nb_commands.BFDAddCommand(self.api,
|
||||||
|
self.columns['output_port'],
|
||||||
|
self.columns['nexthop'],
|
||||||
|
may_exist=True)
|
||||||
|
cmd.run_idl(txn)
|
||||||
|
try:
|
||||||
|
bfd_uuid = cmd.result.uuid
|
||||||
|
except AttributeError:
|
||||||
|
# When the BFD record is created in the same transaction the
|
||||||
|
# post commit code that would resolve the real UUID and look up
|
||||||
|
# the bfd record has not run yet, and consequently the object
|
||||||
|
# returned by BFDAddCommand() is an UUID object.
|
||||||
|
bfd_uuid = cmd.result
|
||||||
|
|
||||||
row = txn.insert(self.api._tables['Logical_Router_Static_Route'])
|
row = txn.insert(self.api._tables['Logical_Router_Static_Route'])
|
||||||
for col, val in self.columns.items():
|
for col, val in self.columns.items():
|
||||||
setattr(row, col, val)
|
setattr(row, col, val)
|
||||||
|
if bfd_uuid:
|
||||||
|
setattr(row, 'bfd', bfd_uuid)
|
||||||
_addvalue_to_list(lrouter, 'static_routes', row.uuid)
|
_addvalue_to_list(lrouter, 'static_routes', row.uuid)
|
||||||
|
|
||||||
|
|
||||||
@ -915,10 +937,11 @@ class CheckRevisionNumberCommand(command.BaseCommand):
|
|||||||
|
|
||||||
class DeleteLRouterExtGwCommand(command.BaseCommand):
|
class DeleteLRouterExtGwCommand(command.BaseCommand):
|
||||||
|
|
||||||
def __init__(self, api, lrouter, if_exists):
|
def __init__(self, api, lrouter, if_exists, maintain_bfd=True):
|
||||||
super(DeleteLRouterExtGwCommand, self).__init__(api)
|
super(DeleteLRouterExtGwCommand, self).__init__(api)
|
||||||
self.lrouter = lrouter
|
self.lrouter = lrouter
|
||||||
self.if_exists = if_exists
|
self.if_exists = if_exists
|
||||||
|
self.maintain_bfd = maintain_bfd
|
||||||
|
|
||||||
def run_idl(self, txn):
|
def run_idl(self, txn):
|
||||||
try:
|
try:
|
||||||
@ -930,9 +953,20 @@ class DeleteLRouterExtGwCommand(command.BaseCommand):
|
|||||||
msg = _("Logical Router %s does not exist") % self.lrouter
|
msg = _("Logical Router %s does not exist") % self.lrouter
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
|
if self.maintain_bfd:
|
||||||
|
lrp_names = set()
|
||||||
|
for lrp in getattr(lrouter, 'ports', []):
|
||||||
|
lrp_names.add(lrp.name)
|
||||||
for route in lrouter.static_routes:
|
for route in lrouter.static_routes:
|
||||||
external_ids = getattr(route, 'external_ids', {})
|
external_ids = getattr(route, 'external_ids', {})
|
||||||
if ovn_const.OVN_ROUTER_IS_EXT_GW in external_ids:
|
if ovn_const.OVN_ROUTER_IS_EXT_GW in external_ids:
|
||||||
|
bfd = getattr(route, 'bfd', [])
|
||||||
|
if bfd and self.maintain_bfd:
|
||||||
|
for bfd_rec in bfd:
|
||||||
|
bfd_logical_port = getattr(bfd_rec, 'logical_port', '')
|
||||||
|
if bfd_logical_port in lrp_names:
|
||||||
|
route.delvalue('bfd', bfd_rec)
|
||||||
|
bfd_rec.delete()
|
||||||
lrouter.delvalue('static_routes', route)
|
lrouter.delvalue('static_routes', route)
|
||||||
route.delete()
|
route.delete()
|
||||||
|
|
||||||
|
@ -464,8 +464,9 @@ class OvsdbNbOvnIdl(nb_impl_idl.OvnNbApiIdlImpl, Backend):
|
|||||||
def delete_acl(self, lswitch, lport, if_exists=True):
|
def delete_acl(self, lswitch, lport, if_exists=True):
|
||||||
return cmd.DelACLCommand(self, lswitch, lport, if_exists)
|
return cmd.DelACLCommand(self, lswitch, lport, if_exists)
|
||||||
|
|
||||||
def add_static_route(self, lrouter, **columns):
|
def add_static_route(self, lrouter, maintain_bfd=False, **columns):
|
||||||
return cmd.AddStaticRouteCommand(self, lrouter, **columns)
|
return cmd.AddStaticRouteCommand(self, lrouter, maintain_bfd,
|
||||||
|
**columns)
|
||||||
|
|
||||||
def delete_static_route(self, lrouter, ip_prefix, nexthop, if_exists=True):
|
def delete_static_route(self, lrouter, ip_prefix, nexthop, if_exists=True):
|
||||||
return cmd.DelStaticRouteCommand(self, lrouter, ip_prefix, nexthop,
|
return cmd.DelStaticRouteCommand(self, lrouter, ip_prefix, nexthop,
|
||||||
@ -815,8 +816,10 @@ class OvsdbNbOvnIdl(nb_impl_idl.OvnNbApiIdlImpl, Backend):
|
|||||||
return lr
|
return lr
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def delete_lrouter_ext_gw(self, lrouter_name, if_exists=True):
|
def delete_lrouter_ext_gw(self, lrouter_name, if_exists=True,
|
||||||
return cmd.DeleteLRouterExtGwCommand(self, lrouter_name, if_exists)
|
maintain_bfd=True):
|
||||||
|
return cmd.DeleteLRouterExtGwCommand(self, lrouter_name, if_exists,
|
||||||
|
maintain_bfd)
|
||||||
|
|
||||||
def get_port_group(self, pg_name):
|
def get_port_group(self, pg_name):
|
||||||
if uuidutils.is_uuid_like(pg_name):
|
if uuidutils.is_uuid_like(pg_name):
|
||||||
|
@ -1264,6 +1264,8 @@ class OVNClient(object):
|
|||||||
lrouter_name = utils.ovn_name(router['id'])
|
lrouter_name = utils.ovn_name(router['id'])
|
||||||
router_default_route_ecmp_enabled = router.get(
|
router_default_route_ecmp_enabled = router.get(
|
||||||
'enable_default_route_ecmp', False)
|
'enable_default_route_ecmp', False)
|
||||||
|
router_default_route_bfd_enabled = router.get(
|
||||||
|
'enable_default_route_bfd', False)
|
||||||
|
|
||||||
# 1. Add the external gateway router port.
|
# 1. Add the external gateway router port.
|
||||||
admin_context = context.elevated()
|
admin_context = context.elevated()
|
||||||
@ -1285,9 +1287,16 @@ class OVNClient(object):
|
|||||||
columns = {'external_ids': {
|
columns = {'external_ids': {
|
||||||
ovn_const.OVN_ROUTER_IS_EXT_GW: 'true',
|
ovn_const.OVN_ROUTER_IS_EXT_GW: 'true',
|
||||||
ovn_const.OVN_SUBNET_EXT_ID_KEY: gw_info.subnet_id}}
|
ovn_const.OVN_SUBNET_EXT_ID_KEY: gw_info.subnet_id}}
|
||||||
|
if router_default_route_bfd_enabled:
|
||||||
|
columns.update({
|
||||||
|
'output_port': utils.ovn_lrouter_port_name(
|
||||||
|
gw_port['id']),
|
||||||
|
})
|
||||||
txn.add(self._nb_idl.add_static_route(
|
txn.add(self._nb_idl.add_static_route(
|
||||||
lrouter_name, ip_prefix=gw_info.ip_prefix,
|
lrouter_name, ip_prefix=gw_info.ip_prefix,
|
||||||
nexthop=gw_info.gateway_ip, **columns))
|
nexthop=gw_info.gateway_ip,
|
||||||
|
maintain_bfd=router_default_route_bfd_enabled,
|
||||||
|
**columns))
|
||||||
|
|
||||||
# 3. Add snat rules for tenant networks in lrouter if snat is enabled
|
# 3. Add snat rules for tenant networks in lrouter if snat is enabled
|
||||||
if utils.is_snat_enabled(router) and networks:
|
if utils.is_snat_enabled(router) and networks:
|
||||||
@ -1328,6 +1337,22 @@ class OVNClient(object):
|
|||||||
if snat.external_ip != gw_info.router_ip:
|
if snat.external_ip != gw_info.router_ip:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
router_default_route_bfd = router.get(
|
||||||
|
'enable_default_route_bfd',
|
||||||
|
False
|
||||||
|
)
|
||||||
|
|
||||||
|
for route in ovn_static_routes:
|
||||||
|
# If gateway in OVN DB has static routes, the ovn_static_route
|
||||||
|
# parameter contains data from
|
||||||
|
# `utils.get_lrouter_ext_gw_static_route`, otherwise it will
|
||||||
|
# contain a Dict ref `update_router` method.
|
||||||
|
route_bfd = getattr(route, 'bfd', [])
|
||||||
|
if router_default_route_bfd and not route_bfd:
|
||||||
|
return True
|
||||||
|
elif route_bfd and not router_default_route_bfd:
|
||||||
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def update_router_routes(self, context, router_id, add, remove,
|
def update_router_routes(self, context, router_id, add, remove,
|
||||||
@ -1528,6 +1553,9 @@ class OVNClient(object):
|
|||||||
"""Delete a logical router."""
|
"""Delete a logical router."""
|
||||||
lrouter_name = utils.ovn_name(router_id)
|
lrouter_name = utils.ovn_name(router_id)
|
||||||
with self._nb_idl.transaction(check_error=True) as txn:
|
with self._nb_idl.transaction(check_error=True) as txn:
|
||||||
|
# This will ensure any BFD records are removed
|
||||||
|
txn.add(self._nb_idl.delete_lrouter_ext_gw(lrouter_name,
|
||||||
|
if_exists=True))
|
||||||
txn.add(self._nb_idl.lr_del(lrouter_name, if_exists=True))
|
txn.add(self._nb_idl.lr_del(lrouter_name, if_exists=True))
|
||||||
db_rev.delete_revision(context, router_id, ovn_const.TYPE_ROUTERS)
|
db_rev.delete_revision(context, router_id, ovn_const.TYPE_ROUTERS)
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ from neutron_lib import constants
|
|||||||
from oslo_utils import netutils
|
from oslo_utils import netutils
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
from ovsdbapp.backend.ovs_idl import connection
|
from ovsdbapp.backend.ovs_idl import connection
|
||||||
|
from ovsdbapp.backend.ovs_idl import idlutils
|
||||||
from ovsdbapp import constants as const
|
from ovsdbapp import constants as const
|
||||||
from ovsdbapp import event as ovsdb_event
|
from ovsdbapp import event as ovsdb_event
|
||||||
from ovsdbapp.tests.functional import base
|
from ovsdbapp.tests.functional import base
|
||||||
@ -417,6 +418,178 @@ class TestNbApi(BaseOvnIdlTest):
|
|||||||
self.assertEqual(r.options[ovn_const.LR_OPTIONS_MAC_AGE_LIMIT],
|
self.assertEqual(r.options[ovn_const.LR_OPTIONS_MAC_AGE_LIMIT],
|
||||||
ovn_conf.get_ovn_mac_binding_age_threshold())
|
ovn_conf.get_ovn_mac_binding_age_threshold())
|
||||||
|
|
||||||
|
def _add_static_route(self, txn, lr_name, lrp_name, **columns):
|
||||||
|
r = txn.add(self.nbapi.lr_add(lr_name))
|
||||||
|
if lrp_name:
|
||||||
|
txn.add(self.nbapi.add_lrouter_port(lrp_name, lr_name))
|
||||||
|
columns.update({'output_port': lrp_name})
|
||||||
|
txn.add(self.nbapi.add_static_route(lr_name, **columns))
|
||||||
|
|
||||||
|
return r
|
||||||
|
|
||||||
|
def test_add_static_route(self):
|
||||||
|
name = 'router_with_static_routes'
|
||||||
|
columns = {
|
||||||
|
'bfd': [],
|
||||||
|
'external_ids': {'fake_eid_key': 'fake_eid_value'},
|
||||||
|
'ip_prefix': '0.0.0.0/0',
|
||||||
|
'nexthop': '192.0.2.1',
|
||||||
|
'options': {'fake_option_key': 'fake_option_value'},
|
||||||
|
'output_port': [],
|
||||||
|
'policy': ['dst-ip'],
|
||||||
|
'route_table': '',
|
||||||
|
}
|
||||||
|
with self.nbapi.transaction(check_error=True) as txn:
|
||||||
|
r = self._add_static_route(txn, name, '', **columns)
|
||||||
|
for route in r.result.static_routes:
|
||||||
|
for k, v in columns.items():
|
||||||
|
self.assertEqual(
|
||||||
|
getattr(route, k),
|
||||||
|
v)
|
||||||
|
|
||||||
|
def _add_static_route_bfd_assert(self, r, lr_name, lrp_name, ip_prefix,
|
||||||
|
nexthop):
|
||||||
|
for route in r.result.static_routes:
|
||||||
|
self.assertEqual(
|
||||||
|
route.ip_prefix,
|
||||||
|
ip_prefix)
|
||||||
|
self.assertEqual(
|
||||||
|
route.nexthop,
|
||||||
|
nexthop)
|
||||||
|
self.assertEqual(
|
||||||
|
route.output_port[0],
|
||||||
|
lrp_name)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
route.bfd[0].logical_port,
|
||||||
|
lrp_name)
|
||||||
|
self.assertEqual(
|
||||||
|
route.bfd[0].dst_ip,
|
||||||
|
nexthop)
|
||||||
|
|
||||||
|
def test_add_static_route_bfd(self):
|
||||||
|
lr_name = 'router_with_static_routes_and_bfd'
|
||||||
|
lrp_name = 'lrp-' + lr_name
|
||||||
|
ip_prefix = '0.0.0.0/0'
|
||||||
|
nexthop = '192.0.2.1'
|
||||||
|
with self.nbapi.transaction(check_error=True) as txn:
|
||||||
|
r = self._add_static_route(txn, lr_name, lrp_name,
|
||||||
|
ip_prefix=ip_prefix,
|
||||||
|
nexthop=nexthop,
|
||||||
|
maintain_bfd=True)
|
||||||
|
|
||||||
|
self._add_static_route_bfd_assert(r, lr_name, lrp_name, ip_prefix,
|
||||||
|
nexthop)
|
||||||
|
|
||||||
|
def test_add_static_route_bfd_record_exists(self):
|
||||||
|
lr_name = 'router_with_static_routes_and_preexisting_bfd_record'
|
||||||
|
lrp_name = 'lrp-' + lr_name
|
||||||
|
ip_prefix = '0.0.0.0/0'
|
||||||
|
nexthop = '192.0.2.1'
|
||||||
|
with self.nbapi.transaction(check_error=True) as txn:
|
||||||
|
bfd = txn.add(self.nbapi.bfd_add(lrp_name, nexthop))
|
||||||
|
r = self._add_static_route(txn, lr_name, lrp_name,
|
||||||
|
ip_prefix=ip_prefix,
|
||||||
|
nexthop=nexthop,
|
||||||
|
maintain_bfd=True)
|
||||||
|
|
||||||
|
for route in r.result.static_routes:
|
||||||
|
self.assertEqual(
|
||||||
|
bfd.result,
|
||||||
|
route.bfd[0],
|
||||||
|
)
|
||||||
|
self._add_static_route_bfd_assert(r, lr_name, lrp_name, ip_prefix,
|
||||||
|
nexthop)
|
||||||
|
|
||||||
|
def test_add_static_route_bfd_record_exists_multiple_txn(self):
|
||||||
|
lr_name = 'router_with_static_routes_and_preexisting_bfd_record_txn'
|
||||||
|
lrp_name = 'lrp-' + lr_name
|
||||||
|
ip_prefix = '0.0.0.0/0'
|
||||||
|
nexthop = '192.0.2.1'
|
||||||
|
with self.nbapi.transaction(check_error=True) as txn:
|
||||||
|
bfd = txn.add(self.nbapi.bfd_add(lrp_name, nexthop))
|
||||||
|
with self.nbapi.transaction(check_error=True) as txn:
|
||||||
|
r = self._add_static_route(txn, lr_name, lrp_name,
|
||||||
|
ip_prefix=ip_prefix,
|
||||||
|
nexthop=nexthop,
|
||||||
|
maintain_bfd=True)
|
||||||
|
|
||||||
|
for route in r.result.static_routes:
|
||||||
|
self.assertEqual(
|
||||||
|
bfd.result,
|
||||||
|
route.bfd[0],
|
||||||
|
)
|
||||||
|
self._add_static_route_bfd_assert(r, lr_name, lrp_name, ip_prefix,
|
||||||
|
nexthop)
|
||||||
|
|
||||||
|
def test_delete_lrouter_ext_gw(self):
|
||||||
|
lr_name = 'router_with_ext_gw'
|
||||||
|
ip_prefix = '0.0.0.0/0'
|
||||||
|
nexthop = '192.0.2.1'
|
||||||
|
external_ids = {ovn_const.OVN_ROUTER_IS_EXT_GW: 'True'}
|
||||||
|
with self.nbapi.transaction(check_error=True) as txn:
|
||||||
|
r = self._add_static_route(txn, lr_name, '',
|
||||||
|
ip_prefix=ip_prefix,
|
||||||
|
nexthop=nexthop,
|
||||||
|
external_ids=external_ids)
|
||||||
|
|
||||||
|
uuids = []
|
||||||
|
for route in r.result.static_routes:
|
||||||
|
lkp = self.nbapi.lookup("Logical_Router_Static_Route", route.uuid)
|
||||||
|
uuids.append(lkp.uuid)
|
||||||
|
self.assertTrue(len(uuids))
|
||||||
|
|
||||||
|
with self.nbapi.transaction(check_error=True) as txn:
|
||||||
|
txn.add(self.nbapi.delete_lrouter_ext_gw(lr_name))
|
||||||
|
|
||||||
|
for route_uuid in uuids:
|
||||||
|
self.assertRaises(
|
||||||
|
idlutils.RowNotFound,
|
||||||
|
self.nbapi.lookup,
|
||||||
|
"Logical_Router_Static_Route",
|
||||||
|
route_uuid)
|
||||||
|
|
||||||
|
def test_delete_lrouter_ext_gw_bfd(self):
|
||||||
|
lr_name = 'router_with_ext_gw_bfd'
|
||||||
|
lrp_name = 'lrp-' + lr_name
|
||||||
|
ip_prefix = '0.0.0.0/0'
|
||||||
|
nexthop = '192.0.2.1'
|
||||||
|
external_ids = {ovn_const.OVN_ROUTER_IS_EXT_GW: 'True'}
|
||||||
|
with self.nbapi.transaction(check_error=True) as txn:
|
||||||
|
r = self._add_static_route(txn, lr_name, lrp_name,
|
||||||
|
ip_prefix=ip_prefix,
|
||||||
|
nexthop=nexthop,
|
||||||
|
external_ids=external_ids,
|
||||||
|
maintain_bfd=True)
|
||||||
|
|
||||||
|
uuids = []
|
||||||
|
bfd_uuids = []
|
||||||
|
for route in r.result.static_routes:
|
||||||
|
lkp = self.nbapi.lookup("Logical_Router_Static_Route", route.uuid)
|
||||||
|
uuids.append(lkp.uuid)
|
||||||
|
self.assertTrue(len(lkp.bfd))
|
||||||
|
for bfd_rec in lkp.bfd:
|
||||||
|
bfd_uuids.append(bfd_rec.uuid)
|
||||||
|
self.assertTrue(len(uuids))
|
||||||
|
self.assertTrue(len(bfd_uuids))
|
||||||
|
|
||||||
|
with self.nbapi.transaction(check_error=True) as txn:
|
||||||
|
txn.add(self.nbapi.delete_lrouter_ext_gw(lr_name))
|
||||||
|
|
||||||
|
for route_uuid in uuids:
|
||||||
|
self.assertRaises(
|
||||||
|
idlutils.RowNotFound,
|
||||||
|
self.nbapi.lookup,
|
||||||
|
"Logical_Router_Static_Route",
|
||||||
|
route_uuid)
|
||||||
|
|
||||||
|
for bfd_uuid in bfd_uuids:
|
||||||
|
self.assertRaises(
|
||||||
|
idlutils.RowNotFound,
|
||||||
|
self.nbapi.lookup,
|
||||||
|
"BFD",
|
||||||
|
bfd_uuid)
|
||||||
|
|
||||||
|
|
||||||
class TestIgnoreConnectionTimeout(BaseOvnIdlTest):
|
class TestIgnoreConnectionTimeout(BaseOvnIdlTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -45,7 +45,7 @@ class TestRouter(base.TestOVNFunctionalBase):
|
|||||||
self.sb_api.idl.notify_handler.watch_event(self.cr_lrp_pb_event)
|
self.sb_api.idl.notify_handler.watch_event(self.cr_lrp_pb_event)
|
||||||
|
|
||||||
def _create_router(self, name, gw_info=None, az_hints=None,
|
def _create_router(self, name, gw_info=None, az_hints=None,
|
||||||
enable_ecmp=None):
|
enable_ecmp=None, enable_bfd=None):
|
||||||
router = {'router':
|
router = {'router':
|
||||||
{'name': name,
|
{'name': name,
|
||||||
'admin_state_up': True,
|
'admin_state_up': True,
|
||||||
@ -54,6 +54,8 @@ class TestRouter(base.TestOVNFunctionalBase):
|
|||||||
router['router']['availability_zone_hints'] = az_hints
|
router['router']['availability_zone_hints'] = az_hints
|
||||||
if gw_info:
|
if gw_info:
|
||||||
router['router']['external_gateway_info'] = gw_info
|
router['router']['external_gateway_info'] = gw_info
|
||||||
|
if enable_bfd:
|
||||||
|
router['router']['enable_default_route_bfd'] = enable_bfd
|
||||||
if enable_ecmp:
|
if enable_ecmp:
|
||||||
router['router']['enable_default_route_ecmp'] = enable_ecmp
|
router['router']['enable_default_route_ecmp'] = enable_ecmp
|
||||||
return self.l3_plugin.create_router(self.context, router)
|
return self.l3_plugin.create_router(self.context, router)
|
||||||
@ -658,6 +660,101 @@ class TestRouter(base.TestOVNFunctionalBase):
|
|||||||
len(lr.static_routes),
|
len(lr.static_routes),
|
||||||
len(gws['router']['external_gateways']))
|
len(gws['router']['external_gateways']))
|
||||||
|
|
||||||
|
def test_create_delete_router_multiple_gw_ports_ecmp_and_bfd(self):
|
||||||
|
default_gw = "10.0.60.1"
|
||||||
|
ext6 = self._create_ext_network(
|
||||||
|
'ext6', 'flat', 'physnet6', None, default_gw, "10.0.60.0/24")
|
||||||
|
router = self._create_router('router6', gw_info=None,
|
||||||
|
enable_bfd=True, enable_ecmp=True)
|
||||||
|
gws = self._add_external_gateways(
|
||||||
|
router['id'],
|
||||||
|
[
|
||||||
|
{'network_id': ext6['network']['id']},
|
||||||
|
{'network_id': ext6['network']['id']},
|
||||||
|
])
|
||||||
|
lr = self.nb_api.lookup('Logical_Router',
|
||||||
|
ovn_utils.ovn_name(router['id']))
|
||||||
|
|
||||||
|
# Check that the expected number of ports are created
|
||||||
|
self.assertEqual(
|
||||||
|
len(lr.ports),
|
||||||
|
len(gws['router']['external_gateways']))
|
||||||
|
# Check that the expected number of static routes are created
|
||||||
|
self.assertEqual(
|
||||||
|
len(lr.static_routes),
|
||||||
|
len(gws['router']['external_gateways']))
|
||||||
|
# Check that static_route bfd and output_port attributes is set to the
|
||||||
|
# expected values
|
||||||
|
for static_route in lr.static_routes:
|
||||||
|
self.assertNotEqual(
|
||||||
|
[],
|
||||||
|
static_route.bfd)
|
||||||
|
self.assertNotEqual(
|
||||||
|
[],
|
||||||
|
static_route.output_port)
|
||||||
|
self.assertIn(static_route.output_port[0],
|
||||||
|
[lrp.name
|
||||||
|
for lrp in lr.ports])
|
||||||
|
self.assertIn(static_route.bfd[0].logical_port,
|
||||||
|
[lrp.name
|
||||||
|
for lrp in lr.ports])
|
||||||
|
self.assertEqual(static_route.bfd[0].logical_port,
|
||||||
|
static_route.output_port[0])
|
||||||
|
|
||||||
|
router_ips = set()
|
||||||
|
for ext_gws in gws['router']['external_gateways']:
|
||||||
|
for ext_fip in ext_gws['external_fixed_ips']:
|
||||||
|
router_ips.add(ext_fip['ip_address'])
|
||||||
|
|
||||||
|
lrps = set()
|
||||||
|
for lrp in lr.ports:
|
||||||
|
for network in lrp.networks:
|
||||||
|
self.assertIn(
|
||||||
|
network.split('/')[0],
|
||||||
|
router_ips)
|
||||||
|
lrps.add(lrp.name)
|
||||||
|
bfd_rows = self.nb_api.bfd_find(
|
||||||
|
lrp.name, default_gw).execute(check_error=True)
|
||||||
|
if not bfd_rows:
|
||||||
|
raise AssertionError('None of the expected BFD rows found.')
|
||||||
|
for bfd_row in bfd_rows:
|
||||||
|
self.assertEqual(
|
||||||
|
bfd_row.logical_port,
|
||||||
|
lrp.name)
|
||||||
|
self.assertEqual(
|
||||||
|
bfd_row.dst_ip,
|
||||||
|
default_gw)
|
||||||
|
|
||||||
|
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']))
|
||||||
|
for lrp_name in lrps:
|
||||||
|
if self.nb_api.bfd_find(
|
||||||
|
lrp.name, default_gw).execute(check_error=True):
|
||||||
|
raise AssertionError('Unexpectedly found BFD rows.')
|
||||||
|
|
||||||
|
def test_update_router_single_gw_bfd(self):
|
||||||
|
ext1 = self._create_ext_network(
|
||||||
|
'ext7', 'flat', 'physnet1', None, "10.0.70.1", "10.0.70.0/24")
|
||||||
|
gw_info = {'network_id': ext1['network']['id']}
|
||||||
|
router = self._create_router('router7', gw_info=gw_info)
|
||||||
|
self.assertFalse(router['enable_default_route_bfd'])
|
||||||
|
lr = self.nb_api.lr_get(ovn_utils.ovn_name(router['id'])).execute()
|
||||||
|
for route in ovn_utils.get_lrouter_ext_gw_static_route(lr):
|
||||||
|
self.assertEqual(
|
||||||
|
[],
|
||||||
|
route.bfd)
|
||||||
|
|
||||||
|
router = self.l3_plugin.update_router(
|
||||||
|
self.context, router['id'],
|
||||||
|
{'router': {'enable_default_route_bfd': True}})
|
||||||
|
self.assertTrue(router['enable_default_route_bfd'])
|
||||||
|
lr = self.nb_api.lr_get(ovn_utils.ovn_name(router['id'])).execute()
|
||||||
|
for route in ovn_utils.get_lrouter_ext_gw_static_route(lr):
|
||||||
|
self.assertNotEqual(
|
||||||
|
[],
|
||||||
|
route.bfd)
|
||||||
|
|
||||||
def test_gateway_chassis_rebalance(self):
|
def test_gateway_chassis_rebalance(self):
|
||||||
ovn_client = self.l3_plugin._ovn_client
|
ovn_client = self.l3_plugin._ovn_client
|
||||||
chassis4 = self.add_fake_chassis(
|
chassis4 = self.add_fake_chassis(
|
||||||
|
@ -797,7 +797,8 @@ class FakeOVNPort(object):
|
|||||||
|
|
||||||
|
|
||||||
FakeStaticRoute = collections.namedtuple(
|
FakeStaticRoute = collections.namedtuple(
|
||||||
'Static_Routes', ['ip_prefix', 'nexthop', 'external_ids'])
|
'Static_Routes', ['ip_prefix', 'nexthop', 'external_ids', 'bfd'],
|
||||||
|
defaults=([],))
|
||||||
|
|
||||||
|
|
||||||
class FakeOVNRouterPort(object):
|
class FakeOVNRouterPort(object):
|
||||||
|
@ -85,6 +85,7 @@ class TestOVNClient(TestOVNClientBase):
|
|||||||
'neutron-' + router['id'],
|
'neutron-' + router['id'],
|
||||||
ip_prefix='0.0.0.0/0',
|
ip_prefix='0.0.0.0/0',
|
||||||
nexthop='10.42.0.1',
|
nexthop='10.42.0.1',
|
||||||
|
maintain_bfd=False,
|
||||||
external_ids={
|
external_ids={
|
||||||
'neutron:is_ext_gw': 'true',
|
'neutron:is_ext_gw': 'true',
|
||||||
'neutron:subnet_id': subnet['id']})
|
'neutron:subnet_id': subnet['id']})
|
||||||
@ -136,6 +137,7 @@ class TestOVNClient(TestOVNClientBase):
|
|||||||
mock.call('neutron-' + router['id'],
|
mock.call('neutron-' + router['id'],
|
||||||
ip_prefix='0.0.0.0/0',
|
ip_prefix='0.0.0.0/0',
|
||||||
nexthop=subnet1['gateway_ip'],
|
nexthop=subnet1['gateway_ip'],
|
||||||
|
maintain_bfd=False,
|
||||||
external_ids={
|
external_ids={
|
||||||
'neutron:is_ext_gw': 'true',
|
'neutron:is_ext_gw': 'true',
|
||||||
'neutron:subnet_id': subnet1['id']},
|
'neutron:subnet_id': subnet1['id']},
|
||||||
@ -143,6 +145,7 @@ class TestOVNClient(TestOVNClientBase):
|
|||||||
mock.call('neutron-' + router['id'],
|
mock.call('neutron-' + router['id'],
|
||||||
ip_prefix='0.0.0.0/0',
|
ip_prefix='0.0.0.0/0',
|
||||||
nexthop=subnet2['gateway_ip'],
|
nexthop=subnet2['gateway_ip'],
|
||||||
|
maintain_bfd=False,
|
||||||
external_ids={
|
external_ids={
|
||||||
'neutron:is_ext_gw': 'true',
|
'neutron:is_ext_gw': 'true',
|
||||||
'neutron:subnet_id': subnet2['id']},
|
'neutron:subnet_id': subnet2['id']},
|
||||||
|
@ -627,6 +627,7 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
|||||||
expected_calls = [
|
expected_calls = [
|
||||||
mock.call('neutron-router-id', ip_prefix='0.0.0.0/0',
|
mock.call('neutron-router-id', ip_prefix='0.0.0.0/0',
|
||||||
nexthop='192.168.1.254',
|
nexthop='192.168.1.254',
|
||||||
|
maintain_bfd=False,
|
||||||
external_ids={
|
external_ids={
|
||||||
ovn_const.OVN_ROUTER_IS_EXT_GW: 'true',
|
ovn_const.OVN_ROUTER_IS_EXT_GW: 'true',
|
||||||
ovn_const.OVN_SUBNET_EXT_ID_KEY: 'ext-subnet-id'})]
|
ovn_const.OVN_SUBNET_EXT_ID_KEY: 'ext-subnet-id'})]
|
||||||
@ -798,6 +799,7 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
|||||||
lsp_address=ovn_const.DEFAULT_ADDR_FOR_LSP_WITH_PEER)
|
lsp_address=ovn_const.DEFAULT_ADDR_FOR_LSP_WITH_PEER)
|
||||||
self.l3_inst._nb_ovn.add_static_route.assert_called_once_with(
|
self.l3_inst._nb_ovn.add_static_route.assert_called_once_with(
|
||||||
'neutron-router-id', ip_prefix='0.0.0.0/0',
|
'neutron-router-id', ip_prefix='0.0.0.0/0',
|
||||||
|
maintain_bfd=False,
|
||||||
external_ids={ovn_const.OVN_ROUTER_IS_EXT_GW: 'true',
|
external_ids={ovn_const.OVN_ROUTER_IS_EXT_GW: 'true',
|
||||||
ovn_const.OVN_SUBNET_EXT_ID_KEY: 'ext-subnet-id'},
|
ovn_const.OVN_SUBNET_EXT_ID_KEY: 'ext-subnet-id'},
|
||||||
nexthop='192.168.1.254')
|
nexthop='192.168.1.254')
|
||||||
@ -868,6 +870,7 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
|||||||
self.l3_inst._nb_ovn.add_static_route.assert_called_once_with(
|
self.l3_inst._nb_ovn.add_static_route.assert_called_once_with(
|
||||||
'neutron-router-id', ip_prefix='0.0.0.0/0',
|
'neutron-router-id', ip_prefix='0.0.0.0/0',
|
||||||
nexthop='192.168.1.254',
|
nexthop='192.168.1.254',
|
||||||
|
maintain_bfd=False,
|
||||||
external_ids={ovn_const.OVN_ROUTER_IS_EXT_GW: 'true',
|
external_ids={ovn_const.OVN_ROUTER_IS_EXT_GW: 'true',
|
||||||
ovn_const.OVN_SUBNET_EXT_ID_KEY: 'ext-subnet-id'})
|
ovn_const.OVN_SUBNET_EXT_ID_KEY: 'ext-subnet-id'})
|
||||||
self.l3_inst._nb_ovn.add_nat_rule_in_lrouter.assert_called_once_with(
|
self.l3_inst._nb_ovn.add_nat_rule_in_lrouter.assert_called_once_with(
|
||||||
@ -920,6 +923,7 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
|||||||
self.l3_inst._nb_ovn.add_static_route.assert_called_once_with(
|
self.l3_inst._nb_ovn.add_static_route.assert_called_once_with(
|
||||||
'neutron-router-id', ip_prefix='0.0.0.0/0',
|
'neutron-router-id', ip_prefix='0.0.0.0/0',
|
||||||
nexthop='192.168.1.254',
|
nexthop='192.168.1.254',
|
||||||
|
maintain_bfd=False,
|
||||||
external_ids={ovn_const.OVN_ROUTER_IS_EXT_GW: 'true',
|
external_ids={ovn_const.OVN_ROUTER_IS_EXT_GW: 'true',
|
||||||
ovn_const.OVN_SUBNET_EXT_ID_KEY: 'ext-subnet-id'})
|
ovn_const.OVN_SUBNET_EXT_ID_KEY: 'ext-subnet-id'})
|
||||||
self.l3_inst._nb_ovn.add_nat_rule_in_lrouter.assert_called_once_with(
|
self.l3_inst._nb_ovn.add_nat_rule_in_lrouter.assert_called_once_with(
|
||||||
@ -971,6 +975,7 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
|||||||
# Need not check lsp and lrp here, it has been tested in other cases
|
# Need not check lsp and lrp here, it has been tested in other cases
|
||||||
self.l3_inst._nb_ovn.add_static_route.assert_called_once_with(
|
self.l3_inst._nb_ovn.add_static_route.assert_called_once_with(
|
||||||
'neutron-router-id', ip_prefix='0.0.0.0/0',
|
'neutron-router-id', ip_prefix='0.0.0.0/0',
|
||||||
|
maintain_bfd=False,
|
||||||
external_ids={ovn_const.OVN_ROUTER_IS_EXT_GW: 'true',
|
external_ids={ovn_const.OVN_ROUTER_IS_EXT_GW: 'true',
|
||||||
ovn_const.OVN_SUBNET_EXT_ID_KEY: 'ext-subnet-id'},
|
ovn_const.OVN_SUBNET_EXT_ID_KEY: 'ext-subnet-id'},
|
||||||
nexthop='192.168.1.254')
|
nexthop='192.168.1.254')
|
||||||
@ -2131,8 +2136,8 @@ class OVNL3ExtrarouteTests(test_l3_gw.ExtGwModeIntTestCase,
|
|||||||
ovn_const.OVN_SUBNET_EXT_ID_KEY: mock.ANY}
|
ovn_const.OVN_SUBNET_EXT_ID_KEY: mock.ANY}
|
||||||
add_static_route_calls = [
|
add_static_route_calls = [
|
||||||
mock.call(mock.ANY, ip_prefix='0.0.0.0/0', nexthop='10.0.0.1',
|
mock.call(mock.ANY, ip_prefix='0.0.0.0/0', nexthop='10.0.0.1',
|
||||||
external_ids=expected_ext_ids),
|
maintain_bfd=False, external_ids=expected_ext_ids),
|
||||||
mock.call(mock.ANY, ip_prefix='::/0', nexthop='2001:db8::',
|
mock.call(mock.ANY, ip_prefix='::/0', nexthop='2001:db8::',
|
||||||
external_ids=expected_ext_ids)]
|
maintain_bfd=False, external_ids=expected_ext_ids)]
|
||||||
self.l3_inst._nb_ovn.add_static_route.assert_has_calls(
|
self.l3_inst._nb_ovn.add_static_route.assert_has_calls(
|
||||||
add_static_route_calls, any_order=True)
|
add_static_route_calls, any_order=True)
|
||||||
|
@ -46,7 +46,7 @@ osprofiler>=2.3.0 # Apache-2.0
|
|||||||
os-ken>=2.2.0 # Apache-2.0
|
os-ken>=2.2.0 # Apache-2.0
|
||||||
os-resource-classes>=1.1.0 # Apache-2.0
|
os-resource-classes>=1.1.0 # Apache-2.0
|
||||||
ovs>=2.10.0 # Apache-2.0
|
ovs>=2.10.0 # Apache-2.0
|
||||||
ovsdbapp>=2.2.1 # Apache-2.0
|
ovsdbapp>=2.3.0 # Apache-2.0
|
||||||
psutil>=5.3.0 # BSD
|
psutil>=5.3.0 # BSD
|
||||||
pyroute2>=0.7.3;sys_platform!='win32' # Apache-2.0 (+ dual licensed GPL2)
|
pyroute2>=0.7.3;sys_platform!='win32' # Apache-2.0 (+ dual licensed GPL2)
|
||||||
pyOpenSSL>=17.1.0 # Apache-2.0
|
pyOpenSSL>=17.1.0 # Apache-2.0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user