parent
719c8e68c8
commit
d592e9b0f8
@ -34,5 +34,5 @@
|
|||||||
neighbor {{ bgp_session['name'] }} activate
|
neighbor {{ bgp_session['name'] }} activate
|
||||||
exit-address-family
|
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 #}
|
{# set the bgp neighbor timers if they have not default values #}
|
||||||
{% if (bgp_session['keepalive'] is defined and bgp_session['keepalive'] | int != 60)
|
{% if (bgp_session['keepalive'] is defined and bgp_session['keepalive'] | int != 60)
|
||||||
or (bgp_session['holdtime'] is defined and bgp_session['holdtime'] | int != 180) %}
|
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 %}
|
{% 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' %}
|
{% 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 %}
|
{% if neighbor_addr | ipv4 %}
|
||||||
address-family ipv4
|
address-family ipv4
|
||||||
{% if 'ASIC' in bgp_session['name'] %}
|
{% if 'ASIC' in bgp_session['name'] %}
|
||||||
neighbor {{ neighbor_addr }} peer-group PEER_V4_INT
|
neighbor {{ neighbor_addr }} peer-group PEER_V4_INT
|
||||||
{% else %}
|
{% else %}
|
||||||
neighbor {{ neighbor_addr }} peer-group PEER_V4
|
neighbor {{ neighbor_addr }} peer-group PEER_V4
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
!
|
||||||
{% if CONFIG_DB__DEVICE_METADATA['localhost']['sub_role'] == 'BackEnd' %}
|
{% if CONFIG_DB__DEVICE_METADATA['localhost']['sub_role'] == 'BackEnd' %}
|
||||||
neighbor {{ neighbor_addr }} route-map FROM_BGP_PEER_V4_INT in
|
neighbor {{ neighbor_addr }} route-map FROM_BGP_PEER_V4_INT in
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
!
|
||||||
{% elif neighbor_addr | ipv6 %}
|
{% elif neighbor_addr | ipv6 %}
|
||||||
address-family ipv6
|
address-family ipv6
|
||||||
{% if 'ASIC' in bgp_session['name'] %}
|
{% if 'ASIC' in bgp_session['name'] %}
|
||||||
neighbor {{ neighbor_addr }} peer-group PEER_V6_INT
|
neighbor {{ neighbor_addr }} peer-group PEER_V6_INT
|
||||||
{% else %}
|
{% else %}
|
||||||
neighbor {{ neighbor_addr }} peer-group PEER_V6
|
neighbor {{ neighbor_addr }} peer-group PEER_V6
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
!
|
||||||
{% if CONFIG_DB__DEVICE_METADATA['localhost']['sub_role'] == 'BackEnd' %}
|
{% if CONFIG_DB__DEVICE_METADATA['localhost']['sub_role'] == 'BackEnd' %}
|
||||||
neighbor {{ neighbor_addr }} route-map FROM_BGP_PEER_V6_INT in
|
neighbor {{ neighbor_addr }} route-map FROM_BGP_PEER_V6_INT in
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% 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
|
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
|
neighbor {{ neighbor_addr }} next-hop-self
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if 'ASIC' in bgp_session['name'] %}
|
!
|
||||||
|
{% if 'ASIC' in bgp_session['name'] %}
|
||||||
neighbor {{ neighbor_addr }} next-hop-self force
|
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" %}
|
{% if bgp_session["asn"] == bgp_asn and CONFIG_DB__DEVICE_METADATA['localhost']['type'] == "SpineChassisFrontendRouter" %}
|
||||||
address-family l2vpn evpn
|
address-family l2vpn evpn
|
||||||
@ -52,8 +59,6 @@
|
|||||||
advertise-all-vni
|
advertise-all-vni
|
||||||
exit-address-family
|
exit-address-family
|
||||||
{% endif %}
|
{% endif %}
|
||||||
neighbor {{ neighbor_addr }} activate
|
|
||||||
exit-address-family
|
|
||||||
!
|
!
|
||||||
! end of template: bgpd/templates/general/instance.conf.j2
|
! 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/
|
build/
|
||||||
dist/
|
dist/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
|
app/*.pyc
|
||||||
tests/*.pyc
|
tests/*.pyc
|
||||||
tests/__pycache__/
|
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
|
#!/usr/bin/env python
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import subprocess
|
|
||||||
import datetime
|
import datetime
|
||||||
import time
|
import time
|
||||||
import syslog
|
import syslog
|
||||||
import signal
|
import signal
|
||||||
import traceback
|
import traceback
|
||||||
import os
|
import os
|
||||||
import tempfile
|
|
||||||
import json
|
import json
|
||||||
from collections import defaultdict, OrderedDict
|
from collections import defaultdict
|
||||||
from pprint import pprint
|
|
||||||
from functools import partial
|
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
import jinja2
|
import jinja2
|
||||||
import netaddr
|
import netaddr
|
||||||
from swsscommon import swsscommon
|
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_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):
|
class Directory(object):
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
from setuptools import setup
|
import setuptools
|
||||||
|
|
||||||
setup(name='sonic-bgpcfgd',
|
setuptools.setup(name='sonic-bgpcfgd',
|
||||||
version='1.0',
|
version='1.0',
|
||||||
description='Utility to dynamically generate BGP configuration for FRR',
|
description='Utility to dynamically generate BGP configuration for FRR',
|
||||||
author='Pavel Shirshov',
|
author='Pavel Shirshov',
|
||||||
author_email='pavelsh@microsoft.com',
|
author_email='pavelsh@microsoft.com',
|
||||||
url='https://github.com/Azure/sonic-buildimage',
|
url='https://github.com/Azure/sonic-buildimage',
|
||||||
|
packages=setuptools.find_packages(),
|
||||||
scripts=['bgpcfgd'],
|
scripts=['bgpcfgd'],
|
||||||
install_requires=['jinja2>=2.10', 'netaddr', 'pyyaml'],
|
install_requires=['jinja2>=2.10', 'netaddr', 'pyyaml'],
|
||||||
setup_requires=['pytest-runner', 'pytest'],
|
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
|
Reference in New Issue
Block a user