Skip to content

Commit 5410c2d

Browse files
Adding logic to update state db table during peer creation/deletion/update
Signed-off-by: Sudarshan Kumar (from Dev Box) <[email protected]>
1 parent 99e0696 commit 5410c2d

File tree

3 files changed

+107
-31
lines changed

3 files changed

+107
-31
lines changed

dockers/docker-fpm-frr/frr/bgpd/templates/dynamic/instance_update.conf.j2 renamed to dockers/docker-fpm-frr/frr/bgpd/templates/dynamic/update.conf.j2

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
!
2-
! template: bgpd/templates/dynamic/instance_update.conf.j2
2+
! template: bgpd/templates/dynamic/update.conf.j2
33
!
44
{% for ip_range in delete_ranges %}
55
no bgp listen range {{ ip_range }} peer-group {{ bgp_session['name'] }}
@@ -8,5 +8,5 @@
88
bgp listen range {{ ip_range }} peer-group {{ bgp_session['name'] }}
99
{% endfor %}
1010
!
11-
! end of template: bgpd/templates/dynamic/instance_update.conf.j2
11+
! end of template: bgpd/templates/dynamic/update.conf.j2
1212
!

src/sonic-bgpcfgd/bgpcfgd/managers_bgp.py

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,14 @@ def __init__(self, common_objs, db_name, table_name, peer_type, check_neig_meta)
101101
base_template = "bgpd/templates/" + self.constants["bgp"]["peers"][peer_type]["template_dir"] + "/"
102102
self.templates = {
103103
"add": self.fabric.from_file(base_template + "instance.conf.j2"),
104-
"update": self.fabric.from_file(base_template + "instance_update.conf.j2"),
105104
"delete": self.fabric.from_string('no neighbor {{ neighbor_addr }}'),
106105
"shutdown": self.fabric.from_string('neighbor {{ neighbor_addr }} shutdown'),
107106
"no shutdown": self.fabric.from_string('no neighbor {{ neighbor_addr }} shutdown'),
108107
}
109108

109+
if (os.path.exists(self.fabric.env.loader.searchpath[0] + "/" + base_template + "update.conf.j2")):
110+
self.templates["update"] = self.fabric.from_file(base_template + "update.conf.j2")
111+
110112
deps = [
111113
("CONFIG_DB", swsscommon.CFG_DEVICE_METADATA_TABLE_NAME, "localhost/bgp_asn"),
112114
("CONFIG_DB", swsscommon.CFG_DEVICE_METADATA_TABLE_NAME, "localhost/type"),
@@ -229,10 +231,46 @@ def add_peer(self, vrf, nbr, data):
229231
self.apply_op(cmd, vrf)
230232
key = (vrf, nbr)
231233
self.peers.add(key)
234+
self.update_state_db(vrf, nbr, data, "SET")
232235
log_info("Peer '(%s|%s)' has been scheduled to be added with attributes '%s'" % print_data)
233236

234237
return True
235238

239+
def update_state_db(self, vrf, nbr, data, op):
240+
"""
241+
Update the database with the new data
242+
:param vrf: vrf name. Name is equal "default" for the global vrf
243+
:param nbr: neighbor ip address (name for dynamic peer type)
244+
:param data: associated data (will be empty for deletion case)
245+
:param op: operation type. It can be "SET" or "DEL"
246+
:return: True if this adding was successful, False otherwise
247+
"""
248+
if (vrf == "default"):
249+
key = nbr
250+
else:
251+
key = vrf + "|" + nbr
252+
# Update the peer in the STATE_DB table
253+
try:
254+
state_db = swsscommon.DBConnector("STATE_DB", 0)
255+
state_peer_table = swsscommon.Table(state_db, swsscommon.STATE_BGP_PEER_CONFIGURED_TABLE_NAME)
256+
if (op == "SET"):
257+
state_peer_table.set(key, list(sorted(data.items())))
258+
log_info("Peer '(%s)' has been added to BGP_PEER_CONFIGURED_TABLE with attributes '%s'" % (key, data))
259+
elif (op == "DEL"):
260+
(status, fvs) = state_peer_table.get(key)
261+
if status == True:
262+
state_peer_table.delete(key)
263+
log_info("Peer '(%s)' has been deleted from BGP_PEER_CONFIGURED_TABLE" % (key))
264+
else:
265+
log_warn("Peer '(%s)' not found in BGP_PEER_CONFIGURED_TABLE" % (key))
266+
else:
267+
log_err("Update state DB called for Peer '(%s)' with unkown operation '%s'" % (key, op))
268+
return False
269+
return True
270+
except Exception as e:
271+
log_err("Update of state db failed for peer '(%s)' with error: %s" % (key, str(e)))
272+
return False
273+
236274
def update_peer(self, vrf, nbr, data):
237275
"""
238276
Update a peer. This is used when the peer is already in the FRR
@@ -244,11 +282,8 @@ def update_peer(self, vrf, nbr, data):
244282
"""
245283
if "admin_status" in data:
246284
self.change_admin_status(vrf, nbr, data)
247-
elif "ip_range" in data and self.peer_type == 'dynamic':
248-
if (os.path.exists(self.templates["update"])):
249-
self.change_ip_range(vrf, nbr, data)
250-
else:
251-
log_err("Peer '(%s|%s)': Can't update the peer. Template 'update' doesn't exist" % (vrf, nbr))
285+
elif "update" in self.templates and "ip_range" in data and self.peer_type == 'dynamic':
286+
self.change_ip_range(vrf, nbr, data)
252287
else:
253288
log_err("Peer '(%s|%s)': Can't update the peer. Only 'admin_status' attribute is supported" % (vrf, nbr))
254289

@@ -263,14 +298,14 @@ def change_admin_status(self, vrf, nbr, data):
263298
:return: True if this adding was successful, False otherwise
264299
"""
265300
if data['admin_status'] == 'up':
266-
self.apply_admin_status(vrf, nbr, "no shutdown", "up")
301+
self.apply_admin_status(vrf, nbr, "no shutdown", "up", data)
267302
elif data['admin_status'] == 'down':
268-
self.apply_admin_status(vrf, nbr, "shutdown", "down")
303+
self.apply_admin_status(vrf, nbr, "shutdown", "down", data)
269304
else:
270305
print_data = vrf, nbr, data['admin_status']
271306
log_err("Peer '%s|%s': Can't update the peer. It has wrong attribute value attr['admin_status'] = '%s'" % print_data)
272307

273-
def apply_admin_status(self, vrf, nbr, template_name, admin_state):
308+
def apply_admin_status(self, vrf, nbr, template_name, admin_state, data):
274309
"""
275310
Render admin state template and apply the command to the FRR
276311
:param vrf: vrf name. Name is equal "default" for the global vrf
@@ -282,6 +317,7 @@ def apply_admin_status(self, vrf, nbr, template_name, admin_state):
282317
print_data = vrf, nbr, admin_state
283318
ret_code = self.apply_op(self.templates[template_name].render(neighbor_addr=nbr), vrf)
284319
if ret_code:
320+
self.update_state_db(vrf, nbr, data, "SET")
285321
log_info("Peer '%s|%s' admin state is set to '%s'" % print_data)
286322
else:
287323
log_err("Can't set peer '%s|%s' admin state to '%s'." % print_data)
@@ -321,7 +357,7 @@ def get_existing_ip_ranges(self, vrf, nbr):
321357
Get existing ip range of a peer
322358
:param vrf: vrf name. Name is equal "default" for the global vrf
323359
:param nbr: neighbor ip address (name for dynamic peer type)
324-
:return: existing ip range of a peer
360+
:return: existing ipv4 and ipv6 ranges of a peer if they exist.
325361
"""
326362
ipv4_ranges = []
327363
ipv6_ranges = []
@@ -334,10 +370,10 @@ def get_existing_ip_ranges(self, vrf, nbr):
334370
if ret_code == 0:
335371
js_bgp = json.loads(out)
336372
if nbr in js_bgp and 'dynamicRanges' in js_bgp[nbr] and 'IPv4' in js_bgp[nbr]['dynamicRanges'] and 'ranges' in js_bgp[nbr]['dynamicRanges']['IPv4']:
337-
ipv4_ranges= js_bgp[nbr]['dynamicRanges']['IPv4']['ranges']
373+
ipv4_ranges = js_bgp[nbr]['dynamicRanges']['IPv4']['ranges']
338374
log_info("Peer '(%s|%s)' already has ipV4 range: %s" % (vrf, nbr, ipv4_ranges))
339375
if nbr in js_bgp and 'dynamicRanges' in js_bgp[nbr] and 'IPv6' in js_bgp[nbr]['dynamicRanges'] and 'ranges' in js_bgp[nbr]['dynamicRanges']['IPv6']:
340-
ipv6_ranges= js_bgp[nbr]['dynamicRanges']['IPv6']['ranges']
376+
ipv6_ranges = js_bgp[nbr]['dynamicRanges']['IPv6']['ranges']
341377
log_info("Peer '(%s|%s)' already has ipV6 range: %s" % (vrf, nbr, ipv6_ranges))
342378
return ipv4_ranges, ipv6_ranges
343379
else:
@@ -357,22 +393,22 @@ def apply_range_changes(self, vrf, nbr, new_ip_range, ip_ranges_to_del, data):
357393
"""
358394
print_data = vrf, nbr, data
359395
kwargs = {
360-
'operation': 'update',
396+
'CONFIG_DB__DEVICE_METADATA': self.directory.get_slot("CONFIG_DB", swsscommon.CFG_DEVICE_METADATA_TABLE_NAME),
361397
'vrf': vrf,
362-
'neighbor_addr': nbr,
363398
'bgp_session': data,
364399
'delete_ranges': ip_ranges_to_del,
365400
'add_ranges': new_ip_range
366401
}
367402
try:
368-
cmd = self.templates["add"].render(**kwargs)
403+
cmd = self.templates["update"].render(**kwargs)
369404
except jinja2.TemplateError as e:
370405
msg = "Peer '(%s|%s)'. Error in rendering the template for 'SET' command '%s'" % print_data
371406
log_err("%s: %s" % (msg, str(e)))
372407
return True
373408
if cmd is not None:
374409
self.apply_op(cmd, vrf)
375-
log_info("Peer '(%s|%s)' ip range has been scheduled to be updated with new range '%s'" % (vrf, nbr, new_ip_range))
410+
self.update_state_db(vrf, nbr, data, "SET")
411+
log_info("Peer '(%s|%s)' ip range has been scheduled to be updated with range '%s'" % (vrf, nbr, data['ip_range']))
376412

377413
def del_handler(self, key):
378414
"""
@@ -387,6 +423,7 @@ def del_handler(self, key):
387423
cmd = self.templates["delete"].render(neighbor_addr=nbr)
388424
ret_code = self.apply_op(cmd, vrf)
389425
if ret_code:
426+
self.update_state_db(vrf, nbr, {}, "DEL")
390427
log_info("Peer '(%s|%s)' has been removed" % (vrf, nbr))
391428
self.peers.remove(peer_key)
392429
else:

src/sonic-bgpcfgd/tests/test_bgp.py

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ def constructor(constants_path, bgp_router_id="", peer_type="general", with_lo0_
3333

3434
return_value_map = {
3535
"['vtysh', '-H', '/dev/null', '-c', 'show bgp vrfs json']": (0, "{\"vrfs\": {\"default\": {}}}", ""),
36-
"['vtysh', '-c', 'show bgp vrf default neighbors json']": (0, "{\"10.10.10.1\": {}, \"20.20.20.1\": {}, \"fc00:10::1\": {}}", "")
36+
"['vtysh', '-c', 'show bgp vrf default neighbors json']": (0, "{\"10.10.10.1\": {}, \"20.20.20.1\": {}, \"fc00:10::1\": {}, \"DynNbr1\": {}, \"DynNbr2\": {}}", ""),
37+
"['vtysh', '-c', 'show bgp peer-group DynNbr1 json']": (0, "{\"DynNbr1\":{\"dynamicRanges\":{\"IPv4\":{\"count\":1,\"ranges\":[\"10.255.0.0/24\"]}}}}", ""),
38+
"['vtysh', '-c', 'show bgp peer-group DynNbr2 json']": (0, "{\"DynNbr2\":{\"dynamicRanges\":{\"IPv4\":{\"count\":1,\"ranges\":[\"192.168.0.0/24\",\"192.168.1.0/24\"]}}}}", "")
3739
}
3840

3941
bgpcfgd.managers_bgp.run_command = lambda cmd: return_value_map[str(cmd)]
@@ -162,29 +164,66 @@ def test_add_peer_ipv6_in_vnet():
162164
for constant in load_constant_files():
163165
m = constructor(constant)
164166
res = m.set_handler("Vnet-10|fc00:20::1", {'asn': '65200', 'holdtime': '180', 'keepalive': '60', 'local_addr': 'fc00:20::20', 'name': 'TOR', 'nhopself': '0', 'rrclient': '0'})
165-
def test_add_dynamic_peer():
167+
168+
@patch('bgpcfgd.managers_bgp.log_info')
169+
def test_add_dynamic_peer(mocked_log_info):
166170
for constant in load_constant_files():
167171
m = constructor(constant, peer_type="dynamic")
168-
res = m.set_handler("BGPSLBPassive", {"peer_asn": "65200", "ip_range": "10.250.0.0/27"})
172+
m.check_neig_meta = False
173+
res = m.set_handler("BGPSLBPassive", {"peer_asn": "65200", "ip_range": "10.250.0.0/27", "name": "BGPSLBPassive", "src_address": "10.250.0.1"})
174+
mocked_log_info.assert_called_with("Peer '(default|BGPSLBPassive)' has been scheduled to be added with attributes '{'peer_asn': '65200', 'ip_range': '10.250.0.0/27', 'name': 'BGPSLBPassive', 'src_address': '10.250.0.1'}'")
169175
assert res, "Expect True return value"
170176

171-
def test_add_dynamic_peer_ipv6():
177+
@patch('bgpcfgd.managers_bgp.log_info')
178+
def test_add_dynamic_peer_ipv6(mocked_log_info):
172179
for constant in load_constant_files():
173180
m = constructor(constant, peer_type="dynamic")
174-
res = m.set_handler("BGPSLBPassive", {"peer_asn": "65200", "ip_range": "fc00:20::/64"})
181+
m.check_neig_meta = False
182+
res = m.set_handler("BGPSLBPassive", {"peer_asn": "65200", "ip_range": "fc00:20::/64", "name": "BGPSLBPassive", "src_address": "fc00:20::1"})
183+
mocked_log_info.assert_called_with("Peer '(default|BGPSLBPassive)' has been scheduled to be added with attributes '{'peer_asn': '65200', 'ip_range': 'fc00:20::/64', 'name': 'BGPSLBPassive', 'src_address': 'fc00:20::1'}'")
175184
assert res, "Expect True return value"
176185

177-
def test_modify_dynamic_peer_range():
186+
@patch('bgpcfgd.managers_bgp.log_info')
187+
@patch('bgpcfgd.managers_bgp.swsscommon.Table')
188+
@patch('bgpcfgd.managers_bgp.swsscommon.DBConnector')
189+
def modify_dynamic_peer_common(mock_db_conn, mock_table, mocked_log_info, peer, data, expected_cmds, expected_log):
178190
for constant in load_constant_files():
179191
m = constructor(constant, peer_type="dynamic")
180-
res = m.set_handler("BGPSLBPassive", {"peer_asn": "65200", "ip_range": "10.250.0.0/27"})
181-
assert res, "Expect True return value"
182-
res = m.set_handler("BGPSLBPassive", {"peer_asn": "65200", "ip_range": "10.250.0.0/27,10.251.0.0/27"})
183-
assert res, "Expect True return value"
184-
res = m.set_handler("BGPSLBPassive", {"peer_asn": "65200", "ip_range": "10.250.0.0/27,10.251.0.0/26"})
185-
assert res, "Expect True return value"
186-
res = m.set_handler("BGPSLBPassive", {"peer_asn": "65200", "ip_range": "10.251.0.0/27"})
192+
m.cfg_mgr.push = MagicMock(return_value = None)
193+
m.check_neig_meta = False
194+
swsscommon.STATE_BGP_PEER_CONFIGURED_TABLE_NAME = "BGP_PEER_CONFIGURED_TABLE"
195+
mock_state_db_table = MagicMock()
196+
mock_table.return_value = mock_state_db_table
197+
res = m.set_handler(peer, data)
187198
assert res, "Expect True return value"
199+
mock_state_db_table.set.assert_called_once_with(peer, list(sorted(data.items())))
200+
mocked_log_info.assert_called_with(expected_log)
201+
m.cfg_mgr.push.assert_called_once_with(expected_cmds)
202+
203+
def test_add_dynamic_peer_range():
204+
expected_cmds = 'router bgp 65100\n bgp suppress-fib-pending\n!\n! template: bgpd/templates/dynamic/update.conf.j2\n!\n\n\n' \
205+
' bgp listen range 10.255.1.0/24 peer-group DynNbr1\n\n!\n! end of template: bgpd/templates/dynamic/update.conf.j2\n!\nexit'
206+
data = {"peer_asn": "65200", "ip_range": "10.255.0.0/24,10.255.1.0/24", "name": "DynNbr1"}
207+
peer = "DynNbr1"
208+
expected_log = "Peer '(default|DynNbr1)' ip range has been scheduled to be updated with range '10.255.0.0/24,10.255.1.0/24'"
209+
modify_dynamic_peer_common(peer=peer, data=data, expected_cmds=expected_cmds, expected_log=expected_log)
210+
211+
def test_modify_dynamic_peer_range():
212+
expected_cmds = 'router bgp 65100\n bgp suppress-fib-pending\n!\n! template: bgpd/templates/dynamic/update.conf.j2\n!\n\n' \
213+
' no bgp listen range 10.255.0.0/24 peer-group DynNbr1\n\n\n bgp listen range 10.255.0.0/26 peer-group DynNbr1\n\n!\n!' \
214+
' end of template: bgpd/templates/dynamic/update.conf.j2\n!\nexit'
215+
data = {"peer_asn": "65200", "ip_range": "10.255.0.0/26", "name": "DynNbr1"}
216+
peer = "DynNbr1"
217+
expected_log = "Peer '(default|DynNbr1)' ip range has been scheduled to be updated with range '10.255.0.0/26'"
218+
modify_dynamic_peer_common(peer=peer, data=data, expected_cmds=expected_cmds, expected_log=expected_log)
219+
220+
def test_delete_dynamic_peer_range():
221+
expected_cmds = 'router bgp 65100\n bgp suppress-fib-pending\n!\n! template: bgpd/templates/dynamic/update.conf.j2\n!\n\n' \
222+
' no bgp listen range 192.168.1.0/24 peer-group DynNbr2\n\n\n!\n! end of template: bgpd/templates/dynamic/update.conf.j2\n!\nexit'
223+
data = {"peer_asn": "65200", "ip_range": "192.168.0.0/24", "name": "DynNbr2"}
224+
peer = "DynNbr2"
225+
expected_log = "Peer '(default|DynNbr2)' ip range has been scheduled to be updated with range '192.168.0.0/24'"
226+
modify_dynamic_peer_common(peer=peer, data=data, expected_cmds=expected_cmds, expected_log=expected_log)
188227

189228
@patch('bgpcfgd.managers_bgp.log_warn')
190229
def test_add_peer_no_local_addr(mocked_log_warn):

0 commit comments

Comments
 (0)