]> git.proxmox.com Git - mirror_frr.git/blobdiff - tools/frr-reload.py
Merge pull request #3362 from pacovn/Coverity_1475469_null_check
[mirror_frr.git] / tools / frr-reload.py
index e19eeb04eea305d96b0ea11ff603afd050844bcc..3b41b57d682e4d2f218e1c6ac32fdca2a068ad9c 100755 (executable)
@@ -28,6 +28,7 @@ This program
   text file
 """
 
+from __future__ import print_function, unicode_literals
 import argparse
 import copy
 import logging
@@ -38,9 +39,22 @@ import string
 import subprocess
 import sys
 from collections import OrderedDict
-from ipaddr import IPv6Address, IPNetwork
+try:
+    from ipaddress import IPv6Address, ip_network
+except ImportError:
+    from ipaddr import IPv6Address, IPNetwork
 from pprint import pformat
 
+try:
+    dict.iteritems
+except AttributeError:
+    # Python 3
+    def iteritems(d):
+        return iter(d.items())
+else:
+    # Python 2
+    def iteritems(d):
+        return d.iteritems()
 
 log = logging.getLogger(__name__)
 
@@ -116,8 +130,12 @@ class Config(object):
             ve.output = e.output
             raise ve
 
-        for line in file_output.split('\n'):
+        for line in file_output.decode('utf-8').split('\n'):
             line = line.strip()
+
+            # Compress duplicate whitespaces
+            line = ' '.join(line.split())
+
             if ":" in line:
                 qv6_line = get_normalized_ipv6_line(line)
                 self.lines.append(qv6_line)
@@ -143,7 +161,7 @@ class Config(object):
             ve.output = e.output
             raise ve
 
-        for line in config_text.split('\n'):
+        for line in config_text.decode('utf-8').split('\n'):
             line = line.strip()
 
             if (line == 'Building configuration...' or
@@ -167,8 +185,8 @@ class Config(object):
         Return the parsed context as strings for display, log etc.
         """
 
-        for (_, ctx) in sorted(self.contexts.iteritems()):
-            print str(ctx) + '\n'
+        for (_, ctx) in sorted(iteritems(self.contexts)):
+            print(str(ctx) + '\n')
 
     def save_contexts(self, key, lines):
         """
@@ -191,11 +209,18 @@ class Config(object):
             addr = re_key_rt.group(2)
             if '/' in addr:
                 try:
-                    newaddr = IPNetwork(addr)
-                    key[0] = '%s route %s/%s%s' % (re_key_rt.group(1),
-                                                   newaddr.network,
-                                                   newaddr.prefixlen,
-                                                   re_key_rt.group(3))
+                    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))
+                    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))
                 except ValueError:
                     pass
 
@@ -207,8 +232,13 @@ class Config(object):
             addr = re_key_rt.group(4)
             if '/' in addr:
                 try:
-                    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)
                 except ValueError:
                     newaddr = addr
             else:
@@ -249,10 +279,16 @@ class Config(object):
                         addr = addr + '/8'
 
                     try:
-                        newaddr = IPNetwork(addr)
-                        line = 'network %s/%s %s' % (newaddr.network,
-                                                     newaddr.prefixlen,
-                                                     re_net.group(2))
+                        if 'ipaddress' not in sys.modules:
+                            newaddr = IPNetwork(addr)
+                            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))
                         newlines.append(line)
                     except ValueError:
                         # Really this should be an error. Whats a network
@@ -399,7 +435,7 @@ end
                 self.save_contexts(ctx_keys, current_context_lines)
                 new_ctx = True
 
-            elif line == "end":
+            elif line in ["end", "exit-vrf"]:
                 self.save_contexts(ctx_keys, current_context_lines)
                 log.debug('LINE %-50s: exiting old context, %-50s', line, ctx_keys)
 
@@ -409,7 +445,7 @@ end
                 ctx_keys = []
                 current_context_lines = []
 
-            elif line == "exit-address-family" or line == "exit" or line == "exit-vni":
+            elif line in ["exit-address-family", "exit", "exit-vnc"]:
                 # if this exit is for address-family ipv4 unicast, ignore the pop
                 if main_ctx_key:
                     self.save_contexts(ctx_keys, current_context_lines)
@@ -419,6 +455,15 @@ end
                     current_context_lines = []
                     log.debug('LINE %-50s: popping from subcontext to ctx%-50s', line, ctx_keys)
 
+            elif line == "exit-vni":
+                if sub_main_ctx_key:
+                    self.save_contexts(ctx_keys, current_context_lines)
+
+                    # 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)
+
             elif new_ctx is True:
                 if not main_ctx_key:
                     ctx_keys = [line, ]
@@ -429,19 +474,10 @@ end
                 current_context_lines = []
                 new_ctx = False
                 log.debug('LINE %-50s: entering new context, %-50s', line, ctx_keys)
-
-            elif "vni " in line:
-                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)
-
-                ctx_keys.append(line)
-
-            elif "address-family " in line:
+            elif (line.startswith("address-family ") or
+                  line.startswith("vnc defaults") or
+                  line.startswith("vnc l2-group") or
+                  line.startswith("vnc nve-group")):
                 main_ctx_key = []
 
                 # Save old context first
@@ -459,6 +495,18 @@ end
                 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')):
+
+                # 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)
+                ctx_keys.append(line)
+
             else:
                 # Continuing in an existing context, add non-commented lines to it
                 current_context_lines.append(line)
@@ -582,8 +630,12 @@ def get_normalized_ipv6_line(line):
             norm_word = None
             if "/" in word:
                 try:
-                    v6word = IPNetwork(word)
-                    norm_word = '%s/%s' % (v6word.network, v6word.prefixlen)
+                    if 'ipaddress' not in sys.modules:
+                        v6word = IPNetwork(word)
+                        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)
                 except ValueError:
                     pass
             if not norm_word:
@@ -598,10 +650,15 @@ def get_normalized_ipv6_line(line):
     return norm_line.strip()
 
 
-def line_exist(lines, target_ctx_keys, target_line):
+def line_exist(lines, target_ctx_keys, target_line, exact_match=True):
     for (ctx_keys, line) in lines:
-        if ctx_keys == target_ctx_keys and line == target_line:
-            return True
+        if ctx_keys == target_ctx_keys:
+            if exact_match:
+                if line == target_line:
+                    return True
+            else:
+                if line.startswith(target_line):
+                    return True
     return False
 
 
@@ -614,140 +671,154 @@ 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 and 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
-                neighbor swp1 peer-group FOO
-
-            but today we display via a single line
-                neighbor swp1 interface peer-group FOO
-
-            This change confuses frr-reload.py so check to see if we are deleting
-                neighbor swp1 interface peer-group FOO
-
-            and adding
-                neighbor swp1 interface
-                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)
-
-            if re_swpx_int_peergroup or re_swpx_int_v6only_peergroup:
-                swpx_interface = None
-                swpx_peergroup = None
-
-                if re_swpx_int_peergroup:
-                    swpx = re_swpx_int_peergroup.group(1)
-                    peergroup = re_swpx_int_peergroup.group(2)
-                    swpx_interface = "neighbor %s interface" % swpx
-                elif re_swpx_int_v6only_peergroup:
-                    swpx = re_swpx_int_v6only_peergroup.group(1)
-                    peergroup = re_swpx_int_v6only_peergroup.group(2)
-                    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)
-                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 = tuple(tmp_ctx_keys)
-                    found_add_swpx_peergroup = line_exist(lines_to_add, tmp_ctx_keys, swpx_peergroup)
+        if ctx_keys[0].startswith('router bgp') and line:
+
+            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
+                    neighbor swp1 peer-group FOO
+
+                but today we display via a single line
+                    neighbor swp1 interface peer-group FOO
+
+                This change confuses frr-reload.py so check to see if we are deleting
+                    neighbor swp1 interface peer-group FOO
+
+                and adding
+                    neighbor swp1 interface
+                    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)
+
+                if re_swpx_int_peergroup or re_swpx_int_v6only_peergroup:
+                    swpx_interface = None
+                    swpx_peergroup = None
+
+                    if re_swpx_int_peergroup:
+                        swpx = re_swpx_int_peergroup.group(1)
+                        peergroup = re_swpx_int_peergroup.group(2)
+                        swpx_interface = "neighbor %s interface" % swpx
+                    elif re_swpx_int_v6only_peergroup:
+                        swpx = re_swpx_int_v6only_peergroup.group(1)
+                        peergroup = re_swpx_int_v6only_peergroup.group(2)
+                        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)
+                    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 ipv6 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)
 
-                if found_add_swpx_interface and found_add_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 = tuple(tmp_ctx_keys)
+                            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
+                        lines_to_del_to_del.append((ctx_keys, line))
+                        lines_to_add_to_del.append((ctx_keys, swpx_interface))
+                        lines_to_add_to_del.append((tmp_ctx_keys, swpx_peergroup))
+
+                '''
+                We changed how we display the neighbor interface command. Older
+                versions of frr would display the following:
+                    neighbor swp1 interface
+                    neighbor swp1 remote-as external
+                    neighbor swp1 capability extended-nexthop
+
+                but today we display via a single line
+                    neighbor swp1 interface remote-as external
+
+                and capability extended-nexthop is no longer needed because we
+                automatically enable it when the neighbor is of type interface.
+
+                This change confuses frr-reload.py so check to see if we are deleting
+                    neighbor swp1 interface remote-as (external|internal|ASNUM)
+
+                and adding
+                    neighbor swp1 interface
+                    neighbor swp1 remote-as (external|internal|ASNUM)
+                    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)
+
+                if re_swpx_int_remoteas or re_swpx_int_v6only_remoteas:
+                    swpx_interface = None
+                    swpx_remoteas = None
+
+                    if re_swpx_int_remoteas:
+                        swpx = re_swpx_int_remoteas.group(1)
+                        remoteas = re_swpx_int_remoteas.group(2)
+                        swpx_interface = "neighbor %s interface" % swpx
+                    elif re_swpx_int_v6only_remoteas:
+                        swpx = re_swpx_int_v6only_remoteas.group(1)
+                        remoteas = re_swpx_int_v6only_remoteas.group(2)
+                        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)
+                    tmp_ctx_keys = tuple(list(ctx_keys))
+
+                    if found_add_swpx_interface and found_add_swpx_remoteas:
+                        deleted = True
+                        lines_to_del_to_del.append((ctx_keys, line))
+                        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
+            releases with this change, the no-as-set keyword is not shown as it
+            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'
+                found_asrelax_old = line_exist(lines_to_add, ctx_keys, old_asrelax_cmd)
+
+                if re_asrelax_new and found_asrelax_old:
                     deleted = True
                     lines_to_del_to_del.append((ctx_keys, line))
-                    lines_to_add_to_del.append((ctx_keys, swpx_interface))
-                    lines_to_add_to_del.append((tmp_ctx_keys, swpx_peergroup))
-
-            """
-            In 3.0.1 we changed how we display neighbor interface command. Older
-            versions of frr would display the following:
-                neighbor swp1 interface
-                neighbor swp1 remote-as external
-                neighbor swp1 capability extended-nexthop
-
-            but today we display via a single line
-                neighbor swp1 interface remote-as external
-
-            and capability extended-nexthop is no longer needed because we
-            automatically enable it when the neighbor is of type interface.
-
-            This change confuses frr-reload.py so check to see if we are deleting
-                neighbor swp1 interface remote-as (external|internal|ASNUM)
-
-            and adding
-                neighbor swp1 interface
-                neighbor swp1 remote-as (external|internal|ASNUM)
-                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)
-
-            if re_swpx_int_remoteas or re_swpx_int_v6only_remoteas:
-                swpx_interface = None
-                swpx_remoteas = None
-
-                if re_swpx_int_remoteas:
-                    swpx = re_swpx_int_remoteas.group(1)
-                    remoteas = re_swpx_int_remoteas.group(2)
-                    swpx_interface = "neighbor %s interface" % swpx
-                elif re_swpx_int_v6only_remoteas:
-                    swpx = re_swpx_int_v6only_remoteas.group(1)
-                    remoteas = re_swpx_int_v6only_remoteas.group(2)
-                    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)
-                tmp_ctx_keys = tuple(list(ctx_keys))
-
-                if found_add_swpx_interface and found_add_swpx_remoteas:
-                    deleted = True
+                    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 found_table_map:
                     lines_to_del_to_del.append((ctx_keys, line))
-                    lines_to_add_to_del.append((ctx_keys, swpx_interface))
-                    lines_to_add_to_del.append((tmp_ctx_keys, swpx_remoteas))
-
-        '''
-           In 3.0, we made bgp bestpath multipath as-relax command
-           automatically assume no-as-set since the lack of this option caused
-           weird routing problems and this problem was peculiar to this
-           implementation. When the running config is shown in relases after
-           3.0, the no-as-set is not shown as its the default. This causes
-           reload to unnecessarily unapply this option to only apply it back
-           again, causing unnecessary session resets. Handle this.
-        '''
-        if ctx_keys[0].startswith('router bgp') and line and '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:
-                deleted = True
-                lines_to_del_to_del.append((ctx_keys, line))
-                lines_to_add_to_del.append((ctx_keys, old_asrelax_cmd))
 
         '''
-           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.
+        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])
         if re_importtbl:
@@ -851,6 +922,34 @@ def ignore_delete_re_add_lines(lines_to_add, lines_to_del):
     return (lines_to_add, lines_to_del)
 
 
+def ignore_unconfigurable_lines(lines_to_add, lines_to_del):
+    """
+    There are certain commands that cannot be removed.  Remove
+    those commands from lines_to_del.
+    """
+    lines_to_del_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('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')):
+
+            log.info("(%s, %s) cannot be removed" % (pformat(ctx_keys), line))
+            lines_to_del_to_del.append((ctx_keys, line))
+
+    for (ctx_keys, line) in lines_to_del_to_del:
+        lines_to_del.remove((ctx_keys, line))
+
+    return (lines_to_add, lines_to_del)
+
+
 def compare_context_objects(newconf, running):
     """
     Create a context diff for the two specified contexts
@@ -863,7 +962,7 @@ def compare_context_objects(newconf, running):
 
     # Find contexts that are in newconf but not in running
     # Find contexts that are in running but not in newconf
-    for (running_ctx_keys, running_ctx) in running.contexts.iteritems():
+    for (running_ctx_keys, running_ctx) in iteritems(running.contexts):
 
         if running_ctx_keys not in newconf.contexts:
 
@@ -914,7 +1013,7 @@ def compare_context_objects(newconf, running):
 
     # Find the lines within each context to add
     # Find the lines within each context to del
-    for (newconf_ctx_keys, newconf_ctx) in newconf.contexts.iteritems():
+    for (newconf_ctx_keys, newconf_ctx) in iteritems(newconf.contexts):
 
         if newconf_ctx_keys in running.contexts:
             running_ctx = running.contexts[newconf_ctx_keys]
@@ -927,7 +1026,7 @@ def compare_context_objects(newconf, running):
                 if line not in newconf_ctx.dlines:
                     lines_to_del.append((newconf_ctx_keys, line))
 
-    for (newconf_ctx_keys, newconf_ctx) in newconf.contexts.iteritems():
+    for (newconf_ctx_keys, newconf_ctx) in iteritems(newconf.contexts):
 
         if newconf_ctx_keys not in running.contexts:
             lines_to_add.append((newconf_ctx_keys, None))
@@ -936,6 +1035,7 @@ def compare_context_objects(newconf, running):
                 lines_to_add.append((newconf_ctx_keys, line))
 
     (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)
 
@@ -952,14 +1052,14 @@ def vtysh_config_available():
         cmd = ['/usr/bin/vtysh', '-c', 'conf t']
         output = subprocess.check_output(cmd, stderr=subprocess.STDOUT).strip()
 
-        if 'VTY configuration is locked by other VTY' in output:
-            print output
+        if 'VTY configuration is locked by other VTY' in output.decode('utf-8'):
+            print(output)
             log.error("'%s' returned\n%s\n" % (' '.join(cmd), output))
             return False
 
     except subprocess.CalledProcessError as e:
         msg = "vtysh could not connect with any frr daemons"
-        print msg
+        print(msg)
         log.error(msg)
         return False
 
@@ -1006,13 +1106,13 @@ if __name__ == '__main__':
     # Verify the new config file is valid
     if not os.path.isfile(args.filename):
         msg = "Filename %s does not exist" % args.filename
-        print msg
+        print(msg)
         log.error(msg)
         sys.exit(1)
 
     if not os.path.getsize(args.filename):
         msg = "Filename %s is an empty file" % args.filename
-        print msg
+        print(msg)
         log.error(msg)
         sys.exit(1)
 
@@ -1031,7 +1131,7 @@ if __name__ == '__main__':
 
     if not service_integrated_vtysh_config:
         msg = "'service integrated-vtysh-config' is not configured, this is required for 'service frr reload'"
-        print msg
+        print(msg)
         log.error(msg)
         sys.exit(1)
 
@@ -1059,8 +1159,8 @@ if __name__ == '__main__':
         lines_to_configure = []
 
         if lines_to_del:
-            print "\nLines To Delete"
-            print "==============="
+            print("\nLines To Delete")
+            print("===============")
 
             for (ctx_keys, line) in lines_to_del:
 
@@ -1069,11 +1169,11 @@ if __name__ == '__main__':
 
                 cmd = line_for_vtysh_file(ctx_keys, line, True)
                 lines_to_configure.append(cmd)
-                print cmd
+                print(cmd)
 
         if lines_to_add:
-            print "\nLines To Add"
-            print "============"
+            print("\nLines To Add")
+            print("============")
 
             for (ctx_keys, line) in lines_to_add:
 
@@ -1082,7 +1182,7 @@ if __name__ == '__main__':
 
                 cmd = line_for_vtysh_file(ctx_keys, line, False)
                 lines_to_configure.append(cmd)
-                print cmd
+                print(cmd)
 
     elif args.reload: