]> git.ipfire.org Git - thirdparty/openldap.git/commitdiff
Allow wrapping of slapd invocations
authorOndřej Kuzník <ondra@mistotebe.net>
Mon, 22 Feb 2021 14:43:27 +0000 (14:43 +0000)
committerQuanah Gibson-Mount <quanah@openldap.org>
Fri, 26 Feb 2021 16:04:40 +0000 (16:04 +0000)
tests/scripts/defines.sh
tests/scripts/gdb.py [new file with mode: 0644]
tests/scripts/grandchild_wrapper.py [new file with mode: 0755]

index 12102393bbe599ffa1856bb0091ce73524e645c4..40b0d467c1455966299471b35c99528e742f092b 100755 (executable)
@@ -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 (file)
index 0000000..d8229de
--- /dev/null
@@ -0,0 +1,85 @@
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## 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
+## <http://www.OpenLDAP.org/license.html>.
+"""
+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 (executable)
index 0000000..700222d
--- /dev/null
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## 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
+## <http://www.OpenLDAP.org/license.html>.
+"""
+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())