From 4b826680a87d6add0e6a78df7d25584ea607d265 Mon Sep 17 00:00:00 2001 From: zsdc Date: Fri, 27 Dec 2019 23:17:40 +0200 Subject: FRRouting: T1514: Added commands to restart FRRouting daemon It can be restarted the whole FRRouting (all running) daemons or only selected ones. The configuration is saving during the restart process, so after it, the active config should be the same as before. There are no checks for safety, so responsibility for the results of running command is fully on the operator. --- op-mode-definitions/restart-frr.xml | 63 +++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 op-mode-definitions/restart-frr.xml (limited to 'op-mode-definitions') diff --git a/op-mode-definitions/restart-frr.xml b/op-mode-definitions/restart-frr.xml new file mode 100644 index 000000000..4b649febd --- /dev/null +++ b/op-mode-definitions/restart-frr.xml @@ -0,0 +1,63 @@ + + + + + + + Restart FRRouting daemons + + ${vyos_op_scripts_dir}/restart_frr.py --action restart + + + + Restart Bidirectional Forwarding Detection daemon + + ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon bfdd + + + + Restart Border Gateway Protocol daemon + + ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon bgpd + + + + Restart OSPFv2 daemon + + ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon ospfd + + + + Restart OSPFv3 daemon + + ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon ospf6d + + + + Restart Routing Information Protocol daemon + + ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon ripd + + + + Restart RIPng daemon + + ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon ripngd + + + + Restart Static Route daemon + + ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon staticd + + + + Restart IP routing manager daemon + + ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon zebra + + + + + + -- cgit v1.2.3 From 452a9651f215205b34268c723beec5b2f964ad09 Mon Sep 17 00:00:00 2001 From: zsdc Date: Tue, 31 Dec 2019 00:04:50 +0200 Subject: FRRouting: T1514: Extended FRR restarting functionality and fixed some bugs This change addressed to fix bug with empty configuration after FRR restarting in some cases and protect from some other potential problems. * added warning and request for confirmation before doing any actions * added a couple of safety checks (already running restart, active watchfrr.sh or vtysh session) * now Python script running via sudo to give us the ability to get processes information and work with all directories and vtysh * moved configuration restoring functionality from frrcommon.sh to Python script, as frrcommon.sh implementation in some cases not load configuration in time, which leads to empty config after * the `/etc/frr/frr.conf` is not used anymore. Instead, we are saving active configuration to the temporary directory --- op-mode-definitions/restart-frr.xml | 18 ++--- src/op_mode/restart_frr.py | 135 ++++++++++++++++++++++++++---------- 2 files changed, 107 insertions(+), 46 deletions(-) (limited to 'op-mode-definitions') diff --git a/op-mode-definitions/restart-frr.xml b/op-mode-definitions/restart-frr.xml index 4b649febd..96ad1a650 100644 --- a/op-mode-definitions/restart-frr.xml +++ b/op-mode-definitions/restart-frr.xml @@ -6,55 +6,55 @@ Restart FRRouting daemons - ${vyos_op_scripts_dir}/restart_frr.py --action restart + sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart Restart Bidirectional Forwarding Detection daemon - ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon bfdd + sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon bfdd Restart Border Gateway Protocol daemon - ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon bgpd + sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon bgpd Restart OSPFv2 daemon - ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon ospfd + sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon ospfd Restart OSPFv3 daemon - ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon ospf6d + sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon ospf6d Restart Routing Information Protocol daemon - ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon ripd + sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon ripd Restart RIPng daemon - ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon ripngd + sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon ripngd Restart Static Route daemon - ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon staticd + sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon staticd Restart IP routing manager daemon - ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon zebra + sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon zebra diff --git a/src/op_mode/restart_frr.py b/src/op_mode/restart_frr.py index 085b8c355..da6407e23 100755 --- a/src/op_mode/restart_frr.py +++ b/src/op_mode/restart_frr.py @@ -20,12 +20,13 @@ import argparse import subprocess import logging from logging.handlers import SysLogHandler +from pathlib import Path +import psutil # some default values watchfrr = '/usr/lib/frr/watchfrr.sh' vtysh = '/usr/bin/vtysh' -frrconfig = '/etc/frr/frr.conf' -frrconfig_tmp = '/etc/frr/frr.conf.temporary' +frrconfig_tmp = '/tmp/frr_restart' # configure logging logger = logging.getLogger(__name__) @@ -34,36 +35,79 @@ logs_handler.setFormatter(logging.Formatter('%(filename)s: %(message)s')) logger.addHandler(logs_handler) logger.setLevel(logging.INFO) -# save or restore current config file -def _save_and_restore(action): - if action == "save": - command = "sudo mv {} {}".format(frrconfig, frrconfig_tmp) - logmsg = "Permanent configuration saved to {}".format(frrconfig_tmp) - if action == "restore": - command = "sudo mv {} {}".format(frrconfig_tmp, frrconfig) - logmsg = "Permanent configuration restored from {}".format(frrconfig_tmp) +# check if it is safe to restart FRR +def _check_safety(): + try: + # print warning + answer = input("WARNING: This is a potentially unsafe function! You may lose the connection to the router or active configuration after running this command. Use it at your own risk! Continue? [y/N]: ") + if not answer.lower() == "y": + logger.error("User aborted command") + return False - return_code = subprocess.call(command, shell=True) - if not return_code == 0: - logger.error("Failed to rename permanent config: \"{}\" returned exit code: {}".format(command, return_code)) + # check if another restart process already running + if len([process for process in psutil.process_iter(attrs=['pid', 'name', 'cmdline']) if 'python' in process.info['name'] and 'restart_frr.py' in process.info['cmdline'][1]]) > 1: + logger.error("Another restart_frr.py already running") + answer = input("Another restart_frr.py process is already running. It is unsafe to continue. Do you want to process anyway? [y/N]: ") + if not answer.lower() == "y": + return False + + # check if watchfrr.sh is running + for process in psutil.process_iter(attrs=['pid', 'name', 'cmdline']): + if 'bash' in process.info['name'] and watchfrr in process.info['cmdline']: + logger.error("Another {} already running".format(watchfrr)) + answer = input("Another {} process is already running. It is unsafe to continue. Do you want to process anyway? [y/N]: ".format(watchfrr)) + if not answer.lower() == "y": + return False + + # check if vtysh is running + for process in psutil.process_iter(attrs=['pid', 'name', 'cmdline']): + if 'vtysh' in process.info['name']: + logger.error("The vtysh is running by another task") + answer = input("The vtysh is running by another task. It is unsafe to continue. Do you want to process anyway? [y/N]: ") + if not answer.lower() == "y": + return False + + # check if temporary directory exists + if Path(frrconfig_tmp).exists(): + logger.error("The temporary directory \"{}\" already exists".format(frrconfig_tmp)) + answer = input("The temporary directory \"{}\" already exists. It is unsafe to continue. Do you want to process anyway? [y/N]: ".format(frrconfig_tmp)) + if not answer.lower() == "y": + return False + except: + logger.error("Something goes wrong in _check_safety()") return False - logger.info(logmsg) + # return True if all check was passed or user confirmed to ignore they results return True # write active config to file def _write_config(): - command = "sudo {} -n -c write ".format(vtysh) + # create temporary directory + Path(frrconfig_tmp).mkdir(parents=False, exist_ok=True) + # save frr.conf to it + command = "{} -n -w --config_dir {} 2> /dev/null".format(vtysh, frrconfig_tmp) return_code = subprocess.call(command, shell=True) if not return_code == 0: logger.error("Failed to save active config: \"{}\" returned exit code: {}".format(command, return_code)) return False - logger.info("Active config saved to {}".format(frrconfig)) + logger.info("Active config saved to {}".format(frrconfig_tmp)) return True +# clear and remove temporary directory +def _cleanup(): + tmpdir = Path(frrconfig_tmp) + try: + if tmpdir.exists(): + for file in tmpdir.iterdir(): + file.unlink() + tmpdir.rmdir() + except: + logger.error("Failed to remove temporary directory {}".format(frrconfig_tmp)) + print("Failed to remove temporary directory {}".format(frrconfig_tmp)) + # check if daemon is running def _daemon_check(daemon): - command = "sudo {} print_status {}".format(watchfrr, daemon) + command = "{} print_status {}".format(watchfrr, daemon) return_code = subprocess.call(command, shell=True) if not return_code == 0: logger.error("Daemon \"{}\" is not running".format(daemon)) @@ -74,16 +118,32 @@ def _daemon_check(daemon): # restart daemon def _daemon_restart(daemon): - command = "sudo {} restart {}".format(watchfrr, daemon) + command = "{} restart {}".format(watchfrr, daemon) return_code = subprocess.call(command, shell=True) if not return_code == 0: logger.error("Failed to restart daemon \"{}\"".format(daemon)) return False - # return True if restarted sucessfully + # return True if restarted successfully logger.info("Daemon \"{}\" restarted".format(daemon)) return True +# reload old config +def _reload_config(daemon): + if daemon != '': + command = "{} -n -b --config_dir {} -d {} 2> /dev/null".format(vtysh, frrconfig_tmp, daemon) + else: + command = "{} -n -b --config_dir {} 2> /dev/null".format(vtysh, frrconfig_tmp) + + return_code = subprocess.call(command, shell=True) + if not return_code == 0: + logger.error("Failed to reinstall configuration") + return False + + # return True if restarted successfully + logger.info("Configuration reinstalled successfully") + return True + # check all daemons if they are running def _check_args_daemon(daemons): for daemon in daemons: @@ -102,34 +162,35 @@ cmd_args = cmd_args_parser.parse_args() # main logic # restart daemon if cmd_args.action == 'restart': - if not _save_and_restore('save'): - logger.error("Failed to rename permanent comfig") - print("Failed to rename permanent comfig") + # check if it is safe to restart FRR + if not _check_safety(): + print("\nOne of the safety checks was failed or user aborted command. Exiting.") sys.exit(1) if not _write_config(): print("Failed to save active config") - _save_and_restore('restore') + _cleanup() sys.exit(1) - if cmd_args.daemon: - # check all daemons if they are running + # a little trick to make further commands more clear + if not cmd_args.daemon: + cmd_args.daemon = [''] + + # check all daemons if they are running + if cmd_args.daemon != ['']: if not _check_args_daemon(cmd_args.daemon): print("Warning: some of listed daemons are not running") - # run command to restart daemon - for daemon in cmd_args.daemon: - if not _daemon_restart(daemon): - print("Failed to restart daemon: {}".format(daemon)) - _save_and_restore('restore') - sys.exit(1) - else: - # run command to restart FRR - if not _daemon_restart(''): - print("Failed to restart FRRouting") - _save_and_restore('restore') + # run command to restart daemon + for daemon in cmd_args.daemon: + if not _daemon_restart(daemon): + print("Failed to restart daemon: {}".format(daemon)) + _cleanup() sys.exit(1) + # reinstall old configuration + _reload_config(daemon) - _save_and_restore('restore') + # cleanup after all actions + _cleanup() sys.exit(0) -- cgit v1.2.3