[master]staticroutebfd process implementation (#13789)

* [BFD] staticroutebfd implementation
* To enable the BFD for static route

HLD: sonic-net/SONiC#1216
This commit is contained in:
qiwang4 2023-05-27 07:32:05 +08:00 committed by GitHub
parent a09048acd5
commit 359b80e012
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 1465 additions and 7 deletions

View File

@ -9,4 +9,5 @@ program:pimd
program:frrcfgd
{%- else %}
program:bgpcfgd
program:staticroutebfd
{%- endif %}

View File

@ -141,6 +141,20 @@ stderr_logfile=syslog
dependent_startup=true
dependent_startup_wait_for=bgpd:running
{% if DEVICE_METADATA.localhost.frr_mgmt_framework_config is defined and DEVICE_METADATA.localhost.frr_mgmt_framework_config == "true" %}
{% else %}
[program:staticroutebfd]
command=/usr/local/bin/staticroutebfd
priority=6
autostart=false
autorestart=false
startsecs=0
stdout_logfile=syslog
stderr_logfile=syslog
dependent_startup=true
dependent_startup_wait_for=bgpd:running
{% endif %}
[program:bgpmon]
command=/usr/local/bin/bgpmon
priority=6

View File

@ -25,6 +25,7 @@ class StaticRouteMgr(Manager):
self.directory.subscribe([("CONFIG_DB", swsscommon.CFG_DEVICE_METADATA_TABLE_NAME, "localhost/bgp_asn"),], self.on_bgp_asn_change)
self.static_routes = {}
self.vrf_pending_redistribution = set()
self.config_db = None
OP_DELETE = 'DELETE'
OP_ADD = 'ADD'
@ -41,7 +42,17 @@ class StaticRouteMgr(Manager):
intf_list = arg_list(data['ifname']) if 'ifname' in data else None
dist_list = arg_list(data['distance']) if 'distance' in data else None
nh_vrf_list = arg_list(data['nexthop-vrf']) if 'nexthop-vrf' in data else None
route_tag = self.ROUTE_ADVERTISE_DISABLE_TAG if 'advertise' in data and data['advertise'] == "false" else self.ROUTE_ADVERTISE_ENABLE_TAG
bfd_enable = arg_list(data['bfd']) if 'bfd' in data else None
route_tag = self.ROUTE_ADVERTISE_DISABLE_TAG if 'advertise' in data and data['advertise'] == "false" else self.ROUTE_ADVERTISE_ENABLE_TAG
# bfd enabled route would be handled in staticroutebfd, skip here
if bfd_enable and bfd_enable[0].lower() == "true":
log_debug("{} static route {} bfd flag is true".format(self.db_name, key))
tmp_nh_set, tmp_route_tag = self.static_routes.get(vrf, {}).get(ip_prefix, (IpNextHopSet(is_ipv6), route_tag))
if tmp_nh_set: #clear nexthop set if it is not empty
log_debug("{} static route {} bfd flag is true, cur_nh is not empty, clear it".format(self.db_name, key))
self.static_routes.setdefault(vrf, {}).pop(ip_prefix, None)
return True
try:
ip_nh_set = IpNextHopSet(is_ipv6, bkh_list, nh_list, intf_list, dist_list, nh_vrf_list)
@ -60,19 +71,59 @@ class StaticRouteMgr(Manager):
if cmd_list:
self.cfg_mgr.push_list(cmd_list)
log_debug("Static route {} is scheduled for updates".format(key))
log_debug("{} Static route {} is scheduled for updates. {}".format(self.db_name, key, str(cmd_list)))
else:
log_debug("Nothing to update for static route {}".format(key))
log_debug("{} Nothing to update for static route {}".format(self.db_name, key))
self.static_routes.setdefault(vrf, {})[ip_prefix] = (ip_nh_set, route_tag)
return True
def skip_appl_del(self, vrf, ip_prefix):
"""
If a static route is bfd enabled, the processed static route is written into application DB by staticroutebfd.
When we disable bfd for that route at runtime, that static route entry will be removed from APPL_DB STATIC_ROUTE_TABLE.
In the case, the StaticRouteMgr(appl_db) cannot uninstall the static route from FRR if the static route is still in CONFIG_DB,
so need this checking (skip appl_db deletion) to avoid race condition between StaticRouteMgr(appl_db) and StaticRouteMgr(config_db)
For more detailed information:
https://github.com/sonic-net/SONiC/blob/master/doc/static-route/SONiC_static_route_bfd_hld.md#bfd-field-changes-from-true-to-false
:param vrf: vrf from the split_key(key) return
:param ip_prefix: ip_prefix from the split_key(key) return
:return: True if the deletion comes from APPL_DB and the vrf|ip_prefix exists in CONFIG_DB, otherwise return False
"""
if self.db_name == "CONFIG_DB":
return False
if self.config_db is None:
self.config_db = swsscommon.SonicV2Connector()
self.config_db.connect(self.config_db.CONFIG_DB)
#just pop local cache if the route exist in config_db
cfg_key = "STATIC_ROUTE|" + vrf + "|" + ip_prefix
nexthop = self.config_db.get(self.config_db.CONFIG_DB, cfg_key, "nexthop")
if nexthop and len(nexthop)>0:
self.static_routes.setdefault(vrf, {}).pop(ip_prefix, None)
return True
if vrf == "default":
cfg_key = "STATIC_ROUTE|" + ip_prefix
nexthop = self.config_db.get(self.config_db.CONFIG_DB, cfg_key, "nexthop")
if nexthop and len(nexthop)>0:
self.static_routes.setdefault(vrf, {}).pop(ip_prefix, None)
return True
return False
def del_handler(self, key):
vrf, ip_prefix = self.split_key(key)
is_ipv6 = TemplateFabric.is_ipv6(ip_prefix)
if self.skip_appl_del(vrf, ip_prefix):
log_debug("{} ignore appl_db static route deletion because of key {} exist in config_db".format(self.db_name, key))
return
ip_nh_set = IpNextHopSet(is_ipv6)
cur_nh_set, route_tag = self.static_routes.get(vrf, {}).get(ip_prefix, (IpNextHopSet(is_ipv6), self.ROUTE_ADVERTISE_DISABLE_TAG))
cmd_list = self.static_route_commands(ip_nh_set, cur_nh_set, ip_prefix, vrf, route_tag, route_tag)
@ -85,9 +136,9 @@ class StaticRouteMgr(Manager):
if cmd_list:
self.cfg_mgr.push_list(cmd_list)
log_debug("Static route {} is scheduled for updates".format(key))
log_debug("{} Static route {} is scheduled for updates. {}".format(self.db_name, key, str(cmd_list)))
else:
log_debug("Nothing to update for static route {}".format(key))
log_debug("{} Nothing to update for static route {}".format(self.db_name, key))
self.static_routes.setdefault(vrf, {}).pop(ip_prefix, None)

View File

@ -11,6 +11,7 @@ setuptools.setup(
entry_points = {
'console_scripts': [
'bgpcfgd = bgpcfgd.main:main',
'staticroutebfd = staticroutebfd.main:main',
'bgpmon = bgpmon.bgpmon:main',
]
},

View File

@ -0,0 +1,764 @@
import os
import signal
import sys
import syslog
import threading
import traceback
from collections import defaultdict
from ipaddress import IPv4Address, IPv6Address
from copy import deepcopy
from swsscommon import swsscommon
from .vars import g_debug, bfd_multihop, bfd_rx_interval, bfd_tx_interval, bfd_multiplier
g_run = True
CONFIG_DB_NAME = "CONFIG_DB"
APPL_DB_NAME = "APPL_DB"
STATE_DB_NAME = "STATE_DB"
INTERFACE_TABLE_NAME = "INTERFACE"
PORTCHANNEL_INTERFACE_TABLE_NAME = "PORTCHANNEL_INTERFACE"
STATIC_ROUTE_TABLE_NAME = "STATIC_ROUTE"
BFD_SESSION_TABLE_NAME = "BFD_SESSION_TABLE"
LOCAL_CONFIG_TABLE = "config"
LOCAL_NEXTHOP_TABLE = "nexthop"
LOCAL_SRT_TABLE = "srt"
LOCAL_BFD_TABLE = "bfd"
LOCAL_BFD_PENDING_TABLE = "bfd_pending"
LOCAL_INTERFACE_TABLE = "interface"
def log_debug(msg):
""" Send a message msg to the syslog as DEBUG """
if g_debug:
syslog.syslog(syslog.LOG_DEBUG, msg)
def log_notice(msg):
""" Send a message msg to the syslog as NOTICE """
syslog.syslog(syslog.LOG_NOTICE, msg)
def log_info(msg):
""" Send a message msg to the syslog as INFO """
syslog.syslog(syslog.LOG_INFO, msg)
def log_warn(msg):
""" Send a message msg to the syslog as WARNING """
syslog.syslog(syslog.LOG_WARNING, msg)
def log_err(msg):
""" Send a message msg to the syslog as ERR """
syslog.syslog(syslog.LOG_ERR, msg)
def log_crit(msg):
""" Send a message msg to the syslog as CRIT """
syslog.syslog(syslog.LOG_CRIT, msg)
def signal_handler(_, __): # signal_handler(signum, frame)
""" signal handler """
global g_run
g_run = False
def static_route_split_key(key):
"""
Split key into vrf name and prefix.
:param key: key to split
:return: valid, vrf name extracted from the key, ip prefix extracted from the key
"""
l = tuple(key.split('|'))
if len(l) == 1:
return True, 'default', l[0]
return True, l[0], l[1]
def check_ip(ip):
if len(ip) == 0:
return False, False, ""
value = ip.split('/',1)
v = value[0]
try:
IPv4Address(v)
valid = True
is_ipv4 = True
except:
is_ipv4 = False
try:
IPv6Address(v)
valid = True
except:
valid = False
return valid, is_ipv4, v
class StaticRouteBfd(object):
SELECT_TIMEOUT = 1000
BFD_DEFAULT_CFG = {"multihop": "false", "rx_interval": "50", "tx_interval": "50"}
def __init__(self):
self.local_db = defaultdict(dict)
self.local_db[LOCAL_CONFIG_TABLE] = defaultdict(dict)
self.local_db[LOCAL_NEXTHOP_TABLE] = defaultdict(set)
self.local_db[LOCAL_SRT_TABLE] = defaultdict(set)
self.local_db[LOCAL_BFD_TABLE] = defaultdict(dict)
self.local_db[LOCAL_BFD_PENDING_TABLE] = defaultdict(dict)
#interface, portchannel_interface and loopback_interface share same table, assume name is unique
#assume only one ipv4 and/or one ipv6 for each interface
self.local_db[LOCAL_INTERFACE_TABLE] = defaultdict(dict)
self.config_db = swsscommon.DBConnector(CONFIG_DB_NAME, 0, True)
self.appl_db = swsscommon.DBConnector(APPL_DB_NAME, 0, True)
self.state_db = swsscommon.DBConnector(STATE_DB_NAME, 0, True)
self.bfd_appl_tbl = swsscommon.ProducerStateTable(self.appl_db, BFD_SESSION_TABLE_NAME)
self.static_route_appl_tbl = swsscommon.Table(self.appl_db, STATIC_ROUTE_TABLE_NAME)
self.selector = swsscommon.Select()
self.callbacks = defaultdict(lambda: defaultdict(list)) # db -> table -> handlers[]
self.subscribers = set()
self.first_time = True
def get_ip_from_key(self, key):
"""
Get ip address from key for LOOPBACK/INTERFACE/PORTCHANNEL_INTERFACE table
:param key: key in the tables
:return: valid, is_ipv4, ip address
"""
if '|' not in key:
return False, False, "", ""
else:
if_ip = key.split('|')
if len(if_ip) < 2:
return False, False, "", ""
if_name = if_ip[0]
value = if_ip[1]
valid, is_ipv4, ip = check_ip(value)
log_debug("get ip from intf key: valid %s is_ipv4 %s, if_name %s ip %s"%(str(valid), str(is_ipv4), if_name, ip))
return valid, is_ipv4, if_name, ip
def set_local_db(self, table, key, data):
try:
self.local_db[table][key] = data
return
except:
log_err("set_local_db error, table %s key %s"%(table, key))
pass
def get_local_db(self, table, key):
try:
v = self.local_db[table][key]
return v
except:
return {}
def remove_from_local_db(self, table, key):
if table in self.local_db:
if key in self.local_db[table]:
del self.local_db[table][key]
def append_to_nh_table_entry(self, nh_key, ip_prefix):
entry = self.get_local_db(LOCAL_NEXTHOP_TABLE, nh_key)
entry.add(ip_prefix)
def remove_from_nh_table_entry(self, nh_key, ip_prefix):
entry = self.get_local_db(LOCAL_NEXTHOP_TABLE, nh_key)
if ip_prefix in entry:
entry.remove(ip_prefix)
if len(entry) == 0:
self.remove_from_local_db(LOCAL_NEXTHOP_TABLE, nh_key)
def set_bfd_session_into_appl_db(self, key, data):
fvs = swsscommon.FieldValuePairs(list(data.items()))
self.bfd_appl_tbl.set(key, fvs)
log_debug("set bfd session to appl_db, key %s, data %s"%(key, str(data)))
def del_bfd_session_from_appl_db(self, key):
self.bfd_appl_tbl.delete(key)
def interface_set_handler(self, key, data):
valid, is_ipv4, if_name, ip = self.get_ip_from_key(key)
if not valid:
return True
value = self.get_local_db(LOCAL_INTERFACE_TABLE, if_name)
if len(value) == 0:
value = {is_ipv4: ip}
else:
value[is_ipv4] = ip
self.set_local_db(LOCAL_INTERFACE_TABLE, if_name, value)
self.update_bfd_pending(if_name)
return True
def interface_del_handler(self, key):
valid, is_ipv4, if_name, ip = self.get_ip_from_key(key)
if not valid:
return True
value = self.get_local_db(LOCAL_INTERFACE_TABLE, if_name)
if len(value) == 0:
return
else:
value[is_ipv4] = "" #remove the IP address for the interface
self.set_local_db(LOCAL_INTERFACE_TABLE, if_name, value)
def find_interface_ip(self, if_name, ip_example):
valid, is_ipv4, nh = check_ip(ip_example)
if not valid:
return False, ""
value = self.get_local_db(LOCAL_INTERFACE_TABLE, if_name)
ip = value.get(is_ipv4, "")
if len(ip)>0: #ip should be verified when add to local_db
return True, ip
return False, ""
def update_bfd_pending(self, if_name):
del_list=[]
for k, v in self.local_db[LOCAL_BFD_PENDING_TABLE].items():
if len(v) == 3 and v[0] == if_name:
intf, nh_ip, bfd_key = v[0], v[1], v[2]
valid, local_addr = self.find_interface_ip(intf, nh_ip)
if not valid: #IP address might not be available for this type of nh_ip (IPv4 or IPv6) yet
continue
log_notice("bfd_pending: get ip for interface: %s, create bfd session for %s"%(intf, bfd_key))
bfd_entry_cfg = self.BFD_DEFAULT_CFG.copy()
if all([bfd_rx_interval, bfd_tx_interval, bfd_multiplier, bfd_multihop]):
bfd_entry_cfg["multihop"] = bfd_multihop
bfd_entry_cfg["rx_interval"] = bfd_rx_interval
bfd_entry_cfg["tx_interval"] = bfd_tx_interval
bfd_entry_cfg["multiplier"] = bfd_multiplier
bfd_entry_cfg["local_addr"] = local_addr
self.set_bfd_session_into_appl_db(bfd_key, bfd_entry_cfg)
bfd_entry_cfg["static_route"] = "true"
self.set_local_db(LOCAL_BFD_TABLE, bfd_key, bfd_entry_cfg)
del_list.append(k)
for k in del_list:
self.local_db[LOCAL_BFD_PENDING_TABLE].pop(k)
def strip_table_name(self, key, splitter):
return key.split(splitter, 1)[1]
def reconciliation(self):
#to use SonicV2Connector get_all method, DBConnector doesn't have get_all
db = swsscommon.SonicV2Connector()
db.connect(db.CONFIG_DB)
db.connect(db.APPL_DB)
db.connect(db.STATE_DB)
#MUST keep the restore sequene
#restore interface(loopback/interface/portchannel_interface) tables
#restore interface tables
log_info("restore interface table -->")
keys = db.keys(db.CONFIG_DB, "LOOPBACK_INTERFACE|*")
for key in keys:
key_new = self.strip_table_name(key, "|")
self.interface_set_handler(key_new, "")
keys = db.keys(db.CONFIG_DB, "INTERFACE|*")
for key in keys:
key_new = self.strip_table_name(key, "|")
self.interface_set_handler(key_new, "")
keys = db.keys(db.CONFIG_DB, "PORTCHANNEL_INTERFACE|*")
for key in keys:
key_new = self.strip_table_name(key, "|")
self.interface_set_handler(key_new, "")
#restore bfd session table, static route won't create bfd session if it is already in appl_db
log_info("restore bfd session table -->")
keys = db.keys(db.APPL_DB, "BFD_SESSION_TABLE:*")
for key in keys:
data = db.get_all(db.APPL_DB, key)
key_new = self.strip_table_name(key, ":")
self.set_local_db(LOCAL_BFD_TABLE, key_new, data)
#restore static route table
log_info("restore static route table -->")
keys = db.keys(db.CONFIG_DB, "STATIC_ROUTE|*")
for key in keys:
data = db.get_all(db.CONFIG_DB, key)
key_new = self.strip_table_name(key, "|")
log_debug("SRT_BFD: restore static route from config_db, key %s, data %s"%(key, str(data)))
self.static_route_set_handler(key_new, data)
#clean up local bfd table, remove non static route bfd session
log_info("cleanup bfd session table -->")
self.cleanup_local_bfd_table()
#restore bfd state table
log_info("restore bfd state table -->")
keys = db.keys(db.STATE_DB, "BFD_SESSION_TABLE|*")
for key in keys:
data = db.get_all(db.STATE_DB, key)
key_new = self.strip_table_name(key, "|")
self.bfd_state_set_handler(key_new, data)
def cleanup_local_bfd_table(self):
kl=[]
for key in self.local_db[LOCAL_BFD_TABLE]:
kl.append(key)
for key in kl:
bfd_session = self.local_db[LOCAL_BFD_TABLE][key]
if "static_route" not in bfd_session or bfd_session["static_route"] != "true":
self.local_db[LOCAL_BFD_TABLE].pop(key)
def isFieldTrue(self, bfd_field):
if isinstance(bfd_field, list):
if len(bfd_field) == 1:
if isinstance(bfd_field[0], str):
if bfd_field[0].lower() == "true":
return True
return False
def refresh_active_nh(self, route_cfg_key):
data = self.get_local_db(LOCAL_CONFIG_TABLE, route_cfg_key)
arg_list = lambda v: [x.strip() for x in v.split(',')] if len(v.strip()) != 0 else None
nh_list = arg_list(data['nexthop']) if 'nexthop' in data else None
nh_vrf_list = arg_list(data['nexthop-vrf']) if 'nexthop-vrf' in data else None
nh_cnt = 0
for index in range(len(nh_list)):
nh_ip = nh_list[index]
nh_vrf = nh_vrf_list[index]
nh_key = nh_vrf + "|" + nh_ip
bfd_key = nh_vrf + ":default:" + nh_ip
bfd_session = self.get_local_db(LOCAL_BFD_TABLE, bfd_key)
if len(bfd_session) == 0:
continue
if "state" in bfd_session and bfd_session["state"].upper() == "UP":
self.append_to_srt_table_entry(route_cfg_key, (nh_vrf, nh_ip))
nh_cnt += 1
#do not write to appl_db is no nexthop reachable
if nh_cnt == 0:
return
#if there is any bfd session state UP, we don't need to hold the static route update.
data['bfd_nh_hold'] = "false"
new_config = self.reconstruct_static_route_config(data, self.get_local_db(LOCAL_SRT_TABLE, route_cfg_key))
self.set_static_route_into_appl_db(route_cfg_key.replace("|", ":"), new_config)
def handle_bfd_change(self, cfg_key, data, to_bfd_enable):
valid, vrf, ip_prefix = static_route_split_key(cfg_key)
key = vrf + ":" + ip_prefix
log_debug("SRT_BFD: handle_bfd_change. key %s, data %s, to_bfd_enable %s"%(key, str(data), str(to_bfd_enable)))
if to_bfd_enable:
#write route with full_nh_list to appl_db, let StaticRouteMgr(appl_db) install this route to update its cache
data['bfd'] = "false"
data['expiry'] = "false"
self.set_static_route_into_appl_db(key, data)
log_debug("SRT_BFD: bfd toggle to true. write the route to appl_db, update StaticRouteMgr(appl_db), key %s"%(key))
else:
self.del_static_route_from_appl_db(key)
log_debug("SRT_BFD: bfd toggle to false. delete static route from appl_db, key %s"%(key))
#treat it as static route deletion, but do not delete it from LOCAL_CONFIG_TABLE
self.static_route_del_handler(cfg_key, False)
def static_route_set_handler(self, key, data):
#sanity checking
if len(data) == 0:
return True
valid, vrf, ip_prefix = static_route_split_key(key)
route_cfg_key = vrf + "|" + ip_prefix
if not valid:
return True
valid, is_ipv4, ip = check_ip(ip_prefix)
if not valid:
log_err("invalid ip prefix for static route: ", key)
return True
arg_list = lambda v: [x.strip() for x in v.split(',')] if len(v.strip()) != 0 else None
bfd_field = arg_list(data['bfd']) if 'bfd' in data else ["false"]
cur_data = self.get_local_db(LOCAL_CONFIG_TABLE, route_cfg_key)
cur_bfd_enabled = False
if cur_data:
cur_bfd_field = arg_list(cur_data['bfd']) if 'bfd' in cur_data else ["false"]
cur_bfd_enabled = self.isFieldTrue(cur_bfd_field)
# this process, staticroutebfd, only handle the bfd enabled case, other cases would be handled in bgpcfgd/StaticRouteMgr
bfd_enabled = self.isFieldTrue(bfd_field)
#when bfd changed from "false" to "true", before bfd session created and state becomes up,
#the installed static route need to be kept in the system system, so put this route in "hold" state until at least one
#bfd session becomes UP.
data_copy = data.copy()
data['bfd_nh_hold'] = "false"
if cur_data:
if cur_bfd_enabled and not bfd_enabled: # dynamic bfd flag change from TRUE to FALSE
self.handle_bfd_change(key, data_copy, False)
if not cur_bfd_enabled and bfd_enabled: # dynamic bfd flag change from FALSE to TRUE
self.handle_bfd_change(key, data_copy, True)
data['bfd_nh_hold'] = "true"
if not bfd_enabled:
#skip if bfd is not enabled, but store it to local_db to detect bfd field dynamic change
data['bfd'] = "false"
self.set_local_db(LOCAL_CONFIG_TABLE, route_cfg_key, data)
return True
bkh_list = arg_list(data['blackhole']) if 'blackhole' in data else None
nh_list = arg_list(data['nexthop']) if 'nexthop' in data else None
intf_list = arg_list(data['ifname']) if 'ifname' in data else None
dist_list = arg_list(data['distance']) if 'distance' in data else None
nh_vrf_list = arg_list(data['nexthop-vrf']) if 'nexthop-vrf' in data else None
if nh_vrf_list is None:
nh_vrf_list = [vrf] * len(nh_list) if len(nh_list) > 0 else None
data['nexthop-vrf'] = ','.join(nh_vrf_list) if nh_vrf_list else ''
if intf_list is None or nh_list is None or nh_vrf_list is None or \
len(intf_list) != len(nh_list) or len(intf_list) != len(nh_vrf_list):
log_err("Static route bfd set Failed, nexthop, interface and vrf lists do not match.")
return True
if cur_data and cur_bfd_enabled:
# route with the prefix already exist, remove the deleted nexthops
nh_list_exist = arg_list(cur_data['nexthop']) if 'nexthop' in cur_data else None
nh_vrf_list_exist = arg_list(cur_data['nexthop-vrf']) if 'nexthop-vrf' in cur_data else None
if nh_vrf_list_exist is None:
nh_vrf_list_exist = []
for nh in nh_list:
nh_vrf_list_exist.append(vrf)
intf_list_exist = arg_list(cur_data['ifname']) if 'ifname' in cur_data else None
nh_key_list_exist = list(zip(nh_vrf_list_exist, intf_list_exist, nh_list_exist))
nh_key_list_new = list(zip(nh_vrf_list, intf_list, nh_list))
for nh in nh_key_list_exist:
if nh not in nh_key_list_new:
nh_vrf = nh[0]
nh_ip = nh[2]
nh_key = nh_vrf + "|" + nh_ip
self.remove_from_srt_table_entry(route_cfg_key, (nh_vrf, nh_ip))
self.remove_from_nh_table_entry(nh_key, route_cfg_key)
if len(self.get_local_db(LOCAL_NEXTHOP_TABLE, nh_key)) == 0:
bfd_key = nh_vrf + ":default:" + nh_ip
self.remove_from_local_db(LOCAL_BFD_TABLE, bfd_key)
self.del_bfd_session_from_appl_db(bfd_key)
self.set_local_db(LOCAL_CONFIG_TABLE, route_cfg_key, data)
for index in range(len(nh_list)):
nh_ip = nh_list[index]
intf = intf_list[index]
nh_vrf = nh_vrf_list[index]
nh_key = nh_vrf + "|" + nh_ip
#check if the bfd session is already created
bfd_key = nh_vrf + ":default:" + nh_ip
bfd_session = self.get_local_db(LOCAL_BFD_TABLE, bfd_key)
if len(bfd_session)>0:
self.local_db[LOCAL_BFD_TABLE][bfd_key]["static_route"] = "true"
if len(self.get_local_db(LOCAL_NEXTHOP_TABLE, nh_key)) == 0 and len(bfd_session) == 0:
valid, local_addr = self.find_interface_ip(intf, nh_ip)
if not valid:
#interface IP is not available yet, put this request to cache
self.set_local_db(LOCAL_BFD_PENDING_TABLE, intf+"_"+bfd_key, [intf, nh_ip, bfd_key])
self.append_to_nh_table_entry(nh_key, vrf + "|" + ip_prefix)
log_warn("bfd_pending: cannot find ip for interface: %s, postpone bfd session creation" %intf)
continue
bfd_entry_cfg = self.BFD_DEFAULT_CFG.copy()
if all([bfd_rx_interval, bfd_tx_interval, bfd_multiplier, bfd_multihop]):
bfd_entry_cfg["multihop"] = bfd_multihop
bfd_entry_cfg["rx_interval"] = bfd_rx_interval
bfd_entry_cfg["tx_interval"] = bfd_tx_interval
bfd_entry_cfg["multiplier"] = bfd_multiplier
bfd_entry_cfg["local_addr"] = local_addr
self.set_bfd_session_into_appl_db(bfd_key, bfd_entry_cfg)
bfd_entry_cfg["static_route"] = "true"
self.set_local_db(LOCAL_BFD_TABLE, bfd_key, bfd_entry_cfg)
self.append_to_nh_table_entry(nh_key, vrf + "|" + ip_prefix)
self.refresh_active_nh(route_cfg_key)
return True
def static_route_del_handler(self, key, redis_del):
valid, vrf, ip_prefix = static_route_split_key(key)
if not valid:
return True
route_cfg_key = vrf + "|" + ip_prefix
valid, is_ipv4, ip = check_ip(ip_prefix)
if not valid:
return True
data = self.get_local_db(LOCAL_CONFIG_TABLE, route_cfg_key)
if len(data) == 0:
# this route is not handled by StaticRouteBfd, skip
return True
arg_list = lambda v: [x.strip() for x in v.split(',')] if len(v.strip()) != 0 else None
nh_list = arg_list(data['nexthop']) if 'nexthop' in data else None
nh_vrf_list = arg_list(data['nexthop-vrf']) if 'nexthop-vrf' in data else None
for index in range(len(nh_list)):
nh_ip = nh_list[index]
nh_vrf = nh_vrf_list[index]
nh_key = nh_vrf + "|" + nh_ip
self.remove_from_nh_table_entry(nh_key, route_cfg_key)
if len(self.get_local_db(LOCAL_NEXTHOP_TABLE, nh_key)) == 0:
bfd_key = nh_vrf + ":default:" + nh_ip
self.remove_from_local_db(LOCAL_BFD_TABLE, bfd_key)
self.del_bfd_session_from_appl_db(bfd_key)
self.del_static_route_from_appl_db(route_cfg_key.replace("|", ":"))
self.remove_from_local_db(LOCAL_SRT_TABLE, route_cfg_key)
if redis_del:
self.remove_from_local_db(LOCAL_CONFIG_TABLE, route_cfg_key)
return True
def interface_callback(self, key, op, data):
if op == swsscommon.SET_COMMAND:
self.interface_set_handler(key, data)
elif op == swsscommon.DEL_COMMAND:
self.interface_del_handler(key)
else:
log_err("Invalid operation '%s' for key '%s'" % (op, key))
def static_route_callback(self, key, op, data):
if op == swsscommon.SET_COMMAND:
self.static_route_set_handler(key, data)
elif op == swsscommon.DEL_COMMAND:
self.static_route_del_handler(key, True)
else:
log_err("Invalid operation '%s' for key '%s'" % (op, key))
def bfd_state_split_key(self, key):
"""
Split key into table name, vrf name, interface name and peer ip.
:param key: key to split
:return: table name, vrf name, interface name and peer ip extracted from the key
"""
if key.count("|") < 2:
return 'default', 'default', key
else:
return tuple(key.split('|'))
def append_to_srt_table_entry(self, srt_key, nh_info):
entry = self.get_local_db(LOCAL_SRT_TABLE, srt_key)
entry.add(nh_info)
def remove_from_srt_table_entry(self, srt_key, nh_info):
entry = self.get_local_db(LOCAL_SRT_TABLE, srt_key)
if nh_info in entry:
entry.remove(nh_info)
if len(entry) == 0:
self.remove_from_local_db(LOCAL_SRT_TABLE, srt_key)
def set_static_route_into_appl_db(self, key, data):
fvs = swsscommon.FieldValuePairs(list(data.items()))
self.static_route_appl_tbl.set(key, fvs)
log_debug("SRT_BFD: set static route to appl_db, key %s, data %s"%(key, str(data)))
def del_static_route_from_appl_db(self, key):
self.static_route_appl_tbl.delete(key)
def reconstruct_static_route_config(self, original_config, reachable_nexthops):
arg_list = lambda v: [x.strip() for x in v.split(',')] if len(v.strip()) != 0 else None
bkh_list = arg_list(original_config['blackhole']) if 'blackhole' in original_config else None
nh_list = arg_list(original_config['nexthop']) if 'nexthop' in original_config else None
intf_list = arg_list(original_config['ifname']) if 'ifname' in original_config else None
dist_list = arg_list(original_config['distance']) if 'distance' in original_config else None
nh_vrf_list = arg_list(original_config['nexthop-vrf']) if 'nexthop-vrf' in original_config else None
bkh_candidate = ""
nh_candidate = ""
intf_candidate = ""
dist_candidate = ""
nh_vrf_candidate = ""
for i in range(len(nh_list)):
if (nh_vrf_list[i], nh_list[i]) in reachable_nexthops:
bkh_candidate += "," + (bkh_list[i] if bkh_list else "")
nh_candidate += "," + (nh_list[i] if nh_list else "")
intf_candidate += "," + (intf_list[i] if intf_list else "")
dist_candidate += "," + (dist_list[i] if dist_list else "")
nh_vrf_candidate += "," + (nh_vrf_list[i] if nh_vrf_list else "")
new_config = dict()
for key in original_config:
if key == "bfd":
continue
if key == "bfd_nh_hold":
continue
if key == "blackhole":
new_config[key] = bkh_candidate[1:]
elif key == "nexthop":
new_config[key] = nh_candidate[1:]
elif key == "ifname":
new_config[key] = intf_candidate[1:]
elif key == "distance":
new_config[key] = dist_candidate[1:]
elif key == "nexthop-vrf":
new_config[key] = nh_vrf_candidate[1:]
else:
new_config[key] = original_config[key]
new_config["expiry"] = "false"
return new_config
def bfd_state_set_handler(self, key, data):
#key are diff in state db and appl_db,
#intf is always default for multihop bfd
vrf, intf, peer_ip = self.bfd_state_split_key(key)
bfd_key = vrf + ":" + intf + ":" + peer_ip
#check if the BFD session is in local table
bfd_session = self.get_local_db(LOCAL_BFD_TABLE, bfd_key)
if len(bfd_session) == 0:
return True
nh_key = vrf + "|" + peer_ip
state = data['state'] if 'state' in data else "DOWN"
log_info("bfd seesion %s state %s" %(bfd_key, state))
self.local_db[LOCAL_BFD_TABLE][bfd_key]["state"] = state
if state.upper() == "UP":
for prefix in self.get_local_db(LOCAL_NEXTHOP_TABLE, nh_key):
srt_key = prefix
config_key = prefix
#skip if the (vrf, peer_ip) is already in the nexthop list
if (vrf, peer_ip) in self.get_local_db(LOCAL_SRT_TABLE, srt_key):
continue
self.append_to_srt_table_entry(srt_key, (vrf, peer_ip))
config_data = self.get_local_db(LOCAL_CONFIG_TABLE, config_key)
#exit "hold" state when any BFD session becomes UP
config_data['bfd_nh_hold'] = "false"
new_config = self.reconstruct_static_route_config(config_data, self.get_local_db(LOCAL_SRT_TABLE, srt_key))
self.set_static_route_into_appl_db(srt_key.replace("|", ":"), new_config)
elif state.upper() == "DOWN":
for prefix in self.get_local_db(LOCAL_NEXTHOP_TABLE, nh_key):
srt_key = prefix
config_key = prefix
config_data = self.get_local_db(LOCAL_CONFIG_TABLE, config_key)
#skip if the static route is in "hold" state
if config_data['bfd_nh_hold'] == "true":
continue
self.remove_from_srt_table_entry(srt_key, (vrf, peer_ip))
if len(self.get_local_db(LOCAL_SRT_TABLE, srt_key)) == 0:
log_debug("SRT_BFD: bfd_state DOWN. nh_list is empty, delete static route from appl_db, key %s"%(srt_key.replace("|", ":")))
self.del_static_route_from_appl_db(srt_key.replace("|", ":"))
else:
config_data = self.get_local_db(LOCAL_CONFIG_TABLE, config_key)
new_config = self.reconstruct_static_route_config(config_data, self.get_local_db(LOCAL_SRT_TABLE, srt_key))
self.set_static_route_into_appl_db(srt_key.replace("|", ":"), new_config)
def bfd_state_del_handler(self, key):
vrf, intf, peer_ip = self.bfd_state_split_key(key)
bfd_key = vrf + ":" + intf + ":" + peer_ip
nh_key = vrf + "|" + peer_ip
for prefix in self.get_local_db(LOCAL_NEXTHOP_TABLE, nh_key):
srt_key = prefix
config_key = prefix
self.remove_from_srt_table_entry(srt_key, (vrf, peer_ip))
if len(self.get_local_db(LOCAL_SRT_TABLE, srt_key)) == 0:
log_debug("SRT_BFD: bfd_state deletion. nh_list is empty, delete static route from appl_db, key %s"%(srt_key.replace("|", ":")))
self.del_static_route_from_appl_db(srt_key.replace("|", ":"))
else:
config_data = self.get_local_db(LOCAL_CONFIG_TABLE, config_key)
new_config = self.reconstruct_static_route_config(config_data, self.get_local_db(LOCAL_SRT_TABLE, srt_key))
self.set_static_route_into_appl_db(srt_key.replace("|", ":"), new_config)
def bfd_state_callback(self, key, op, data):
if op == swsscommon.SET_COMMAND:
self.bfd_state_set_handler(key, data)
elif op == swsscommon.DEL_COMMAND:
self.bfd_state_del_handler(key)
else:
log_err("Invalid operation '%s' for key '%s'" % (op, key))
def prepare_selector(self):
interface_subscriber = swsscommon.SubscriberStateTable(self.config_db, INTERFACE_TABLE_NAME)
portchannel_interface_subscriber = swsscommon.SubscriberStateTable(self.config_db, PORTCHANNEL_INTERFACE_TABLE_NAME)
static_route_subscriber = swsscommon.SubscriberStateTable(self.config_db, STATIC_ROUTE_TABLE_NAME)
bfd_state_subscriber = swsscommon.SubscriberStateTable(self.state_db, swsscommon.STATE_BFD_SESSION_TABLE_NAME)
self.selector.addSelectable(interface_subscriber)
self.selector.addSelectable(portchannel_interface_subscriber)
self.selector.addSelectable(static_route_subscriber)
self.selector.addSelectable(bfd_state_subscriber)
self.subscribers.add(interface_subscriber)
self.subscribers.add(portchannel_interface_subscriber)
self.subscribers.add(static_route_subscriber)
self.subscribers.add(bfd_state_subscriber)
self.callbacks[self.config_db.getDbId()][INTERFACE_TABLE_NAME].append(self.interface_callback)
self.callbacks[self.config_db.getDbId()][PORTCHANNEL_INTERFACE_TABLE_NAME].append(self.interface_callback)
self.callbacks[self.config_db.getDbId()][STATIC_ROUTE_TABLE_NAME].append(self.static_route_callback)
self.callbacks[self.state_db.getDbId()][swsscommon.STATE_BFD_SESSION_TABLE_NAME].append(self.bfd_state_callback)
def run(self):
self.prepare_selector()
while g_run:
state, _ = self.selector.select(self.SELECT_TIMEOUT)
if state == self.selector.TIMEOUT:
continue
elif state == self.selector.ERROR:
raise Exception("Received error from select")
if self.first_time:
self.first_time = False
self.reconciliation()
for sub in self.subscribers:
while True:
key, op, fvs = sub.pop()
if len(key) == 0:
break
log_debug("Received message : '%s'" % str((key, op, fvs)))
for callback in self.callbacks[sub.getDbConnector().getDbId()][sub.getTableName()]:
callback(key, op, dict(fvs))
def do_work():
sr_bfd = StaticRouteBfd()
sr_bfd.run()
def main():
rc = 0
try:
syslog.openlog('staticroutebfd')
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGINT, signal_handler)
do_work()
except KeyboardInterrupt:
syslog.syslog(syslog.LOG_NOTICE, "Keyboard interrupt")
pass
except RuntimeError as exc:
syslog.syslog(syslog.LOG_CRIT, str(exc))
rc = -2
if g_debug:
raise
except Exception as exc:
syslog.syslog(syslog.LOG_CRIT, "Got an exception %s: Traceback: %s" % (str(exc), traceback.format_exc()))
rc = -1
if g_debug:
raise
finally:
syslog.closelog()
try:
sys.exit(rc)
except SystemExit:
os._exit(rc)

View File

@ -0,0 +1,5 @@
g_debug = True
bfd_multihop = "true"
bfd_rx_interval = "50"
bfd_tx_interval = "50"
bfd_multiplier = "3"

View File

@ -604,7 +604,7 @@ def test_set_no_action(mocked_log_debug):
True,
[]
)
mocked_log_debug.assert_called_with("Nothing to update for static route default|10.1.1.0/24")
mocked_log_debug.assert_called_with("CONFIG_DB Nothing to update for static route default|10.1.1.0/24")
@patch('bgpcfgd.managers_static_rt.log_debug')
def test_del_no_action(mocked_log_debug):
@ -616,7 +616,7 @@ def test_del_no_action(mocked_log_debug):
True,
[]
)
mocked_log_debug.assert_called_with("Nothing to update for static route default|10.1.1.0/24")
mocked_log_debug.assert_called_with("CONFIG_DB Nothing to update for static route default|10.1.1.0/24")
def test_set_invalid_arg():
mgr = constructor()
@ -821,3 +821,109 @@ def test_set_tag_change():
"ip route 10.1.0.0/24 10.0.0.57 tag 2",
]
)
def test_set_bfd_false():
mgr = constructor()
set_del_test(
mgr,
"SET",
("10.1.0.0/24", {
"bfd": "false",
"nexthop": "PortChannel0001",
}),
True,
[
"ip route 10.1.0.0/24 PortChannel0001 tag 1",
"route-map STATIC_ROUTE_FILTER permit 10",
" match tag 1",
"router bgp 65100",
" address-family ipv4",
" redistribute static route-map STATIC_ROUTE_FILTER",
" address-family ipv6",
" redistribute static route-map STATIC_ROUTE_FILTER"
]
)
set_del_test(
mgr,
"DEL",
("10.1.0.0/24",),
True,
[
"no ip route 10.1.0.0/24 PortChannel0001 tag 1",
"router bgp 65100",
" address-family ipv4",
" no redistribute static route-map STATIC_ROUTE_FILTER",
" address-family ipv6",
" no redistribute static route-map STATIC_ROUTE_FILTER",
"no route-map STATIC_ROUTE_FILTER"
]
)
def test_set_bfd_true():
mgr = constructor()
set_del_test(
mgr,
"SET",
("10.1.0.0/24", {
"bfd": "false",
"nexthop": "PortChannel0001",
}),
True,
[
"ip route 10.1.0.0/24 PortChannel0001 tag 1",
"route-map STATIC_ROUTE_FILTER permit 10",
" match tag 1",
"router bgp 65100",
" address-family ipv4",
" redistribute static route-map STATIC_ROUTE_FILTER",
" address-family ipv6",
" redistribute static route-map STATIC_ROUTE_FILTER"
]
)
#do nothing for adding smae route second time
set_del_test(
mgr,
"SET",
("10.1.0.0/24", {
"bfd": "false",
"nexthop": "PortChannel0001",
}),
True,
[
]
)
#clear internal cache if bfd flag is true
set_del_test(
mgr,
"SET",
("10.1.0.0/24", {
"bfd": "true",
"nexthop": "PortChannel0001",
}),
True,
[
]
)
#install the route becasue that cache was cleared above
set_del_test(
mgr,
"SET",
("10.1.0.0/24", {
"bfd": "false",
"nexthop": "PortChannel0001",
}),
True,
[
"ip route 10.1.0.0/24 PortChannel0001 tag 1",
"route-map STATIC_ROUTE_FILTER permit 10",
" match tag 1",
"router bgp 65100",
" address-family ipv4",
" redistribute static route-map STATIC_ROUTE_FILTER",
" address-family ipv6",
" redistribute static route-map STATIC_ROUTE_FILTER"
]
)

View File

@ -0,0 +1,516 @@
from unittest.mock import patch
#from unittest.mock import MagicMock, patch
from staticroutebfd.main import *
from swsscommon import swsscommon
@patch('swsscommon.swsscommon.DBConnector.__init__')
@patch('swsscommon.swsscommon.ProducerStateTable.__init__')
@patch('swsscommon.swsscommon.Table.__init__')
def constructor(mock_db, mock_producer, mock_tbl):
mock_db.return_value = None
mock_producer.return_value = None
mock_tbl.return_value = None
srt_bfd = StaticRouteBfd()
return srt_bfd
def set_del_test(dut, hdlr, op, args, e_bfd_dict, e_srt_dict):
set_del_test.bfd_dict = {}
set_del_test.srt_dict = {}
def bfd_app_set(key, data):
set_del_test.bfd_dict["set_"+key] = data.copy()
def bfd_app_del(key):
set_del_test.bfd_dict["del_"+key] = {}
def srt_app_set(key, data):
set_del_test.srt_dict["set_"+key] = data.copy()
def srt_app_del(key):
set_del_test.srt_dict["del_"+key] = {}
def compare_dict(r, e):
if len(r) == 0 and len(e) == 0:
return True
if len(r) != len(e):
return False
for k in e:
if k not in r:
return False
if type(e[k]) is str:
r_sort = "".join(sorted([x.strip() for x in r[k].split(',')]))
e_sort = "".join(sorted([x.strip() for x in e[k].split(',')]))
if r_sort != e_sort:
return False
if type(e[k]) is dict:
ret = compare_dict(r[k], e[k])
if not ret:
return False
return True
dut.set_bfd_session_into_appl_db = bfd_app_set
dut.del_bfd_session_from_appl_db = bfd_app_del
dut.set_static_route_into_appl_db = srt_app_set
dut.del_static_route_from_appl_db = srt_app_del
if op == "SET":
if hdlr == "bfd":
dut.bfd_state_set_handler(*args)
if hdlr == "srt":
dut.static_route_set_handler(*args)
if hdlr == "intf":
dut.interface_set_handler(*args)
elif op == "DEL":
if hdlr == "bfd":
dut.bfd_state_del_handler(*args)
if hdlr == "srt":
dut.static_route_del_handler(*args)
if hdlr == "intf":
dut.interface_del_handler(*args)
else:
assert False, "Wrong operation"
assert compare_dict(set_del_test.bfd_dict, e_bfd_dict)
assert compare_dict(set_del_test.srt_dict, e_srt_dict)
def intf_setup(dut):
set_del_test(dut, "intf",
"SET",
("if1|192.168.1.1/24", {}
),
{},
{}
)
set_del_test(dut, "intf",
"SET",
("if2|192.168.2.1/24", {}
),
{},
{}
)
set_del_test(dut, "intf",
"SET",
("if3|192.168.3.1/24", {}
),
{},
{}
)
def test_set_del():
dut = constructor()
intf_setup(dut)
#test #1
set_del_test(dut, "srt",
"SET",
("2.2.2.0/24", {
"bfd": "true",
"nexthop": "192.168.1.2 , 192.168.2.2, 192.168.3.2",
"ifname": "if1, if2, if3",
}),
{
"set_default:default:192.168.1.2" : {'multihop': 'true', 'rx_interval': '50', 'tx_interval': '50', 'multiplier': '3', 'local_addr': '192.168.1.1'},
"set_default:default:192.168.2.2" : {'multihop': 'true', 'rx_interval': '50', 'tx_interval': '50', 'multiplier': '3', 'local_addr': '192.168.2.1'},
"set_default:default:192.168.3.2" : {'multihop': 'true', 'rx_interval': '50', 'tx_interval': '50', 'multiplier': '3', 'local_addr': '192.168.3.1'}
},
{}
)
set_del_test(dut, "bfd",
"SET",
("192.168.1.2", {
"state": "Up"
}),
{},
{'set_default:2.2.2.0/24': {'nexthop': '192.168.1.2', 'ifname': 'if1', 'nexthop-vrf': 'default', 'expiry': 'false'}}
)
set_del_test(dut, "bfd",
"SET",
("192.168.2.2", {
"state": "Up"
}),
{},
{'set_default:2.2.2.0/24': {'nexthop': '192.168.2.2,192.168.1.2 ', 'ifname': 'if2,if1', 'nexthop-vrf': 'default,default', 'expiry': 'false'}}
)
set_del_test(dut, "bfd",
"SET",
("192.168.3.2", {
"state": "Up"
}),
{},
{'set_default:2.2.2.0/24': {'nexthop': '192.168.2.2,192.168.1.2,192.168.3.2 ', 'ifname': 'if2,if1,if3', 'nexthop-vrf': 'default,default,default', 'expiry': 'false'}}
)
#test #2
set_del_test(dut, "srt",
"SET",
("2.2.2.0/24", {
"bfd": "true",
"nexthop": "192.168.1.2 , 192.168.2.2",
"ifname": "if1, if2",
}),
{
"del_default:default:192.168.3.2" : {}
},
{'set_default:2.2.2.0/24': {'nexthop': '192.168.2.2,192.168.1.2 ', 'ifname': 'if2,if1', 'nexthop-vrf': 'default,default', 'expiry': 'false'}}
)
#test #3
set_del_test(dut, "srt",
"DEL",
("2.2.2.0/24", {
"bfd": "true",
"nexthop": "192.168.1.2 , 192.168.2.2",
"ifname": "if1, if2",
}),
{
"del_default:default:192.168.1.2" : {},
"del_default:default:192.168.2.2" : {}
},
{'del_default:2.2.2.0/24': {}}
)
def test_bfd_del():
dut = constructor()
intf_setup(dut)
set_del_test(dut, "srt",
"SET",
("2.2.2.0/24", {
"bfd": "true",
"nexthop": "192.168.1.2 , 192.168.2.2, 192.168.3.2",
"ifname": "if1, if2, if3",
}),
{
"set_default:default:192.168.1.2" : {'multihop': 'true', 'rx_interval': '50', 'tx_interval': '50', 'multiplier': '3', 'local_addr': '192.168.1.1'},
"set_default:default:192.168.2.2" : {'multihop': 'true', 'rx_interval': '50', 'tx_interval': '50', 'multiplier': '3', 'local_addr': '192.168.2.1'},
"set_default:default:192.168.3.2" : {'multihop': 'true', 'rx_interval': '50', 'tx_interval': '50', 'multiplier': '3', 'local_addr': '192.168.3.1'}
},
{}
)
set_del_test(dut, "bfd",
"SET",
("192.168.1.2", {
"state": "Up"
}),
{},
{'set_default:2.2.2.0/24': {'nexthop': '192.168.1.2', 'ifname': 'if1', 'nexthop-vrf': 'default', 'expiry': 'false'}}
)
set_del_test(dut, "bfd",
"SET",
("192.168.2.2", {
"state": "Up"
}),
{},
{'set_default:2.2.2.0/24': {'nexthop': '192.168.2.2,192.168.1.2 ', 'ifname': 'if2,if1', 'nexthop-vrf': 'default,default', 'expiry': 'false'}}
)
set_del_test(dut, "bfd",
"SET",
("192.168.3.2", {
"state": "Up"
}),
{},
{'set_default:2.2.2.0/24': {'nexthop': '192.168.2.2,192.168.1.2,192.168.3.2 ', 'ifname': 'if2,if1,if3', 'nexthop-vrf': 'default,default,default', 'expiry': 'false'}}
)
#test bfd state del
set_del_test(dut, "bfd",
"DEL",
({"192.168.2.2"}),
{},
{'set_default:2.2.2.0/24': {'nexthop': '192.168.1.2,192.168.3.2 ', 'ifname': 'if1,if3', 'nexthop-vrf': 'default,default', 'expiry': 'false'}}
)
def test_set_2routes():
dut = constructor()
intf_setup(dut)
#test #4
set_del_test(dut, "srt",
"SET",
("2.2.2.0/24", {
"bfd": "true",
"nexthop": "192.168.1.2 , 192.168.2.2, 192.168.3.2",
"ifname": "if1, if2, if3",
}),
{
"set_default:default:192.168.1.2" : {'multihop': 'true', 'rx_interval': '50', 'tx_interval': '50', 'multiplier': '3', 'local_addr': '192.168.1.1'},
"set_default:default:192.168.2.2" : {'multihop': 'true', 'rx_interval': '50', 'tx_interval': '50', 'multiplier': '3', 'local_addr': '192.168.2.1'},
"set_default:default:192.168.3.2" : {'multihop': 'true', 'rx_interval': '50', 'tx_interval': '50', 'multiplier': '3', 'local_addr': '192.168.3.1'}
},
{}
)
set_del_test(dut, "bfd",
"SET",
("192.168.1.2", {
"state": "Up"
}),
{},
{'set_default:2.2.2.0/24': {'nexthop': '192.168.1.2', 'ifname': 'if1', 'nexthop-vrf': 'default', 'expiry': 'false'}}
)
set_del_test(dut, "bfd",
"SET",
("192.168.2.2", {
"state": "Up"
}),
{},
{'set_default:2.2.2.0/24': {'nexthop': '192.168.2.2,192.168.1.2 ', 'ifname': 'if2,if1', 'nexthop-vrf': 'default,default', 'expiry': 'false'}}
)
set_del_test(dut, "bfd",
"SET",
("192.168.3.2", {
"state": "Up"
}),
{},
{'set_default:2.2.2.0/24': {'nexthop': '192.168.2.2,192.168.1.2,192.168.3.2 ', 'ifname': 'if2,if1,if3', 'nexthop-vrf': 'default,default,default', 'expiry': 'false'}}
)
set_del_test(dut, "srt",
"SET",
("3.3.3.0/24", {
"bfd": "true",
"nexthop": "192.168.2.2",
"ifname": "if2",
}),
{},
{'set_default:3.3.3.0/24': {'nexthop': '192.168.2.2', 'ifname': 'if2', 'nexthop-vrf': 'default', 'expiry': 'false'}}
)
#test #5
set_del_test(dut, "bfd",
"SET",
("192.168.2.2", {
"state": "Down"
}),
{},
{'set_default:2.2.2.0/24': {'nexthop': '192.168.3.2,192.168.1.2 ', 'ifname': 'if3,if1', 'nexthop-vrf': 'default,default', 'expiry': 'false'}, 'del_default:3.3.3.0/24': {}}
)
#test #6
set_del_test(dut, "bfd",
"SET",
("192.168.2.2", {
"state": "Up"
}),
{},
{'set_default:2.2.2.0/24': {'nexthop': '192.168.2.2,192.168.1.2,192.168.3.2 ', 'ifname': 'if2,if1,if3', 'nexthop-vrf': 'default,default,default', 'expiry': 'false'},
'set_default:3.3.3.0/24': {'nexthop': '192.168.2.2', 'ifname': 'if2', 'nexthop-vrf': 'default', 'expiry': 'false'}}
)
def test_set_bfd_change_hold():
dut = constructor()
intf_setup(dut)
#test #9 bfd: true -> false
set_del_test(dut, "srt",
"SET",
("2.2.2.0/24", {
"bfd": "true",
"nexthop": "192.168.1.2 , 192.168.2.2, 192.168.3.2",
"ifname": "if1, if2, if3",
}),
{
"set_default:default:192.168.1.2" : {'multihop': 'true', 'rx_interval': '50', 'tx_interval': '50', 'multiplier': '3', 'local_addr': '192.168.1.1'},
"set_default:default:192.168.2.2" : {'multihop': 'true', 'rx_interval': '50', 'tx_interval': '50', 'multiplier': '3', 'local_addr': '192.168.2.1'},
"set_default:default:192.168.3.2" : {'multihop': 'true', 'rx_interval': '50', 'tx_interval': '50', 'multiplier': '3', 'local_addr': '192.168.3.1'}
},
{}
)
set_del_test(dut, "bfd",
"SET",
("192.168.1.2", {
"state": "Up"
}),
{},
{'set_default:2.2.2.0/24': {'nexthop': '192.168.1.2', 'ifname': 'if1', 'nexthop-vrf': 'default', 'expiry': 'false'}}
)
set_del_test(dut, "bfd",
"SET",
("192.168.2.2", {
"state": "Up"
}),
{},
{'set_default:2.2.2.0/24': {'nexthop': '192.168.2.2,192.168.1.2 ', 'ifname': 'if2,if1', 'nexthop-vrf': 'default,default', 'expiry': 'false'}}
)
set_del_test(dut, "bfd",
"SET",
("192.168.3.2", {
"state": "Up"
}),
{},
{'set_default:2.2.2.0/24': {'nexthop': '192.168.2.2,192.168.1.2,192.168.3.2 ', 'ifname': 'if2,if1,if3', 'nexthop-vrf': 'default,default,default', 'expiry': 'false'}}
)
set_del_test(dut, "srt",
"SET",
("2.2.2.0/24", {
"bfd": "false",
"nexthop": "192.168.1.2 , 192.168.2.2, 192.168.3.2",
"ifname": "if1, if2, if3",
}),
{
"del_default:default:192.168.1.2" : {},
"del_default:default:192.168.2.2" : {},
"del_default:default:192.168.3.2" : {}
},
{
'del_default:2.2.2.0/24': {}
}
)
return
#test #10 'bfd': false --> true, write original rout first
set_del_test(dut, "srt",
"SET",
("2.2.2.0/24", {
"bfd": "true",
"nexthop": "192.168.1.2 , 192.168.2.2, 192.168.3.2",
"ifname": "if1, if2, if3",
}),
{
"set_default:default:192.168.1.2" : {'multihop': 'true', 'rx_interval': '50', 'tx_interval': '50', 'multiplier': '3', 'local_addr': '192.168.1.1'},
"set_default:default:192.168.2.2" : {'multihop': 'true', 'rx_interval': '50', 'tx_interval': '50', 'multiplier': '3', 'local_addr': '192.168.2.1'},
"set_default:default:192.168.3.2" : {'multihop': 'true', 'rx_interval': '50', 'tx_interval': '50', 'multiplier': '3', 'local_addr': '192.168.3.1'}
},
{'set_default:2.2.2.0/24': {'bfd':'false', 'nexthop': '192.168.2.2,192.168.1.2,192.168.3.2 ', 'ifname': 'if2,if1,if3', 'expiry': 'false'}}
)
set_del_test(dut, "bfd",
"SET",
("192.168.1.2", {
"state": "Up"
}),
{},
{'set_default:2.2.2.0/24': {'nexthop': '192.168.1.2', 'ifname': 'if1', 'nexthop-vrf': 'default', 'expiry': 'false'}}
)
set_del_test(dut, "bfd",
"SET",
("192.168.2.2", {
"state": "Up"
}),
{},
{'set_default:2.2.2.0/24': {'nexthop': '192.168.2.2,192.168.1.2 ', 'ifname': 'if2,if1', 'nexthop-vrf': 'default,default', 'expiry': 'false'}}
)
set_del_test(dut, "bfd",
"SET",
("192.168.3.2", {
"state": "Up"
}),
{},
{'set_default:2.2.2.0/24': {'nexthop': '192.168.2.2,192.168.1.2,192.168.3.2 ', 'ifname': 'if2,if1,if3', 'nexthop-vrf': 'default,default,default', 'expiry': 'false'}}
)
def test_set_bfd_change_no_hold():
dut = constructor()
intf_setup(dut)
#setup runtime "bfd"="false" condition``
set_del_test(dut, "srt",
"SET",
("2.2.2.0/24", {
"bfd": "true",
"nexthop": "192.168.1.2 , 192.168.2.2, 192.168.3.2",
"ifname": "if1, if2, if3",
}),
{
"set_default:default:192.168.1.2" : {'multihop': 'true', 'rx_interval': '50', 'tx_interval': '50', 'multiplier': '3', 'local_addr': '192.168.1.1'},
"set_default:default:192.168.2.2" : {'multihop': 'true', 'rx_interval': '50', 'tx_interval': '50', 'multiplier': '3', 'local_addr': '192.168.2.1'},
"set_default:default:192.168.3.2" : {'multihop': 'true', 'rx_interval': '50', 'tx_interval': '50', 'multiplier': '3', 'local_addr': '192.168.3.1'}
},
{}
)
set_del_test(dut, "bfd",
"SET",
("192.168.1.2", {
"state": "Up"
}),
{},
{'set_default:2.2.2.0/24': {'nexthop': '192.168.1.2', 'ifname': 'if1', 'nexthop-vrf': 'default', 'expiry': 'false'}}
)
set_del_test(dut, "bfd",
"SET",
("192.168.2.2", {
"state": "Up"
}),
{},
{'set_default:2.2.2.0/24': {'nexthop': '192.168.2.2,192.168.1.2 ', 'ifname': 'if2,if1', 'nexthop-vrf': 'default,default', 'expiry': 'false'}}
)
set_del_test(dut, "bfd",
"SET",
("192.168.3.2", {
"state": "Up"
}),
{},
{'set_default:2.2.2.0/24': {'nexthop': '192.168.2.2,192.168.1.2,192.168.3.2 ', 'ifname': 'if2,if1,if3', 'nexthop-vrf': 'default,default,default', 'expiry': 'false'}}
)
set_del_test(dut, "srt",
"SET",
("3.3.3.0/24", {
"bfd": "true",
"nexthop": "192.168.2.2",
"ifname": "if2",
}),
{},
{'set_default:3.3.3.0/24': {'nexthop': '192.168.2.2', 'ifname': 'if2', 'nexthop-vrf': 'default', 'expiry': 'false'}}
)
set_del_test(dut, "srt",
"SET",
("2.2.2.0/24", {
"bfd": "false",
"nexthop": "192.168.1.2 , 192.168.2.2, 192.168.3.2",
"ifname": "if1, if2, if3",
}),
{
"del_default:default:192.168.1.2" : {},
"del_default:default:192.168.3.2" : {}
},
{
'del_default:2.2.2.0/24': {}
}
)
#test #10 change 'bfd': false to true, because the bfd session "default:default:192.168.2.2" is up, so add that nexthop right after "bfd" change to "true"
set_del_test(dut, "srt",
"SET",
("2.2.2.0/24", {
"bfd": "true",
"nexthop": "192.168.1.2 , 192.168.2.2, 192.168.3.2",
"ifname": "if1, if2, if3",
}),
{
"set_default:default:192.168.1.2" : {'multihop': 'true', 'rx_interval': '50', 'tx_interval': '50', 'multiplier': '3', 'local_addr': '192.168.1.1'},
"set_default:default:192.168.3.2" : {'multihop': 'true', 'rx_interval': '50', 'tx_interval': '50', 'multiplier': '3', 'local_addr': '192.168.3.1'}
},
{'set_default:2.2.2.0/24': {'nexthop': '192.168.2.2', 'ifname': 'if2', 'nexthop-vrf': 'default', 'expiry': 'false'}}
)
set_del_test(dut, "bfd",
"SET",
("192.168.1.2", {
"state": "Up"
}),
{},
{'set_default:2.2.2.0/24': {'nexthop': '192.168.2.2,192.168.1.2 ', 'ifname': 'if2,if1', 'nexthop-vrf': 'default,default', 'expiry': 'false'}}
)
set_del_test(dut, "bfd",
"SET",
("192.168.2.2", {
"state": "Up"
}),
{},
{}
)
set_del_test(dut, "bfd",
"SET",
("192.168.3.2", {
"state": "Up"
}),
{},
{'set_default:2.2.2.0/24': {'nexthop': '192.168.2.2,192.168.1.2,192.168.3.2 ', 'ifname': 'if2,if1,if3', 'nexthop-vrf': 'default,default,default', 'expiry': 'false'}}
)