[bgpcfgd] Add bgpcfgd support to advertise routes (#9197)

Why I did it
Add bgpcfgd support to advertise routes.

How I did it
Make bgpcfgd subscribe to the ADVERTISE_NETWORK table in STATE_DB and configure route advertisement accordingly.

How to verify it
Added unit tests in bgpcfgd and verify on KVM about route advertisement.
This commit is contained in:
Shi Su 2021-11-29 23:17:57 -08:00 committed by GitHub
parent b28c800f17
commit 4b357044b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 297 additions and 1 deletions

View File

@ -172,7 +172,7 @@ start() {
$SONIC_DB_CLI GB_ASIC_DB FLUSHDB
$SONIC_DB_CLI GB_COUNTERS_DB FLUSHDB
$SONIC_DB_CLI RESTAPI_DB FLUSHDB
clean_up_tables STATE_DB "'PORT_TABLE*', 'MGMT_PORT_TABLE*', 'VLAN_TABLE*', 'VLAN_MEMBER_TABLE*', 'LAG_TABLE*', 'LAG_MEMBER_TABLE*', 'INTERFACE_TABLE*', 'MIRROR_SESSION*', 'VRF_TABLE*', 'FDB_TABLE*', 'FG_ROUTE_TABLE*', 'BUFFER_POOL*', 'BUFFER_PROFILE*', 'MUX_CABLE_TABLE*'"
clean_up_tables STATE_DB "'PORT_TABLE*', 'MGMT_PORT_TABLE*', 'VLAN_TABLE*', 'VLAN_MEMBER_TABLE*', 'LAG_TABLE*', 'LAG_MEMBER_TABLE*', 'INTERFACE_TABLE*', 'MIRROR_SESSION*', 'VRF_TABLE*', 'FDB_TABLE*', 'FG_ROUTE_TABLE*', 'BUFFER_POOL*', 'BUFFER_PROFILE*', 'MUX_CABLE_TABLE*', 'ADVERTISE_NETWORK_TABLE*'"
$SONIC_DB_CLI APPL_STATE_DB FLUSHDB
fi

View File

@ -9,6 +9,7 @@ from swsscommon import swsscommon
from .config import ConfigMgr
from .directory import Directory
from .log import log_notice, log_crit
from .managers_advertise_rt import AdvertiseRouteMgr
from .managers_allow_list import BGPAllowListMgr
from .managers_bbr import BBRMgr
from .managers_bgp import BGPPeerMgrBase
@ -59,6 +60,8 @@ def do_work():
BBRMgr(common_objs, "CONFIG_DB", "BGP_BBR"),
# Static Route Managers
StaticRouteMgr(common_objs, "CONFIG_DB", "STATIC_ROUTE"),
# Route Advertisement Managers
AdvertiseRouteMgr(common_objs, "STATE_DB", swsscommon.STATE_ADVERTISE_NETWORK_TABLE_NAME),
]
runner = Runner(common_objs['cfg_mgr'])
for mgr in managers:

View File

@ -0,0 +1,108 @@
from .manager import Manager
from .template import TemplateFabric
from swsscommon import swsscommon
class AdvertiseRouteMgr(Manager):
""" This class Advertises routes when ADVERTISE_NETWORK_TABLE in STATE_DB is updated """
def __init__(self, common_objs, db, table):
"""
Initialize the object
:param common_objs: common object dictionary
:param db: name of the db
:param table: name of the table in the db
"""
super(AdvertiseRouteMgr, self).__init__(
common_objs,
[],
db,
table,
)
self.directory.subscribe([("CONFIG_DB", swsscommon.CFG_DEVICE_METADATA_TABLE_NAME, "localhost/bgp_asn"),], self.on_bgp_asn_change)
self.advertised_routes = dict()
OP_DELETE = 'DELETE'
OP_ADD = 'ADD'
def set_handler(self, key, data):
vrf, ip_prefix = self.split_key(key)
self.add_route_advertisement(vrf, ip_prefix)
return True
def del_handler(self, key):
vrf, ip_prefix = self.split_key(key)
self.remove_route_advertisement(vrf, ip_prefix)
def add_route_advertisement(self, vrf, ip_prefix):
if self.directory.path_exist("CONFIG_DB", swsscommon.CFG_DEVICE_METADATA_TABLE_NAME, "localhost/bgp_asn"):
if not self.advertised_routes.get(vrf, set()):
self.bgp_network_import_check_commands(vrf, self.OP_ADD)
self.advertise_route_commands(ip_prefix, vrf, self.OP_ADD)
self.advertised_routes.setdefault(vrf, set()).add(ip_prefix)
def remove_route_advertisement(self, vrf, ip_prefix):
self.advertised_routes.setdefault(vrf, set()).discard(ip_prefix)
if not self.advertised_routes.get(vrf, set()):
self.advertised_routes.pop(vrf, None)
if self.directory.path_exist("CONFIG_DB", swsscommon.CFG_DEVICE_METADATA_TABLE_NAME, "localhost/bgp_asn"):
if not self.advertised_routes.get(vrf, set()):
self.bgp_network_import_check_commands(vrf, self.OP_DELETE)
self.advertise_route_commands(ip_prefix, vrf, self.OP_DELETE)
def advertise_route_commands(self, ip_prefix, vrf, op):
is_ipv6 = TemplateFabric.is_ipv6(ip_prefix)
bgp_asn = self.directory.get_slot("CONFIG_DB", swsscommon.CFG_DEVICE_METADATA_TABLE_NAME)["localhost"]["bgp_asn"]
cmd_list = []
if vrf == 'default':
cmd_list.append("router bgp %s" % bgp_asn)
else:
cmd_list.append("router bgp %s vrf %s" % (bgp_asn, vrf))
cmd_list.append(" address-family %s unicast" % ("ipv6" if is_ipv6 else "ipv4"))
cmd_list.append(" %snetwork %s" % ('no ' if op == self.OP_DELETE else '', ip_prefix))
self.cfg_mgr.push_list(cmd_list)
def bgp_network_import_check_commands(self, vrf, op):
bgp_asn = self.directory.get_slot("CONFIG_DB", swsscommon.CFG_DEVICE_METADATA_TABLE_NAME)["localhost"]["bgp_asn"]
cmd_list = []
if vrf == 'default':
cmd_list.append("router bgp %s" % bgp_asn)
else:
cmd_list.append("router bgp %s vrf %s" % (bgp_asn, vrf))
cmd_list.append(" %sbgp network import-check" % ('' if op == self.OP_DELETE else 'no '))
self.cfg_mgr.push_list(cmd_list)
def on_bgp_asn_change(self):
if self.directory.path_exist("CONFIG_DB", swsscommon.CFG_DEVICE_METADATA_TABLE_NAME, "localhost/bgp_asn"):
for vrf, ip_prefixes in self.advertised_routes.items():
self.bgp_network_import_check_commands(vrf, self.OP_ADD)
for ip_prefix in ip_prefixes:
self.add_route_advertisement(vrf, ip_prefix)
@staticmethod
def split_key(key):
"""
Split key into vrf name and prefix.
:param key: key to split
:return: vrf name extracted from the key, ip prefix extracted from the key
"""
if '|' not in key:
return 'default', key
else:
return tuple(key.split('|', 1))

View File

@ -0,0 +1,185 @@
from unittest.mock import MagicMock, patch
from bgpcfgd.directory import Directory
from bgpcfgd.template import TemplateFabric
from bgpcfgd.managers_advertise_rt import AdvertiseRouteMgr
from swsscommon import swsscommon
def constructor(skip_bgp_asn=False):
cfg_mgr = MagicMock()
common_objs = {
'directory': Directory(),
'cfg_mgr': cfg_mgr,
'tf': TemplateFabric(),
'constants': {},
}
mgr = AdvertiseRouteMgr(common_objs, "STATE_DB", swsscommon.STATE_ADVERTISE_NETWORK_TABLE_NAME)
if not skip_bgp_asn:
mgr.directory.put("CONFIG_DB", swsscommon.CFG_DEVICE_METADATA_TABLE_NAME, "localhost", {"bgp_asn": "65100"})
assert len(mgr.advertised_routes) == 0
return mgr
def set_del_test(mgr, op, args, expected_ret, expected_cmds):
set_del_test.push_list_called = False
def push_list(cmds):
set_del_test.push_list_called = True
assert cmds in expected_cmds
return True
mgr.cfg_mgr.push_list = push_list
if op == "SET":
ret = mgr.set_handler(*args)
assert ret == expected_ret
elif op == "DEL":
mgr.del_handler(*args)
else:
assert False, "Wrong operation"
if expected_cmds:
assert set_del_test.push_list_called, "cfg_mgr.push_list wasn't called"
else:
assert not set_del_test.push_list_called, "cfg_mgr.push_list was called"
def test_set_del():
mgr = constructor()
set_del_test(
mgr,
"SET",
("10.1.0.0/24", {}),
True,
[
["router bgp 65100",
" no bgp network import-check"],
["router bgp 65100",
" address-family ipv4 unicast",
" network 10.1.0.0/24"]
]
)
set_del_test(
mgr,
"SET",
("fc00:10::/64", {}),
True,
[
["router bgp 65100",
" address-family ipv6 unicast",
" network fc00:10::/64"]
]
)
set_del_test(
mgr,
"DEL",
("10.1.0.0/24",),
True,
[
["router bgp 65100",
" address-family ipv4 unicast",
" no network 10.1.0.0/24"]
]
)
set_del_test(
mgr,
"DEL",
("fc00:10::/64",),
True,
[
["router bgp 65100",
" bgp network import-check"],
["router bgp 65100",
" address-family ipv6 unicast",
" no network fc00:10::/64"]
]
)
def test_set_del_vrf():
mgr = constructor()
set_del_test(
mgr,
"SET",
("vrfRED|10.2.0.0/24", {}),
True,
[
["router bgp 65100 vrf vrfRED",
" no bgp network import-check"],
["router bgp 65100 vrf vrfRED",
" address-family ipv4 unicast",
" network 10.2.0.0/24"]
]
)
set_del_test(
mgr,
"SET",
("vrfRED|fc00:20::/64", {}),
True,
[
["router bgp 65100 vrf vrfRED",
" address-family ipv6 unicast",
" network fc00:20::/64"]
]
)
set_del_test(
mgr,
"DEL",
("vrfRED|10.2.0.0/24",),
True,
[
["router bgp 65100 vrf vrfRED",
" address-family ipv4 unicast",
" no network 10.2.0.0/24"]
]
)
set_del_test(
mgr,
"DEL",
("vrfRED|fc00:20::/64",),
True,
[
["router bgp 65100 vrf vrfRED",
" bgp network import-check"],
["router bgp 65100 vrf vrfRED",
" address-family ipv6 unicast",
" no network fc00:20::/64"]
]
)
def test_set_del_bgp_asn_change():
mgr = constructor(skip_bgp_asn=True)
set_del_test(
mgr,
"SET",
("vrfRED|10.3.0.0/24", {}),
True,
[]
)
test_set_del_bgp_asn_change.push_list_called = False
expected_cmds = [
["router bgp 65100 vrf vrfRED",
" no bgp network import-check"],
["router bgp 65100 vrf vrfRED",
" address-family ipv4 unicast",
" network 10.3.0.0/24"]
]
def push_list(cmds):
test_set_del_bgp_asn_change.push_list_called = True
assert cmds in expected_cmds
return True
mgr.cfg_mgr.push_list = push_list
assert not test_set_del_bgp_asn_change.push_list_called
mgr.directory.put("CONFIG_DB", swsscommon.CFG_DEVICE_METADATA_TABLE_NAME, "localhost", {"bgp_asn": "65100"})
assert test_set_del_bgp_asn_change.push_list_called