mirror of
https://git.proxmox.com/git/mirror_frr
synced 2025-07-09 09:47:11 +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
|
||||
bgp router-id 192.168.0.1
|
||||
bgp log-neighbor-changes
|
||||
@ -41,7 +48,7 @@ router bgp 65501
|
||||
exit-address-family
|
||||
!
|
||||
router bgp 65501 vrf vrf1
|
||||
bgp router_id 192.168.0.1
|
||||
bgp router-id 192.168.0.1
|
||||
bgp log-neighbor-changes
|
||||
address-family ipv4 unicast
|
||||
label vpn export 101
|
@ -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
|
||||
bgp router-id 192.168.0.1
|
||||
bgp log-neighbor-changes
|
||||
@ -15,7 +22,6 @@ router bgp 65501 vrf vrf1
|
||||
bmp monitor ipv6 unicast loc-rib
|
||||
exit
|
||||
!
|
||||
|
||||
address-family ipv4 unicast
|
||||
neighbor 192.168.0.2 activate
|
||||
neighbor 192.168.0.2 soft-reconfiguration inbound
|
@ -9,7 +9,6 @@
|
||||
"nexthops": [
|
||||
{
|
||||
"ip": "192.168.0.2",
|
||||
"hostname": "r2",
|
||||
"afi": "ipv4",
|
||||
"used": true
|
||||
}
|
@ -9,12 +9,10 @@
|
||||
"nexthops": [
|
||||
{
|
||||
"ip": "192:168::2",
|
||||
"hostname": "r2",
|
||||
"afi": "ipv6",
|
||||
"scope": "global"
|
||||
},
|
||||
{
|
||||
"hostname": "r2",
|
||||
"afi": "ipv6",
|
||||
"scope": "link-local",
|
||||
"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
|
||||
bgp router-id 192.168.0.2
|
||||
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
|
||||
bgp router-id 192.168.0.2
|
||||
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
|
||||
|
||||
|
||||
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:
|
||||
return data[offset + attr_len :], None
|
||||
|
||||
# RFC1771, 4.3 UPDATE Message Format
|
||||
# The path segment length is a 1-octet long field containing
|
||||
# the number of ASs in the path segment value field.
|
||||
if type_code == PATH_ATTR_TYPE_AS_PATH and attr_len == 0:
|
||||
return data[offset:], path_attr_cls.dissect(data[offset : offset + 2])
|
||||
|
||||
return data[offset + attr_len :], path_attr_cls.dissect(
|
||||
data[offset : offset + attr_len]
|
||||
)
|
||||
|
@ -5,8 +5,11 @@
|
||||
# Authored by Farid Mihoub <farid.mihoub@6wind.com>
|
||||
#
|
||||
import argparse
|
||||
import errno
|
||||
import logging
|
||||
|
||||
# XXX: something more reliable should be used "Twisted" a great choice.
|
||||
import os
|
||||
import signal
|
||||
import socket
|
||||
import sys
|
||||
@ -20,11 +23,11 @@ BGP_MAX_SIZE = 4096
|
||||
# Global variable to track shutdown signal
|
||||
shutdown = False
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-a", "--address", type=str, default="0.0.0.0")
|
||||
parser.add_argument("-p", "--port", type=int, default=1789)
|
||||
parser.add_argument("-l", "--logfile", type=str, default="/var/log/bmp.log")
|
||||
parser.add_argument("-r", "--pidfile", type=str, default="/var/run/bmp.pid")
|
||||
|
||||
|
||||
def handle_signal(signum, frame):
|
||||
@ -40,6 +43,74 @@ def timestamp_print(message, file=sys.stderr):
|
||||
print(f"[{current_time}] {message}", file=file)
|
||||
|
||||
|
||||
def check_pid(pid):
|
||||
if pid < 0: # user input error
|
||||
return False
|
||||
if pid == 0: # all processes
|
||||
return False
|
||||
try:
|
||||
os.kill(pid, 0)
|
||||
return True
|
||||
except OSError as err:
|
||||
if err.errno == errno.EPERM: # a process we were denied access to
|
||||
return True
|
||||
if err.errno == errno.ESRCH: # No such process
|
||||
return False
|
||||
# should never happen
|
||||
return False
|
||||
|
||||
|
||||
def savepid():
|
||||
ownid = os.getpid()
|
||||
|
||||
flags = os.O_CREAT | os.O_EXCL | os.O_WRONLY
|
||||
mode = ((os.R_OK | os.W_OK) << 6) | (os.R_OK << 3) | os.R_OK
|
||||
|
||||
try:
|
||||
fd = os.open(pid_file, flags, mode)
|
||||
except OSError:
|
||||
try:
|
||||
pid = open(pid_file, "r").readline().strip()
|
||||
if check_pid(int(pid)):
|
||||
timestamp_print(
|
||||
"PID file already exists and program still running %s\n" % pid_file
|
||||
)
|
||||
return False
|
||||
else:
|
||||
# If pid is not running, reopen file without O_EXCL
|
||||
fd = os.open(pid_file, flags ^ os.O_EXCL, mode)
|
||||
except (OSError, IOError, ValueError):
|
||||
timestamp_print(
|
||||
"issue accessing PID file %s (most likely permission or ownership)\n"
|
||||
% pid_file
|
||||
)
|
||||
return False
|
||||
|
||||
try:
|
||||
f = os.fdopen(fd, "w")
|
||||
line = "%d\n" % ownid
|
||||
f.write(line)
|
||||
f.close()
|
||||
saved_pid = True
|
||||
except IOError:
|
||||
timestamp_print("Can not create PID file %s\n" % pid_file)
|
||||
return False
|
||||
timestamp_print("Created PID file %s with value %d\n" % (pid_file, ownid))
|
||||
return True
|
||||
|
||||
|
||||
def removepid():
|
||||
try:
|
||||
os.remove(pid_file)
|
||||
except OSError as exc:
|
||||
if exc.errno == errno.ENOENT:
|
||||
pass
|
||||
else:
|
||||
timestamp_print("Can not remove PID file %s\n" % pid_file)
|
||||
return
|
||||
timestamp_print("Removed PID file %s\n" % pid_file)
|
||||
|
||||
|
||||
def main():
|
||||
global shutdown
|
||||
|
||||
@ -51,8 +122,13 @@ def main():
|
||||
ADDRESS, PORT = args.address, args.port
|
||||
LOG_FILE = args.logfile
|
||||
|
||||
global pid_file
|
||||
pid_file = args.pidfile
|
||||
|
||||
timestamp_print(f"Starting bmpserver on {args.address}:{args.port}")
|
||||
|
||||
savepid()
|
||||
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
try:
|
||||
@ -80,9 +156,7 @@ def main():
|
||||
while len(data) > BMPMsg.MIN_LEN:
|
||||
data = BMPMsg.dissect(data, log_file=LOG_FILE)
|
||||
|
||||
timestamp_print(
|
||||
f"Finished dissecting data from {client_address}"
|
||||
)
|
||||
timestamp_print(f"Finished dissecting data from {client_address}")
|
||||
|
||||
except Exception as e:
|
||||
timestamp_print(f"{e}")
|
||||
@ -99,6 +173,7 @@ def main():
|
||||
timestamp_print(f"{e}")
|
||||
finally:
|
||||
timestamp_print(f"Server shutting down on {ADDRESS}:{PORT}")
|
||||
removepid()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
@ -106,4 +181,5 @@ if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
except KeyboardInterrupt:
|
||||
logging.info("BMP server was interrupted and is shutting down.")
|
||||
removepid()
|
||||
sys.exit(0)
|
@ -1293,18 +1293,19 @@ class TopoBMPCollector(TopoHost):
|
||||
log_err = os.path.join(log_dir, "bmpserver.log")
|
||||
|
||||
log_arg = "-l {}".format(log_file) if log_file else ""
|
||||
self.pid_file = os.path.join(log_dir, "bmpserver.pid")
|
||||
|
||||
with open(log_err, "w") as err:
|
||||
self.run(
|
||||
"{}/bmp_collector/bmpserver -a {} -p {} {}&".format(
|
||||
CWD, self.ip, self.port, log_arg
|
||||
"{}/bmp_collector/bmpserver.py -a {} -p {} -r {} {}&".format(
|
||||
CWD, self.ip, self.port, self.pid_file, log_arg
|
||||
),
|
||||
stdout=None,
|
||||
stderr=err,
|
||||
)
|
||||
|
||||
def stop(self):
|
||||
self.run("pkill -f bmpserver")
|
||||
self.run(f"kill $(cat {self.pid_file}")
|
||||
return ""
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user