summaryrefslogtreecommitdiff
path: root/src/op_mode/restart_frr.py
blob: d1b66b33f3158769f3ca18db24a2607413629121 (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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
#!/usr/bin/env python3
#
# Copyright (C) 2019 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 sys
import argparse
import logging
from logging.handlers import SysLogHandler
from pathlib import Path
import psutil

from vyos.util import call

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

        # 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

    # 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
    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 = call(command)
    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_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 = "{} print_status {}".format(watchfrr, daemon)
    return_code = call(command)
    if not return_code == 0:
        logger.error("Daemon \"{}\" is not running".format(daemon))
        return False

    # return True if all checks were passed
    return True

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

    # 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 = call(command)
    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:
        if not _daemon_check(daemon):
            return False
    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', 'ospfd', 'ospf6d', 'ripd', 'ripngd', 'staticd', 'zebra'], 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.")
        sys.exit(1)

    if not _write_config():
        print("Failed to save active config")
        _cleanup()
        sys.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 != ['']:
        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))
            _cleanup()
            sys.exit(1)
        # reinstall old configuration
        _reload_config(daemon)

    # cleanup after all actions
    _cleanup()

sys.exit(0)