Skip to content

Commit ee2b1e5

Browse files
jipanyanglguohan
authored andcommitted
[warm-reboot]: add bgp eoiu support to speed up route reconcile (sonic-net#856)
Three PRs for adding BGP eoiu support to speed up route reconciliation in fpmsyncd sonic-buildimage: sonic-net#2823 sonic-swss-common: sonic-net/sonic-swss-common#273 sonic-swss: sonic-net#856 Why I did it Similar to restore_neigbors.py for neigborsyncd, start a bgp_eoiu_mark.py for bgp docker. The script check bgp neighbor state via cli interface periodically (every 1 second) It looks for explicit EOR and implicit EOR (keep alive after established) in the json output of show ip bgp neighbors A.B.C.D json Once the script has collected all needed EORs, it set a EOIU flag in stateDB. fpmsyncd could hold a few seconds (3 seconds) after getting the flag before starting routing reconciliation. For any reason the script failed to set EOIU flag in stateDB, the current warm_restart bgp_timer will kick in later. This approach may have a few more seconds delay compared with the FRR embedded EOIU solution, but simple and less risk. Signed-off-by: Jipan Yang <[email protected]>
1 parent 9143018 commit ee2b1e5

File tree

5 files changed

+463
-10
lines changed

5 files changed

+463
-10
lines changed

debian/swss.install

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
swssconfig/sample/netbouncer.json etc/swss/config.d
22
swssconfig/sample/00-copp.config.json etc/swss/config.d
33
neighsyncd/restore_neighbors.py usr/bin
4+
fpmsyncd/bgp_eoiu_marker.py usr/bin

doc/swss-schema.md

+28-1
Original file line numberDiff line numberDiff line change
@@ -809,7 +809,7 @@ Stores information for physical switch ports managed by the switch chip. Ports t
809809
;Stores application and orchdameon warm start status
810810
;Status: work in progress
811811

812-
key = WARM_RESTART_TABLE:process_name ; process_name is a unique process identifier.
812+
key = WARM_RESTART_TABLE|process_name ; process_name is a unique process identifier.
813813
; with exception of 'warm-shutdown' operation.
814814
; 'warm-shutdown' operation key is used to
815815
; track warm shutdown stages and results.
@@ -839,6 +839,33 @@ Stores information for physical switch ports managed by the switch chip. Ports t
839839
key = NEIGH_RESTORE_TABLE|Flags
840840
restored = "true" / "false" ; restored state
841841

842+
### BGP\_STATE\_TABLE
843+
;Stores bgp status
844+
;Status: work in progress
845+
846+
key = BGP_STATE_TABLE|family|eoiu ; family = "IPv4" / "IPv6" ; address family.
847+
848+
state = "unknown" / "reached" / "consumed" ; unknown: eoiu state not fetched yet.
849+
; reached: bgp eoiu done.
850+
;
851+
; consumed: the reached state has been consumed by application.
852+
timestamp = time-stamp ; "%Y-%m-%d %H:%M:%S", full-date and partial-time separated by
853+
; white space. Example: 2019-04-25 09:39:19
854+
855+
;value annotations
856+
date-fullyear = 4DIGIT
857+
date-month = 2DIGIT ; 01-12
858+
date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on
859+
; month/year
860+
time-hour = 2DIGIT ; 00-23
861+
time-minute = 2DIGIT ; 00-59
862+
time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second
863+
; rules
864+
865+
partial-time = time-hour ":" time-minute ":" time-second
866+
full-date = date-fullyear "-" date-month "-" date-mday
867+
time-stamp = full-date %x20 partial-time
868+
842869
## Configuration files
843870
What configuration files should we have? Do apps, orch agent each need separate files?
844871

fpmsyncd/bgp_eoiu_marker.py

+221
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
#!/usr/bin/env python
2+
3+
""""
4+
Description: bgp_eoiu_marker.py -- populating bgp eoiu marker flags in stateDB during warm reboot.
5+
The script is started by supervisord in bgp docker when the docker is started.
6+
It does not do anything in case neither system nor bgp warm restart is enabled.
7+
8+
The script check bgp neighbor state via vtysh cli interface periodically (every 1 second).
9+
It looks for explicit EOR and implicit EOR (keep alive after established) in the json output of show ip bgp neighbors A.B.C.D json
10+
11+
Once the script has collected all needed EORs, it set a EOIU flags in stateDB.
12+
13+
fpmsyncd may hold a few seconds (2~5 seconds) after getting the flag before starting routing reconciliation.
14+
2-5 seconds should be enough for all the route to be synced to fpmsyncd from bgp. If not, the system probably is already in wrong state.
15+
16+
For any reason the script failed to set EOIU flag in stateDB, the current warm_restart bgp_timer will kick in later.
17+
"""
18+
19+
import sys
20+
import swsssdk
21+
import time
22+
import syslog
23+
import traceback
24+
import commands
25+
import json
26+
from swsscommon import swsscommon
27+
import errno
28+
from time import gmtime, strftime
29+
30+
class BgpStateCheck():
31+
# timeout the restore process in 120 seconds if not finished
32+
# This is in consistent with the default timerout for bgp warm restart set in fpmsyncd
33+
34+
DEF_TIME_OUT = 120
35+
36+
# every 1 seconds to check bgp neighbors state
37+
CHECK_INTERVAL = 1
38+
def __init__(self):
39+
self.ipv4_neighbors = []
40+
self.ipv4_neigh_eor_status = {}
41+
self.ipv6_neighbors = []
42+
self.ipv6_neigh_eor_status = {}
43+
self.keepalivesRecvCnt = {}
44+
self.bgp_ipv4_eoiu = False
45+
self.bgp_ipv6_eoiu = False
46+
self.get_peers_wt = self.DEF_TIME_OUT
47+
48+
def get_all_peers(self):
49+
while self.get_peers_wt >= 0:
50+
try:
51+
cmd = "vtysh -c 'show bgp summary json'"
52+
output = commands.getoutput(cmd)
53+
peer_info = json.loads(output)
54+
if "ipv4Unicast" in peer_info and "peers" in peer_info["ipv4Unicast"]:
55+
self.ipv4_neighbors = peer_info["ipv4Unicast"]["peers"].keys()
56+
57+
if "ipv6Unicast" in peer_info and "peers" in peer_info["ipv6Unicast"]:
58+
self.ipv6_neighbors = peer_info["ipv6Unicast"]["peers"].keys()
59+
60+
syslog.syslog('BGP ipv4 neighbors: {}'.format(self.ipv4_neighbors))
61+
syslog.syslog('BGP ipv4 neighbors: {}'.format(self.ipv6_neighbors))
62+
return
63+
64+
except Exception:
65+
syslog.syslog(syslog.LOG_ERR, "*ERROR* get_all_peers Exception: %s" % (traceback.format_exc()))
66+
time.sleep(5)
67+
self.get_peers_wt -= 5
68+
self.get_all_peers()
69+
syslog.syslog(syslog.LOG_ERR, "Failed to get bgp neighbor info in {} seconds, exiting".format(self.DEF_TIME_OUT));
70+
sys.exit(1)
71+
72+
def init_peers_eor_status(self):
73+
# init neigh eor status to unknown
74+
for neigh in self.ipv4_neighbors:
75+
self.ipv4_neigh_eor_status[neigh] = "unknown"
76+
for neigh in self.ipv6_neighbors:
77+
self.ipv6_neigh_eor_status[neigh] = "unknown"
78+
79+
# Set the statedb "BGP_STATE_TABLE|eoiu", so fpmsyncd can get the bgp eoiu signal
80+
# Only two families: 'ipv4' and 'ipv6'
81+
# state is "unknown" / "reached" / "consumed"
82+
def set_bgp_eoiu_marker(self, family, state):
83+
db = swsssdk.SonicV2Connector(host='127.0.0.1')
84+
db.connect(db.STATE_DB, False)
85+
key = "BGP_STATE_TABLE|%s|eoiu" % family
86+
db.set(db.STATE_DB, key, 'state', state)
87+
timesamp = strftime("%Y-%m-%d %H:%M:%S", gmtime())
88+
db.set(db.STATE_DB, key, 'timestamp', timesamp)
89+
db.close(db.STATE_DB)
90+
return
91+
92+
def clean_bgp_eoiu_marker(self):
93+
db = swsssdk.SonicV2Connector(host='127.0.0.1')
94+
db.connect(db.STATE_DB, False)
95+
db.delete(db.STATE_DB, "BGP_STATE_TABLE|IPv4|eoiu")
96+
db.delete(db.STATE_DB, "BGP_STATE_TABLE|IPv6|eoiu")
97+
db.close(db.STATE_DB)
98+
syslog.syslog('Cleaned ipv4 and ipv6 eoiu marker flags')
99+
return
100+
101+
def bgp_eor_received(self, neigh, is_ipv4):
102+
try:
103+
neighstr = "%s" % neigh
104+
eor_received = False
105+
cmd = "vtysh -c 'show bgp neighbors %s json'" % neighstr
106+
output = commands.getoutput(cmd)
107+
neig_status = json.loads(output)
108+
if neighstr in neig_status:
109+
if "gracefulRestartInfo" in neig_status[neighstr]:
110+
if "endOfRibRecv" in neig_status[neighstr]["gracefulRestartInfo"]:
111+
eor_info = neig_status[neighstr]["gracefulRestartInfo"]["endOfRibRecv"]
112+
if is_ipv4 and "IPv4 Unicast" in eor_info and eor_info["IPv4 Unicast"] == True:
113+
eor_received = True
114+
elif not is_ipv4 and "IPv6 Unicast" in eor_info and eor_info["IPv6 Unicast"] == True:
115+
eor_received = True
116+
if eor_received:
117+
syslog.syslog('BGP eor received for neighbors: {}'.format(neigh))
118+
119+
# No explict eor, try implicit eor
120+
if eor_received == False and "bgpState" in neig_status[neighstr] and neig_status[neighstr]["bgpState"] == "Established":
121+
# if "messageStats" in neig_status and "keepalivesRecv" in neig_status["messageStats"]:
122+
# it looks we need to record the keepalivesRecv count for detecting count change
123+
if neighstr not in self.keepalivesRecvCnt:
124+
self.keepalivesRecvCnt[neighstr] = neig_status[neighstr]["messageStats"]["keepalivesRecv"]
125+
else:
126+
eor_received = (self.keepalivesRecvCnt[neighstr] is not neig_status[neighstr]["messageStats"]["keepalivesRecv"])
127+
if eor_received:
128+
syslog.syslog('BGP implicit eor received for neighbors: {}'.format(neigh))
129+
130+
return eor_received
131+
132+
except Exception:
133+
syslog.syslog(syslog.LOG_ERR, "*ERROR* bgp_eor_received Exception: %s" % (traceback.format_exc()))
134+
135+
136+
# This function is to collect eor state based on the saved ipv4_neigh_eor_status and ipv6_neigh_eor_status dictionaries
137+
# It iterates through the dictionary, and check whether the specific neighbor has EOR received.
138+
# EOR may be explicit EOR (End-Of-RIB) or an implicit-EOR.
139+
# The first keep-alive after BGP has reached Established is considered an implicit-EOR.
140+
#
141+
# ipv4 and ipv6 neighbors are processed separately.
142+
# Once all ipv4 neighbors have EOR received, bgp_ipv4_eoiu becomes True.
143+
# Once all ipv6 neighbors have EOR received, bgp_ipv6_eoiu becomes True.
144+
145+
# The neighbor EoR states were checked in a loop with an interval (CHECK_INTERVAL)
146+
# The function will timeout in case eoiu states never meet the condition
147+
# after some time (DEF_TIME_OUT).
148+
def wait_for_bgp_eoiu(self):
149+
wait_time = self.DEF_TIME_OUT
150+
while wait_time >= 0:
151+
if not self.bgp_ipv4_eoiu:
152+
for neigh, eor_status in self.ipv4_neigh_eor_status.items():
153+
if eor_status == "unknown" and self.bgp_eor_received(neigh, True):
154+
self.ipv4_neigh_eor_status[neigh] = "rcvd"
155+
if "unknown" not in self.ipv4_neigh_eor_status.values():
156+
self.bgp_ipv4_eoiu = True
157+
syslog.syslog("BGP ipv4 eoiu reached")
158+
159+
if not self.bgp_ipv6_eoiu:
160+
for neigh, eor_status in self.ipv6_neigh_eor_status.items():
161+
if eor_status == "unknown" and self.bgp_eor_received(neigh, False):
162+
self.ipv6_neigh_eor_status[neigh] = "rcvd"
163+
if "unknown" not in self.ipv6_neigh_eor_status.values():
164+
self.bgp_ipv6_eoiu = True
165+
syslog.syslog('BGP ipv6 eoiu reached')
166+
167+
if self.bgp_ipv6_eoiu and self.bgp_ipv4_eoiu:
168+
break;
169+
time.sleep(self.CHECK_INTERVAL)
170+
wait_time -= self.CHECK_INTERVAL
171+
172+
if not self.bgp_ipv6_eoiu:
173+
syslog.syslog(syslog.LOG_ERR, "BGP ipv6 eoiu not reached: {}".format(self.ipv6_neigh_eor_status));
174+
175+
if not self.bgp_ipv4_eoiu:
176+
syslog.syslog(syslog.LOG_ERR, "BGP ipv4 eoiu not reached: {}".format(self.ipv4_neigh_eor_status));
177+
178+
def main():
179+
180+
print "bgp_eoiu_marker service is started"
181+
182+
try:
183+
bgp_state_check = BgpStateCheck()
184+
except Exception, e:
185+
syslog.syslog(syslog.LOG_ERR, "{}: error exit 1, reason {}".format(THIS_MODULE, str(e)))
186+
exit(1)
187+
188+
# Always clean the eoiu marker in stateDB first
189+
bgp_state_check.clean_bgp_eoiu_marker()
190+
191+
# Use warmstart python binding to check warmstart information
192+
warmstart = swsscommon.WarmStart()
193+
warmstart.initialize("bgp", "bgp")
194+
warmstart.checkWarmStart("bgp", "bgp", False)
195+
196+
# if bgp or system warm reboot not enabled, don't run
197+
if not warmstart.isWarmStart():
198+
print "bgp_eoiu_marker service is skipped as warm restart not enabled"
199+
return
200+
201+
bgp_state_check.set_bgp_eoiu_marker("IPv4", "unknown")
202+
bgp_state_check.set_bgp_eoiu_marker("IPv6", "unknown")
203+
bgp_state_check.get_all_peers()
204+
bgp_state_check.init_peers_eor_status()
205+
try:
206+
bgp_state_check.wait_for_bgp_eoiu()
207+
except Exception as e:
208+
syslog.syslog(syslog.LOG_ERR, str(e))
209+
sys.exit(1)
210+
211+
# set statedb to signal other processes like fpmsynd
212+
if bgp_state_check.bgp_ipv4_eoiu:
213+
bgp_state_check.set_bgp_eoiu_marker("IPv4", "reached")
214+
if bgp_state_check.bgp_ipv6_eoiu:
215+
bgp_state_check.set_bgp_eoiu_marker("IPv6", "reached")
216+
217+
print "bgp_eoiu_marker service is done"
218+
return
219+
220+
if __name__ == '__main__':
221+
main()

0 commit comments

Comments
 (0)