diff options
Diffstat (limited to 'tests/topotests/lib/common_config.py')
| -rw-r--r-- | tests/topotests/lib/common_config.py | 345 |
1 files changed, 190 insertions, 155 deletions
diff --git a/tests/topotests/lib/common_config.py b/tests/topotests/lib/common_config.py index 9e38608631..22a678862a 100644 --- a/tests/topotests/lib/common_config.py +++ b/tests/topotests/lib/common_config.py @@ -22,18 +22,16 @@ from collections import OrderedDict from datetime import datetime, timedelta from time import sleep from copy import deepcopy -from subprocess import call -from subprocess import STDOUT as SUB_STDOUT -from subprocess import PIPE as SUB_PIPE -from subprocess import Popen from functools import wraps from re import search as re_search from tempfile import mkdtemp +import json import os import sys import traceback import socket +import subprocess import ipaddress import platform @@ -235,14 +233,12 @@ def run_frr_cmd(rnode, cmd, isjson=False): if True: if isjson: - logger.debug(ret_data) - print_data = rnode.vtysh_cmd(cmd.rstrip("json"), isjson=False) + print_data = json.dumps(ret_data) else: print_data = ret_data - logger.info( - "Output for command [ %s] on router %s:\n%s", - cmd.rstrip("json"), + "Output for command [%s] on router %s:\n%s", + cmd, rnode.name, print_data, ) @@ -365,7 +361,7 @@ def create_common_configuration( return True -def kill_router_daemons(tgen, router, daemons): +def kill_router_daemons(tgen, router, daemons, save_config=True): """ Router's current config would be saved to /etc/frr/ for each daemon and daemon would be killed forcefully using SIGKILL. @@ -379,9 +375,10 @@ def kill_router_daemons(tgen, router, daemons): try: router_list = tgen.routers() - # Saving router config to /etc/frr, which will be loaded to router - # when it starts - router_list[router].vtysh_cmd("write memory") + if save_config: + # Saving router config to /etc/frr, which will be loaded to router + # when it starts + router_list[router].vtysh_cmd("write memory") # Kill Daemons result = router_list[router].killDaemons(daemons) @@ -469,111 +466,114 @@ def reset_config_on_routers(tgen, routerName=None): logger.debug("Entering API: reset_config_on_routers") + # Trim the router list if needed router_list = tgen.routers() - for rname in ROUTER_LIST: - if routerName and routerName != rname: - continue - - router = router_list[rname] - logger.info("Configuring router %s to initial test configuration", rname) - - cfg = router.run("vtysh -c 'show running'") - fname = "{}/{}/frr.sav".format(TMPDIR, rname) - dname = "{}/{}/delta.conf".format(TMPDIR, rname) - f = open(fname, "w") - for line in cfg.split("\n"): - line = line.strip() - - if ( - line == "Building configuration..." - or line == "Current configuration:" - or not line - ): - continue - f.write(line) - f.write("\n") - - f.close() - run_cfg_file = "{}/{}/frr.sav".format(TMPDIR, rname) - init_cfg_file = "{}/{}/frr_json_initial.conf".format(TMPDIR, rname) - command = "/usr/lib/frr/frr-reload.py --input {} --test {} > {}".format( - run_cfg_file, init_cfg_file, dname + if routerName: + if ((routerName not in ROUTER_LIST) or (routerName not in router_list)): + logger.debug("Exiting API: reset_config_on_routers: no routers") + return True + router_list = { routerName: router_list[routerName] } + + delta_fmt = TMPDIR + "/{}/delta.conf" + init_cfg_fmt = TMPDIR + "/{}/frr_json_initial.conf" + run_cfg_fmt = TMPDIR + "/{}/frr.sav" + + # + # Get all running configs in parallel + # + procs = {} + for rname in router_list: + logger.info("Fetching running config for router %s", rname) + procs[rname] = router_list[rname].popen( + ["/usr/bin/env", "vtysh", "-c", "show running-config no-header"], + stdin=None, + stdout=open(run_cfg_fmt.format(rname), "w"), + stderr=subprocess.PIPE, ) - result = call(command, shell=True, stderr=SUB_STDOUT, stdout=SUB_PIPE) - - # Assert if command fail - if result > 0: - logger.error("Delta file creation failed. Command executed %s", command) - with open(run_cfg_file, "r") as fd: - logger.info( - "Running configuration saved in %s is:\n%s", run_cfg_file, fd.read() - ) - with open(init_cfg_file, "r") as fd: - logger.info( - "Test configuration saved in %s is:\n%s", init_cfg_file, fd.read() + for rname, p in procs.items(): + _, error = p.communicate() + if p.returncode: + logger.error("Get running config for %s failed %d: %s", rname, p.returncode, error) + raise InvalidCLIError("vtysh show running error on {}: {}".format(rname, error)) + + # + # Get all delta's in parallel + # + procs = {} + for rname in router_list: + logger.info("Generating delta for router %s to new configuration", rname) + procs[rname] = subprocess.Popen( + [ "/usr/lib/frr/frr-reload.py", + "--test-reset", + "--input", + run_cfg_fmt.format(rname), + "--test", + init_cfg_fmt.format(rname) ], + stdin=None, + stdout=open(delta_fmt.format(rname), "w"), + stderr=subprocess.PIPE, + ) + for rname, p in procs.items(): + _, error = p.communicate() + if p.returncode: + logger.error("Delta file creation for %s failed %d: %s", rname, p.returncode, error) + raise InvalidCLIError("frr-reload error for {}: {}".format(rname, error)) + + # + # Apply all the deltas in parallel + # + procs = {} + for rname in router_list: + logger.info("Applying delta config on router %s", rname) + + procs[rname] = router_list[rname].popen( + ["/usr/bin/env", "vtysh", "-f", delta_fmt.format(rname)], + stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + for rname, p in procs.items(): + output, _ = p.communicate() + vtysh_command = "vtysh -f {}".format(delta_fmt.format(rname)) + if not p.returncode: + router_list[rname].logger.info( + '\nvtysh config apply => "{}"\nvtysh output <= "{}"'.format(vtysh_command, output) + ) + else: + router_list[rname].logger.error( + '\nvtysh config apply => "{}"\nvtysh output <= "{}"'.format(vtysh_command, output) + ) + logger.error("Delta file apply for %s failed %d: %s", rname, p.returncode, output) + + # We really need to enable this failure; however, currently frr-reload.py + # producing invalid "no" commands as it just preprends "no", but some of the + # command forms lack matching values (e.g., final values). Until frr-reload + # is fixed to handle this (or all the CLI no forms are adjusted) we can't + # fail tests. + # raise InvalidCLIError("frr-reload error for {}: {}".format(rname, output)) + + # + # Optionally log all new running config if "show_router_config" is defined in + # "pytest.ini" + # + if show_router_config: + procs = {} + for rname in router_list: + logger.info("Fetching running config for router %s", rname) + procs[rname] = router_list[rname].popen( + ["/usr/bin/env", "vtysh", "-c", "show running-config no-header"], + stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + for rname, p in procs.items(): + output, _ = p.communicate() + if p.returncode: + logger.warning( + "Get running config for %s failed %d: %s", rname, p.returncode, output ) - - err_cmd = ["/usr/bin/vtysh", "-m", "-f", run_cfg_file] - result = Popen(err_cmd, stdout=SUB_PIPE, stderr=SUB_PIPE) - output = result.communicate() - for out_data in output: - temp_data = out_data.decode("utf-8").lower() - for out_err in ERROR_LIST: - if out_err.lower() in temp_data: - logger.error( - "Found errors while validating data in" " %s", run_cfg_file - ) - raise InvalidCLIError(out_data) - raise InvalidCLIError("Unknown error in %s", output) - - f = open(dname, "r") - delta = StringIO() - delta.write("configure terminal\n") - t_delta = f.read() - - # Don't disable debugs - check_debug = True - - for line in t_delta.split("\n"): - line = line.strip() - if line == "Lines To Delete" or line == "===============" or not line: - continue - - if line == "Lines To Add": - check_debug = False - continue - - if line == "============" or not line: - continue - - # Leave debugs and log output alone - if check_debug: - if "debug" in line or "log file" in line: - continue - - delta.write(line) - delta.write("\n") - - f.close() - - delta.write("end\n") - - output = router.vtysh_multicmd(delta.getvalue(), pretty_output=False) - - delta.close() - delta = StringIO() - cfg = router.run("vtysh -c 'show running'") - for line in cfg.split("\n"): - line = line.strip() - delta.write(line) - delta.write("\n") - - # Router current configuration to log file or console if - # "show_router_config" is defined in "pytest.ini" - if show_router_config: - logger.info("Configuration on router {} after reset:".format(rname)) - logger.info(delta.getvalue()) - delta.close() + else: + logger.info("Configuration on router {} after reset:\n{}".format(rname, output)) logger.debug("Exiting API: reset_config_on_routers") return True @@ -636,6 +636,7 @@ def load_config_to_router(tgen, routerName, save_bkup=False): return True + def get_frr_ipv6_linklocal(tgen, router, intf=None, vrf=None): """ API to get the link local ipv6 address of a particular interface using @@ -668,38 +669,48 @@ def get_frr_ipv6_linklocal(tgen, router, intf=None, vrf=None): else: cmd = "show interface" - ifaces = router_list[router].run('vtysh -c "{}"'.format(cmd)) - - # Fix newlines (make them all the same) - ifaces = ("\n".join(ifaces.splitlines()) + "\n").splitlines() - - interface = None - ll_per_if_count = 0 - for line in ifaces: - # Interface name - m = re_search("Interface ([a-zA-Z0-9-]+) is", line) - if m: - interface = m.group(1).split(" ")[0] - ll_per_if_count = 0 - - # Interface ip - m1 = re_search("inet6 (fe80[:a-fA-F0-9]+[/0-9]+)", line) - if m1: - local = m1.group(1) - ll_per_if_count += 1 - if ll_per_if_count > 1: - linklocal += [["%s-%s" % (interface, ll_per_if_count), local]] - else: - linklocal += [[interface, local]] - - if linklocal: - if intf: - return [_linklocal[1] for _linklocal in linklocal if _linklocal[0] == intf][ - 0 - ].split("/")[0] - return linklocal - else: - errormsg = "Link local ip missing on router {}" + linklocal = [] + if vrf: + cmd = "show interface vrf {}".format(vrf) + else: + cmd = "show interface" + for chk_ll in range(0, 60): + sleep(1/4) + ifaces = router_list[router].run('vtysh -c "{}"'.format(cmd)) + # Fix newlines (make them all the same) + ifaces = ('\n'.join(ifaces.splitlines()) + '\n').splitlines() + + interface = None + ll_per_if_count = 0 + for line in ifaces: + # Interface name + m = re_search('Interface ([a-zA-Z0-9-]+) is', line) + if m: + interface = m.group(1).split(" ")[0] + ll_per_if_count = 0 + + # Interface ip + m1 = re_search('inet6 (fe80[:a-fA-F0-9]+[\/0-9]+)', + line) + if m1: + local = m1.group(1) + ll_per_if_count += 1 + if ll_per_if_count > 1: + linklocal += [["%s-%s" % + (interface, ll_per_if_count), local]] + else: + linklocal += [[interface, local]] + + try: + if linklocal: + if intf: + return [_linklocal[1] for _linklocal in linklocal if _linklocal[0]==intf][0].\ + split("/")[0] + return linklocal + except IndexError: + continue + + errormsg = "Link local ip missing on router {}".format(router) return errormsg @@ -712,20 +723,36 @@ def generate_support_bundle(): tgen = get_topogen() router_list = tgen.routers() - test_name = sys._getframe(2).f_code.co_name + test_name = os.environ.get('PYTEST_CURRENT_TEST').split(':')[-1].split(' ')[0] + TMPDIR = os.path.join(LOGDIR, tgen.modname) + bundle_procs = {} for rname, rnode in router_list.items(): - logger.info("Generating support bundle for {}".format(rname)) + logger.info("Spawn collection of support bundle for %s", rname) rnode.run("mkdir -p /var/log/frr") + bundle_procs[rname] = tgen.net[rname].popen( + "/usr/lib/frr/generate_support_bundle.py", + stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) - # Support only python3 going forward - bundle_log = rnode.run("env python3 /usr/lib/frr/generate_support_bundle.py") - - logger.info(bundle_log) - + for rname, rnode in router_list.items(): dst_bundle = "{}/{}/support_bundles/{}".format(TMPDIR, rname, test_name) src_bundle = "/var/log/frr" + + output, error = bundle_procs[rname].communicate() + + logger.info("Saving support bundle for %s", rname) + if output: + logger.info( + "Output from collecting support bundle for %s:\n%s", rname, output + ) + if error: + logger.warning( + "Error from collecting support bundle for %s:\n%s", rname, error + ) rnode.run("rm -rf {}".format(dst_bundle)) rnode.run("mkdir -p {}".format(dst_bundle)) rnode.run("mv -f {}/* {}".format(src_bundle, dst_bundle)) @@ -1829,6 +1856,14 @@ def create_interfaces_cfg(tgen, topo, build=False): else: interface_data.append("ipv6 address {}".format(intf_addr)) + # Wait for vrf interfaces to get link local address once they are up + if not destRouterLink == 'lo' and 'vrf' in topo[c_router][ + 'links'][destRouterLink]: + vrf = topo[c_router]['links'][destRouterLink]['vrf'] + intf = topo[c_router]['links'][destRouterLink]['interface'] + ll = get_frr_ipv6_linklocal(tgen, c_router, intf=intf, + vrf = vrf) + if "ipv6-link-local" in data: intf_addr = c_data["links"][destRouterLink]["ipv6-link-local"] @@ -1851,7 +1886,7 @@ def create_interfaces_cfg(tgen, topo, build=False): ) if "ospf6" in data: interface_data += _create_interfaces_ospf_cfg( - "ospf6", c_data, data, ospf_keywords + "ospf6", c_data, data, ospf_keywords + ["area"] ) result = create_common_configuration( |
