From c1d9f44cac8cbe3ed15395c138bca250aa4017e6 Mon Sep 17 00:00:00 2001 From: snuffy22 <22233310+snuffy22@users.noreply.github.com> Date: Tue, 23 Jun 2026 18:44:27 -0400 Subject: [PATCH 1/4] Add validation for EOS/FRR BFD check for OSPFv2/v3 --- netsim/validate/ospf/eos.py | 10 ++++++++-- netsim/validate/ospf/frr.py | 22 ++++++++++++++++++---- tests/integration/ospf/ospfv2/bfd.yml | 15 +++++++++++++++ tests/integration/ospf/ospfv3/bfd.yml | 15 +++++++++++++++ 4 files changed, 56 insertions(+), 6 deletions(-) diff --git a/netsim/validate/ospf/eos.py b/netsim/validate/ospf/eos.py index 3b54baacdd..51cc499e89 100644 --- a/netsim/validate/ospf/eos.py +++ b/netsim/validate/ospf/eos.py @@ -29,7 +29,7 @@ def get_ospf_neighbor_data(data: Box, *, id: str, proto: str, vrf: str = 'defaul ngb_list = [ ngb for ngb in ngb_list if ngb.routerId == id ] return ngb_list -def show_ospf_neighbor(id: str, present: bool = True, vrf: str = 'default') -> str: +def show_ospf_neighbor(id: str, present: bool = True, vrf: str = 'default', bfd: bool = False) -> str: try: ipaddress.IPv4Address(id) except: @@ -41,7 +41,8 @@ def valid_ospf_neighbor( present: bool = True, vrf: str = 'default',*, proto: str='ospf', - proto_name: str = 'OSPFv2') -> bool: + proto_name: str = 'OSPFv2', + bfd: bool = False) -> bool: _result = global_vars.get_result_dict('_result') ngb_list = get_ospf_neighbor_data(_result,id=id,proto=proto,vrf=vrf) @@ -54,6 +55,11 @@ def valid_ospf_neighbor( if not present: raise Exception(f'Unexpected {proto_name} neighbor {id} in state {n_state.adjacencyState}') + if bfd: + _common.report_state( + exit_msg=f'{proto_name} neighbor {id} is in BFD state {n_state.details.bfdState} ', + OK=n_state.details.bfdState == "up") + _common.report_state( exit_msg=f'{proto_name} neighbor {id} is in state {n_state.adjacencyState}', OK=n_state.adjacencyState.startswith('full')) diff --git a/netsim/validate/ospf/frr.py b/netsim/validate/ospf/frr.py index eb3e8f0fb0..01c36dda4a 100644 --- a/netsim/validate/ospf/frr.py +++ b/netsim/validate/ospf/frr.py @@ -14,14 +14,14 @@ from . import OSPF_PREFIX_NAMES -def show_ospf_neighbor(id: str, present: bool = True, vrf: str = 'default') -> str: +def show_ospf_neighbor(id: str, present: bool = True, vrf: str = 'default', bfd: bool = False) -> str: try: ipaddress.IPv4Address(id) except Exception as exc: raise Exception(f'OSPF router ID {id} is not a valid IPv4 address') from exc - return f'ip ospf vrf {vrf} neighbor {id} json' + return f'ip ospf vrf {vrf} neighbor {id} detail json' -def valid_ospf_neighbor(id: str, present: bool = True, vrf: str = 'default') -> bool: +def valid_ospf_neighbor(id: str, present: bool = True, vrf: str = 'default', bfd: bool = False) -> bool: _result = global_vars.get_result_dict('_result') if vrf in _result: @@ -36,6 +36,13 @@ def valid_ospf_neighbor(id: str, present: bool = True, vrf: str = 'default') -> if not present: raise Exception(f'Unexpected OSPFv2 neighbor {id} in state {n_state.nbrState}') + if bfd: + exit_msg = f'OSPFv2 neighbor {id} is in BFD state {n_state.peerBfdInfo.status}' + if n_state.peerBfdInfo and n_state.peerBfdInfo.status == "Up": + raise log.Result(exit_msg) + else: + raise Exception(exit_msg) + exit_msg = f'OSPFv2 neighbor {id} is in state {n_state.nbrState}' if not n_state.nbrState.startswith('Full'): raise Exception(exit_msg) @@ -49,7 +56,7 @@ def show_ospf6_neighbor(id: str, present: bool = True, vrf: str = 'default', **k raise Exception(f'OSPF router ID {id} is not a valid IPv4 address') from exc return f'ipv6 ospf6 vrf {vrf} neighbor {id} json' -def valid_ospf6_neighbor(id: str, present: bool = True, vrf: str = 'default') -> bool: +def valid_ospf6_neighbor(id: str, present: bool = True, vrf: str = 'default', bfd: bool = False) -> bool: _result = global_vars.get_result_dict('_result') vrf_name = '' if vrf == 'default' else f' in VRF {vrf}' @@ -71,6 +78,13 @@ def valid_ospf6_neighbor(id: str, present: bool = True, vrf: str = 'default') -> if n_state.neighborState != 'Full': raise Exception(f'OSPFv3 neighbor {id}{vrf_name} is in state {n_state.neighborState}') + if bfd: + exit_msg = f'OSPFv3 neighbor {id} is in BFD state {n_state.peerBfdInfo.status}' + if n_state.peerBfdInfo and n_state.peerBfdInfo.status == "Up": + raise log.Result(exit_msg) + else: + raise Exception(exit_msg) + return True def show_ospf_prefix(pfx: str, **kwargs: typing.Any) -> str: diff --git a/tests/integration/ospf/ospfv2/bfd.yml b/tests/integration/ospf/ospfv2/bfd.yml index 3d75caf319..9459ee445c 100644 --- a/tests/integration/ospf/ospfv2/bfd.yml +++ b/tests/integration/ospf/ospfv2/bfd.yml @@ -4,6 +4,7 @@ message: | * Multiple OSPF areas on ABR * BFD enabled on all interfaces +defaults.sources.extra: [ ../../wait_times.yml, ../../warnings.yml ] module: [ ospf, bfd ] ospf.bfd.ipv4: True @@ -24,3 +25,17 @@ links: cost: 42 network_type: broadcast - abr: + +validate: + adj: + description: Check OSPF adjacencies + wait: ospfv2_adj_p2p + wait_msg: Waiting for OSPF adjacency process to complete + nodes: [ r1, bb ] + plugin: ospf_neighbor(nodes.abr.ospf.router_id) + bfd: + description: Check OSPF adjacencies + wait: ospfv2_adj_p2p + wait_msg: Waiting for OSPF adjacency process to complete + nodes: [ r1, bb ] + plugin: ospf_neighbor(nodes.abr.ospf.router_id,bfd=True) diff --git a/tests/integration/ospf/ospfv3/bfd.yml b/tests/integration/ospf/ospfv3/bfd.yml index 0bb5bc01bd..ee0b9cc188 100644 --- a/tests/integration/ospf/ospfv3/bfd.yml +++ b/tests/integration/ospf/ospfv3/bfd.yml @@ -15,6 +15,7 @@ addressing: ipv4: False ipv6: 2001:db8:3::/48 +defaults.sources.extra: [ ../../wait_times.yml, ../../warnings.yml ] module: [ ospf, bfd ] ospf.bfd.ipv6: True @@ -35,3 +36,17 @@ links: cost: 42 network_type: broadcast - abr: + +validate: + adj: + description: Check OSPF adjacencies + wait: ospfv3_adj_p2p + wait_msg: Waiting for OSPF adjacency process to complete + nodes: [ r1, bb ] + plugin: ospf6_neighbor(nodes.abr.ospf.router_id) + bfd: + description: Check OSPF adjacencies + wait: ospfv3_adj_p2p + wait_msg: Waiting for OSPF adjacency process to complete + nodes: [ r1, bb ] + plugin: ospf6_neighbor(nodes.abr.ospf.router_id,bfd=True) From ffdaf4df8d88dc9364a541b81699ee4669fee818 Mon Sep 17 00:00:00 2001 From: snuffy22 <22233310+snuffy22@users.noreply.github.com> Date: Fri, 26 Jun 2026 10:16:15 -0400 Subject: [PATCH 2/4] Update BFD tests with received feedback --- netsim/validate/ospf/eos.py | 18 +++++++++++------- netsim/validate/ospf/frr.py | 20 +++++++++++++------- tests/integration/ospf/ospfv2/bfd.yml | 6 +++--- tests/integration/ospf/ospfv3/bfd.yml | 4 ++-- 4 files changed, 29 insertions(+), 19 deletions(-) diff --git a/netsim/validate/ospf/eos.py b/netsim/validate/ospf/eos.py index 51cc499e89..e2cbceb790 100644 --- a/netsim/validate/ospf/eos.py +++ b/netsim/validate/ospf/eos.py @@ -55,14 +55,18 @@ def valid_ospf_neighbor( if not present: raise Exception(f'Unexpected {proto_name} neighbor {id} in state {n_state.adjacencyState}') - if bfd: - _common.report_state( - exit_msg=f'{proto_name} neighbor {id} is in BFD state {n_state.details.bfdState} ', - OK=n_state.details.bfdState == "up") + exit_msg = f'{proto_name} neighbor {id} is in state {n_state.adjacencyState}' + if not n_state.adjacencyState.startswith('full'): + raise Exception(exit_msg) + + if not bfd: + raise log.Result(exit_msg) + + exit_msg = f'{proto_name} neighbor {id} is in BFD state {n_state.details.bfdState}' + if not n_state.details.bfdState == "up": + raise Exception(exit_msg) - _common.report_state( - exit_msg=f'{proto_name} neighbor {id} is in state {n_state.adjacencyState}', - OK=n_state.adjacencyState.startswith('full')) + raise log.Result(exit_msg) def show_ospf6_neighbor(id: str, present: bool = True, vrf: str = 'default', **kwargs: typing.Any) -> str: try: diff --git a/netsim/validate/ospf/frr.py b/netsim/validate/ospf/frr.py index 01c36dda4a..8ece3e0388 100644 --- a/netsim/validate/ospf/frr.py +++ b/netsim/validate/ospf/frr.py @@ -36,20 +36,26 @@ def valid_ospf_neighbor(id: str, present: bool = True, vrf: str = 'default', bfd if not present: raise Exception(f'Unexpected OSPFv2 neighbor {id} in state {n_state.nbrState}') - if bfd: - exit_msg = f'OSPFv2 neighbor {id} is in BFD state {n_state.peerBfdInfo.status}' - if n_state.peerBfdInfo and n_state.peerBfdInfo.status == "Up": - raise log.Result(exit_msg) - else: - raise Exception(exit_msg) exit_msg = f'OSPFv2 neighbor {id} is in state {n_state.nbrState}' if not n_state.nbrState.startswith('Full'): raise Exception(exit_msg) - else: + + if not bfd: raise log.Result(exit_msg) +<<<<<<< HEAD def show_ospf6_neighbor(id: str, present: bool = True, vrf: str = 'default', **kwargs: typing.Any) -> str: +======= + exit_msg = f'OSPFv2 neighbor {id} is in BFD state {n_state.peerBfdInfo.status}' + if not n_state.peerBfdInfo.status == "Up": + raise Exception(exit_msg) + + raise log.Result(exit_msg) + + +def show_ospf6_neighbor(id: str, **kwargs: typing.Any) -> str: +>>>>>>> e01560d71 (Update BFD tests with received feedback) try: ipaddress.IPv4Address(id) except Exception as exc: diff --git a/tests/integration/ospf/ospfv2/bfd.yml b/tests/integration/ospf/ospfv2/bfd.yml index 9459ee445c..329077538c 100644 --- a/tests/integration/ospf/ospfv2/bfd.yml +++ b/tests/integration/ospf/ospfv2/bfd.yml @@ -34,8 +34,8 @@ validate: nodes: [ r1, bb ] plugin: ospf_neighbor(nodes.abr.ospf.router_id) bfd: - description: Check OSPF adjacencies + description: Check OSPF BFD adjacencies wait: ospfv2_adj_p2p - wait_msg: Waiting for OSPF adjacency process to complete - nodes: [ r1, bb ] + wait_msg: Waiting for OSPF BFD adjacency process to complete + nodes: [ r1,bb ] plugin: ospf_neighbor(nodes.abr.ospf.router_id,bfd=True) diff --git a/tests/integration/ospf/ospfv3/bfd.yml b/tests/integration/ospf/ospfv3/bfd.yml index ee0b9cc188..8d4a98de4f 100644 --- a/tests/integration/ospf/ospfv3/bfd.yml +++ b/tests/integration/ospf/ospfv3/bfd.yml @@ -45,8 +45,8 @@ validate: nodes: [ r1, bb ] plugin: ospf6_neighbor(nodes.abr.ospf.router_id) bfd: - description: Check OSPF adjacencies + description: Check OSPF BFD adjacencies wait: ospfv3_adj_p2p - wait_msg: Waiting for OSPF adjacency process to complete + wait_msg: Waiting for OSPF BFD adjacency process to complete nodes: [ r1, bb ] plugin: ospf6_neighbor(nodes.abr.ospf.router_id,bfd=True) From d331f3c346a570adb0f448079c80a113b352fec3 Mon Sep 17 00:00:00 2001 From: snuffy22 <22233310+snuffy22@users.noreply.github.com> Date: Fri, 26 Jun 2026 10:25:34 -0400 Subject: [PATCH 3/4] Fix lint issue --- netsim/validate/ospf/frr.py | 6 +----- tests/integration/ospf/ospfv2/bfd.yml | 4 ++-- tests/integration/ospf/ospfv3/bfd.yml | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/netsim/validate/ospf/frr.py b/netsim/validate/ospf/frr.py index 8ece3e0388..562bec68cf 100644 --- a/netsim/validate/ospf/frr.py +++ b/netsim/validate/ospf/frr.py @@ -44,9 +44,6 @@ def valid_ospf_neighbor(id: str, present: bool = True, vrf: str = 'default', bfd if not bfd: raise log.Result(exit_msg) -<<<<<<< HEAD -def show_ospf6_neighbor(id: str, present: bool = True, vrf: str = 'default', **kwargs: typing.Any) -> str: -======= exit_msg = f'OSPFv2 neighbor {id} is in BFD state {n_state.peerBfdInfo.status}' if not n_state.peerBfdInfo.status == "Up": raise Exception(exit_msg) @@ -54,8 +51,7 @@ def show_ospf6_neighbor(id: str, present: bool = True, vrf: str = 'default', **k raise log.Result(exit_msg) -def show_ospf6_neighbor(id: str, **kwargs: typing.Any) -> str: ->>>>>>> e01560d71 (Update BFD tests with received feedback) +def show_ospf6_neighbor(id: str, present: bool = True, vrf: str = 'default', **kwargs: typing.Any) -> str: try: ipaddress.IPv4Address(id) except Exception as exc: diff --git a/tests/integration/ospf/ospfv2/bfd.yml b/tests/integration/ospf/ospfv2/bfd.yml index 329077538c..4c9e328717 100644 --- a/tests/integration/ospf/ospfv2/bfd.yml +++ b/tests/integration/ospf/ospfv2/bfd.yml @@ -33,9 +33,9 @@ validate: wait_msg: Waiting for OSPF adjacency process to complete nodes: [ r1, bb ] plugin: ospf_neighbor(nodes.abr.ospf.router_id) - bfd: + ospf_bfd: description: Check OSPF BFD adjacencies wait: ospfv2_adj_p2p wait_msg: Waiting for OSPF BFD adjacency process to complete - nodes: [ r1,bb ] + nodes: [ r1, bb ] plugin: ospf_neighbor(nodes.abr.ospf.router_id,bfd=True) diff --git a/tests/integration/ospf/ospfv3/bfd.yml b/tests/integration/ospf/ospfv3/bfd.yml index 8d4a98de4f..0ad0a050aa 100644 --- a/tests/integration/ospf/ospfv3/bfd.yml +++ b/tests/integration/ospf/ospfv3/bfd.yml @@ -44,7 +44,7 @@ validate: wait_msg: Waiting for OSPF adjacency process to complete nodes: [ r1, bb ] plugin: ospf6_neighbor(nodes.abr.ospf.router_id) - bfd: + ospf_bfd: description: Check OSPF BFD adjacencies wait: ospfv3_adj_p2p wait_msg: Waiting for OSPF BFD adjacency process to complete From dc7ba0271f66c60772af6037e96b23585c31cb2e Mon Sep 17 00:00:00 2001 From: Ivan Pepelnjak Date: Sun, 28 Jun 2026 06:24:11 +0200 Subject: [PATCH 4/4] Polished the code, finalized the integration tests --- netsim/validate/ospf/eos.py | 5 +- netsim/validate/ospf/frr.py | 14 +++-- .../ospf/ospfv2/{bfd.yml => 42-bfd.yml} | 22 +++++--- tests/integration/ospf/ospfv3/42-bfd.yml | 47 +++++++++++++++++ tests/integration/ospf/ospfv3/bfd.yml | 52 ------------------- 5 files changed, 68 insertions(+), 72 deletions(-) rename tests/integration/ospf/ospfv2/{bfd.yml => 42-bfd.yml} (71%) create mode 100644 tests/integration/ospf/ospfv3/42-bfd.yml delete mode 100644 tests/integration/ospf/ospfv3/bfd.yml diff --git a/netsim/validate/ospf/eos.py b/netsim/validate/ospf/eos.py index e2cbceb790..df604786d0 100644 --- a/netsim/validate/ospf/eos.py +++ b/netsim/validate/ospf/eos.py @@ -63,10 +63,7 @@ def valid_ospf_neighbor( raise log.Result(exit_msg) exit_msg = f'{proto_name} neighbor {id} is in BFD state {n_state.details.bfdState}' - if not n_state.details.bfdState == "up": - raise Exception(exit_msg) - - raise log.Result(exit_msg) + _common.report_state(exit_msg,n_state.details.bfdState == "up") def show_ospf6_neighbor(id: str, present: bool = True, vrf: str = 'default', **kwargs: typing.Any) -> str: try: diff --git a/netsim/validate/ospf/frr.py b/netsim/validate/ospf/frr.py index 562bec68cf..6b5aa86215 100644 --- a/netsim/validate/ospf/frr.py +++ b/netsim/validate/ospf/frr.py @@ -77,17 +77,15 @@ def valid_ospf6_neighbor(id: str, present: bool = True, vrf: str = 'default', bf if not present: raise Exception(f'Unexpected OSPFv3 neighbor {id}{vrf_name} in state {n_state.neighborState}') + ngb_msg = f'OSPFv3 neighbor {id}{vrf_name} is in state {n_state.neighborState}' if n_state.neighborState != 'Full': - raise Exception(f'OSPFv3 neighbor {id}{vrf_name} is in state {n_state.neighborState}') + raise Exception(ngb_msg) - if bfd: - exit_msg = f'OSPFv3 neighbor {id} is in BFD state {n_state.peerBfdInfo.status}' - if n_state.peerBfdInfo and n_state.peerBfdInfo.status == "Up": - raise log.Result(exit_msg) - else: - raise Exception(exit_msg) + if not bfd: + raise log.Result(ngb_msg) - return True + exit_msg = f'OSPFv3 neighbor {id} is in BFD state {n_state.peerBfdInfo.status}' + _common.report_state(exit_msg,n_state.peerBfdInfo and n_state.peerBfdInfo.status == "Up") def show_ospf_prefix(pfx: str, **kwargs: typing.Any) -> str: return 'ip ospf route json' diff --git a/tests/integration/ospf/ospfv2/bfd.yml b/tests/integration/ospf/ospfv2/42-bfd.yml similarity index 71% rename from tests/integration/ospf/ospfv2/bfd.yml rename to tests/integration/ospf/ospfv2/42-bfd.yml index 4c9e328717..0d65cb090f 100644 --- a/tests/integration/ospf/ospfv2/bfd.yml +++ b/tests/integration/ospf/ospfv2/42-bfd.yml @@ -9,33 +9,39 @@ module: [ ospf, bfd ] ospf.bfd.ipv4: True +groups: + probes: + device: frr + provider: clab + members: [ bb, r1 ] + mtu: 1500 + nodes: + dut: bb: - ospf.router_id: 10.42.42.1 - abr: r1: ospf.area: 1 links: -- bb-abr -- abr: +- dut-bb +- dut: r1: ospf: area: 1 cost: 42 network_type: broadcast -- abr: +- dut: validate: adj: description: Check OSPF adjacencies - wait: ospfv2_adj_p2p + wait: ospfv2_adj_lan wait_msg: Waiting for OSPF adjacency process to complete nodes: [ r1, bb ] - plugin: ospf_neighbor(nodes.abr.ospf.router_id) + plugin: ospf_neighbor(nodes.dut.ospf.router_id) ospf_bfd: description: Check OSPF BFD adjacencies wait: ospfv2_adj_p2p wait_msg: Waiting for OSPF BFD adjacency process to complete nodes: [ r1, bb ] - plugin: ospf_neighbor(nodes.abr.ospf.router_id,bfd=True) + plugin: ospf_neighbor(nodes.dut.ospf.router_id,bfd=True) diff --git a/tests/integration/ospf/ospfv3/42-bfd.yml b/tests/integration/ospf/ospfv3/42-bfd.yml new file mode 100644 index 0000000000..16606d6e0c --- /dev/null +++ b/tests/integration/ospf/ospfv3/42-bfd.yml @@ -0,0 +1,47 @@ +message: | + This topology tests the OSPFv2 with BFD + + * Multiple OSPF areas on ABR + * BFD enabled on all interfaces + +defaults.sources.extra: [ ../../wait_times.yml, ../../warnings.yml ] +module: [ ospf, bfd ] + +ospf.bfd.ipv6: True + +groups: + probes: + device: frr + provider: clab + members: [ bb, r1 ] + mtu: 1500 + +nodes: + dut: + bb: + r1: + ospf.area: 1 + +links: +- dut-bb +- dut: + r1: + ospf: + area: 1 + cost: 42 + network_type: broadcast +- dut: + +validate: + adj: + description: Check OSPFv3 adjacencies + wait: ospfv3_adj_lan + wait_msg: Waiting for OSPFv3 adjacency process to complete + nodes: [ r1, bb ] + plugin: ospf6_neighbor(nodes.dut.ospf.router_id) + ospf_bfd: + description: Check OSPFv3 BFD adjacencies + wait: ospfv2_adj_p2p + wait_msg: Waiting for OSPFv3 BFD adjacency process to complete + nodes: [ r1, bb ] + plugin: ospf6_neighbor(nodes.dut.ospf.router_id,bfd=True) diff --git a/tests/integration/ospf/ospfv3/bfd.yml b/tests/integration/ospf/ospfv3/bfd.yml deleted file mode 100644 index 0ad0a050aa..0000000000 --- a/tests/integration/ospf/ospfv3/bfd.yml +++ /dev/null @@ -1,52 +0,0 @@ -message: | - This topology tests the OSPFv3 with BFD - - * Multiple OSPF areas on ABR - * BFD enabled on all interfaces - -addressing: - loopback: - ipv4: False - ipv6: 2001:db8:1::/48 - lan: - ipv4: False - ipv6: 2001:db8:2::/48 - p2p: - ipv4: False - ipv6: 2001:db8:3::/48 - -defaults.sources.extra: [ ../../wait_times.yml, ../../warnings.yml ] -module: [ ospf, bfd ] - -ospf.bfd.ipv6: True - -nodes: - bb: - ospf.router_id: 10.42.42.1 - abr: - r1: - ospf.area: 1 - -links: -- bb-abr -- abr: - r1: - ospf: - area: 1 - cost: 42 - network_type: broadcast -- abr: - -validate: - adj: - description: Check OSPF adjacencies - wait: ospfv3_adj_p2p - wait_msg: Waiting for OSPF adjacency process to complete - nodes: [ r1, bb ] - plugin: ospf6_neighbor(nodes.abr.ospf.router_id) - ospf_bfd: - description: Check OSPF BFD adjacencies - wait: ospfv3_adj_p2p - wait_msg: Waiting for OSPF BFD adjacency process to complete - nodes: [ r1, bb ] - plugin: ospf6_neighbor(nodes.abr.ospf.router_id,bfd=True)