Merge pull request #5224 from manuhalo/fix_frr_reload_paths

Fixes and extensions to frr_reload.py
This commit is contained in:
Quentin Young 2019-11-19 17:12:38 -05:00 committed by GitHub
commit 1d92edb209
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 79 additions and 31 deletions

View File

@ -414,9 +414,9 @@ struct cmd_node {
/* Daemons lists */ /* Daemons lists */
#define DAEMONS_STR \ #define DAEMONS_STR \
"For the zebra daemon\nFor the rip daemon\nFor the ripng daemon\nFor the ospf daemon\nFor the ospfv6 daemon\nFor the bgp daemon\nFor the isis daemon\nFor the pbr daemon\nFor the fabricd daemon\nFor the pim daemon\nFor the static daemon\nFor the sharpd daemon\nFor the vrrpd daemon\n" "For the zebra daemon\nFor the rip daemon\nFor the ripng daemon\nFor the ospf daemon\nFor the ospfv6 daemon\nFor the bgp daemon\nFor the isis daemon\nFor the pbr daemon\nFor the fabricd daemon\nFor the pim daemon\nFor the static daemon\nFor the sharpd daemon\nFor the vrrpd daemon\nFor the ldpd daemon\n"
#define DAEMONS_LIST \ #define DAEMONS_LIST \
"<zebra|ripd|ripngd|ospfd|ospf6d|bgpd|isisd|pbrd|fabricd|pimd|staticd|sharpd|vrrpd>" "<zebra|ripd|ripngd|ospfd|ospf6d|bgpd|isisd|pbrd|fabricd|pimd|staticd|sharpd|vrrpd|ldpd>"
/* Prototypes. */ /* Prototypes. */
extern void install_node(struct cmd_node *, int (*)(struct vty *)); extern void install_node(struct cmd_node *, int (*)(struct vty *));

View File

@ -114,7 +114,7 @@ class Config(object):
self.lines = [] self.lines = []
self.contexts = OrderedDict() self.contexts = OrderedDict()
def load_from_file(self, filename): def load_from_file(self, filename, bindir, confdir):
""" """
Read configuration from specified file and slurp it into internal memory Read configuration from specified file and slurp it into internal memory
The internal representation has been marked appropriately by passing it The internal representation has been marked appropriately by passing it
@ -123,7 +123,7 @@ class Config(object):
log.info('Loading Config object from file %s', filename) log.info('Loading Config object from file %s', filename)
try: try:
file_output = subprocess.check_output(['/usr/bin/vtysh', '-m', '-f', filename], file_output = subprocess.check_output([str(bindir + '/vtysh'), '-m', '-f', filename, '--config_dir', confdir],
stderr=subprocess.STDOUT) stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
ve = VtyshMarkException(e) ve = VtyshMarkException(e)
@ -144,7 +144,7 @@ class Config(object):
self.load_contexts() self.load_contexts()
def load_from_show_running(self): def load_from_show_running(self, bindir, confdir, daemon):
""" """
Read running configuration and slurp it into internal memory Read running configuration and slurp it into internal memory
The internal representation has been marked appropriately by passing it The internal representation has been marked appropriately by passing it
@ -154,7 +154,7 @@ class Config(object):
try: try:
config_text = subprocess.check_output( config_text = subprocess.check_output(
"/usr/bin/vtysh -c 'show run' | /usr/bin/tail -n +4 | /usr/bin/vtysh -m -f -", bindir + "/vtysh --config_dir " + confdir + " -c 'show run " + daemon + "' | /usr/bin/tail -n +4 | " + bindir + "/vtysh --config_dir " + confdir + " -m -f -",
shell=True, stderr=subprocess.STDOUT) shell=True, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
ve = VtyshMarkException(e) ve = VtyshMarkException(e)
@ -404,7 +404,8 @@ end
"ip ", "ip ",
"ipv6 ", "ipv6 ",
"log ", "log ",
"mpls", "mpls lsp",
"mpls label",
"no ", "no ",
"password ", "password ",
"ptm-enable", "ptm-enable",
@ -424,7 +425,12 @@ end
continue continue
# one line contexts # one line contexts
if new_ctx is True and any(line.startswith(keyword) for keyword in oneline_ctx_keywords): # there is one exception though: ldpd accepts a 'router-id' clause
# as part of its 'mpls ldp' config context. If we are processing
# ldp configuration and encounter a router-id we should NOT switch
# to a new context
if new_ctx is True and any(line.startswith(keyword) for keyword in oneline_ctx_keywords) and not (
ctx_keys and ctx_keys[0].startswith("mpls ldp") and line.startswith("router-id ")):
self.save_contexts(ctx_keys, current_context_lines) self.save_contexts(ctx_keys, current_context_lines)
# Start a new context # Start a new context
@ -467,7 +473,7 @@ end
current_context_lines = [] current_context_lines = []
log.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 line == "exit-vni": elif line in ["exit-vni", "exit-ldp-if"]:
if sub_main_ctx_key: if sub_main_ctx_key:
self.save_contexts(ctx_keys, current_context_lines) self.save_contexts(ctx_keys, current_context_lines)
@ -489,7 +495,8 @@ end
elif (line.startswith("address-family ") or elif (line.startswith("address-family ") or
line.startswith("vnc defaults") or line.startswith("vnc defaults") or
line.startswith("vnc l2-group") or line.startswith("vnc l2-group") or
line.startswith("vnc nve-group")): line.startswith("vnc nve-group") or
line.startswith("member pseudowire")):
main_ctx_key = [] main_ctx_key = []
# Save old context first # Save old context first
@ -498,9 +505,9 @@ end
main_ctx_key = copy.deepcopy(ctx_keys) main_ctx_key = copy.deepcopy(ctx_keys)
log.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": if line == "address-family ipv6" and not ctx_keys[0].startswith("mpls ldp"):
ctx_keys.append("address-family ipv6 unicast") ctx_keys.append("address-family ipv6 unicast")
elif line == "address-family ipv4": elif line == "address-family ipv4" and not ctx_keys[0].startswith("mpls ldp"):
ctx_keys.append("address-family ipv4 unicast") ctx_keys.append("address-family ipv4 unicast")
elif line == "address-family evpn": elif line == "address-family evpn":
ctx_keys.append("address-family l2vpn evpn") ctx_keys.append("address-family l2vpn evpn")
@ -518,6 +525,18 @@ end
sub_main_ctx_key = copy.deepcopy(ctx_keys) sub_main_ctx_key = copy.deepcopy(ctx_keys)
log.debug('LINE %-50s: entering sub-sub-context, append to ctx_keys', line) log.debug('LINE %-50s: entering sub-sub-context, append to ctx_keys', line)
ctx_keys.append(line) ctx_keys.append(line)
elif ((line.startswith("interface ") and
len(ctx_keys) == 2 and
ctx_keys[0].startswith('mpls ldp') and
ctx_keys[1].startswith('address-family'))):
# Save old context first
self.save_contexts(ctx_keys, current_context_lines)
current_context_lines = []
sub_main_ctx_key = copy.deepcopy(ctx_keys)
log.debug('LINE %-50s: entering sub-sub-context, append to ctx_keys', line)
ctx_keys.append(line)
else: else:
# Continuing in an existing context, add non-commented lines to it # Continuing in an existing context, add non-commented lines to it
@ -528,13 +547,15 @@ end
self.save_contexts(ctx_keys, current_context_lines) self.save_contexts(ctx_keys, current_context_lines)
def line_to_vtysh_conft(ctx_keys, line, delete): def line_to_vtysh_conft(ctx_keys, line, delete, bindir, confdir):
""" """
Return the vtysh command for the specified context line Return the vtysh command for the specified context line
""" """
cmd = [] cmd = []
cmd.append('vtysh') cmd.append(str(bindir + '/vtysh'))
cmd.append('--config_dir')
cmd.append(confdir)
cmd.append('-c') cmd.append('-c')
cmd.append('conf t') cmd.append('conf t')
@ -1075,7 +1096,7 @@ def compare_context_objects(newconf, running):
def vtysh_config_available(): def vtysh_config_available(bindir, confdir):
""" """
Return False if no frr daemon is running or some other vtysh session is Return False if no frr daemon is running or some other vtysh session is
in 'configuration terminal' mode which will prevent us from making any in 'configuration terminal' mode which will prevent us from making any
@ -1083,7 +1104,7 @@ def vtysh_config_available():
""" """
try: try:
cmd = ['/usr/bin/vtysh', '-c', 'conf t'] cmd = [str(bindir + '/vtysh'), '--config_dir', confdir, '-c', 'conf t']
output = subprocess.check_output(cmd, stderr=subprocess.STDOUT).strip() output = subprocess.check_output(cmd, stderr=subprocess.STDOUT).strip()
if 'VTY configuration is locked by other VTY' in output.decode('utf-8'): if 'VTY configuration is locked by other VTY' in output.decode('utf-8'):
@ -1111,6 +1132,11 @@ if __name__ == '__main__':
parser.add_argument('--stdout', action='store_true', help='Log to STDOUT', default=False) parser.add_argument('--stdout', action='store_true', help='Log to STDOUT', default=False)
parser.add_argument('filename', help='Location of new frr config file') parser.add_argument('filename', help='Location of new frr config file')
parser.add_argument('--overwrite', action='store_true', help='Overwrite frr.conf with running config output', default=False) parser.add_argument('--overwrite', action='store_true', help='Overwrite frr.conf with running config output', default=False)
parser.add_argument('--bindir', help='path to the vtysh executable', default='/usr/bin')
parser.add_argument('--confdir', help='path to the daemon config files', default='/etc/frr')
parser.add_argument('--rundir', help='path for the temp config file', default='/var/run/frr')
parser.add_argument('--daemon', help='daemon for which want to replace the config', default='')
args = parser.parse_args() args = parser.parse_args()
# Logging # Logging
@ -1150,8 +1176,29 @@ if __name__ == '__main__':
log.error(msg) log.error(msg)
sys.exit(1) sys.exit(1)
# Verify that confdir is correct
if not os.path.isdir(args.confdir):
msg = "Confdir %s is not a valid path" % args.confdir
print(msg)
log.error(msg)
sys.exit(1)
# Verify that bindir is correct
if not os.path.isdir(args.bindir) or not os.path.isfile(args.bindir + '/vtysh'):
msg = "Bindir %s is not a valid path to vtysh" % args.bindir
print(msg)
log.error(msg)
sys.exit(1)
# verify that the daemon, if specified, is valid
if args.daemon and args.daemon not in ['zebra', 'bgpd', 'fabricd', 'isisd', 'ospf6d', 'ospfd', 'pbrd', 'pimd', 'ripd', 'ripngd', 'sharpd', 'staticd', 'vrrpd', 'ldpd']:
msg = "Daemon %s is not a valid option for 'show running-config'" % args.daemon
print(msg)
log.error(msg)
sys.exit(1)
# Verify that 'service integrated-vtysh-config' is configured # Verify that 'service integrated-vtysh-config' is configured
vtysh_filename = '/etc/frr/vtysh.conf' vtysh_filename = args.confdir + '/vtysh.conf'
service_integrated_vtysh_config = True service_integrated_vtysh_config = True
if os.path.isfile(vtysh_filename): if os.path.isfile(vtysh_filename):
@ -1163,7 +1210,7 @@ if __name__ == '__main__':
service_integrated_vtysh_config = False service_integrated_vtysh_config = False
break break
if not service_integrated_vtysh_config: if not service_integrated_vtysh_config and not args.daemon:
msg = "'service integrated-vtysh-config' is not configured, this is required for 'service frr reload'" msg = "'service integrated-vtysh-config' is not configured, this is required for 'service frr reload'"
print(msg) print(msg)
log.error(msg) log.error(msg)
@ -1176,7 +1223,7 @@ if __name__ == '__main__':
# Create a Config object from the config generated by newconf # Create a Config object from the config generated by newconf
newconf = Config() newconf = Config()
newconf.load_from_file(args.filename) newconf.load_from_file(args.filename, args.bindir, args.confdir)
reload_ok = True reload_ok = True
if args.test: if args.test:
@ -1185,9 +1232,9 @@ if __name__ == '__main__':
running = Config() running = Config()
if args.input: if args.input:
running.load_from_file(args.input) running.load_from_file(args.input, args.bindir, args.confdir)
else: else:
running.load_from_show_running() running.load_from_show_running(args.bindir, args.confdir, args.daemon)
(lines_to_add, lines_to_del) = compare_context_objects(newconf, running) (lines_to_add, lines_to_del) = compare_context_objects(newconf, running)
lines_to_configure = [] lines_to_configure = []
@ -1221,7 +1268,7 @@ if __name__ == '__main__':
elif args.reload: elif args.reload:
# We will not be able to do anything, go ahead and exit(1) # We will not be able to do anything, go ahead and exit(1)
if not vtysh_config_available(): if not vtysh_config_available(args.bindir, args.confdir):
sys.exit(1) sys.exit(1)
log.debug('New Frr Config\n%s', newconf.get_lines()) log.debug('New Frr Config\n%s', newconf.get_lines())
@ -1265,7 +1312,7 @@ if __name__ == '__main__':
for x in range(2): for x in range(2):
running = Config() running = Config()
running.load_from_show_running() running.load_from_show_running(args.bindir, args.confdir, args.daemon)
log.debug('Running Frr Config (Pass #%d)\n%s', x, running.get_lines()) log.debug('Running Frr Config (Pass #%d)\n%s', x, running.get_lines())
(lines_to_add, lines_to_del) = compare_context_objects(newconf, running) (lines_to_add, lines_to_del) = compare_context_objects(newconf, running)
@ -1297,7 +1344,7 @@ if __name__ == '__main__':
# 'no' commands are tricky, we can't just put them in a file and # 'no' commands are tricky, we can't just put them in a file and
# vtysh -f that file. See the next comment for an explanation # vtysh -f that file. See the next comment for an explanation
# of their quirks # of their quirks
cmd = line_to_vtysh_conft(ctx_keys, line, True) cmd = line_to_vtysh_conft(ctx_keys, line, True, args.bindir, args.confdir)
original_cmd = cmd original_cmd = cmd
# Some commands in frr are picky about taking a "no" of the entire line. # Some commands in frr are picky about taking a "no" of the entire line.
@ -1353,7 +1400,7 @@ if __name__ == '__main__':
string.ascii_uppercase + string.ascii_uppercase +
string.digits) for _ in range(6)) string.digits) for _ in range(6))
filename = "/var/run/frr/reload-%s.txt" % random_string filename = args.rundir + "/reload-%s.txt" % random_string
log.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: with open(filename, 'w') as fh:
@ -1361,15 +1408,16 @@ if __name__ == '__main__':
fh.write(line + '\n') fh.write(line + '\n')
try: try:
subprocess.check_output(['/usr/bin/vtysh', '-f', filename], stderr=subprocess.STDOUT) subprocess.check_output([str(args.bindir + '/vtysh'), '--config_dir', args.confdir, '-f', filename], stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
log.warning("frr-reload.py failed due to\n%s" % e.output) log.warning("frr-reload.py failed due to\n%s" % e.output)
reload_ok = False reload_ok = False
os.unlink(filename) os.unlink(filename)
# Make these changes persistent # Make these changes persistent
if args.overwrite or args.filename != '/etc/frr/frr.conf': target = str(args.confdir + '/frr.conf')
subprocess.call(['/usr/bin/vtysh', '-c', 'write']) if args.overwrite or (not args.daemon and args.filename != target):
subprocess.call([str(args.bindir + '/vtysh'), '--config_dir', args.confdir, '-c', 'write'])
if not reload_ok: if not reload_ok:
sys.exit(1) sys.exit(1)

View File

@ -725,19 +725,19 @@ int vtysh_mark_file(const char *filename)
switch (vty->node) { switch (vty->node) {
case LDP_IPV4_IFACE_NODE: case LDP_IPV4_IFACE_NODE:
if (strncmp(vty_buf_copy, " ", 3)) { if (strncmp(vty_buf_copy, " ", 3)) {
vty_out(vty, " end\n"); vty_out(vty, " exit-ldp-if\n");
vty->node = LDP_IPV4_NODE; vty->node = LDP_IPV4_NODE;
} }
break; break;
case LDP_IPV6_IFACE_NODE: case LDP_IPV6_IFACE_NODE:
if (strncmp(vty_buf_copy, " ", 3)) { if (strncmp(vty_buf_copy, " ", 3)) {
vty_out(vty, " end\n"); vty_out(vty, " exit-ldp-if\n");
vty->node = LDP_IPV6_NODE; vty->node = LDP_IPV6_NODE;
} }
break; break;
case LDP_PSEUDOWIRE_NODE: case LDP_PSEUDOWIRE_NODE:
if (strncmp(vty_buf_copy, " ", 2)) { if (strncmp(vty_buf_copy, " ", 2)) {
vty_out(vty, " end\n"); vty_out(vty, " exit\n");
vty->node = LDP_L2VPN_NODE; vty->node = LDP_L2VPN_NODE;
} }
break; break;