summaryrefslogtreecommitdiff
path: root/src/op_mode/restart_frr.py
blob: 5cce377ebd04c8216bcb1b40781ee2226dd2baa1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
#!/usr/bin/env python3
#
# Copyright (C) 2019-2023 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.utils.io import ask_yes_no
from vyos.utils.file import makedir
from vyos.utils.process import call
from vyos.utils.process import process_named_running

# 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)