Support functions, sourced by eventscripts and other scripts.
- statd-callout
-
- rpc.statd high-availability callout to support lock migration on
- failover.
-
Notes:
* All of these scripts are written in POSIX Bourne shell. Please
# Restart lock manager, notify clients
# shellcheck disable=SC2317
# Called indirectly via check_thresholds()
- if [ -x "${CTDB_BASE}/statd-callout" ]; then
- "${CTDB_BASE}/statd-callout" notify &
+ if [ -x "${CTDB_HELPER_BINDIR}/statd_callout_helper" ] ; then
+ "${CTDB_HELPER_BINDIR}/statd_callout_helper" notify &
fi >/dev/null 2>&1
}
}
##################################################################
-# use statd-callout to update NFS lock info
+# use statd_callout_helper to update NFS lock info
##################################################################
nfs_update_lock_info()
{
- if [ -x "$CTDB_BASE/statd-callout" ]; then
- "$CTDB_BASE/statd-callout" update
+ if [ -x "$CTDB_HELPER_BINDIR/statd_callout_helper" ] ; then
+ "$CTDB_HELPER_BINDIR/statd_callout_helper" update
fi
}
case "$1" in
startup)
- if [ -x "${CTDB_BASE}/statd-callout" ] ; then
- "${CTDB_BASE}/statd-callout" startup
+ if [ -x "${CTDB_HELPER_BINDIR}/statd_callout_helper" ] ; then
+ "${CTDB_HELPER_BINDIR}/statd_callout_helper" startup
fi
nfs_callout "$@" || exit $?
}
# Cache of public IP addresses assigned to this node. This function
-# exists mainly so statd-callout does not need to talk to ctdbd, so
+# exists mainly so statd_callout does not need to talk to ctdbd, so
# can be run as non-root, but it may be used in other places. This
# must be updated/refreshed on failover. This is done in
# 10.interface, but doing it in "ipreallocated" isn't enough because
--- /dev/null
+/*
+ CTDB NFSv3 rpc.statd HA callout
+
+ Copyright 2023, DataDirect Networks, Inc. All rights reserved.
+ Author: Martin Schwenke <mschwenke@ddn.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+/*
+ * A configuration file, created by statd_callout_helper, containing
+ * at least 1 line of text.
+ *
+ * The first line is the mode. Currently supported modes are:
+ *
+ * persistent_db
+ *
+ * In this mode, the file contains 2 subsequent lines of text:
+ *
+ * path: directory where files should be created
+ * ips_file: file containing node's currently assigned public IP addresses
+ */
+#define CONFIG_FILE CTDB_VARDIR "/scripts/statd_callout.conf"
+
+static const char *progname;
+
+struct {
+ enum {
+ CTDB_SC_MODE_PERSISTENT_DB,
+ } mode;
+ union {
+ struct {
+ char *path;
+ char *ips_file;
+ };
+ };
+} config;
+
+static bool getline_strip(char **restrict lineptr,
+ size_t *n,
+ FILE *restrict stream)
+{
+ bool was_null;
+ int ret;
+
+ was_null = *lineptr == NULL;
+
+ ret = getline(lineptr, n, stream);
+ if (ret == -1 || ret <= 2) {
+ if (was_null) {
+ free(*lineptr);
+ *lineptr = NULL;
+ *n = 0;
+ }
+ return false;
+ }
+
+ if ((*lineptr)[ret - 1] == '\n') {
+ (*lineptr)[ret - 1] = '\0';
+ }
+
+ return true;
+}
+
+static void free_config(void)
+{
+ switch (config.mode) {
+ case CTDB_SC_MODE_PERSISTENT_DB:
+ free(config.path);
+ config.path = NULL;
+ free(config.ips_file);
+ config.ips_file = NULL;
+ }
+}
+
+static void read_config(void)
+{
+ const char *config_file = NULL;
+ FILE *f = NULL;
+ char *mode = NULL;
+ size_t n = 0;
+ bool status;
+
+ /* For testing only */
+ config_file = getenv("CTDB_STATD_CALLOUT_CONFIG_FILE");
+ if (config_file == NULL || strlen(config_file) == 0) {
+ config_file = CONFIG_FILE;
+ }
+
+ f = fopen(config_file, "r");
+ if (f == NULL) {
+ fprintf(stderr,
+ "%s: unable to open config file (%s)\n",
+ progname,
+ config_file);
+ exit(1);
+ }
+
+ status = getline_strip(&mode, &n, f);
+ if (!status) {
+ fprintf(stderr,
+ "%s: error parsing mode in %s\n",
+ progname,
+ config_file);
+ exit(1);
+ }
+ if (strcmp(mode, "persistent_db") == 0) {
+ config.mode = CTDB_SC_MODE_PERSISTENT_DB;
+ } else {
+ fprintf(stderr,
+ "%s: unknown mode=%s in %s\n",
+ progname,
+ mode,
+ config_file);
+ free(mode);
+ exit(1);
+ }
+ free(mode);
+
+ switch (config.mode) {
+ case CTDB_SC_MODE_PERSISTENT_DB:
+ status = getline_strip(&config.path, &n, f);
+ if (!status) {
+ goto parse_error;
+ }
+
+ status = getline_strip(&config.ips_file, &n, f);
+ if (!status) {
+ goto parse_error;
+ }
+
+ break;
+ }
+
+ fclose(f);
+ return;
+
+parse_error:
+ fprintf(stderr,
+ "%s: error parsing contents of %s\n",
+ progname,
+ config_file);
+ free_config();
+ exit(1);
+}
+
+static void for_each_sip(void (*line_func)(const char *sip, const char *cip),
+ const char *cip)
+{
+ FILE *f = NULL;
+ char *line = NULL;
+ size_t n = 0;
+
+ f = fopen(config.ips_file, "r");
+ if (f == NULL) {
+ fprintf(stderr,
+ "%s: unable to open IPs file (%s)\n",
+ progname,
+ config.ips_file);
+ exit(1);
+ }
+
+ for (;;) {
+ bool status;
+
+ status = getline_strip(&line, &n, f);
+ if (!status) {
+ if (!feof(f)) {
+ fprintf(stderr,
+ "%s: error parsing contents of %s\n",
+ progname,
+ config.ips_file);
+ free(line);
+ exit(1);
+ }
+ break;
+ }
+
+ line_func(line, cip);
+ }
+
+ free(line);
+ fclose(f);
+ return;
+}
+
+static void make_path(char *buf,
+ size_t num,
+ const char *sip,
+ const char *cip)
+{
+ int ret = snprintf(buf,
+ num,
+ "%s/statd-state@%s@%s",
+ config.path,
+ sip,
+ cip);
+ if (ret < 0) {
+ /* Not possible for snprintf(3)? */
+ fprintf(stderr,
+ "%s: error constructing path %s/statd-state@%s@%s\n",
+ progname,
+ config.path,
+ sip,
+ cip);
+ exit(1);
+ }
+ if ((size_t)ret >= num) {
+ fprintf(stderr,
+ "%s: path too long %s/statd-state@%s@%s\n",
+ progname,
+ config.path,
+ sip,
+ cip);
+ exit(1);
+ }
+}
+
+static void add_client_persistent_db_line(const char *sip, const char *cip)
+{
+ char path[PATH_MAX];
+ FILE *f;
+ long long datetime;
+
+ make_path(path, sizeof(path), sip, cip);
+
+ datetime = (long long)time(NULL);
+
+ f = fopen(path, "w");
+ if (f == NULL) {
+ fprintf(stderr,
+ "%s: unable to open for writing %s\n",
+ progname,
+ path);
+ exit(1);
+ }
+ fprintf(f, "\"statd-state@%s@%s\" \"%lld\"\n", sip, cip, datetime);
+ fclose(f);
+}
+
+static void add_client_persistent_db(const char *cip)
+{
+ for_each_sip(add_client_persistent_db_line, cip);
+}
+
+static void del_client_persistent_db_line(const char *sip, const char *cip)
+{
+ char path[PATH_MAX];
+ FILE *f;
+
+ make_path(path, sizeof(path), sip, cip);
+
+ f = fopen(path, "w");
+ if (f == NULL) {
+ fprintf(stderr,
+ "%s: unable to open for writing %s\n",
+ progname,
+ path);
+ exit(1);
+ }
+ fprintf(f, "\"statd-state@%s@%s\" \"\"\n", sip, cip);
+ fclose(f);
+}
+
+static void del_client_persistent_db(const char *cip)
+{
+ for_each_sip(del_client_persistent_db_line, cip);
+}
+
+static void usage(void)
+{
+ printf("usage: %s: { add-client | del-client } <client-ip>\n", progname);
+ exit(1);
+}
+
+int main(int argc, const char *argv[])
+{
+ const char *event = NULL;
+ const char *mon_name = NULL;
+
+ progname = argv[0];
+ if (argc < 3) {
+ usage();
+ }
+
+ read_config();
+
+ event = argv[1];
+ if (strcmp(event, "add-client") == 0) {
+ mon_name = argv[2];
+ switch (config.mode) {
+ case CTDB_SC_MODE_PERSISTENT_DB:
+ add_client_persistent_db(mon_name);
+ break;
+ }
+ } else if (strcmp(event, "del-client") == 0) {
+ mon_name = argv[2];
+ switch (config.mode) {
+ case CTDB_SC_MODE_PERSISTENT_DB:
+ del_client_persistent_db(mon_name);
+ break;
+ }
+ } else {
+ usage();
+ }
+
+ free_config();
+}
;;
esac
fi
-
- # This is really nasty. However, when we test NFS we don't
- # actually test statd-callout. If we leave it there then left
- # over, backgrounded instances of statd-callout will do
- # horrible things with the "ctdb ip" stub and cause the actual
- # statd-callout tests that follow to fail.
- rm "${CTDB_BASE}/statd-callout"
}
rpc_services_down()
# Hey Emacs, this is a -*- shell-script -*- !!! :-)
-#
-# Augment PATH with relevant stubs/ directories.
-#
-
-stubs_dir="${CTDB_TEST_SUITE_DIR}/stubs"
-[ -d "${stubs_dir}" ] || die "Failed to locate stubs/ subdirectory"
-
-# Make the path absolute for tests that change directory
-case "$stubs_dir" in
-/*) : ;;
-*) stubs_dir="${PWD}/${stubs_dir}" ;;
-esac
-
-# Use stubs as helpers
-export CTDB_HELPER_BINDIR="$stubs_dir"
-
-PATH="${stubs_dir}:${PATH}"
-
export CTDB="ctdb"
# Force this to be absolute - event scripts can change directory
debug_locks.sh \
functions \
nfs-checks.d \
- nfs-linux-kernel-callout \
- statd-callout
+ nfs-linux-kernel-callout
export FAKE_CTDB_STATE="${CTDB_TEST_TMP_DIR}/fake-ctdb"
mkdir -p "$FAKE_CTDB_STATE"
esac
_s="${script_dir}/${script}"
- [ -r "$_s" ] ||
+ if [ ! -r "$_s" ] && [ "$script" != "statd-callout" ]; then
die "Internal error - unable to find script \"${_s}\""
+ fi
case "$script" in
*.script) script_short="${script%.script}" ;;
printf "%-17s %-10s %-4s - %s\n\n" \
"$script_short" "$event" "$_num" "$desc"
+ #
+ # Augment PATH with relevant stubs/ directories.
+ #
+
+ stubs_dir="${CTDB_TEST_SUITE_DIR}/stubs"
+ [ -d "${stubs_dir}" ] || die "Failed to locate stubs/ subdirectory"
+
+ # Make the path absolute for tests that change directory
+ case "$stubs_dir" in
+ /*) : ;;
+ *) stubs_dir="${PWD}/${stubs_dir}" ;;
+ esac
+
+ # Use stubs as helpers but remember the original, so
+ # certain things (e.g. statd_callout) can be found
+ #
+ export CTDB_HELPER_BINDIR="$stubs_dir"
+ PATH="${stubs_dir}:${PATH}"
+
_f="${CTDB_TEST_SUITE_DIR}/scripts/${script_short}.sh"
if [ -r "$_f" ]; then
. "$_f"
{
setup_public_addresses
ctdb_set_pnn
- setup_date "123456789"
+ setup_date "1234567890"
}
ctdb_catdb_format_pairs()
echo "Dumped ${_count} records"
}
+result_filter()
+{
+ sed -e 's|^\(data(10) = \)".........."$|data(8) = "DATETIME"|'
+}
+
check_ctdb_tdb_statd_state()
{
ctdb_get_my_public_addresses |
while read -r _ _sip _; do
for _cip; do
cat <<EOF
-statd-state@${_sip}@${_cip} $(date)
+statd-state@${_sip}@${_cip} DATETIME
EOF
done
done |
simple_test_event "notify"
} || exit $?
}
+
+simple_test_event()
+{
+ # If something has previously failed then don't continue.
+ : "${_passed:=true}"
+ $_passed || return 1
+
+ event="$1"
+ shift
+ echo "=================================================="
+
+ [ -n "$event" ] || die 'simple_test: event not set'
+
+ args="$*"
+
+ # shellcheck disable=SC2317
+ # used in unit_test(), etc.
+ test_header()
+ {
+ echo "Running \"${cmd} $event${args:+ }$args\""
+ }
+
+ # shellcheck disable=SC2317
+ # used in unit_test(), etc.
+ extra_header()
+ {
+ cat <<EOF
+
+##################################################
+CTDB_BASE="$CTDB_BASE"
+CTDB_SYS_ETCDIR="$CTDB_SYS_ETCDIR"
+EOF
+ }
+
+ CTDB_STATD_CALLOUT_CONFIG_FILE="${CTDB_TEST_TMP_DIR}/statd_callout.conf"
+ export CTDB_STATD_CALLOUT_CONFIG_FILE
+
+ case "$event" in
+ add-client | del-client)
+ cmd="${CTDB_SCRIPTS_TESTS_LIBEXEC_DIR}/statd_callout"
+ unit_test "$cmd" "$event" "$@"
+ ;;
+ *)
+ cmd="${CTDB_SCRIPTS_TOOLS_HELPER_DIR}/statd_callout_helper"
+ script_test "$cmd" "$event" "$@"
+ ;;
+ esac
+
+ reset_test_header
+ reset_extra_header
+}
#!/bin/sh
-# Make statd-callout happy
+# Make statd_callout_helper happy
case "$*" in
*/var/lib/nfs/statd/sm*) : ;;
*) exec /bin/rm "$@" ;;
--- /dev/null
+#!/bin/sh
+
+# Do nothing. statd_callout and statd_callout_helper are tested
+# separately.
+
+exit 0
"${CTDB_SCRIPTS_BASE}/ctdb-crash-cleanup.sh" \
"${CTDB_SCRIPTS_BASE}/debug-hung-script.sh" \
"${CTDB_SCRIPTS_BASE}/debug_locks.sh" \
- "${CTDB_SCRIPTS_BASE}/nfs-linux-kernel-callout" \
- "${CTDB_SCRIPTS_BASE}/statd-callout"
+ "${CTDB_SCRIPTS_BASE}/nfs-linux-kernel-callout"
shellcheck_test \
"${CTDB_SCRIPTS_TOOLS_HELPER_DIR}/ctdb_lvs" \
- "${CTDB_SCRIPTS_TOOLS_HELPER_DIR}/ctdb_natgw"
+ "${CTDB_SCRIPTS_TOOLS_HELPER_DIR}/ctdb_natgw" \
+ "${CTDB_SCRIPTS_TOOLS_HELPER_DIR}/statd_callout_helper"
#
# [statd]
# name = mycluster
-# ha-callout = /etc/ctdb/statd-callout
+# ha-callout = /usr/local/libexec/ctdb/statd_callout
#
# Older Linux versions may use something like the following...
#
# /etc/sysconfig/nfs (Red Hat) or /etc/default/nfs-common (Debian):
# NFS_HOSTNAME=mycluster
-# STATD_HOSTNAME="${NFS_HOSTNAME} -H /etc/ctdb/statd-callout"
+# STATD_HOSTNAME="${NFS_HOSTNAME} -H /usr/local/libexec/ctdb/statd_callout"
#
-[ -n "$CTDB_BASE" ] ||
- CTDB_BASE=$(d=$(dirname "$0") && cd -P "$d" && echo "$PWD")
+if [ -z "$CTDB_BASE" ] ; then
+ export CTDB_BASE="/usr/local/etc/ctdb"
+fi
. "${CTDB_BASE}/functions"
# Overwrite this so we get some logging
die()
{
- script_log "statd-callout" "$@"
+ script_log "statd_callout_helper" "$@"
exit 1
}
fi
[ -n "$NFS_HOSTNAME" ] ||
- die "NFS_HOSTNAME is not configured. statd-callout failed"
+ die "NFS_HOSTNAME is not configured. statd_callout_helper failed"
############################################################
# script_state_dir set by ctdb_setup_state_dir()
# shellcheck disable=SC2154
-statd_callout_state_dir="${script_state_dir}/statd-callout"
+statd_callout_state_dir="${script_state_dir}/statd_callout"
statd_callout_db="ctdb.tdb"
statd_callout_queue_dir="${statd_callout_state_dir}/queue"
create_add_del_client_dir "$statd_callout_queue_dir"
$CTDB attach "$statd_callout_db" persistent
+
+ _default="${CTDB_SCRIPT_VARDIR}/statd_callout.conf"
+ _config_file="${CTDB_STATD_CALLOUT_CONFIG_FILE:-"${_default}"}"
+ cat >"$_config_file" <<EOF
+persistent_db
+${statd_callout_queue_dir}
+${CTDB_MY_PUBLIC_IPS_CACHE}
+EOF
}
############################################################
startup
;;
-add-client)
- # statd does not tell us to which IP the client connected so
- # we must add it to all the IPs that we serve
- cip="$2"
- date=$(date '+%s')
- while read -r sip; do
- key="statd-state@${sip}@${cip}"
- file="${statd_callout_queue_dir}/${key}"
- echo "\"${key}\" \"${date}\"" >"$file"
- done <"$CTDB_MY_PUBLIC_IPS_CACHE"
- ;;
-
-del-client)
- # statd does not tell us from which IP the client disconnected
- # so we must add it to all the IPs that we serve
- cip="$2"
- while read -r sip; do
- key="statd-state@${sip}@${cip}"
- file="${statd_callout_queue_dir}/${key}"
- echo "\"${key}\" \"\"" >"$file"
- done <"$CTDB_MY_PUBLIC_IPS_CACHE"
- ;;
-
update)
cd "$statd_callout_queue_dir" ||
die "Failed to change directory to \"${statd_callout_queue_dir}\""
samba-util sys_rw replace tdb''',
install_path='${CTDB_HELPER_BINDIR}')
+ bld.SAMBA_BINARY("statd_callout",
+ source="failover/statd_callout.c",
+ install_path="${CTDB_HELPER_BINDIR}")
+
bld.SAMBA_BINARY('ctdb_takeover_helper',
source='server/ctdb_takeover_helper.c',
deps='''ctdb-client ctdb-protocol ctdb-util
bld.INSTALL_FILES('${CTDB_HELPER_BINDIR}', 'ctdb_lvs',
destname='ctdb_lvs', chmod=MODE_755)
+ bld.SAMBA_GENERATOR("ctdb-statd-callout-helper",
+ source="tools/statd_callout_helper",
+ target="statd_callout_helper",
+ rule=f"sed {sed_cmdline} ${{SRC}} > ${{TGT}}")
+ bld.INSTALL_FILES("${CTDB_HELPER_BINDIR}", "statd_callout_helper",
+ destname="statd_callout_helper", chmod=MODE_755)
+
+ bld.symlink_as(os.path.join(bld.env.CTDB_ETCDIR, "statd-callout"),
+ os.path.join(bld.env.CTDB_HELPER_BINDIR, "statd_callout"))
+
def SUBDIR_MODE_callback(arg, dirname, fnames):
for f in fnames:
fl = os.path.join(dirname, f)
'debug_locks.sh',
'nfs-linux-kernel-callout',
'notify.sh',
- 'statd-callout'
]
for t in etc_scripts: