391 lines
16 KiB
Python
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 |