Skip to content

Commit 97bea61

Browse files
shi-suqiluo-msft
authored andcommitted
[bgpcfgd] Add bgpcfgd support for static routes (#7233)
Why I did it Add bgpcfgd support for static routes. How I did it Add bgpcfgd support to subscribe changes in STATIC_ROUTE table in CONFIG_DB and program via vtysh. The key of STATIC_ROUTE table is formatted as STATIC_ROUTE|vrf|ip_prefix, while the vrf is optional. If would be treated the same as "default" if no vrf is given. Add unit tests.
1 parent 75071a9 commit 97bea61

File tree

3 files changed

+663
-0
lines changed

3 files changed

+663
-0
lines changed

src/sonic-bgpcfgd/bgpcfgd/main.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from .managers_db import BGPDataBaseMgr
1616
from .managers_intf import InterfaceMgr
1717
from .managers_setsrc import ZebraSetSrc
18+
from .managers_static_rt import StaticRouteMgr
1819
from .runner import Runner, signal_handler
1920
from .template import TemplateFabric
2021
from .utils import read_constants
@@ -53,6 +54,8 @@ def do_work():
5354
BGPAllowListMgr(common_objs, "CONFIG_DB", "BGP_ALLOWED_PREFIXES"),
5455
# BBR Manager
5556
BBRMgr(common_objs, "CONFIG_DB", "BGP_BBR"),
57+
# Static Route Managers
58+
StaticRouteMgr(common_objs, "CONFIG_DB", "STATIC_ROUTE"),
5659
]
5760
runner = Runner(common_objs['cfg_mgr'])
5861
for mgr in managers:
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import traceback
2+
from .log import log_crit, log_err, log_debug
3+
from .manager import Manager
4+
from .template import TemplateFabric
5+
import socket
6+
7+
class StaticRouteMgr(Manager):
8+
""" This class updates static routes when STATIC_ROUTE table is updated """
9+
def __init__(self, common_objs, db, table):
10+
"""
11+
Initialize the object
12+
:param common_objs: common object dictionary
13+
:param db: name of the db
14+
:param table: name of the table in the db
15+
"""
16+
super(StaticRouteMgr, self).__init__(
17+
common_objs,
18+
[],
19+
db,
20+
table,
21+
)
22+
23+
self.static_routes = {}
24+
25+
OP_DELETE = 'DELETE'
26+
OP_ADD = 'ADD'
27+
28+
def set_handler(self, key, data):
29+
vrf, ip_prefix = self.split_key(key)
30+
is_ipv6 = TemplateFabric.is_ipv6(ip_prefix)
31+
32+
arg_list = lambda v: v.split(',') if len(v.strip()) != 0 else None
33+
bkh_list = arg_list(data['blackhole']) if 'blackhole' in data else None
34+
nh_list = arg_list(data['nexthop']) if 'nexthop' in data else None
35+
intf_list = arg_list(data['ifname']) if 'ifname' in data else None
36+
dist_list = arg_list(data['distance']) if 'distance' in data else None
37+
nh_vrf_list = arg_list(data['nexthop-vrf']) if 'nexthop-vrf' in data else None
38+
39+
try:
40+
ip_nh_set = IpNextHopSet(is_ipv6, bkh_list, nh_list, intf_list, dist_list, nh_vrf_list)
41+
cur_nh_set = self.static_routes.get(vrf, {}).get(ip_prefix, IpNextHopSet(is_ipv6))
42+
cmd_list = self.static_route_commands(ip_nh_set, cur_nh_set, ip_prefix, vrf)
43+
except Exception as exc:
44+
log_crit("Got an exception %s: Traceback: %s" % (str(exc), traceback.format_exc()))
45+
return False
46+
47+
if cmd_list:
48+
self.cfg_mgr.push_list(cmd_list)
49+
log_debug("Static route {} is scheduled for updates".format(key))
50+
else:
51+
log_debug("Nothing to update for static route {}".format(key))
52+
53+
self.static_routes.setdefault(vrf, {})[ip_prefix] = ip_nh_set
54+
55+
return True
56+
57+
58+
def del_handler(self, key):
59+
vrf, ip_prefix = self.split_key(key)
60+
is_ipv6 = TemplateFabric.is_ipv6(ip_prefix)
61+
62+
ip_nh_set = IpNextHopSet(is_ipv6)
63+
cur_nh_set = self.static_routes.get(vrf, {}).get(ip_prefix, IpNextHopSet(is_ipv6))
64+
cmd_list = self.static_route_commands(ip_nh_set, cur_nh_set, ip_prefix, vrf)
65+
66+
if cmd_list:
67+
self.cfg_mgr.push_list(cmd_list)
68+
log_debug("Static route {} is scheduled for updates".format(key))
69+
else:
70+
log_debug("Nothing to update for static route {}".format(key))
71+
72+
self.static_routes.setdefault(vrf, {}).pop(ip_prefix, None)
73+
74+
@staticmethod
75+
def split_key(key):
76+
"""
77+
Split key into vrf name and prefix.
78+
:param key: key to split
79+
:return: vrf name extracted from the key, ip prefix extracted from the key
80+
"""
81+
if '|' not in key:
82+
return 'default', key
83+
else:
84+
return tuple(key.split('|', 1))
85+
86+
def static_route_commands(self, ip_nh_set, cur_nh_set, ip_prefix, vrf):
87+
diff_set = ip_nh_set.symmetric_difference(cur_nh_set)
88+
89+
op_cmd_list = {}
90+
for ip_nh in diff_set:
91+
if ip_nh in cur_nh_set:
92+
op = self.OP_DELETE
93+
else:
94+
op = self.OP_ADD
95+
96+
op_cmds = op_cmd_list.setdefault(op, [])
97+
op_cmds.append(self.generate_command(op, ip_nh, ip_prefix, vrf))
98+
99+
cmd_list = op_cmd_list.get(self.OP_DELETE, [])
100+
cmd_list += op_cmd_list.get(self.OP_ADD, [])
101+
102+
return cmd_list
103+
104+
def generate_command(self, op, ip_nh, ip_prefix, vrf):
105+
return '{}{} route {}{}{}'.format(
106+
'no ' if op == self.OP_DELETE else '',
107+
'ipv6' if ip_nh.af == socket.AF_INET6 else 'ip',
108+
ip_prefix,
109+
ip_nh,
110+
' vrf {}'.format(vrf) if vrf != 'default' else ''
111+
)
112+
113+
class IpNextHop:
114+
def __init__(self, af_id, blackhole, dst_ip, if_name, dist, vrf):
115+
zero_ip = lambda af: '0.0.0.0' if af == socket.AF_INET else '::'
116+
self.af = af_id
117+
self.blackhole = 'false' if blackhole is None or blackhole == '' else blackhole
118+
self.distance = 0 if dist is None else int(dist)
119+
if self.blackhole == 'true':
120+
dst_ip = if_name = vrf = None
121+
self.ip = zero_ip(af_id) if dst_ip is None or dst_ip == '' else dst_ip
122+
self.interface = '' if if_name is None else if_name
123+
self.nh_vrf = '' if vrf is None else vrf
124+
if self.blackhole != 'true' and self.is_zero_ip() and len(self.interface.strip()) == 0:
125+
log_err('Mandatory attribute not found for nexthop')
126+
raise ValueError
127+
def __eq__(self, other):
128+
return (self.af == other.af and self.blackhole == other.blackhole and
129+
self.ip == other.ip and self.interface == other.interface and
130+
self.distance == other.distance and self.nh_vrf == other.nh_vrf)
131+
def __ne__(self, other):
132+
return (self.af != other.af or self.blackhole != other.blackhole or
133+
self.ip != other.ip or self.interface != other.interface or
134+
self.distance != other.distance or self.nh_vrf != other.nh_vrf)
135+
def __hash__(self):
136+
return hash((self.af, self.blackhole, self.ip, self.interface, self.distance, self.nh_vrf))
137+
def is_zero_ip(self):
138+
return sum([x for x in socket.inet_pton(self.af, self.ip)]) == 0
139+
def __format__(self, format):
140+
ret_val = ''
141+
if self.blackhole == 'true':
142+
ret_val += ' blackhole'
143+
if not (self.ip is None or self.is_zero_ip()):
144+
ret_val += ' %s' % self.ip
145+
if not (self.interface is None or self.interface == ''):
146+
ret_val += ' %s' % self.interface
147+
if not (self.distance is None or self.distance == 0):
148+
ret_val += ' %d' % self.distance
149+
if not (self.nh_vrf is None or self.nh_vrf == ''):
150+
ret_val += ' nexthop-vrf %s' % self.nh_vrf
151+
return ret_val
152+
153+
class IpNextHopSet(set):
154+
def __init__(self, is_ipv6, bkh_list = None, ip_list = None, intf_list = None, dist_list = None, vrf_list = None):
155+
super(IpNextHopSet, self).__init__()
156+
af = socket.AF_INET6 if is_ipv6 else socket.AF_INET
157+
if bkh_list is None and ip_list is None and intf_list is None:
158+
# empty set, for delete case
159+
return
160+
nums = {len(x) for x in [bkh_list, ip_list, intf_list, dist_list, vrf_list] if x is not None}
161+
if len(nums) != 1:
162+
log_err("Lists of next-hop attribute have different sizes: %s" % nums)
163+
for x in [bkh_list, ip_list, intf_list, dist_list, vrf_list]:
164+
log_debug("List: %s" % x)
165+
raise ValueError
166+
nh_cnt = nums.pop()
167+
item = lambda lst, i: lst[i] if lst is not None else None
168+
for idx in range(nh_cnt):
169+
try:
170+
self.add(IpNextHop(af, item(bkh_list, idx), item(ip_list, idx), item(intf_list, idx),
171+
item(dist_list, idx), item(vrf_list, idx), ))
172+
except ValueError:
173+
continue

0 commit comments

Comments
 (0)