#!/usr/bin/env python3
#
# Copyright (C) 2019-2021 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import os
import argparse
import logging
import psutil

from logging.handlers import SysLogHandler
from shutil import rmtree

from vyos.base import Warning
from vyos.util import call
from vyos.util import ask_yes_no
from vyos.util import process_named_running
from vyos.util import makedir

# some default values
watchfrr = '/usr/lib/frr/watchfrr.sh'
vtysh = '/usr/bin/vtysh'
frrconfig_tmp = '/tmp/frr_restart'

# configure logging
logger = logging.getLogger(__name__)
logs_handler = SysLogHandler('/dev/log')
logs_handler.setFormatter(logging.Formatter('%(filename)s: %(message)s'))
logger.addHandler(logs_handler)
logger.setLevel(logging.INFO)

# check if it is safe to restart FRR
def _check_safety():
    try:
        # print warning
        if not ask_yes_no('WARNING: This is a potentially unsafe function!\n' \
                          'You may lose the connection to the router or active configuration after\n' \
                          'running this command. Use it at your own risk!\n\n'
                          'Continue?'):
            return False

        # 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:
            message = 'Another restart_frr.py process is already running!'
            logger.error(message)
            if not ask_yes_no(f'\n{message} It is unsafe to continue.\n\n' \
                              'Do you want to process anyway?'):
                return False

        # check if watchfrr.sh is running
        tmp = os.path.basename(watchfrr)
        if process_named_running(tmp):
            message = f'Another {tmp} process is already running.'
            logger.error(message)
            if not ask_yes_no(f'{message} It is unsafe to continue.\n\n' \
                              'Do you want to process anyway?'):
                return False

        # check if vtysh is running
        if process_named_running('vtysh'):
            message = 'vtysh process is executed by another task.'
            logger.error(message)
            if not ask_yes_no(f'{message} It is unsafe to continue.\n\n' \
                              'Do you want to process anyway?'):
                return False

        # check if temporary directory exists
        if os.path.exists(frrconfig_tmp):
            message = f'Temporary directory "{frrconfig_tmp}" already exists!'
            logger.error(message)
            if not ask_yes_no(f'{message} It is unsafe to continue.\n\n' \
                              'Do you want to process anyway?'):
                return False

    except:
        logger.error("Something goes wrong in _check_safety()")
        return False

    # return True if all check was passed or user confirmed to ignore they results
    return True

# write active config to file
def _write_config():
    # create temporary directory
    makedir(frrconfig_tmp)
    # save frr.conf to it
    command = f'{vtysh} -n -w --config_dir {frrconfig_tmp} 2> /dev/null'
    return_code = call(command)
    if return_code != 0:
        logger.error(f'Failed to save active config: "{command}" returned exit code: {return_code}')
        return False
    logger.info(f'Active config saved to {frrconfig_tmp}')
    return True

# clear and remove temporary directory
def _cleanup():
    if os.path.isdir(frrconfig_tmp):
        rmtree(frrconfig_tmp)

# restart daemon
def _daemon_restart(daemon):
    command = f'{watchfrr} restart {daemon}'
    return_code = call(command)
    if not return_code == 0:
        logger.error(f'Failed to restart daemon "{daemon}"!')
        return False

    # return True if restarted successfully
    logger.info(f'Daemon "{daemon}" restarted!')
    return True

# reload old config
def _reload_config(daemon):
    if daemon != '':
        command = f'{vtysh} -n -b --config_dir {frrconfig_tmp} -d {daemon} 2> /dev/null'
    else:
        command = f'{vtysh} -n -b --config_dir {frrconfig_tmp} 2> /dev/null'

    return_code = call(command)
    if not return_code == 0:
        logger.error('Failed to re-install configuration!')
        return False

    # return True if restarted successfully
    logger.info('Configuration re-installed successfully!')
    return True

# define program arguments
cmd_args_parser = argparse.ArgumentParser(description='restart frr daemons')
cmd_args_parser.add_argument('--action', choices=['restart'], required=True, help='action to frr daemons')
cmd_args_parser.add_argument('--daemon', choices=['bfdd', 'bgpd', 'ldpd', 'ospfd', 'ospf6d', 'isisd', 'ripd', 'ripngd', 'staticd', 'zebra', 'babeld'], required=False,  nargs='*', help='select single or multiple daemons')
# parse arguments
cmd_args = cmd_args_parser.parse_args()

# main logic
# restart daemon
if cmd_args.action == 'restart':
    # 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.")
        exit(1)

    if not _write_config():
        print("Failed to save active config")
        _cleanup()
        exit(1)

    # 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 != ['']:
        for daemon in cmd_args.daemon:
            if not process_named_running(daemon):
                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: {daemon}')
            _cleanup()
            exit(1)
        # reinstall old configuration
        _reload_config(daemon)

    # cleanup after all actions
    _cleanup()

exit(0)