Skip to content

Commit da08ea6

Browse files
committed
Add SRv6 support in Bgpcfgd
<!-- Please make sure you've read and understood our contributing guidelines: https://github.com/Azure/SONiC/blob/gh-pages/CONTRIBUTING.md ** Make sure all your commits include a signature generated with `git commit -s` ** If this is a bug fix, make sure your description includes "fixes #xxxx", or "closes #xxxx" or "resolves #xxxx" Please provide the following information: --> #### 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](sonic-net/sonic-buildimage#21380) #### 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 <!-- If PR needs to be backported, then the PR must be tested against the base branch and the earliest backport release branch and provide tested image version on these two branches. For example, if the PR is requested for master, 202211 and 202012, then the requester needs to provide test results on master and 202012. --> #### Which release branch to backport (provide reason below if selected) <!-- - Note we only backport fixes to a release branch, *not* features! - Please also provide a reason for the backporting below. - e.g. - [x] 202006 --> - [ ] 201811 - [ ] 201911 - [ ] 202006 - [ ] 202012 - [ ] 202106 - [ ] 202111 - [ ] 202205 - [ ] 202211 - [ ] 202305 #### Tested branch (Please provide the tested image version) <!-- - Please provide tested image version - e.g. - [x] 20201231.100 --> - [ ] <!-- image version 1 --> - [ ] <!-- image version 2 --> #### Description for the changelog <!-- Write a short (one line) summary that describes the changes in this pull request for inclusion in the changelog: --> <!-- Ensure to add label/tag for the feature raised. example - PR#2174 under sonic-utilities repo. where, Generic Config and Update feature has been labelled as GCU. --> #### Link to config_db schema for YANG module changes <!-- Provide a link to config_db schema for the table for which YANG model is defined Link should point to correct section on https://github.com/Azure/sonic-buildimage/blob/master/src/sonic-yang-models/doc/Configuration.md --> [SRv6 static config HLD](sonic-net/SONiC#1860) #### A picture of a cute animal (not mandatory but encouraged)
1 parent afeebee commit da08ea6

File tree

3 files changed

+303
-0
lines changed

3 files changed

+303
-0
lines changed

src/sonic-bgpcfgd/bgpcfgd/main.py

+4
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():
+136
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 []

src/sonic-bgpcfgd/tests/test_srv6.py

+163
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)