This repository has been archived on 2025-03-20. You can view files and clone it, but cannot push or open issues or pull requests.
sonic-buildimage/src/sonic-bgpcfgd/bgpcfgd/managers_bgp.py
pavel-shirshov 969f77cf3e [bgpcfgd]: Extract classes into their own files. Run bgpcfgd as a module (#5535)
1. Rename app module to bgpcfgd
2. Extract classes from one file to the module
2020-10-06 11:21:00 -07:00

391 lines
16 KiB
Python

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
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")
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
return self.update_entity(policy, "Routing policy for peer '%s'" % name)
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)
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
else:
cmd = ('router bgp %s vrf %s\n' % (kwargs['bgp_asn'], kwargs['vrf'])) + pg
return self.update_entity(cmd, "Peer-group for peer '%s'" % name)
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:
"""
ret_code = self.cfg_mgr.push(cmd)
if ret_code:
log_info("%s was updated" % txt)
else:
log_err("Can't update %s" % txt)
return ret_code
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_LOOPBACK_INTERFACE_TABLE_NAME, "Loopback0"),
("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"))
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_lo0_ipv4()
if lo0_ipv4 is None:
log_warn("Loopback0 ipv4 address is not presented yet")
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),
'constants': self.constants,
'bgp_asn': bgp_asn,
'vrf': vrf,
'neighbor_addr': nbr,
'bgp_session': data,
'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:
ret_code = self.apply_op(cmd, vrf)
key = (vrf, nbr)
if ret_code:
self.peers.add(key)
log_info("Peer '(%s|%s)' added with attributes '%s'" % print_data)
else:
log_err("Peer '(%s|%s)' wasn't added." % (vrf, nbr))
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"]
if vrf == 'default':
cmd = ('router bgp %s\n' % bgp_asn) + cmd
else:
cmd = ('router bgp %s vrf %s\n' % (bgp_asn, vrf)) + cmd
return self.cfg_mgr.push(cmd)
def get_lo0_ipv4(self):
"""
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).iterkeys():
if loopback.startswith("Loopback0|"):
loopback0_prefix_str = loopback.replace("Loopback0|", "")
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 local_address.has_key("interface") 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 interface.has_key("vnet_name") 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", "-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