mirror of
https://git.proxmox.com/git/mirror_frr
synced 2025-07-09 16:52:25 +00:00
Merge pull request #17306 from pguibert6WIND/bmp_test_factorise_plus_fix
BMP test rework
This commit is contained in:
commit
6e1eeed507
233
tests/topotests/bgp_bmp/bgpbmp.py
Normal file
233
tests/topotests/bgp_bmp/bgpbmp.py
Normal 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
|
@ -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
|
@ -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
|
|
||||||
!
|
|
@ -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
|
@ -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
|
||||||
}
|
}
|
@ -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
|
@ -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
|
@ -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
|
|
||||||
!
|
|
@ -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
|
@ -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))
|
|
257
tests/topotests/bgp_bmp/test_bgp_bmp_1.py
Normal file
257
tests/topotests/bgp_bmp/test_bgp_bmp_1.py
Normal 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))
|
255
tests/topotests/bgp_bmp/test_bgp_bmp_2.py
Normal file
255
tests/topotests/bgp_bmp/test_bgp_bmp_2.py
Normal 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))
|
@ -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
|
|
||||||
!
|
|
@ -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
|
|
||||||
!
|
|
@ -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))
|
|
@ -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))
|
||||||
|
@ -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]
|
||||||
)
|
)
|
||||||
|
@ -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)
|
@ -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 ""
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user