Skip to content

Commit 01c3112

Browse files
authored
[Bgpcfgd]: Add SRv6 support in Bgpcfgd (#21156)
Why I did it There is a motivation to add capabilities in SONiC that allows static configuration of SRv6 network. Work item tracking Microsoft ADO (number only): 30251795 How I did it I added a SRv6 manager in Bgpcfgd that subscribes to SRV6_MY_LOCATORS and SRV6_MY_SIDS in CONFIG_DB and programs the changes to FRR's configuration. Note: this change depends on the availability and implementation details of the following FRR patch FRR SRv6 Static SID CLI How to verify it - Run unit tests - Build an image that contains this change and the relevant FRR CLI support. - Test the Image on a virtual device or physical device
1 parent f14e534 commit 01c3112

3 files changed

Lines changed: 303 additions & 0 deletions

File tree

src/sonic-bgpcfgd/bgpcfgd/main.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from .managers_rm import RouteMapMgr
2323
from .managers_device_global import DeviceGlobalCfgMgr
2424
from .managers_chassis_app_db import ChassisAppDbMgr
25+
from .managers_srv6 import SRv6Mgr
2526
from .static_rt_timer import StaticRouteTimer
2627
from .runner import Runner, signal_handler
2728
from .template import TemplateFabric
@@ -75,6 +76,9 @@ def do_work():
7576
RouteMapMgr(common_objs, "APPL_DB", swsscommon.APP_BGP_PROFILE_TABLE_NAME),
7677
# Device Global Manager
7778
DeviceGlobalCfgMgr(common_objs, "CONFIG_DB", swsscommon.CFG_BGP_DEVICE_GLOBAL_TABLE_NAME),
79+
# SRv6 Manager
80+
SRv6Mgr(common_objs, "CONFIG_DB", "SRV6_MY_SIDS"),
81+
SRv6Mgr(common_objs, "CONFIG_DB", "SRV6_MY_LOCATORS")
7882
]
7983

8084
if device_info.is_chassis():
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
from .log import log_err, log_debug, log_warn
2+
from .manager import Manager
3+
from ipaddress import IPv6Address
4+
from swsscommon import swsscommon
5+
6+
supported_SRv6_behaviors = {
7+
'uN',
8+
'uDT46',
9+
}
10+
11+
DEFAULT_VRF = "default"
12+
SRV6_MY_SIDS_TABLE_NAME = "SRV6_MY_SIDS"
13+
14+
class SRv6Mgr(Manager):
15+
""" This class updates SRv6 configurations when SRV6_MY_SID_TABLE table is updated """
16+
def __init__(self, common_objs, db, table):
17+
"""
18+
Initialize the object
19+
:param common_objs: common object dictionary
20+
:param db: name of the db
21+
:param table: name of the table in the db
22+
"""
23+
super(SRv6Mgr, self).__init__(
24+
common_objs,
25+
[],
26+
db,
27+
table,
28+
)
29+
30+
def set_handler(self, key, data):
31+
if self.table_name == SRV6_MY_SIDS_TABLE_NAME:
32+
return self.sids_set_handler(key, data)
33+
else:
34+
return self.locators_set_handler(key, data)
35+
36+
def locators_set_handler(self, key, data):
37+
locator_name = key
38+
39+
locator = Locator(locator_name, data)
40+
cmd_list = ["segment-routing", "srv6"]
41+
cmd_list += ['locators',
42+
'locator {}'.format(locator_name),
43+
'prefix {} block-len {} node-len {} func-bits {}'.format(
44+
locator.prefix,
45+
locator.block_len, locator.node_len, locator.func_len),
46+
"behavior usid"
47+
]
48+
49+
self.cfg_mgr.push_list(cmd_list)
50+
log_debug("{} SRv6 static configuration {}|{} is scheduled for updates. {}".format(self.db_name, self.table_name, key, str(cmd_list)))
51+
52+
self.directory.put(self.db_name, self.table_name, key, locator)
53+
return True
54+
55+
def sids_set_handler(self, key, data):
56+
locator_name = key.split("|")[0]
57+
ip_addr = key.split("|")[1].lower()
58+
key = "{}|{}".format(locator_name, ip_addr)
59+
60+
if not self.directory.path_exist(self.db_name, "SRV6_MY_LOCATORS", locator_name):
61+
log_err("Found a SRv6 SID config entry with a locator that does not exist: {} | {}".format(key, data))
62+
return False
63+
64+
locator = self.directory.get(self.db_name, "SRV6_MY_LOCATORS", locator_name)
65+
66+
if 'action' not in data:
67+
log_err("Found a SRv6 SID config entry that does not specify action: {} | {}".format(key, data))
68+
return False
69+
70+
if data['action'] not in supported_SRv6_behaviors:
71+
log_err("Found a SRv6 SID config entry associated with unsupported action: {} | {}".format(key, data))
72+
return False
73+
74+
sid = SID(locator_name, ip_addr, data) # the information in data will be parsed into SID's attributes
75+
76+
cmd_list = ['segment-routing', 'srv6', 'static-sids']
77+
sid_cmd = 'sid {}/{} locator {} behavior {}'.format(ip_addr, locator.block_len + locator.node_len + locator.func_len, locator_name, sid.action)
78+
if sid.decap_vrf != DEFAULT_VRF:
79+
sid_cmd += ' vrf {}'.format(sid.decap_vrf)
80+
cmd_list.append(sid_cmd)
81+
82+
self.cfg_mgr.push_list(cmd_list)
83+
log_debug("{} SRv6 static configuration {}|{} is scheduled for updates. {}".format(self.db_name, self.table_name, key, str(cmd_list)))
84+
85+
self.directory.put(self.db_name, self.table_name, key, (sid, sid_cmd))
86+
return True
87+
88+
def del_handler(self, key):
89+
if self.table_name == SRV6_MY_SIDS_TABLE_NAME:
90+
self.sids_del_handler(key)
91+
else:
92+
self.locators_del_handler(key)
93+
94+
def locators_del_handler(self, key):
95+
locator_name = key
96+
cmd_list = ['segment-routing', 'srv6', 'locators', 'no locator {}'.format(locator_name)]
97+
98+
self.cfg_mgr.push_list(cmd_list)
99+
log_debug("{} SRv6 static configuration {}|{} is scheduled for updates. {}".format(self.db_name, self.table_name, key, str(cmd_list)))
100+
self.directory.remove(self.db_name, self.table_name, key)
101+
102+
def sids_del_handler(self, key):
103+
locator_name = key.split("|")[0]
104+
ip_addr = key.split("|")[1].lower()
105+
key = "{}|{}".format(locator_name, ip_addr)
106+
107+
if not self.directory.path_exist(self.db_name, self.table_name, key):
108+
log_warn("Encountered a config deletion with a SRv6 SID that does not exist: {}".format(key))
109+
return
110+
111+
_, sid_cmd = self.directory.get(self.db_name, self.table_name, key)
112+
cmd_list = ['segment-routing', 'srv6', "static-sids"]
113+
no_sid_cmd = 'no ' + sid_cmd
114+
cmd_list.append(no_sid_cmd)
115+
116+
self.cfg_mgr.push_list(cmd_list)
117+
log_debug("{} SRv6 static configuration {}|{} is scheduled for updates. {}".format(self.db_name, self.table_name, key, str(cmd_list)))
118+
self.directory.remove(self.db_name, self.table_name, key)
119+
120+
class Locator:
121+
def __init__(self, name, data):
122+
self.name = name
123+
self.block_len = int(data['block_len'] if 'block_len' in data else 32)
124+
self.node_len = int(data['node_len'] if 'node_len' in data else 16)
125+
self.func_len = int(data['func_len'] if 'func_len' in data else 16)
126+
self.arg_len = int(data['arg_len'] if 'arg_len' in data else 0)
127+
self.prefix = data['prefix'].lower() + "/{}".format(self.block_len + self.node_len)
128+
129+
class SID:
130+
def __init__(self, locator, ip_addr, data):
131+
self.locator_name = locator
132+
self.ip_addr = ip_addr
133+
134+
self.action = data['action']
135+
self.decap_vrf = data['decap_vrf'] if 'decap_vrf' in data else DEFAULT_VRF
136+
self.adj = data['adj'].split(',') if 'adj' in data else []
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
from unittest.mock import MagicMock, patch
2+
3+
from bgpcfgd.directory import Directory
4+
from bgpcfgd.template import TemplateFabric
5+
from bgpcfgd.managers_srv6 import SRv6Mgr
6+
7+
def constructor():
8+
cfg_mgr = MagicMock()
9+
10+
common_objs = {
11+
'directory': Directory(),
12+
'cfg_mgr': cfg_mgr,
13+
'tf': TemplateFabric(),
14+
'constants': {},
15+
}
16+
17+
loc_mgr = SRv6Mgr(common_objs, "CONFIG_DB", "SRV6_MY_LOCATORS")
18+
sid_mgr = SRv6Mgr(common_objs, "CONFIG_DB", "SRV6_MY_SIDS")
19+
20+
return loc_mgr, sid_mgr
21+
22+
def op_test(mgr: SRv6Mgr, op, args, expected_ret, expected_cmds):
23+
op_test.push_list_called = False
24+
def push_list_checker(cmds):
25+
op_test.push_list_called = True
26+
assert len(cmds) == len(expected_cmds)
27+
for i in range(len(expected_cmds)):
28+
assert cmds[i].lower() == expected_cmds[i].lower()
29+
return True
30+
mgr.cfg_mgr.push_list = push_list_checker
31+
32+
if op == 'SET':
33+
ret = mgr.set_handler(*args)
34+
mgr.cfg_mgr.push_list = MagicMock()
35+
assert expected_ret == ret
36+
elif op == 'DEL':
37+
mgr.del_handler(*args)
38+
mgr.cfg_mgr.push_list = MagicMock()
39+
else:
40+
mgr.cfg_mgr.push_list = MagicMock()
41+
assert False, "Unexpected operation {}".format(op)
42+
43+
if expected_ret and expected_cmds:
44+
assert op_test.push_list_called, "cfg_mgr.push_list wasn't called"
45+
else:
46+
assert not op_test.push_list_called, "cfg_mgr.push_list was called"
47+
48+
def test_locator_add():
49+
loc_mgr, _ = constructor()
50+
51+
op_test(loc_mgr, 'SET', ("loc1", {
52+
'prefix': 'fcbb:bbbb:1::'
53+
}), expected_ret=True, expected_cmds=[
54+
'segment-routing',
55+
'srv6',
56+
'locators',
57+
'locator loc1',
58+
'prefix fcbb:bbbb:1::/48 block-len 32 node-len 16 func-bits 16',
59+
'behavior usid'
60+
])
61+
62+
assert loc_mgr.directory.path_exist(loc_mgr.db_name, loc_mgr.table_name, "loc1")
63+
64+
def test_locator_del():
65+
loc_mgr, _ = constructor()
66+
loc_mgr.set_handler("loc1", {'prefix': 'fcbb:bbbb:1::'})
67+
68+
op_test(loc_mgr, 'DEL', ("loc1",), expected_ret=True, expected_cmds=[
69+
'segment-routing',
70+
'srv6',
71+
'locators',
72+
'no locator loc1'
73+
])
74+
75+
assert not loc_mgr.directory.path_exist(loc_mgr.db_name, loc_mgr.table_name, "loc1")
76+
77+
def test_uN_add():
78+
loc_mgr, sid_mgr = constructor()
79+
assert loc_mgr.set_handler("loc1", {'prefix': 'fcbb:bbbb:1::'})
80+
81+
op_test(sid_mgr, 'SET', ("loc1|FCBB:BBBB:1:F1::", {
82+
'action': 'uN'
83+
}), expected_ret=True, expected_cmds=[
84+
'segment-routing',
85+
'srv6',
86+
'static-sids',
87+
'sid fcbb:bbbb:1:f1::/64 locator loc1 behavior uN'
88+
])
89+
90+
assert sid_mgr.directory.path_exist(sid_mgr.db_name, sid_mgr.table_name, "loc1|fcbb:bbbb:1:f1::")
91+
92+
def test_uDT46_add_vrf1():
93+
loc_mgr, sid_mgr = constructor()
94+
assert loc_mgr.set_handler("loc1", {'prefix': 'fcbb:bbbb:1::'})
95+
96+
op_test(sid_mgr, 'SET', ("loc1|FCBB:BBBB:1:F2::", {
97+
'action': 'uDT46',
98+
'decap_vrf': 'Vrf1'
99+
}), expected_ret=True, expected_cmds=[
100+
'segment-routing',
101+
'srv6',
102+
'static-sids',
103+
'sid fcbb:bbbb:1:f2::/64 locator loc1 behavior uDT46 vrf Vrf1'
104+
])
105+
106+
assert sid_mgr.directory.path_exist(sid_mgr.db_name, sid_mgr.table_name, "loc1|fcbb:bbbb:1:f2::")
107+
108+
def test_uN_del():
109+
loc_mgr, sid_mgr = constructor()
110+
assert loc_mgr.set_handler("loc1", {'prefix': 'fcbb:bbbb:1::'})
111+
112+
# add uN function first
113+
assert sid_mgr.set_handler("loc1|FCBB:BBBB:1:F1::", {
114+
'action': 'uN'
115+
})
116+
117+
# test the deletion
118+
op_test(sid_mgr, 'DEL', ("loc1|FCBB:BBBB:1:F1::",),
119+
expected_ret=True, expected_cmds=[
120+
'segment-routing',
121+
'srv6',
122+
'static-sids',
123+
'no sid fcbb:bbbb:1:f1::/64 locator loc1 behavior uN'
124+
])
125+
126+
assert not sid_mgr.directory.path_exist(sid_mgr.db_name, sid_mgr.table_name, "loc1|fcbb:bbbb:1:f1::")
127+
128+
def test_uDT46_del_vrf1():
129+
loc_mgr, sid_mgr = constructor()
130+
assert loc_mgr.set_handler("loc1", {'prefix': 'fcbb:bbbb:1::'})
131+
132+
# add a uN action first to make the uDT46 action not the last function
133+
assert sid_mgr.set_handler("loc1|FCBB:BBBB:1:F1::", {
134+
'action': 'uN'
135+
})
136+
137+
# add the uDT46 action
138+
assert sid_mgr.set_handler("loc1|FCBB:BBBB:1:F2::", {
139+
'action': 'uDT46',
140+
"decap_vrf": "Vrf1"
141+
})
142+
143+
# test the deletion of uDT46
144+
op_test(sid_mgr, 'DEL', ("loc1|FCBB:BBBB:1:F2::",),
145+
expected_ret=True, expected_cmds=[
146+
'segment-routing',
147+
'srv6',
148+
'static-sids',
149+
'no sid fcbb:bbbb:1:f2::/64 locator loc1 behavior uDT46 vrf Vrf1'
150+
])
151+
152+
assert sid_mgr.directory.path_exist(sid_mgr.db_name, sid_mgr.table_name, "loc1|fcbb:bbbb:1:f1::")
153+
assert not sid_mgr.directory.path_exist(sid_mgr.db_name, sid_mgr.table_name, "loc1|fcbb:bbbb:1:f2::")
154+
155+
def test_invalid_add():
156+
_, sid_mgr = constructor()
157+
158+
# test the addition of a SID with a non-existent locator
159+
op_test(sid_mgr, 'SET', ("loc2|FCBB:BBBB:21:F1::", {
160+
'action': 'uN'
161+
}), expected_ret=False, expected_cmds=[])
162+
163+
assert not sid_mgr.directory.path_exist(sid_mgr.db_name, sid_mgr.table_name, "loc2|fcbb:bbbb:21:f1::")

0 commit comments

Comments
 (0)