Skip to content

Commit b149194

Browse files
authored
Add W-ECMP to BGP OA (#18533)
- Why I did it W-ECMP feature implementation is according to the HLD - How I did it Added W-ECMP YANG model implementation Added W-ECMP BGP OA implementation - How to verify it Run UTs Verified manually with the feature qualification Signed-off-by: Nazarii Hnydyn <[email protected]>
1 parent 78e1393 commit b149194

File tree

11 files changed

+477
-114
lines changed

11 files changed

+477
-114
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
!
2+
! template: bgpd/wcmp/bgpd.wcmp.conf.j2
3+
!
4+
route-map TO_BGP_PEER_V4 permit 100
5+
{%- if wcmp_enabled == 'true' %}
6+
set extcommunity bandwidth num-multipaths
7+
{%- else %}
8+
no set extcommunity bandwidth
9+
{%- endif %}
10+
exit
11+
!
12+
route-map TO_BGP_PEER_V6 permit 100
13+
{%- if wcmp_enabled == 'true' %}
14+
set extcommunity bandwidth num-multipaths
15+
{%- else %}
16+
no set extcommunity bandwidth
17+
{%- endif %}
18+
exit
19+
!
20+
! end of template: bgpd/wcmp/bgpd.wcmp.conf.j2
21+
!

files/build_templates/init_cfg.json.j2

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"BGP_DEVICE_GLOBAL": {
3232
"STATE": {
3333
"tsa_enabled": "false",
34+
"wcmp_enabled": "false",
3435
"idf_isolation_state": "unisolated"
3536
}
3637
},

src/sonic-bgpcfgd/bgpcfgd/managers_device_global.py

+137-28
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1+
import re
2+
import jinja2
3+
14
from .manager import Manager
25
from .log import log_err, log_debug, log_notice
3-
import re
46
from swsscommon import swsscommon
57

68
class DeviceGlobalCfgMgr(Manager):
79
"""This class responds to change in device-specific state"""
810

11+
TSA_DEFAULTS = "false"
12+
WCMP_DEFAULTS = "false"
13+
IDF_DEFAULTS = "unisolated"
14+
915
def __init__(self, common_objs, db, table):
1016
"""
1117
Initialize the object
@@ -19,6 +25,7 @@ def __init__(self, common_objs, db, table):
1925
self.constants = common_objs['constants']
2026
self.tsa_template = common_objs['tf'].from_file("bgpd/tsa/bgpd.tsa.isolate.conf.j2")
2127
self.tsb_template = common_objs['tf'].from_file("bgpd/tsa/bgpd.tsa.unisolate.conf.j2")
28+
self.wcmp_template = common_objs['tf'].from_file("bgpd/wcmp/bgpd.wcmp.conf.j2")
2229
self.idf_isolate_template = common_objs['tf'].from_file("bgpd/idf_isolate/idf_isolate.conf.j2")
2330
self.idf_unisolate_template = common_objs['tf'].from_file("bgpd/idf_isolate/idf_unisolate.conf.j2")
2431
self.directory.subscribe([("CONFIG_DB", swsscommon.CFG_DEVICE_METADATA_TABLE_NAME, "localhost/switch_type"),], self.on_switch_type_change)
@@ -29,49 +36,130 @@ def __init__(self, common_objs, db, table):
2936
table,
3037
)
3138

39+
# By default TSA feature is disabled
40+
if not self.directory.path_exist(self.db_name, self.table_name, "tsa_enabled"):
41+
self.directory.put(self.db_name, self.table_name, "tsa_enabled", self.TSA_DEFAULTS)
42+
# By default W-ECMP feature is disabled
43+
if not self.directory.path_exist(self.db_name, self.table_name, "wcmp_enabled"):
44+
self.directory.put(self.db_name, self.table_name, "wcmp_enabled", self.WCMP_DEFAULTS)
45+
# By default IDF feature is unisolated
46+
if not self.directory.path_exist(self.db_name, self.table_name, "idf_isolation_state"):
47+
self.directory.put(self.db_name, self.table_name, "idf_isolation_state", self.IDF_DEFAULTS)
48+
3249
def on_switch_type_change(self):
3350
log_debug("DeviceGlobalCfgMgr:: Switch type update handler")
3451
if self.directory.path_exist("CONFIG_DB", swsscommon.CFG_DEVICE_METADATA_TABLE_NAME, "localhost/switch_type"):
3552
self.switch_type = self.directory.get_slot("CONFIG_DB", swsscommon.CFG_DEVICE_METADATA_TABLE_NAME)["localhost"]["switch_type"]
3653
log_debug("DeviceGlobalCfgMgr:: Switch type: %s" % self.switch_type)
3754

3855
def set_handler(self, key, data):
56+
""" Handle device TSA/W-ECMP state change """
3957
log_debug("DeviceGlobalCfgMgr:: set handler")
58+
4059
if self.switch_type:
4160
log_debug("DeviceGlobalCfgMgr:: Switch type: %s" % self.switch_type)
42-
""" Handle device tsa_enabled state change """
4361
if not data:
4462
log_err("DeviceGlobalCfgMgr:: data is None")
4563
return False
4664

47-
tsa_status = "false"
48-
idf_isolation_state = "unisolated"
65+
# TSA configuration
66+
self.configure_tsa(data)
67+
# W-ECMP configuration
68+
self.configure_wcmp(data)
69+
# IDF configuration
70+
self.configure_idf(data)
4971

50-
if self.directory.path_exist("CONFIG_DB", swsscommon.CFG_BGP_DEVICE_GLOBAL_TABLE_NAME, "tsa_enabled"):
51-
tsa_status = self.directory.get_slot("CONFIG_DB", swsscommon.CFG_BGP_DEVICE_GLOBAL_TABLE_NAME)["tsa_enabled"]
52-
if self.directory.path_exist("CONFIG_DB", swsscommon.CFG_BGP_DEVICE_GLOBAL_TABLE_NAME, "idf_isolation_state"):
53-
idf_isolation_state = self.directory.get_slot("CONFIG_DB", swsscommon.CFG_BGP_DEVICE_GLOBAL_TABLE_NAME)["idf_isolation_state"]
54-
55-
if "tsa_enabled" in data:
56-
self.directory.put(self.db_name, self.table_name, "tsa_enabled", data["tsa_enabled"])
57-
if tsa_status != data["tsa_enabled"]:
58-
self.cfg_mgr.commit()
59-
self.cfg_mgr.update()
60-
self.isolate_unisolate_device(data["tsa_enabled"])
61-
62-
63-
if "idf_isolation_state" in data:
64-
self.directory.put(self.db_name, self.table_name, "idf_isolation_state", data["idf_isolation_state"])
65-
if idf_isolation_state != data["idf_isolation_state"]:
66-
if self.switch_type and self.switch_type != "SpineRouter":
67-
log_debug("DeviceGlobalCfgMgr:: Skipping IDF isolation configuration on Switch type: %s" % self.switch_type)
68-
return True
69-
self.downstream_isolate_unisolate(data["idf_isolation_state"])
70-
7172
return True
7273

7374
def del_handler(self, key):
7475
log_debug("DeviceGlobalCfgMgr:: del handler")
76+
77+
# TSA configuration
78+
self.configure_tsa()
79+
# W-ECMP configuration
80+
self.configure_wcmp()
81+
# IDF configuration
82+
self.configure_idf()
83+
84+
return True
85+
86+
def is_update_required(self, key, value):
87+
if self.directory.path_exist(self.db_name, self.table_name, key):
88+
return value != self.directory.get(self.db_name, self.table_name, key)
89+
return True
90+
91+
def configure_tsa(self, data=None):
92+
""" Configure TSA feature"""
93+
94+
state = self.TSA_DEFAULTS
95+
96+
if data is not None:
97+
if "tsa_enabled" in data:
98+
state = data["tsa_enabled"]
99+
100+
if self.is_update_required("tsa_enabled", state):
101+
self.cfg_mgr.commit()
102+
self.cfg_mgr.update()
103+
if self.isolate_unisolate_device(state):
104+
self.directory.put(self.db_name, self.table_name, "tsa_enabled", state)
105+
else:
106+
log_notice("DeviceGlobalCfgMgr:: TSA configuration is up-to-date")
107+
108+
def configure_wcmp(self, data=None):
109+
""" Configure W-ECMP feature"""
110+
111+
state = self.WCMP_DEFAULTS
112+
113+
if data is not None:
114+
if "wcmp_enabled" in data:
115+
state = data["wcmp_enabled"]
116+
117+
if self.is_update_required("wcmp_enabled", state):
118+
if self.set_wcmp(state):
119+
self.directory.put(self.db_name, self.table_name, "wcmp_enabled", state)
120+
else:
121+
log_notice("DeviceGlobalCfgMgr:: W-ECMP configuration is up-to-date")
122+
123+
def configure_idf(self, data=None):
124+
""" Configure IDF feature"""
125+
126+
state = self.IDF_DEFAULTS
127+
128+
if data is not None:
129+
if "idf_isolation_state" in data:
130+
state = data["idf_isolation_state"]
131+
132+
if self.is_update_required("idf_isolation_state", state):
133+
if self.downstream_isolate_unisolate(state):
134+
self.directory.put(self.db_name, self.table_name, "idf_isolation_state", state)
135+
else:
136+
log_notice("DeviceGlobalCfgMgr:: IDF configuration is up-to-date")
137+
138+
def set_wcmp(self, status):
139+
""" API to set/unset W-ECMP """
140+
141+
if status not in ["true", "false"]:
142+
log_err("W-ECMP: invalid value({}) is provided".format(status))
143+
return False
144+
145+
if status == "true":
146+
log_notice("DeviceGlobalCfgMgr:: Enabling W-ECMP...")
147+
else:
148+
log_notice("DeviceGlobalCfgMgr:: Disabling W-ECMP...")
149+
150+
cmd = "\n"
151+
152+
try:
153+
cmd += self.wcmp_template.render(wcmp_enabled=status)
154+
except jinja2.TemplateError as e:
155+
msg = "W-ECMP: error in template rendering"
156+
log_err("%s: %s" % (msg, str(e)))
157+
return False
158+
159+
self.cfg_mgr.push(cmd)
160+
161+
log_debug("DeviceGlobalCfgMgr::Done")
162+
75163
return True
76164

77165
def check_state_and_get_tsa_routemaps(self, cfg):
@@ -87,6 +175,11 @@ def check_state_and_get_tsa_routemaps(self, cfg):
87175

88176
def isolate_unisolate_device(self, tsa_status):
89177
""" API to get TSA/TSB route-maps and apply configuration"""
178+
179+
if tsa_status not in ["true", "false"]:
180+
log_err("TSA: invalid value({}) is provided".format(tsa_status))
181+
return False
182+
90183
cmd = "\n"
91184
if tsa_status == "true":
92185
log_notice("DeviceGlobalCfgMgr:: Device isolated. Executing TSA")
@@ -98,6 +191,8 @@ def isolate_unisolate_device(self, tsa_status):
98191
self.cfg_mgr.push(cmd)
99192
log_debug("DeviceGlobalCfgMgr::Done")
100193

194+
return True
195+
101196
def get_ts_routemaps(self, cmds, ts_template):
102197
if not cmds:
103198
return ""
@@ -134,6 +229,16 @@ def __extract_out_route_map_names(self, cmds):
134229
return route_map_names
135230

136231
def downstream_isolate_unisolate(self, idf_isolation_state):
232+
""" API to apply IDF configuration """
233+
234+
if idf_isolation_state not in ["unisolated", "isolated_withdraw_all", "isolated_no_export"]:
235+
log_err("IDF: invalid value({}) is provided".format(idf_isolation_state))
236+
return False
237+
238+
if self.switch_type and self.switch_type != "SpineRouter":
239+
log_debug("DeviceGlobalCfgMgr:: Skipping IDF isolation configuration on Switch type: %s" % self.switch_type)
240+
return True
241+
137242
cmd = "\n"
138243
if idf_isolation_state == "unisolated":
139244
cmd += self.idf_unisolate_template.render(constants=self.constants)
@@ -145,12 +250,16 @@ def downstream_isolate_unisolate(self, idf_isolation_state):
145250
self.cfg_mgr.push(cmd)
146251
log_debug("DeviceGlobalCfgMgr::Done")
147252

253+
return True
254+
148255
def check_state_and_get_idf_isolation_routemaps(self):
149256
""" API to get TSA route-maps if device is isolated"""
257+
150258
cmd = ""
151259
if self.directory.path_exist("CONFIG_DB", swsscommon.CFG_BGP_DEVICE_GLOBAL_TABLE_NAME, "idf_isolation_state"):
152260
idf_isolation_state = self.directory.get_slot("CONFIG_DB", swsscommon.CFG_BGP_DEVICE_GLOBAL_TABLE_NAME)["idf_isolation_state"]
153-
if idf_isolation_state != "unisolated":
261+
if idf_isolation_state != "unisolated":
154262
log_notice("DeviceGlobalCfgMgr:: IDF is isolated. Applying required route-maps")
155-
cmd = self.idf_isolate_template.render(isolation_status=idf_isolation_state, constants=self.constants)
156-
return cmd
263+
cmd = self.idf_isolate_template.render(isolation_status=idf_isolation_state, constants=self.constants)
264+
265+
return cmd
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
!
3+
! template: bgpd/wcmp/bgpd.wcmp.conf.j2
4+
!
5+
route-map TO_BGP_PEER_V4 permit 100
6+
set extcommunity bandwidth num-multipaths
7+
exit
8+
!
9+
route-map TO_BGP_PEER_V6 permit 100
10+
set extcommunity bandwidth num-multipaths
11+
exit
12+
!
13+
! end of template: bgpd/wcmp/bgpd.wcmp.conf.j2
14+
!
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
!
3+
! template: bgpd/wcmp/bgpd.wcmp.conf.j2
4+
!
5+
route-map TO_BGP_PEER_V4 permit 100
6+
no set extcommunity bandwidth
7+
exit
8+
!
9+
route-map TO_BGP_PEER_V6 permit 100
10+
no set extcommunity bandwidth
11+
exit
12+
!
13+
! end of template: bgpd/wcmp/bgpd.wcmp.conf.j2
14+
!

0 commit comments

Comments
 (0)