Merge "Add support for manipulating BFD entries"

This commit is contained in:
Zuul 2023-04-20 16:14:54 +00:00 committed by Gerrit Code Review
commit e77599280b
4 changed files with 280 additions and 0 deletions

View File

@ -1397,3 +1397,72 @@ class API(api.API, metaclass=abc.ABCMeta):
:type meter: string or uuid.UUID
:returns: :class:`Command` with RowView result
"""
@abc.abstractmethod
def bfd_add(self, logical_port, dst_ip, min_tx=None, min_rx=None,
detect_mult=None, external_ids=None, options=None,
may_exist=False):
"""Create a BFD entry
:param logical_port: Name of logical port where BFD engine should run.
:type logical_port: str
:param dst_ip: BFD peer IP address.
:type dst_ip: str
:param min_tx: This is the minimum interval, in milliseconds,
that the local system would like to use when
transmitting BFD Control packets, less any jitter
applied. The value zero is reserved. Default value
is 1000 ms.
:type min_tx: Optional[int]
:param min_rx: This is the minimum interval, in milliseconds,
between received BFD Control packets that this
system is capable of supporting, less any jitter
applied by the sender. If this value is zero, the
transmitting system does not want the remote
system to send any periodic BFD Control packets.
:type min_rx: Optional[int]
:param detect_mult: Detection time multiplier. The negotiated
transmit interval, multiplied by this value,
provides the Detection Time for the receiving
system in Asynchronous mode. Default value is 5.
:type detect_mult: Optional[int]
:param external_ids: Values to be added as external_id pairs.
:type external_ids: Optional[Dict[str,str]]
:param options: Reserved for future use.
:type options: Optional[Dict[str,str]]
:param may_exist: If True, update any existing BFD entry if it
already exists. Default is False which will raise
an error if a BFD entry with same logical_port,
dst_ip pair already exists.
:type may_exist: Optional[bool]
:returns: :class:`Command` with RowView result
"""
@abc.abstractmethod
def bfd_del(self, uuid):
"""Delete a BFD entry
:param uuid: The uuid of the BFD entry
:type uuid: uuid.UUID
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def bfd_find(self, logical_port, dst_ip):
"""Find BFD entry.
:param logical_port: Name of logical port where BFD engine runs.
:type logical_port: str
:param dst_ip: BFD peer IP address.
:type dst_ip: str
:returns: :class:`Command` with List[Dict[str,any]] result
"""
@abc.abstractmethod
def bfd_get(self, uuid):
"""Get the BFD entry
:param uuid: The uuid of the BFD entry
:type uuid: uuid.UUID
:returns: :class:`Command` with RowView result
"""

View File

@ -1045,6 +1045,98 @@ class LrpDelNetworksCommand(_LrpNetworksCommand):
lrp.delvalue('networks', network)
class BFDFindCommand(cmd.DbFindCommand):
table = 'BFD'
def __init__(self, api, port, dst_ip):
super().__init__(
api,
self.table,
('logical_port', '=', port),
('dst_ip', '=', dst_ip),
row=True,
)
class BFDAddCommand(cmd.AddCommand):
# cmd.AddCommand uses self.table_name, other base commands use self.table
table_name = 'BFD'
def __init__(self, api, logical_port, dst_ip, min_tx=None, min_rx=None,
detect_mult=None, external_ids=None, options=None,
may_exist=False):
for attr in ('logical_port', 'dst_ip'):
if not isinstance(locals().get(attr), str):
raise ValueError("%s must be of type str" % attr)
for attr in ('min_tx', 'min_rx', 'detect_mult'):
value = locals().get(attr)
if value and (not isinstance(value, int) or value < 1):
raise ValueError("%s must be of type int and > 0" % attr)
for attr in ('external_ids', 'options'):
value = locals().get(attr)
if value and not isinstance(value, dict):
raise ValueError("%s must be of type dict" % attr)
super().__init__(api)
self.logical_port = logical_port
self.dst_ip = dst_ip
self.columns = {
'logical_port': logical_port,
'dst_ip': dst_ip,
'min_tx': [min_tx] if min_tx else [],
'min_rx': [min_rx] if min_rx else [],
'detect_mult': [detect_mult] if detect_mult else [],
'external_ids': external_ids or {},
'options': options or {},
}
self.may_exist = may_exist
def run_idl(self, txn):
cmd = BFDFindCommand(self.api, self.logical_port, self.dst_ip)
cmd.run_idl(txn)
bfd_result = cmd.result
if bfd_result:
if len(bfd_result) > 1:
# With the current database schema, this cannot happen, but
# better safe than sorry.
raise RuntimeError(
"Unexpected duplicates in database for port %s "
"and dst_ip %s" % (self.logical_port, self.dst_ip))
bfd = bfd_result[0]
if self.may_exist:
self.set_columns(bfd, **self.columns)
# When no changes are made to a record, the parent
# `post_commit` method will not be called.
#
# Ensure consistent return to caller of `Command.execute()`
# even when no changes have been applied.
self.result = rowview.RowView(bfd)
return
else:
raise RuntimeError(
"BFD entry for port %s and dst_ip %s exists" % (
self.logical_port, self.dst_ip))
bfd = txn.insert(self.api.tables[self.table_name])
bfd.logical_port = self.logical_port
bfd.dst_ip = self.dst_ip
self.set_columns(bfd, **self.columns)
# Setting the result to something other than a :class:`rowview.RowView`
# or :class:`ovs.db.idl.Row` typed value will make the parent
# `post_commit` method retrieve the newly insterted row from IDL and
# return that to the caller.
self.result = bfd.uuid
class BFDDelCommand(cmd.DbDestroyCommand):
table = 'BFD'
def __init__(self, api, uuid):
super().__init__(api, self.table, uuid)
class BFDGetCommand(cmd.BaseGetRowCommand):
table = 'BFD'
class LrRouteAddCommand(cmd.BaseCommand):
def __init__(self, api, router, prefix, nexthop, port=None,
policy='dst-ip', may_exist=False):

View File

@ -415,3 +415,20 @@ class OvnNbApiIdlImpl(ovs_idl.Backend, api.API):
def meter_get(self, meter):
return cmd.MeterGetCommand(self, meter)
def bfd_add(self, logical_port, dst_ip, min_tx=None, min_rx=None,
detect_mult=None, external_ids=None, options=None,
may_exist=False):
return cmd.BFDAddCommand(self, logical_port, dst_ip, min_tx=min_tx,
min_rx=min_rx, detect_mult=detect_mult,
external_ids=external_ids, options=options,
may_exist=may_exist)
def bfd_del(self, uuid):
return cmd.BFDDelCommand(self, uuid)
def bfd_find(self, logical_port, dst_ip):
return cmd.BFDFindCommand(self, logical_port, dst_ip)
def bfd_get(self, uuid):
return cmd.BFDGetCommand(self, uuid)

View File

@ -2424,3 +2424,105 @@ class TestMeterOps(OvnNorthboundTest):
def test_meter_get_name(self):
self._meter_get('name')
class TestBFDOps(OvnNorthboundTest):
def setUp(self):
super(TestBFDOps, self).setUp()
self.table = self.api.tables['BFD']
def _bfd_add(self, *args, **kwargs):
cmd = self.api.bfd_add(*args, **kwargs)
row = cmd.execute(check_error=True)
self.assertEqual(cmd.logical_port, row.logical_port)
self.assertEqual(cmd.dst_ip, row.dst_ip)
self.assertEqual(cmd.columns['min_tx'] if cmd.columns[
'min_tx'] else [], row.min_tx)
self.assertEqual(cmd.columns['min_rx'] if cmd.columns[
'min_rx'] else [], row.min_rx)
self.assertEqual(cmd.columns['detect_mult'] if cmd.columns[
'detect_mult'] else [], row.detect_mult)
self.assertEqual(cmd.columns['external_ids'] or {}, row.external_ids)
self.assertEqual(cmd.columns['options'] or {}, row.options)
return idlutils.frozen_row(row)
def test_bfd_add(self):
name = utils.get_rand_name()
self._bfd_add(name, name)
def test_bfd_add_non_defaults(self):
name = utils.get_rand_name()
self._bfd_add(
name,
name,
min_rx=1,
min_tx=2,
detect_mult=3,
external_ids={'a': 'A'},
options={'b': 'B'},
may_exist=True,
)
def test_bfd_add_duplicate(self):
name = utils.get_rand_name()
cmd = self.api.bfd_add(name, name)
cmd.execute(check_error=True)
self.assertRaises(RuntimeError, cmd.execute, check_error=True)
def test_bfd_add_may_exist_no_change(self):
name = utils.get_rand_name()
b1 = self._bfd_add(name, name)
b2 = self._bfd_add(name, name, may_exist=True)
self.assertEqual(b1, b2)
def test_bfd_add_may_exist_change(self):
name = utils.get_rand_name()
b1 = self._bfd_add(name, name)
b2 = self._bfd_add(
name,
name,
min_rx=11,
min_tx=22,
detect_mult=33,
external_ids={'aa': 'AA'},
options={'bb': 'BB'},
may_exist=True,
)
self.assertNotEqual(b1, b2)
self.assertEqual(b1.uuid, b2.uuid)
def test_bfd_del(self):
name = utils.get_rand_name()
b1 = self._bfd_add(name, name)
self.assertIn(b1.uuid, self.table.rows)
self.api.bfd_del(b1.uuid).execute(check_error=True)
self.assertNotIn(b1.uuid, self.table.rows)
def test_bfd_find(self):
name1 = utils.get_rand_name()
name2 = utils.get_rand_name()
b1 = self._bfd_add(name1, name1)
b2 = self._bfd_add(name1, name2)
b3 = self._bfd_add(name2, name1)
b4 = self._bfd_add(name2, name2)
self.assertIn(b1.uuid, self.table.rows)
self.assertIn(b2.uuid, self.table.rows)
self.assertIn(b3.uuid, self.table.rows)
self.assertIn(b4.uuid, self.table.rows)
found = self.api.bfd_find(
b1.logical_port,
b1.dst_ip).execute(check_error=True)
self.assertEqual(1, len(found))
self.assertEqual(b1.uuid, found[0].uuid)
for col in set(self.api.tables['BFD'].columns.keys()) - set(
('_uuid', 'status')):
self.assertEqual(
getattr(b1, col),
getattr(found[0], col))
def test_bfd_get(self):
name = utils.get_rand_name()
b1 = self.api.bfd_add(name, name).execute(check_error=True)
b2 = self.api.bfd_get(b1.uuid).execute(check_error=True)
self.assertEqual(b1, b2)