Skip to content

Commit

Permalink
Tests for bgpcfgd templates (#4841)
Browse files Browse the repository at this point in the history
* Tests for bgpcfgd templates
  • Loading branch information
pavel-shirshov authored and abdosi committed Jul 5, 2020
1 parent 1930b3a commit 6958441
Show file tree
Hide file tree
Showing 59 changed files with 1,072 additions and 260 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
!
31 changes: 18 additions & 13 deletions dockers/docker-fpm-frr/frr/bgpd/templates/general/instance.conf.j2
Original file line number Diff line number Diff line change
Expand Up @@ -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' %}
Expand All @@ -15,45 +15,50 @@
!
{% 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
neighbor {{ neighbor_addr }} activate
advertise-all-vni
exit-address-family
{% endif %}
neighbor {{ neighbor_addr }} activate
exit-address-family
!
! end of template: bgpd/templates/general/instance.conf.j2
!
2 changes: 2 additions & 0 deletions src/sonic-bgpcfgd/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@
build/
dist/
*.egg-info/
app/*.pyc
tests/*.pyc
tests/__pycache__/
.idea
Empty file.
103 changes: 103 additions & 0 deletions src/sonic-bgpcfgd/app/config.py
Original file line number Diff line number Diff line change
@@ -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 changes: 33 additions & 0 deletions src/sonic-bgpcfgd/app/log.py
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 1 addition & 1 deletion src/sonic-bgpcfgd/app/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,4 @@ def pfx_filter(value):
log_err("'%s' is invalid ip address" % ip_address)
else:
table[key] = val
return table
return table
22 changes: 22 additions & 0 deletions src/sonic-bgpcfgd/app/util.py
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions src/sonic-bgpcfgd/app/vars.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
g_debug = False
Loading

0 comments on commit 6958441

Please sign in to comment.