diff options
Diffstat (limited to 'tools/quagga-reload.py')
| -rwxr-xr-x | tools/quagga-reload.py | 140 |
1 files changed, 97 insertions, 43 deletions
diff --git a/tools/quagga-reload.py b/tools/quagga-reload.py index 9c0cbb48d6..900ed55c43 100755 --- a/tools/quagga-reload.py +++ b/tools/quagga-reload.py @@ -23,6 +23,9 @@ from ipaddr import IPv6Address from pprint import pformat +log = logging.getLogger(__name__) + + class Context(object): """ @@ -80,12 +83,12 @@ class Config(object): The internal representation has been marked appropriately by passing it through vtysh with the -m parameter """ - logger.info('Loading Config object from file %s', filename) + log.info('Loading Config object from file %s', filename) try: file_output = subprocess.check_output(['/usr/bin/vtysh', '-m', '-f', filename]) except subprocess.CalledProcessError as e: - logger.error('vtysh marking of config file %s failed with error %s:', filename, str(e)) + log.error('vtysh marking of config file %s failed with error %s:', filename, str(e)) print "vtysh marking of file %s failed with error: %s" % (filename, str(e)) sys.exit(1) @@ -105,14 +108,14 @@ class Config(object): The internal representation has been marked appropriately by passing it through vtysh with the -m parameter """ - logger.info('Loading Config object from vtysh show running') + log.info('Loading Config object from vtysh show running') try: config_text = subprocess.check_output( "/usr/bin/vtysh -c 'show run' | /usr/bin/tail -n +4 | /usr/bin/vtysh -m -f -", shell=True) except subprocess.CalledProcessError as e: - logger.error('vtysh marking of running config failed with error %s:', str(e)) + log.error('vtysh marking of running config failed with error %s:', str(e)) print "vtysh marking of running config failed with error %s:" % (str(e)) sys.exit(1) @@ -259,13 +262,13 @@ end ctx_keys = [line, ] current_context_lines = [] - logger.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) - logger.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 @@ -281,7 +284,7 @@ end # Start a new context ctx_keys = copy.deepcopy(main_ctx_key) current_context_lines = [] - logger.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 new_ctx is True: if not main_ctx_key: @@ -292,7 +295,7 @@ end current_context_lines = [] new_ctx = False - logger.debug('LINE %-50s: entering new context, %-50s', line, ctx_keys) + log.debug('LINE %-50s: entering new context, %-50s', line, ctx_keys) elif "address-family " in line: main_ctx_key = [] @@ -301,7 +304,7 @@ end self.save_contexts(ctx_keys, current_context_lines) current_context_lines = [] main_ctx_key = copy.deepcopy(ctx_keys) - logger.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": ctx_keys.append("address-family ipv6 unicast") @@ -313,7 +316,7 @@ end else: # Continuing in an existing context, add non-commented lines to it current_context_lines.append(line) - logger.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) @@ -448,7 +451,7 @@ 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.startswith('neighbor '): + 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 quagga would display these on separate lines: @@ -507,6 +510,56 @@ 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)) + """ + In 3.0.1 we changed how we display neighbor interface command. Older + versions of quagga 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 quagga-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)) + if not deleted: found_add_line = line_exist(lines_to_add, ctx_keys, line) @@ -558,7 +611,7 @@ def compare_context_objects(newconf, running): # Compare the two Config objects to find the lines that we need to add/del lines_to_add = [] lines_to_del = [] - restart_bgpd = False + delete_bgpd = False # Find contexts that are in newconf but not in running # Find contexts that are in running but not in newconf @@ -566,17 +619,21 @@ def compare_context_objects(newconf, running): if running_ctx_keys not in newconf.contexts: - # Check if bgp's local ASN has changed. If yes, just restart it # We check that the len is 1 here so that we only look at ('router bgp 10') # and not ('router bgp 10', 'address-family ipv4 unicast'). The - # latter could cause a false restart_bgpd positive if ipv4 unicast is in + # latter could cause a false delete_bgpd positive if ipv4 unicast is in # running but not in newconf. if "router bgp" in running_ctx_keys[0] and len(running_ctx_keys) == 1: - restart_bgpd = True + delete_bgpd = True + lines_to_del.append((running_ctx_keys, None)) + + # 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: continue # Non-global context - if 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)) # Global context @@ -602,11 +659,6 @@ def compare_context_objects(newconf, running): for (newconf_ctx_keys, newconf_ctx) in newconf.contexts.iteritems(): if newconf_ctx_keys not in running.contexts: - - # If its "router bgp" and we're restarting bgp, skip doing - # anything specific for bgp - if "router bgp" in newconf_ctx_keys[0] and restart_bgpd: - continue lines_to_add.append((newconf_ctx_keys, None)) for line in newconf_ctx.lines: @@ -614,7 +666,7 @@ def compare_context_objects(newconf, running): (lines_to_add, lines_to_del) = ignore_delete_re_add_lines(lines_to_add, lines_to_del) - return (lines_to_add, lines_to_del, restart_bgpd) + return (lines_to_add, lines_to_del) if __name__ == '__main__': # Command line options @@ -624,15 +676,21 @@ if __name__ == '__main__': 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) parser.add_argument('--debug', action='store_true', help='Enable debugs', default=False) + parser.add_argument('--stdout', action='store_true', help='Log to STDOUT', default=False) parser.add_argument('filename', help='Location of new quagga config file') args = parser.parse_args() # Logging # For --test log to stdout # For --reload log to /var/log/quagga/quagga-reload.log - if args.test: + if args.test or args.stdout: logging.basicConfig(level=logging.INFO, 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)) + elif args.reload: if not os.path.isdir('/var/log/quagga/'): os.makedirs('/var/log/quagga/') @@ -644,7 +702,7 @@ if __name__ == '__main__': # argparse should prevent this from happening but just to be safe... else: raise Exception('Must specify --reload or --test') - logger = logging.getLogger(__name__) + log = logging.getLogger(__name__) # Verify the new config file is valid if not os.path.isfile(args.filename): @@ -657,15 +715,15 @@ if __name__ == '__main__': # Verify that 'service integrated-vtysh-config' is configured vtysh_filename = '/etc/quagga/vtysh.conf' - service_integrated_vtysh_config = False + service_integrated_vtysh_config = True if os.path.isfile(vtysh_filename): with open(vtysh_filename, 'r') as fh: for line in fh.readlines(): line = line.strip() - if line == 'service integrated-vtysh-config': - service_integrated_vtysh_config = True + if line == 'no service integrated-vtysh-config': + service_integrated_vtysh_config = False break if not service_integrated_vtysh_config: @@ -673,9 +731,9 @@ if __name__ == '__main__': sys.exit(1) if args.debug: - logger.setLevel(logging.DEBUG) + log.setLevel(logging.DEBUG) - logger.info('Called via "%s"', str(args)) + log.info('Called via "%s"', str(args)) # Create a Config object from the config generated by newconf newconf = Config() @@ -691,7 +749,7 @@ if __name__ == '__main__': else: running.load_from_show_running() - (lines_to_add, lines_to_del, restart_bgp) = compare_context_objects(newconf, running) + (lines_to_add, lines_to_del) = compare_context_objects(newconf, running) lines_to_configure = [] if lines_to_del: @@ -720,12 +778,9 @@ if __name__ == '__main__': lines_to_configure.append(cmd) print cmd - if restart_bgp: - print "BGP local AS changed, bgpd would restart" - elif args.reload: - logger.debug('New Quagga Config\n%s', newconf.get_lines()) + log.debug('New Quagga 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: @@ -752,9 +807,9 @@ if __name__ == '__main__': for x in range(2): running = Config() running.load_from_show_running() - logger.debug('Running Quagga Config (Pass #%d)\n%s', x, running.get_lines()) + log.debug('Running Quagga Config (Pass #%d)\n%s', x, running.get_lines()) - (lines_to_add, lines_to_del, restart_bgp) = compare_context_objects(newconf, running) + (lines_to_add, lines_to_del) = compare_context_objects(newconf, running) if lines_to_del: for (ctx_keys, line) in lines_to_del: @@ -792,17 +847,17 @@ 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 - logger.warning('Failed to execute %s', ' '.join(cmd)) + log.warning('Failed to execute %s', ' '.join(cmd)) last_arg = cmd[-1].split(' ') if len(last_arg) <= 2: - logger.error('"%s" we failed to remove this command', original_cmd) + log.error('"%s" we failed to remove this command', original_cmd) break new_last_arg = last_arg[0:-1] cmd[-1] = ' '.join(new_last_arg) else: - logger.info('Executed "%s"', ' '.join(cmd)) + log.info('Executed "%s"', ' '.join(cmd)) break if lines_to_add: @@ -822,7 +877,7 @@ if __name__ == '__main__': string.digits) for _ in range(6)) filename = "/var/run/quagga/reload-%s.txt" % random_string - logger.info("%s content\n%s" % (filename, pformat(lines_to_configure))) + log.info("%s content\n%s" % (filename, pformat(lines_to_configure))) with open(filename, 'w') as fh: for line in lines_to_configure: @@ -830,6 +885,5 @@ if __name__ == '__main__': subprocess.call(['/usr/bin/vtysh', '-f', filename]) os.unlink(filename) - if restart_bgp: - subprocess.call(['sudo', 'systemctl', 'reset-failed', 'quagga']) - subprocess.call(['sudo', 'systemctl', '--no-block', 'restart', 'quagga']) + # Make these changes persistent + subprocess.call(['/usr/bin/vtysh', '-c', 'write']) |
