diff options
Diffstat (limited to 'tests/topotests/lib/topotest.py')
-rw-r--r-- | tests/topotests/lib/topotest.py | 226 |
1 files changed, 210 insertions, 16 deletions
diff --git a/tests/topotests/lib/topotest.py b/tests/topotests/lib/topotest.py index c220bcf..2bb8923 100644 --- a/tests/topotests/lib/topotest.py +++ b/tests/topotests/lib/topotest.py @@ -31,7 +31,8 @@ from copy import deepcopy import lib.topolog as topolog from lib.micronet_compat import Node from lib.topolog import logger -from munet.base import Timeout +from munet.base import commander, get_exec_path_host, Timeout +from munet.testing.util import retry from lib import micronet @@ -1261,8 +1262,8 @@ def rlimit_atleast(rname, min_value, raises=False): def fix_netns_limits(ns): # Maximum read and write socket buffer sizes - sysctl_atleast(ns, "net.ipv4.tcp_rmem", [10 * 1024, 87380, 16 * 2 ** 20]) - sysctl_atleast(ns, "net.ipv4.tcp_wmem", [10 * 1024, 87380, 16 * 2 ** 20]) + sysctl_atleast(ns, "net.ipv4.tcp_rmem", [10 * 1024, 87380, 16 * 2**20]) + sysctl_atleast(ns, "net.ipv4.tcp_wmem", [10 * 1024, 87380, 16 * 2**20]) sysctl_assure(ns, "net.ipv4.conf.all.rp_filter", 0) sysctl_assure(ns, "net.ipv4.conf.default.rp_filter", 0) @@ -1321,8 +1322,8 @@ def fix_host_limits(): sysctl_atleast(None, "net.core.netdev_max_backlog", 4 * 1024) # Maximum read and write socket buffer sizes - sysctl_atleast(None, "net.core.rmem_max", 16 * 2 ** 20) - sysctl_atleast(None, "net.core.wmem_max", 16 * 2 ** 20) + sysctl_atleast(None, "net.core.rmem_max", 16 * 2**20) + sysctl_atleast(None, "net.core.wmem_max", 16 * 2**20) # Garbage Collection Settings for ARP and Neighbors sysctl_atleast(None, "net.ipv4.neigh.default.gc_thresh2", 4 * 1024) @@ -1363,6 +1364,8 @@ def setup_node_tmpdir(logdir, name): class Router(Node): "A Node with IPv4/IPv6 forwarding enabled" + gdb_emacs_router = None + def __init__(self, name, *posargs, **params): # Backward compatibility: # Load configuration defaults like topogen. @@ -1380,6 +1383,8 @@ class Router(Node): ) self.perf_daemons = {} + self.rr_daemons = {} + self.valgrind_gdb_daemons = {} # If this topology is using old API and doesn't have logdir # specified, then attempt to generate an unique logdir. @@ -1420,6 +1425,7 @@ class Router(Node): "pathd": 0, "snmpd": 0, "mgmtd": 0, + "snmptrapd": 0, } self.daemons_options = {"zebra": ""} self.reportCores = True @@ -1799,7 +1805,12 @@ class Router(Node): gdb_breakpoints = g_pytest_config.get_option_list("--gdb-breakpoints") gdb_daemons = g_pytest_config.get_option_list("--gdb-daemons") gdb_routers = g_pytest_config.get_option_list("--gdb-routers") + gdb_use_emacs = bool(g_pytest_config.option.gdb_use_emacs) + rr_daemons = g_pytest_config.get_option_list("--rr-daemons") + rr_routers = g_pytest_config.get_option_list("--rr-routers") + rr_options = g_pytest_config.get_option("--rr-options", "") valgrind_extra = bool(g_pytest_config.option.valgrind_extra) + valgrind_leak_kinds = g_pytest_config.option.valgrind_leak_kinds valgrind_memleaks = bool(g_pytest_config.option.valgrind_memleaks) strace_daemons = g_pytest_config.get_option_list("--strace-daemons") @@ -1875,6 +1886,15 @@ class Router(Node): # do not since apparently presence of the pidfile impacts BGP GR self.cmd_status("rm -f {0}.pid {0}.vty".format(runbase)) + def do_gdb_or_rr(gdb): + routers = gdb_routers if gdb else rr_routers + daemons = gdb_daemons if gdb else rr_daemons + return ( + (routers or daemons) + and (not routers or self.name in routers or "all" in routers) + and (not daemons or daemon in daemons or "all" in daemons) + ) + rediropt = " > {0}.out 2> {0}.err".format(daemon) if daemon == "snmpd": binary = "/usr/sbin/snmpd" @@ -1883,6 +1903,15 @@ class Router(Node): daemon_opts ) + "{}.pid -x /etc/frr/agentx".format(runbase) # check_daemon_files.append(runbase + ".pid") + elif daemon == "snmptrapd": + binary = "/usr/sbin/snmptrapd" + cmdenv = "" + cmdopt = ( + "{} ".format(daemon_opts) + + "-C -c /etc/{}/snmptrapd.conf".format(self.routertype) + + " -p {}.pid".format(runbase) + + " -LF 6-7 {}/{}/snmptrapd.log".format(self.logdir, self.name) + ) else: binary = os.path.join(self.daemondir, daemon) check_daemon_files.extend([runbase + ".pid", runbase + ".vty"]) @@ -1901,13 +1930,23 @@ class Router(Node): supp_file = os.path.abspath( os.path.join(this_dir, "../../../tools/valgrind.supp") ) - cmdenv += " /usr/bin/valgrind --num-callers=50 --log-file={1}/{2}.valgrind.{0}.%p --leak-check=full --suppressions={3}".format( - daemon, self.logdir, self.name, supp_file + + valgrind_logbase = f"{self.logdir}/{self.name}.valgrind.{daemon}" + if do_gdb_or_rr(True): + cmdenv += " exec" + cmdenv += ( + " /usr/bin/valgrind --num-callers=50" + f" --log-file={valgrind_logbase}.%p" + f" --leak-check=full --suppressions={supp_file}" ) + if valgrind_leak_kinds: + cmdenv += f" --show-leak-kinds={valgrind_leak_kinds}" if valgrind_extra: cmdenv += ( " --gen-suppressions=all --expensive-definedness-checks=yes" ) + if do_gdb_or_rr(True): + cmdenv += " --vgdb-error=0" elif daemon in strace_daemons or "all" in strace_daemons: cmdenv = "strace -f -D -o {1}/{2}.strace.{0} ".format( daemon, self.logdir, self.name @@ -1922,16 +1961,22 @@ class Router(Node): tail_log_files.append( "{}/{}/{}.log".format(self.logdir, self.name, daemon) ) + if extra_opts: cmdopt += " " + extra_opts + if do_gdb_or_rr(True) and do_gdb_or_rr(False): + logger.warning("cant' use gdb and rr at same time") + if ( - (gdb_routers or gdb_daemons) - and ( - not gdb_routers or self.name in gdb_routers or "all" in gdb_routers - ) - and (not gdb_daemons or daemon in gdb_daemons or "all" in gdb_daemons) - ): + not gdb_use_emacs or Router.gdb_emacs_router or valgrind_memleaks + ) and do_gdb_or_rr(True): + if Router.gdb_emacs_router is not None: + logger.warning( + "--gdb-use-emacs can only run a single router and daemon, using" + " new window" + ) + if daemon == "snmpd": cmdopt += " -f " @@ -1941,9 +1986,133 @@ class Router(Node): gdbcmd += " -ex 'set breakpoint pending on'" for bp in gdb_breakpoints: gdbcmd += " -ex 'b {}'".format(bp) - gdbcmd += " -ex 'run {}'".format(cmdopt) - self.run_in_window(gdbcmd, daemon) + if not valgrind_memleaks: + gdbcmd += " -ex 'run {}'".format(cmdopt) + self.run_in_window(gdbcmd, daemon) + + logger.info( + "%s: %s %s launched in gdb window", + self, + self.routertype, + daemon, + ) + + else: + cmd = " ".join([cmdenv, binary, cmdopt]) + p = self.popen(cmd) + self.valgrind_gdb_daemons[daemon] = p + if p.poll() and p.returncode: + self.logger.error( + '%s: Failed to launch "%s" (%s) with perf using: %s', + self, + daemon, + p.returncode, + cmd, + ) + assert False, "Faled to launch valgrind with gdb" + logger.debug( + "%s: %s %s started with perf", self, self.routertype, daemon + ) + # Now read the erorr log file until we ae given launch priority + timeout = Timeout(30) + vpid = None + for remaining in timeout: + try: + fname = f"{valgrind_logbase}.{p.pid}" + logging.info("Checking %s for valgrind launch info", fname) + o = open(fname, encoding="ascii").read() + except FileNotFoundError: + logging.info("%s not present yet", fname) + else: + m = re.search(r"target remote \| (.*vgdb) --pid=(\d+)", o) + if m: + vgdb_cmd = m.group(0) + break + time.sleep(1) + else: + assert False, "Faled to get launch info for valgrind with gdb" + + gdbcmd += f" -ex '{vgdb_cmd}'" + gdbcmd += " -ex 'c'" + self.run_in_window(gdbcmd, daemon) + + logger.info( + "%s: %s %s launched in gdb window", + self, + self.routertype, + daemon, + ) + elif gdb_use_emacs and do_gdb_or_rr(True): + assert Router.gdb_emacs_router is None + Router.gdb_emacs_router = self + + assert not valgrind_memleaks, "vagrind gdb in emacs not supported yet" + + if daemon == "snmpd": + cmdopt += " -f " + cmdopt += rediropt + + sudo_path = get_exec_path_host("sudo") + ecbin = [ + sudo_path, + "-Eu", + os.environ["SUDO_USER"], + get_exec_path_host("emacsclient"), + ] + pre_cmd = self._get_pre_cmd(True, False, ns_only=True, root_level=True) + # why fail:? gdb -i=mi -iex='set debuginfod enabled off' {binary} " + gdbcmd = f"{sudo_path} {pre_cmd} gdb -i=mi {binary} " + + commander.cmd_raises( + ecbin + + [ + "--eval", + f'(gdb "{gdbcmd}"))', + ] + ) + + elcheck = ( + '(ignore-errors (with-current-buffer "*gud-nsenter*"' + " (and (string-match-p" + ' "(gdb) "' + " (buffer-substring-no-properties " + ' (- (point-max) 10) (point-max))) "ready")))' + ) + + @retry(10) + def emacs_gdb_ready(): + check = commander.cmd_nostatus(ecbin + ["--eval", elcheck]) + return None if "ready" in check else False + + emacs_gdb_ready() + + # target gdb commands + cmd = "set breakpoint pending on" + self.cmd_raises( + ecbin + + [ + "--eval", + f'(gud-gdb-run-command-fetch-lines "{cmd}" "*gud-gdb*")', + ] + ) + # gdb breakpoints + for bp in gdb_breakpoints: + self.cmd_raises( + ecbin + + [ + "--eval", + f'(gud-gdb-run-command-fetch-lines "br {bp}" "*gud-gdb*")', + ] + ) + + self.cmd_raises( + ecbin + + [ + "--eval", + f'(gud-gdb-run-command-fetch-lines "run {cmdopt}" "*gud-gdb*")', + ] + ) logger.info( "%s: %s %s launched in gdb window", self, self.routertype, daemon @@ -1969,8 +2138,31 @@ class Router(Node): logger.debug( "%s: %s %s started with perf", self, self.routertype, daemon ) + elif do_gdb_or_rr(False): + cmdopt += rediropt + cmd = " ".join( + [ + "rr record -o {} {} --".format(self.rundir / "rr", rr_options), + binary, + cmdopt, + ] + ) + p = self.popen(cmd) + self.rr_daemons[daemon] = p + if p.poll() and p.returncode: + self.logger.error( + '%s: Failed to launch "%s" (%s) with rr using: %s', + self, + daemon, + p.returncode, + cmd, + ) + else: + logger.debug( + "%s: %s %s started with rr", self, self.routertype, daemon + ) else: - if daemon != "snmpd": + if daemon != "snmpd" and daemon != "snmptrapd": cmdopt += " -d " cmdopt += rediropt @@ -2213,6 +2405,8 @@ class Router(Node): for daemon in self.daemons: if daemon == "snmpd": continue + if daemon == "snmptrapd": + continue if (self.daemons[daemon] == 1) and not (daemon in daemonsRunning): sys.stderr.write("%s: Daemon %s not running\n" % (self.name, daemon)) if daemon == "staticd": |