diff --git a/files/scripts/swss.sh b/files/scripts/swss.sh index 63e8ba8944..c8d28f8c69 100755 --- a/files/scripts/swss.sh +++ b/files/scripts/swss.sh @@ -147,7 +147,7 @@ start() { $SONIC_DB_CLI COUNTERS_DB FLUSHDB $SONIC_DB_CLI FLEX_COUNTER_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*'" fi # start service docker diff --git a/src/sonic-bgpcfgd/bgpcfgd/main.py b/src/sonic-bgpcfgd/bgpcfgd/main.py index 799390273d..52d591d1fc 100644 --- a/src/sonic-bgpcfgd/bgpcfgd/main.py +++ b/src/sonic-bgpcfgd/bgpcfgd/main.py @@ -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 @@ -57,6 +58,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: diff --git a/src/sonic-bgpcfgd/bgpcfgd/managers_advertise_rt.py b/src/sonic-bgpcfgd/bgpcfgd/managers_advertise_rt.py new file mode 100644 index 0000000000..352b89f728 --- /dev/null +++ b/src/sonic-bgpcfgd/bgpcfgd/managers_advertise_rt.py @@ -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)) diff --git a/src/sonic-bgpcfgd/tests/test_advertise_rt.py b/src/sonic-bgpcfgd/tests/test_advertise_rt.py new file mode 100644 index 0000000000..26f7b66176 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/test_advertise_rt.py @@ -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