-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
7.5 backports #8156
7.5 backports #8156
Conversation
When looking up the conversion from kernel protocol to internal protocol family make sure we use the correct AF_INET( what the kernel uses ) instead of AFI_IP (which is what FRR uses ). Routes from OSPF will show up from the kernel as OSPF6 instead of OSPF. Which will cause mayhem Ticket: CM-33306 Signed-off-by: Donald Sharp <sharpd@nvidia.com>
When checking for local connected address used as a nexthop gateway, we should check nexthop VRF, not route VRF. Signed-off-by: Igor Ryzhov <iryzhov@nfware.com>
Currently, staticd creates a VRF for the nexthop it is trying to install. Later, when this nexthop is deleted, the VRF stays in the system and can not be deleted by the user because "no vrf" command doesn't work for this VRF because it was not created through northbound code. There is no need to create the VRF. Just set nh_vrf_id to VRF_UNKNOWN when the VRF doesn't exist. Signed-off-by: Igor Ryzhov <iryzhov@nfware.com>
When enabling the VRF, we should not install the nexthops that rely on non-existent VRF. For example, if we have route "1.1.1.0/24 2.2.2.2 vrf red nexthop-vrf blue", and VRF red is enabled, we should not install it if VRF blue doesn't exist. Signed-off-by: Igor Ryzhov <iryzhov@nfware.com>
When bgp registers for a nexthop that is not reachable due to the nexthop pointing to a blackhole, bgp is never going to be able to reach it when attempting to open a connection. Broken behavior: <show bgp nexthop> 192.168.161.204 valid [IGP metric 0], #paths 0, peer 192.168.161.204 blackhole Last update: Thu Feb 11 09:46:10 2021 eva# show bgp ipv4 uni summ fail BGP router identifier 10.10.3.11, local AS number 3235 vrf-id 0 BGP table version 40 RIB entries 78, using 14 KiB of memory Peers 2, using 54 KiB of memory Neighbor EstdCnt DropCnt ResetTime Reason 192.168.161.204 0 0 never Waiting for peer OPEN The log file fills up with this type of message: 2021-02-09T18:53:11.653433+00:00 nq-sjc6c-cor-01 bgpd[6548]: can't connect to 24.51.27.241 fd 26 : Invalid argument 2021-02-09T18:53:21.654005+00:00 nq-sjc6c-cor-01 bgpd[6548]: can't connect to 24.51.27.241 fd 26 : Invalid argument 2021-02-09T18:53:31.654381+00:00 nq-sjc6c-cor-01 bgpd[6548]: can't connect to 24.51.27.241 fd 26 : Invalid argument 2021-02-09T18:53:41.654729+00:00 nq-sjc6c-cor-01 bgpd[6548]: can't connect to 24.51.27.241 fd 26 : Invalid argument 2021-02-09T18:53:51.655147+00:00 nq-sjc6c-cor-01 bgpd[6548]: can't connect to 24.51.27.241 fd 26 : Invalid argument As that the connect to a blackhole is correctly rejected by the kernel Fixed behavior: eva# show bgp ipv4 uni summ BGP router identifier 10.10.3.11, local AS number 3235 vrf-id 0 BGP table version 40 RIB entries 78, using 14 KiB of memory Peers 2, using 54 KiB of memory Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt Desc annie(192.168.161.2) 4 64539 126264 39 0 0 0 00:01:36 38 40 N/A 192.168.161.178 4 0 0 0 0 0 0 never Active 0 N/A Total number of neighbors 2 eva# show bgp ipv4 uni summ fail BGP router identifier 10.10.3.11, local AS number 3235 vrf-id 0 BGP table version 40 RIB entries 78, using 14 KiB of memory Peers 2, using 54 KiB of memory Neighbor EstdCnt DropCnt ResetTime Reason 192.168.161.178 0 0 never Waiting for NHT Total number of neighbors 2 eva# show bgp nexthop Current BGP nexthop cache: 192.168.161.2 valid [IGP metric 0], #paths 38, peer 192.168.161.2 if enp39s0 Last update: Thu Feb 11 09:52:05 2021 192.168.161.131 valid [IGP metric 0], #paths 0, peer 192.168.161.131 if enp39s0 Last update: Thu Feb 11 09:52:05 2021 192.168.161.178 invalid, #paths 0, peer 192.168.161.178 Must be Connected Last update: Thu Feb 11 09:53:37 2021 eva# Signed-off-by: Donald Sharp <sharpd@nvidia.com>
Issue: The rpki subcontext uses exit instead of end to exit. This makes issues with frr-reload in the way that frr-reload never exits rpki context until it reaches the next end statement. this also happens when parsing the configuration from vtysh. Fixes: FRRouting#7887 Signed-off-by: Runar Borge <runar@borge.nu>
Recent changes to allow bgpd to handle v6 LL slightly differently in the nexthop tracking code has not interacted well with the blackhole nexthop change for peers. Modify the code to do the right thing Signed-off-by: Donald Sharp <sharpd@nvidia.com>
Dependencies between bgp instances is necessary only when it comes to configure some specific services like ipv4-vpn, ipv6-vpn or l2vpn-evpn. The list of config possibilities is listed, and an error is returned if one of the above services is configured on the bgp vrf instance. There may be some missingn services not covered. For clarification, here are services configured on bgp vrf instances, while trying to delete main bgp instance: - if evpn main instance is the main bgp instance, and if evpn rt5 service is configured (with advertise command) - if a vni is configured in the vrf instance - if l3vpn import/export commands are solicitated for importing/exporting entries from a vpnv4/6 network located on main bgp instance. (in l3vpn, the main bgp instance is the location where vpnv4/6 sits). Signed-off-by: Philippe Guibert <philippe.guibert@6wind.com>
Description: Holdtime and keepalive parameters weren't copied from peer-group to peer-group members. Fixed the issue by copying holdtime and keepalive parameters from peer-group to its members. Problem Description/Summary : Holdtime and keepalive parameters weren't copied from peer-group to peer-group members. Fixed the issue by copying holdtime and keepalive parameters from peer-group to its members. Signed-off-by: sudhanshukumar22 <sudhanshu.kumar@broadcom.com>
Signed-off-by: Igor Ryzhov <iryzhov@nfware.com>
Signed-off-by: Igor Ryzhov <iryzhov@nfware.com>
When the control plane protocol is created, the vrf structure is allocated, and its address is stored in the northbound node. The vrf structure may later be deleted by the user, which will lead to a stale pointer stored in this node. Instead of this, allow daemons that use the vrf pointer to register the dependency between the control plane protocol and vrf nodes. This will guarantee that the nodes will always be created and deleted together, and there won't be any stale pointers. Add such registration to staticd. Signed-off-by: Igor Ryzhov <iryzhov@nfware.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your contribution to FRR!
Click for style suggestions
To apply these suggestions:
curl -s https://gist.githubusercontent.com/polychaeta/9926629745582ed2268b251cac033ecf/raw/f5027bb3810a3b928954f635350ac98adaf0d09b/cr_8156_1614343994.diff | git apply
diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c
index 60aee515b..a5b84dea1 100644
--- a/bgpd/bgp_vty.c
+++ b/bgpd/bgp_vty.c
@@ -1314,24 +1314,42 @@ DEFUN (no_router_bgp,
for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, tmp_bgp)) {
if (tmp_bgp->inst_type != BGP_INSTANCE_TYPE_VRF)
continue;
- if (CHECK_FLAG(tmp_bgp->af_flags[AFI_IP][SAFI_UNICAST],
- BGP_CONFIG_MPLSVPN_TO_VRF_IMPORT) ||
- CHECK_FLAG(tmp_bgp->af_flags[AFI_IP6][SAFI_UNICAST],
- BGP_CONFIG_MPLSVPN_TO_VRF_IMPORT) ||
- CHECK_FLAG(tmp_bgp->af_flags[AFI_IP][SAFI_UNICAST],
- BGP_CONFIG_VRF_TO_MPLSVPN_EXPORT) ||
- CHECK_FLAG(tmp_bgp->af_flags[AFI_IP6][SAFI_UNICAST],
- BGP_CONFIG_VRF_TO_MPLSVPN_EXPORT) ||
- CHECK_FLAG(tmp_bgp->af_flags[AFI_IP][SAFI_UNICAST],
- BGP_CONFIG_VRF_TO_VRF_EXPORT) ||
- CHECK_FLAG(tmp_bgp->af_flags[AFI_IP6][SAFI_UNICAST],
- BGP_CONFIG_VRF_TO_VRF_EXPORT) ||
- (bgp == bgp_get_evpn() &&
- (CHECK_FLAG(tmp_bgp->af_flags[AFI_L2VPN][SAFI_EVPN],
- BGP_L2VPN_EVPN_ADVERTISE_IPV4_UNICAST) ||
- CHECK_FLAG(tmp_bgp->af_flags[AFI_L2VPN][SAFI_EVPN],
- BGP_L2VPN_EVPN_ADVERTISE_IPV6_UNICAST))) ||
- (tmp_bgp->vnihash && hashcount(tmp_bgp->vnihash))) {
+ if (CHECK_FLAG(tmp_bgp->af_flags[AFI_IP]
+ [SAFI_UNICAST],
+ BGP_CONFIG_MPLSVPN_TO_VRF_IMPORT)
+ || CHECK_FLAG(
+ tmp_bgp->af_flags[AFI_IP6]
+ [SAFI_UNICAST],
+ BGP_CONFIG_MPLSVPN_TO_VRF_IMPORT)
+ || CHECK_FLAG(
+ tmp_bgp->af_flags[AFI_IP]
+ [SAFI_UNICAST],
+ BGP_CONFIG_VRF_TO_MPLSVPN_EXPORT)
+ || CHECK_FLAG(
+ tmp_bgp->af_flags[AFI_IP6]
+ [SAFI_UNICAST],
+ BGP_CONFIG_VRF_TO_MPLSVPN_EXPORT)
+ || CHECK_FLAG(
+ tmp_bgp->af_flags[AFI_IP]
+ [SAFI_UNICAST],
+ BGP_CONFIG_VRF_TO_VRF_EXPORT)
+ || CHECK_FLAG(
+ tmp_bgp->af_flags[AFI_IP6]
+ [SAFI_UNICAST],
+ BGP_CONFIG_VRF_TO_VRF_EXPORT)
+ || (bgp == bgp_get_evpn()
+ && (CHECK_FLAG(
+ tmp_bgp->af_flags
+ [AFI_L2VPN]
+ [SAFI_EVPN],
+ BGP_L2VPN_EVPN_ADVERTISE_IPV4_UNICAST)
+ || CHECK_FLAG(
+ tmp_bgp->af_flags
+ [AFI_L2VPN]
+ [SAFI_EVPN],
+ BGP_L2VPN_EVPN_ADVERTISE_IPV6_UNICAST)))
+ || (tmp_bgp->vnihash
+ && hashcount(tmp_bgp->vnihash))) {
vty_out(vty,
"%% Cannot delete default BGP instance. Dependent VRF instances exist\n");
return CMD_WARNING_CONFIG_FAILED;
diff --git a/tools/frr-reload.py b/tools/frr-reload.py
index 88a40d89e..1b6e956e6 100755
--- a/tools/frr-reload.py
+++ b/tools/frr-reload.py
@@ -39,6 +39,7 @@ import string
import subprocess
import sys
from collections import OrderedDict
+
try:
from ipaddress import IPv6Address, ip_network
except ImportError:
@@ -51,45 +52,49 @@ except AttributeError:
# Python 3
def iteritems(d):
return iter(d.items())
+
+
else:
# Python 2
def iteritems(d):
return d.iteritems()
+
log = logging.getLogger(__name__)
class VtyshException(Exception):
pass
+
class Vtysh(object):
def __init__(self, bindir=None, confdir=None, sockdir=None, pathspace=None):
self.bindir = bindir
self.confdir = confdir
self.pathspace = pathspace
- self.common_args = [os.path.join(bindir or '', 'vtysh')]
+ self.common_args = [os.path.join(bindir or "", "vtysh")]
if confdir:
- self.common_args.extend(['--config_dir', confdir])
+ self.common_args.extend(["--config_dir", confdir])
if sockdir:
- self.common_args.extend(['--vty_socket', sockdir])
+ self.common_args.extend(["--vty_socket", sockdir])
if pathspace:
- self.common_args.extend(['-N', pathspace])
+ self.common_args.extend(["-N", pathspace])
def _call(self, args, stdin=None, stdout=None, stderr=None):
kwargs = {}
if stdin is not None:
- kwargs['stdin'] = stdin
+ kwargs["stdin"] = stdin
if stdout is not None:
- kwargs['stdout'] = stdout
+ kwargs["stdout"] = stdout
if stderr is not None:
- kwargs['stderr'] = stderr
+ kwargs["stderr"] = stderr
return subprocess.Popen(self.common_args + args, **kwargs)
def _call_cmd(self, command, stdin=None, stdout=None, stderr=None):
if isinstance(command, list):
- args = [item for sub in command for item in ['-c', sub]]
+ args = [item for sub in command for item in ["-c", sub]]
else:
- args = ['-c', command]
+ args = ["-c", command]
return self._call(args, stdin, stdout, stderr)
def __call__(self, command):
@@ -102,9 +107,10 @@ class Vtysh(object):
proc = self._call_cmd(command, stdout=subprocess.PIPE)
stdout, stderr = proc.communicate()
if proc.wait() != 0:
- raise VtyshException('vtysh returned status %d for command "%s"'
- % (proc.returncode, command))
- return stdout.decode('UTF-8')
+ raise VtyshException(
+ 'vtysh returned status %d for command "%s"' % (proc.returncode, command)
+ )
+ return stdout.decode("UTF-8")
def is_config_available(self):
"""
@@ -113,71 +119,84 @@ class Vtysh(object):
configuration changes.
"""
- output = self('configure')
+ output = self("configure")
- if 'VTY configuration is locked by other VTY' in output:
+ if "VTY configuration is locked by other VTY" in output:
log.error("vtysh 'configure' returned\n%s\n" % (output))
return False
return True
def exec_file(self, filename):
- child = self._call(['-f', filename])
+ child = self._call(["-f", filename])
if child.wait() != 0:
- raise VtyshException('vtysh (exec file) exited with status %d'
- % (child.returncode))
+ raise VtyshException(
+ "vtysh (exec file) exited with status %d" % (child.returncode)
+ )
def mark_file(self, filename, stdin=None):
- child = self._call(['-m', '-f', filename],
- stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
+ child = self._call(
+ ["-m", "-f", filename],
+ stdout=subprocess.PIPE,
+ stdin=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
try:
stdout, stderr = child.communicate()
except subprocess.TimeoutExpired:
child.kill()
stdout, stderr = child.communicate()
- raise VtyshException('vtysh call timed out!')
+ raise VtyshException("vtysh call timed out!")
if child.wait() != 0:
- raise VtyshException('vtysh (mark file) exited with status %d:\n%s'
- % (child.returncode, stderr))
+ raise VtyshException(
+ "vtysh (mark file) exited with status %d:\n%s"
+ % (child.returncode, stderr)
+ )
- return stdout.decode('UTF-8')
+ return stdout.decode("UTF-8")
- def mark_show_run(self, daemon = None):
- cmd = 'show running-config'
+ def mark_show_run(self, daemon=None):
+ cmd = "show running-config"
if daemon:
- cmd += ' %s' % daemon
- cmd += ' no-header'
+ cmd += " %s" % daemon
+ cmd += " no-header"
show_run = self._call_cmd(cmd, stdout=subprocess.PIPE)
- mark = self._call(['-m', '-f', '-'], stdin=show_run.stdout, stdout=subprocess.PIPE)
+ mark = self._call(
+ ["-m", "-f", "-"], stdin=show_run.stdout, stdout=subprocess.PIPE
+ )
show_run.wait()
stdout, stderr = mark.communicate()
mark.wait()
if show_run.returncode != 0:
- raise VtyshException('vtysh (show running-config) exited with status %d:'
- % (show_run.returncode))
+ raise VtyshException(
+ "vtysh (show running-config) exited with status %d:"
+ % (show_run.returncode)
+ )
if mark.returncode != 0:
- raise VtyshException('vtysh (mark running-config) exited with status %d'
- % (mark.returncode))
+ raise VtyshException(
+ "vtysh (mark running-config) exited with status %d" % (mark.returncode)
+ )
+
+ return stdout.decode("UTF-8")
- return stdout.decode('UTF-8')
class Context(object):
"""
- A Context object represents a section of frr configuration such as:
-!
-interface swp3
- description swp3 -> r8's swp1
- ipv6 nd suppress-ra
- link-detect
-!
+ A Context object represents a section of frr configuration such as:
+ !
+ interface swp3
+ description swp3 -> r8's swp1
+ ipv6 nd suppress-ra
+ link-detect
+ !
-or a single line context object such as this:
+ or a single line context object such as this:
-ip forwarding
+ ip forwarding
"""
@@ -202,6 +221,7 @@ ip forwarding
for ligne in lines:
self.dlines[ligne] = True
+
def get_normalized_es_id(line):
"""
The es-id or es-sys-mac need to be converted to lower case
@@ -214,6 +234,7 @@ def get_normalized_es_id(line):
break
return line
+
def get_normalized_mac_ip_line(line):
if line.startswith("evpn mh es"):
return get_normalized_es_id(line)
@@ -223,6 +244,7 @@ def get_normalized_mac_ip_line(line):
return line
+
class Config(object):
"""
@@ -242,15 +264,15 @@ class Config(object):
The internal representation has been marked appropriately by passing it
through vtysh with the -m parameter
"""
- log.info('Loading Config object from file %s', filename)
+ log.info("Loading Config object from file %s", filename)
file_output = self.vtysh.mark_file(filename)
- for line in file_output.split('\n'):
+ for line in file_output.split("\n"):
line = line.strip()
# Compress duplicate whitespaces
- line = ' '.join(line.split())
+ line = " ".join(line.split())
if ":" in line:
line = get_normalized_mac_ip_line(line)
@@ -265,16 +287,18 @@ class Config(object):
The internal representation has been marked appropriately by passing it
through vtysh with the -m parameter
"""
- log.info('Loading Config object from vtysh show running')
+ log.info("Loading Config object from vtysh show running")
config_text = self.vtysh.mark_show_run(daemon)
- for line in config_text.split('\n'):
+ for line in config_text.split("\n"):
line = line.strip()
- if (line == 'Building configuration...' or
- line == 'Current configuration:' or
- not line):
+ if (
+ line == "Building configuration..."
+ or line == "Current configuration:"
+ or not line
+ ):
continue
self.lines.append(line)
@@ -286,7 +310,7 @@ class Config(object):
Return the lines read in from the configuration
"""
- return '\n'.join(self.lines)
+ return "\n".join(self.lines)
def get_contexts(self):
"""
@@ -294,7 +318,7 @@ class Config(object):
"""
for (_, ctx) in sorted(iteritems(self.contexts)):
- print(str(ctx) + '\n')
+ print(str(ctx) + "\n")
def save_contexts(self, key, lines):
"""
@@ -304,99 +328,116 @@ class Config(object):
if not key:
return
- '''
+ """
IP addresses specified in "network" statements, "ip prefix-lists"
etc. can differ in the host part of the specification the user
provides and what the running config displays. For example, user
can specify 11.1.1.1/24, and the running config displays this as
11.1.1.0/24. Ensure we don't do a needless operation for such
lines. IS-IS & OSPFv3 have no "network" support.
- '''
- re_key_rt = re.match(r'(ip|ipv6)\s+route\s+([A-Fa-f:.0-9/]+)(.*)$', key[0])
+ """
+ re_key_rt = re.match(r"(ip|ipv6)\s+route\s+([A-Fa-f:.0-9/]+)(.*)$", key[0])
if re_key_rt:
addr = re_key_rt.group(2)
- if '/' in addr:
+ if "/" in addr:
try:
- if 'ipaddress' not in sys.modules:
+ if "ipaddress" not in sys.modules:
newaddr = IPNetwork(addr)
- key[0] = '%s route %s/%s%s' % (re_key_rt.group(1),
- newaddr.network,
- newaddr.prefixlen,
- re_key_rt.group(3))
+ key[0] = "%s route %s/%s%s" % (
+ re_key_rt.group(1),
+ newaddr.network,
+ newaddr.prefixlen,
+ re_key_rt.group(3),
+ )
else:
newaddr = ip_network(addr, strict=False)
- key[0] = '%s route %s/%s%s' % (re_key_rt.group(1),
- str(newaddr.network_address),
- newaddr.prefixlen,
- re_key_rt.group(3))
+ key[0] = "%s route %s/%s%s" % (
+ re_key_rt.group(1),
+ str(newaddr.network_address),
+ newaddr.prefixlen,
+ re_key_rt.group(3),
+ )
except ValueError:
pass
re_key_rt = re.match(
- r'(ip|ipv6)\s+prefix-list(.*)(permit|deny)\s+([A-Fa-f:.0-9/]+)(.*)$',
- key[0]
+ r"(ip|ipv6)\s+prefix-list(.*)(permit|deny)\s+([A-Fa-f:.0-9/]+)(.*)$", key[0]
)
if re_key_rt:
addr = re_key_rt.group(4)
- if '/' in addr:
+ if "/" in addr:
try:
- if 'ipaddress' not in sys.modules:
- newaddr = '%s/%s' % (IPNetwork(addr).network,
- IPNetwork(addr).prefixlen)
+ if "ipaddress" not in sys.modules:
+ newaddr = "%s/%s" % (
+ IPNetwork(addr).network,
+ IPNetwork(addr).prefixlen,
+ )
else:
network_addr = ip_network(addr, strict=False)
- newaddr = '%s/%s' % (str(network_addr.network_address),
- network_addr.prefixlen)
+ newaddr = "%s/%s" % (
+ str(network_addr.network_address),
+ network_addr.prefixlen,
+ )
except ValueError:
newaddr = addr
else:
newaddr = addr
legestr = re_key_rt.group(5)
- re_lege = re.search(r'(.*)le\s+(\d+)\s+ge\s+(\d+)(.*)', legestr)
+ re_lege = re.search(r"(.*)le\s+(\d+)\s+ge\s+(\d+)(.*)", legestr)
if re_lege:
- legestr = '%sge %s le %s%s' % (re_lege.group(1),
- re_lege.group(3),
- re_lege.group(2),
- re_lege.group(4))
- re_lege = re.search(r'(.*)ge\s+(\d+)\s+le\s+(\d+)(.*)', legestr)
-
- if (re_lege and ((re_key_rt.group(1) == "ip" and
- re_lege.group(3) == "32") or
- (re_key_rt.group(1) == "ipv6" and
- re_lege.group(3) == "128"))):
- legestr = '%sge %s%s' % (re_lege.group(1),
- re_lege.group(2),
- re_lege.group(4))
-
- key[0] = '%s prefix-list%s%s %s%s' % (re_key_rt.group(1),
- re_key_rt.group(2),
- re_key_rt.group(3),
- newaddr,
- legestr)
-
- if lines and key[0].startswith('router bgp'):
+ legestr = "%sge %s le %s%s" % (
+ re_lege.group(1),
+ re_lege.group(3),
+ re_lege.group(2),
+ re_lege.group(4),
+ )
+ re_lege = re.search(r"(.*)ge\s+(\d+)\s+le\s+(\d+)(.*)", legestr)
+
+ if re_lege and (
+ (re_key_rt.group(1) == "ip" and re_lege.group(3) == "32")
+ or (re_key_rt.group(1) == "ipv6" and re_lege.group(3) == "128")
+ ):
+ legestr = "%sge %s%s" % (
+ re_lege.group(1),
+ re_lege.group(2),
+ re_lege.group(4),
+ )
+
+ key[0] = "%s prefix-list%s%s %s%s" % (
+ re_key_rt.group(1),
+ re_key_rt.group(2),
+ re_key_rt.group(3),
+ newaddr,
+ legestr,
+ )
+
+ if lines and key[0].startswith("router bgp"):
newlines = []
for line in lines:
- re_net = re.match(r'network\s+([A-Fa-f:.0-9/]+)(.*)$', line)
+ re_net = re.match(r"network\s+([A-Fa-f:.0-9/]+)(.*)$", line)
if re_net:
addr = re_net.group(1)
- if '/' not in addr and key[0].startswith('router bgp'):
+ if "/" not in addr and key[0].startswith("router bgp"):
# This is most likely an error because with no
# prefixlen, BGP treats the prefixlen as 8
- addr = addr + '/8'
+ addr = addr + "/8"
try:
- if 'ipaddress' not in sys.modules:
+ if "ipaddress" not in sys.modules:
newaddr = IPNetwork(addr)
- line = 'network %s/%s %s' % (newaddr.network,
- newaddr.prefixlen,
- re_net.group(2))
+ line = "network %s/%s %s" % (
+ newaddr.network,
+ newaddr.prefixlen,
+ re_net.group(2),
+ )
else:
network_addr = ip_network(addr, strict=False)
- line = 'network %s/%s %s' % (str(network_addr.network_address),
- network_addr.prefixlen,
- re_net.group(2))
+ line = "network %s/%s %s" % (
+ str(network_addr.network_address),
+ network_addr.prefixlen,
+ re_net.group(2),
+ )
newlines.append(line)
except ValueError:
# Really this should be an error. Whats a network
@@ -406,13 +447,16 @@ class Config(object):
newlines.append(line)
lines = newlines
- '''
+ """
More fixups in user specification and what running config shows.
"null0" in routes must be replaced by Null0.
- '''
- if (key[0].startswith('ip route') or key[0].startswith('ipv6 route') and
- 'null0' in key[0]):
- key[0] = re.sub(r'\s+null0(\s*$)', ' Null0', key[0])
+ """
+ if (
+ key[0].startswith("ip route")
+ or key[0].startswith("ipv6 route")
+ and "null0" in key[0]
+ ):
+ key[0] = re.sub(r"\s+null0(\s*$)", " Null0", key[0])
if lines:
if tuple(key) not in self.contexts:
@@ -435,7 +479,7 @@ class Config(object):
current_context_lines = []
ctx_keys = []
- '''
+ """
The end of a context is flagged via the 'end' keyword:
!
@@ -479,7 +523,7 @@ router ospf
timers throttle spf 0 50 5000
!
end
- '''
+ """
# The code assumes that its working on the output from the "vtysh -m"
# command. That provides the appropriate markers to signify end of
@@ -499,45 +543,49 @@ end
# the keywords that we know are single line contexts. bgp in this case
# is not the main router bgp block, but enabling multi-instance
- oneline_ctx_keywords = ("access-list ",
- "agentx",
- "allow-external-route-update",
- "bgp ",
- "debug ",
- "domainname ",
- "dump ",
- "enable ",
- "frr ",
- "hostname ",
- "ip ",
- "ipv6 ",
- "log ",
- "mpls lsp",
- "mpls label",
- "no ",
- "password ",
- "ptm-enable",
- "router-id ",
- "service ",
- "table ",
- "username ",
- "zebra ",
- "vrrp autoconfigure",
- "evpn mh")
+ oneline_ctx_keywords = (
+ "access-list ",
+ "agentx",
+ "allow-external-route-update",
+ "bgp ",
+ "debug ",
+ "domainname ",
+ "dump ",
+ "enable ",
+ "frr ",
+ "hostname ",
+ "ip ",
+ "ipv6 ",
+ "log ",
+ "mpls lsp",
+ "mpls label",
+ "no ",
+ "password ",
+ "ptm-enable",
+ "router-id ",
+ "service ",
+ "table ",
+ "username ",
+ "zebra ",
+ "vrrp autoconfigure",
+ "evpn mh",
+ )
for line in self.lines:
if not line:
continue
- if line.startswith('!') or line.startswith('#'):
+ if line.startswith("!") or line.startswith("#"):
continue
- if (len(ctx_keys) == 2
- and ctx_keys[0].startswith('bfd')
- and ctx_keys[1].startswith('profile ')
- and line == 'end'):
- log.debug('LINE %-50s: popping from sub context, %-50s', line, ctx_keys)
+ if (
+ len(ctx_keys) == 2
+ and ctx_keys[0].startswith("bfd")
+ and ctx_keys[1].startswith("profile ")
+ and line == "end"
+ ):
+ log.debug("LINE %-50s: popping from sub context, %-50s", line, ctx_keys)
if main_ctx_key:
self.save_contexts(ctx_keys, current_context_lines)
@@ -550,22 +598,31 @@ end
# as part of its 'mpls ldp' config context. If we are processing
# ldp configuration and encounter a router-id we should NOT switch
# to a new context
- if new_ctx is True and any(line.startswith(keyword) for keyword in oneline_ctx_keywords) and not (
- ctx_keys and ctx_keys[0].startswith("mpls ldp") and line.startswith("router-id ")):
+ if (
+ new_ctx is True
+ and any(line.startswith(keyword) for keyword in oneline_ctx_keywords)
+ and not (
+ ctx_keys
+ and ctx_keys[0].startswith("mpls ldp")
+ and line.startswith("router-id ")
+ )
+ ):
self.save_contexts(ctx_keys, current_context_lines)
# Start a new context
main_ctx_key = []
- ctx_keys = [line, ]
+ ctx_keys = [
+ line,
+ ]
current_context_lines = []
- log.debug('LINE %-50s: entering new context, %-50s', line, ctx_keys)
+ log.debug("LINE %-50s: entering new context, %-50s", line, ctx_keys)
self.save_contexts(ctx_keys, current_context_lines)
new_ctx = True
elif line == "end":
self.save_contexts(ctx_keys, current_context_lines)
- log.debug('LINE %-50s: exiting old context, %-50s', line, ctx_keys)
+ log.debug("LINE %-50s: exiting old context, %-50s", line, ctx_keys)
# Start a new context
new_ctx = True
@@ -586,9 +643,11 @@ end
elif line == "exit-vrf":
self.save_contexts(ctx_keys, current_context_lines)
current_context_lines.append(line)
- log.debug('LINE %-50s: append to current_context_lines, %-50s', line, ctx_keys)
+ log.debug(
+ "LINE %-50s: append to current_context_lines, %-50s", line, ctx_keys
+ )
- #Start a new context
+ # Start a new context
new_ctx = True
main_ctx_key = []
ctx_keys = []
@@ -602,7 +661,11 @@ end
# Start a new context
ctx_keys = copy.deepcopy(main_ctx_key)
current_context_lines = []
- log.debug('LINE %-50s: popping from subcontext to ctx%-50s', line, ctx_keys)
+ log.debug(
+ "LINE %-50s: popping from subcontext to ctx%-50s",
+ line,
+ ctx_keys,
+ )
elif line in ["exit-vni", "exit-ldp-if"]:
if sub_main_ctx_key:
@@ -611,70 +674,90 @@ end
# Start a new context
ctx_keys = copy.deepcopy(sub_main_ctx_key)
current_context_lines = []
- log.debug('LINE %-50s: popping from sub-subcontext to ctx%-50s', line, ctx_keys)
+ log.debug(
+ "LINE %-50s: popping from sub-subcontext to ctx%-50s",
+ line,
+ ctx_keys,
+ )
elif new_ctx is True:
if not main_ctx_key:
- ctx_keys = [line, ]
+ ctx_keys = [
+ line,
+ ]
else:
ctx_keys = copy.deepcopy(main_ctx_key)
main_ctx_key = []
current_context_lines = []
new_ctx = False
- log.debug('LINE %-50s: entering new context, %-50s', line, ctx_keys)
- elif (line.startswith("address-family ") or
- line.startswith("vnc defaults") or
- line.startswith("vnc l2-group") or
- line.startswith("vnc nve-group") or
- line.startswith("peer") or
- line.startswith("key ") or
- line.startswith("member pseudowire")):
+ log.debug("LINE %-50s: entering new context, %-50s", line, ctx_keys)
+ elif (
+ line.startswith("address-family ")
+ or line.startswith("vnc defaults")
+ or line.startswith("vnc l2-group")
+ or line.startswith("vnc nve-group")
+ or line.startswith("peer")
+ or line.startswith("key ")
+ or line.startswith("member pseudowire")
+ ):
main_ctx_key = []
# Save old context first
self.save_contexts(ctx_keys, current_context_lines)
current_context_lines = []
main_ctx_key = copy.deepcopy(ctx_keys)
- log.debug('LINE %-50s: entering sub-context, append to ctx_keys', line)
+ log.debug("LINE %-50s: entering sub-context, append to ctx_keys", line)
- if line == "address-family ipv6" and not ctx_keys[0].startswith("mpls ldp"):
+ if line == "address-family ipv6" and not ctx_keys[0].startswith(
+ "mpls ldp"
+ ):
ctx_keys.append("address-family ipv6 unicast")
- elif line == "address-family ipv4" and not ctx_keys[0].startswith("mpls ldp"):
+ elif line == "address-family ipv4" and not ctx_keys[0].startswith(
+ "mpls ldp"
+ ):
ctx_keys.append("address-family ipv4 unicast")
elif line == "address-family evpn":
ctx_keys.append("address-family l2vpn evpn")
else:
ctx_keys.append(line)
- elif ((line.startswith("vni ") and
- len(ctx_keys) == 2 and
- ctx_keys[0].startswith('router bgp') and
- ctx_keys[1] == 'address-family l2vpn evpn')):
+ elif (
+ line.startswith("vni ")
+ and len(ctx_keys) == 2
+ and ctx_keys[0].startswith("router bgp")
+ and ctx_keys[1] == "address-family l2vpn evpn"
+ ):
# Save old context first
self.save_contexts(ctx_keys, current_context_lines)
current_context_lines = []
sub_main_ctx_key = copy.deepcopy(ctx_keys)
- log.debug('LINE %-50s: entering sub-sub-context, append to ctx_keys', line)
+ log.debug(
+ "LINE %-50s: entering sub-sub-context, append to ctx_keys", line
+ )
ctx_keys.append(line)
-
- elif ((line.startswith("interface ") and
- len(ctx_keys) == 2 and
- ctx_keys[0].startswith('mpls ldp') and
- ctx_keys[1].startswith('address-family'))):
+
+ elif (
+ line.startswith("interface ")
+ and len(ctx_keys) == 2
+ and ctx_keys[0].startswith("mpls ldp")
+ and ctx_keys[1].startswith("address-family")
+ ):
# Save old context first
self.save_contexts(ctx_keys, current_context_lines)
current_context_lines = []
sub_main_ctx_key = copy.deepcopy(ctx_keys)
- log.debug('LINE %-50s: entering sub-sub-context, append to ctx_keys', line)
+ log.debug(
+ "LINE %-50s: entering sub-sub-context, append to ctx_keys", line
+ )
ctx_keys.append(line)
elif (
- line.startswith('profile ')
+ line.startswith("profile ")
and len(ctx_keys) == 1
- and ctx_keys[0].startswith('bfd')
+ and ctx_keys[0].startswith("bfd")
):
# Save old context first
@@ -683,14 +766,16 @@ end
main_ctx_key = copy.deepcopy(ctx_keys)
log.debug(
"LINE %-50s: entering BFD profile sub-context, append to ctx_keys",
- line
+ line,
)
ctx_keys.append(line)
else:
# Continuing in an existing context, add non-commented lines to it
current_context_lines.append(line)
- log.debug('LINE %-50s: append to current_context_lines, %-50s', line, ctx_keys)
+ log.debug(
+ "LINE %-50s: append to current_context_lines, %-50s", line, ctx_keys
+ )
# Save the context of the last one
self.save_contexts(ctx_keys, current_context_lines)
@@ -704,20 +789,20 @@ def lines_to_config(ctx_keys, line, delete):
if line:
for (i, ctx_key) in enumerate(ctx_keys):
- cmd.append(' ' * i + ctx_key)
+ cmd.append(" " * i + ctx_key)
line = line.lstrip()
- indent = len(ctx_keys) * ' '
+ indent = len(ctx_keys) * " "
# There are some commands that are on by default so their "no" form will be
# displayed in the config. "no bgp default ipv4-unicast" is one of these.
# If we need to remove this line we do so by adding "bgp default ipv4-unicast",
# not by doing a "no no bgp default ipv4-unicast"
if delete:
- if line.startswith('no '):
- cmd.append('%s%s' % (indent, line[3:]))
+ if line.startswith("no "):
+ cmd.append("%s%s" % (indent, line[3:]))
else:
- cmd.append('%sno %s' % (indent, line))
+ cmd.append("%sno %s" % (indent, line))
else:
cmd.append(indent + line)
@@ -726,16 +811,16 @@ def lines_to_config(ctx_keys, line, delete):
# context ('no router ospf' for example)
else:
for i, ctx_key in enumerate(ctx_keys[:-1]):
- cmd.append('%s%s' % (' ' * i, ctx_key))
+ cmd.append("%s%s" % (" " * i, ctx_key))
# Only put the 'no' on the last sub-context
if delete:
- if ctx_keys[-1].startswith('no '):
- cmd.append('%s%s' % (' ' * (len(ctx_keys) - 1), ctx_keys[-1][3:]))
+ if ctx_keys[-1].startswith("no "):
+ cmd.append("%s%s" % (" " * (len(ctx_keys) - 1), ctx_keys[-1][3:]))
else:
- cmd.append('%sno %s' % (' ' * (len(ctx_keys) - 1), ctx_keys[-1]))
+ cmd.append("%sno %s" % (" " * (len(ctx_keys) - 1), ctx_keys[-1]))
else:
- cmd.append('%s%s' % (' ' * (len(ctx_keys) - 1), ctx_keys[-1]))
+ cmd.append("%s%s" % (" " * (len(ctx_keys) - 1), ctx_keys[-1]))
return cmd
@@ -748,23 +833,26 @@ def get_normalized_ipv6_line(line):
the IPv6 word is a network
"""
norm_line = ""
- words = line.split(' ')
+ words = line.split(" ")
for word in words:
if ":" in word:
norm_word = None
if "/" in word:
try:
- if 'ipaddress' not in sys.modules:
+ if "ipaddress" not in sys.modules:
v6word = IPNetwork(word)
- norm_word = '%s/%s' % (v6word.network, v6word.prefixlen)
+ norm_word = "%s/%s" % (v6word.network, v6word.prefixlen)
else:
v6word = ip_network(word, strict=False)
- norm_word = '%s/%s' % (str(v6word.network_address), v6word.prefixlen)
+ norm_word = "%s/%s" % (
+ str(v6word.network_address),
+ v6word.prefixlen,
+ )
except ValueError:
pass
if not norm_word:
try:
- norm_word = '%s' % IPv6Address(word)
+ norm_word = "%s" % IPv6Address(word)
except ValueError:
norm_word = word
else:
@@ -785,6 +873,7 @@ def line_exist(lines, target_ctx_keys, target_line, exact_match=True):
return True
return False
+
def check_for_exit_vrf(lines_to_add, lines_to_del):
# exit-vrf is a bit tricky. If the new config is missing it but we
@@ -797,25 +886,26 @@ def check_for_exit_vrf(lines_to_add, lines_to_del):
for (ctx_keys, line) in lines_to_add:
if add_exit_vrf == True:
if ctx_keys[0] != prior_ctx_key:
- insert_key=(prior_ctx_key),
+ insert_key = ((prior_ctx_key),)
lines_to_add.insert(index, ((insert_key, "exit-vrf")))
add_exit_vrf = False
- if ctx_keys[0].startswith('vrf') and line:
+ if ctx_keys[0].startswith("vrf") and line:
if line is not "exit-vrf":
add_exit_vrf = True
- prior_ctx_key = (ctx_keys[0])
+ prior_ctx_key = ctx_keys[0]
else:
add_exit_vrf = False
- index+=1
+ index += 1
for (ctx_keys, line) in lines_to_del:
if line == "exit-vrf":
- if (line_exist(lines_to_add, ctx_keys, line)):
+ if line_exist(lines_to_add, ctx_keys, line):
lines_to_del.remove((ctx_keys, line))
return (lines_to_add, lines_to_del)
+
def ignore_delete_re_add_lines(lines_to_add, lines_to_del):
# Quite possibly the most confusing (while accurate) variable names in history
@@ -825,10 +915,10 @@ def ignore_delete_re_add_lines(lines_to_add, lines_to_del):
for (ctx_keys, line) in lines_to_del:
deleted = False
- if ctx_keys[0].startswith('router bgp') and line:
+ if ctx_keys[0].startswith("router bgp") and line:
- if line.startswith('neighbor '):
- '''
+ if line.startswith("neighbor "):
+ """
BGP changed how it displays swpX peers that are part of peer-group. Older
versions of frr would display these on separate lines:
neighbor swp1 interface
@@ -845,10 +935,14 @@ def ignore_delete_re_add_lines(lines_to_add, lines_to_del):
neighbor swp1 peer-group FOO
If so then chop the del line and the corresponding add lines
- '''
+ """
- re_swpx_int_peergroup = re.search('neighbor (\S+) interface peer-group (\S+)', line)
- re_swpx_int_v6only_peergroup = re.search('neighbor (\S+) interface v6only peer-group (\S+)', line)
+ re_swpx_int_peergroup = re.search(
+ "neighbor (\S+) interface peer-group (\S+)", line
+ )
+ re_swpx_int_v6only_peergroup = re.search(
+ "neighbor (\S+) interface v6only peer-group (\S+)", line
+ )
if re_swpx_int_peergroup or re_swpx_int_v6only_peergroup:
swpx_interface = None
@@ -864,21 +958,29 @@ def ignore_delete_re_add_lines(lines_to_add, lines_to_del):
swpx_interface = "neighbor %s interface v6only" % swpx
swpx_peergroup = "neighbor %s peer-group %s" % (swpx, peergroup)
- found_add_swpx_interface = line_exist(lines_to_add, ctx_keys, swpx_interface)
- found_add_swpx_peergroup = line_exist(lines_to_add, ctx_keys, swpx_peergroup)
+ found_add_swpx_interface = line_exist(
+ lines_to_add, ctx_keys, swpx_interface
+ )
+ found_add_swpx_peergroup = line_exist(
+ lines_to_add, ctx_keys, swpx_peergroup
+ )
tmp_ctx_keys = tuple(list(ctx_keys))
if not found_add_swpx_peergroup:
tmp_ctx_keys = list(ctx_keys)
- tmp_ctx_keys.append('address-family ipv4 unicast')
+ tmp_ctx_keys.append("address-family ipv4 unicast")
tmp_ctx_keys = tuple(tmp_ctx_keys)
- found_add_swpx_peergroup = line_exist(lines_to_add, tmp_ctx_keys, swpx_peergroup)
+ found_add_swpx_peergroup = line_exist(
+ lines_to_add, tmp_ctx_keys, swpx_peergroup
+ )
if not found_add_swpx_peergroup:
tmp_ctx_keys = list(ctx_keys)
- tmp_ctx_keys.append('address-family ipv6 unicast')
+ tmp_ctx_keys.append("address-family ipv6 unicast")
tmp_ctx_keys = tuple(tmp_ctx_keys)
- found_add_swpx_peergroup = line_exist(lines_to_add, tmp_ctx_keys, swpx_peergroup)
+ found_add_swpx_peergroup = line_exist(
+ lines_to_add, tmp_ctx_keys, swpx_peergroup
+ )
if found_add_swpx_interface and found_add_swpx_peergroup:
deleted = True
@@ -886,30 +988,36 @@ def ignore_delete_re_add_lines(lines_to_add, lines_to_del):
lines_to_add_to_del.append((ctx_keys, swpx_interface))
lines_to_add_to_del.append((tmp_ctx_keys, swpx_peergroup))
- '''
+ """
Changing the bfd timers on neighbors is allowed without doing
a delete/add process. Since doing a "no neighbor blah bfd ..."
will cause the peer to bounce unnecessarily, just skip the delete
and just do the add.
- '''
- re_nbr_bfd_timers = re.search(r'neighbor (\S+) bfd (\S+) (\S+) (\S+)', line)
+ """
+ re_nbr_bfd_timers = re.search(
+ r"neighbor (\S+) bfd (\S+) (\S+) (\S+)", line
+ )
if re_nbr_bfd_timers:
nbr = re_nbr_bfd_timers.group(1)
bfd_nbr = "neighbor %s" % nbr
- bfd_search_string = bfd_nbr + r' bfd (\S+) (\S+) (\S+)'
+ bfd_search_string = bfd_nbr + r" bfd (\S+) (\S+) (\S+)"
for (ctx_keys, add_line) in lines_to_add:
- if ctx_keys[0].startswith('router bgp'):
- re_add_nbr_bfd_timers = re.search(bfd_search_string, add_line)
+ if ctx_keys[0].startswith("router bgp"):
+ re_add_nbr_bfd_timers = re.search(
+ bfd_search_string, add_line
+ )
if re_add_nbr_bfd_timers:
- found_add_bfd_nbr = line_exist(lines_to_add, ctx_keys, bfd_nbr, False)
+ found_add_bfd_nbr = line_exist(
+ lines_to_add, ctx_keys, bfd_nbr, False
+ )
if found_add_bfd_nbr:
lines_to_del_to_del.append((ctx_keys, line))
- '''
+ """
We changed how we display the neighbor interface command. Older
versions of frr would display the following:
neighbor swp1 interface
@@ -931,9 +1039,13 @@ def ignore_delete_re_add_lines(lines_to_add, lines_to_del):
neighbor swp1 capability extended-nexthop
If so then chop the del line and the corresponding add lines
- '''
- re_swpx_int_remoteas = re.search('neighbor (\S+) interface remote-as (\S+)', line)
- re_swpx_int_v6only_remoteas = re.search('neighbor (\S+) interface v6only remote-as (\S+)', line)
+ """
+ re_swpx_int_remoteas = re.search(
+ "neighbor (\S+) interface remote-as (\S+)", line
+ )
+ re_swpx_int_v6only_remoteas = re.search(
+ "neighbor (\S+) interface v6only remote-as (\S+)", line
+ )
if re_swpx_int_remoteas or re_swpx_int_v6only_remoteas:
swpx_interface = None
@@ -949,8 +1061,12 @@ def ignore_delete_re_add_lines(lines_to_add, lines_to_del):
swpx_interface = "neighbor %s interface v6only" % swpx
swpx_remoteas = "neighbor %s remote-as %s" % (swpx, remoteas)
- found_add_swpx_interface = line_exist(lines_to_add, ctx_keys, swpx_interface)
- found_add_swpx_remoteas = line_exist(lines_to_add, ctx_keys, swpx_remoteas)
+ found_add_swpx_interface = line_exist(
+ lines_to_add, ctx_keys, swpx_interface
+ )
+ found_add_swpx_remoteas = line_exist(
+ lines_to_add, ctx_keys, swpx_remoteas
+ )
tmp_ctx_keys = tuple(list(ctx_keys))
if found_add_swpx_interface and found_add_swpx_remoteas:
@@ -959,7 +1075,7 @@ def ignore_delete_re_add_lines(lines_to_add, lines_to_del):
lines_to_add_to_del.append((ctx_keys, swpx_interface))
lines_to_add_to_del.append((tmp_ctx_keys, swpx_remoteas))
- '''
+ """
We made the 'bgp bestpath as-path multipath-relax' command
automatically assume 'no-as-set' since the lack of this option caused
weird routing problems. When the running config is shown in
@@ -967,10 +1083,12 @@ def ignore_delete_re_add_lines(lines_to_add, lines_to_del):
is the default. This causes frr-reload to unnecessarily unapply
this option only to apply it back again, causing unnecessary session
resets.
- '''
- if 'multipath-relax' in line:
- re_asrelax_new = re.search('^bgp\s+bestpath\s+as-path\s+multipath-relax$', line)
- old_asrelax_cmd = 'bgp bestpath as-path multipath-relax no-as-set'
+ """
+ if "multipath-relax" in line:
+ re_asrelax_new = re.search(
+ "^bgp\s+bestpath\s+as-path\s+multipath-relax$", line
+ )
+ old_asrelax_cmd = "bgp bestpath as-path multipath-relax no-as-set"
found_asrelax_old = line_exist(lines_to_add, ctx_keys, old_asrelax_cmd)
if re_asrelax_new and found_asrelax_old:
@@ -978,34 +1096,36 @@ def ignore_delete_re_add_lines(lines_to_add, lines_to_del):
lines_to_del_to_del.append((ctx_keys, line))
lines_to_add_to_del.append((ctx_keys, old_asrelax_cmd))
- '''
+ """
If we are modifying the BGP table-map we need to avoid a del/add and
instead modify the table-map in place via an add. This is needed to
avoid installing all routes in the RIB the second the 'no table-map'
is issued.
- '''
- if line.startswith('table-map'):
- found_table_map = line_exist(lines_to_add, ctx_keys, 'table-map', False)
+ """
+ if line.startswith("table-map"):
+ found_table_map = line_exist(lines_to_add, ctx_keys, "table-map", False)
if found_table_map:
lines_to_del_to_del.append((ctx_keys, line))
- '''
+ """
More old-to-new config handling. ip import-table no longer accepts
distance, but we honor the old syntax. But 'show running' shows only
the new syntax. This causes an unnecessary 'no import-table' followed
by the same old 'ip import-table' which causes perturbations in
announced routes leading to traffic blackholes. Fix this issue.
- '''
- re_importtbl = re.search('^ip\s+import-table\s+(\d+)$', ctx_keys[0])
+ """
+ re_importtbl = re.search("^ip\s+import-table\s+(\d+)$", ctx_keys[0])
if re_importtbl:
table_num = re_importtbl.group(1)
for ctx in lines_to_add:
- if ctx[0][0].startswith('ip import-table %s distance' % table_num):
- lines_to_del_to_del.append((('ip import-table %s' % table_num,), None))
+ if ctx[0][0].startswith("ip import-table %s distance" % table_num):
+ lines_to_del_to_del.append(
+ (("ip import-table %s" % table_num,), None)
+ )
lines_to_add_to_del.append((ctx[0], None))
- '''
+ """
ip/ipv6 prefix-list can be specified without a seq number. However,
the running config always adds 'seq x', where x is a number incremented
by 5 for every element, to the prefix list. So, ignore such lines as
@@ -1013,24 +1133,36 @@ def ignore_delete_re_add_lines(lines_to_add, lines_to_del):
ip prefix-list PR-TABLE-2 seq 5 permit 20.8.2.0/24 le 32
ip prefix-list PR-TABLE-2 seq 10 permit 20.8.2.0/24 le 32
ipv6 prefix-list vrfdev6-12 permit 2000:9:2::/64 gt 64
- '''
- re_ip_pfxlst = re.search('^(ip|ipv6)(\s+prefix-list\s+)(\S+\s+)(seq \d+\s+)(permit|deny)(.*)$',
- ctx_keys[0])
+ """
+ re_ip_pfxlst = re.search(
+ "^(ip|ipv6)(\s+prefix-list\s+)(\S+\s+)(seq \d+\s+)(permit|deny)(.*)$",
+ ctx_keys[0],
+ )
if re_ip_pfxlst:
- tmpline = (re_ip_pfxlst.group(1) + re_ip_pfxlst.group(2) +
- re_ip_pfxlst.group(3) + re_ip_pfxlst.group(5) +
- re_ip_pfxlst.group(6))
+ tmpline = (
+ re_ip_pfxlst.group(1)
+ + re_ip_pfxlst.group(2)
+ + re_ip_pfxlst.group(3)
+ + re_ip_pfxlst.group(5)
+ + re_ip_pfxlst.group(6)
+ )
for ctx in lines_to_add:
if ctx[0][0] == tmpline:
lines_to_del_to_del.append((ctx_keys, None))
lines_to_add_to_del.append(((tmpline,), None))
- if (len(ctx_keys) == 3 and
- ctx_keys[0].startswith('router bgp') and
- ctx_keys[1] == 'address-family l2vpn evpn' and
- ctx_keys[2].startswith('vni')):
+ if (
+ len(ctx_keys) == 3
+ and ctx_keys[0].startswith("router bgp")
+ and ctx_keys[1] == "address-family l2vpn evpn"
+ and ctx_keys[2].startswith("vni")
+ ):
- re_route_target = re.search('^route-target import (.*)$', line) if line is not None else False
+ re_route_target = (
+ re.search("^route-target import (.*)$", line)
+ if line is not None
+ else False
+ )
if re_route_target:
rt = re_route_target.group(1).strip()
@@ -1038,10 +1170,14 @@ def ignore_delete_re_add_lines(lines_to_add, lines_to_del):
route_target_export_line = "route-target export %s" % rt
route_target_both_line = "route-target both %s" % rt
- found_route_target_export_line = line_exist(lines_to_del, ctx_keys, route_target_export_line)
- found_route_target_both_line = line_exist(lines_to_add, ctx_keys, route_target_both_line)
+ found_route_target_export_line = line_exist(
+ lines_to_del, ctx_keys, route_target_export_line
+ )
+ found_route_target_both_line = line_exist(
+ lines_to_add, ctx_keys, route_target_both_line
+ )
- '''
+ """
If the running configs has
route-target import 1:1
route-target export 1:1
@@ -1050,7 +1186,7 @@ def ignore_delete_re_add_lines(lines_to_add, lines_to_del):
route-target both 1:1
then we can ignore deleting the import/export and ignore adding the 'both'
- '''
+ """
if found_route_target_export_line and found_route_target_both_line:
lines_to_del_to_del.append((ctx_keys, route_target_import_line))
lines_to_del_to_del.append((ctx_keys, route_target_export_line))
@@ -1059,10 +1195,9 @@ def ignore_delete_re_add_lines(lines_to_add, lines_to_del):
# Deleting static routes under a vrf can lead to time-outs if each is sent
# as separate vtysh -c commands. Change them from being in lines_to_del and
# put the "no" form in lines_to_add
- if ctx_keys[0].startswith('vrf ') and line:
- if (line.startswith('ip route') or
- line.startswith('ipv6 route')):
- add_cmd = ('no ' + line)
+ if ctx_keys[0].startswith("vrf ") and line:
+ if line.startswith("ip route") or line.startswith("ipv6 route"):
+ add_cmd = "no " + line
lines_to_add.append((ctx_keys, add_cmd))
lines_to_del_to_del.append((ctx_keys, line))
@@ -1073,7 +1208,7 @@ def ignore_delete_re_add_lines(lines_to_add, lines_to_del):
lines_to_del_to_del.append((ctx_keys, line))
lines_to_add_to_del.append((ctx_keys, line))
else:
- '''
+ """
We have commands that used to be displayed in the global part
of 'router bgp' that are now displayed under 'address-family ipv4 unicast'
@@ -1089,8 +1224,12 @@ def ignore_delete_re_add_lines(lines_to_add, lines_to_del):
neighbor ISL advertisement-interval 0
Look to see if we are deleting it in one format just to add it back in the other
- '''
- if ctx_keys[0].startswith('router bgp') and len(ctx_keys) > 1 and ctx_keys[1] == 'address-family ipv4 unicast':
+ """
+ if (
+ ctx_keys[0].startswith("router bgp")
+ and len(ctx_keys) > 1
+ and ctx_keys[1] == "address-family ipv4 unicast"
+ ):
tmp_ctx_keys = list(ctx_keys)[:-1]
tmp_ctx_keys = tuple(tmp_ctx_keys)
@@ -1118,16 +1257,18 @@ def ignore_unconfigurable_lines(lines_to_add, lines_to_del):
for (ctx_keys, line) in lines_to_del:
- if (ctx_keys[0].startswith('frr version') or
- ctx_keys[0].startswith('frr defaults') or
- ctx_keys[0].startswith('username') or
- ctx_keys[0].startswith('password') or
- ctx_keys[0].startswith('line vty') or
-
+ if (
+ ctx_keys[0].startswith("frr version")
+ or ctx_keys[0].startswith("frr defaults")
+ or ctx_keys[0].startswith("username")
+ or ctx_keys[0].startswith("password")
+ or ctx_keys[0].startswith("line vty")
+ or
# This is technically "no"able but if we did so frr-reload would
# stop working so do not let the user shoot themselves in the foot
# by removing this.
- ctx_keys[0].startswith('service integrated-vtysh-config')):
+ ctx_keys[0].startswith("service integrated-vtysh-config")
+ ):
log.info('"%s" cannot be removed' % (ctx_keys[-1],))
lines_to_del_to_del.append((ctx_keys, line))
@@ -1163,25 +1304,35 @@ def compare_context_objects(newconf, running):
lines_to_del.append((running_ctx_keys, None))
# We cannot do 'no interface' or 'no vrf' in FRR, and so deal with it
- elif running_ctx_keys[0].startswith('interface') or running_ctx_keys[0].startswith('vrf'):
+ elif running_ctx_keys[0].startswith("interface") or running_ctx_keys[
+ 0
+ ].startswith("vrf"):
for line in running_ctx.lines:
lines_to_del.append((running_ctx_keys, line))
# If this is an address-family under 'router bgp' and we are already deleting the
# entire 'router bgp' context then ignore this sub-context
- elif "router bgp" in running_ctx_keys[0] and len(running_ctx_keys) > 1 and delete_bgpd:
+ elif (
+ "router bgp" in running_ctx_keys[0]
+ and len(running_ctx_keys) > 1
+ and delete_bgpd
+ ):
continue
# Delete an entire vni sub-context under "address-family l2vpn evpn"
- elif ("router bgp" in running_ctx_keys[0] and
- len(running_ctx_keys) > 2 and
- running_ctx_keys[1].startswith('address-family l2vpn evpn') and
- running_ctx_keys[2].startswith('vni ')):
+ elif (
+ "router bgp" in running_ctx_keys[0]
+ and len(running_ctx_keys) > 2
+ and running_ctx_keys[1].startswith("address-family l2vpn evpn")
+ and running_ctx_keys[2].startswith("vni ")
+ ):
lines_to_del.append((running_ctx_keys, None))
- elif ("router bgp" in running_ctx_keys[0] and
- len(running_ctx_keys) > 1 and
- running_ctx_keys[1].startswith('address-family')):
+ elif (
+ "router bgp" in running_ctx_keys[0]
+ and len(running_ctx_keys) > 1
+ and running_ctx_keys[1].startswith("address-family")
+ ):
# There's no 'no address-family' support and so we have to
# delete each line individually again
for line in running_ctx.lines:
@@ -1191,30 +1342,40 @@ def compare_context_objects(newconf, running):
# doing vtysh -c inefficient (and can time out.) For
# these commands, instead of adding them to lines_to_del,
# add the "no " version to lines_to_add.
- elif (running_ctx_keys[0].startswith('ip route') or
- running_ctx_keys[0].startswith('ipv6 route') or
- running_ctx_keys[0].startswith('access-list') or
- running_ctx_keys[0].startswith('ipv6 access-list') or
- running_ctx_keys[0].startswith('ip prefix-list') or
- running_ctx_keys[0].startswith('ipv6 prefix-list')):
- add_cmd = ('no ' + running_ctx_keys[0],)
+ elif (
+ running_ctx_keys[0].startswith("ip route")
+ or running_ctx_keys[0].startswith("ipv6 route")
+ or running_ctx_keys[0].startswith("access-list")
+ or running_ctx_keys[0].startswith("ipv6 access-list")
+ or running_ctx_keys[0].startswith("ip prefix-list")
+ or running_ctx_keys[0].startswith("ipv6 prefix-list")
+ ):
+ add_cmd = ("no " + running_ctx_keys[0],)
lines_to_add.append((add_cmd, None))
# if this an interface sub-subcontext in an address-family block in ldpd and
# we are already deleting the whole context, then ignore this
- elif (len(running_ctx_keys) > 2 and running_ctx_keys[0].startswith('mpls ldp') and
- running_ctx_keys[1].startswith('address-family') and
- (running_ctx_keys[:2], None) in lines_to_del):
+ elif (
+ len(running_ctx_keys) > 2
+ and running_ctx_keys[0].startswith("mpls ldp")
+ and running_ctx_keys[1].startswith("address-family")
+ and (running_ctx_keys[:2], None) in lines_to_del
+ ):
continue
# same thing for a pseudowire sub-context inside an l2vpn context
- elif (len(running_ctx_keys) > 1 and running_ctx_keys[0].startswith('l2vpn') and
- running_ctx_keys[1].startswith('member pseudowire') and
- (running_ctx_keys[:1], None) in lines_to_del):
+ elif (
+ len(running_ctx_keys) > 1
+ and running_ctx_keys[0].startswith("l2vpn")
+ and running_ctx_keys[1].startswith("member pseudowire")
+ and (running_ctx_keys[:1], None) in lines_to_del
+ ):
continue
# Non-global context
- elif running_ctx_keys and not any("address-family" in key for key in running_ctx_keys):
+ elif running_ctx_keys and not any(
+ "address-family" in key for key in running_ctx_keys
+ ):
lines_to_del.append((running_ctx_keys, None))
elif running_ctx_keys and not any("vni" in key for key in running_ctx_keys):
@@ -1249,33 +1410,78 @@ def compare_context_objects(newconf, running):
lines_to_add.append((newconf_ctx_keys, line))
(lines_to_add, lines_to_del) = check_for_exit_vrf(lines_to_add, lines_to_del)
- (lines_to_add, lines_to_del) = ignore_delete_re_add_lines(lines_to_add, lines_to_del)
- (lines_to_add, lines_to_del) = ignore_unconfigurable_lines(lines_to_add, lines_to_del)
+ (lines_to_add, lines_to_del) = ignore_delete_re_add_lines(
+ lines_to_add, lines_to_del
+ )
+ (lines_to_add, lines_to_del) = ignore_unconfigurable_lines(
+ lines_to_add, lines_to_del
+ )
return (lines_to_add, lines_to_del)
-if __name__ == '__main__':
+if __name__ == "__main__":
# Command line options
- parser = argparse.ArgumentParser(description='Dynamically apply diff in frr configs')
- parser.add_argument('--input', help='Read running config from file instead of "show running"')
+ parser = argparse.ArgumentParser(
+ description="Dynamically apply diff in frr configs"
+ )
+ parser.add_argument(
+ "--input", help='Read running config from file instead of "show running"'
+ )
group = parser.add_mutually_exclusive_group(required=True)
- group.add_argument('--reload', action='store_true', help='Apply the deltas', default=False)
- group.add_argument('--test', action='store_true', help='Show the deltas', default=False)
+ group.add_argument(
+ "--reload", action="store_true", help="Apply the deltas", default=False
+ )
+ group.add_argument(
+ "--test", action="store_true", help="Show the deltas", default=False
+ )
level_group = parser.add_mutually_exclusive_group()
- level_group.add_argument('--debug', action='store_true',
- help='Enable debugs (synonym for --log-level=debug)', default=False)
- level_group.add_argument('--log-level', help='Log level', default="info",
- choices=("critical", "error", "warning", "info", "debug"))
- parser.add_argument('--stdout', action='store_true', help='Log to STDOUT', default=False)
- parser.add_argument('--pathspace', '-N', metavar='NAME', help='Reload specified path/namespace', default=None)
- parser.add_argument('filename', help='Location of new frr config file')
- parser.add_argument('--overwrite', action='store_true', help='Overwrite frr.conf with running config output', default=False)
- parser.add_argument('--bindir', help='path to the vtysh executable', default='/usr/bin')
- parser.add_argument('--confdir', help='path to the daemon config files', default='/etc/frr')
- parser.add_argument('--rundir', help='path for the temp config file', default='/var/run/frr')
- parser.add_argument('--vty_socket', help='socket to be used by vtysh to connect to the daemons', default=None)
- parser.add_argument('--daemon', help='daemon for which want to replace the config', default='')
+ level_group.add_argument(
+ "--debug",
+ action="store_true",
+ help="Enable debugs (synonym for --log-level=debug)",
+ default=False,
+ )
+ level_group.add_argument(
+ "--log-level",
+ help="Log level",
+ default="info",
+ choices=("critical", "error", "warning", "info", "debug"),
+ )
+ parser.add_argument(
+ "--stdout", action="store_true", help="Log to STDOUT", default=False
+ )
+ parser.add_argument(
+ "--pathspace",
+ "-N",
+ metavar="NAME",
+ help="Reload specified path/namespace",
+ default=None,
+ )
+ parser.add_argument("filename", help="Location of new frr config file")
+ parser.add_argument(
+ "--overwrite",
+ action="store_true",
+ help="Overwrite frr.conf with running config output",
+ default=False,
+ )
+ parser.add_argument(
+ "--bindir", help="path to the vtysh executable", default="/usr/bin"
+ )
+ parser.add_argument(
+ "--confdir", help="path to the daemon config files", default="/etc/frr"
+ )
+ parser.add_argument(
+ "--rundir", help="path for the temp config file", default="/var/run/frr"
+ )
+ parser.add_argument(
+ "--vty_socket",
+ help="socket to be used by vtysh to connect to the daemons",
+ default=None,
+ )
+ parser.add_argument(
+ "--daemon", help="daemon for which want to replace the config", default=""
+ )
args = parser.parse_args()
@@ -1283,22 +1489,28 @@ if __name__ == '__main__':
# For --test log to stdout
# For --reload log to /var/log/frr/frr-reload.log
if args.test or args.stdout:
- logging.basicConfig(format='%(asctime)s %(levelname)5s: %(message)s')
+ logging.basicConfig(format="%(asctime)s %(levelname)5s: %(message)s")
# Color the errors and warnings in red
- logging.addLevelName(logging.ERROR, "\033[91m %s\033[0m" % logging.getLevelName(logging.ERROR))
- logging.addLevelName(logging.WARNING, "\033[91m%s\033[0m" % logging.getLevelName(logging.WARNING))
+ logging.addLevelName(
+ logging.ERROR, "\033[91m %s\033[0m" % logging.getLevelName(logging.ERROR)
+ )
+ logging.addLevelName(
+ logging.WARNING, "\033[91m%s\033[0m" % logging.getLevelName(logging.WARNING)
+ )
elif args.reload:
- if not os.path.isdir('/var/log/frr/'):
- os.makedirs('/var/log/frr/')
+ if not os.path.isdir("/var/log/frr/"):
+ os.makedirs("/var/log/frr/")
- logging.basicConfig(filename='/var/log/frr/frr-reload.log',
- format='%(asctime)s %(levelname)5s: %(message)s')
+ logging.basicConfig(
+ filename="/var/log/frr/frr-reload.log",
+ format="%(asctime)s %(levelname)5s: %(message)s",
+ )
# argparse should prevent this from happening but just to be safe...
else:
- raise Exception('Must specify --reload or --test')
+ raise Exception("Must specify --reload or --test")
log = logging.getLogger(__name__)
if args.debug:
@@ -1332,40 +1544,59 @@ if __name__ == '__main__':
sys.exit(1)
# Verify that bindir is correct
- if not os.path.isdir(args.bindir) or not os.path.isfile(args.bindir + '/vtysh'):
+ if not os.path.isdir(args.bindir) or not os.path.isfile(args.bindir + "/vtysh"):
log.error("Bindir %s is not a valid path to vtysh" % args.bindir)
sys.exit(1)
# verify that the vty_socket, if specified, is valid
if args.vty_socket and not os.path.isdir(args.vty_socket):
- log.error('vty_socket %s is not a valid path' % args.vty_socket)
+ log.error("vty_socket %s is not a valid path" % args.vty_socket)
sys.exit(1)
# verify that the daemon, if specified, is valid
- if args.daemon and args.daemon not in ['zebra', 'bgpd', 'fabricd', 'isisd', 'ospf6d', 'ospfd', 'pbrd', 'pimd', 'ripd', 'ripngd', 'sharpd', 'staticd', 'vrrpd', 'ldpd']:
- log.error("Daemon %s is not a valid option for 'show running-config'" % args.daemon)
+ if args.daemon and args.daemon not in [
+ "zebra",
+ "bgpd",
+ "fabricd",
+ "isisd",
+ "ospf6d",
+ "ospfd",
+ "pbrd",
+ "pimd",
+ "ripd",
+ "ripngd",
+ "sharpd",
+ "staticd",
+ "vrrpd",
+ "ldpd",
+ ]:
+ log.error(
+ "Daemon %s is not a valid option for 'show running-config'" % args.daemon
+ )
sys.exit(1)
vtysh = Vtysh(args.bindir, args.confdir, args.vty_socket, args.pathspace)
# Verify that 'service integrated-vtysh-config' is configured
if args.pathspace:
- vtysh_filename = args.confdir + '/' + args.pathspace + '/vtysh.conf'
+ vtysh_filename = args.confdir + "/" + args.pathspace + "/vtysh.conf"
else:
- vtysh_filename = args.confdir + '/vtysh.conf'
+ vtysh_filename = args.confdir + "/vtysh.conf"
service_integrated_vtysh_config = True
if os.path.isfile(vtysh_filename):
- with open(vtysh_filename, 'r') as fh:
+ with open(vtysh_filename, "r") as fh:
for line in fh.readlines():
line = line.strip()
- if line == 'no service integrated-vtysh-config':
+ if line == "no service integrated-vtysh-config":
service_integrated_vtysh_config = False
break
if not service_integrated_vtysh_config and not args.daemon:
- log.error("'service integrated-vtysh-config' is not configured, this is required for 'service frr reload'")
+ log.error(
+ "'service integrated-vtysh-config' is not configured, this is required for 'service frr reload'"
+ )
sys.exit(1)
log.info('Called via "%s"', str(args))
@@ -1398,10 +1629,10 @@ if __name__ == '__main__':
for (ctx_keys, line) in lines_to_del:
- if line == '!':
+ if line == "!":
continue
- cmd = '\n'.join(lines_to_config(ctx_keys, line, True))
+ cmd = "\n".join(lines_to_config(ctx_keys, line, True))
lines_to_configure.append(cmd)
print(cmd)
@@ -1411,10 +1642,10 @@ if __name__ == '__main__':
for (ctx_keys, line) in lines_to_add:
- if line == '!':
+ if line == "!":
continue
- cmd = '\n'.join(lines_to_config(ctx_keys, line, False))
+ cmd = "\n".join(lines_to_config(ctx_keys, line, False))
lines_to_configure.append(cmd)
print(cmd)
@@ -1424,7 +1655,7 @@ if __name__ == '__main__':
if not vtysh.is_config_available():
sys.exit(1)
- log.debug('New Frr Config\n%s', newconf.get_lines())
+ log.debug("New Frr Config\n%s", newconf.get_lines())
# This looks a little odd but we have to do this twice...here is why
# If the user had this running bgp config:
@@ -1466,7 +1697,7 @@ if __name__ == '__main__':
for x in range(2):
running = Config(vtysh)
running.load_from_show_running(args.daemon)
- log.debug('Running Frr Config (Pass #%d)\n%s', x, running.get_lines())
+ log.debug("Running Frr Config (Pass #%d)\n%s", x, running.get_lines())
(lines_to_add, lines_to_del) = compare_context_objects(newconf, running)
@@ -1491,7 +1722,7 @@ if __name__ == '__main__':
if lines_to_del and x == 0:
for (ctx_keys, line) in lines_to_del:
- if line == '!':
+ if line == "!":
continue
# 'no' commands are tricky, we can't just put them in a file and
@@ -1516,7 +1747,7 @@ if __name__ == '__main__':
while True:
try:
- vtysh(['configure'] + cmd)
+ vtysh(["configure"] + cmd)
except VtyshException:
@@ -1524,17 +1755,20 @@ if __name__ == '__main__':
# 'no ip ospf authentication message-digest 1.1.1.1' in
# our example above
# - Split that last entry by whitespace and drop the last word
- log.info('Failed to execute %s', ' '.join(cmd))
- last_arg = cmd[-1].split(' ')
+ log.info("Failed to execute %s", " ".join(cmd))
+ last_arg = cmd[-1].split(" ")
if len(last_arg) <= 2:
- log.error('"%s" we failed to remove this command', ' -- '.join(original_cmd))
+ log.error(
+ '"%s" we failed to remove this command',
+ " -- ".join(original_cmd),
+ )
break
new_last_arg = last_arg[0:-1]
- cmd[-1] = ' '.join(new_last_arg)
+ cmd[-1] = " ".join(new_last_arg)
else:
- log.info('Executed "%s"', ' '.join(cmd))
+ log.info('Executed "%s"', " ".join(cmd))
break
if lines_to_add:
@@ -1542,28 +1776,31 @@ if __name__ == '__main__':
for (ctx_keys, line) in lines_to_add:
- if line == '!':
+ if line == "!":
continue
# Don't run "no" commands twice since they can error
# out the second time due to first deletion
- if x == 1 and ctx_keys[0].startswith('no '):
+ if x == 1 and ctx_keys[0].startswith("no "):
continue
- cmd = '\n'.join(lines_to_config(ctx_keys, line, False)) + '\n'
+ cmd = "\n".join(lines_to_config(ctx_keys, line, False)) + "\n"
lines_to_configure.append(cmd)
if lines_to_configure:
- random_string = ''.join(random.SystemRandom().choice(
- string.ascii_uppercase +
- string.digits) for _ in range(6))
+ random_string = "".join(
+ random.SystemRandom().choice(
+ string.ascii_uppercase + string.digits
+ )
+ for _ in range(6)
+ )
filename = args.rundir + "/reload-%s.txt" % random_string
log.info("%s content\n%s" % (filename, pformat(lines_to_configure)))
- with open(filename, 'w') as fh:
+ with open(filename, "w") as fh:
for line in lines_to_configure:
- fh.write(line + '\n')
+ fh.write(line + "\n")
try:
vtysh.exec_file(filename)
@@ -1573,9 +1810,9 @@ if __name__ == '__main__':
os.unlink(filename)
# Make these changes persistent
- target = str(args.confdir + '/frr.conf')
+ target = str(args.confdir + "/frr.conf")
if args.overwrite or (not args.daemon and args.filename != target):
- vtysh('write')
+ vtysh("write")
if not reload_ok:
sys.exit(1)
If you are a new contributor to FRR, please see our contributing guidelines.
After making changes, you do not need to create a new PR. You should perform an amend or interactive rebase followed by a force push.
Continuous Integration Result: SUCCESSFULCongratulations, this patch passed basic tests Tested-by: NetDEF / OpenSourceRouting.org CI System CI System Testrun URL: https://ci1.netdef.org/browse/FRR-FRRPULLREQ-17333/ This is a comment from an automated CI system. Warnings Generated during build:Checkout code: Successful with additional warnings
CLANG Static Analyzer Summary
No Changes in Static Analysis warnings compared to base6 Static Analyzer issues remaining.See details at |
No description provided.