From 0af653eaf7861219284f82b97b9c4a37dc568985 Mon Sep 17 00:00:00 2001 From: Philippe Guibert Date: Fri, 25 Oct 2024 22:06:11 +0200 Subject: [PATCH 1/7] topotests: bmp, create shared library for bmp The bgp_bmp and bgp_bmp_vrf tests use similar routines to test the bmp, but are duplicates. Gather the bgp_bmp and the bgp_bmp_vrf tests in a single bgp_bmp folder. - Create a bgpbmp.py library under the bgp_bmp test folder - The bgp_bmp and bgp_bmp_vrf test are renamed to bgp_bmp_1 and bgp_bmp_2 test. - Maintain separate folder for config and output results. Adapt the bgp_bmp library accordingly. - The json output for bgp_bmp_2 test has no referenc to hostame. Signed-off-by: Philippe Guibert --- tests/topotests/bgp_bmp/bgpbmp.py | 214 ++++++++ .../bmp1vrf}/bmp-update-loc-rib-step1.json | 0 .../bmp-update-post-policy-step1.json | 0 .../bmp1vrf}/bmp-update-pre-policy-step1.json | 0 .../bmp1vrf}/bmp-withdraw-loc-rib-step1.json | 0 .../bmp-withdraw-post-policy-step1.json | 0 .../bmp-withdraw-pre-policy-step1.json | 0 .../r1 => bgp_bmp/r1vrf}/bgpd.conf | 0 .../r1vrf}/show-bgp-ipv4-update-step1.json | 1 - .../r1vrf}/show-bgp-ipv4-withdraw-step1.json | 0 .../r1vrf}/show-bgp-ipv6-update-step1.json | 2 - .../r1vrf}/show-bgp-ipv6-withdraw-step1.json | 0 .../r1 => bgp_bmp/r1vrf}/zebra.conf | 4 +- .../r2 => bgp_bmp/r2vrf}/bgpd.conf | 0 .../r2 => bgp_bmp/r2vrf}/zebra.conf | 4 +- tests/topotests/bgp_bmp/test_bgp_bmp.py | 476 ------------------ tests/topotests/bgp_bmp/test_bgp_bmp_1.py | 260 ++++++++++ tests/topotests/bgp_bmp/test_bgp_bmp_2.py | 258 ++++++++++ tests/topotests/bgp_bmp_vrf/__init__.py | 0 .../topotests/bgp_bmp_vrf/test_bgp_bmp_vrf.py | 418 --------------- tests/topotests/lib/bgp.py | 19 + 21 files changed, 755 insertions(+), 901 deletions(-) create mode 100644 tests/topotests/bgp_bmp/bgpbmp.py rename tests/topotests/{bgp_bmp_vrf/bmp1 => bgp_bmp/bmp1vrf}/bmp-update-loc-rib-step1.json (100%) rename tests/topotests/{bgp_bmp_vrf/bmp1 => bgp_bmp/bmp1vrf}/bmp-update-post-policy-step1.json (100%) rename tests/topotests/{bgp_bmp_vrf/bmp1 => bgp_bmp/bmp1vrf}/bmp-update-pre-policy-step1.json (100%) rename tests/topotests/{bgp_bmp_vrf/bmp1 => bgp_bmp/bmp1vrf}/bmp-withdraw-loc-rib-step1.json (100%) rename tests/topotests/{bgp_bmp_vrf/bmp1 => bgp_bmp/bmp1vrf}/bmp-withdraw-post-policy-step1.json (100%) rename tests/topotests/{bgp_bmp_vrf/bmp1 => bgp_bmp/bmp1vrf}/bmp-withdraw-pre-policy-step1.json (100%) rename tests/topotests/{bgp_bmp_vrf/r1 => bgp_bmp/r1vrf}/bgpd.conf (100%) rename tests/topotests/{bgp_bmp_vrf/r1 => bgp_bmp/r1vrf}/show-bgp-ipv4-update-step1.json (91%) rename tests/topotests/{bgp_bmp_vrf/r1 => bgp_bmp/r1vrf}/show-bgp-ipv4-withdraw-step1.json (100%) rename tests/topotests/{bgp_bmp_vrf/r1 => bgp_bmp/r1vrf}/show-bgp-ipv6-update-step1.json (88%) rename tests/topotests/{bgp_bmp_vrf/r1 => bgp_bmp/r1vrf}/show-bgp-ipv6-withdraw-step1.json (100%) rename tests/topotests/{bgp_bmp_vrf/r1 => bgp_bmp/r1vrf}/zebra.conf (66%) rename tests/topotests/{bgp_bmp_vrf/r2 => bgp_bmp/r2vrf}/bgpd.conf (100%) rename tests/topotests/{bgp_bmp_vrf/r2 => bgp_bmp/r2vrf}/zebra.conf (72%) delete mode 100644 tests/topotests/bgp_bmp/test_bgp_bmp.py create mode 100644 tests/topotests/bgp_bmp/test_bgp_bmp_1.py create mode 100644 tests/topotests/bgp_bmp/test_bgp_bmp_2.py delete mode 100644 tests/topotests/bgp_bmp_vrf/__init__.py delete mode 100644 tests/topotests/bgp_bmp_vrf/test_bgp_bmp_vrf.py diff --git a/tests/topotests/bgp_bmp/bgpbmp.py b/tests/topotests/bgp_bmp/bgpbmp.py new file mode 100644 index 000000000000..950ffef2f71f --- /dev/null +++ b/tests/topotests/bgp_bmp/bgpbmp.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: GPL-2.0-or-later + +# Copyright 2023, 6wind +import json +import os + +from lib import topotest +from lib.topogen import get_topogen +from lib.topolog import logger + +# remember the last sequence number of the logging messages +SEQ = 0 + + +def bmp_reset_seq(): + global SEQ + SEQ = 0 + + +def get_bmp_messages(bmp_collector, bmp_log_file): + """ + Read the BMP logging messages. + """ + messages = [] + text_output = bmp_collector.run(f"cat {bmp_log_file}") + + for m in text_output.splitlines(): + # some output in the bash can break the message decoding + try: + messages.append(json.loads(m)) + except Exception as e: + logger.warning(str(e) + " message: {}".format(str(m))) + continue + + if not messages: + logger.error("Bad BMP log format, check your BMP server") + + return messages + + +def bmp_update_seq(bmp_collector, bmp_log_file): + global SEQ + + messages = get_bmp_messages(bmp_collector, bmp_log_file) + + if len(messages): + SEQ = messages[-1]["seq"] + + +def bmp_update_expected_files( + bmp_actual, expected_prefixes, bmp_log_type, policy, step, bmp_client +): + tgen = get_topogen() + + with open(f"/tmp/bmp-{bmp_log_type}-{policy}-step{step}.json", "w") as json_file: + json.dump(bmp_actual, json_file, indent=4) + + out = bmp_client.vtysh_cmd("show bgp vrf vrf1 ipv4 json", isjson=True) + filtered_out = { + "routes": { + prefix: route_info + for prefix, route_info in out["routes"].items() + if prefix in expected_prefixes + } + } + if bmp_log_type == "withdraw": + for pfx in expected_prefixes: + if "::" in pfx: + continue + filtered_out["routes"][pfx] = None + + # ls /tmp/show*json | while read file; do egrep -v 'prefix|network|metric|ocPrf|version|weight|peerId|vrf|Version|valid|Reason|fe80' $file >$(basename $file); echo >> $(basename $file); done + with open(f"/tmp/show-bgp-ipv4-{bmp_log_type}-step{step}.json", "w") as json_file: + json.dump(filtered_out, json_file, indent=4) + + out = tgen.gears["r1"].vtysh_cmd("show bgp vrf vrf1 ipv6 json", isjson=True) + filtered_out = { + "routes": { + prefix: route_info + for prefix, route_info in out["routes"].items() + if prefix in expected_prefixes + } + } + if bmp_log_type == "withdraw": + for pfx in expected_prefixes: + if "::" not in pfx: + continue + filtered_out["routes"][pfx] = None + + with open(f"/tmp/show-bgp-ipv6-{bmp_log_type}-step{step}.json", "w") as json_file: + json.dump(filtered_out, json_file, indent=4) + + +def bmp_check_for_prefixes( + expected_prefixes, + bmp_log_type, + policy, + step, + bmp_collector, + bmp_log_file, + bmp_client, + expected_json_path, + update_expected_json, + loc_rib, +): + """ + Check for the presence of the given prefixes in the BMP server logs with + the given message type and the set policy. + + """ + global SEQ + + # we care only about the new messages + messages = [ + m + for m in sorted( + get_bmp_messages(bmp_collector, bmp_log_file), key=lambda d: d["seq"] + ) + if m["seq"] > SEQ + ] + + # create empty initial files + # for step in $(seq 1); do + # for i in "update" "withdraw"; do + # for j in "pre-policy" "post-policy" "loc-rib"; do + # echo '{"null": {}}'> bmp-$i-$j-step$step.json + # done + # done + # done + + ref_file = f"{expected_json_path}/bmp-{bmp_log_type}-{policy}-step{step}.json" + expected = json.loads(open(ref_file).read()) + + # Build actual json from logs + actual = {} + for m in messages: + if ( + "bmp_log_type" in m.keys() + and "ip_prefix" in m.keys() + and m["ip_prefix"] in expected_prefixes + and m["bmp_log_type"] == bmp_log_type + and m["policy"] == policy + ): + policy_dict = actual.setdefault(m["policy"], {}) + bmp_log_type_dict = policy_dict.setdefault(m["bmp_log_type"], {}) + + # Add or update the ip_prefix dictionary with filtered key-value pairs + bmp_log_type_dict[m["ip_prefix"]] = { + k: v + for k, v in sorted(m.items()) + # filter out variable keys + if k not in ["timestamp", "seq", "nxhp_link-local"] + and ( + # When policy is loc-rib, the peer-distinguisher is 0:0 + # for the default VRF or the RD if any or the 0:. + # 0: is used to distinguished. RFC7854 says: "If the + # peer is a "Local Instance Peer", it is set to a unique, + # locally defined value." The value is not tested because it + # is variable. + k != "peer_distinguisher" + or policy != loc_rib + or v == "0:0" + or not v.startswith("0:") + ) + } + + # build expected JSON files + if ( + update_expected_json + and actual + and set(actual.get(policy, {}).get(bmp_log_type, {}).keys()) + == set(expected_prefixes) + ): + bmp_update_expected_files( + actual, expected_prefixes, bmp_log_type, policy, step, bmp_client + ) + + return topotest.json_cmp(actual, expected, exact=True) + + +def bmp_check_for_peer_message( + expected_peers, bmp_log_type, bmp_collector, bmp_log_file +): + """ + Check for the presence of a peer up message for the peer + """ + global SEQ + + # we care only about the new messages + messages = [ + m + for m in sorted( + get_bmp_messages(bmp_collector, bmp_log_file), key=lambda d: d["seq"] + ) + if m["seq"] > SEQ + ] + + # get the list of pairs (prefix, policy, seq) for the given message type + peers = [ + m["peer_ip"] + for m in messages + if "peer_ip" in m.keys() and m["bmp_log_type"] == bmp_log_type + ] + + # check for prefixes + for ep in expected_peers: + if ep not in peers: + msg = "The peer {} is not present in the {} log messages." + logger.debug(msg.format(ep, bmp_log_type)) + return False + + SEQ = messages[-1]["seq"] + return True diff --git a/tests/topotests/bgp_bmp_vrf/bmp1/bmp-update-loc-rib-step1.json b/tests/topotests/bgp_bmp/bmp1vrf/bmp-update-loc-rib-step1.json similarity index 100% rename from tests/topotests/bgp_bmp_vrf/bmp1/bmp-update-loc-rib-step1.json rename to tests/topotests/bgp_bmp/bmp1vrf/bmp-update-loc-rib-step1.json diff --git a/tests/topotests/bgp_bmp_vrf/bmp1/bmp-update-post-policy-step1.json b/tests/topotests/bgp_bmp/bmp1vrf/bmp-update-post-policy-step1.json similarity index 100% rename from tests/topotests/bgp_bmp_vrf/bmp1/bmp-update-post-policy-step1.json rename to tests/topotests/bgp_bmp/bmp1vrf/bmp-update-post-policy-step1.json diff --git a/tests/topotests/bgp_bmp_vrf/bmp1/bmp-update-pre-policy-step1.json b/tests/topotests/bgp_bmp/bmp1vrf/bmp-update-pre-policy-step1.json similarity index 100% rename from tests/topotests/bgp_bmp_vrf/bmp1/bmp-update-pre-policy-step1.json rename to tests/topotests/bgp_bmp/bmp1vrf/bmp-update-pre-policy-step1.json diff --git a/tests/topotests/bgp_bmp_vrf/bmp1/bmp-withdraw-loc-rib-step1.json b/tests/topotests/bgp_bmp/bmp1vrf/bmp-withdraw-loc-rib-step1.json similarity index 100% rename from tests/topotests/bgp_bmp_vrf/bmp1/bmp-withdraw-loc-rib-step1.json rename to tests/topotests/bgp_bmp/bmp1vrf/bmp-withdraw-loc-rib-step1.json diff --git a/tests/topotests/bgp_bmp_vrf/bmp1/bmp-withdraw-post-policy-step1.json b/tests/topotests/bgp_bmp/bmp1vrf/bmp-withdraw-post-policy-step1.json similarity index 100% rename from tests/topotests/bgp_bmp_vrf/bmp1/bmp-withdraw-post-policy-step1.json rename to tests/topotests/bgp_bmp/bmp1vrf/bmp-withdraw-post-policy-step1.json diff --git a/tests/topotests/bgp_bmp_vrf/bmp1/bmp-withdraw-pre-policy-step1.json b/tests/topotests/bgp_bmp/bmp1vrf/bmp-withdraw-pre-policy-step1.json similarity index 100% rename from tests/topotests/bgp_bmp_vrf/bmp1/bmp-withdraw-pre-policy-step1.json rename to tests/topotests/bgp_bmp/bmp1vrf/bmp-withdraw-pre-policy-step1.json diff --git a/tests/topotests/bgp_bmp_vrf/r1/bgpd.conf b/tests/topotests/bgp_bmp/r1vrf/bgpd.conf similarity index 100% rename from tests/topotests/bgp_bmp_vrf/r1/bgpd.conf rename to tests/topotests/bgp_bmp/r1vrf/bgpd.conf diff --git a/tests/topotests/bgp_bmp_vrf/r1/show-bgp-ipv4-update-step1.json b/tests/topotests/bgp_bmp/r1vrf/show-bgp-ipv4-update-step1.json similarity index 91% rename from tests/topotests/bgp_bmp_vrf/r1/show-bgp-ipv4-update-step1.json rename to tests/topotests/bgp_bmp/r1vrf/show-bgp-ipv4-update-step1.json index 038c87ca9dd0..dc0228db61f0 100644 --- a/tests/topotests/bgp_bmp_vrf/r1/show-bgp-ipv4-update-step1.json +++ b/tests/topotests/bgp_bmp/r1vrf/show-bgp-ipv4-update-step1.json @@ -9,7 +9,6 @@ "nexthops": [ { "ip": "192.168.0.2", - "hostname": "r2", "afi": "ipv4", "used": true } diff --git a/tests/topotests/bgp_bmp_vrf/r1/show-bgp-ipv4-withdraw-step1.json b/tests/topotests/bgp_bmp/r1vrf/show-bgp-ipv4-withdraw-step1.json similarity index 100% rename from tests/topotests/bgp_bmp_vrf/r1/show-bgp-ipv4-withdraw-step1.json rename to tests/topotests/bgp_bmp/r1vrf/show-bgp-ipv4-withdraw-step1.json diff --git a/tests/topotests/bgp_bmp_vrf/r1/show-bgp-ipv6-update-step1.json b/tests/topotests/bgp_bmp/r1vrf/show-bgp-ipv6-update-step1.json similarity index 88% rename from tests/topotests/bgp_bmp_vrf/r1/show-bgp-ipv6-update-step1.json rename to tests/topotests/bgp_bmp/r1vrf/show-bgp-ipv6-update-step1.json index db34220149e2..64c8622ab523 100644 --- a/tests/topotests/bgp_bmp_vrf/r1/show-bgp-ipv6-update-step1.json +++ b/tests/topotests/bgp_bmp/r1vrf/show-bgp-ipv6-update-step1.json @@ -9,12 +9,10 @@ "nexthops": [ { "ip": "192:168::2", - "hostname": "r2", "afi": "ipv6", "scope": "global" }, { - "hostname": "r2", "afi": "ipv6", "scope": "link-local", "used": true diff --git a/tests/topotests/bgp_bmp_vrf/r1/show-bgp-ipv6-withdraw-step1.json b/tests/topotests/bgp_bmp/r1vrf/show-bgp-ipv6-withdraw-step1.json similarity index 100% rename from tests/topotests/bgp_bmp_vrf/r1/show-bgp-ipv6-withdraw-step1.json rename to tests/topotests/bgp_bmp/r1vrf/show-bgp-ipv6-withdraw-step1.json diff --git a/tests/topotests/bgp_bmp_vrf/r1/zebra.conf b/tests/topotests/bgp_bmp/r1vrf/zebra.conf similarity index 66% rename from tests/topotests/bgp_bmp_vrf/r1/zebra.conf rename to tests/topotests/bgp_bmp/r1vrf/zebra.conf index 0b523c9e18d8..a242eadce8a7 100644 --- a/tests/topotests/bgp_bmp_vrf/r1/zebra.conf +++ b/tests/topotests/bgp_bmp/r1vrf/zebra.conf @@ -1,7 +1,7 @@ -interface r1-eth0 +interface r1vrf-eth0 ip address 192.0.2.1/24 ! -interface r1-eth1 +interface r1vrf-eth1 ip address 192.168.0.1/24 ipv6 address 192:168::1/64 ! diff --git a/tests/topotests/bgp_bmp_vrf/r2/bgpd.conf b/tests/topotests/bgp_bmp/r2vrf/bgpd.conf similarity index 100% rename from tests/topotests/bgp_bmp_vrf/r2/bgpd.conf rename to tests/topotests/bgp_bmp/r2vrf/bgpd.conf diff --git a/tests/topotests/bgp_bmp_vrf/r2/zebra.conf b/tests/topotests/bgp_bmp/r2vrf/zebra.conf similarity index 72% rename from tests/topotests/bgp_bmp_vrf/r2/zebra.conf rename to tests/topotests/bgp_bmp/r2vrf/zebra.conf index 9d82bfe2df5c..9a8da2d95fbd 100644 --- a/tests/topotests/bgp_bmp_vrf/r2/zebra.conf +++ b/tests/topotests/bgp_bmp/r2vrf/zebra.conf @@ -1,8 +1,8 @@ -interface r2-eth0 +interface r2vrf-eth0 ip address 192.168.0.2/24 ipv6 address 192:168::2/64 ! -interface r2-eth1 +interface r2vrf-eth1 ip address 172.31.0.2/24 ipv6 address 172:31::2/64 ! diff --git a/tests/topotests/bgp_bmp/test_bgp_bmp.py b/tests/topotests/bgp_bmp/test_bgp_bmp.py deleted file mode 100644 index 658ad2b99a2c..000000000000 --- a/tests/topotests/bgp_bmp/test_bgp_bmp.py +++ /dev/null @@ -1,476 +0,0 @@ -#!/usr/bin/env python -# SPDX-License-Identifier: ISC - -# Copyright 2023 6WIND S.A. -# Authored by Farid Mihoub -# - -""" -test_bgp_bmp.py: Test BGP BMP functionalities - - +------+ +------+ +------+ - | | | | | | - | BMP1 |------------| R1 |---------------| R2 | - | | | | | | - +------+ +------+ +------+ - -Setup two routers R1 and R2 with one link configured with IPv4 and -IPv6 addresses. -Configure BGP in R1 and R2 to exchange prefixes from -the latter to the first router. -Setup a link between R1 and the BMP server, activate the BMP feature in R1 -and ensure the monitored BGP sessions logs are well present on the BMP server. -""" - -from functools import partial -from ipaddress import ip_network -import json -import os -import pytest -import sys - -# Save the Current Working Directory to find configuration files. -CWD = os.path.dirname(os.path.realpath(__file__)) -sys.path.append(os.path.join("../")) -sys.path.append(os.path.join("../lib/")) - -# pylint: disable=C0413 -# Import topogen and topotest helpers -from lib import topotest -from lib.bgp import verify_bgp_convergence_from_running_config -from lib.topogen import Topogen, TopoRouter, get_topogen -from lib.topolog import logger - -pytestmark = [pytest.mark.bgpd] - -# remember the last sequence number of the logging messages -SEQ = 0 - -PRE_POLICY = "pre-policy" -POST_POLICY = "post-policy" -LOC_RIB = "loc-rib" - -UPDATE_EXPECTED_JSON = False -DEBUG_PCAP = False - - -def build_topo(tgen): - tgen.add_router("r1") - tgen.add_router("r2") - tgen.add_bmp_server("bmp1", ip="192.0.2.10", defaultRoute="via 192.0.2.1") - - switch = tgen.add_switch("s1") - switch.add_link(tgen.gears["r1"]) - switch.add_link(tgen.gears["bmp1"]) - - tgen.add_link(tgen.gears["r1"], tgen.gears["r2"], "r1-eth1", "r2-eth0") - - -def setup_module(mod): - tgen = Topogen(build_topo, mod.__name__) - tgen.start_topology() - - if DEBUG_PCAP: - tgen.gears["r1"].run("rm /tmp/bmp.pcap") - tgen.gears["r1"].run( - "tcpdump -nni r1-eth0 -s 0 -w /tmp/bmp.pcap &", stdout=None - ) - - for rname, router in tgen.routers().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)), - "-M bmp", - ) - - tgen.start_router() - - logger.info("starting BMP servers") - for bmp_name, server in tgen.get_bmp_servers().items(): - server.start(log_file=os.path.join(tgen.logdir, bmp_name, "bmp.log")) - - -def teardown_module(_mod): - tgen = get_topogen() - tgen.stop_topology() - - -def test_bgp_convergence(): - tgen = get_topogen() - if tgen.routers_have_failure(): - pytest.skip(tgen.errors) - - result = verify_bgp_convergence_from_running_config(tgen, dut="r1") - assert result is True, "BGP is not converging" - - -def get_bmp_messages(): - """ - Read the BMP logging messages. - """ - messages = [] - tgen = get_topogen() - text_output = tgen.gears["bmp1"].run( - "cat {}".format(os.path.join(tgen.logdir, "bmp1", "bmp.log")) - ) - - for m in text_output.splitlines(): - # some output in the bash can break the message decoding - try: - messages.append(json.loads(m)) - except Exception as e: - logger.warning(str(e) + " message: {}".format(str(m))) - continue - - if not messages: - logger.error("Bad BMP log format, check your BMP server") - - return messages - - -def update_seq(): - global SEQ - - messages = get_bmp_messages() - - if len(messages): - SEQ = messages[-1]["seq"] - - -def update_expected_files(bmp_actual, expected_prefixes, bmp_log_type, policy, step): - tgen = get_topogen() - - with open(f"/tmp/bmp-{bmp_log_type}-{policy}-step{step}.json", "w") as json_file: - json.dump(bmp_actual, json_file, indent=4) - - if step == 2: # vpn - rd = "444:2" - out = tgen.gears["r1"].vtysh_cmd("show bgp ipv4 vpn json", isjson=True) - filtered_out = { - "routes": { - "routeDistinguishers": { - rd: { - prefix: route_info - for prefix, route_info in out["routes"] - .get("routeDistinguishers", {}) - .get(rd, {}) - .items() - if prefix in expected_prefixes - } - } - } - } - if bmp_log_type == "withdraw": - for pfx in expected_prefixes: - if "::" in pfx: - continue - filtered_out["routes"]["routeDistinguishers"][rd][pfx] = None - - # ls /tmp/show*json | while read file; do egrep -v 'prefix|network|metric|ocPrf|version|weight|peerId|vrf|Version|valid|Reason|fe80' $file >$(basename $file); echo >> $(basename $file); done - with open( - f"/tmp/show-bgp-ipv4-{bmp_log_type}-step{step}.json", "w" - ) as json_file: - json.dump(filtered_out, json_file, indent=4) - - rd = "555:2" - out = tgen.gears["r1"].vtysh_cmd("show bgp ipv6 vpn json", isjson=True) - filtered_out = { - "routes": { - "routeDistinguishers": { - rd: { - prefix: route_info - for prefix, route_info in out["routes"] - .get("routeDistinguishers", {}) - .get(rd, {}) - .items() - if prefix in expected_prefixes - } - } - } - } - if bmp_log_type == "withdraw": - for pfx in expected_prefixes: - if "::" not in pfx: - continue - filtered_out["routes"]["routeDistinguishers"][rd][pfx] = None - with open( - f"/tmp/show-bgp-ipv6-{bmp_log_type}-step{step}.json", "w" - ) as json_file: - json.dump(filtered_out, json_file, indent=4) - - return - - out = tgen.gears["r1"].vtysh_cmd("show bgp ipv4 json", isjson=True) - filtered_out = { - "routes": { - prefix: route_info - for prefix, route_info in out["routes"].items() - if prefix in expected_prefixes - } - } - if bmp_log_type == "withdraw": - for pfx in expected_prefixes: - if "::" in pfx: - continue - filtered_out["routes"][pfx] = None - - # ls /tmp/show*json | while read file; do egrep -v 'prefix|network|metric|ocPrf|version|weight|peerId|vrf|Version|valid|Reason|fe80' $file >$(basename $file); echo >> $(basename $file); done - with open(f"/tmp/show-bgp-ipv4-{bmp_log_type}-step{step}.json", "w") as json_file: - json.dump(filtered_out, json_file, indent=4) - - out = tgen.gears["r1"].vtysh_cmd("show bgp ipv6 json", isjson=True) - filtered_out = { - "routes": { - prefix: route_info - for prefix, route_info in out["routes"].items() - if prefix in expected_prefixes - } - } - if bmp_log_type == "withdraw": - for pfx in expected_prefixes: - if "::" not in pfx: - continue - filtered_out["routes"][pfx] = None - with open(f"/tmp/show-bgp-ipv6-{bmp_log_type}-step{step}.json", "w") as json_file: - json.dump(filtered_out, json_file, indent=4) - - -def check_for_prefixes(expected_prefixes, bmp_log_type, policy, step): - """ - Check for the presence of the given prefixes in the BMP server logs with - the given message type and the set policy. - - """ - global SEQ - - # we care only about the new messages - messages = [ - m for m in sorted(get_bmp_messages(), key=lambda d: d["seq"]) if m["seq"] > SEQ - ] - - # create empty initial files - # for step in $(seq 2); do - # for i in "update" "withdraw"; do - # for j in "pre-policy" "post-policy" "loc-rib"; do - # echo '{"null": {}}'> bmp-$i-$j-step$step.json - # done - # done - # done - - ref_file = f"{CWD}/bmp1/bmp-{bmp_log_type}-{policy}-step{step}.json" - expected = json.loads(open(ref_file).read()) - - # Build actual json from logs - actual = {} - for m in messages: - if ( - "bmp_log_type" in m.keys() - and "ip_prefix" in m.keys() - and m["ip_prefix"] in expected_prefixes - and m["bmp_log_type"] == bmp_log_type - and m["policy"] == policy - ): - policy_dict = actual.setdefault(m["policy"], {}) - bmp_log_type_dict = policy_dict.setdefault(m["bmp_log_type"], {}) - - # Add or update the ip_prefix dictionary with filtered key-value pairs - bmp_log_type_dict[m["ip_prefix"]] = { - k: v - for k, v in sorted(m.items()) - # filter out variable keys - if k not in ["timestamp", "seq", "nxhp_link-local"] - and ( - # When policy is loc-rib, the peer-distinguisher is 0:0 - # for the default VRF or the RD if any or the 0:. - # 0: is used to distinguished. RFC7854 says: "If the - # peer is a "Local Instance Peer", it is set to a unique, - # locally defined value." The value is not tested because it - # is variable. - k != "peer_distinguisher" - or policy != LOC_RIB - or v == "0:0" - or not v.startswith("0:") - ) - } - - # build expected JSON files - if ( - UPDATE_EXPECTED_JSON - and actual - and set(actual.get(policy, {}).get(bmp_log_type, {}).keys()) - == set(expected_prefixes) - ): - update_expected_files(actual, expected_prefixes, bmp_log_type, policy, step) - - return topotest.json_cmp(actual, expected, exact=True) - - -def check_for_peer_message(expected_peers, bmp_log_type): - """ - Check for the presence of a peer up message for the peer - """ - global SEQ - # we care only about the new messages - messages = [ - m for m in sorted(get_bmp_messages(), key=lambda d: d["seq"]) if m["seq"] > SEQ - ] - - # get the list of pairs (prefix, policy, seq) for the given message type - peers = [ - m["peer_ip"] - for m in messages - if "peer_ip" in m.keys() and m["bmp_log_type"] == bmp_log_type - ] - - # check for prefixes - for ep in expected_peers: - if ep not in peers: - msg = "The peer {} is not present in the {} log messages." - logger.debug(msg.format(ep, bmp_log_type)) - return False - - SEQ = messages[-1]["seq"] - return True - - -def configure_prefixes(tgen, node, asn, safi, prefixes, vrf=None, update=True): - """ - Configure the bgp prefixes. - """ - withdraw = "no " if not update else "" - vrf = " vrf {}".format(vrf) if vrf else "" - for p in prefixes: - ip = ip_network(p) - cmd = [ - "conf t\n", - "router bgp {}{}\n".format(asn, vrf), - "address-family ipv{} {}\n".format(ip.version, safi), - "{}network {}\n".format(withdraw, ip), - "exit-address-family\n", - ] - logger.debug("setting prefix: ipv{} {} {}".format(ip.version, safi, ip)) - tgen.gears[node].vtysh_cmd("".join(cmd)) - - -def _test_prefixes(policy, vrf=None, step=0): - """ - Setup the BMP monitor policy, Add and withdraw ipv4/v6 prefixes. - Check if the previous actions are logged in the BMP server with the right - message type and the right policy. - """ - tgen = get_topogen() - - safi = "vpn" if vrf else "unicast" - - prefixes = ["172.31.0.15/32", "2001::1111/128"] - - for type in ("update", "withdraw"): - update_seq() - - configure_prefixes( - tgen, "r2", 65502, "unicast", prefixes, vrf=vrf, update=(type == "update") - ) - - logger.info(f"checking for prefixes {type}") - - for ipver in [4, 6]: - if UPDATE_EXPECTED_JSON: - continue - ref_file = "{}/r1/show-bgp-ipv{}-{}-step{}.json".format( - CWD, ipver, type, step - ) - expected = json.loads(open(ref_file).read()) - - test_func = partial( - topotest.router_json_cmp, - tgen.gears["r1"], - f"show bgp ipv{ipver} {safi} json", - expected, - ) - _, res = topotest.run_and_expect(test_func, None, count=30, wait=1) - assertmsg = f"r1: BGP IPv{ipver} convergence failed" - assert res is None, assertmsg - - # check - test_func = partial(check_for_prefixes, prefixes, type, policy, step) - success, res = topotest.run_and_expect(test_func, None, count=30, wait=1) - assert success, "Checking the updated prefixes has failed ! %s" % res - - -def test_bmp_server_logging(): - """ - Assert the logging of the bmp server. - """ - - def check_for_log_file(): - tgen = get_topogen() - output = tgen.gears["bmp1"].run( - "ls {}".format(os.path.join(tgen.logdir, "bmp1")) - ) - if "bmp.log" not in output: - return False - return True - - success, _ = topotest.run_and_expect(check_for_log_file, True, count=30, wait=1) - assert success, "The BMP server is not logging" - - -def test_peer_up(): - """ - Checking for BMP peers up messages - """ - - peers = ["192.168.0.2", "192:168::2"] - - logger.info("checking for BMP peers up messages") - - test_func = partial(check_for_peer_message, peers, "peer up") - success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1) - assert success, "Checking the updated prefixes has been failed !." - - -def test_bmp_bgp_unicast(): - """ - Add/withdraw bgp unicast prefixes and check the bmp logs. - """ - logger.info("*** Unicast prefixes pre-policy logging ***") - _test_prefixes(PRE_POLICY, step=1) - logger.info("*** Unicast prefixes post-policy logging ***") - _test_prefixes(POST_POLICY, step=1) - logger.info("*** Unicast prefixes loc-rib logging ***") - _test_prefixes(LOC_RIB, step=1) - - -def test_bmp_bgp_vpn(): - # check for the prefixes in the BMP server logging file - logger.info("***** VPN prefixes pre-policy logging *****") - _test_prefixes(PRE_POLICY, vrf="vrf1", step=2) - logger.info("***** VPN prefixes post-policy logging *****") - _test_prefixes(POST_POLICY, vrf="vrf1", step=2) - logger.info("***** VPN prefixes loc-rib logging *****") - _test_prefixes(LOC_RIB, vrf="vrf1", step=2) - - -def test_peer_down(): - """ - Checking for BMP peers down messages - """ - tgen = get_topogen() - - tgen.gears["r2"].vtysh_cmd("clear bgp *") - - peers = ["192.168.0.2", "192:168::2"] - - logger.info("checking for BMP peers down messages") - - test_func = partial(check_for_peer_message, peers, "peer down") - success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1) - assert success, "Checking the updated prefixes has been failed !." - - -if __name__ == "__main__": - args = ["-s"] + sys.argv[1:] - sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_bmp/test_bgp_bmp_1.py b/tests/topotests/bgp_bmp/test_bgp_bmp_1.py new file mode 100644 index 000000000000..cc7bc31041d9 --- /dev/null +++ b/tests/topotests/bgp_bmp/test_bgp_bmp_1.py @@ -0,0 +1,260 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright 2023 6WIND S.A. +# Authored by Farid Mihoub +# + +""" +test_bgp_bmp.py: Test BGP BMP functionalities + + +------+ +------+ +------+ + | | | | | | + | BMP1 |------------| R1 |---------------| R2 | + | | | | | | + +------+ +------+ +------+ + +Setup two routers R1 and R2 with one link configured with IPv4 and +IPv6 addresses. +Configure BGP in R1 and R2 to exchange prefixes from +the latter to the first router. +Setup a link between R1 and the BMP server, activate the BMP feature in R1 +and ensure the monitored BGP sessions logs are well present on the BMP server. +""" + +from functools import partial +import json +import os +import pytest +import sys + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join("../")) +sys.path.append(os.path.join("../lib/")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.bgp import verify_bgp_convergence_from_running_config +from lib.bgp import bgp_configure_prefixes +from .bgpbmp import ( + bmp_check_for_prefixes, + bmp_check_for_peer_message, + bmp_update_seq, +) +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +pytestmark = [pytest.mark.bgpd] + +PRE_POLICY = "pre-policy" +POST_POLICY = "post-policy" +LOC_RIB = "loc-rib" + +UPDATE_EXPECTED_JSON = False +DEBUG_PCAP = False + + +def build_topo(tgen): + tgen.add_router("r1") + tgen.add_router("r2") + tgen.add_bmp_server("bmp1", ip="192.0.2.10", defaultRoute="via 192.0.2.1") + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["bmp1"]) + + tgen.add_link(tgen.gears["r1"], tgen.gears["r2"], "r1-eth1", "r2-eth0") + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + if DEBUG_PCAP: + tgen.gears["r1"].run("rm /tmp/bmp.pcap") + tgen.gears["r1"].run( + "tcpdump -nni r1-eth0 -s 0 -w /tmp/bmp.pcap &", stdout=None + ) + + for rname, router in tgen.routers().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)), + "-M bmp", + ) + + tgen.start_router() + + logger.info("starting BMP servers") + for bmp_name, server in tgen.get_bmp_servers().items(): + server.start(log_file=os.path.join(tgen.logdir, bmp_name, "bmp.log")) + + +def teardown_module(_mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_convergence(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + result = verify_bgp_convergence_from_running_config(tgen, dut="r1") + assert result is True, "BGP is not converging" + + +def _test_prefixes(policy, vrf=None, step=0): + """ + Setup the BMP monitor policy, Add and withdraw ipv4/v6 prefixes. + Check if the previous actions are logged in the BMP server with the right + message type and the right policy. + """ + tgen = get_topogen() + + safi = "vpn" if vrf else "unicast" + + prefixes = ["172.31.0.15/32", "2001::1111/128"] + + for type in ("update", "withdraw"): + bmp_update_seq(tgen.gears["bmp1"], os.path.join(tgen.logdir, "bmp1", "bmp.log")) + + bgp_configure_prefixes( + tgen.gears["r2"], + 65502, + "unicast", + prefixes, + vrf=vrf, + update=(type == "update"), + ) + + logger.info(f"checking for prefixes {type}") + + for ipver in [4, 6]: + if UPDATE_EXPECTED_JSON: + continue + ref_file = "{}/r1/show-bgp-ipv{}-{}-step{}.json".format( + CWD, ipver, type, step + ) + expected = json.loads(open(ref_file).read()) + + test_func = partial( + topotest.router_json_cmp, + tgen.gears["r1"], + f"show bgp ipv{ipver} {safi} json", + expected, + ) + _, res = topotest.run_and_expect(test_func, None, count=30, wait=1) + assertmsg = f"r1: BGP IPv{ipver} convergence failed" + assert res is None, assertmsg + + # check + test_func = partial( + bmp_check_for_prefixes, + prefixes, + type, + policy, + step, + tgen.gears["bmp1"], + os.path.join(tgen.logdir, "bmp1", "bmp.log"), + tgen.gears["r1"], + f"{CWD}/bmp1", + UPDATE_EXPECTED_JSON, + LOC_RIB, + ) + success, res = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert success, "Checking the updated prefixes has failed ! %s" % res + + +def test_bmp_server_logging(): + """ + Assert the logging of the bmp server. + """ + + def check_for_log_file(): + tgen = get_topogen() + output = tgen.gears["bmp1"].run( + "ls {}".format(os.path.join(tgen.logdir, "bmp1")) + ) + if "bmp.log" not in output: + return False + return True + + success, _ = topotest.run_and_expect(check_for_log_file, True, count=30, wait=1) + assert success, "The BMP server is not logging" + + +def test_peer_up(): + """ + Checking for BMP peers up messages + """ + + tgen = get_topogen() + peers = ["192.168.0.2", "192:168::2"] + + logger.info("checking for BMP peers up messages") + + test_func = partial( + bmp_check_for_peer_message, + peers, + "peer up", + tgen.gears["bmp1"], + os.path.join(tgen.logdir, "bmp1", "bmp.log"), + ) + success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1) + assert success, "Checking the updated prefixes has been failed !." + + +def test_bmp_bgp_unicast(): + """ + Add/withdraw bgp unicast prefixes and check the bmp logs. + """ + logger.info("*** Unicast prefixes pre-policy logging ***") + _test_prefixes(PRE_POLICY, step=1) + logger.info("*** Unicast prefixes post-policy logging ***") + _test_prefixes(POST_POLICY, step=1) + logger.info("*** Unicast prefixes loc-rib logging ***") + _test_prefixes(LOC_RIB, step=1) + + +def test_bmp_bgp_vpn(): + # check for the prefixes in the BMP server logging file + logger.info("***** VPN prefixes pre-policy logging *****") + _test_prefixes(PRE_POLICY, vrf="vrf1", step=2) + logger.info("***** VPN prefixes post-policy logging *****") + _test_prefixes(POST_POLICY, vrf="vrf1", step=2) + logger.info("***** VPN prefixes loc-rib logging *****") + _test_prefixes(LOC_RIB, vrf="vrf1", step=2) + + +def test_peer_down(): + """ + Checking for BMP peers down messages + """ + tgen = get_topogen() + + tgen.gears["r2"].vtysh_cmd("clear bgp *") + + peers = ["192.168.0.2", "192:168::2"] + + logger.info("checking for BMP peers down messages") + + test_func = partial( + bmp_check_for_peer_message, + peers, + "peer down", + tgen.gears["bmp1"], + os.path.join(tgen.logdir, "bmp1", "bmp.log"), + ) + success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1) + assert success, "Checking the updated prefixes has been failed !." + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_bmp/test_bgp_bmp_2.py b/tests/topotests/bgp_bmp/test_bgp_bmp_2.py new file mode 100644 index 000000000000..97cc98962bb6 --- /dev/null +++ b/tests/topotests/bgp_bmp/test_bgp_bmp_2.py @@ -0,0 +1,258 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright 2023 6WIND S.A. +# Authored by Farid Mihoub +# + +""" +test_bgp_bmp.py: Test BGP BMP functionalities + + +------+ +------+ +------+ + | | | | | | + | BMP1 |------------| R1 |---------------| R2 | + | | | | | | + +------+ +------+ +------+ + +Setup two routers R1 and R2 with one link configured with IPv4 and +IPv6 addresses. +Configure BGP in R1 and R2 to exchange prefixes from +the latter to the first router. +Setup a link between R1 and the BMP server, activate the BMP feature in R1 +and ensure the monitored BGP sessions logs are well present on the BMP server. +""" + +from functools import partial +import json +import os +import platform +import pytest +import sys + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join("../")) +sys.path.append(os.path.join("../lib/")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.bgp import verify_bgp_convergence_from_running_config +from lib.bgp import bgp_configure_prefixes +from .bgpbmp import ( + bmp_check_for_prefixes, + bmp_check_for_peer_message, + bmp_update_seq, + bmp_reset_seq, +) + + +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +pytestmark = [pytest.mark.bgpd] + +PRE_POLICY = "pre-policy" +POST_POLICY = "post-policy" +LOC_RIB = "loc-rib" + +UPDATE_EXPECTED_JSON = False +DEBUG_PCAP = False + + +def build_topo(tgen): + tgen.add_router("r1vrf") + tgen.add_router("r2vrf") + tgen.add_bmp_server("bmp1vrf", ip="192.0.2.10", defaultRoute="via 192.0.2.1") + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1vrf"]) + switch.add_link(tgen.gears["bmp1vrf"]) + + tgen.add_link(tgen.gears["r1vrf"], tgen.gears["r2vrf"], "r1vrf-eth1", "r2vrf-eth0") + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + tgen.net["r1vrf"].cmd( + """ +ip link add vrf1 type vrf table 10 +ip link set vrf1 up +ip link set r1vrf-eth1 master vrf1 +""" + ) + bmp_reset_seq() + if DEBUG_PCAP: + tgen.gears["r1vrf"].run("rm /tmp/bmp_vrf.pcap") + tgen.gears["r1vrf"].run( + "tcpdump -nni r1vrf-eth0 -s 0 -w /tmp/bmp_vrf.pcap &", stdout=None + ) + + for rname, router in tgen.routers().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)), + "-M bmp", + ) + + tgen.start_router() + + logger.info("starting BMP servers") + for bmp_name, server in tgen.get_bmp_servers().items(): + server.start(log_file=os.path.join(tgen.logdir, bmp_name, "bmp.log")) + + +def teardown_module(_mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_convergence(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + result = verify_bgp_convergence_from_running_config(tgen, dut="r1vrf") + assert result is True, "BGP is not converging" + + +def _test_prefixes(policy, step=1): + """ + Setup the BMP monitor policy, Add and withdraw ipv4/v6 prefixes. + Check if the previous actions are logged in the BMP server with the right + message type and the right policy. + """ + tgen = get_topogen() + + prefixes = ["172.31.0.15/32", "2111::1111/128"] + + for type in ("update", "withdraw"): + bmp_update_seq( + tgen.gears["bmp1vrf"], os.path.join(tgen.logdir, "bmp1vrf", "bmp.log") + ) + + # add prefixes + bgp_configure_prefixes( + tgen.gears["r2vrf"], 65502, "unicast", prefixes, update=(type == "update") + ) + + logger.info(f"checking for prefixes {type}") + + for ipver in [4, 6]: + if UPDATE_EXPECTED_JSON: + continue + ref_file = "{}/r1vrf/show-bgp-ipv{}-{}-step{}.json".format( + CWD, ipver, type, step + ) + expected = json.loads(open(ref_file).read()) + + test_func = partial( + topotest.router_json_cmp, + tgen.gears["r1vrf"], + f"show bgp vrf vrf1 ipv{ipver} json", + expected, + ) + _, res = topotest.run_and_expect(test_func, None, count=30, wait=1) + assertmsg = f"r1vrf: BGP IPv{ipver} convergence failed" + assert res is None, assertmsg + + # check + test_func = partial( + bmp_check_for_prefixes, + prefixes, + type, + policy, + step, + tgen.gears["bmp1vrf"], + os.path.join(tgen.logdir, "bmp1vrf", "bmp.log"), + tgen.gears["r1vrf"], + f"{CWD}/bmp1vrf", + UPDATE_EXPECTED_JSON, + LOC_RIB, + ) + success, res = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert success, "Checking the updated prefixes has failed ! %s" % res + + +def test_bmp_server_logging(): + """ + Assert the logging of the bmp server. + """ + + def check_for_log_file(): + tgen = get_topogen() + output = tgen.gears["bmp1vrf"].run( + "ls {}".format(os.path.join(tgen.logdir, "bmp1vrf")) + ) + if "bmp.log" not in output: + return False + return True + + success, _ = topotest.run_and_expect(check_for_log_file, True, count=30, wait=1) + assert success, "The BMP server is not logging" + + +def test_peer_up(): + """ + Checking for BMP peers up messages + """ + + tgen = get_topogen() + peers = ["192.168.0.2", "192:168::2"] + + logger.info("checking for BMP peers up messages") + + test_func = partial( + bmp_check_for_peer_message, + peers, + "peer up", + tgen.gears["bmp1vrf"], + os.path.join(tgen.logdir, "bmp1vrf", "bmp.log"), + ) + success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1) + assert success, "Checking the updated prefixes has been failed !." + + +def test_bmp_bgp_unicast(): + """ + Add/withdraw bgp unicast prefixes and check the bmp logs. + """ + logger.info("*** Unicast prefixes pre-policy logging ***") + _test_prefixes(PRE_POLICY) + logger.info("*** Unicast prefixes post-policy logging ***") + _test_prefixes(POST_POLICY) + logger.info("*** Unicast prefixes loc-rib logging ***") + _test_prefixes(LOC_RIB) + + +def test_peer_down(): + """ + Checking for BMP peers down messages + """ + tgen = get_topogen() + + tgen.gears["r2vrf"].vtysh_cmd("clear bgp *") + + peers = ["192.168.0.2", "192:168::2"] + + logger.info("checking for BMP peers down messages") + + test_func = partial( + bmp_check_for_peer_message, + peers, + "peer down", + tgen.gears["bmp1vrf"], + os.path.join(tgen.logdir, "bmp1vrf", "bmp.log"), + ) + success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1) + assert success, "Checking the updated prefixes has been failed !." + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_bmp_vrf/__init__.py b/tests/topotests/bgp_bmp_vrf/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/tests/topotests/bgp_bmp_vrf/test_bgp_bmp_vrf.py b/tests/topotests/bgp_bmp_vrf/test_bgp_bmp_vrf.py deleted file mode 100644 index d31328bdb659..000000000000 --- a/tests/topotests/bgp_bmp_vrf/test_bgp_bmp_vrf.py +++ /dev/null @@ -1,418 +0,0 @@ -#!/usr/bin/env python -# SPDX-License-Identifier: ISC - -# Copyright 2023 6WIND S.A. -# Authored by Farid Mihoub -# - -""" -test_bgp_bmp.py: Test BGP BMP functionalities - - +------+ +------+ +------+ - | | | | | | - | BMP1 |------------| R1 |---------------| R2 | - | | | | | | - +------+ +------+ +------+ - -Setup two routers R1 and R2 with one link configured with IPv4 and -IPv6 addresses. -Configure BGP in R1 and R2 to exchange prefixes from -the latter to the first router. -Setup a link between R1 and the BMP server, activate the BMP feature in R1 -and ensure the monitored BGP sessions logs are well present on the BMP server. -""" - -from functools import partial -from ipaddress import ip_network -import json -import os -import platform -import pytest -import sys - -# Save the Current Working Directory to find configuration files. -CWD = os.path.dirname(os.path.realpath(__file__)) -sys.path.append(os.path.join("../")) -sys.path.append(os.path.join("../lib/")) - -# pylint: disable=C0413 -# Import topogen and topotest helpers -from lib import topotest -from lib.bgp import verify_bgp_convergence_from_running_config -from lib.topogen import Topogen, TopoRouter, get_topogen -from lib.topolog import logger - -pytestmark = [pytest.mark.bgpd] - -# remember the last sequence number of the logging messages -SEQ = 0 - -PRE_POLICY = "pre-policy" -POST_POLICY = "post-policy" -LOC_RIB = "loc-rib" - -UPDATE_EXPECTED_JSON = False -DEBUG_PCAP = False - - -def build_topo(tgen): - tgen.add_router("r1") - tgen.add_router("r2") - tgen.add_bmp_server("bmp1", ip="192.0.2.10", defaultRoute="via 192.0.2.1") - - switch = tgen.add_switch("s1") - switch.add_link(tgen.gears["r1"]) - switch.add_link(tgen.gears["bmp1"]) - - tgen.add_link(tgen.gears["r1"], tgen.gears["r2"], "r1-eth1", "r2-eth0") - - -def setup_module(mod): - tgen = Topogen(build_topo, mod.__name__) - tgen.start_topology() - - tgen.net["r1"].cmd( - """ -ip link add vrf1 type vrf table 10 -ip link set vrf1 up -ip link set r1-eth1 master vrf1 -""" - ) - - if DEBUG_PCAP: - tgen.gears["r1"].run("rm /tmp/bmp_vrf.pcap") - tgen.gears["r1"].run( - "tcpdump -nni r1-eth0 -s 0 -w /tmp/bmp_vrf.pcap &", stdout=None - ) - - for rname, router in tgen.routers().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)), - "-M bmp", - ) - - tgen.start_router() - - logger.info("starting BMP servers") - for bmp_name, server in tgen.get_bmp_servers().items(): - server.start(log_file=os.path.join(tgen.logdir, bmp_name, "bmp.log")) - - -def teardown_module(_mod): - tgen = get_topogen() - tgen.stop_topology() - - -def test_bgp_convergence(): - tgen = get_topogen() - if tgen.routers_have_failure(): - pytest.skip(tgen.errors) - - result = verify_bgp_convergence_from_running_config(tgen, dut="r1") - assert result is True, "BGP is not converging" - - -def get_bmp_messages(): - """ - Read the BMP logging messages. - """ - messages = [] - tgen = get_topogen() - text_output = tgen.gears["bmp1"].run( - "cat {}".format(os.path.join(tgen.logdir, "bmp1", "bmp.log")) - ) - - for m in text_output.splitlines(): - # some output in the bash can break the message decoding - try: - messages.append(json.loads(m)) - except Exception as e: - logger.warning(str(e) + " message: {}".format(str(m))) - continue - - if not messages: - logger.error("Bad BMP log format, check your BMP server") - - return messages - - -def update_seq(): - global SEQ - - messages = get_bmp_messages() - - if len(messages): - SEQ = messages[-1]["seq"] - - -def update_expected_files(bmp_actual, expected_prefixes, bmp_log_type, policy, step): - tgen = get_topogen() - - with open(f"/tmp/bmp-{bmp_log_type}-{policy}-step{step}.json", "w") as json_file: - json.dump(bmp_actual, json_file, indent=4) - - out = tgen.gears["r1"].vtysh_cmd("show bgp vrf vrf1 ipv4 json", isjson=True) - filtered_out = { - "routes": { - prefix: route_info - for prefix, route_info in out["routes"].items() - if prefix in expected_prefixes - } - } - if bmp_log_type == "withdraw": - for pfx in expected_prefixes: - if "::" in pfx: - continue - filtered_out["routes"][pfx] = None - - # ls /tmp/show*json | while read file; do egrep -v 'prefix|network|metric|ocPrf|version|weight|peerId|vrf|Version|valid|Reason|fe80' $file >$(basename $file); echo >> $(basename $file); done - with open(f"/tmp/show-bgp-ipv4-{bmp_log_type}-step{step}.json", "w") as json_file: - json.dump(filtered_out, json_file, indent=4) - - out = tgen.gears["r1"].vtysh_cmd("show bgp vrf vrf1 ipv6 json", isjson=True) - filtered_out = { - "routes": { - prefix: route_info - for prefix, route_info in out["routes"].items() - if prefix in expected_prefixes - } - } - if bmp_log_type == "withdraw": - for pfx in expected_prefixes: - if "::" not in pfx: - continue - filtered_out["routes"][pfx] = None - - with open(f"/tmp/show-bgp-ipv6-{bmp_log_type}-step{step}.json", "w") as json_file: - json.dump(filtered_out, json_file, indent=4) - - -def check_for_prefixes(expected_prefixes, bmp_log_type, policy, step): - """ - Check for the presence of the given prefixes in the BMP server logs with - the given message type and the set policy. - - """ - global SEQ - - # we care only about the new messages - messages = [ - m for m in sorted(get_bmp_messages(), key=lambda d: d["seq"]) if m["seq"] > SEQ - ] - - # create empty initial files - # for step in $(seq 1); do - # for i in "update" "withdraw"; do - # for j in "pre-policy" "post-policy" "loc-rib"; do - # echo '{"null": {}}'> bmp-$i-$j-step$step.json - # done - # done - # done - - ref_file = f"{CWD}/bmp1/bmp-{bmp_log_type}-{policy}-step{step}.json" - expected = json.loads(open(ref_file).read()) - - # Build actual json from logs - actual = {} - for m in messages: - if ( - "bmp_log_type" in m.keys() - and "ip_prefix" in m.keys() - and m["ip_prefix"] in expected_prefixes - and m["bmp_log_type"] == bmp_log_type - and m["policy"] == policy - ): - policy_dict = actual.setdefault(m["policy"], {}) - bmp_log_type_dict = policy_dict.setdefault(m["bmp_log_type"], {}) - - # Add or update the ip_prefix dictionary with filtered key-value pairs - bmp_log_type_dict[m["ip_prefix"]] = { - k: v - for k, v in sorted(m.items()) - # filter out variable keys - if k not in ["timestamp", "seq", "nxhp_link-local"] - and ( - # When policy is loc-rib, the peer-distinguisher is 0:0 - # for the default VRF or the RD if any or the 0:. - # 0: is used to distinguished. RFC7854 says: "If the - # peer is a "Local Instance Peer", it is set to a unique, - # locally defined value." The value is not tested because it - # is variable. - k != "peer_distinguisher" - or policy != LOC_RIB - or v == "0:0" - or not v.startswith("0:") - ) - } - - # build expected JSON files - if ( - UPDATE_EXPECTED_JSON - and actual - and set(actual.get(policy, {}).get(bmp_log_type, {}).keys()) - == set(expected_prefixes) - ): - update_expected_files(actual, expected_prefixes, bmp_log_type, policy, step) - - return topotest.json_cmp(actual, expected, exact=True) - - -def check_for_peer_message(expected_peers, bmp_log_type): - """ - Check for the presence of a peer up message for the peer - """ - global SEQ - # we care only about the new messages - messages = [ - m for m in sorted(get_bmp_messages(), key=lambda d: d["seq"]) if m["seq"] > SEQ - ] - - # get the list of pairs (prefix, policy, seq) for the given message type - peers = [ - m["peer_ip"] - for m in messages - if "peer_ip" in m.keys() and m["bmp_log_type"] == bmp_log_type - ] - - # check for prefixes - for ep in expected_peers: - if ep not in peers: - msg = "The peer {} is not present in the {} log messages." - logger.debug(msg.format(ep, bmp_log_type)) - return False - - SEQ = messages[-1]["seq"] - return True - - -def configure_prefixes(tgen, node, asn, safi, prefixes, vrf=None, update=True): - """ - Configure the bgp prefixes. - """ - withdraw = "no " if not update else "" - vrf = " vrf {}".format(vrf) if vrf else "" - for p in prefixes: - ip = ip_network(p) - cmd = [ - "conf t\n", - "router bgp {}{}\n".format(asn, vrf), - "address-family ipv{} {}\n".format(ip.version, safi), - "{}network {}\n".format(withdraw, ip), - "exit-address-family\n", - ] - logger.debug("setting prefix: ipv{} {} {}".format(ip.version, safi, ip)) - tgen.gears[node].vtysh_cmd("".join(cmd)) - - -def _test_prefixes(policy, step=1): - """ - Setup the BMP monitor policy, Add and withdraw ipv4/v6 prefixes. - Check if the previous actions are logged in the BMP server with the right - message type and the right policy. - """ - tgen = get_topogen() - - prefixes = ["172.31.0.15/32", "2111::1111/128"] - - for type in ("update", "withdraw"): - update_seq() - - # add prefixes - configure_prefixes( - tgen, "r2", 65502, "unicast", prefixes, update=(type == "update") - ) - - logger.info(f"checking for prefixes {type}") - - for ipver in [4, 6]: - if UPDATE_EXPECTED_JSON: - continue - ref_file = "{}/r1/show-bgp-ipv{}-{}-step{}.json".format( - CWD, ipver, type, step - ) - expected = json.loads(open(ref_file).read()) - - test_func = partial( - topotest.router_json_cmp, - tgen.gears["r1"], - f"show bgp vrf vrf1 ipv{ipver} json", - expected, - ) - _, res = topotest.run_and_expect(test_func, None, count=30, wait=1) - assertmsg = f"r1: BGP IPv{ipver} convergence failed" - assert res is None, assertmsg - - # check - test_func = partial(check_for_prefixes, prefixes, type, policy, step) - success, res = topotest.run_and_expect(test_func, None, count=30, wait=1) - assert success, "Checking the updated prefixes has been failed ! %s" % res - - -def test_bmp_server_logging(): - """ - Assert the logging of the bmp server. - """ - - def check_for_log_file(): - tgen = get_topogen() - output = tgen.gears["bmp1"].run( - "ls {}".format(os.path.join(tgen.logdir, "bmp1")) - ) - if "bmp.log" not in output: - return False - return True - - success, _ = topotest.run_and_expect(check_for_log_file, True, count=30, wait=1) - assert success, "The BMP server is not logging" - - -def test_peer_up(): - """ - Checking for BMP peers up messages - """ - - peers = ["192.168.0.2", "192:168::2"] - - logger.info("checking for BMP peers up messages") - - test_func = partial(check_for_peer_message, peers, "peer up") - success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1) - assert success, "Checking the updated prefixes has been failed !." - - -def test_bmp_bgp_unicast(): - """ - Add/withdraw bgp unicast prefixes and check the bmp logs. - """ - logger.info("*** Unicast prefixes pre-policy logging ***") - _test_prefixes(PRE_POLICY) - logger.info("*** Unicast prefixes post-policy logging ***") - _test_prefixes(POST_POLICY) - logger.info("*** Unicast prefixes loc-rib logging ***") - _test_prefixes(LOC_RIB) - - -def test_peer_down(): - """ - Checking for BMP peers down messages - """ - tgen = get_topogen() - - tgen.gears["r2"].vtysh_cmd("clear bgp *") - - peers = ["192.168.0.2", "192:168::2"] - - logger.info("checking for BMP peers down messages") - - test_func = partial(check_for_peer_message, peers, "peer down") - success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1) - assert success, "Checking the updated prefixes has been failed !." - - -if __name__ == "__main__": - args = ["-s"] + sys.argv[1:] - sys.exit(pytest.main(args)) diff --git a/tests/topotests/lib/bgp.py b/tests/topotests/lib/bgp.py index bcd1c748120f..329c2b54f568 100644 --- a/tests/topotests/lib/bgp.py +++ b/tests/topotests/lib/bgp.py @@ -5638,3 +5638,22 @@ def configure_bgp_soft_configuration(tgen, dut, neighbor_dict, direction): ) ) return True + + +def bgp_configure_prefixes(router, asn, safi, prefixes, vrf=None, update=True): + """ + Configure the bgp prefixes. + """ + withdraw = "no " if not update else "" + vrf = " vrf {}".format(vrf) if vrf else "" + for p in prefixes: + ip = ipaddress.ip_network(p) + cmd = [ + "conf t\n", + f"router bgp {asn}{vrf}\n" + f"address-family ipv{ip.version} {safi}\n" + f"{withdraw}network {ip}\n".format(withdraw, ip), + "exit-address-family\n", + ] + logger.debug(f"setting prefix: ipv{ip.version} {safi} {ip}") + router.vtysh_cmd("".join(cmd)) From 2dc693e808985ed7e15a12dd5a7bb8f71a60a9d3 Mon Sep 17 00:00:00 2001 From: Philippe Guibert Date: Mon, 28 Oct 2024 18:20:13 +0100 Subject: [PATCH 2/7] topotests: fix bmp_collector handling of empty as-path When configuring the bgp_bmp test with an additional peer that sends empty AS-PATH, the bmp collector is stopping: > [2024-10-28 17:41:51] Finished dissecting data from ('192.0.2.1', 33922) > [2024-10-28 17:41:52] Data received from ('192.0.2.1', 33922): length 195 > [2024-10-28 17:41:52] Got message type: > [2024-10-28 17:41:52] unpack_from requires a buffer of at least 2 bytes for unpacking 2 bytes at offset 0 (actual buffer size is 0) > [2024-10-28 17:41:52] TCP session closed with ('192.0.2.1', 33922) > [2024-10-28 17:41:52] Server shutting down on 192.0.2.10:1789 The parser attempts to read an empty AS-path and considers the length value as a length in bytes, whereas RFC mentions this value as definining the number of AS-PAths. Fix this in the parser. Signed-off-by: Philippe Guibert --- .../lib/bmp_collector/bgp/update/path_attributes.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/topotests/lib/bmp_collector/bgp/update/path_attributes.py b/tests/topotests/lib/bmp_collector/bgp/update/path_attributes.py index 3694cb4fe3df..ca49c405d147 100644 --- a/tests/topotests/lib/bmp_collector/bgp/update/path_attributes.py +++ b/tests/topotests/lib/bmp_collector/bgp/update/path_attributes.py @@ -72,6 +72,12 @@ def dissect(cls, data): if path_attr_cls == cls.UNKNOWN_ATTR: return data[offset + attr_len :], None + # RFC1771, 4.3 UPDATE Message Format + # The path segment length is a 1-octet long field containing + # the number of ASs in the path segment value field. + if type_code == PATH_ATTR_TYPE_AS_PATH and attr_len == 0: + return data[offset:], path_attr_cls.dissect(data[offset : offset + 2]) + return data[offset + attr_len :], path_attr_cls.dissect( data[offset : offset + attr_len] ) From 1ea8ba59028e482173e8c540a306155bd39a5430 Mon Sep 17 00:00:00 2001 From: Philippe Guibert Date: Mon, 18 Nov 2024 21:38:45 +0100 Subject: [PATCH 3/7] topotests: bgp_bmp, use unified configuration Use unified configuration procedure. Signed-off-by: Philippe Guibert --- tests/topotests/bgp_bmp/r1/{bgpd.conf => frr.conf} | 9 ++++++++- tests/topotests/bgp_bmp/r1/zebra.conf | 7 ------- tests/topotests/bgp_bmp/r1vrf/{bgpd.conf => frr.conf} | 8 +++++++- tests/topotests/bgp_bmp/r1vrf/zebra.conf | 7 ------- tests/topotests/bgp_bmp/r2/{bgpd.conf => frr.conf} | 8 ++++++++ tests/topotests/bgp_bmp/r2/zebra.conf | 8 -------- tests/topotests/bgp_bmp/r2vrf/{bgpd.conf => frr.conf} | 8 ++++++++ tests/topotests/bgp_bmp/r2vrf/zebra.conf | 8 -------- tests/topotests/bgp_bmp/test_bgp_bmp_1.py | 11 ++++------- tests/topotests/bgp_bmp/test_bgp_bmp_2.py | 11 ++++------- 10 files changed, 39 insertions(+), 46 deletions(-) rename tests/topotests/bgp_bmp/r1/{bgpd.conf => frr.conf} (91%) delete mode 100644 tests/topotests/bgp_bmp/r1/zebra.conf rename tests/topotests/bgp_bmp/r1vrf/{bgpd.conf => frr.conf} (86%) delete mode 100644 tests/topotests/bgp_bmp/r1vrf/zebra.conf rename tests/topotests/bgp_bmp/r2/{bgpd.conf => frr.conf} (87%) delete mode 100644 tests/topotests/bgp_bmp/r2/zebra.conf rename tests/topotests/bgp_bmp/r2vrf/{bgpd.conf => frr.conf} (74%) delete mode 100644 tests/topotests/bgp_bmp/r2vrf/zebra.conf diff --git a/tests/topotests/bgp_bmp/r1/bgpd.conf b/tests/topotests/bgp_bmp/r1/frr.conf similarity index 91% rename from tests/topotests/bgp_bmp/r1/bgpd.conf rename to tests/topotests/bgp_bmp/r1/frr.conf index 485c2170967c..f7cb669b3dd2 100644 --- a/tests/topotests/bgp_bmp/r1/bgpd.conf +++ b/tests/topotests/bgp_bmp/r1/frr.conf @@ -1,3 +1,10 @@ +interface r1-eth0 + ip address 192.0.2.1/24 +! +interface r1-eth1 + ip address 192.168.0.1/24 + ipv6 address 192:168::1/64 +! router bgp 65501 bgp router-id 192.168.0.1 bgp log-neighbor-changes @@ -41,7 +48,7 @@ router bgp 65501 exit-address-family ! router bgp 65501 vrf vrf1 - bgp router_id 192.168.0.1 + bgp router-id 192.168.0.1 bgp log-neighbor-changes address-family ipv4 unicast label vpn export 101 diff --git a/tests/topotests/bgp_bmp/r1/zebra.conf b/tests/topotests/bgp_bmp/r1/zebra.conf deleted file mode 100644 index 0b523c9e18d8..000000000000 --- a/tests/topotests/bgp_bmp/r1/zebra.conf +++ /dev/null @@ -1,7 +0,0 @@ -interface r1-eth0 - ip address 192.0.2.1/24 -! -interface r1-eth1 - ip address 192.168.0.1/24 - ipv6 address 192:168::1/64 -! diff --git a/tests/topotests/bgp_bmp/r1vrf/bgpd.conf b/tests/topotests/bgp_bmp/r1vrf/frr.conf similarity index 86% rename from tests/topotests/bgp_bmp/r1vrf/bgpd.conf rename to tests/topotests/bgp_bmp/r1vrf/frr.conf index 961e20498b2e..cb8a7d2b14e4 100644 --- a/tests/topotests/bgp_bmp/r1vrf/bgpd.conf +++ b/tests/topotests/bgp_bmp/r1vrf/frr.conf @@ -1,3 +1,10 @@ +interface r1vrf-eth0 + ip address 192.0.2.1/24 +! +interface r1vrf-eth1 + ip address 192.168.0.1/24 + ipv6 address 192:168::1/64 +! router bgp 65501 vrf vrf1 bgp router-id 192.168.0.1 bgp log-neighbor-changes @@ -15,7 +22,6 @@ router bgp 65501 vrf vrf1 bmp monitor ipv6 unicast loc-rib exit ! - address-family ipv4 unicast neighbor 192.168.0.2 activate neighbor 192.168.0.2 soft-reconfiguration inbound diff --git a/tests/topotests/bgp_bmp/r1vrf/zebra.conf b/tests/topotests/bgp_bmp/r1vrf/zebra.conf deleted file mode 100644 index a242eadce8a7..000000000000 --- a/tests/topotests/bgp_bmp/r1vrf/zebra.conf +++ /dev/null @@ -1,7 +0,0 @@ -interface r1vrf-eth0 - ip address 192.0.2.1/24 -! -interface r1vrf-eth1 - ip address 192.168.0.1/24 - ipv6 address 192:168::1/64 -! diff --git a/tests/topotests/bgp_bmp/r2/bgpd.conf b/tests/topotests/bgp_bmp/r2/frr.conf similarity index 87% rename from tests/topotests/bgp_bmp/r2/bgpd.conf rename to tests/topotests/bgp_bmp/r2/frr.conf index 40e2cd5bbcb9..250071f48477 100644 --- a/tests/topotests/bgp_bmp/r2/bgpd.conf +++ b/tests/topotests/bgp_bmp/r2/frr.conf @@ -1,3 +1,11 @@ +interface r2-eth0 + ip address 192.168.0.2/24 + ipv6 address 192:168::2/64 +! +interface r2-eth1 + ip address 172.31.0.2/24 + ipv6 address 172:31::2/64 +! router bgp 65502 bgp router-id 192.168.0.2 bgp log-neighbor-changes diff --git a/tests/topotests/bgp_bmp/r2/zebra.conf b/tests/topotests/bgp_bmp/r2/zebra.conf deleted file mode 100644 index 9d82bfe2df5c..000000000000 --- a/tests/topotests/bgp_bmp/r2/zebra.conf +++ /dev/null @@ -1,8 +0,0 @@ -interface r2-eth0 - ip address 192.168.0.2/24 - ipv6 address 192:168::2/64 -! -interface r2-eth1 - ip address 172.31.0.2/24 - ipv6 address 172:31::2/64 -! diff --git a/tests/topotests/bgp_bmp/r2vrf/bgpd.conf b/tests/topotests/bgp_bmp/r2vrf/frr.conf similarity index 74% rename from tests/topotests/bgp_bmp/r2vrf/bgpd.conf rename to tests/topotests/bgp_bmp/r2vrf/frr.conf index 7c8255a17563..5268190dec21 100644 --- a/tests/topotests/bgp_bmp/r2vrf/bgpd.conf +++ b/tests/topotests/bgp_bmp/r2vrf/frr.conf @@ -1,3 +1,11 @@ +interface r2vrf-eth0 + ip address 192.168.0.2/24 + ipv6 address 192:168::2/64 +! +interface r2vrf-eth1 + ip address 172.31.0.2/24 + ipv6 address 172:31::2/64 +! router bgp 65502 bgp router-id 192.168.0.2 bgp log-neighbor-changes diff --git a/tests/topotests/bgp_bmp/r2vrf/zebra.conf b/tests/topotests/bgp_bmp/r2vrf/zebra.conf deleted file mode 100644 index 9a8da2d95fbd..000000000000 --- a/tests/topotests/bgp_bmp/r2vrf/zebra.conf +++ /dev/null @@ -1,8 +0,0 @@ -interface r2vrf-eth0 - ip address 192.168.0.2/24 - ipv6 address 192:168::2/64 -! -interface r2vrf-eth1 - ip address 172.31.0.2/24 - ipv6 address 172:31::2/64 -! diff --git a/tests/topotests/bgp_bmp/test_bgp_bmp_1.py b/tests/topotests/bgp_bmp/test_bgp_bmp_1.py index cc7bc31041d9..493f357f4b4b 100644 --- a/tests/topotests/bgp_bmp/test_bgp_bmp_1.py +++ b/tests/topotests/bgp_bmp/test_bgp_bmp_1.py @@ -79,13 +79,10 @@ def setup_module(mod): ) for rname, router in tgen.routers().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)), - "-M bmp", + logger.info("Loading router %s" % rname) + router.load_frr_config( + os.path.join(CWD, "{}/frr.conf".format(rname)), + [(TopoRouter.RD_ZEBRA, None), (TopoRouter.RD_BGP, "-M bmp")], ) tgen.start_router() diff --git a/tests/topotests/bgp_bmp/test_bgp_bmp_2.py b/tests/topotests/bgp_bmp/test_bgp_bmp_2.py index 97cc98962bb6..82d3824a1b93 100644 --- a/tests/topotests/bgp_bmp/test_bgp_bmp_2.py +++ b/tests/topotests/bgp_bmp/test_bgp_bmp_2.py @@ -91,13 +91,10 @@ def setup_module(mod): ) for rname, router in tgen.routers().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)), - "-M bmp", + logger.info("Loading router %s" % rname) + router.load_frr_config( + os.path.join(CWD, "{}/frr.conf".format(rname)), + [(TopoRouter.RD_ZEBRA, None), (TopoRouter.RD_BGP, "-M bmp")], ) tgen.start_router() From bbf8ee1769ec0ad1d614d243cf079f830ed97d84 Mon Sep 17 00:00:00 2001 From: Louis Scalbert Date: Wed, 20 Nov 2024 11:23:51 +0100 Subject: [PATCH 4/7] tests: add py extension to bmpserver to help identify the file type. And apply black. Signed-off-by: Louis Scalbert --- tests/topotests/lib/bmp_collector/{bmpserver => bmpserver.py} | 4 +--- tests/topotests/lib/topogen.py | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) rename tests/topotests/lib/bmp_collector/{bmpserver => bmpserver.py} (95%) diff --git a/tests/topotests/lib/bmp_collector/bmpserver b/tests/topotests/lib/bmp_collector/bmpserver.py similarity index 95% rename from tests/topotests/lib/bmp_collector/bmpserver rename to tests/topotests/lib/bmp_collector/bmpserver.py index 56d85fc74b30..1f8c12cdd5cf 100755 --- a/tests/topotests/lib/bmp_collector/bmpserver +++ b/tests/topotests/lib/bmp_collector/bmpserver.py @@ -80,9 +80,7 @@ def main(): while len(data) > BMPMsg.MIN_LEN: data = BMPMsg.dissect(data, log_file=LOG_FILE) - timestamp_print( - f"Finished dissecting data from {client_address}" - ) + timestamp_print(f"Finished dissecting data from {client_address}") except Exception as e: timestamp_print(f"{e}") diff --git a/tests/topotests/lib/topogen.py b/tests/topotests/lib/topogen.py index 4d7c56423e36..dce83d5ef5a3 100644 --- a/tests/topotests/lib/topogen.py +++ b/tests/topotests/lib/topogen.py @@ -1296,7 +1296,7 @@ def start(self, log_file=None): with open(log_err, "w") as err: self.run( - "{}/bmp_collector/bmpserver -a {} -p {} {}&".format( + "{}/bmp_collector/bmpserver.py -a {} -p {} {}&".format( CWD, self.ip, self.port, log_arg ), stdout=None, @@ -1304,7 +1304,7 @@ def start(self, log_file=None): ) def stop(self): - self.run("pkill -f bmpserver") + self.run("pkill -f bmpserver.py") return "" From b12e04fdf1e3de3c03e5efc58bd4a58f893f5717 Mon Sep 17 00:00:00 2001 From: Louis Scalbert Date: Wed, 20 Nov 2024 11:24:13 +0100 Subject: [PATCH 5/7] tests: fix bmp test parallelization Multiple BMP tests can run in parallel but, when one instance ends, it kills the BMP server process of all BMP tests. Save the PID of a BMP server and only kill it at the end. Link: https://github.com/FRRouting/frr/issues/17465 Fixes: 875511c466 ("topotests: add basic bmp collector") Signed-off-by: Louis Scalbert --- .../topotests/lib/bmp_collector/bmpserver.py | 78 ++++++++++++++++++- tests/topotests/lib/topogen.py | 7 +- 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/tests/topotests/lib/bmp_collector/bmpserver.py b/tests/topotests/lib/bmp_collector/bmpserver.py index 1f8c12cdd5cf..a7c9743c9604 100755 --- a/tests/topotests/lib/bmp_collector/bmpserver.py +++ b/tests/topotests/lib/bmp_collector/bmpserver.py @@ -7,6 +7,7 @@ import argparse # XXX: something more reliable should be used "Twisted" a great choice. +import os import signal import socket import sys @@ -20,11 +21,11 @@ # Global variable to track shutdown signal shutdown = False - parser = argparse.ArgumentParser() parser.add_argument("-a", "--address", type=str, default="0.0.0.0") parser.add_argument("-p", "--port", type=int, default=1789) parser.add_argument("-l", "--logfile", type=str, default="/var/log/bmp.log") +parser.add_argument("-r", "--pidfile", type=str, default="/var/run/bmp.pid") def handle_signal(signum, frame): @@ -40,6 +41,74 @@ def timestamp_print(message, file=sys.stderr): print(f"[{current_time}] {message}", file=file) +def check_pid(pid): + if pid < 0: # user input error + return False + if pid == 0: # all processes + return False + try: + os.kill(pid, 0) + return True + except OSError as err: + if err.errno == errno.EPERM: # a process we were denied access to + return True + if err.errno == errno.ESRCH: # No such process + return False + # should never happen + return False + + +def savepid(): + ownid = os.getpid() + + flags = os.O_CREAT | os.O_EXCL | os.O_WRONLY + mode = ((os.R_OK | os.W_OK) << 6) | (os.R_OK << 3) | os.R_OK + + try: + fd = os.open(pid_file, flags, mode) + except OSError: + try: + pid = open(pid_file, "r").readline().strip() + if check_pid(int(pid)): + timestamp_print( + "PID file already exists and program still running %s\n" % pid_file + ) + return False + else: + # If pid is not running, reopen file without O_EXCL + fd = os.open(pid_file, flags ^ os.O_EXCL, mode) + except (OSError, IOError, ValueError): + timestamp_print( + "issue accessing PID file %s (most likely permission or ownership)\n" + % pid_file + ) + return False + + try: + f = os.fdopen(fd, "w") + line = "%d\n" % ownid + f.write(line) + f.close() + saved_pid = True + except IOError: + timestamp_print("Can not create PID file %s\n" % pid_file) + return False + timestamp_print("Created PID file %s with value %d\n" % (pid_file, ownid)) + return True + + +def removepid(): + try: + os.remove(pid_file) + except OSError as exc: + if exc.errno == errno.ENOENT: + pass + else: + timestamp_print("Can not remove PID file %s\n" % pid_file) + return + timestamp_print("Removed PID file %s\n" % pid_file) + + def main(): global shutdown @@ -51,8 +120,13 @@ def main(): ADDRESS, PORT = args.address, args.port LOG_FILE = args.logfile + global pid_file + pid_file = args.pidfile + timestamp_print(f"Starting bmpserver on {args.address}:{args.port}") + savepid() + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: @@ -97,6 +171,7 @@ def main(): timestamp_print(f"{e}") finally: timestamp_print(f"Server shutting down on {ADDRESS}:{PORT}") + removepid() if __name__ == "__main__": @@ -104,4 +179,5 @@ def main(): sys.exit(main()) except KeyboardInterrupt: logging.info("BMP server was interrupted and is shutting down.") + removepid() sys.exit(0) diff --git a/tests/topotests/lib/topogen.py b/tests/topotests/lib/topogen.py index dce83d5ef5a3..0a9a84a4bb07 100644 --- a/tests/topotests/lib/topogen.py +++ b/tests/topotests/lib/topogen.py @@ -1293,18 +1293,19 @@ def start(self, log_file=None): log_err = os.path.join(log_dir, "bmpserver.log") log_arg = "-l {}".format(log_file) if log_file else "" + self.pid_file = os.path.join(log_dir, "bmpserver.pid") with open(log_err, "w") as err: self.run( - "{}/bmp_collector/bmpserver.py -a {} -p {} {}&".format( - CWD, self.ip, self.port, log_arg + "{}/bmp_collector/bmpserver.py -a {} -p {} -r {} {}&".format( + CWD, self.ip, self.port, self.pid_file, log_arg ), stdout=None, stderr=err, ) def stop(self): - self.run("pkill -f bmpserver.py") + self.run(f"kill $(cat {self.pid_file}") return "" From f2eb14d381b7e490c0ed1394d1955fa860d9b018 Mon Sep 17 00:00:00 2001 From: Louis Scalbert Date: Thu, 21 Nov 2024 11:29:53 +0100 Subject: [PATCH 6/7] tests: save bmp pcap in logdir instead of /tmp DEBUG_PCAP can be set True to manually enable pcap debugging when running bmp tests. Save bmp pcap in logdir (ie. /tmp/topotests/bgp_bmp.bgp_bmp_X/ instead of /tmp. Signed-off-by: Louis Scalbert Signed-off-by: Philippe Guibert --- tests/topotests/bgp_bmp/test_bgp_bmp_1.py | 4 ++-- tests/topotests/bgp_bmp/test_bgp_bmp_2.py | 4 ++-- tests/topotests/lib/bmp_collector/bmpserver.py | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/topotests/bgp_bmp/test_bgp_bmp_1.py b/tests/topotests/bgp_bmp/test_bgp_bmp_1.py index 493f357f4b4b..9fc8e127720f 100644 --- a/tests/topotests/bgp_bmp/test_bgp_bmp_1.py +++ b/tests/topotests/bgp_bmp/test_bgp_bmp_1.py @@ -73,9 +73,9 @@ def setup_module(mod): tgen.start_topology() if DEBUG_PCAP: - tgen.gears["r1"].run("rm /tmp/bmp.pcap") + pcap_file = os.path.join(tgen.logdir, "r1/bmp.pcap") tgen.gears["r1"].run( - "tcpdump -nni r1-eth0 -s 0 -w /tmp/bmp.pcap &", stdout=None + "tcpdump -nni r1-eth0 -s 0 -w {} &".format(pcap_file), stdout=None ) for rname, router in tgen.routers().items(): diff --git a/tests/topotests/bgp_bmp/test_bgp_bmp_2.py b/tests/topotests/bgp_bmp/test_bgp_bmp_2.py index 82d3824a1b93..21e4ce8abeae 100644 --- a/tests/topotests/bgp_bmp/test_bgp_bmp_2.py +++ b/tests/topotests/bgp_bmp/test_bgp_bmp_2.py @@ -85,9 +85,9 @@ def setup_module(mod): ) bmp_reset_seq() if DEBUG_PCAP: - tgen.gears["r1vrf"].run("rm /tmp/bmp_vrf.pcap") + pcap_file = os.path.join(tgen.logdir, "r1vrf/bmp.pcap") tgen.gears["r1vrf"].run( - "tcpdump -nni r1vrf-eth0 -s 0 -w /tmp/bmp_vrf.pcap &", stdout=None + "tcpdump -nni r1vrf-eth0 -s 0 -w {} &".format(pcap_file), stdout=None ) for rname, router in tgen.routers().items(): diff --git a/tests/topotests/lib/bmp_collector/bmpserver.py b/tests/topotests/lib/bmp_collector/bmpserver.py index a7c9743c9604..c42c3875633f 100755 --- a/tests/topotests/lib/bmp_collector/bmpserver.py +++ b/tests/topotests/lib/bmp_collector/bmpserver.py @@ -5,6 +5,8 @@ # Authored by Farid Mihoub # import argparse +import errno +import logging # XXX: something more reliable should be used "Twisted" a great choice. import os From d1301f1c2a891704df8a2af56fbd4bbc7c08562b Mon Sep 17 00:00:00 2001 From: Philippe Guibert Date: Mon, 2 Dec 2024 17:07:14 +0100 Subject: [PATCH 7/7] topotests: save bgp_bmp json temp files to bmp log folder instead of /tmp Some temporary files are hardwritten in /tmp folder. Use the bmp log folder instead. Replace the bmp log file argument with bmp log folder. Signed-off-by: Philippe Guibert --- tests/topotests/bgp_bmp/bgpbmp.py | 33 ++++++++++++++++++----- tests/topotests/bgp_bmp/test_bgp_bmp_1.py | 2 +- tests/topotests/bgp_bmp/test_bgp_bmp_2.py | 2 +- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/tests/topotests/bgp_bmp/bgpbmp.py b/tests/topotests/bgp_bmp/bgpbmp.py index 950ffef2f71f..41995e2b5e53 100644 --- a/tests/topotests/bgp_bmp/bgpbmp.py +++ b/tests/topotests/bgp_bmp/bgpbmp.py @@ -49,11 +49,19 @@ def bmp_update_seq(bmp_collector, bmp_log_file): def bmp_update_expected_files( - bmp_actual, expected_prefixes, bmp_log_type, policy, step, bmp_client + bmp_actual, + expected_prefixes, + bmp_log_type, + policy, + step, + bmp_client, + bmp_log_folder, ): tgen = get_topogen() - with open(f"/tmp/bmp-{bmp_log_type}-{policy}-step{step}.json", "w") as json_file: + with open( + f"{bmp_log_folder}/tmp/bmp-{bmp_log_type}-{policy}-step{step}.json", "w" + ) as json_file: json.dump(bmp_actual, json_file, indent=4) out = bmp_client.vtysh_cmd("show bgp vrf vrf1 ipv4 json", isjson=True) @@ -70,8 +78,10 @@ def bmp_update_expected_files( continue filtered_out["routes"][pfx] = None - # ls /tmp/show*json | while read file; do egrep -v 'prefix|network|metric|ocPrf|version|weight|peerId|vrf|Version|valid|Reason|fe80' $file >$(basename $file); echo >> $(basename $file); done - with open(f"/tmp/show-bgp-ipv4-{bmp_log_type}-step{step}.json", "w") as json_file: + # ls {bmp_log_folder}/tmp/show*json | while read file; do egrep -v 'prefix|network|metric|ocPrf|version|weight|peerId|vrf|Version|valid|Reason|fe80' $file >$(basename $file); echo >> $(basename $file); done + with open( + f"{bmp_log_folder}/tmp/show-bgp-ipv4-{bmp_log_type}-step{step}.json", "w" + ) as json_file: json.dump(filtered_out, json_file, indent=4) out = tgen.gears["r1"].vtysh_cmd("show bgp vrf vrf1 ipv6 json", isjson=True) @@ -88,7 +98,9 @@ def bmp_update_expected_files( continue filtered_out["routes"][pfx] = None - with open(f"/tmp/show-bgp-ipv6-{bmp_log_type}-step{step}.json", "w") as json_file: + with open( + f"{bmp_log_folder}/tmp/show-bgp-ipv6-{bmp_log_type}-step{step}.json", "w" + ) as json_file: json.dump(filtered_out, json_file, indent=4) @@ -98,7 +110,7 @@ def bmp_check_for_prefixes( policy, step, bmp_collector, - bmp_log_file, + bmp_log_folder, bmp_client, expected_json_path, update_expected_json, @@ -111,6 +123,7 @@ def bmp_check_for_prefixes( """ global SEQ + bmp_log_file = f"{bmp_log_folder}/bmp.log" # we care only about the new messages messages = [ m @@ -173,7 +186,13 @@ def bmp_check_for_prefixes( == set(expected_prefixes) ): bmp_update_expected_files( - actual, expected_prefixes, bmp_log_type, policy, step, bmp_client + actual, + expected_prefixes, + bmp_log_type, + policy, + step, + bmp_client, + bmp_log_folder, ) return topotest.json_cmp(actual, expected, exact=True) diff --git a/tests/topotests/bgp_bmp/test_bgp_bmp_1.py b/tests/topotests/bgp_bmp/test_bgp_bmp_1.py index 9fc8e127720f..61428634414c 100644 --- a/tests/topotests/bgp_bmp/test_bgp_bmp_1.py +++ b/tests/topotests/bgp_bmp/test_bgp_bmp_1.py @@ -158,7 +158,7 @@ def _test_prefixes(policy, vrf=None, step=0): policy, step, tgen.gears["bmp1"], - os.path.join(tgen.logdir, "bmp1", "bmp.log"), + os.path.join(tgen.logdir, "bmp1"), tgen.gears["r1"], f"{CWD}/bmp1", UPDATE_EXPECTED_JSON, diff --git a/tests/topotests/bgp_bmp/test_bgp_bmp_2.py b/tests/topotests/bgp_bmp/test_bgp_bmp_2.py index 21e4ce8abeae..b45452e7c475 100644 --- a/tests/topotests/bgp_bmp/test_bgp_bmp_2.py +++ b/tests/topotests/bgp_bmp/test_bgp_bmp_2.py @@ -166,7 +166,7 @@ def _test_prefixes(policy, step=1): policy, step, tgen.gears["bmp1vrf"], - os.path.join(tgen.logdir, "bmp1vrf", "bmp.log"), + os.path.join(tgen.logdir, "bmp1vrf"), tgen.gears["r1vrf"], f"{CWD}/bmp1vrf", UPDATE_EXPECTED_JSON,