diff --git a/dockers/docker-fpm-frr/bgpcfgd b/dockers/docker-fpm-frr/bgpcfgd index f6d0b86385b..4ba8977fddf 100755 --- a/dockers/docker-fpm-frr/bgpcfgd +++ b/dockers/docker-fpm-frr/bgpcfgd @@ -1,124 +1,250 @@ #!/usr/bin/env python import sys -import copy -import Queue -import redis import subprocess +import datetime +import time import syslog +import signal +import traceback import os +import shutil +import tempfile +import json +from collections import defaultdict +from pprint import pprint + +import jinja2 +import netaddr from swsscommon import swsscommon -def run_command(command): - syslog.syslog(syslog.LOG_DEBUG, "execute command {}.".format(command)) - p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - stdout = p.communicate()[0] - p.wait() +g_run = True +g_debug = False + + +def run_command(command, shell=False): + str_cmd = " ".join(command) + if g_debug: + syslog.syslog(syslog.LOG_DEBUG, "execute command {}.".format(str_cmd)) + p = subprocess.Popen(command, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = p.communicate() if p.returncode != 0: - syslog.syslog(syslog.LOG_ERR, 'command execution returned {}. Command: "{}", stdout: "{}"'.format(p.returncode, command, stdout)) + syslog.syslog(syslog.LOG_ERR, 'command execution returned {}. Command: "{}", stdout: "{}", stderr: "{}"'.format(p.returncode, str_cmd, stdout, stderr)) + + return p.returncode, stdout, stderr + +class TemplateFabric(object): + 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=True) + j2_env.filters['ipv4'] = self.is_ipv4 + j2_env.filters['ipv6'] = self.is_ipv6 + self.env = j2_env + + def from_file(self, filename): + return self.env.get_template(filename) + + def from_string(self, tmpl): + return self.env.from_string(tmpl) + + @staticmethod + def is_ipv4(value): + if not value: + return False + if isinstance(value, netaddr.IPNetwork): + addr = value + else: + try: + addr = netaddr.IPNetwork(str(value)) + except: + return False + return addr.version == 4 + + @staticmethod + def is_ipv6(value): + if not value: + return False + if isinstance(value, netaddr.IPNetwork): + addr = value + else: + try: + addr = netaddr.IPNetwork(str(value)) + except: + return False + return addr.version == 6 class BGPConfigManager(object): def __init__(self, daemon): - self.daemon = daemon self.bgp_asn = None - self.bgp_message = Queue.Queue(0) + self.meta = None + self.bgp_messages = [] + self.peers = self.load_peers() # we can have bgp monitors peers here. it could be fixed by adding support for it here + fabric = TemplateFabric() + self.bgp_peer_add_template = fabric.from_file('bgpd.peer.conf.j2') + self.bgp_peer_del_template = fabric.from_string('no neighbor {{ neighbor_addr }}') + self.bgp_peer_shutdown = fabric.from_string('neighbor {{ neighbor_addr }} shutdown') + self.bgp_peer_no_shutdown = fabric.from_string('no neighbor {{ neighbor_addr }} shutdown') daemon.add_manager(swsscommon.CONFIG_DB, swsscommon.CFG_DEVICE_METADATA_TABLE_NAME, self.__metadata_handler) daemon.add_manager(swsscommon.CONFIG_DB, swsscommon.CFG_BGP_NEIGHBOR_TABLE_NAME, self.__bgp_handler) + def load_peers(self): + peers = set() + command = ["vtysh", "-c", "show bgp neighbors json"] + rc, out, err = run_command(command) + if rc == 0: + js_bgp = json.loads(out) + peers = set(js_bgp.keys()) + return peers + def __metadata_handler(self, key, op, data): if key != "localhost" \ or "bgp_asn" not in data \ or self.bgp_asn == data["bgp_asn"]: return - # TODO add ASN update commands + # TODO add ASN update commands + self.meta = { 'localhost': data } self.bgp_asn = data["bgp_asn"] self.__update_bgp() def __update_bgp(self): - while not self.bgp_message.empty(): - key, op, data = self.bgp_message.get() - syslog.syslog(syslog.LOG_INFO, 'value for {} changed to {}'.format(key, data)) + cmds = [] + for key, op, data in self.bgp_messages: if op == swsscommon.SET_COMMAND: - command = "vtysh -c 'configure terminal' -c 'router bgp {}' -c 'neighbor {} remote-as {}'".format(self.bgp_asn, key, data['asn']) - run_command(command) - if "name" in data: - command = "vtysh -c 'configure terminal' -c 'router bgp {}' -c 'neighbor {} description {}'".format(self.bgp_asn, key, data['name']) - run_command(command) - if "admin_status" in data: - command_mod = "no " if data["admin_status"] == "up" else "" - command = "vtysh -c 'configure terminal' -c 'router bgp {}' -c '{}neighbor {} shutdown'".format(self.bgp_asn, command_mod, key) - run_command(command) + if key not in self.peers: + cmds.append(self.bgp_peer_add_template.render(DEVICE_METADATA=self.meta, neighbor_addr=key, bgp_session=data)) + syslog.syslog(syslog.LOG_INFO, 'Peer {} added with attributes {}'.format(key, data)) + self.peers.add(key) + else: + # when the peer is already configured we support "shutdown/no shutdown" + # commands for the peers only + if "admin_status" in data: + if data['admin_status'] == 'up': + cmds.append(self.bgp_peer_no_shutdown.render(neighbor_addr=key)) + syslog.syslog(syslog.LOG_INFO, 'Peer {} admin state is set to "up"'.format(key)) + elif data['admin_status'] == 'down': + cmds.append(self.bgp_peer_shutdown.render(neighbor_addr=key)) + syslog.syslog(syslog.LOG_INFO, 'Peer {} admin state is set to "down"'.format(key)) + else: + syslog.syslog(syslog.LOG_ERR, "Peer {}: Can't update the peer. has wrong attribute value attr['admin_status'] = '{}'".format(key, data['admin_status'])) + else: + syslog.syslog(syslog.LOG_INFO, "Peer {}: Can't update the peer. No 'admin_status' attribute in the request".format(key)) elif op == swsscommon.DEL_COMMAND: - # Neighbor is deleted - command = "vtysh -c 'configure terminal' -c 'router bgp {}' -c 'no neighbor {}'".format(self.bgp_asn, key) - run_command(command) + if key in self.peers: + cmds.append(self.bgp_peer_del_template.render(neighbor_addr=key)) + syslog.syslog(syslog.LOG_INFO, 'Peer {} has been removed'.format(key)) + self.peers.remove(key) + else: + syslog.syslog(syslog.LOG_WARNING, 'Peer {} is not found'.format(key)) + self.bgp_messages = [] + + if len(cmds) == 0: + return + + fd, tmp_filename = tempfile.mkstemp(dir='/tmp') + os.close(fd) + with open (tmp_filename, 'w') as fp: + fp.write('router bgp %s\n' % self.bgp_asn) + for cmd in cmds: + fp.write("%s\n" % cmd) + + command = ["vtysh", "-f", tmp_filename] + run_command(command) #FIXME + os.remove(tmp_filename) def __bgp_handler(self, key, op, data): - self.bgp_message.put((key, op, data)) + self.bgp_messages.append((key, op, data)) # If ASN is not set, we just cache this message until the ASN is set. - if self.bgp_asn == None: - return - self.__update_bgp() + if self.bgp_asn is not None: + self.__update_bgp() class Daemon(object): - SELECT_TIMEOUT = 1000 - SUPPORT_DATABASE_LIST = (swsscommon.APPL_DB, swsscommon.CONFIG_DB) + DATABASE_LIST = [ swsscommon.CONFIG_DB ] def __init__(self): - self.appl_db = swsscommon.DBConnector(swsscommon.APPL_DB, swsscommon.DBConnector.DEFAULT_UNIXSOCKET, 0) - self.conf_db = swsscommon.DBConnector(swsscommon.CONFIG_DB, swsscommon.DBConnector.DEFAULT_UNIXSOCKET, 0) + self.db_connectors = { db : swsscommon.DBConnector(db, swsscommon.DBConnector.DEFAULT_UNIXSOCKET, 0) for db in Daemon.DATABASE_LIST } self.selector = swsscommon.Select() - self.db_connectors = {} - self.callbacks = {} + self.callbacks = defaultdict(lambda : defaultdict(list)) # db -> table -> [] self.subscribers = set() - def get_db_connector(self, db): - if db not in Daemon.SUPPORT_DATABASE_LIST: - raise ValueError("database {} not Daemon support list {}.".format(db, SUPPORT_DATABASE_LIST)) - # if this database connector has been initialized - if db not in self.db_connectors: - self.db_connectors[db] = swsscommon.DBConnector(db, swsscommon.DBConnector.DEFAULT_UNIXSOCKET, 0) - return self.db_connectors[db] - def add_manager(self, db, table_name, callback): - if db not in self.callbacks: - self.callbacks[db] = {} + if db not in Daemon.DATABASE_LIST: + raise ValueError("database {} isn't supported. Supported '{}' only.".format(db, ",".join(Daemon.DATABASE_LIST))) + if table_name not in self.callbacks[db]: - self.callbacks[db][table_name] = [] - conn = self.get_db_connector(db) + conn = self.db_connectors[db] subscriber = swsscommon.SubscriberStateTable(conn, table_name) self.subscribers.add(subscriber) self.selector.addSelectable(subscriber) self.callbacks[db][table_name].append(callback) - def start(self): - while True: - state, selectable = self.selector.select(Daemon.SELECT_TIMEOUT) - if not selectable: + def run(self): + while g_run: + state, _ = self.selector.select(Daemon.SELECT_TIMEOUT) + if state == self.selector.TIMEOUT: continue + elif state == self.selector.ERROR: + raise Exception("Received error from select") + for subscriber in self.subscribers: key, op, fvs = subscriber.pop() - # if no new message if not key: continue - data = dict(fvs) - syslog.syslog(syslog.LOG_DEBUG, "Receive message : {}".format((key, op, fvs))) + if g_debug: + syslog.syslog(syslog.LOG_DEBUG, "Received message : {}".format((key, op, fvs))) for callback in self.callbacks[subscriber.getDbConnector().getDbId()][subscriber.getTableName()]: - callback(key, op, data) + callback(key, op, dict(fvs)) + + +def wait_for_bgpd(): + # wait for 20 seconds + stop_time = datetime.datetime.now() + datetime.timedelta(seconds=20) + syslog.syslog(syslog.LOG_INFO, "Start waiting for bgpd: %s" % str(datetime.datetime.now())) + while datetime.datetime.now() < stop_time: + rc, out, err = run_command(["vtysh", "-c", "show daemons"]) + if rc == 0 and "bgpd" in out: + syslog.syslog(syslog.LOG_INFO, "bgpd connected to vtysh: %s" % str(datetime.datetime.now())) + return + time.sleep(0.1) # sleep 100 ms + raise RuntimeError("bgpd hasn't been started in 20 seconds") def main(): - syslog.openlog("bgpcfgd") + wait_for_bgpd() daemon = Daemon() bgp_manager = BGPConfigManager(daemon) - daemon.start() - syslog.closelog() - -if __name__ == "__main__": - main() + daemon.run() + + +def signal_handler(signum, frame): + global g_run + g_run = False + + +if __name__ == '__main__': + rc = 0 + try: + syslog.openlog('bgpcfgd') + signal.signal(signal.SIGTERM, signal_handler) + signal.signal(signal.SIGINT, signal_handler) + main() + except KeyboardInterrupt: + syslog.syslog(syslog.LOG_NOTICE, "Keyboard interrupt") + except RuntimeError as e: + syslog.syslog(syslog.LOG_CRIT, "%s" % str(e)) + rc = -2 + except Exception as e: + syslog.syslog(syslog.LOG_CRIT, "Got an exception %s: Traceback: %s" % (str(e), traceback.format_exc())) + rc = -1 + finally: + syslog.closelog() + try: + sys.exit(rc) + except SystemExit: + os._exit(rc) diff --git a/dockers/docker-fpm-frr/bgpd.conf.default.j2 b/dockers/docker-fpm-frr/bgpd.conf.default.j2 index 72779ac53d5..957c2792435 100644 --- a/dockers/docker-fpm-frr/bgpd.conf.default.j2 +++ b/dockers/docker-fpm-frr/bgpd.conf.default.j2 @@ -1,3 +1,4 @@ +! {% if DEVICE_METADATA['localhost'].has_key('bgp_asn') %} {% block bgp_init %} ! @@ -12,7 +13,7 @@ route-map TO_BGP_SPEAKER_V4 deny 10 {% if prefix | ipv4 and name == 'Loopback0' %} ip prefix-list PL_LoopbackV4 permit {{ prefix | ip }}/32 {% elif prefix | ipv6 and name == 'Loopback0' %} -ipv6 prefix-list PL_LoopbackV6 permit {{ prefix | ip }}/64 +ipv6 prefix-list PL_LoopbackV6 permit {{ prefix | replace('/128', '/64') | ip_network }}/64 {% endif %} {% endfor %} ! @@ -28,6 +29,13 @@ route-map FROM_BGPMON_V4 deny 10 route-map TO_BGPMON_V4 permit 10 ! {% endif %} +! +route-map ISOLATE permit 10 + set as-path prepend {{ DEVICE_METADATA['localhost']['bgp_asn'] }} +! +route-map set-next-hop-global-v6 permit 10 + set ipv6 next-hop prefer-global +! router bgp {{ DEVICE_METADATA['localhost']['bgp_asn'] }} bgp log-neighbor-changes bgp bestpath as-path multipath-relax @@ -65,63 +73,32 @@ router bgp {{ DEVICE_METADATA['localhost']['bgp_asn'] }} {% endif %} {% endfor %} {% endblock vlan_advertisement %} -{% block bgp_sessions %} -{% for neighbor_addr, bgp_session in BGP_NEIGHBOR.iteritems() %} -{% if bgp_session['asn'] | int != 0 %} - neighbor {{ neighbor_addr }} remote-as {{ bgp_session['asn'] }} - neighbor {{ neighbor_addr }} description {{ bgp_session['name'] }} -{# 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'] }} -{% endif %} -{% if bgp_session.has_key('admin_status') and bgp_session['admin_status'] == 'down' or not bgp_session.has_key('admin_status') and DEVICE_METADATA['localhost'].has_key('default_bgp_status') and DEVICE_METADATA['localhost']['default_bgp_status'] == 'down' %} - neighbor {{ neighbor_addr }} shutdown -{% endif %} -{# Apply default route-map for v4 peers #} -{% if neighbor_addr | ipv4 %} - neighbor {{ neighbor_addr }} route-map TO_BGP_PEER_V4 out -{% endif %} -{% if neighbor_addr | ipv4 %} +{% block maximum_paths %} address-family ipv4 -{% if DEVICE_METADATA['localhost']['type'] == 'ToRRouter' %} - neighbor {{ neighbor_addr }} allowas-in 1 -{% endif %} - neighbor {{ neighbor_addr }} activate - neighbor {{ neighbor_addr }} soft-reconfiguration inbound -{% if bgp_session['rrclient'] | int != 0 %} - neighbor {{ neighbor_addr }} route-reflector-client -{% endif %} -{% if bgp_session['nhopself'] | int != 0 %} - neighbor {{ neighbor_addr }} next-hop-self -{% endif %} maximum-paths 64 exit-address-family -{% endif %} -{% if neighbor_addr | ipv6 %} address-family ipv6 -{% if DEVICE_METADATA['localhost']['type'] == 'ToRRouter' %} - neighbor {{ neighbor_addr }} allowas-in 1 -{% endif %} - neighbor {{ neighbor_addr }} activate - neighbor {{ neighbor_addr }} soft-reconfiguration inbound -{% if bgp_session['rrclient'] | int != 0 %} - neighbor {{ neighbor_addr }} route-reflector-client -{% endif %} -{% if bgp_session['nhopself'] | int != 0 %} - neighbor {{ neighbor_addr }} next-hop-self -{% endif %} -{% if bgp_session['asn'] != DEVICE_METADATA['localhost']['bgp_asn'] %} - neighbor {{ neighbor_addr }} route-map set-next-hop-global-v6 in -{% endif %} -{# Apply default route-map for v6 peers #} - neighbor {{ neighbor_addr }} route-map TO_BGP_PEER_V6 out maximum-paths 64 exit-address-family +{% endblock maximum_paths %} +{% block peers_peer_group %} + neighbor PEER_V4 peer-group + neighbor PEER_V6 peer-group + address-family ipv4 +{% if DEVICE_METADATA['localhost']['type'] == 'ToRRouter' %} + neighbor PEER_V4 allowas-in 1 {% endif %} + neighbor PEER_V4 soft-reconfiguration inbound + neighbor PEER_V4 route-map TO_BGP_PEER_V4 out + exit-address-family + address-family ipv6 +{% if DEVICE_METADATA['localhost']['type'] == 'ToRRouter' %} + neighbor PEER_V6 allowas-in 1 {% endif %} -{% endfor %} -{% endblock bgp_sessions %} + neighbor PEER_V6 soft-reconfiguration inbound + neighbor PEER_V6 route-map TO_BGP_PEER_V6 out + exit-address-family +{% endblock peers_peer_group %} {% block bgp_peers_with_range %} {% if BGP_PEER_RANGE %} {% for bgp_peer in BGP_PEER_RANGE.values() %} @@ -150,11 +127,9 @@ router bgp {{ DEVICE_METADATA['localhost']['bgp_asn'] }} {% endfor %} address-family ipv4 neighbor {{ bgp_peer['name'] }} activate - maximum-paths 64 exit-address-family address-family ipv6 neighbor {{ bgp_peer['name'] }} activate - maximum-paths 64 exit-address-family {% endfor %} {% endif %} diff --git a/dockers/docker-fpm-frr/bgpd.conf.j2 b/dockers/docker-fpm-frr/bgpd.conf.j2 index e9554806b64..b4b2cd59c9b 100644 --- a/dockers/docker-fpm-frr/bgpd.conf.j2 +++ b/dockers/docker-fpm-frr/bgpd.conf.j2 @@ -6,28 +6,13 @@ ! {% endblock banner %} ! -{% block system_init %} -hostname {{ DEVICE_METADATA['localhost']['hostname'] }} -password zebra -log syslog informational -log facility local4 +{% include "daemons.common.conf.j2" %} +! agentx -! enable password {# {{ en_passwd }} TODO: param needed #} -{% endblock system_init %} ! {% if DEVICE_METADATA['localhost']['type'] == "SpineChassisFrontendRouter" %} {% include "bgpd.conf.spine_chassis_frontend_router.j2" %} -{% else%} -{% include "bgpd.conf.default.j2" %} -{% endif %} -! -{% if DEVICE_METADATA['localhost'].has_key('bgp_asn') %} -maximum-paths 64 -! -route-map ISOLATE permit 10 -set as-path prepend {{ DEVICE_METADATA['localhost']['bgp_asn'] }} {% endif %} ! -route-map set-next-hop-global-v6 permit 10 -set ipv6 next-hop prefer-global +{% include "bgpd.conf.default.j2" %} ! diff --git a/dockers/docker-fpm-frr/bgpd.conf.spine_chassis_frontend_router.j2 b/dockers/docker-fpm-frr/bgpd.conf.spine_chassis_frontend_router.j2 index 9bd5ef1947c..afb621b0bf6 100644 --- a/dockers/docker-fpm-frr/bgpd.conf.spine_chassis_frontend_router.j2 +++ b/dockers/docker-fpm-frr/bgpd.conf.spine_chassis_frontend_router.j2 @@ -1,3 +1,4 @@ +! {# VNET BGP Instance #} ! Vnet BGP instance {% set interfaces_in_vnets = [] %} @@ -59,70 +60,4 @@ router bgp {{ DEVICE_METADATA['localhost']['bgp_asn'] }} vrf {{ vnet_name }} {% endfor %} {% endfor %} {% endblock vnet_bgp_instance %} - -{# default bgp #} -{% block default_bgp_instance %} -{% block bgp_init %} -! -! bgp multiple-instance -! -route-map FROM_BGP_SPEAKER_V4 permit 10 -! -route-map TO_BGP_SPEAKER_V4 deny 10 ! -router bgp {{ DEVICE_METADATA['localhost']['bgp_asn'] }} - bgp log-neighbor-changes - bgp bestpath as-path multipath-relax - no bgp default ipv4-unicast - bgp graceful-restart restart-time 240 - bgp graceful-restart -{% for (name, prefix) in LOOPBACK_INTERFACE | pfx_filter %} -{% if prefix | ipv4 and name == 'Loopback0' %} - bgp router-id {{ prefix | ip }} -{% endif %} -{% endfor %} -{# advertise loopback #} -{% for (name, prefix) in LOOPBACK_INTERFACE | pfx_filter %} -{% if prefix | ipv4 and name == 'Loopback0' %} - network {{ prefix | ip }}/32 -{% elif prefix | ipv6 and name == 'Loopback0' %} - address-family ipv6 - network {{ prefix | ip }}/64 - exit-address-family -{% endif %} -{% endfor %} -{% endblock bgp_init %} -{% block bgp_sessions %} -{% for neighbor_addr, bgp_session in BGP_NEIGHBOR.iteritems() %} -{% if not bgp_session.has_key("local_addr") or bgp_session["local_addr"] not in interfaces_in_vnets %} -{% if bgp_session['asn'] | int != 0 %} - neighbor {{ neighbor_addr }} remote-as {{ bgp_session['asn'] }} - neighbor {{ neighbor_addr }} description {{ bgp_session['name'] }} -{# 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'] }} -{% endif %} -{% if bgp_session.has_key('admin_status') and bgp_session['admin_status'] == 'down' or not bgp_session.has_key('admin_status') and DEVICE_METADATA['localhost'].has_key('default_bgp_status') and DEVICE_METADATA['localhost']['default_bgp_status'] == 'down' %} - neighbor {{ neighbor_addr }} shutdown -{% endif %} -{% if bgp_session["asn"] != DEVICE_METADATA['localhost']['bgp_asn'] %} -{% if neighbor_addr | ipv4 %} - address-family ipv4 unicast - neighbor {{ neighbor_addr }} allowas-in 1 - neighbor {{ neighbor_addr }} activate - neighbor {{ neighbor_addr }} soft-reconfiguration inbound - maximum-paths 64 - exit-address-family -{% endif %} -{% else %} - address-family l2vpn evpn - neighbor {{ neighbor_addr }} activate - advertise-all-vni - exit-address-family -{% endif %} -{% endif %} -{% endif %} -{% endfor %} -{% endblock bgp_sessions %} -{% endblock default_bgp_instance %} diff --git a/dockers/docker-fpm-frr/bgpd.peer.conf.j2 b/dockers/docker-fpm-frr/bgpd.peer.conf.j2 new file mode 100644 index 00000000000..7dfdd50ea2c --- /dev/null +++ b/dockers/docker-fpm-frr/bgpd.peer.conf.j2 @@ -0,0 +1,30 @@ +{% block bgp_peer %} + neighbor {{ neighbor_addr }} remote-as {{ bgp_session['asn'] }} + neighbor {{ neighbor_addr }} description {{ bgp_session['name'] }} +{# 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'] }} +{% endif %} +{% if bgp_session.has_key('admin_status') and bgp_session['admin_status'] == 'down' or not bgp_session.has_key('admin_status') and DEVICE_METADATA['localhost'].has_key('default_bgp_status') and DEVICE_METADATA['localhost']['default_bgp_status'] == 'down' %} + neighbor {{ neighbor_addr }} shutdown +{% endif %} +{% if neighbor_addr | ipv4 %} + address-family ipv4 + neighbor {{ neighbor_addr }} peer-group PEER_V4 +{% elif neighbor_addr | ipv6 %} + address-family ipv6 +{% if bgp_session['asn'] != DEVICE_METADATA['localhost']['bgp_asn'] %} + neighbor {{ neighbor_addr }} route-map set-next-hop-global-v6 in +{% endif %} + neighbor {{ neighbor_addr }} peer-group PEER_V6 +{% endif %} +{% if bgp_session['rrclient'] | int != 0 %} + neighbor {{ neighbor_addr }} route-reflector-client +{% endif %} +{% if bgp_session['nhopself'] | int != 0 %} + neighbor {{ neighbor_addr }} next-hop-self +{% endif %} + neighbor {{ neighbor_addr }} activate + exit-address-family +{% endblock bgp_peer %} diff --git a/dockers/docker-fpm-frr/daemons.common.conf.j2 b/dockers/docker-fpm-frr/daemons.common.conf.j2 new file mode 100644 index 00000000000..23eb5184f5e --- /dev/null +++ b/dockers/docker-fpm-frr/daemons.common.conf.j2 @@ -0,0 +1,12 @@ +! +{% block sys_init %} +hostname {{ DEVICE_METADATA['localhost']['hostname'] }} +password zebra +enable password zebra +{% endblock sys_init %} +! +{% block logging %} +log syslog informational +log facility local4 +{% endblock logging %} +! diff --git a/dockers/docker-fpm-frr/frr.conf.j2 b/dockers/docker-fpm-frr/frr.conf.j2 index d9dd6bf0563..afa40ad8ba7 100644 --- a/dockers/docker-fpm-frr/frr.conf.j2 +++ b/dockers/docker-fpm-frr/frr.conf.j2 @@ -6,205 +6,13 @@ ! {% endblock banner %} ! -{% block system_init %} -hostname {{ DEVICE_METADATA['localhost']['hostname'] }} -password zebra -log syslog informational -log facility local4 -agentx -! enable password {# {{ en_passwd }} TODO: param needed #} -{% endblock system_init %} -! -{% block interfaces %} -! Enable link-detect (default disabled) -{% for (name, prefix) in INTERFACE|pfx_filter %} -interface {{ name }} -link-detect -! -{% endfor %} -{% for pc in PORTCHANNEL %} -interface {{ pc }} -link-detect -! -{% endfor %} -{% endblock interfaces %} -! -{% block default_route %} -! set static default route to mgmt gateway as a backup to learned default -{% for (name, prefix) in MGMT_INTERFACE|pfx_filter %} -{% if prefix | ipv4 %} -ip route 0.0.0.0/0 {{ MGMT_INTERFACE[(name, prefix)]['gwaddr'] }} 200 -{% endif %} -{% endfor %} -{% endblock default_route %} -! -{% block source_loopback %} -{% set lo_ipv4_addrs = [] %} -{% set lo_ipv6_addrs = [] %} -{% if LOOPBACK_INTERFACE %} -{% for (name, prefix) in LOOPBACK_INTERFACE|pfx_filter %} -{% if name == 'Loopback0' %} -{% if prefix | ipv6 %} -{% if lo_ipv6_addrs.append(prefix) %} -{% endif %} -{% else %} -{% if lo_ipv4_addrs.append(prefix) %} -{% endif %} -{% endif %} -{% endif %} -{% endfor %} -{% endif %} -! Set ip source to loopback for bgp learned routes -route-map RM_SET_SRC permit 10 - set src {{ lo_ipv4_addrs[0] | ip }} -! -{% if lo_ipv6_addrs|length > 0 %} -route-map RM_SET_SRC6 permit 10 - set src {{ lo_ipv6_addrs[0] | ip }} -! -{% endif %} -ip protocol bgp route-map RM_SET_SRC +{% include "daemons.common.conf.j2" %} ! -{% if lo_ipv6_addrs|length > 0 %} -ipv6 protocol bgp route-map RM_SET_SRC6 -! -{% endif %} -{% endblock source_loopback %} -! -{% if DEVICE_METADATA['localhost'].has_key('bgp_asn') %} -{% block bgp_init %} -! -! bgp multiple-instance -! -route-map FROM_BGP_SPEAKER_V4 permit 10 -! -route-map TO_BGP_SPEAKER_V4 deny 10 -! -router bgp {{ DEVICE_METADATA['localhost']['bgp_asn'] }} - bgp log-neighbor-changes - bgp bestpath as-path multipath-relax - no bgp default ipv4-unicast -{# Advertise graceful restart capability for ToR #} -{% if DEVICE_METADATA['localhost']['type'] == 'ToRRouter' %} - bgp graceful-restart -{% endif %} -{% for (name, prefix) in LOOPBACK_INTERFACE|pfx_filter %} -{% if prefix | ipv4 and name == 'Loopback0' %} - bgp router-id {{ prefix | ip }} -{% endif %} -{% endfor %} -{# advertise loopback #} -{% for (name, prefix) in LOOPBACK_INTERFACE|pfx_filter %} -{% if prefix | ipv4 and name == 'Loopback0' %} - network {{ prefix | ip }}/32 -{% elif prefix | ipv6 and name == 'Loopback0' %} - address-family ipv6 - network {{ prefix | ip }}/64 - exit-address-family -{% endif %} -{% endfor %} -{% endblock bgp_init %} -{% endif %} -{% block vlan_advertisement %} -{% for (name, prefix) in VLAN_INTERFACE|pfx_filter %} -{% if prefix | ipv4 %} - network {{ prefix }} -{% elif prefix | ipv6 %} - address-family ipv6 - network {{ prefix }} - exit-address-family -{% endif %} -{% endfor %} -{% endblock vlan_advertisement %} -{% block bgp_sessions %} -{% for neighbor_addr, bgp_session in BGP_NEIGHBOR.iteritems() %} -{% if bgp_session['asn'] | int != 0 %} - neighbor {{ neighbor_addr }} remote-as {{ bgp_session['asn'] }} - neighbor {{ neighbor_addr }} description {{ bgp_session['name'] }} -{# 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'] }} -{% endif %} -{% if bgp_session.has_key('admin_status') and bgp_session['admin_status'] == 'down' or not bgp_session.has_key('admin_status') and DEVICE_METADATA['localhost'].has_key('default_bgp_status') and DEVICE_METADATA['localhost']['default_bgp_status'] == 'down' %} - neighbor {{ neighbor_addr }} shutdown -{% endif %} -{% if neighbor_addr | ipv4 %} - address-family ipv4 -{% if DEVICE_METADATA['localhost']['type'] == 'ToRRouter' %} - neighbor {{ neighbor_addr }} allowas-in 1 -{% endif %} - neighbor {{ neighbor_addr }} activate - neighbor {{ neighbor_addr }} soft-reconfiguration inbound -{% if bgp_session['rrclient'] | int != 0 %} - neighbor {{ neighbor_addr }} route-reflector-client -{% endif %} -{% if bgp_session['nhopself'] | int != 0 %} - neighbor {{ neighbor_addr }} next-hop-self -{% endif %} - maximum-paths 64 - exit-address-family -{% endif %} -{% if neighbor_addr | ipv6 %} - address-family ipv6 -{% if DEVICE_METADATA['localhost']['type'] == 'ToRRouter' %} - neighbor {{ neighbor_addr }} allowas-in 1 -{% endif %} - neighbor {{ neighbor_addr }} activate - neighbor {{ neighbor_addr }} soft-reconfiguration inbound -{% if bgp_session['rrclient'] | int != 0 %} - neighbor {{ neighbor_addr }} route-reflector-client -{% endif %} -{% if bgp_session['nhopself'] | int != 0 %} - neighbor {{ neighbor_addr }} next-hop-self -{% endif %} -{% if bgp_session['asn'] != DEVICE_METADATA['localhost']['bgp_asn'] %} - neighbor {{ neighbor_addr }} route-map set-next-hop-global-v6 in -{% endif %} - maximum-paths 64 - exit-address-family -{% endif %} -{% endif %} -{% endfor %} -{% endblock bgp_sessions %} -{% block bgp_peers_with_range %} -{% if BGP_PEER_RANGE %} -{% for bgp_peer in BGP_PEER_RANGE.values() %} - neighbor {{ bgp_peer['name'] }} peer-group - neighbor {{ bgp_peer['name'] }} passive - neighbor {{ bgp_peer['name'] }} remote-as {{ deployment_id_asn_map[DEVICE_METADATA['localhost']['deployment_id']] }} - neighbor {{ bgp_peer['name'] }} ebgp-multihop 255 -{% for (name, prefix) in LOOPBACK_INTERFACE|pfx_filter %} -{% if name == 'Loopback1' %} - neighbor {{ bgp_peer['name'] }} update-source {{ prefix | ip }} -{% endif %} -{% endfor %} -{% for ip_range in bgp_peer['ip_range'] %} - bgp listen range {{ip_range}} peer-group {{ bgp_peer['name'] }} -{% endfor %} - address-family ipv4 - neighbor {{ bgp_peer['name'] }} activate - neighbor {{ bgp_peer['name'] }} soft-reconfiguration inbound - neighbor {{ bgp_peer['name'] }} route-map FROM_BGP_SPEAKER_V4 in - neighbor {{ bgp_peer['name'] }} route-map TO_BGP_SPEAKER_V4 out - maximum-paths 64 - exit-address-family - address-family ipv6 - neighbor {{ bgp_peer['name'] }} activate - neighbor {{ bgp_peer['name'] }} soft-reconfiguration inbound - maximum-paths 64 - exit-address-family -{% endfor %} -{% endif %} -{% endblock bgp_peers_with_range %} +agentx ! -{% if DEVICE_METADATA['localhost'].has_key('bgp_asn') %} -maximum-paths 64 +{% include "zebra.interfaces.conf.j2" %} ! -route-map ISOLATE permit 10 -set as-path prepend {{ DEVICE_METADATA['localhost']['bgp_asn'] }} -{% endif %} +{% include "staticd.default_route.conf.j2" %} ! -route-map set-next-hop-global-v6 permit 10 -set ipv6 next-hop prefer-global +{% include "bgpd.conf.default.j2" %} ! diff --git a/dockers/docker-fpm-frr/start.sh b/dockers/docker-fpm-frr/start.sh index fd092919e00..b0d102e8fd0 100755 --- a/dockers/docker-fpm-frr/start.sh +++ b/dockers/docker-fpm-frr/start.sh @@ -31,8 +31,6 @@ rm -f /var/run/rsyslogd.pid supervisorctl start rsyslogd -supervisorctl start bgpcfgd - # Start Quagga processes supervisorctl start zebra supervisorctl start staticd @@ -43,3 +41,5 @@ if [ "$CONFIG_TYPE" == "unified" ]; then fi supervisorctl start fpmsyncd + +supervisorctl start bgpcfgd diff --git a/dockers/docker-fpm-frr/staticd.conf.j2 b/dockers/docker-fpm-frr/staticd.conf.j2 index b3de8c424f1..4e39e17d7db 100644 --- a/dockers/docker-fpm-frr/staticd.conf.j2 +++ b/dockers/docker-fpm-frr/staticd.conf.j2 @@ -6,24 +6,7 @@ ! {% endblock banner %} ! -{% block sys_init %} -hostname {{ DEVICE_METADATA['localhost']['hostname'] }} -password zebra -enable password zebra -{% endblock sys_init %} +{% include "daemons.common.conf.j2" %} ! -{% block default_route %} -! set static default route to mgmt gateway as a backup to learned default -{% for (name, prefix) in MGMT_INTERFACE|pfx_filter %} -{% if prefix | ipv4 %} -ip route 0.0.0.0/0 {{ MGMT_INTERFACE[(name, prefix)]['gwaddr'] }} 200 -{% endif %} -{% endfor %} -{% endblock default_route %} +{% include "staticd.default_route.conf.j2" %} ! -{% block logging %} -log syslog informational -log facility local4 -{% endblock logging %} -! - diff --git a/dockers/docker-fpm-frr/staticd.default_route.conf.j2 b/dockers/docker-fpm-frr/staticd.default_route.conf.j2 new file mode 100644 index 00000000000..22d61ebbd0f --- /dev/null +++ b/dockers/docker-fpm-frr/staticd.default_route.conf.j2 @@ -0,0 +1,10 @@ +! +{% block default_route %} +! set static default route to mgmt gateway as a backup to learned default +{% for (name, prefix) in MGMT_INTERFACE|pfx_filter %} +{% if prefix | ipv4 %} +ip route 0.0.0.0/0 {{ MGMT_INTERFACE[(name, prefix)]['gwaddr'] }} 200 +{% endif %} +{% endfor %} +{% endblock default_route %} +! diff --git a/dockers/docker-fpm-frr/zebra.conf.j2 b/dockers/docker-fpm-frr/zebra.conf.j2 index 3135a056a65..8c1c6f96484 100644 --- a/dockers/docker-fpm-frr/zebra.conf.j2 +++ b/dockers/docker-fpm-frr/zebra.conf.j2 @@ -6,74 +6,7 @@ ! {% endblock banner %} ! -{% block sys_init %} -hostname {{ DEVICE_METADATA['localhost']['hostname'] }} -password zebra -enable password zebra -{% endblock sys_init %} +{% include "daemons.common.conf.j2" %} ! -{% block vrf %} -{% if VNET is defined %} -{% for vnet_name, vnet_metadata in VNET.iteritems() %} -vrf {{ vnet_name }} -vni {{ vnet_metadata['vni'] }} +{% include "zebra.interfaces.conf.j2" %} ! -{% endfor %} -{% endif %} -{% endblock vrf %} -! -{% block interfaces %} -! Enable link-detect (default disabled) -{% for (name, prefix) in INTERFACE|pfx_filter %} -interface {{ name }} -link-detect -! -{% endfor %} -{% for pc in PORTCHANNEL %} -interface {{ pc }} -link-detect -! -{% endfor %} -{% endblock interfaces %} -! -{% block source_loopback %} -{% set lo_ipv4_addrs = [] %} -{% set lo_ipv6_addrs = [] %} -{% if LOOPBACK_INTERFACE %} -{% for (name, prefix) in LOOPBACK_INTERFACE|pfx_filter %} -{% if name == 'Loopback0' %} -{% if prefix | ipv6 %} -{% if lo_ipv6_addrs.append(prefix) %} -{% endif %} -{% else %} -{% if lo_ipv4_addrs.append(prefix) %} -{% endif %} -{% endif %} -{% endif %} -{% endfor %} -{% endif %} -! Set ip source to loopback for bgp learned routes -{% if lo_ipv4_addrs|length > 0 -%} -route-map RM_SET_SRC permit 10 - set src {{ lo_ipv4_addrs[0] | ip }} -! -{% endif %} -{% if lo_ipv6_addrs|length > 0 %} -route-map RM_SET_SRC6 permit 10 - set src {{ lo_ipv6_addrs[0] | ip }} -! -{% endif %} -ip protocol bgp route-map RM_SET_SRC -! -{% if lo_ipv6_addrs|length > 0 %} -ipv6 protocol bgp route-map RM_SET_SRC6 -! -{% endif %} -{% endblock source_loopback %} -! -{% block logging %} -log syslog informational -log facility local4 -{% endblock logging %} -! - diff --git a/dockers/docker-fpm-frr/zebra.interfaces.conf.j2 b/dockers/docker-fpm-frr/zebra.interfaces.conf.j2 new file mode 100644 index 00000000000..4a089e4dc72 --- /dev/null +++ b/dockers/docker-fpm-frr/zebra.interfaces.conf.j2 @@ -0,0 +1,60 @@ +! +{% block vrf %} +{% if VNET is defined %} +{% for vnet_name, vnet_metadata in VNET.iteritems() %} +vrf {{ vnet_name }} +vni {{ vnet_metadata['vni'] }} +! +{% endfor %} +{% endif %} +{% endblock vrf %} +! +{% block interfaces %} +! Enable link-detect (default disabled) +{% for (name, prefix) in INTERFACE|pfx_filter %} +interface {{ name }} +link-detect +! +{% endfor %} +{% for pc in PORTCHANNEL %} +interface {{ pc }} +link-detect +! +{% endfor %} +{% endblock interfaces %} +! +{% block source_loopback %} +{% set lo_ipv4_addrs = [] %} +{% set lo_ipv6_addrs = [] %} +{% if LOOPBACK_INTERFACE %} +{% for (name, prefix) in LOOPBACK_INTERFACE|pfx_filter %} +{% if name == 'Loopback0' %} +{% if prefix | ipv6 %} +{% if lo_ipv6_addrs.append(prefix) %} +{% endif %} +{% else %} +{% if lo_ipv4_addrs.append(prefix) %} +{% endif %} +{% endif %} +{% endif %} +{% endfor %} +{% endif %} +! Set ip source to loopback for bgp learned routes +{% if lo_ipv4_addrs|length > 0 -%} +route-map RM_SET_SRC permit 10 + set src {{ lo_ipv4_addrs[0] | ip }} +! +{% endif %} +{% if lo_ipv6_addrs|length > 0 %} +route-map RM_SET_SRC6 permit 10 + set src {{ lo_ipv6_addrs[0] | ip }} +! +{% endif %} +ip protocol bgp route-map RM_SET_SRC +! +{% if lo_ipv6_addrs|length > 0 %} +ipv6 protocol bgp route-map RM_SET_SRC6 +! +{% endif %} +{% endblock source_loopback %} +! diff --git a/src/sonic-config-engine/sonic-cfggen b/src/sonic-config-engine/sonic-cfggen index b07172b17e7..83739b3d6eb 100755 --- a/src/sonic-config-engine/sonic-cfggen +++ b/src/sonic-config-engine/sonic-cfggen @@ -103,6 +103,14 @@ def pfx_filter(value): table[key] = val return table +def ip_network(value): + """ Extract network for network prefix """ + try: + r_v = netaddr.IPNetwork(value) + except: + return "Invalid ip address %s" % value + return r_v.network + class FormatConverter: """Convert config DB based schema to legacy minigraph based schema for backward capability. We will move to DB schema and remove this class when the config templates are modified. @@ -262,6 +270,7 @@ def main(): env.filters['ipv6'] = is_ipv6 env.filters['unique_name'] = unique_name env.filters['pfx_filter'] = pfx_filter + env.filters['ip_network'] = ip_network for attr in ['ip', 'network', 'prefixlen', 'netmask']: env.filters[attr] = partial(prefix_attr, attr) template = env.get_template(template_file) diff --git a/src/sonic-config-engine/tests/sample_output/bgpd_frr.conf b/src/sonic-config-engine/tests/sample_output/bgpd_frr.conf index c796ef8c164..3a8abf050b4 100644 --- a/src/sonic-config-engine/tests/sample_output/bgpd_frr.conf +++ b/src/sonic-config-engine/tests/sample_output/bgpd_frr.conf @@ -4,12 +4,18 @@ ! file: bgpd.conf ! ! +! hostname switch-t0 password zebra +enable password zebra +! log syslog informational log facility local4 +!! agentx -! enable password ! +! +! +! ! ! bgp multiple-instance ! @@ -18,7 +24,7 @@ route-map FROM_BGP_SPEAKER_V4 permit 10 route-map TO_BGP_SPEAKER_V4 deny 10 ! ip prefix-list PL_LoopbackV4 permit 10.1.0.32/32 -ipv6 prefix-list PL_LoopbackV6 permit fc00:1::32/64 +ipv6 prefix-list PL_LoopbackV6 permit fc00:1::/64 ! ! route-map TO_BGP_PEER_V4 permit 100 @@ -29,6 +35,13 @@ route-map FROM_BGPMON_V4 deny 10 ! route-map TO_BGPMON_V4 permit 10 ! +! +route-map ISOLATE permit 10 + set as-path prepend 65100 +! +route-map set-next-hop-global-v6 permit 10 + set ipv6 next-hop prefer-global +! router bgp 65100 bgp log-neighbor-changes bgp bestpath as-path multipath-relax @@ -42,81 +55,23 @@ router bgp 65100 network fc00:1::32/64 exit-address-family network 192.168.0.1/27 - neighbor 10.0.0.57 remote-as 64600 - neighbor 10.0.0.57 description ARISTA01T1 - neighbor 10.0.0.57 route-map TO_BGP_PEER_V4 out - address-family ipv4 - neighbor 10.0.0.57 allowas-in 1 - neighbor 10.0.0.57 activate - neighbor 10.0.0.57 soft-reconfiguration inbound - maximum-paths 64 - exit-address-family - neighbor 10.0.0.59 remote-as 64600 - neighbor 10.0.0.59 description ARISTA02T1 - neighbor 10.0.0.59 route-map TO_BGP_PEER_V4 out - address-family ipv4 - neighbor 10.0.0.59 allowas-in 1 - neighbor 10.0.0.59 activate - neighbor 10.0.0.59 soft-reconfiguration inbound - maximum-paths 64 - exit-address-family - neighbor 10.0.0.61 remote-as 64600 - neighbor 10.0.0.61 description ARISTA03T1 - neighbor 10.0.0.61 route-map TO_BGP_PEER_V4 out - address-family ipv4 - neighbor 10.0.0.61 allowas-in 1 - neighbor 10.0.0.61 activate - neighbor 10.0.0.61 soft-reconfiguration inbound - maximum-paths 64 - exit-address-family - neighbor 10.0.0.63 remote-as 64600 - neighbor 10.0.0.63 description ARISTA04T1 - neighbor 10.0.0.63 route-map TO_BGP_PEER_V4 out address-family ipv4 - neighbor 10.0.0.63 allowas-in 1 - neighbor 10.0.0.63 activate - neighbor 10.0.0.63 soft-reconfiguration inbound maximum-paths 64 exit-address-family - neighbor fc00::7a remote-as 64600 - neighbor fc00::7a description ARISTA03T1 address-family ipv6 - neighbor fc00::7a allowas-in 1 - neighbor fc00::7a activate - neighbor fc00::7a soft-reconfiguration inbound - neighbor fc00::7a route-map set-next-hop-global-v6 in - neighbor fc00::7a route-map TO_BGP_PEER_V6 out maximum-paths 64 exit-address-family - neighbor fc00::7e remote-as 64600 - neighbor fc00::7e description ARISTA04T1 - address-family ipv6 - neighbor fc00::7e allowas-in 1 - neighbor fc00::7e activate - neighbor fc00::7e soft-reconfiguration inbound - neighbor fc00::7e route-map set-next-hop-global-v6 in - neighbor fc00::7e route-map TO_BGP_PEER_V6 out - maximum-paths 64 - exit-address-family - neighbor fc00::72 remote-as 64600 - neighbor fc00::72 description ARISTA01T1 - address-family ipv6 - neighbor fc00::72 allowas-in 1 - neighbor fc00::72 activate - neighbor fc00::72 soft-reconfiguration inbound - neighbor fc00::72 route-map set-next-hop-global-v6 in - neighbor fc00::72 route-map TO_BGP_PEER_V6 out - maximum-paths 64 + neighbor PEER_V4 peer-group + neighbor PEER_V6 peer-group + address-family ipv4 + neighbor PEER_V4 allowas-in 1 + neighbor PEER_V4 soft-reconfiguration inbound + neighbor PEER_V4 route-map TO_BGP_PEER_V4 out exit-address-family - neighbor fc00::76 remote-as 64600 - neighbor fc00::76 description ARISTA02T1 address-family ipv6 - neighbor fc00::76 allowas-in 1 - neighbor fc00::76 activate - neighbor fc00::76 soft-reconfiguration inbound - neighbor fc00::76 route-map set-next-hop-global-v6 in - neighbor fc00::76 route-map TO_BGP_PEER_V6 out - maximum-paths 64 + neighbor PEER_V6 allowas-in 1 + neighbor PEER_V6 soft-reconfiguration inbound + neighbor PEER_V6 route-map TO_BGP_PEER_V6 out exit-address-family neighbor BGPMON_V4 peer-group neighbor BGPMON_V4 update-source 10.1.0.32 @@ -129,11 +84,3 @@ router bgp 65100 neighbor 10.20.30.40 description BGPMonitor neighbor 10.20.30.40 activate !! -maximum-paths 64 -! -route-map ISOLATE permit 10 -set as-path prepend 65100 -! -route-map set-next-hop-global-v6 permit 10 -set ipv6 next-hop prefer-global -! diff --git a/src/sonic-config-engine/tests/sample_output/frr.conf b/src/sonic-config-engine/tests/sample_output/frr.conf index 8e7f97cf8c5..edcf0939a03 100644 --- a/src/sonic-config-engine/tests/sample_output/frr.conf +++ b/src/sonic-config-engine/tests/sample_output/frr.conf @@ -4,12 +4,18 @@ ! file: frr.conf ! ! +! hostname switch-t0 password zebra +enable password zebra +! log syslog informational log facility local4 +!! agentx -! enable password ! +! +! +! ! Enable link-detect (default disabled) interface PortChannel01 link-detect @@ -24,9 +30,6 @@ interface PortChannel04 link-detect ! ! -! set static default route to mgmt gateway as a backup to learned default -ip route 0.0.0.0/0 10.0.0.1 200 -! ! Set ip source to loopback for bgp learned routes route-map RM_SET_SRC permit 10 set src 10.1.0.32 @@ -39,6 +42,11 @@ ip protocol bgp route-map RM_SET_SRC ! ipv6 protocol bgp route-map RM_SET_SRC6 ! +!! +! +! set static default route to mgmt gateway as a backup to learned default +ip route 0.0.0.0/0 10.0.0.1 200 +!! ! ! ! bgp multiple-instance @@ -47,91 +55,64 @@ route-map FROM_BGP_SPEAKER_V4 permit 10 ! route-map TO_BGP_SPEAKER_V4 deny 10 ! +ip prefix-list PL_LoopbackV4 permit 10.1.0.32/32 +ipv6 prefix-list PL_LoopbackV6 permit fc00:1::/64 +! +! +route-map TO_BGP_PEER_V4 permit 100 +! +route-map TO_BGP_PEER_V6 permit 100 +! +route-map FROM_BGPMON_V4 deny 10 +! +route-map TO_BGPMON_V4 permit 10 +! +! +route-map ISOLATE permit 10 + set as-path prepend 65100 +! +route-map set-next-hop-global-v6 permit 10 + set ipv6 next-hop prefer-global +! router bgp 65100 bgp log-neighbor-changes bgp bestpath as-path multipath-relax no bgp default ipv4-unicast + bgp graceful-restart restart-time 240 bgp graceful-restart + bgp graceful-restart preserve-fw-state bgp router-id 10.1.0.32 network 10.1.0.32/32 address-family ipv6 network fc00:1::32/64 exit-address-family network 192.168.0.1/27 - neighbor 10.0.0.57 remote-as 64600 - neighbor 10.0.0.57 description ARISTA01T1 address-family ipv4 - neighbor 10.0.0.57 allowas-in 1 - neighbor 10.0.0.57 activate - neighbor 10.0.0.57 soft-reconfiguration inbound maximum-paths 64 exit-address-family - neighbor 10.0.0.59 remote-as 64600 - neighbor 10.0.0.59 description ARISTA02T1 - address-family ipv4 - neighbor 10.0.0.59 allowas-in 1 - neighbor 10.0.0.59 activate - neighbor 10.0.0.59 soft-reconfiguration inbound - maximum-paths 64 - exit-address-family - neighbor 10.0.0.61 remote-as 64600 - neighbor 10.0.0.61 description ARISTA03T1 - address-family ipv4 - neighbor 10.0.0.61 allowas-in 1 - neighbor 10.0.0.61 activate - neighbor 10.0.0.61 soft-reconfiguration inbound - maximum-paths 64 - exit-address-family - neighbor 10.0.0.63 remote-as 64600 - neighbor 10.0.0.63 description ARISTA04T1 - address-family ipv4 - neighbor 10.0.0.63 allowas-in 1 - neighbor 10.0.0.63 activate - neighbor 10.0.0.63 soft-reconfiguration inbound - maximum-paths 64 - exit-address-family - neighbor fc00::7a remote-as 64600 - neighbor fc00::7a description ARISTA03T1 - address-family ipv6 - neighbor fc00::7a allowas-in 1 - neighbor fc00::7a activate - neighbor fc00::7a soft-reconfiguration inbound - neighbor fc00::7a route-map set-next-hop-global-v6 in - maximum-paths 64 - exit-address-family - neighbor fc00::7e remote-as 64600 - neighbor fc00::7e description ARISTA04T1 address-family ipv6 - neighbor fc00::7e allowas-in 1 - neighbor fc00::7e activate - neighbor fc00::7e soft-reconfiguration inbound - neighbor fc00::7e route-map set-next-hop-global-v6 in maximum-paths 64 exit-address-family - neighbor fc00::72 remote-as 64600 - neighbor fc00::72 description ARISTA01T1 - address-family ipv6 - neighbor fc00::72 allowas-in 1 - neighbor fc00::72 activate - neighbor fc00::72 soft-reconfiguration inbound - neighbor fc00::72 route-map set-next-hop-global-v6 in - maximum-paths 64 + neighbor PEER_V4 peer-group + neighbor PEER_V6 peer-group + address-family ipv4 + neighbor PEER_V4 allowas-in 1 + neighbor PEER_V4 soft-reconfiguration inbound + neighbor PEER_V4 route-map TO_BGP_PEER_V4 out exit-address-family - neighbor fc00::76 remote-as 64600 - neighbor fc00::76 description ARISTA02T1 address-family ipv6 - neighbor fc00::76 allowas-in 1 - neighbor fc00::76 activate - neighbor fc00::76 soft-reconfiguration inbound - neighbor fc00::76 route-map set-next-hop-global-v6 in - maximum-paths 64 + neighbor PEER_V6 allowas-in 1 + neighbor PEER_V6 soft-reconfiguration inbound + neighbor PEER_V6 route-map TO_BGP_PEER_V6 out exit-address-family -! -maximum-paths 64 -! -route-map ISOLATE permit 10 -set as-path prepend 65100 -! -route-map set-next-hop-global-v6 permit 10 -set ipv6 next-hop prefer-global -! + neighbor BGPMON_V4 peer-group + neighbor BGPMON_V4 update-source 10.1.0.32 + neighbor BGPMON_V4 route-map FROM_BGPMON_V4 in + neighbor BGPMON_V4 route-map TO_BGPMON_V4 out + neighbor BGPMON_V4 send-community + neighbor BGPMON_V4 maximum-prefix 1 + neighbor 10.20.30.40 remote-as 65100 + neighbor 10.20.30.40 peer-group BGPMON_V4 + neighbor 10.20.30.40 description BGPMonitor + neighbor 10.20.30.40 activate +!! diff --git a/src/sonic-config-engine/tests/sample_output/staticd_frr.conf b/src/sonic-config-engine/tests/sample_output/staticd_frr.conf index a1a5fddf322..12a81de8212 100644 --- a/src/sonic-config-engine/tests/sample_output/staticd_frr.conf +++ b/src/sonic-config-engine/tests/sample_output/staticd_frr.conf @@ -4,14 +4,15 @@ ! file: staticd.conf ! ! +! hostname switch-t0 password zebra enable password zebra ! -! set static default route to mgmt gateway as a backup to learned default -ip route 0.0.0.0/0 10.0.0.1 200 -! log syslog informational log facility local4 +!! ! - +! set static default route to mgmt gateway as a backup to learned default +ip route 0.0.0.0/0 10.0.0.1 200 +!! diff --git a/src/sonic-config-engine/tests/sample_output/t2-chassis-fe-bgpd.conf b/src/sonic-config-engine/tests/sample_output/t2-chassis-fe-bgpd.conf index 515e0aba8df..b0b5e2cb119 100644 --- a/src/sonic-config-engine/tests/sample_output/t2-chassis-fe-bgpd.conf +++ b/src/sonic-config-engine/tests/sample_output/t2-chassis-fe-bgpd.conf @@ -4,12 +4,17 @@ ! file: bgpd.conf ! ! +! hostname SpineFront01 password zebra +enable password zebra +! log syslog informational log facility local4 +!! agentx -! enable password ! +! +! ! Vnet BGP instance router bgp 4000 vrf VnetFE no bgp default ipv4-unicast @@ -30,7 +35,8 @@ router bgp 4000 vrf VnetFE address-family l2vpn evpn advertise ipv4 unicast exit-address-family - +!! +! ! ! bgp multiple-instance ! @@ -38,6 +44,20 @@ route-map FROM_BGP_SPEAKER_V4 permit 10 ! route-map TO_BGP_SPEAKER_V4 deny 10 ! +ip prefix-list PL_LoopbackV4 permit 4.0.0.0/32 +! +! +route-map TO_BGP_PEER_V4 permit 100 +! +route-map TO_BGP_PEER_V6 permit 100 +! +! +route-map ISOLATE permit 10 + set as-path prepend 4000 +! +route-map set-next-hop-global-v6 permit 10 + set ipv6 next-hop prefer-global +! router bgp 4000 bgp log-neighbor-changes bgp bestpath as-path multipath-relax @@ -46,37 +66,20 @@ router bgp 4000 bgp graceful-restart bgp router-id 4.0.0.0 network 4.0.0.0/32 - neighbor 4.0.0.1 remote-as 4000 - neighbor 4.0.0.1 description SpineFront02 - neighbor 4.0.0.1 timers 3 10 - address-family l2vpn evpn - neighbor 4.0.0.1 activate - advertise-all-vni - exit-address-family - neighbor 172.16.0.2 remote-as 5000 - neighbor 172.16.0.2 description SpineBack01 - neighbor 172.16.0.2 timers 3 10 - address-family ipv4 unicast - neighbor 172.16.0.2 allowas-in 1 - neighbor 172.16.0.2 activate - neighbor 172.16.0.2 soft-reconfiguration inbound + address-family ipv4 maximum-paths 64 exit-address-family - neighbor 172.16.0.10 remote-as 5000 - neighbor 172.16.0.10 description SpineBack02 - neighbor 172.16.0.10 timers 3 10 - address-family ipv4 unicast - neighbor 172.16.0.10 allowas-in 1 - neighbor 172.16.0.10 activate - neighbor 172.16.0.10 soft-reconfiguration inbound + address-family ipv6 maximum-paths 64 exit-address-family -! -maximum-paths 64 -! -route-map ISOLATE permit 10 -set as-path prepend 4000 -! -route-map set-next-hop-global-v6 permit 10 -set ipv6 next-hop prefer-global -! + neighbor PEER_V4 peer-group + neighbor PEER_V6 peer-group + address-family ipv4 + neighbor PEER_V4 soft-reconfiguration inbound + neighbor PEER_V4 route-map TO_BGP_PEER_V4 out + exit-address-family + address-family ipv6 + neighbor PEER_V6 soft-reconfiguration inbound + neighbor PEER_V6 route-map TO_BGP_PEER_V6 out + exit-address-family +!! diff --git a/src/sonic-config-engine/tests/sample_output/t2-chassis-fe-vni-zebra.conf b/src/sonic-config-engine/tests/sample_output/t2-chassis-fe-vni-zebra.conf index 30571f2082a..bd2b5c84f47 100644 --- a/src/sonic-config-engine/tests/sample_output/t2-chassis-fe-vni-zebra.conf +++ b/src/sonic-config-engine/tests/sample_output/t2-chassis-fe-vni-zebra.conf @@ -4,10 +4,15 @@ ! file: zebra.conf ! ! +! hostname SpineFront01 password zebra enable password zebra ! +log syslog informational +log facility local4 +!! +! vrf VnetFE vni 9000 ! @@ -29,8 +34,4 @@ route-map RM_SET_SRC permit 10 ! ip protocol bgp route-map RM_SET_SRC ! -! -log syslog informational -log facility local4 -! - +!! diff --git a/src/sonic-config-engine/tests/sample_output/t2-chassis-fe-zebra.conf b/src/sonic-config-engine/tests/sample_output/t2-chassis-fe-zebra.conf index c8157b0519a..e047fcd64f2 100644 --- a/src/sonic-config-engine/tests/sample_output/t2-chassis-fe-zebra.conf +++ b/src/sonic-config-engine/tests/sample_output/t2-chassis-fe-zebra.conf @@ -4,10 +4,15 @@ ! file: zebra.conf ! ! +! hostname SpineFront01 password zebra enable password zebra ! +log syslog informational +log facility local4 +!! +! vrf VnetFE vni 8000 ! @@ -29,8 +34,4 @@ route-map RM_SET_SRC permit 10 ! ip protocol bgp route-map RM_SET_SRC ! -! -log syslog informational -log facility local4 -! - +!! diff --git a/src/sonic-config-engine/tests/sample_output/zebra_frr.conf b/src/sonic-config-engine/tests/sample_output/zebra_frr.conf index 2b21ea523f7..690f609dafc 100644 --- a/src/sonic-config-engine/tests/sample_output/zebra_frr.conf +++ b/src/sonic-config-engine/tests/sample_output/zebra_frr.conf @@ -4,10 +4,15 @@ ! file: zebra.conf ! ! +! hostname switch-t0 password zebra enable password zebra ! +log syslog informational +log facility local4 +!! +! ! ! Enable link-detect (default disabled) interface PortChannel01 @@ -35,8 +40,4 @@ ip protocol bgp route-map RM_SET_SRC ! ipv6 protocol bgp route-map RM_SET_SRC6 ! -! -log syslog informational -log facility local4 -! - +!! diff --git a/src/sonic-config-engine/tests/test_frr.py b/src/sonic-config-engine/tests/test_frr.py new file mode 100644 index 00000000000..fcbff063b13 --- /dev/null +++ b/src/sonic-config-engine/tests/test_frr.py @@ -0,0 +1,62 @@ +from unittest import TestCase +import subprocess +import os +import filecmp + + +class TestCfgGen(TestCase): + def setUp(self): + self.test_dir = os.path.dirname(os.path.realpath(__file__)) + self.script_file = os.path.join(self.test_dir, '..', 'sonic-cfggen') + self.t0_minigraph = os.path.join(self.test_dir, 't0-sample-graph.xml') + self.t0_port_config = os.path.join(self.test_dir, 't0-sample-port-config.ini') + self.output_file = os.path.join(self.test_dir, 'output') + + def tearDown(self): + try: + os.remove(self.output_file) + except OSError: + pass + + + def run_script(self, argument, check_stderr=False): +# print '\n Running sonic-cfggen ' + argument + if check_stderr: + output = subprocess.check_output(self.script_file + ' ' + argument, stderr=subprocess.STDOUT, shell=True) + else: + output = subprocess.check_output(self.script_file + ' ' + argument, shell=True) + + linecount = output.strip().count('\n') + if linecount <= 0: + print ' Output: ' + output.strip() + else: + print ' Output: ({0} lines, {1} bytes)'.format(linecount + 1, len(output)) + return output + + def run_diff(self, file1, file2): + return subprocess.check_output('diff -u {} {} || true'.format(file1, file2), shell=True) + + def run_case(self, template, target): + conf_template = os.path.join(self.test_dir, '..', '..', '..', 'dockers', 'docker-fpm-frr', template) + cmd = '-m ' + self.t0_minigraph + ' -p ' + self.t0_port_config + ' -t ' + conf_template + ' > ' + self.output_file + self.run_script(cmd) + + original_filename = os.path.join(self.test_dir, 'sample_output', target) + r = filecmp.cmp(original_filename, self.output_file) + diff_output = self.run_diff(original_filename, self.output_file) if not r else "" + + return r, "Diff:\n" + diff_output + + + def test_config_frr(self): + self.assertTrue(*self.run_case('frr.conf.j2', 'frr.conf')) + + def test_bgpd_frr(self): + self.assertTrue(*self.run_case('bgpd.conf.j2', 'bgpd_frr.conf')) + + def test_zebra_frr(self): + self.assertTrue(*self.run_case('zebra.conf.j2', 'zebra_frr.conf')) + + def test_staticd_frr(self): + self.assertTrue(*self.run_case('staticd.conf.j2', 'staticd_frr.conf')) + diff --git a/src/sonic-config-engine/tests/test_j2files.py b/src/sonic-config-engine/tests/test_j2files.py index 1fd9df37e98..c3585a41d44 100644 --- a/src/sonic-config-engine/tests/test_j2files.py +++ b/src/sonic-config-engine/tests/test_j2files.py @@ -77,34 +77,6 @@ def test_zebra_quagga(self): self.run_script(argument) self.assertTrue(filecmp.cmp(os.path.join(self.test_dir, 'sample_output', 'zebra_quagga.conf'), self.output_file)) - def test_config_frr(self): - conf_template = os.path.join(self.test_dir, '..', '..', '..', 'dockers', 'docker-fpm-frr', 'frr.conf.j2') - argument = '-m ' + self.t0_minigraph + ' -p ' + self.t0_port_config + ' -t ' + conf_template + ' > ' + self.output_file - self.run_script(argument) - self.assertTrue(filecmp.cmp(os.path.join(self.test_dir, 'sample_output', 'frr.conf'), self.output_file)) - - - def test_bgpd_frr(self): - conf_template = os.path.join(self.test_dir, '..', '..', '..', 'dockers', 'docker-fpm-frr', 'bgpd.conf.j2') - argument = '-m ' + self.t0_minigraph + ' -p ' + self.t0_port_config + ' -t ' + conf_template + ' > ' + self.output_file - self.run_script(argument) - original_filename = os.path.join(self.test_dir, 'sample_output', 'bgpd_frr.conf') - r = filecmp.cmp(original_filename, self.output_file) - diff_output = self.run_diff(original_filename, self.output_file) if not r else "" - self.assertTrue(r, "Diff:\n" + diff_output) - - def test_zebra_frr(self): - conf_template = os.path.join(self.test_dir, '..', '..', '..', 'dockers', 'docker-fpm-frr', 'zebra.conf.j2') - argument = '-m ' + self.t0_minigraph + ' -p ' + self.t0_port_config + ' -t ' + conf_template + ' > ' + self.output_file - self.run_script(argument) - self.assertTrue(filecmp.cmp(os.path.join(self.test_dir, 'sample_output', 'zebra_frr.conf'), self.output_file)) - - def test_staticd_frr(self): - conf_template = os.path.join(self.test_dir, '..', '..', '..', 'dockers', 'docker-fpm-frr', 'staticd.conf.j2') - argument = '-m ' + self.t0_minigraph + ' -p ' + self.t0_port_config + ' -t ' + conf_template + ' > ' + self.output_file - self.run_script(argument) - self.assertTrue(filecmp.cmp(os.path.join(self.test_dir, 'sample_output', 'staticd_frr.conf'), self.output_file)) - def test_ipinip(self): ipinip_file = os.path.join(self.test_dir, '..', '..', '..', 'dockers', 'docker-orchagent', 'ipinip.json.j2') argument = '-m ' + self.t0_minigraph + ' -p ' + self.t0_port_config + ' -t ' + ipinip_file + ' > ' + self.output_file diff --git a/src/sonic-config-engine/tests/test_j2files_t2_chassis_fe.py b/src/sonic-config-engine/tests/test_j2files_t2_chassis_fe.py index 531a9d477a6..41ac347e2b1 100644 --- a/src/sonic-config-engine/tests/test_j2files_t2_chassis_fe.py +++ b/src/sonic-config-engine/tests/test_j2files_t2_chassis_fe.py @@ -16,42 +16,39 @@ def setUp(self): self.t2_chassis_fe_port_config = os.path.join(self.test_dir, 't2-chassis-fe-port-config.ini') self.output_file = os.path.join(self.test_dir, 'output') + def tearDown(self): + try: + os.remove(self.output_file) + except OSError: + pass + def run_script(self, argument): print 'CMD: sonic-cfggen ' + argument return subprocess.check_output(self.script_file + ' ' + argument, shell=True) + def run_diff(self, file1, file2): + return subprocess.check_output('diff -u {} {} || true'.format(file1, file2), shell=True) + + def run_case(self, minigraph, template, target): + conf_template = os.path.join(self.test_dir, '..', '..', '..', 'dockers', 'docker-fpm-frr', template) + cmd = '-m ' + minigraph + ' -p ' + self.t2_chassis_fe_port_config + ' -t ' + conf_template + ' > ' + self.output_file + self.run_script(cmd) + + original_filename = os.path.join(self.test_dir, 'sample_output', target) + r = filecmp.cmp(original_filename, self.output_file) + diff_output = self.run_diff(original_filename, self.output_file) if not r else "" + + return r, "Diff:\n" + diff_output + # Test zebra.conf in FRR docker for a T2 chassis frontend (fe) def test_t2_chassis_fe_zebra_frr(self): - conf_template = os.path.join(self.test_dir, '..', '..', '..', 'dockers', 'docker-fpm-frr', 'zebra.conf.j2') - argument = '-m ' + self.t2_chassis_fe_minigraph + ' -p ' + self.t2_chassis_fe_port_config + ' -t ' + conf_template + ' > ' + self.output_file - self.run_script(argument) - self.assertTrue(filecmp.cmp(os.path.join(self.test_dir, 'sample_output', 't2-chassis-fe-zebra.conf'), self.output_file)) - - # Test zebra.conf in FRR docker for a T2 chassis frontend (fe) switch with port channel interfaces - def test_t2_chassis_fe_pc_zebra_frr(self): - conf_template = os.path.join(self.test_dir, '..', '..', '..', 'dockers', 'docker-fpm-frr', 'zebra.conf.j2') - argument = '-m ' + self.t2_chassis_fe_pc_minigraph + ' -p ' + self.t2_chassis_fe_port_config + ' -t ' + conf_template + ' > ' + self.output_file - self.run_script(argument) - self.assertTrue(filecmp.cmp(os.path.join(self.test_dir, 'sample_output', 't2-chassis-fe-pc-zebra.conf'), self.output_file)) + self.assertTrue(*self.run_case(self.t2_chassis_fe_minigraph, 'zebra.conf.j2', 't2-chassis-fe-zebra.conf')) # Test zebra.conf in FRR docker for a T2 chassis frontend (fe) switch with specified VNI def test_t2_chassis_fe_vni_zebra_frr(self): - conf_template = os.path.join(self.test_dir, '..', '..', '..', 'dockers', 'docker-fpm-frr', 'zebra.conf.j2') - argument = '-m ' + self.t2_chassis_fe_vni_minigraph + ' -p ' + self.t2_chassis_fe_port_config + ' -t ' + conf_template + ' > ' + self.output_file - self.run_script(argument) - self.assertTrue(filecmp.cmp(os.path.join(self.test_dir, 'sample_output', 't2-chassis-fe-vni-zebra.conf'), self.output_file)) + self.assertTrue(*self.run_case(self.t2_chassis_fe_vni_minigraph, 'zebra.conf.j2', 't2-chassis-fe-vni-zebra.conf')) # Test bgpd.conf in FRR docker for a T2 chassis frontend (fe) def test_t2_chassis_frontend_bgpd_frr(self): - conf_template = os.path.join(self.test_dir, '..', '..', '..', 'dockers', 'docker-fpm-frr', 'bgpd.conf.j2') - argument = '-m ' + self.t2_chassis_fe_minigraph + ' -p ' + self.t2_chassis_fe_port_config + ' -t ' + conf_template + ' > ' + self.output_file - self.run_script(argument) - self.assertTrue(filecmp.cmp(os.path.join(self.test_dir, 'sample_output', 't2-chassis-fe-bgpd.conf'), self.output_file)) - - def tearDown(self): - try: - os.remove(self.output_file) - except OSError: - pass - + self.assertTrue(*self.run_case(self.t2_chassis_fe_minigraph, 'bgpd.conf.j2', 't2-chassis-fe-bgpd.conf'))