#!/usr/bin/env python # SPDX-License-Identifier: ISC # # test_bgp_nexthop_ipv6_topo1.py # # Copyright (c) 2024 by # Cumulus Networks, Inc. # 6WIND S.A. # """ Ensure that BGP ipv6 nexthops are correct """ import os import sys import pytest from functools import partial import json # Save the Current Working Directory to find configuration files. CWD = os.path.dirname(os.path.realpath(__file__)) sys.path.append(os.path.join(CWD, "../")) # pylint: disable=C0413 # Import topogen and topotest helpers from lib import topotest from lib.topogen import Topogen, TopoRouter, get_topogen from lib.topolog import logger # Required to instantiate the topology builder class. pytestmark = [pytest.mark.bgpd] def build_topo(tgen): r""" All peers are FRR BGP peers except r3 that is a exabgp peer. rr is a route-reflector for AS 65000 iBGP peers. Exabgp does not send any IPv6 Link-Local nexthop iBGP peers | eBGP peers | AS 65000 | | +---+ | | r6| | +---+ | | | fd00:0:3::0/64 | | | AS 65002 +---+ | +---+ |rr |----fd00:0:4::0/64---| r5| +---+ |_________+---+ | | +---+ fd00:0:2::0/64----------------| r4| / | \ | +---+ +---+ +---+ +---+ | AS 65001 | r1| | r2| |r3 | | +---+ +---+ +---+ """ def connect_routers(tgen, left, right): for rname in [left, right]: if rname not in tgen.routers().keys(): tgen.add_router(rname) switch = tgen.add_switch("s-{}-{}".format(left, right)) switch.add_link(tgen.gears[left], nodeif="eth-{}".format(right)) switch.add_link(tgen.gears[right], nodeif="eth-{}".format(left)) def connect_switchs(tgen, rname, switch): if rname not in tgen.routers().keys(): tgen.add_router(rname) switch.add_link(tgen.gears[rname], nodeif="eth-{}".format(switch.name)) def connect_dummy(tgen, rname, switch): if rname not in tgen.routers().keys(): tgen.add_router(rname) switch.add_link(tgen.gears[rname], nodeif="eth-dummy") # sw_du switch is for a dummy interface (for local network) for i in range(1, 7): if i == 3: # r3 is an exabgp peer continue sw_du = tgen.add_switch("sw%s" % i) connect_dummy(tgen, "r%s" % i, sw_du) # sw switch is for interconnecting peers on the same subnet sw = tgen.add_switch("sw") connect_switchs(tgen, "rr", sw) connect_switchs(tgen, "r1", sw) connect_switchs(tgen, "r2", sw) connect_switchs(tgen, "r4", sw) # directly connected without switch routers connect_routers(tgen, "rr", "r5") connect_routers(tgen, "rr", "r6") ## Add iBGP ExaBGP neighbor peer_ip = "fd00:0:2::3" ## peer peer_route = "via fd00:0:2::9" ## router r3 = tgen.add_exabgp_peer("r3", ip=peer_ip, defaultRoute=peer_route) sw.add_link(r3) ##################################################### ## ## Tests starting ## ##################################################### def setup_module(module): "Setup topology" tgen = Topogen(build_topo, module.__name__) tgen.start_topology() # This is a sample of configuration loading. router_list = tgen.routers() for rname, router in router_list.items(): router.load_config( TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) ) router.load_config( TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) ) tgen.start_router() # Start r3 exabgp peer r3 = tgen.gears["r3"] r3.start(os.path.join(CWD, "r3"), os.path.join(CWD, "exabgp.env")) def get_link_local(rname, ifname, cache): ip = cache.get(rname, {}).get(ifname) if ip: return ip tgen = get_topogen() out = tgen.gears[rname].vtysh_cmd("show interface %s json" % ifname, isjson=True) for address in out[ifname]["ipAddresses"]: if not address["address"].startswith("fe80::"): continue ip = address["address"].split("/")[0] cache.setdefault(rname, {})[ifname] = ip return ip def replace_link_local(expected, cache): for prefix, prefix_info in expected.get("routes", {}).items(): for nexthop in prefix_info[0].get("nexthops", []): ip = nexthop.get("ip", "") if not ip.startswith("link-local:"): continue rname = ip.split(":")[1] ifname = ip.split(":")[2] ip = get_link_local(rname, ifname, cache) nexthop["ip"] = ip def check_rr_sub_group(expected): tgen = get_topogen() rr = tgen.gears["rr"] output = json.loads(rr.vtysh_cmd("show bgp update-groups json")) actual = [ subgroup["peers"] for entry in output.get("default", {}).values() for subgroup in entry["subGroup"] ] return topotest.json_cmp(actual, expected) def teardown_module(_mod): "Teardown the pytest environment" tgen = get_topogen() # This function tears down the whole topology. tgen.stop_topology() def test_converge_protocols(): "Wait for protocol convergence" tgen = get_topogen() # Don't run this test if we have any failure. if tgen.routers_have_failure(): pytest.skip(tgen.errors) rr = tgen.gears["rr"] ref_file = "{}/{}/show_bgp_ipv6_summary.json".format(CWD, rr.name) expected = json.loads(open(ref_file).read()) test_func = partial( topotest.router_json_cmp, rr, "show bgp ipv6 summary json", expected, ) _, res = topotest.run_and_expect(test_func, None, count=30, wait=1) assertmsg = "{}: BGP convergence".format(rr.name) assert res is None, assertmsg def test_bgp_ipv6_table_step1(): tgen = get_topogen() # Don't run this test if we have any failure. if tgen.routers_have_failure(): pytest.skip(tgen.errors) global link_local_cache link_local_cache = {} router_list = tgen.routers().values() for router in router_list: ref_file = "{}/{}/show_bgp_ipv6_step1.json".format(CWD, router.name) expected = json.loads(open(ref_file).read()) replace_link_local(expected, link_local_cache) test_func = partial( topotest.router_json_cmp, router, "show bgp ipv6 unicast json", expected, ) _, res = topotest.run_and_expect(test_func, None, count=30, wait=1) assertmsg = "{}: BGP IPv6 Nexthop failure".format(router.name) assert res is None, assertmsg # check rr sub-groups expected = [ ["fd00:0:2::1", "fd00:0:2::2"], ["fd00:0:2::3"], ["fd00:0:2::4"], ["fd00:0:3::5"], ["fd00:0:4::6"], ] test_func = partial(check_rr_sub_group, expected) _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) assert result is None, "Peer group split failed" def test_bgp_ipv6_table_step2(): tgen = get_topogen() # Don't run this test if we have any failure. if tgen.routers_have_failure(): pytest.skip(tgen.errors) rr = tgen.gears["rr"] rr.vtysh_cmd( """ configure terminal router bgp 65000 address-family ipv6 unicast no neighbor fd00:0:2::1 nexthop-local unchanged no neighbor fd00:0:2::2 nexthop-local unchanged no neighbor fd00:0:2::3 nexthop-local unchanged no neighbor fd00:0:2::4 nexthop-local unchanged no neighbor fd00:0:3::5 nexthop-local unchanged no neighbor fd00:0:4::6 nexthop-local unchanged """ ) router_list = tgen.routers().values() for router in router_list: ref_file = "{}/{}/show_bgp_ipv6_step2.json".format(CWD, router.name) expected = json.loads(open(ref_file).read()) replace_link_local(expected, link_local_cache) test_func = partial( topotest.router_json_cmp, router, "show bgp ipv6 unicast json", expected, ) _, res = topotest.run_and_expect(test_func, None, count=30, wait=1) assertmsg = "{}: BGP IPv6 Nexthop failure".format(router.name) assert res is None, assertmsg # check rr sub-groups expected = [ ["fd00:0:2::1", "fd00:0:2::2"], ["fd00:0:2::3"], ["fd00:0:3::5", "fd00:0:2::4"], ["fd00:0:4::6"], ] test_func = partial(check_rr_sub_group, expected) _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) assert result is None, "Peer group split failed" def test_bgp_ipv6_table_step3(): tgen = get_topogen() # Don't run this test if we have any failure. if tgen.routers_have_failure(): pytest.skip(tgen.errors) rr = tgen.gears["rr"] rr.vtysh_cmd( """ configure terminal router bgp 65000 address-family ipv6 unicast neighbor fd00:0:2::1 nexthop-local unchanged neighbor fd00:0:2::2 nexthop-local unchanged neighbor fd00:0:2::3 nexthop-local unchanged neighbor fd00:0:2::4 nexthop-local unchanged neighbor fd00:0:3::5 nexthop-local unchanged neighbor fd00:0:4::6 nexthop-local unchanged """ ) router_list = tgen.routers().values() for router in router_list: ref_file = "{}/{}/show_bgp_ipv6_step1.json".format(CWD, router.name) expected = json.loads(open(ref_file).read()) replace_link_local(expected, link_local_cache) test_func = partial( topotest.router_json_cmp, router, "show bgp ipv6 unicast json", expected, ) _, res = topotest.run_and_expect(test_func, None, count=30, wait=1) assertmsg = "{}: BGP IPv6 Nexthop failure".format(router.name) assert res is None, assertmsg # check rr sub-groups expected = [ ["fd00:0:2::1", "fd00:0:2::2"], ["fd00:0:2::3"], ["fd00:0:2::4"], ["fd00:0:3::5"], ["fd00:0:4::6"], ] test_func = partial(check_rr_sub_group, expected) _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) assert result is None, "Peer group split failed" if __name__ == "__main__": args = ["-s"] + sys.argv[1:] sys.exit(pytest.main(args))