Merge pull request #17306 from pguibert6WIND/bmp_test_factorise_plus_fix

BMP test rework
This commit is contained in:
Russ White 2024-12-03 08:42:24 -05:00 committed by GitHub
commit 6e1eeed507
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 885 additions and 936 deletions

View File

@ -0,0 +1,233 @@
#!/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,
bmp_log_folder,
):
tgen = get_topogen()
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)
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 {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)
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"{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)
def bmp_check_for_prefixes(
expected_prefixes,
bmp_log_type,
policy,
step,
bmp_collector,
bmp_log_folder,
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
bmp_log_file = f"{bmp_log_folder}/bmp.log"
# 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:<vrf_id>.
# 0:<vrf_id> 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,
bmp_log_folder,
)
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

View File

@ -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 router bgp 65501
bgp router-id 192.168.0.1 bgp router-id 192.168.0.1
bgp log-neighbor-changes bgp log-neighbor-changes
@ -41,7 +48,7 @@ router bgp 65501
exit-address-family exit-address-family
! !
router bgp 65501 vrf vrf1 router bgp 65501 vrf vrf1
bgp router_id 192.168.0.1 bgp router-id 192.168.0.1
bgp log-neighbor-changes bgp log-neighbor-changes
address-family ipv4 unicast address-family ipv4 unicast
label vpn export 101 label vpn export 101

View File

@ -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
!

View File

@ -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 router bgp 65501 vrf vrf1
bgp router-id 192.168.0.1 bgp router-id 192.168.0.1
bgp log-neighbor-changes bgp log-neighbor-changes
@ -15,7 +22,6 @@ router bgp 65501 vrf vrf1
bmp monitor ipv6 unicast loc-rib bmp monitor ipv6 unicast loc-rib
exit exit
! !
address-family ipv4 unicast address-family ipv4 unicast
neighbor 192.168.0.2 activate neighbor 192.168.0.2 activate
neighbor 192.168.0.2 soft-reconfiguration inbound neighbor 192.168.0.2 soft-reconfiguration inbound

View File

@ -9,7 +9,6 @@
"nexthops": [ "nexthops": [
{ {
"ip": "192.168.0.2", "ip": "192.168.0.2",
"hostname": "r2",
"afi": "ipv4", "afi": "ipv4",
"used": true "used": true
} }

View File

@ -9,12 +9,10 @@
"nexthops": [ "nexthops": [
{ {
"ip": "192:168::2", "ip": "192:168::2",
"hostname": "r2",
"afi": "ipv6", "afi": "ipv6",
"scope": "global" "scope": "global"
}, },
{ {
"hostname": "r2",
"afi": "ipv6", "afi": "ipv6",
"scope": "link-local", "scope": "link-local",
"used": true "used": true

View File

@ -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 router bgp 65502
bgp router-id 192.168.0.2 bgp router-id 192.168.0.2
bgp log-neighbor-changes bgp log-neighbor-changes

View File

@ -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
!

View File

@ -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 router bgp 65502
bgp router-id 192.168.0.2 bgp router-id 192.168.0.2
bgp log-neighbor-changes bgp log-neighbor-changes

View File

@ -1,476 +0,0 @@
#!/usr/bin/env python
# SPDX-License-Identifier: ISC
# Copyright 2023 6WIND S.A.
# Authored by Farid Mihoub <farid.mihoub@6wind.com>
#
"""
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:<vrf_id>.
# 0:<vrf_id> 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))

View File

@ -0,0 +1,257 @@
#!/usr/bin/env python
# SPDX-License-Identifier: ISC
# Copyright 2023 6WIND S.A.
# Authored by Farid Mihoub <farid.mihoub@6wind.com>
#
"""
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:
pcap_file = os.path.join(tgen.logdir, "r1/bmp.pcap")
tgen.gears["r1"].run(
"tcpdump -nni r1-eth0 -s 0 -w {} &".format(pcap_file), stdout=None
)
for rname, router in tgen.routers().items():
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()
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"),
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))

View File

@ -0,0 +1,255 @@
#!/usr/bin/env python
# SPDX-License-Identifier: ISC
# Copyright 2023 6WIND S.A.
# Authored by Farid Mihoub <farid.mihoub@6wind.com>
#
"""
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:
pcap_file = os.path.join(tgen.logdir, "r1vrf/bmp.pcap")
tgen.gears["r1vrf"].run(
"tcpdump -nni r1vrf-eth0 -s 0 -w {} &".format(pcap_file), stdout=None
)
for rname, router in tgen.routers().items():
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()
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"),
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))

View File

@ -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
!

View File

@ -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
!

View File

@ -1,418 +0,0 @@
#!/usr/bin/env python
# SPDX-License-Identifier: ISC
# Copyright 2023 6WIND S.A.
# Authored by Farid Mihoub <farid.mihoub@6wind.com>
#
"""
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:<vrf_id>.
# 0:<vrf_id> 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))

View File

@ -5638,3 +5638,22 @@ def configure_bgp_soft_configuration(tgen, dut, neighbor_dict, direction):
) )
) )
return True 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))

View File

@ -72,6 +72,12 @@ class PathAttribute:
if path_attr_cls == cls.UNKNOWN_ATTR: if path_attr_cls == cls.UNKNOWN_ATTR:
return data[offset + attr_len :], None 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( return data[offset + attr_len :], path_attr_cls.dissect(
data[offset : offset + attr_len] data[offset : offset + attr_len]
) )

View File

@ -5,8 +5,11 @@
# Authored by Farid Mihoub <farid.mihoub@6wind.com> # Authored by Farid Mihoub <farid.mihoub@6wind.com>
# #
import argparse import argparse
import errno
import logging
# XXX: something more reliable should be used "Twisted" a great choice. # XXX: something more reliable should be used "Twisted" a great choice.
import os
import signal import signal
import socket import socket
import sys import sys
@ -20,11 +23,11 @@ BGP_MAX_SIZE = 4096
# Global variable to track shutdown signal # Global variable to track shutdown signal
shutdown = False shutdown = False
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("-a", "--address", type=str, default="0.0.0.0") parser.add_argument("-a", "--address", type=str, default="0.0.0.0")
parser.add_argument("-p", "--port", type=int, default=1789) parser.add_argument("-p", "--port", type=int, default=1789)
parser.add_argument("-l", "--logfile", type=str, default="/var/log/bmp.log") 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): def handle_signal(signum, frame):
@ -40,6 +43,74 @@ def timestamp_print(message, file=sys.stderr):
print(f"[{current_time}] {message}", file=file) 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(): def main():
global shutdown global shutdown
@ -51,8 +122,13 @@ def main():
ADDRESS, PORT = args.address, args.port ADDRESS, PORT = args.address, args.port
LOG_FILE = args.logfile LOG_FILE = args.logfile
global pid_file
pid_file = args.pidfile
timestamp_print(f"Starting bmpserver on {args.address}:{args.port}") timestamp_print(f"Starting bmpserver on {args.address}:{args.port}")
savepid()
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try: try:
@ -80,9 +156,7 @@ def main():
while len(data) > BMPMsg.MIN_LEN: while len(data) > BMPMsg.MIN_LEN:
data = BMPMsg.dissect(data, log_file=LOG_FILE) data = BMPMsg.dissect(data, log_file=LOG_FILE)
timestamp_print( timestamp_print(f"Finished dissecting data from {client_address}")
f"Finished dissecting data from {client_address}"
)
except Exception as e: except Exception as e:
timestamp_print(f"{e}") timestamp_print(f"{e}")
@ -99,6 +173,7 @@ def main():
timestamp_print(f"{e}") timestamp_print(f"{e}")
finally: finally:
timestamp_print(f"Server shutting down on {ADDRESS}:{PORT}") timestamp_print(f"Server shutting down on {ADDRESS}:{PORT}")
removepid()
if __name__ == "__main__": if __name__ == "__main__":
@ -106,4 +181,5 @@ if __name__ == "__main__":
sys.exit(main()) sys.exit(main())
except KeyboardInterrupt: except KeyboardInterrupt:
logging.info("BMP server was interrupted and is shutting down.") logging.info("BMP server was interrupted and is shutting down.")
removepid()
sys.exit(0) sys.exit(0)

View File

@ -1293,18 +1293,19 @@ class TopoBMPCollector(TopoHost):
log_err = os.path.join(log_dir, "bmpserver.log") log_err = os.path.join(log_dir, "bmpserver.log")
log_arg = "-l {}".format(log_file) if log_file else "" 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: with open(log_err, "w") as err:
self.run( self.run(
"{}/bmp_collector/bmpserver -a {} -p {} {}&".format( "{}/bmp_collector/bmpserver.py -a {} -p {} -r {} {}&".format(
CWD, self.ip, self.port, log_arg CWD, self.ip, self.port, self.pid_file, log_arg
), ),
stdout=None, stdout=None,
stderr=err, stderr=err,
) )
def stop(self): def stop(self):
self.run("pkill -f bmpserver") self.run(f"kill $(cat {self.pid_file}")
return "" return ""