From e051eb28fb43e0710593e04ebb6900a62277b90e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Mon, 22 Feb 2021 14:43:27 +0000 Subject: [PATCH] Allow wrapping of slapd invocations --- tests/scripts/defines.sh | 35 +++++++++--- tests/scripts/gdb.py | 85 +++++++++++++++++++++++++++++ tests/scripts/grandchild_wrapper.py | 72 ++++++++++++++++++++++++ 3 files changed, 185 insertions(+), 7 deletions(-) create mode 100644 tests/scripts/gdb.py create mode 100755 tests/scripts/grandchild_wrapper.py diff --git a/tests/scripts/defines.sh b/tests/scripts/defines.sh index 12102393bb..40b0d467c1 100755 --- a/tests/scripts/defines.sh +++ b/tests/scripts/defines.sh @@ -66,6 +66,10 @@ case "$SCHEMADIR" in .*) ABS_SCHEMADIR="$TESTWD/$SCHEMADIR" ;; *) ABS_SCHEMADIR="$SCHEMADIR" ;; esac +case "$SRCDIR" in +.*) ABS_SRCDIR="$TESTWD/$SRCDIR" ;; +*) ABS_SRCDIR="$SRCDIR" ;; +esac DBDIR1A=$TESTDIR/db.1.a DBDIR1B=$TESTDIR/db.1.b @@ -182,6 +186,23 @@ SLURPLOG=$TESTDIR/slurp.log CONFIGPWF=$TESTDIR/configpw +# wrappers (valgrind, gdb, environment variables, etc.) +if [ -n "$WRAPPER" ]; then + : # skip +elif [ "$SLAPD_COMMON_WRAPPER" = gdb ]; then + WRAPPER="$ABS_SRCDIR/scripts/grandchild_wrapper.py gdb -nx -x $ABS_SRCDIR/scripts/gdb.py -batch-silent -return-child-result --args" +elif [ "$SLAPD_COMMON_WRAPPER" = valgrind ]; then + WRAPPER="valgrind --log-file=$TESTDIR/valgrind.%p.log --fullpath-after=`dirname $ABS_SRCDIR` --keep-debuginfo=yes --leak-check=full" +elif [ "$SLAPD_COMMON_WRAPPER" = "valgrind-errstop" ]; then + WRAPPER="valgrind --log-file=$TESTDIR/valgrind.%p.log --vgdb=yes --vgdb-error=1" +elif [ "$SLAPD_COMMON_WRAPPER" = vgdb ]; then + WRAPPER="valgrind --log-file=$TESTDIR/valgrind.%p.log --vgdb=yes --vgdb-error=0" +fi + +if [ -n "$WRAPPER" ]; then + SLAPD_WRAPPER="$TESTWD/../libtool --mode=execute env $WRAPPER" +fi + # args SASLARGS="-Q" TOOLARGS="-x $LDAP_TOOLARGS" @@ -193,11 +214,11 @@ CONFDIRSYNC=$SRCDIR/scripts/confdirsync.sh MONITORDATA=$SRCDIR/scripts/monitor_data.sh -SLAPADD="$TESTWD/../servers/slapd/slapd -Ta -d 0 $LDAP_VERBOSE" -SLAPCAT="$TESTWD/../servers/slapd/slapd -Tc -d 0 $LDAP_VERBOSE" -SLAPINDEX="$TESTWD/../servers/slapd/slapd -Ti -d 0 $LDAP_VERBOSE" -SLAPMODIFY="$TESTWD/../servers/slapd/slapd -Tm -d 0 $LDAP_VERBOSE" -SLAPPASSWD="$TESTWD/../servers/slapd/slapd -Tpasswd" +SLAPADD="$SLAPD_WRAPPER $TESTWD/../servers/slapd/slapd -Ta -d 0 $LDAP_VERBOSE" +SLAPCAT="$SLAPD_WRAPPER $TESTWD/../servers/slapd/slapd -Tc -d 0 $LDAP_VERBOSE" +SLAPINDEX="$SLAPD_WRAPPER $TESTWD/../servers/slapd/slapd -Ti -d 0 $LDAP_VERBOSE" +SLAPMODIFY="$SLAPD_WRAPPER $TESTWD/../servers/slapd/slapd -Tm -d 0 $LDAP_VERBOSE" +SLAPPASSWD="$SLAPD_WRAPPER $TESTWD/../servers/slapd/slapd -Tpasswd" unset DIFF_OPTIONS # NOTE: -u/-c is not that portable... @@ -205,8 +226,8 @@ DIFF="diff -i" CMP="diff -i" BCMP="diff -iB" CMPOUT=/dev/null -SLAPD="$TESTWD/../servers/slapd/slapd -s0" -LLOADD="$TESTWD/../servers/lloadd/lloadd -s0" +SLAPD="$SLAPD_WRAPPER $TESTWD/../servers/slapd/slapd -s0" +LLOADD="$SLAPD_WRAPPER $TESTWD/../servers/lloadd/lloadd -s0" LDAPPASSWD="$CLIENTDIR/ldappasswd $TOOLARGS" LDAPSASLSEARCH="$CLIENTDIR/ldapsearch $SASLARGS $TOOLPROTO $LDAP_TOOLARGS -LLL" LDAPSASLWHOAMI="$CLIENTDIR/ldapwhoami $SASLARGS $LDAP_TOOLARGS" diff --git a/tests/scripts/gdb.py b/tests/scripts/gdb.py new file mode 100644 index 0000000000..d8229dea4c --- /dev/null +++ b/tests/scripts/gdb.py @@ -0,0 +1,85 @@ +# $OpenLDAP$ +## This work is part of OpenLDAP Software . +## +## Copyright 2020-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## . +""" +This GDB script sets up the debugger to run the program and see if it finishes +of its own accord or is terminated by a signal (like SIGABRT/SIGSEGV). In the +latter case, it saves a full backtrace and core file. + +These signals are considered part of normal operation and will not trigger the +above handling: +- SIGPIPE: normal in a networked environmnet +- SIGHUP: normally used to tell a process to shut down +""" + +import os +import os.path + +import gdb + + +def format_program(inferior=None, thread=None): + "Format program name and p(t)id" + + if thread: + inferior = thread.inferior + elif inferior is None: + inferior = gdb.selected_inferior() + + try: + name = os.path.basename(inferior.progspace.filename) + except AttributeError: # inferior has died already + name = "unknown" + + if thread: + pid = ".".join(tid for tid in thread.ptid if tid) + else: + pid = inferior.pid + + return "{}.{}".format(name, pid) + + +def stop_handler(event): + "Inferior stopped on a signal, record core, backtrace and exit" + + if not isinstance(event, gdb.SignalEvent): + # Ignore breakpoints + return + + thread = event.inferior_thread + + identifier = format_program(thread=thread) + prefix = os.path.expandvars("${TESTDIR}/") + identifier + + if event.stop_signal == "SIGHUP": + # TODO: start a timer to catch shutdown issues/deadlocks + gdb.execute("continue") + return + + gdb.execute('generate-core-file {}.core'.format(prefix)) + + with open(prefix + ".backtrace", "w") as bt_file: + backtrace = gdb.execute("thread apply all backtrace full", + to_string=True) + bt_file.write(backtrace) + + gdb.execute("continue") + + +# We or we could allow the runner to disable randomisation +gdb.execute("set disable-randomization off") + +gdb.execute("handle SIGPIPE noprint") +gdb.execute("handle SIGINT pass") +gdb.events.stop.connect(stop_handler) +gdb.execute("run") diff --git a/tests/scripts/grandchild_wrapper.py b/tests/scripts/grandchild_wrapper.py new file mode 100755 index 0000000000..700222d9e8 --- /dev/null +++ b/tests/scripts/grandchild_wrapper.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +# $OpenLDAP$ +## This work is part of OpenLDAP Software . +## +## Copyright 2020-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## . +""" +Running slapd under GDB in our testsuite, KILLPIDS would record gdb's PID +rather than slapd's. When we want the server to shut down, SIGHUP is sent to +KILLPIDS but GDB cannot handle being signalled directly and the entire thing is +terminated immediately. There might be tests that rely on slapd being given the +chance to shut down gracefully, to do this, we need to make sure the signal is +actually sent to slapd. + +This script attempts to address this shortcoming in our test suite, serving as +the front for gdb/other wrappers, catching SIGHUPs and redirecting them to the +oldest living grandchild. The way we start up gdb, that process should be +slapd, our intended target. + +This requires the pgrep utility provided by the procps package on Debian +systems. +""" + +import asyncio +import os +import signal +import sys + + +async def signal_to_grandchild(child): + # Get the first child, that should be the one we're after + pgrep = await asyncio.create_subprocess_exec( + "pgrep", "-o", "--parent", str(child.pid), + stdout=asyncio.subprocess.PIPE) + + stdout, _ = await pgrep.communicate() + if not stdout: + return + + grandchild = [int(pid) for pid in stdout.split()][0] + + os.kill(grandchild, signal.SIGHUP) + + +def sighup_handler(child): + asyncio.create_task(signal_to_grandchild(child)) + + +async def main(args=None): + if args is None: + args = sys.argv[1:] + + child = await asyncio.create_subprocess_exec(*args) + + # If we got a SIGHUP before we got the child fully started, there's no + # point signalling anyway + loop = asyncio.get_running_loop() + loop.add_signal_handler(signal.SIGHUP, sighup_handler, child) + + raise SystemExit(await child.wait()) + + +if __name__ == '__main__': + asyncio.run(main())