From d592e9b0f896c3a4d7a420b3ee2b58149b100d31 Mon Sep 17 00:00:00 2001 From: pavel-shirshov Date: Thu, 25 Jun 2020 14:54:02 -0700 Subject: [PATCH] Tests for bgpcfgd templates (#4841) * Tests for bgpcfgd templates --- .../bgpd/templates/dynamic/instance.conf.j2 | 2 +- .../bgpd/templates/general/instance.conf.j2 | 31 ++- src/sonic-bgpcfgd/.gitignore | 2 + src/sonic-bgpcfgd/app/__init__.py | 0 src/sonic-bgpcfgd/app/config.py | 103 ++++++++ src/sonic-bgpcfgd/app/log.py | 33 +++ src/sonic-bgpcfgd/app/template.py | 98 +++++++ src/sonic-bgpcfgd/app/util.py | 22 ++ src/sonic-bgpcfgd/app/vars.py | 1 + src/sonic-bgpcfgd/bgpcfgd | 245 +----------------- src/sonic-bgpcfgd/setup.py | 5 +- .../data/dynamic/instance.conf/param_all.json | 21 ++ .../dynamic/instance.conf/param_base.json | 19 ++ .../dynamic/instance.conf/result_all.conf | 22 ++ .../dynamic/instance.conf/result_base.conf | 22 ++ .../dynamic/peer-group.conf/param_all.json | 1 + .../dynamic/peer-group.conf/result_all.conf | 7 + .../data/dynamic/policies.conf/param_all.json | 1 + .../dynamic/policies.conf/result_all.conf | 9 + .../general/instance.conf/param_ASIC_v4.json | 15 ++ .../general/instance.conf/param_ASIC_v6.json | 15 ++ .../general/instance.conf/param_all_v4.json | 23 ++ .../general/instance.conf/param_all_v6.json | 23 ++ .../general/instance.conf/param_base_v4.json | 15 ++ .../general/instance.conf/param_base_v6.json | 15 ++ .../general/instance.conf/param_l2vpn.json | 18 ++ .../instance.conf/param_shutdown_1.json | 17 ++ .../instance.conf/param_shutdown_2.json | 17 ++ .../general/instance.conf/param_timers_1.json | 16 ++ .../general/instance.conf/param_timers_2.json | 16 ++ .../general/instance.conf/result_ASIC_v4.conf | 13 + .../general/instance.conf/result_ASIC_v6.conf | 13 + .../general/instance.conf/result_all_v4.conf | 17 ++ .../general/instance.conf/result_all_v6.conf | 17 ++ .../general/instance.conf/result_base_v4.conf | 12 + .../general/instance.conf/result_base_v6.conf | 12 + .../general/instance.conf/result_l2vpn.conf | 16 ++ .../instance.conf/result_shutdown_1.conf | 13 + .../instance.conf/result_shutdown_2.conf | 12 + .../instance.conf/result_timers_1.conf | 13 + .../instance.conf/result_timers_2.conf | 13 + .../general/peer-group.conf/param_all.json | 7 + .../general/peer-group.conf/param_base.json | 8 + .../general/peer-group.conf/result_all.conf | 30 +++ .../general/peer-group.conf/result_base.conf | 28 ++ .../data/general/policies.conf/param_all.json | 8 + .../general/policies.conf/param_base.json | 8 + .../general/policies.conf/result_all.conf | 25 ++ .../general/policies.conf/result_base.conf | 16 ++ .../monitors/instance.conf/param_all.json | 12 + .../monitors/instance.conf/result_all.conf | 13 + .../monitors/peer-group.conf/param_all.json | 3 + .../monitors/peer-group.conf/result_all.conf | 12 + .../monitors/policies.conf/param_all.json | 1 + .../monitors/policies.conf/result_all.conf | 9 + .../tests/test_ipv6_nexthop_global.py | 114 ++++++++ src/sonic-bgpcfgd/tests/test_sample.py | 4 - src/sonic-bgpcfgd/tests/test_templates.py | 129 +++++++++ src/sonic-bgpcfgd/tests/util.py | 16 ++ 59 files changed, 1169 insertions(+), 259 deletions(-) create mode 100644 src/sonic-bgpcfgd/app/__init__.py create mode 100644 src/sonic-bgpcfgd/app/config.py create mode 100644 src/sonic-bgpcfgd/app/log.py create mode 100644 src/sonic-bgpcfgd/app/template.py create mode 100644 src/sonic-bgpcfgd/app/util.py create mode 100644 src/sonic-bgpcfgd/app/vars.py create mode 100644 src/sonic-bgpcfgd/tests/data/dynamic/instance.conf/param_all.json create mode 100644 src/sonic-bgpcfgd/tests/data/dynamic/instance.conf/param_base.json create mode 100644 src/sonic-bgpcfgd/tests/data/dynamic/instance.conf/result_all.conf create mode 100644 src/sonic-bgpcfgd/tests/data/dynamic/instance.conf/result_base.conf create mode 100644 src/sonic-bgpcfgd/tests/data/dynamic/peer-group.conf/param_all.json create mode 100644 src/sonic-bgpcfgd/tests/data/dynamic/peer-group.conf/result_all.conf create mode 100644 src/sonic-bgpcfgd/tests/data/dynamic/policies.conf/param_all.json create mode 100644 src/sonic-bgpcfgd/tests/data/dynamic/policies.conf/result_all.conf create mode 100644 src/sonic-bgpcfgd/tests/data/general/instance.conf/param_ASIC_v4.json create mode 100644 src/sonic-bgpcfgd/tests/data/general/instance.conf/param_ASIC_v6.json create mode 100644 src/sonic-bgpcfgd/tests/data/general/instance.conf/param_all_v4.json create mode 100644 src/sonic-bgpcfgd/tests/data/general/instance.conf/param_all_v6.json create mode 100644 src/sonic-bgpcfgd/tests/data/general/instance.conf/param_base_v4.json create mode 100644 src/sonic-bgpcfgd/tests/data/general/instance.conf/param_base_v6.json create mode 100644 src/sonic-bgpcfgd/tests/data/general/instance.conf/param_l2vpn.json create mode 100644 src/sonic-bgpcfgd/tests/data/general/instance.conf/param_shutdown_1.json create mode 100644 src/sonic-bgpcfgd/tests/data/general/instance.conf/param_shutdown_2.json create mode 100644 src/sonic-bgpcfgd/tests/data/general/instance.conf/param_timers_1.json create mode 100644 src/sonic-bgpcfgd/tests/data/general/instance.conf/param_timers_2.json create mode 100644 src/sonic-bgpcfgd/tests/data/general/instance.conf/result_ASIC_v4.conf create mode 100644 src/sonic-bgpcfgd/tests/data/general/instance.conf/result_ASIC_v6.conf create mode 100644 src/sonic-bgpcfgd/tests/data/general/instance.conf/result_all_v4.conf create mode 100644 src/sonic-bgpcfgd/tests/data/general/instance.conf/result_all_v6.conf create mode 100644 src/sonic-bgpcfgd/tests/data/general/instance.conf/result_base_v4.conf create mode 100644 src/sonic-bgpcfgd/tests/data/general/instance.conf/result_base_v6.conf create mode 100644 src/sonic-bgpcfgd/tests/data/general/instance.conf/result_l2vpn.conf create mode 100644 src/sonic-bgpcfgd/tests/data/general/instance.conf/result_shutdown_1.conf create mode 100644 src/sonic-bgpcfgd/tests/data/general/instance.conf/result_shutdown_2.conf create mode 100644 src/sonic-bgpcfgd/tests/data/general/instance.conf/result_timers_1.conf create mode 100644 src/sonic-bgpcfgd/tests/data/general/instance.conf/result_timers_2.conf create mode 100644 src/sonic-bgpcfgd/tests/data/general/peer-group.conf/param_all.json create mode 100644 src/sonic-bgpcfgd/tests/data/general/peer-group.conf/param_base.json create mode 100644 src/sonic-bgpcfgd/tests/data/general/peer-group.conf/result_all.conf create mode 100644 src/sonic-bgpcfgd/tests/data/general/peer-group.conf/result_base.conf create mode 100644 src/sonic-bgpcfgd/tests/data/general/policies.conf/param_all.json create mode 100644 src/sonic-bgpcfgd/tests/data/general/policies.conf/param_base.json create mode 100644 src/sonic-bgpcfgd/tests/data/general/policies.conf/result_all.conf create mode 100644 src/sonic-bgpcfgd/tests/data/general/policies.conf/result_base.conf create mode 100644 src/sonic-bgpcfgd/tests/data/monitors/instance.conf/param_all.json create mode 100644 src/sonic-bgpcfgd/tests/data/monitors/instance.conf/result_all.conf create mode 100644 src/sonic-bgpcfgd/tests/data/monitors/peer-group.conf/param_all.json create mode 100644 src/sonic-bgpcfgd/tests/data/monitors/peer-group.conf/result_all.conf create mode 100644 src/sonic-bgpcfgd/tests/data/monitors/policies.conf/param_all.json create mode 100644 src/sonic-bgpcfgd/tests/data/monitors/policies.conf/result_all.conf create mode 100644 src/sonic-bgpcfgd/tests/test_ipv6_nexthop_global.py delete mode 100644 src/sonic-bgpcfgd/tests/test_sample.py create mode 100644 src/sonic-bgpcfgd/tests/test_templates.py create mode 100644 src/sonic-bgpcfgd/tests/util.py diff --git a/dockers/docker-fpm-frr/frr/bgpd/templates/dynamic/instance.conf.j2 b/dockers/docker-fpm-frr/frr/bgpd/templates/dynamic/instance.conf.j2 index efb1546dac..5082b75005 100644 --- a/dockers/docker-fpm-frr/frr/bgpd/templates/dynamic/instance.conf.j2 +++ b/dockers/docker-fpm-frr/frr/bgpd/templates/dynamic/instance.conf.j2 @@ -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 ! diff --git a/dockers/docker-fpm-frr/frr/bgpd/templates/general/instance.conf.j2 b/dockers/docker-fpm-frr/frr/bgpd/templates/general/instance.conf.j2 index aed32d291c..e4422a7716 100644 --- a/dockers/docker-fpm-frr/frr/bgpd/templates/general/instance.conf.j2 +++ b/dockers/docker-fpm-frr/frr/bgpd/templates/general/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 ! diff --git a/src/sonic-bgpcfgd/.gitignore b/src/sonic-bgpcfgd/.gitignore index 013f588165..bb1ba531d1 100644 --- a/src/sonic-bgpcfgd/.gitignore +++ b/src/sonic-bgpcfgd/.gitignore @@ -2,5 +2,7 @@ build/ dist/ *.egg-info/ +app/*.pyc tests/*.pyc tests/__pycache__/ +.idea diff --git a/src/sonic-bgpcfgd/app/__init__.py b/src/sonic-bgpcfgd/app/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/sonic-bgpcfgd/app/config.py b/src/sonic-bgpcfgd/app/config.py new file mode 100644 index 0000000000..26639fe75e --- /dev/null +++ b/src/sonic-bgpcfgd/app/config.py @@ -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 \ No newline at end of file diff --git a/src/sonic-bgpcfgd/app/log.py b/src/sonic-bgpcfgd/app/log.py new file mode 100644 index 0000000000..4083b13aa6 --- /dev/null +++ b/src/sonic-bgpcfgd/app/log.py @@ -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) \ No newline at end of file diff --git a/src/sonic-bgpcfgd/app/template.py b/src/sonic-bgpcfgd/app/template.py new file mode 100644 index 0000000000..5c8a4ed810 --- /dev/null +++ b/src/sonic-bgpcfgd/app/template.py @@ -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 \ No newline at end of file diff --git a/src/sonic-bgpcfgd/app/util.py b/src/sonic-bgpcfgd/app/util.py new file mode 100644 index 0000000000..b25e651f1e --- /dev/null +++ b/src/sonic-bgpcfgd/app/util.py @@ -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 \ No newline at end of file diff --git a/src/sonic-bgpcfgd/app/vars.py b/src/sonic-bgpcfgd/app/vars.py new file mode 100644 index 0000000000..11377fc87f --- /dev/null +++ b/src/sonic-bgpcfgd/app/vars.py @@ -0,0 +1 @@ +g_debug = False diff --git a/src/sonic-bgpcfgd/bgpcfgd b/src/sonic-bgpcfgd/bgpcfgd index adc142c109..0fbe44602a 100755 --- a/src/sonic-bgpcfgd/bgpcfgd +++ b/src/sonic-bgpcfgd/bgpcfgd @@ -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): diff --git a/src/sonic-bgpcfgd/setup.py b/src/sonic-bgpcfgd/setup.py index 2aea5cdb0c..fae1c31385 100755 --- a/src/sonic-bgpcfgd/setup.py +++ b/src/sonic-bgpcfgd/setup.py @@ -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'], diff --git a/src/sonic-bgpcfgd/tests/data/dynamic/instance.conf/param_all.json b/src/sonic-bgpcfgd/tests/data/dynamic/instance.conf/param_all.json new file mode 100644 index 0000000000..c4134ff298 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/dynamic/instance.conf/param_all.json @@ -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" + } + } +} diff --git a/src/sonic-bgpcfgd/tests/data/dynamic/instance.conf/param_base.json b/src/sonic-bgpcfgd/tests/data/dynamic/instance.conf/param_base.json new file mode 100644 index 0000000000..04fd24fa08 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/dynamic/instance.conf/param_base.json @@ -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" + } + } +} diff --git a/src/sonic-bgpcfgd/tests/data/dynamic/instance.conf/result_all.conf b/src/sonic-bgpcfgd/tests/data/dynamic/instance.conf/result_all.conf new file mode 100644 index 0000000000..1ffc3d3434 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/dynamic/instance.conf/result_all.conf @@ -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 +! diff --git a/src/sonic-bgpcfgd/tests/data/dynamic/instance.conf/result_base.conf b/src/sonic-bgpcfgd/tests/data/dynamic/instance.conf/result_base.conf new file mode 100644 index 0000000000..ac2a4f3e18 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/dynamic/instance.conf/result_base.conf @@ -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 +! diff --git a/src/sonic-bgpcfgd/tests/data/dynamic/peer-group.conf/param_all.json b/src/sonic-bgpcfgd/tests/data/dynamic/peer-group.conf/param_all.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/dynamic/peer-group.conf/param_all.json @@ -0,0 +1 @@ +{} diff --git a/src/sonic-bgpcfgd/tests/data/dynamic/peer-group.conf/result_all.conf b/src/sonic-bgpcfgd/tests/data/dynamic/peer-group.conf/result_all.conf new file mode 100644 index 0000000000..86d5c02972 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/dynamic/peer-group.conf/result_all.conf @@ -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 +! diff --git a/src/sonic-bgpcfgd/tests/data/dynamic/policies.conf/param_all.json b/src/sonic-bgpcfgd/tests/data/dynamic/policies.conf/param_all.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/dynamic/policies.conf/param_all.json @@ -0,0 +1 @@ +{} diff --git a/src/sonic-bgpcfgd/tests/data/dynamic/policies.conf/result_all.conf b/src/sonic-bgpcfgd/tests/data/dynamic/policies.conf/result_all.conf new file mode 100644 index 0000000000..17ca09ec2a --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/dynamic/policies.conf/result_all.conf @@ -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 +! diff --git a/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_ASIC_v4.json b/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_ASIC_v4.json new file mode 100644 index 0000000000..1691732b39 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_ASIC_v4.json @@ -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" + } + } +} diff --git a/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_ASIC_v6.json b/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_ASIC_v6.json new file mode 100644 index 0000000000..89a4117fda --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_ASIC_v6.json @@ -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" + } + } +} diff --git a/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_all_v4.json b/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_all_v4.json new file mode 100644 index 0000000000..c2391674a5 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_all_v4.json @@ -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" + } + } +} diff --git a/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_all_v6.json b/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_all_v6.json new file mode 100644 index 0000000000..d19139a75a --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_all_v6.json @@ -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" + } + } +} diff --git a/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_base_v4.json b/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_base_v4.json new file mode 100644 index 0000000000..e2e59575cb --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_base_v4.json @@ -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" + } + } +} diff --git a/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_base_v6.json b/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_base_v6.json new file mode 100644 index 0000000000..5e79378e3e --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_base_v6.json @@ -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" + } + } +} diff --git a/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_l2vpn.json b/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_l2vpn.json new file mode 100644 index 0000000000..e679bff084 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_l2vpn.json @@ -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" +} diff --git a/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_shutdown_1.json b/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_shutdown_1.json new file mode 100644 index 0000000000..b7c1e2075f --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_shutdown_1.json @@ -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" + } + } +} diff --git a/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_shutdown_2.json b/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_shutdown_2.json new file mode 100644 index 0000000000..610254a2b2 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_shutdown_2.json @@ -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" + } + } +} diff --git a/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_timers_1.json b/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_timers_1.json new file mode 100644 index 0000000000..88d2b6defb --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_timers_1.json @@ -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" + } + } +} diff --git a/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_timers_2.json b/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_timers_2.json new file mode 100644 index 0000000000..4b5c2bec22 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/instance.conf/param_timers_2.json @@ -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" + } + } +} diff --git a/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_ASIC_v4.conf b/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_ASIC_v4.conf new file mode 100644 index 0000000000..1e89f82245 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_ASIC_v4.conf @@ -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 +! diff --git a/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_ASIC_v6.conf b/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_ASIC_v6.conf new file mode 100644 index 0000000000..64d14dcd4f --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_ASIC_v6.conf @@ -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 +! diff --git a/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_all_v4.conf b/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_all_v4.conf new file mode 100644 index 0000000000..2241bbf341 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_all_v4.conf @@ -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 +! diff --git a/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_all_v6.conf b/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_all_v6.conf new file mode 100644 index 0000000000..7194b09166 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_all_v6.conf @@ -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 +! diff --git a/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_base_v4.conf b/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_base_v4.conf new file mode 100644 index 0000000000..2990d5aef7 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_base_v4.conf @@ -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 +! diff --git a/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_base_v6.conf b/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_base_v6.conf new file mode 100644 index 0000000000..38ec714894 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_base_v6.conf @@ -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 +! diff --git a/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_l2vpn.conf b/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_l2vpn.conf new file mode 100644 index 0000000000..b30eaaa62a --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_l2vpn.conf @@ -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 +! diff --git a/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_shutdown_1.conf b/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_shutdown_1.conf new file mode 100644 index 0000000000..9303e3b9ab --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_shutdown_1.conf @@ -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 +! diff --git a/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_shutdown_2.conf b/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_shutdown_2.conf new file mode 100644 index 0000000000..2990d5aef7 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_shutdown_2.conf @@ -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 +! diff --git a/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_timers_1.conf b/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_timers_1.conf new file mode 100644 index 0000000000..ffca0e6b69 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_timers_1.conf @@ -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 +! diff --git a/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_timers_2.conf b/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_timers_2.conf new file mode 100644 index 0000000000..3a8ac3d90e --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/instance.conf/result_timers_2.conf @@ -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 +! diff --git a/src/sonic-bgpcfgd/tests/data/general/peer-group.conf/param_all.json b/src/sonic-bgpcfgd/tests/data/general/peer-group.conf/param_all.json new file mode 100644 index 0000000000..293ccc7990 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/peer-group.conf/param_all.json @@ -0,0 +1,7 @@ +{ + "CONFIG_DB__DEVICE_METADATA": { + "localhost": { + "type": "ToRRouter" + } + } +} \ No newline at end of file diff --git a/src/sonic-bgpcfgd/tests/data/general/peer-group.conf/param_base.json b/src/sonic-bgpcfgd/tests/data/general/peer-group.conf/param_base.json new file mode 100644 index 0000000000..046ffb1a64 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/peer-group.conf/param_base.json @@ -0,0 +1,8 @@ +{ + "CONFIG_DB__DEVICE_METADATA": { + "localhost": { + "type": "LeafRouter", + "sub_role": "BackEnd" + } + } +} \ No newline at end of file diff --git a/src/sonic-bgpcfgd/tests/data/general/peer-group.conf/result_all.conf b/src/sonic-bgpcfgd/tests/data/general/peer-group.conf/result_all.conf new file mode 100644 index 0000000000..63211ded87 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/peer-group.conf/result_all.conf @@ -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 +! diff --git a/src/sonic-bgpcfgd/tests/data/general/peer-group.conf/result_base.conf b/src/sonic-bgpcfgd/tests/data/general/peer-group.conf/result_base.conf new file mode 100644 index 0000000000..a681e7f080 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/peer-group.conf/result_base.conf @@ -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 +! diff --git a/src/sonic-bgpcfgd/tests/data/general/policies.conf/param_all.json b/src/sonic-bgpcfgd/tests/data/general/policies.conf/param_all.json new file mode 100644 index 0000000000..148456fe96 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/policies.conf/param_all.json @@ -0,0 +1,8 @@ +{ + "CONFIG_DB__DEVICE_METADATA": { + "localhost": { + "sub_role": "BackEnd" + } + }, + "loopback0_ipv4": "10.10.10.10/32" +} \ No newline at end of file diff --git a/src/sonic-bgpcfgd/tests/data/general/policies.conf/param_base.json b/src/sonic-bgpcfgd/tests/data/general/policies.conf/param_base.json new file mode 100644 index 0000000000..53bf5572ef --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/policies.conf/param_base.json @@ -0,0 +1,8 @@ +{ + "CONFIG_DB__DEVICE_METADATA": { + "localhost": { + "sub_role": "NotBackEnd" + } + }, + "loopback0_ipv4": "10.10.10.10/32" +} \ No newline at end of file diff --git a/src/sonic-bgpcfgd/tests/data/general/policies.conf/result_all.conf b/src/sonic-bgpcfgd/tests/data/general/policies.conf/result_all.conf new file mode 100644 index 0000000000..1e3288b9a7 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/policies.conf/result_all.conf @@ -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 +! diff --git a/src/sonic-bgpcfgd/tests/data/general/policies.conf/result_base.conf b/src/sonic-bgpcfgd/tests/data/general/policies.conf/result_base.conf new file mode 100644 index 0000000000..9572643522 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/general/policies.conf/result_base.conf @@ -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 +! diff --git a/src/sonic-bgpcfgd/tests/data/monitors/instance.conf/param_all.json b/src/sonic-bgpcfgd/tests/data/monitors/instance.conf/param_all.json new file mode 100644 index 0000000000..9ac3ceda97 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/monitors/instance.conf/param_all.json @@ -0,0 +1,12 @@ +{ + "bgp_asn": "555", + "neighbor_addr": "10.20.30.40", + "bgp_session": { + "name": "monitor" + }, + "constants": { + "deployment_id_asn_map": { + "5": "51111" + } + } +} diff --git a/src/sonic-bgpcfgd/tests/data/monitors/instance.conf/result_all.conf b/src/sonic-bgpcfgd/tests/data/monitors/instance.conf/result_all.conf new file mode 100644 index 0000000000..c8faaa895b --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/monitors/instance.conf/result_all.conf @@ -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 +! diff --git a/src/sonic-bgpcfgd/tests/data/monitors/peer-group.conf/param_all.json b/src/sonic-bgpcfgd/tests/data/monitors/peer-group.conf/param_all.json new file mode 100644 index 0000000000..4f45f8a0f3 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/monitors/peer-group.conf/param_all.json @@ -0,0 +1,3 @@ +{ + "loopback0_ipv4": "1.1.1.1/32" +} \ No newline at end of file diff --git a/src/sonic-bgpcfgd/tests/data/monitors/peer-group.conf/result_all.conf b/src/sonic-bgpcfgd/tests/data/monitors/peer-group.conf/result_all.conf new file mode 100644 index 0000000000..d5e9624ff1 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/monitors/peer-group.conf/result_all.conf @@ -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 +! diff --git a/src/sonic-bgpcfgd/tests/data/monitors/policies.conf/param_all.json b/src/sonic-bgpcfgd/tests/data/monitors/policies.conf/param_all.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/monitors/policies.conf/param_all.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/src/sonic-bgpcfgd/tests/data/monitors/policies.conf/result_all.conf b/src/sonic-bgpcfgd/tests/data/monitors/policies.conf/result_all.conf new file mode 100644 index 0000000000..8d53991064 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/data/monitors/policies.conf/result_all.conf @@ -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 +! diff --git a/src/sonic-bgpcfgd/tests/test_ipv6_nexthop_global.py b/src/sonic-bgpcfgd/tests/test_ipv6_nexthop_global.py new file mode 100644 index 0000000000..bc6d01cdb5 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/test_ipv6_nexthop_global.py @@ -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) diff --git a/src/sonic-bgpcfgd/tests/test_sample.py b/src/sonic-bgpcfgd/tests/test_sample.py deleted file mode 100644 index 3adfe439d2..0000000000 --- a/src/sonic-bgpcfgd/tests/test_sample.py +++ /dev/null @@ -1,4 +0,0 @@ -import pytest - -def test_sample(): - assert True diff --git a/src/sonic-bgpcfgd/tests/test_templates.py b/src/sonic-bgpcfgd/tests/test_templates.py new file mode 100644 index 0000000000..bd0bf9dbb4 --- /dev/null +++ b/src/sonic-bgpcfgd/tests/test_templates.py @@ -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) diff --git a/src/sonic-bgpcfgd/tests/util.py b/src/sonic-bgpcfgd/tests/util.py new file mode 100644 index 0000000000..0bc12b060a --- /dev/null +++ b/src/sonic-bgpcfgd/tests/util.py @@ -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 \ No newline at end of file