parent
719c8e68c8
commit
d592e9b0f8
@ -34,5 +34,5 @@
|
||||
neighbor {{ bgp_session['name'] }} activate
|
||||
exit-address-family
|
||||
!
|
||||
! end of template: bgpd/templates/BGP_SPEAKER/instance.conf.j2
|
||||
! end of template: bgpd/templates/dynamic/instance.conf.j2
|
||||
!
|
||||
|
@ -6,7 +6,7 @@
|
||||
{# set the bgp neighbor timers if they have not default values #}
|
||||
{% if (bgp_session['keepalive'] is defined and bgp_session['keepalive'] | int != 60)
|
||||
or (bgp_session['holdtime'] is defined and bgp_session['holdtime'] | int != 180) %}
|
||||
neighbor {{ neighbor_addr }} timers {{ bgp_session['keepalive'] }} {{ bgp_session['holdtime'] }}
|
||||
neighbor {{ neighbor_addr }} timers {{ bgp_session['keepalive'] | default("60") }} {{ bgp_session['holdtime'] | default("180") }}
|
||||
{% endif %}
|
||||
!
|
||||
{% if bgp_session.has_key('admin_status') and bgp_session['admin_status'] == 'down' or not bgp_session.has_key('admin_status') and CONFIG_DB__DEVICE_METADATA['localhost'].has_key('default_bgp_status') and CONFIG_DB__DEVICE_METADATA['localhost']['default_bgp_status'] == 'down' %}
|
||||
@ -15,36 +15,43 @@
|
||||
!
|
||||
{% if neighbor_addr | ipv4 %}
|
||||
address-family ipv4
|
||||
{% if 'ASIC' in bgp_session['name'] %}
|
||||
{% if 'ASIC' in bgp_session['name'] %}
|
||||
neighbor {{ neighbor_addr }} peer-group PEER_V4_INT
|
||||
{% else %}
|
||||
{% else %}
|
||||
neighbor {{ neighbor_addr }} peer-group PEER_V4
|
||||
{% endif %}
|
||||
!
|
||||
{% if CONFIG_DB__DEVICE_METADATA['localhost']['sub_role'] == 'BackEnd' %}
|
||||
neighbor {{ neighbor_addr }} route-map FROM_BGP_PEER_V4_INT in
|
||||
{% endif %}
|
||||
!
|
||||
{% elif neighbor_addr | ipv6 %}
|
||||
address-family ipv6
|
||||
{% if 'ASIC' in bgp_session['name'] %}
|
||||
{% if 'ASIC' in bgp_session['name'] %}
|
||||
neighbor {{ neighbor_addr }} peer-group PEER_V6_INT
|
||||
{% else %}
|
||||
{% else %}
|
||||
neighbor {{ neighbor_addr }} peer-group PEER_V6
|
||||
{% endif %}
|
||||
!
|
||||
{% if CONFIG_DB__DEVICE_METADATA['localhost']['sub_role'] == 'BackEnd' %}
|
||||
neighbor {{ neighbor_addr }} route-map FROM_BGP_PEER_V6_INT in
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
!
|
||||
{% if bgp_session['rrclient'] | int != 0 %}
|
||||
{% if bgp_session.has_key('rrclient') and bgp_session['rrclient'] | int != 0 %}
|
||||
neighbor {{ neighbor_addr }} route-reflector-client
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
!
|
||||
{% if bgp_session['nhopself'] | int != 0 %}
|
||||
{% if bgp_session.has_key('nhopself') and bgp_session['nhopself'] | int != 0 %}
|
||||
neighbor {{ neighbor_addr }} next-hop-self
|
||||
{% endif %}
|
||||
{% if 'ASIC' in bgp_session['name'] %}
|
||||
{% endif %}
|
||||
!
|
||||
{% if 'ASIC' in bgp_session['name'] %}
|
||||
neighbor {{ neighbor_addr }} next-hop-self force
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
!
|
||||
neighbor {{ neighbor_addr }} activate
|
||||
exit-address-family
|
||||
!
|
||||
{% if bgp_session["asn"] == bgp_asn and CONFIG_DB__DEVICE_METADATA['localhost']['type'] == "SpineChassisFrontendRouter" %}
|
||||
address-family l2vpn evpn
|
||||
@ -52,8 +59,6 @@
|
||||
advertise-all-vni
|
||||
exit-address-family
|
||||
{% endif %}
|
||||
neighbor {{ neighbor_addr }} activate
|
||||
exit-address-family
|
||||
!
|
||||
! end of template: bgpd/templates/general/instance.conf.j2
|
||||
!
|
||||
|
2
src/sonic-bgpcfgd/.gitignore
vendored
2
src/sonic-bgpcfgd/.gitignore
vendored
@ -2,5 +2,7 @@
|
||||
build/
|
||||
dist/
|
||||
*.egg-info/
|
||||
app/*.pyc
|
||||
tests/*.pyc
|
||||
tests/__pycache__/
|
||||
.idea
|
||||
|
0
src/sonic-bgpcfgd/app/__init__.py
Normal file
0
src/sonic-bgpcfgd/app/__init__.py
Normal file
103
src/sonic-bgpcfgd/app/config.py
Normal file
103
src/sonic-bgpcfgd/app/config.py
Normal file
@ -0,0 +1,103 @@
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from .vars import g_debug
|
||||
from .log import log_crit, log_err
|
||||
from .util import run_command
|
||||
|
||||
|
||||
class ConfigMgr(object):
|
||||
""" The class represents frr configuration """
|
||||
def __init__(self):
|
||||
self.current_config = None
|
||||
|
||||
def reset(self):
|
||||
""" Reset stored config """
|
||||
self.current_config = None
|
||||
|
||||
def update(self):
|
||||
""" Read current config from FRR """
|
||||
self.current_config = None
|
||||
ret_code, out, err = run_command(["vtysh", "-c", "show running-config"])
|
||||
if ret_code != 0:
|
||||
log_crit("can't update running config: rc=%d out='%s' err='%s'" % (ret_code, out, err))
|
||||
return
|
||||
self.current_config = self.to_canonical(out)
|
||||
|
||||
def push(self, cmd):
|
||||
"""
|
||||
Push new changes to FRR
|
||||
:param cmd: configuration change for FRR. Type: String
|
||||
:return: True if change was applied successfully, False otherwise
|
||||
"""
|
||||
return self.write(cmd)
|
||||
|
||||
def write(self, cmd):
|
||||
"""
|
||||
Write configuration change to FRR.
|
||||
:param cmd: new configuration to write into FRR. Type: String
|
||||
:return: True if change was applied successfully, False otherwise
|
||||
"""
|
||||
fd, tmp_filename = tempfile.mkstemp(dir='/tmp')
|
||||
os.close(fd)
|
||||
with open(tmp_filename, 'w') as fp:
|
||||
fp.write("%s\n" % cmd)
|
||||
command = ["vtysh", "-f", tmp_filename]
|
||||
ret_code, out, err = run_command(command)
|
||||
if not g_debug:
|
||||
os.remove(tmp_filename)
|
||||
if ret_code != 0:
|
||||
err_tuple = str(cmd), ret_code, out, err
|
||||
log_err("ConfigMgr::push(): can't push configuration '%s', rc='%d', stdout='%s', stderr='%s'" % err_tuple)
|
||||
if ret_code == 0:
|
||||
self.current_config = None # invalidate config
|
||||
return ret_code == 0
|
||||
|
||||
@staticmethod
|
||||
def to_canonical(raw_config):
|
||||
"""
|
||||
Convert FRR config into canonical format
|
||||
:param raw_config: config in frr format
|
||||
:return: frr config in canonical format
|
||||
"""
|
||||
parsed_config = []
|
||||
lines_with_comments = raw_config.split("\n")
|
||||
lines = [line for line in lines_with_comments
|
||||
if not line.strip().startswith('!') and line.strip() != '']
|
||||
if len(lines) == 0:
|
||||
return []
|
||||
cur_path = [lines[0]]
|
||||
cur_offset = ConfigMgr.count_spaces(lines[0])
|
||||
for line in lines:
|
||||
n_spaces = ConfigMgr.count_spaces(line)
|
||||
s_line = line.strip()
|
||||
# assert(n_spaces == cur_offset or (n_spaces + 1) == cur_offset or (n_spaces - 1) == cur_offset)
|
||||
if n_spaces == cur_offset:
|
||||
cur_path[-1] = s_line
|
||||
elif n_spaces > cur_offset:
|
||||
cur_path.append(s_line)
|
||||
elif n_spaces < cur_offset:
|
||||
cur_path = cur_path[:-2]
|
||||
cur_path.append(s_line)
|
||||
parsed_config.append(cur_path[:])
|
||||
cur_offset = n_spaces
|
||||
return parsed_config
|
||||
|
||||
@staticmethod
|
||||
def count_spaces(line):
|
||||
""" Count leading spaces in the line """
|
||||
return len(line) - len(line.lstrip())
|
||||
|
||||
@staticmethod
|
||||
def from_canonical(canonical_config):
|
||||
"""
|
||||
Convert config from canonical format into FRR raw format
|
||||
:param canonical_config: config in a canonical format
|
||||
:return: config in the FRR raw format
|
||||
"""
|
||||
out = ""
|
||||
for lines in canonical_config:
|
||||
spaces = len(lines) - 1
|
||||
out += " " * spaces + lines[-1] + "\n"
|
||||
|
||||
return out
|
33
src/sonic-bgpcfgd/app/log.py
Normal file
33
src/sonic-bgpcfgd/app/log.py
Normal file
@ -0,0 +1,33 @@
|
||||
import syslog
|
||||
|
||||
from .vars import g_debug
|
||||
|
||||
def log_debug(msg):
|
||||
""" Send a message msg to the syslog as DEBUG """
|
||||
if g_debug:
|
||||
syslog.syslog(syslog.LOG_DEBUG, msg)
|
||||
|
||||
|
||||
def log_notice(msg):
|
||||
""" Send a message msg to the syslog as NOTICE """
|
||||
syslog.syslog(syslog.LOG_NOTICE, msg)
|
||||
|
||||
|
||||
def log_info(msg):
|
||||
""" Send a message msg to the syslog as INFO """
|
||||
syslog.syslog(syslog.LOG_INFO, msg)
|
||||
|
||||
|
||||
def log_warn(msg):
|
||||
""" Send a message msg to the syslog as WARNING """
|
||||
syslog.syslog(syslog.LOG_WARNING, msg)
|
||||
|
||||
|
||||
def log_err(msg):
|
||||
""" Send a message msg to the syslog as ERR """
|
||||
syslog.syslog(syslog.LOG_ERR, msg)
|
||||
|
||||
|
||||
def log_crit(msg):
|
||||
""" Send a message msg to the syslog as CRIT """
|
||||
syslog.syslog(syslog.LOG_CRIT, msg)
|
98
src/sonic-bgpcfgd/app/template.py
Normal file
98
src/sonic-bgpcfgd/app/template.py
Normal file
@ -0,0 +1,98 @@
|
||||
from collections import OrderedDict
|
||||
from functools import partial
|
||||
|
||||
import jinja2
|
||||
import netaddr
|
||||
|
||||
|
||||
class TemplateFabric(object):
|
||||
""" Fabric for rendering jinja2 templates """
|
||||
def __init__(self, template_path = '/usr/share/sonic/templates'):
|
||||
j2_template_paths = [template_path]
|
||||
j2_loader = jinja2.FileSystemLoader(j2_template_paths)
|
||||
j2_env = jinja2.Environment(loader=j2_loader, trim_blocks=False)
|
||||
j2_env.filters['ipv4'] = self.is_ipv4
|
||||
j2_env.filters['ipv6'] = self.is_ipv6
|
||||
j2_env.filters['pfx_filter'] = self.pfx_filter
|
||||
for attr in ['ip', 'network', 'prefixlen', 'netmask']:
|
||||
j2_env.filters[attr] = partial(self.prefix_attr, attr)
|
||||
self.env = j2_env
|
||||
|
||||
def from_file(self, filename):
|
||||
"""
|
||||
Read a template from a file
|
||||
:param filename: filename of the file. Type String
|
||||
:return: Jinja2 template object
|
||||
"""
|
||||
return self.env.get_template(filename)
|
||||
|
||||
def from_string(self, tmpl):
|
||||
"""
|
||||
Read a template from a string
|
||||
:param tmpl: Text representation of Jinja2 template
|
||||
:return: Jinja2 template object
|
||||
"""
|
||||
return self.env.from_string(tmpl)
|
||||
|
||||
@staticmethod
|
||||
def is_ipv4(value):
|
||||
""" Return True if the value is an ipv4 address """
|
||||
if not value:
|
||||
return False
|
||||
if isinstance(value, netaddr.IPNetwork):
|
||||
addr = value
|
||||
else:
|
||||
try:
|
||||
addr = netaddr.IPNetwork(str(value))
|
||||
except (netaddr.NotRegisteredError, netaddr.AddrFormatError, netaddr.AddrConversionError):
|
||||
return False
|
||||
return addr.version == 4
|
||||
|
||||
@staticmethod
|
||||
def is_ipv6(value):
|
||||
""" Return True if the value is an ipv6 address """
|
||||
if not value:
|
||||
return False
|
||||
if isinstance(value, netaddr.IPNetwork):
|
||||
addr = value
|
||||
else:
|
||||
try:
|
||||
addr = netaddr.IPNetwork(str(value))
|
||||
except (netaddr.NotRegisteredError, netaddr.AddrFormatError, netaddr.AddrConversionError):
|
||||
return False
|
||||
return addr.version == 6
|
||||
|
||||
@staticmethod
|
||||
def prefix_attr(attr, value):
|
||||
"""
|
||||
Extract attribute from IPNetwork object
|
||||
:param attr: attribute to extract
|
||||
:param value: the string representation of ip prefix which will be converted to IPNetwork.
|
||||
:return: the value of the extracted attribute
|
||||
"""
|
||||
if not value:
|
||||
return None
|
||||
else:
|
||||
try:
|
||||
prefix = netaddr.IPNetwork(str(value))
|
||||
except (netaddr.NotRegisteredError, netaddr.AddrFormatError, netaddr.AddrConversionError):
|
||||
return None
|
||||
return str(getattr(prefix, attr))
|
||||
|
||||
@staticmethod
|
||||
def pfx_filter(value):
|
||||
"""INTERFACE Table can have keys in one of the two formats:
|
||||
string or tuple - This filter skips the string keys and only
|
||||
take into account the tuple.
|
||||
For eg - VLAN_INTERFACE|Vlan1000 vs VLAN_INTERFACE|Vlan1000|192.168.0.1/21
|
||||
"""
|
||||
table = OrderedDict()
|
||||
|
||||
if not value:
|
||||
return table
|
||||
|
||||
for key, val in value.items():
|
||||
if not isinstance(key, tuple):
|
||||
continue
|
||||
table[key] = val
|
||||
return table
|
22
src/sonic-bgpcfgd/app/util.py
Normal file
22
src/sonic-bgpcfgd/app/util.py
Normal file
@ -0,0 +1,22 @@
|
||||
import subprocess
|
||||
|
||||
from .log import log_debug, log_err
|
||||
|
||||
|
||||
def run_command(command, shell=False, hide_errors=False):
|
||||
"""
|
||||
Run a linux command. The command is defined as a list. See subprocess.Popen documentation on format
|
||||
:param command: command to execute. Type: List of strings
|
||||
:param shell: execute the command through shell when True. Type: Boolean
|
||||
:param hide_errors: don't report errors to syslog when True. Type: Boolean
|
||||
:return: Tuple: integer exit code from the command, stdout as a string, stderr as a string
|
||||
"""
|
||||
log_debug("execute command '%s'." % str(command))
|
||||
p = subprocess.Popen(command, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
stdout, stderr = p.communicate()
|
||||
if p.returncode != 0:
|
||||
if not hide_errors:
|
||||
print_tuple = p.returncode, str(command), stdout, stderr
|
||||
log_err("command execution returned %d. Command: '%s', stdout: '%s', stderr: '%s'" % print_tuple)
|
||||
|
||||
return p.returncode, stdout, stderr
|
1
src/sonic-bgpcfgd/app/vars.py
Normal file
1
src/sonic-bgpcfgd/app/vars.py
Normal file
@ -0,0 +1 @@
|
||||
g_debug = False
|
@ -1,260 +1,27 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
import subprocess
|
||||
import datetime
|
||||
import time
|
||||
import syslog
|
||||
import signal
|
||||
import traceback
|
||||
import os
|
||||
import tempfile
|
||||
import json
|
||||
from collections import defaultdict, OrderedDict
|
||||
from pprint import pprint
|
||||
from functools import partial
|
||||
from collections import defaultdict
|
||||
|
||||
import yaml
|
||||
import jinja2
|
||||
import netaddr
|
||||
from swsscommon import swsscommon
|
||||
|
||||
from app.vars import g_debug
|
||||
from app.log import log_debug, log_notice, log_info, log_warn, log_err, log_crit
|
||||
from app.template import TemplateFabric
|
||||
from app.config import ConfigMgr
|
||||
from app.util import run_command
|
||||
|
||||
g_run = True
|
||||
g_debug = False
|
||||
|
||||
|
||||
def log_debug(msg):
|
||||
""" Send a message msg to the syslog as DEBUG """
|
||||
if g_debug:
|
||||
syslog.syslog(syslog.LOG_DEBUG, msg)
|
||||
|
||||
def log_notice(msg):
|
||||
""" Send a message msg to the syslog as NOTICE """
|
||||
syslog.syslog(syslog.LOG_NOTICE, msg)
|
||||
|
||||
def log_info(msg):
|
||||
""" Send a message msg to the syslog as INFO """
|
||||
syslog.syslog(syslog.LOG_INFO, msg)
|
||||
|
||||
def log_warn(msg):
|
||||
""" Send a message msg to the syslog as WARNING """
|
||||
syslog.syslog(syslog.LOG_WARNING, msg)
|
||||
|
||||
def log_err(msg):
|
||||
""" Send a message msg to the syslog as ERR """
|
||||
syslog.syslog(syslog.LOG_ERR, msg)
|
||||
|
||||
def log_crit(msg):
|
||||
""" Send a message msg to the syslog as CRIT """
|
||||
syslog.syslog(syslog.LOG_CRIT, msg)
|
||||
|
||||
|
||||
def run_command(command, shell=False, hide_errors=False):
|
||||
"""
|
||||
Run a linux command. The command is defined as a list. See subprocess.Popen documentation on format
|
||||
:param command: command to execute. Type: List of strings
|
||||
:param shell: execute the command through shell when True. Type: Boolean
|
||||
:param hide_errors: don't report errors to syslog when True. Type: Boolean
|
||||
:return: Tuple: integer exit code from the command, stdout as a string, stderr as a string
|
||||
"""
|
||||
log_debug("execute command '%s'." % str(command))
|
||||
p = subprocess.Popen(command, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
stdout, stderr = p.communicate()
|
||||
if p.returncode != 0:
|
||||
if not hide_errors:
|
||||
print_tuple = p.returncode, str(command), stdout, stderr
|
||||
log_err("command execution returned %d. Command: '%s', stdout: '%s', stderr: '%s'" % print_tuple)
|
||||
|
||||
return p.returncode, stdout, stderr
|
||||
|
||||
|
||||
class ConfigMgr(object):
|
||||
""" The class represents frr configuration """
|
||||
def __init__(self):
|
||||
self.current_config = None
|
||||
|
||||
def reset(self):
|
||||
""" Reset stored config """
|
||||
self.current_config = None
|
||||
|
||||
def update(self):
|
||||
""" Read current config from FRR """
|
||||
self.current_config = None
|
||||
ret_code, out, err = run_command(["vtysh", "-c", "show running-config"])
|
||||
if ret_code != 0:
|
||||
log_crit("can't update running config: rc=%d out='%s' err='%s'" % (ret_code, out, err))
|
||||
return
|
||||
self.current_config = self.to_canonical(out)
|
||||
|
||||
def push(self, cmd):
|
||||
"""
|
||||
Push new changes to FRR
|
||||
:param cmd: configuration change for FRR. Type: String
|
||||
:return: True if change was applied successfully, False otherwise
|
||||
"""
|
||||
return self.write(cmd)
|
||||
|
||||
def write(self, cmd):
|
||||
"""
|
||||
Write configuration change to FRR.
|
||||
:param cmd: new configuration to write into FRR. Type: String
|
||||
:return: True if change was applied successfully, False otherwise
|
||||
"""
|
||||
fd, tmp_filename = tempfile.mkstemp(dir='/tmp')
|
||||
os.close(fd)
|
||||
with open(tmp_filename, 'w') as fp:
|
||||
fp.write("%s\n" % cmd)
|
||||
command = ["vtysh", "-f", tmp_filename]
|
||||
ret_code, out, err = run_command(command)
|
||||
if not g_debug:
|
||||
os.remove(tmp_filename)
|
||||
if ret_code != 0:
|
||||
err_tuple = str(cmd), ret_code, out, err
|
||||
log_err("ConfigMgr::push(): can't push configuration '%s', rc='%d', stdout='%s', stderr='%s'" % err_tuple)
|
||||
if ret_code == 0:
|
||||
self.current_config = None # invalidate config
|
||||
return ret_code == 0
|
||||
|
||||
@staticmethod
|
||||
def to_canonical(raw_config):
|
||||
"""
|
||||
Convert FRR config into canonical format
|
||||
:param raw_config: config in frr format
|
||||
:return: frr config in canonical format
|
||||
"""
|
||||
parsed_config = []
|
||||
cur_offset = 0
|
||||
lines = raw_config.split("\n")
|
||||
cur_path = [lines[0]]
|
||||
for line in lines:
|
||||
if line.strip().startswith('!') or line.strip() == '':
|
||||
continue
|
||||
n_spaces = ConfigMgr.count_spaces(line)
|
||||
s_line = line.strip()
|
||||
assert(n_spaces == cur_offset or (n_spaces + 1) == cur_offset or (n_spaces - 1) == cur_offset)
|
||||
if n_spaces == cur_offset:
|
||||
cur_path[-1] = s_line
|
||||
elif n_spaces > cur_offset:
|
||||
cur_path.append(s_line)
|
||||
elif n_spaces < cur_offset:
|
||||
cur_path = cur_path[:-2]
|
||||
cur_path.append(s_line)
|
||||
parsed_config.append(cur_path[:])
|
||||
cur_offset = n_spaces
|
||||
return parsed_config
|
||||
|
||||
@staticmethod
|
||||
def count_spaces(line):
|
||||
""" Count leading spaces in the line """
|
||||
return len(line) - len(line.lstrip())
|
||||
|
||||
@staticmethod
|
||||
def from_canonical(canonical_config):
|
||||
"""
|
||||
Convert config from canonical format into FRR raw format
|
||||
:param canonical_config: config in a canonical format
|
||||
:return: config in the FRR raw format
|
||||
"""
|
||||
out = ""
|
||||
for lines in canonical_config:
|
||||
spaces = len(lines) - 1
|
||||
out += " " * spaces + lines[-1] + "\n"
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class TemplateFabric(object):
|
||||
""" Fabric for rendering jinja2 templates """
|
||||
def __init__(self):
|
||||
j2_template_paths = ['/usr/share/sonic/templates']
|
||||
j2_loader = jinja2.FileSystemLoader(j2_template_paths)
|
||||
j2_env = jinja2.Environment(loader=j2_loader, trim_blocks=False)
|
||||
j2_env.filters['ipv4'] = self.is_ipv4
|
||||
j2_env.filters['ipv6'] = self.is_ipv6
|
||||
j2_env.filters['pfx_filter'] = self.pfx_filter
|
||||
for attr in ['ip', 'network', 'prefixlen', 'netmask']:
|
||||
j2_env.filters[attr] = partial(self.prefix_attr, attr)
|
||||
self.env = j2_env
|
||||
|
||||
def from_file(self, filename):
|
||||
"""
|
||||
Read a template from a file
|
||||
:param filename: filename of the file. Type String
|
||||
:return: Jinja2 template object
|
||||
"""
|
||||
return self.env.get_template(filename)
|
||||
|
||||
def from_string(self, tmpl):
|
||||
"""
|
||||
Read a template from a string
|
||||
:param tmpl: Text representation of Jinja2 template
|
||||
:return: Jinja2 template object
|
||||
"""
|
||||
return self.env.from_string(tmpl)
|
||||
|
||||
@staticmethod
|
||||
def is_ipv4(value):
|
||||
""" Return True if the value is an ipv4 address """
|
||||
if not value:
|
||||
return False
|
||||
if isinstance(value, netaddr.IPNetwork):
|
||||
addr = value
|
||||
else:
|
||||
try:
|
||||
addr = netaddr.IPNetwork(str(value))
|
||||
except (netaddr.NotRegisteredError, netaddr.AddrFormatError, netaddr.AddrConversionError):
|
||||
return False
|
||||
return addr.version == 4
|
||||
|
||||
@staticmethod
|
||||
def is_ipv6(value):
|
||||
""" Return True if the value is an ipv6 address """
|
||||
if not value:
|
||||
return False
|
||||
if isinstance(value, netaddr.IPNetwork):
|
||||
addr = value
|
||||
else:
|
||||
try:
|
||||
addr = netaddr.IPNetwork(str(value))
|
||||
except (netaddr.NotRegisteredError, netaddr.AddrFormatError, netaddr.AddrConversionError):
|
||||
return False
|
||||
return addr.version == 6
|
||||
|
||||
@staticmethod
|
||||
def prefix_attr(attr, value):
|
||||
"""
|
||||
Extract attribute from IPNetwork object
|
||||
:param attr: attribute to extract
|
||||
:param value: the string representation of ip prefix which will be converted to IPNetwork.
|
||||
:return: the value of the extracted attribute
|
||||
"""
|
||||
if not value:
|
||||
return None
|
||||
else:
|
||||
try:
|
||||
prefix = netaddr.IPNetwork(str(value))
|
||||
except (netaddr.NotRegisteredError, netaddr.AddrFormatError, netaddr.AddrConversionError):
|
||||
return None
|
||||
return str(getattr(prefix, attr))
|
||||
|
||||
@staticmethod
|
||||
def pfx_filter(value):
|
||||
"""INTERFACE Table can have keys in one of the two formats:
|
||||
string or tuple - This filter skips the string keys and only
|
||||
take into account the tuple.
|
||||
For eg - VLAN_INTERFACE|Vlan1000 vs VLAN_INTERFACE|Vlan1000|192.168.0.1/21
|
||||
"""
|
||||
table = OrderedDict()
|
||||
|
||||
if not value:
|
||||
return table
|
||||
|
||||
for key, val in value.items():
|
||||
if not isinstance(key, tuple):
|
||||
continue
|
||||
table[key] = val
|
||||
return table
|
||||
|
||||
|
||||
class Directory(object):
|
||||
|
@ -1,13 +1,14 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from setuptools import setup
|
||||
import setuptools
|
||||
|
||||
setup(name='sonic-bgpcfgd',
|
||||
setuptools.setup(name='sonic-bgpcfgd',
|
||||
version='1.0',
|
||||
description='Utility to dynamically generate BGP configuration for FRR',
|
||||
author='Pavel Shirshov',
|
||||
author_email='pavelsh@microsoft.com',
|
||||
url='https://github.com/Azure/sonic-buildimage',
|
||||
packages=setuptools.find_packages(),
|
||||
scripts=['bgpcfgd'],
|
||||
install_requires=['jinja2>=2.10', 'netaddr', 'pyyaml'],
|
||||
setup_requires=['pytest-runner', 'pytest'],
|
||||
|
@ -0,0 +1,21 @@
|
||||
{
|
||||
"CONFIG_DB__DEVICE_METADATA": {
|
||||
"localhost": {
|
||||
"deployment_id": "5"
|
||||
}
|
||||
},
|
||||
"CONFIG_DB__LOOPBACK_INTERFACE": {
|
||||
"Loopback1|55.55.55.55/32": {}
|
||||
},
|
||||
"bgp_session": {
|
||||
"ip_range": "10.10.20.0/24,20.20.20.0/24",
|
||||
"name": "dyn_name",
|
||||
"peer_asn": "11111",
|
||||
"src_address": "1.1.1.1"
|
||||
},
|
||||
"constants": {
|
||||
"deployment_id_asn_map": {
|
||||
"5": "51111"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
{
|
||||
"CONFIG_DB__DEVICE_METADATA": {
|
||||
"localhost": {
|
||||
"deployment_id": "5"
|
||||
}
|
||||
},
|
||||
"CONFIG_DB__LOOPBACK_INTERFACE": {
|
||||
"Loopback1|55.55.55.55/32": {}
|
||||
},
|
||||
"bgp_session": {
|
||||
"ip_range": "10.10.20.0/24,20.20.20.0/24",
|
||||
"name": "dyn_name"
|
||||
},
|
||||
"constants": {
|
||||
"deployment_id_asn_map": {
|
||||
"5": "51111"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
!
|
||||
! template: bgpd/templates/dynamic/instance.conf.j2
|
||||
!
|
||||
neighbor dyn_name peer-group
|
||||
neighbor dyn_name passive
|
||||
neighbor dyn_name ebgp-multihop 255
|
||||
neighbor dyn_name soft-reconfiguration inbound
|
||||
neighbor dyn_name route-map FROM_BGP_SPEAKER in
|
||||
neighbor dyn_name route-map TO_BGP_SPEAKER out
|
||||
neighbor dyn_name remote-as 11111
|
||||
bgp listen range 10.10.20.0/24 peer-group dyn_name
|
||||
bgp listen range 20.20.20.0/24 peer-group dyn_name
|
||||
neighbor dyn_name update-source 1.1.1.1
|
||||
address-family ipv4
|
||||
neighbor dyn_name activate
|
||||
exit-address-family
|
||||
address-family ipv6
|
||||
neighbor dyn_name activate
|
||||
exit-address-family
|
||||
!
|
||||
! end of template: bgpd/templates/dynamic/instance.conf.j2
|
||||
!
|
@ -0,0 +1,22 @@
|
||||
!
|
||||
! template: bgpd/templates/dynamic/instance.conf.j2
|
||||
!
|
||||
neighbor dyn_name peer-group
|
||||
neighbor dyn_name passive
|
||||
neighbor dyn_name ebgp-multihop 255
|
||||
neighbor dyn_name soft-reconfiguration inbound
|
||||
neighbor dyn_name route-map FROM_BGP_SPEAKER in
|
||||
neighbor dyn_name route-map TO_BGP_SPEAKER out
|
||||
neighbor dyn_name remote-as 51111
|
||||
bgp listen range 10.10.20.0/24 peer-group dyn_name
|
||||
bgp listen range 20.20.20.0/24 peer-group dyn_name
|
||||
neighbor dyn_name update-source 55.55.55.55
|
||||
address-family ipv4
|
||||
neighbor dyn_name activate
|
||||
exit-address-family
|
||||
address-family ipv6
|
||||
neighbor dyn_name activate
|
||||
exit-address-family
|
||||
!
|
||||
! end of template: bgpd/templates/dynamic/instance.conf.j2
|
||||
!
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1,7 @@
|
||||
!
|
||||
! template: bgpd/templates/BGP_SPEAKER/peer-group.conf.j2
|
||||
!
|
||||
! nothing is here
|
||||
!
|
||||
! end of template: bgpd/templates/BGP_SPEAKER/peer-group.conf.j2
|
||||
!
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1,9 @@
|
||||
!
|
||||
! template: bgpd/templates/BGP_SPEAKER/policies.conf.j2
|
||||
!
|
||||
route-map FROM_BGP_SPEAKER permit 10
|
||||
!
|
||||
route-map TO_BGP_SPEAKER deny 1
|
||||
!
|
||||
! end of template: bgpd/templates/BGP_SPEAKER/policies.conf.j2
|
||||
!
|
@ -0,0 +1,15 @@
|
||||
{
|
||||
"CONFIG_DB__DEVICE_METADATA": {
|
||||
"localhost": {}
|
||||
},
|
||||
"neighbor_addr": "10.10.10.10",
|
||||
"bgp_session": {
|
||||
"asn": "555",
|
||||
"name": "_ASIC_"
|
||||
},
|
||||
"constants": {
|
||||
"deployment_id_asn_map": {
|
||||
"5": "51111"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
{
|
||||
"CONFIG_DB__DEVICE_METADATA": {
|
||||
"localhost": {}
|
||||
},
|
||||
"neighbor_addr": "FC00::",
|
||||
"bgp_session": {
|
||||
"asn": "555",
|
||||
"name": "_ASIC_"
|
||||
},
|
||||
"constants": {
|
||||
"deployment_id_asn_map": {
|
||||
"5": "51111"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
{
|
||||
"CONFIG_DB__DEVICE_METADATA": {
|
||||
"localhost": {
|
||||
"sub_role": "BackEnd"
|
||||
}
|
||||
},
|
||||
"neighbor_addr": "10.10.10.10",
|
||||
"bgp_session": {
|
||||
"asn": "555",
|
||||
"name": "remote_peer",
|
||||
"keepalive": "5",
|
||||
"holdtime": "30",
|
||||
"admin_status": "down",
|
||||
"ASIC": "something",
|
||||
"rrclient": "1",
|
||||
"nhopself": "1"
|
||||
},
|
||||
"constants": {
|
||||
"deployment_id_asn_map": {
|
||||
"5": "51111"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
{
|
||||
"CONFIG_DB__DEVICE_METADATA": {
|
||||
"localhost": {
|
||||
"sub_role": "BackEnd"
|
||||
}
|
||||
},
|
||||
"neighbor_addr": "fc::10",
|
||||
"bgp_session": {
|
||||
"asn": "555",
|
||||
"name": "remote_peer",
|
||||
"keepalive": "5",
|
||||
"holdtime": "30",
|
||||
"admin_status": "down",
|
||||
"ASIC": "something",
|
||||
"rrclient": "1",
|
||||
"nhopself": "1"
|
||||
},
|
||||
"constants": {
|
||||
"deployment_id_asn_map": {
|
||||
"5": "51111"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
{
|
||||
"CONFIG_DB__DEVICE_METADATA": {
|
||||
"localhost": {}
|
||||
},
|
||||
"neighbor_addr": "10.10.10.10",
|
||||
"bgp_session": {
|
||||
"asn": "555",
|
||||
"name": "remote_peer"
|
||||
},
|
||||
"constants": {
|
||||
"deployment_id_asn_map": {
|
||||
"5": "51111"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
{
|
||||
"CONFIG_DB__DEVICE_METADATA": {
|
||||
"localhost": {}
|
||||
},
|
||||
"neighbor_addr": "fc00::2",
|
||||
"bgp_session": {
|
||||
"asn": "555",
|
||||
"name": "remote_peer"
|
||||
},
|
||||
"constants": {
|
||||
"deployment_id_asn_map": {
|
||||
"5": "51111"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
{
|
||||
"CONFIG_DB__DEVICE_METADATA": {
|
||||
"localhost": {
|
||||
"type": "SpineChassisFrontendRouter"
|
||||
}
|
||||
},
|
||||
"neighbor_addr": "10.10.10.10",
|
||||
"bgp_session": {
|
||||
"asn": "555",
|
||||
"name": "remote_peer"
|
||||
},
|
||||
"constants": {
|
||||
"deployment_id_asn_map": {
|
||||
"5": "51111"
|
||||
}
|
||||
},
|
||||
"bgp_asn": "555"
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
{
|
||||
"CONFIG_DB__DEVICE_METADATA": {
|
||||
"localhost": {
|
||||
"default_bgp_status": "down"
|
||||
}
|
||||
},
|
||||
"neighbor_addr": "10.10.10.10",
|
||||
"bgp_session": {
|
||||
"asn": "555",
|
||||
"name": "remote_peer"
|
||||
},
|
||||
"constants": {
|
||||
"deployment_id_asn_map": {
|
||||
"5": "51111"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
{
|
||||
"CONFIG_DB__DEVICE_METADATA": {
|
||||
"localhost": {
|
||||
"default_bgp_status": "up"
|
||||
}
|
||||
},
|
||||
"neighbor_addr": "10.10.10.10",
|
||||
"bgp_session": {
|
||||
"asn": "555",
|
||||
"name": "remote_peer"
|
||||
},
|
||||
"constants": {
|
||||
"deployment_id_asn_map": {
|
||||
"5": "51111"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
{
|
||||
"CONFIG_DB__DEVICE_METADATA": {
|
||||
"localhost": {}
|
||||
},
|
||||
"neighbor_addr": "10.10.10.10",
|
||||
"bgp_session": {
|
||||
"asn": "555",
|
||||
"name": "remote_peer",
|
||||
"keepalive": "5"
|
||||
},
|
||||
"constants": {
|
||||
"deployment_id_asn_map": {
|
||||
"5": "51111"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
{
|
||||
"CONFIG_DB__DEVICE_METADATA": {
|
||||
"localhost": {}
|
||||
},
|
||||
"neighbor_addr": "10.10.10.10",
|
||||
"bgp_session": {
|
||||
"asn": "555",
|
||||
"name": "remote_peer",
|
||||
"holdtime": "240"
|
||||
},
|
||||
"constants": {
|
||||
"deployment_id_asn_map": {
|
||||
"5": "51111"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
!
|
||||
! template: bgpd/templates/general/instance.conf.j2
|
||||
!
|
||||
neighbor 10.10.10.10 remote-as 555
|
||||
neighbor 10.10.10.10 description _ASIC_
|
||||
address-family ipv4
|
||||
neighbor 10.10.10.10 peer-group PEER_V4_INT
|
||||
neighbor 10.10.10.10 next-hop-self force
|
||||
neighbor 10.10.10.10 activate
|
||||
exit-address-family
|
||||
!
|
||||
! end of template: bgpd/templates/general/instance.conf.j2
|
||||
!
|
@ -0,0 +1,13 @@
|
||||
!
|
||||
! template: bgpd/templates/general/instance.conf.j2
|
||||
!
|
||||
neighbor FC00:: remote-as 555
|
||||
neighbor FC00:: description _ASIC_
|
||||
address-family ipv6
|
||||
neighbor FC00:: peer-group PEER_V6_INT
|
||||
neighbor FC00:: next-hop-self force
|
||||
neighbor FC00:: activate
|
||||
exit-address-family
|
||||
!
|
||||
! end of template: bgpd/templates/general/instance.conf.j2
|
||||
!
|
@ -0,0 +1,17 @@
|
||||
!
|
||||
! template: bgpd/templates/general/instance.conf.j2
|
||||
!
|
||||
neighbor 10.10.10.10 remote-as 555
|
||||
neighbor 10.10.10.10 description remote_peer
|
||||
neighbor 10.10.10.10 timers 5 30
|
||||
neighbor 10.10.10.10 shutdown
|
||||
address-family ipv4
|
||||
neighbor 10.10.10.10 peer-group PEER_V4
|
||||
neighbor 10.10.10.10 route-map FROM_BGP_PEER_V4_INT in
|
||||
neighbor 10.10.10.10 route-reflector-client
|
||||
neighbor 10.10.10.10 next-hop-self
|
||||
neighbor 10.10.10.10 activate
|
||||
exit-address-family
|
||||
!
|
||||
! end of template: bgpd/templates/general/instance.conf.j2
|
||||
!
|
@ -0,0 +1,17 @@
|
||||
!
|
||||
! template: bgpd/templates/general/instance.conf.j2
|
||||
!
|
||||
neighbor fc::10 remote-as 555
|
||||
neighbor fc::10 description remote_peer
|
||||
neighbor fc::10 timers 5 30
|
||||
neighbor fc::10 shutdown
|
||||
address-family ipv6
|
||||
neighbor fc::10 peer-group PEER_V6
|
||||
neighbor fc::10 route-map FROM_BGP_PEER_V6_INT in
|
||||
neighbor fc::10 route-reflector-client
|
||||
neighbor fc::10 next-hop-self
|
||||
neighbor fc::10 activate
|
||||
exit-address-family
|
||||
!
|
||||
! end of template: bgpd/templates/general/instance.conf.j2
|
||||
!
|
@ -0,0 +1,12 @@
|
||||
!
|
||||
! template: bgpd/templates/general/instance.conf.j2
|
||||
!
|
||||
neighbor 10.10.10.10 remote-as 555
|
||||
neighbor 10.10.10.10 description remote_peer
|
||||
address-family ipv4
|
||||
neighbor 10.10.10.10 peer-group PEER_V4
|
||||
neighbor 10.10.10.10 activate
|
||||
exit-address-family
|
||||
!
|
||||
! end of template: bgpd/templates/general/instance.conf.j2
|
||||
!
|
@ -0,0 +1,12 @@
|
||||
!
|
||||
! template: bgpd/templates/general/instance.conf.j2
|
||||
!
|
||||
neighbor fc00::2 remote-as 555
|
||||
neighbor fc00::2 description remote_peer
|
||||
address-family ipv6
|
||||
neighbor fc00::2 peer-group PEER_V6
|
||||
neighbor fc00::2 activate
|
||||
exit-address-family
|
||||
!
|
||||
! end of template: bgpd/templates/general/instance.conf.j2
|
||||
!
|
@ -0,0 +1,16 @@
|
||||
!
|
||||
! template: bgpd/templates/general/instance.conf.j2
|
||||
!
|
||||
neighbor 10.10.10.10 remote-as 555
|
||||
neighbor 10.10.10.10 description remote_peer
|
||||
address-family ipv4
|
||||
neighbor 10.10.10.10 peer-group PEER_V4
|
||||
neighbor 10.10.10.10 activate
|
||||
exit-address-family
|
||||
address-family l2vpn evpn
|
||||
neighbor 10.10.10.10 activate
|
||||
advertise-all-vni
|
||||
exit-address-family
|
||||
!
|
||||
! end of template: bgpd/templates/general/instance.conf.j2
|
||||
!
|
@ -0,0 +1,13 @@
|
||||
!
|
||||
! template: bgpd/templates/general/instance.conf.j2
|
||||
!
|
||||
neighbor 10.10.10.10 remote-as 555
|
||||
neighbor 10.10.10.10 description remote_peer
|
||||
neighbor 10.10.10.10 shutdown
|
||||
address-family ipv4
|
||||
neighbor 10.10.10.10 peer-group PEER_V4
|
||||
neighbor 10.10.10.10 activate
|
||||
exit-address-family
|
||||
!
|
||||
! end of template: bgpd/templates/general/instance.conf.j2
|
||||
!
|
@ -0,0 +1,12 @@
|
||||
!
|
||||
! template: bgpd/templates/general/instance.conf.j2
|
||||
!
|
||||
neighbor 10.10.10.10 remote-as 555
|
||||
neighbor 10.10.10.10 description remote_peer
|
||||
address-family ipv4
|
||||
neighbor 10.10.10.10 peer-group PEER_V4
|
||||
neighbor 10.10.10.10 activate
|
||||
exit-address-family
|
||||
!
|
||||
! end of template: bgpd/templates/general/instance.conf.j2
|
||||
!
|
@ -0,0 +1,13 @@
|
||||
!
|
||||
! template: bgpd/templates/general/instance.conf.j2
|
||||
!
|
||||
neighbor 10.10.10.10 remote-as 555
|
||||
neighbor 10.10.10.10 description remote_peer
|
||||
neighbor 10.10.10.10 timers 5 180
|
||||
address-family ipv4
|
||||
neighbor 10.10.10.10 peer-group PEER_V4
|
||||
neighbor 10.10.10.10 activate
|
||||
exit-address-family
|
||||
!
|
||||
! end of template: bgpd/templates/general/instance.conf.j2
|
||||
!
|
@ -0,0 +1,13 @@
|
||||
!
|
||||
! template: bgpd/templates/general/instance.conf.j2
|
||||
!
|
||||
neighbor 10.10.10.10 remote-as 555
|
||||
neighbor 10.10.10.10 description remote_peer
|
||||
neighbor 10.10.10.10 timers 60 240
|
||||
address-family ipv4
|
||||
neighbor 10.10.10.10 peer-group PEER_V4
|
||||
neighbor 10.10.10.10 activate
|
||||
exit-address-family
|
||||
!
|
||||
! end of template: bgpd/templates/general/instance.conf.j2
|
||||
!
|
@ -0,0 +1,7 @@
|
||||
{
|
||||
"CONFIG_DB__DEVICE_METADATA": {
|
||||
"localhost": {
|
||||
"type": "ToRRouter"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"CONFIG_DB__DEVICE_METADATA": {
|
||||
"localhost": {
|
||||
"type": "LeafRouter",
|
||||
"sub_role": "BackEnd"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
!
|
||||
! template: bgpd/templates/general/peer-group.conf.j2
|
||||
!
|
||||
neighbor PEER_V4 peer-group
|
||||
neighbor PEER_V4_INT peer-group
|
||||
neighbor PEER_V6 peer-group
|
||||
neighbor PEER_V6_INT peer-group
|
||||
address-family ipv4
|
||||
neighbor PEER_V4 allowas-in 1
|
||||
neighbor PEER_V4_INT allowas-in 1
|
||||
neighbor PEER_V4 soft-reconfiguration inbound
|
||||
neighbor PEER_V4 route-map FROM_BGP_PEER_V4 in
|
||||
neighbor PEER_V4 route-map TO_BGP_PEER_V4 out
|
||||
neighbor PEER_V4_INT soft-reconfiguration inbound
|
||||
neighbor PEER_V4_INT route-map FROM_BGP_PEER_V4 in
|
||||
neighbor PEER_V4_INT route-map TO_BGP_PEER_V4 out
|
||||
exit-address-family
|
||||
address-family ipv6
|
||||
neighbor PEER_V6 allowas-in 1
|
||||
neighbor PEER_V6_INT allowas-in 1
|
||||
neighbor PEER_V6 soft-reconfiguration inbound
|
||||
neighbor PEER_V6 route-map FROM_BGP_PEER_V6 in
|
||||
neighbor PEER_V6 route-map TO_BGP_PEER_V6 out
|
||||
neighbor PEER_V6_INT soft-reconfiguration inbound
|
||||
neighbor PEER_V6_INT route-map FROM_BGP_PEER_V6 in
|
||||
neighbor PEER_V6_INT route-map TO_BGP_PEER_V6 out
|
||||
exit-address-family
|
||||
!
|
||||
! end of template: bgpd/templates/general/peer-group.conf.j2
|
||||
!
|
@ -0,0 +1,28 @@
|
||||
!
|
||||
! template: bgpd/templates/general/peer-group.conf.j2
|
||||
!
|
||||
neighbor PEER_V4 peer-group
|
||||
neighbor PEER_V4_INT peer-group
|
||||
neighbor PEER_V6 peer-group
|
||||
neighbor PEER_V6_INT peer-group
|
||||
address-family ipv4
|
||||
neighbor PEER_V4_INT route-reflector-client
|
||||
neighbor PEER_V4 soft-reconfiguration inbound
|
||||
neighbor PEER_V4 route-map FROM_BGP_PEER_V4 in
|
||||
neighbor PEER_V4 route-map TO_BGP_PEER_V4 out
|
||||
neighbor PEER_V4_INT soft-reconfiguration inbound
|
||||
neighbor PEER_V4_INT route-map FROM_BGP_PEER_V4 in
|
||||
neighbor PEER_V4_INT route-map TO_BGP_PEER_V4 out
|
||||
exit-address-family
|
||||
address-family ipv6
|
||||
neighbor PEER_V6_INT route-reflector-client
|
||||
neighbor PEER_V6 soft-reconfiguration inbound
|
||||
neighbor PEER_V6 route-map FROM_BGP_PEER_V6 in
|
||||
neighbor PEER_V6 route-map TO_BGP_PEER_V6 out
|
||||
neighbor PEER_V6_INT soft-reconfiguration inbound
|
||||
neighbor PEER_V6_INT route-map FROM_BGP_PEER_V6 in
|
||||
neighbor PEER_V6_INT route-map TO_BGP_PEER_V6 out
|
||||
exit-address-family
|
||||
!
|
||||
! end of template: bgpd/templates/general/peer-group.conf.j2
|
||||
!
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"CONFIG_DB__DEVICE_METADATA": {
|
||||
"localhost": {
|
||||
"sub_role": "BackEnd"
|
||||
}
|
||||
},
|
||||
"loopback0_ipv4": "10.10.10.10/32"
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"CONFIG_DB__DEVICE_METADATA": {
|
||||
"localhost": {
|
||||
"sub_role": "NotBackEnd"
|
||||
}
|
||||
},
|
||||
"loopback0_ipv4": "10.10.10.10/32"
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
!
|
||||
! template: bgpd/templates/general/policies.conf.j2
|
||||
!
|
||||
route-map FROM_BGP_PEER_V4 permit 100
|
||||
!
|
||||
route-map TO_BGP_PEER_V4 permit 100
|
||||
!
|
||||
route-map FROM_BGP_PEER_V6 permit 1
|
||||
set ipv6 next-hop prefer-global
|
||||
!
|
||||
route-map FROM_BGP_PEER_V6 permit 100
|
||||
!
|
||||
route-map TO_BGP_PEER_V6 permit 100
|
||||
!
|
||||
route-map FROM_BGP_PEER_V4_INT permit 2
|
||||
set originator-id 10.10.10.10
|
||||
!
|
||||
route-map FROM_BGP_PEER_V6_INT permit 1
|
||||
set ipv6 next-hop prefer-global
|
||||
!
|
||||
route-map FROM_BGP_PEER_V6_INT permit 2
|
||||
set originator-id 10.10.10.10
|
||||
!
|
||||
! end of template: bgpd/templates/general/policies.conf.j2
|
||||
!
|
@ -0,0 +1,16 @@
|
||||
!
|
||||
! template: bgpd/templates/general/policies.conf.j2
|
||||
!
|
||||
route-map FROM_BGP_PEER_V4 permit 100
|
||||
!
|
||||
route-map TO_BGP_PEER_V4 permit 100
|
||||
!
|
||||
route-map FROM_BGP_PEER_V6 permit 1
|
||||
set ipv6 next-hop prefer-global
|
||||
!
|
||||
route-map FROM_BGP_PEER_V6 permit 100
|
||||
!
|
||||
route-map TO_BGP_PEER_V6 permit 100
|
||||
!
|
||||
! end of template: bgpd/templates/general/policies.conf.j2
|
||||
!
|
@ -0,0 +1,12 @@
|
||||
{
|
||||
"bgp_asn": "555",
|
||||
"neighbor_addr": "10.20.30.40",
|
||||
"bgp_session": {
|
||||
"name": "monitor"
|
||||
},
|
||||
"constants": {
|
||||
"deployment_id_asn_map": {
|
||||
"5": "51111"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
!
|
||||
! template: bgpd/templates/monitors/instance.conf.j2
|
||||
!
|
||||
neighbor 10.20.30.40 remote-as 555
|
||||
neighbor 10.20.30.40 peer-group BGPMON
|
||||
neighbor 10.20.30.40 description monitor
|
||||
neighbor 10.20.30.40 activate
|
||||
address-family ipv6
|
||||
neighbor 10.20.30.40 activate
|
||||
exit-address-family
|
||||
!
|
||||
! end of template: bgpd/templates/BGPMON/instance.conf.j2
|
||||
!
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"loopback0_ipv4": "1.1.1.1/32"
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
!
|
||||
! template: bgpd/templates/BGPMON/peer-group.conf.j2
|
||||
!
|
||||
neighbor BGPMON peer-group
|
||||
neighbor BGPMON update-source 1.1.1.1
|
||||
neighbor BGPMON route-map FROM_BGPMON in
|
||||
neighbor BGPMON route-map TO_BGPMON out
|
||||
neighbor BGPMON send-community
|
||||
neighbor BGPMON maximum-prefix 1
|
||||
!
|
||||
! end of template: bgpd/templates/BGPMON/peer-group.conf.j2
|
||||
!
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1,9 @@
|
||||
!
|
||||
! template: bgpd/templates/BGPMON/policies.conf.j2
|
||||
!
|
||||
route-map FROM_BGPMON deny 10
|
||||
!
|
||||
route-map TO_BGPMON permit 10
|
||||
!
|
||||
! end of template: bgpd/templates/BGPMON/policies.conf.j2
|
||||
!
|
114
src/sonic-bgpcfgd/tests/test_ipv6_nexthop_global.py
Normal file
114
src/sonic-bgpcfgd/tests/test_ipv6_nexthop_global.py
Normal file
@ -0,0 +1,114 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
from app.template import TemplateFabric
|
||||
from .util import load_constants
|
||||
|
||||
TEMPLATE_PATH = os.path.abspath('../../dockers/docker-fpm-frr/frr')
|
||||
|
||||
|
||||
def parse_instance_conf(filename):
|
||||
activate_re = re.compile(r'^neighbor\s+(\S+)\s+activate$')
|
||||
with open(filename) as fp:
|
||||
lines = [line.strip() for line in fp if not line.strip().startswith('!') and line.strip() != '']
|
||||
# Search all v6 neighbors
|
||||
neighbors = {}
|
||||
for line in lines:
|
||||
if activate_re.match(line):
|
||||
neighbor = activate_re.match(line).group(1)
|
||||
if TemplateFabric.is_ipv6(neighbor):
|
||||
neighbors[neighbor] = {}
|
||||
# Extract peer-groups and route-maps
|
||||
for neighbor, neighbor_data in neighbors.iteritems():
|
||||
route_map_in_re = re.compile(r'^neighbor\s+%s\s+route-map\s+(\S+) in$' % neighbor)
|
||||
peer_group_re = re.compile(r'^neighbor\s+%s\s+peer-group\s+(\S+)$' % neighbor)
|
||||
for line in lines:
|
||||
if route_map_in_re.match(line):
|
||||
assert "route-map" not in neighbor_data
|
||||
neighbor_data["route-map"] = route_map_in_re.match(line).group(1)
|
||||
if peer_group_re.match(line):
|
||||
assert "peer-group" not in neighbor_data
|
||||
neighbor_data["peer-group"] = peer_group_re.match(line).group(1)
|
||||
# Ensure that every ivp6 neighbor has either route-map or peer-group
|
||||
for neighbor, neighbor_data in neighbors.iteritems():
|
||||
assert "route-map" in neighbor_data or "peer-group" in neighbor_data,\
|
||||
"IPv6 neighbor '%s' must have either route-map in or peer-group %s" % (neighbor, neighbor_data)
|
||||
return neighbors
|
||||
|
||||
def load_results(path, dir_name):
|
||||
result_files = []
|
||||
for fname in os.listdir(os.path.join(path, dir_name)):
|
||||
if not fname.startswith("result_"):
|
||||
continue
|
||||
full_fname = os.path.join(path, dir_name, fname)
|
||||
if not os.path.isfile(full_fname):
|
||||
continue
|
||||
result_files.append(full_fname)
|
||||
return result_files
|
||||
|
||||
def process_instances(path):
|
||||
result_files = load_results(path, "instance.conf")
|
||||
# search for ipv6 neighbors
|
||||
neighbors_list = []
|
||||
for fname in result_files:
|
||||
neighbors = parse_instance_conf(fname)
|
||||
if neighbors:
|
||||
neighbors_list.append(neighbors)
|
||||
return neighbors_list
|
||||
|
||||
def parse_peer_group_conf(filename, pg_name):
|
||||
route_map_re = re.compile(r'^neighbor\s+%s\s+route-map\s+(\S+)\s+in$' % pg_name)
|
||||
with open(filename) as fp:
|
||||
lines = [line.strip() for line in fp if not line.strip().startswith('!') and line.strip() != '']
|
||||
route_maps = set()
|
||||
for line in lines:
|
||||
if route_map_re.match(line):
|
||||
route_map = route_map_re.match(line).group(1)
|
||||
route_maps.add(route_map)
|
||||
return route_maps
|
||||
|
||||
def extract_rm_from_peer_group(path, peer_group_name):
|
||||
result_files = load_results(path, "peer-group.conf")
|
||||
rm_set = set()
|
||||
for fname in result_files:
|
||||
route_maps = parse_peer_group_conf(fname, peer_group_name)
|
||||
if route_maps:
|
||||
rm_set |= route_maps
|
||||
return list(rm_set)
|
||||
|
||||
def check_routemap_in_file(filename, route_map_name):
|
||||
route_map_re = re.compile(r'^route-map\s+%s\s+(\S+)' % route_map_name)
|
||||
set_re = re.compile(r'set ipv6 next-hop prefer-global')
|
||||
with open(filename) as fp:
|
||||
lines = [line.strip() for line in fp if not line.strip().startswith('!') and line.strip() != '']
|
||||
found_first_entry = False
|
||||
for line in lines:
|
||||
err_msg = "route-map %s doesn't have mandatory 'set ipv6 next-hop prefer-global' entry as the first rule" % route_map_name
|
||||
assert not (found_first_entry and line.startswith("route-map")), err_msg
|
||||
if found_first_entry and set_re.match(line):
|
||||
break # We're good
|
||||
if route_map_re.match(line):
|
||||
err_msg = "route-map %s doesn't have mandatory permit entry for 'set ipv6 next-hop prefer-global" % route_map_name
|
||||
assert route_map_re.match(line).group(1) == 'permit', err_msg
|
||||
found_first_entry = True
|
||||
return found_first_entry
|
||||
|
||||
def check_routemap(path, route_map_name):
|
||||
result_files = load_results(path, "policies.conf")
|
||||
checked = False
|
||||
for fname in result_files:
|
||||
checked = checked or check_routemap_in_file(fname, route_map_name)
|
||||
assert checked, "route-map %s wasn't found" % route_map_name
|
||||
|
||||
def test_v6_next_hop_global():
|
||||
paths = ["tests/data/%s" % value for value in load_constants().values()]
|
||||
for path in paths:
|
||||
test_cases = process_instances(path)
|
||||
for test_case in test_cases:
|
||||
for neighbor_value in test_case.values():
|
||||
if 'route-map' in neighbor_value:
|
||||
check_routemap(path, neighbor_value['route-map'])
|
||||
elif 'peer-group' in neighbor_value:
|
||||
route_map_in_list = extract_rm_from_peer_group(path, neighbor_value['peer-group'])
|
||||
for route_map_in in route_map_in_list:
|
||||
check_routemap(path, route_map_in)
|
@ -1,4 +0,0 @@
|
||||
import pytest
|
||||
|
||||
def test_sample():
|
||||
assert True
|
129
src/sonic-bgpcfgd/tests/test_templates.py
Normal file
129
src/sonic-bgpcfgd/tests/test_templates.py
Normal file
@ -0,0 +1,129 @@
|
||||
import os
|
||||
import json
|
||||
|
||||
|
||||
from app.template import TemplateFabric
|
||||
from app.config import ConfigMgr
|
||||
from .util import load_constants
|
||||
|
||||
TEMPLATE_PATH = os.path.abspath('../../dockers/docker-fpm-frr/frr')
|
||||
|
||||
|
||||
def load_tests(peer_type, template_name):
|
||||
constants = load_constants()
|
||||
path = "tests/data/%s/%s" % (constants[peer_type], template_name)
|
||||
param_files = [name for name in os.listdir(path)
|
||||
if os.path.isfile(os.path.join(path, name)) and name.startswith("param_")]
|
||||
tests = []
|
||||
for param_fname in param_files:
|
||||
casename = param_fname.replace("param_", "").replace(".json", "")
|
||||
result_fname = "result_%s.conf" % casename
|
||||
full_param_fname = os.path.join(path, param_fname)
|
||||
full_result_fname = os.path.join(path, result_fname)
|
||||
tests.append((casename, full_param_fname, full_result_fname))
|
||||
tmpl_path = os.path.join("bgpd", "templates", constants[peer_type], "%s.j2" % template_name)
|
||||
return tmpl_path, tests
|
||||
|
||||
def load_json(fname):
|
||||
with open(fname) as param_fp:
|
||||
raw_params = json.load(param_fp)
|
||||
params = {}
|
||||
for table_key, table_entries in raw_params.items():
|
||||
if table_key.startswith("CONFIG_DB__"):
|
||||
# convert CONFIG_DB__* entries keys into tuple if needed
|
||||
new_table_entries = {}
|
||||
for entry_key, entry_value in table_entries.items():
|
||||
if '|' in entry_key:
|
||||
new_key = tuple(entry_key.split('|'))
|
||||
else:
|
||||
new_key = entry_key
|
||||
new_table_entries[new_key] = entry_value
|
||||
params[table_key] = new_table_entries
|
||||
else:
|
||||
params[table_key] = table_entries
|
||||
return params
|
||||
|
||||
def compress_comments(raw_config):
|
||||
comment_counter = 0
|
||||
output = []
|
||||
for line in raw_config.split('\n'):
|
||||
stripped_line = line.strip()
|
||||
# Skip empty lines
|
||||
if stripped_line == '':
|
||||
pass
|
||||
# Write lines without comments
|
||||
elif not stripped_line.startswith('!'):
|
||||
if comment_counter > 0:
|
||||
output.append("!")
|
||||
comment_counter = 0
|
||||
output.append(line)
|
||||
# Write non-empty comments
|
||||
elif stripped_line.startswith('!') and len(stripped_line) > 1:
|
||||
if comment_counter > 0:
|
||||
output.append("!")
|
||||
comment_counter = 0
|
||||
output.append(line)
|
||||
# Count empty comments
|
||||
else: # stripped_line == '!'
|
||||
comment_counter += 1
|
||||
# Flush last comment if we have one
|
||||
if comment_counter > 0:
|
||||
output.append("!")
|
||||
return "\n".join(output) + "\n"
|
||||
|
||||
def write_result(fname, raw_result):
|
||||
with open(fname, 'w') as fp:
|
||||
raw_result_w_commpressed_comments = compress_comments(raw_result)
|
||||
fp.write(raw_result_w_commpressed_comments)
|
||||
|
||||
def run_tests(test_name, template_fname, tests):
|
||||
tf = TemplateFabric(TEMPLATE_PATH)
|
||||
template = tf.from_file(template_fname)
|
||||
for case_name, param_fname, result_fname in tests:
|
||||
params = load_json(param_fname)
|
||||
raw_generated_result = str(template.render(params))
|
||||
assert "None" not in raw_generated_result, "Test %s.%s" % (test_name, case_name)
|
||||
# this is used only for initial generation write_result(result_fname, raw_generated_result)
|
||||
canonical_generated_result = ConfigMgr.to_canonical(raw_generated_result)
|
||||
with open(result_fname) as result_fp:
|
||||
raw_saved_result = result_fp.read()
|
||||
canonical_saved_result = ConfigMgr.to_canonical(raw_saved_result)
|
||||
assert canonical_saved_result == canonical_generated_result, "Test %s.%s" % (test_name, case_name)
|
||||
|
||||
# Tests
|
||||
|
||||
def test_general_policies():
|
||||
test_data = load_tests("general", "policies.conf")
|
||||
run_tests("general_policies", *test_data)
|
||||
|
||||
def test_general_pg():
|
||||
test_data = load_tests("general", "peer-group.conf")
|
||||
run_tests("general_pg", *test_data)
|
||||
|
||||
def test_general_instance():
|
||||
test_data = load_tests("general", "instance.conf")
|
||||
run_tests("general_instance", *test_data)
|
||||
|
||||
def test_dynamic_policies():
|
||||
test_data = load_tests("dynamic", "policies.conf")
|
||||
run_tests("dynamic_policies", *test_data)
|
||||
|
||||
def test_dynamic_pg():
|
||||
test_data = load_tests("dynamic", "peer-group.conf")
|
||||
run_tests("dynamic_pg", *test_data)
|
||||
|
||||
def test_dynamic_instance():
|
||||
test_data = load_tests("dynamic", "instance.conf")
|
||||
run_tests("dynamic_instance", *test_data)
|
||||
|
||||
def test_monitors_policies():
|
||||
test_data = load_tests("monitors", "policies.conf")
|
||||
run_tests("monitors_policies", *test_data)
|
||||
|
||||
def test_monitors_pg():
|
||||
test_data = load_tests("monitors", "peer-group.conf")
|
||||
run_tests("monitors_pg", *test_data)
|
||||
|
||||
def test_monitors_instance():
|
||||
test_data = load_tests("monitors", "instance.conf")
|
||||
run_tests("monitors_instance", *test_data)
|
16
src/sonic-bgpcfgd/tests/util.py
Normal file
16
src/sonic-bgpcfgd/tests/util.py
Normal file
@ -0,0 +1,16 @@
|
||||
import os
|
||||
import yaml
|
||||
|
||||
CONSTANTS_PATH = os.path.abspath('../../files/image_config/constants/constants.yml')
|
||||
|
||||
def load_constants():
|
||||
with open(CONSTANTS_PATH) as f:
|
||||
data = yaml.load(f)
|
||||
result = {}
|
||||
assert "constants" in data, "'constants' key not found in constants.yml"
|
||||
assert "bgp" in data["constants"], "'bgp' key not found in constants.yml"
|
||||
assert "peers" in data["constants"]["bgp"], "'peers' key not found in constants.yml"
|
||||
for name, value in data["constants"]["bgp"]["peers"].items():
|
||||
assert "template_dir" in value, "'template_dir' key not found for peer '%s'" % name
|
||||
result[name] = value["template_dir"]
|
||||
return result
|
Loading…
Reference in New Issue
Block a user