-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
Copy pathmanagers_bgp.py
410 lines (369 loc) · 17.6 KB
/
managers_bgp.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
import json
from swsscommon import swsscommon
import jinja2
import netaddr
from .log import log_warn, log_err, log_info, log_debug, log_crit
from .manager import Manager
from .template import TemplateFabric
from .utils import run_command
from .managers_device_global import DeviceGlobalCfgMgr
class BGPPeerGroupMgr(object):
""" This class represents peer-group and routing policy for the peer_type """
def __init__(self, common_objs, base_template):
"""
Construct the object
:param common_objs: common objects
:param base_template: path to the directory with Jinja2 templates
"""
self.cfg_mgr = common_objs['cfg_mgr']
self.constants = common_objs['constants']
tf = common_objs['tf']
self.policy_template = tf.from_file(base_template + "policies.conf.j2")
self.peergroup_template = tf.from_file(base_template + "peer-group.conf.j2")
self.device_global_cfgmgr = DeviceGlobalCfgMgr(common_objs, "CONFIG_DB", swsscommon.CFG_BGP_DEVICE_GLOBAL_TABLE_NAME)
def update(self, name, **kwargs):
"""
Update peer-group and routing policy for the peer with the name
:param name: name of the peer. Used for logging only
:param kwargs: dictionary with parameters for rendering
"""
rc_policy = self.update_policy(name, **kwargs)
rc_pg = self.update_pg(name, **kwargs)
return rc_policy and rc_pg
def update_policy(self, name, **kwargs):
"""
Update routing policy for the peer
:param name: name of the peer. Used for logging only
:param kwargs: dictionary with parameters for rendering
"""
try:
policy = self.policy_template.render(**kwargs)
except jinja2.TemplateError as e:
log_err("Can't render policy template name: '%s': %s" % (name, str(e)))
return False
self.update_entity(policy, "Routing policy for peer '%s'" % name)
return True
def update_pg(self, name, **kwargs):
"""
Update peer-group for the peer
:param name: name of the peer. Used for logging only
:param kwargs: dictionary with parameters for rendering
"""
try:
pg = self.peergroup_template.render(**kwargs)
tsa_rm = self.device_global_cfgmgr.check_state_and_get_tsa_routemaps(pg)
idf_isolation_rm = self.device_global_cfgmgr.check_state_and_get_idf_isolation_routemaps()
except jinja2.TemplateError as e:
log_err("Can't render peer-group template: '%s': %s" % (name, str(e)))
return False
if kwargs['vrf'] == 'default':
cmd = ('router bgp %s\n' % kwargs['bgp_asn']) + pg + tsa_rm + idf_isolation_rm
else:
cmd = ('router bgp %s vrf %s\n' % (kwargs['bgp_asn'], kwargs['vrf'])) + pg + tsa_rm + idf_isolation_rm
self.update_entity(cmd, "Peer-group for peer '%s'" % name)
return True
def update_entity(self, cmd, txt):
"""
Send commands to FRR
:param cmd: commands to send in a raw form
:param txt: text for the syslog output
:return:
"""
self.cfg_mgr.push(cmd)
log_info("%s has been scheduled to be updated" % txt)
return True
class BGPPeerMgrBase(Manager):
""" Manager of BGP peers """
def __init__(self, common_objs, db_name, table_name, peer_type, check_neig_meta):
"""
Initialize the object
:param common_objs: common objects
:param table_name: name of the table with peers
:param peer_type: type of the peers. It is used to find right templates
"""
self.common_objs = common_objs
self.constants = self.common_objs["constants"]
self.fabric = common_objs['tf']
self.peer_type = peer_type
base_template = "bgpd/templates/" + self.constants["bgp"]["peers"][peer_type]["template_dir"] + "/"
self.templates = {
"add": self.fabric.from_file(base_template + "instance.conf.j2"),
"delete": self.fabric.from_string('no neighbor {{ neighbor_addr }}'),
"shutdown": self.fabric.from_string('neighbor {{ neighbor_addr }} shutdown'),
"no shutdown": self.fabric.from_string('no neighbor {{ neighbor_addr }} shutdown'),
}
deps = [
("CONFIG_DB", swsscommon.CFG_DEVICE_METADATA_TABLE_NAME, "localhost/bgp_asn"),
("CONFIG_DB", swsscommon.CFG_DEVICE_METADATA_TABLE_NAME, "localhost/type"),
("CONFIG_DB", swsscommon.CFG_LOOPBACK_INTERFACE_TABLE_NAME, "Loopback0"),
("CONFIG_DB", swsscommon.CFG_BGP_DEVICE_GLOBAL_TABLE_NAME, "tsa_enabled"),
("CONFIG_DB", swsscommon.CFG_BGP_DEVICE_GLOBAL_TABLE_NAME, "idf_isolation_state"),
("LOCAL", "local_addresses", ""),
("LOCAL", "interfaces", ""),
]
if check_neig_meta:
self.check_neig_meta = 'bgp' in self.constants \
and 'use_neighbors_meta' in self.constants['bgp'] \
and self.constants['bgp']['use_neighbors_meta']
else:
self.check_neig_meta = False
self.check_deployment_id = 'bgp' in self.constants \
and 'use_deployment_id' in self.constants['bgp'] \
and self.constants['bgp']['use_deployment_id']
if self.check_neig_meta:
deps.append(("CONFIG_DB", swsscommon.CFG_DEVICE_NEIGHBOR_METADATA_TABLE_NAME, ""))
if self.check_deployment_id:
deps.append(("CONFIG_DB", swsscommon.CFG_DEVICE_METADATA_TABLE_NAME, "localhost/deployment_id"))
if self.peer_type == 'internal':
deps.append(("CONFIG_DB", swsscommon.CFG_LOOPBACK_INTERFACE_TABLE_NAME, "Loopback4096"))
super(BGPPeerMgrBase, self).__init__(
common_objs,
deps,
db_name,
table_name,
)
self.peers = self.load_peers()
self.peer_group_mgr = BGPPeerGroupMgr(self.common_objs, base_template)
return
def set_handler(self, key, data):
"""
It runs on 'SET' command
:param key: key of the changed table
:param data: the data associated with the change
"""
vrf, nbr = self.split_key(key)
peer_key = (vrf, nbr)
if peer_key not in self.peers:
return self.add_peer(vrf, nbr, data)
else:
return self.update_peer(vrf, nbr, data)
def add_peer(self, vrf, nbr, data):
"""
Add a peer into FRR. This is used if the peer is not existed in FRR yet
:param vrf: vrf name. Name is equal "default" for the global vrf
:param nbr: neighbor ip address (name for dynamic peer type)
:param data: associated data
:return: True if this adding was successful, False otherwise
"""
print_data = vrf, nbr, data
bgp_asn = self.directory.get_slot("CONFIG_DB", swsscommon.CFG_DEVICE_METADATA_TABLE_NAME)["localhost"]["bgp_asn"]
#
lo0_ipv4 = self.get_lo_ipv4("Loopback0|")
if (lo0_ipv4 is None and "bgp_router_id"
not in self.directory.get_slot("CONFIG_DB", swsscommon.CFG_DEVICE_METADATA_TABLE_NAME)["localhost"]):
log_warn("Loopback0 ipv4 address is not presented yet and bgp_router_id not configured")
return False
#
if self.peer_type == 'internal':
lo4096_ipv4 = self.get_lo_ipv4("Loopback4096|")
if (lo4096_ipv4 is None and "bgp_router_id"
not in self.directory.get_slot("CONFIG_DB", swsscommon.CFG_DEVICE_METADATA_TABLE_NAME)["localhost"]):
log_warn("Loopback4096 ipv4 address is not presented yet and bgp_router_id not configured")
return False
if "local_addr" not in data:
log_warn("Peer %s. Missing attribute 'local_addr'" % nbr)
else:
# The bgp session that belongs to a vnet cannot be advertised as the default BGP session.
# So we need to check whether this bgp session belongs to a vnet.
data["local_addr"] = str(netaddr.IPNetwork(str(data["local_addr"])).ip)
interface = self.get_local_interface(data["local_addr"])
if not interface:
print_data = nbr, data["local_addr"]
log_debug("Peer '%s' with local address '%s' wait for the corresponding interface to be set" % print_data)
return False
vnet = self.get_vnet(interface)
if vnet:
# Ignore the bgp session that is in a vnet
log_info("Ignore the BGP peer '%s' as the interface '%s' is in vnet '%s'" % (nbr, interface, vnet))
return True
kwargs = {
'CONFIG_DB__DEVICE_METADATA': self.directory.get_slot("CONFIG_DB", swsscommon.CFG_DEVICE_METADATA_TABLE_NAME),
'CONFIG_DB__BGP_BBR': self.directory.get_slot('CONFIG_DB', 'BGP_BBR'),
'constants': self.constants,
'bgp_asn': bgp_asn,
'vrf': vrf,
'neighbor_addr': nbr,
'bgp_session': data,
'CONFIG_DB__LOOPBACK_INTERFACE':{ tuple(key.split('|')) : {} for key in self.directory.get_slot("CONFIG_DB", swsscommon.CFG_LOOPBACK_INTERFACE_TABLE_NAME)
if '|' in key }
}
if lo0_ipv4 is not None:
kwargs['loopback0_ipv4'] = lo0_ipv4
if self.check_neig_meta:
neigmeta = self.directory.get_slot("CONFIG_DB", swsscommon.CFG_DEVICE_NEIGHBOR_METADATA_TABLE_NAME)
if 'name' in data and data["name"] not in neigmeta:
log_info("DEVICE_NEIGHBOR_METADATA is not ready for neighbor '%s' - '%s'" % (nbr, data['name']))
return False
kwargs['CONFIG_DB__DEVICE_NEIGHBOR_METADATA'] = neigmeta
tag = data['name'] if 'name' in data else nbr
self.peer_group_mgr.update(tag, **kwargs)
try:
cmd = self.templates["add"].render(**kwargs)
except jinja2.TemplateError as e:
msg = "Peer '(%s|%s)'. Error in rendering the template for 'SET' command '%s'" % print_data
log_err("%s: %s" % (msg, str(e)))
return True
if cmd is not None:
self.apply_op(cmd, vrf)
key = (vrf, nbr)
self.peers.add(key)
log_info("Peer '(%s|%s)' has been scheduled to be added with attributes '%s'" % print_data)
return True
def update_peer(self, vrf, nbr, data):
"""
Update a peer. This is used when the peer is already in the FRR
Update support only "admin_status" for now
:param vrf: vrf name. Name is equal "default" for the global vrf
:param nbr: neighbor ip address (name for dynamic peer type)
:param data: associated data
:return: True if this adding was successful, False otherwise
"""
if "admin_status" in data:
self.change_admin_status(vrf, nbr, data)
else:
log_err("Peer '(%s|%s)': Can't update the peer. Only 'admin_status' attribute is supported" % (vrf, nbr))
return True
def change_admin_status(self, vrf, nbr, data):
"""
Change admin status of a peer
:param vrf: vrf name. Name is equal "default" for the global vrf
:param nbr: neighbor ip address (name for dynamic peer type)
:param data: associated data
:return: True if this adding was successful, False otherwise
"""
if data['admin_status'] == 'up':
self.apply_admin_status(vrf, nbr, "no shutdown", "up")
elif data['admin_status'] == 'down':
self.apply_admin_status(vrf, nbr, "shutdown", "down")
else:
print_data = vrf, nbr, data['admin_status']
log_err("Peer '%s|%s': Can't update the peer. It has wrong attribute value attr['admin_status'] = '%s'" % print_data)
def apply_admin_status(self, vrf, nbr, template_name, admin_state):
"""
Render admin state template and apply the command to the FRR
:param vrf: vrf name. Name is equal "default" for the global vrf
:param nbr: neighbor ip address (name for dynamic peer type)
:param template_name: name of the template to render
:param admin_state: desired admin state
:return: True if this adding was successful, False otherwise
"""
print_data = vrf, nbr, admin_state
ret_code = self.apply_op(self.templates[template_name].render(neighbor_addr=nbr), vrf)
if ret_code:
log_info("Peer '%s|%s' admin state is set to '%s'" % print_data)
else:
log_err("Can't set peer '%s|%s' admin state to '%s'." % print_data)
def del_handler(self, key):
"""
'DEL' handler for the BGP PEER tables
:param key: key of the neighbor
"""
vrf, nbr = self.split_key(key)
peer_key = (vrf, nbr)
if peer_key not in self.peers:
log_warn("Peer '(%s|%s)' has not been found" % (vrf, nbr))
return
cmd = self.templates["delete"].render(neighbor_addr=nbr)
ret_code = self.apply_op(cmd, vrf)
if ret_code:
log_info("Peer '(%s|%s)' has been removed" % (vrf, nbr))
self.peers.remove(peer_key)
else:
log_err("Peer '(%s|%s)' hasn't been removed" % (vrf, nbr))
def apply_op(self, cmd, vrf):
"""
Push commands cmd into FRR
:param cmd: commands in raw format
:param vrf: vrf where the commands should be applied
:return: True if no errors, False if there are errors
"""
bgp_asn = self.directory.get_slot("CONFIG_DB", swsscommon.CFG_DEVICE_METADATA_TABLE_NAME)["localhost"]["bgp_asn"]
enable_bgp_suppress_fib_pending_cmd = 'bgp suppress-fib-pending'
if vrf == 'default':
cmd = ('router bgp %s\n %s\n' % (bgp_asn, enable_bgp_suppress_fib_pending_cmd)) + cmd
else:
cmd = ('router bgp %s vrf %s\n %s\n' % (bgp_asn, vrf, enable_bgp_suppress_fib_pending_cmd)) + cmd
self.cfg_mgr.push(cmd)
return True
def get_lo_ipv4(self, loopback_str):
"""
Extract Loopback0 ipv4 address from the Directory
:return: ipv4 address for Loopback0, None if nothing found
"""
loopback0_ipv4 = None
for loopback in self.directory.get_slot("CONFIG_DB", swsscommon.CFG_LOOPBACK_INTERFACE_TABLE_NAME).keys():
if loopback.startswith(loopback_str):
loopback0_prefix_str = loopback.replace(loopback_str, "")
loopback0_ip_str = loopback0_prefix_str[:loopback0_prefix_str.find('/')]
if TemplateFabric.is_ipv4(loopback0_ip_str):
loopback0_ipv4 = loopback0_ip_str
break
return loopback0_ipv4
def get_local_interface(self, local_addr):
"""
Get interface according to the local address from the directory
:param: directory: Directory object that stored metadata of interfaces
:param: local_addr: Local address of the interface
:return: Return the metadata of the interface with the local address
If the interface has not been set, return None
"""
local_addresses = self.directory.get_slot("LOCAL", "local_addresses")
# Check if the local address of this bgp session has been set
if local_addr not in local_addresses:
return None
local_address = local_addresses[local_addr]
interfaces = self.directory.get_slot("LOCAL", "interfaces")
# Check if the information for the interface of this local address has been set
if "interface" in local_address and local_address["interface"] in interfaces:
return interfaces[local_address["interface"]]
else:
return None
@staticmethod
def get_vnet(interface):
"""
Get the VNet name of the interface
:param: interface: The metadata of the interface
:return: Return the vnet name of the interface if this interface belongs to a vnet,
Otherwise return None
"""
if "vnet_name" in interface and interface["vnet_name"]:
return interface["vnet_name"]
else:
return None
@staticmethod
def split_key(key):
"""
Split key into ip address and vrf name. If there is no vrf, "default" would be return for vrf
:param key: key to split
:return: vrf name extracted from the key, peer ip address extracted from the key
"""
if '|' not in key:
return 'default', key
else:
return tuple(key.split('|', 1))
@staticmethod
def load_peers():
"""
Load peers from FRR.
:return: set of peers, which are already installed in FRR
"""
command = ["vtysh", "-H", "/dev/null", "-c", "show bgp vrfs json"]
ret_code, out, err = run_command(command)
if ret_code == 0:
js_vrf = json.loads(out)
vrfs = js_vrf['vrfs'].keys()
else:
log_crit("Can't read bgp vrfs: %s" % err)
raise Exception("Can't read bgp vrfs: %s" % err)
peers = set()
for vrf in vrfs:
command = ["vtysh", "-c", 'show bgp vrf %s neighbors json' % str(vrf)]
ret_code, out, err = run_command(command)
if ret_code == 0:
js_bgp = json.loads(out)
for nbr in js_bgp.keys():
peers.add((vrf, nbr))
else:
log_crit("Can't read vrf '%s' neighbors: %s" % (vrf, str(err)))
raise Exception("Can't read vrf '%s' neighbors: %s" % (vrf, str(err)))
return peers