summaryrefslogtreecommitdiffstats
path: root/cts/support
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 06:53:20 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 06:53:20 +0000
commite5a812082ae033afb1eed82c0f2df3d0f6bdc93f (patch)
treea6716c9275b4b413f6c9194798b34b91affb3cc7 /cts/support
parentInitial commit. (diff)
downloadpacemaker-e5a812082ae033afb1eed82c0f2df3d0f6bdc93f.tar.xz
pacemaker-e5a812082ae033afb1eed82c0f2df3d0f6bdc93f.zip
Adding upstream version 2.1.6.upstream/2.1.6
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'cts/support')
-rw-r--r--cts/support/LSBDummy.in85
-rw-r--r--cts/support/Makefile.am24
-rw-r--r--cts/support/cts-support.in170
-rw-r--r--cts/support/cts.conf3
-rw-r--r--cts/support/fence_dummy.in528
-rw-r--r--cts/support/pacemaker-cts-dummyd.conf2
-rw-r--r--cts/support/pacemaker-cts-dummyd.in55
-rw-r--r--cts/support/pacemaker-cts-dummyd@.service.in9
8 files changed, 876 insertions, 0 deletions
diff --git a/cts/support/LSBDummy.in b/cts/support/LSBDummy.in
new file mode 100644
index 0000000..eb2978b
--- /dev/null
+++ b/cts/support/LSBDummy.in
@@ -0,0 +1,85 @@
+#!/bin/sh
+#
+#
+# Dummy LSB RA. Does nothing but touch and remove a state file
+#
+# Copyright 2006-2021 the Pacemaker project contributors
+#
+# The version control history for this file may have further details.
+#
+# This source code is licensed under the GNU General Public License version 2
+# (GPLv2) WITHOUT ANY WARRANTY.
+
+#######################################################################
+# Initialization:
+
+desc="Dummy LSB service"
+. @OCF_ROOT_DIR@/resource.d/heartbeat/.ocf-directories
+: ${HA_VARRUN=/tmp} # Backup in case .ocf-directories doesn't exist
+
+#######################################################################
+
+success()
+{
+ printf "[ OK ]\r"
+}
+
+failure()
+{
+ printf "[FAILED]\r"
+}
+
+dummy_usage() {
+ cat <<END
+usage: $0 {start|stop|status}
+
+Dummy LSB resource
+END
+}
+
+dummy_start() {
+ echo -n "Starting $desc: "
+ touch ${state}
+ if [ -f ${state} ]; then
+ success
+ return 0
+ fi
+
+ failure
+ return 1
+}
+
+dummy_stop() {
+ echo -n "Stopping $desc: "
+ rm -f ${state}
+ if [ ! -f ${state} ]; then
+ success
+ return 0
+ fi
+
+ failure
+ return 1
+}
+
+dummy_monitor() {
+ if [ -f ${state} ]; then
+ echo "Running OK"
+ return 0
+ fi
+ echo "$desc is stopped"
+ return 3
+}
+
+state="${HA_VARRUN}/Dummy-`basename $0`.state"
+
+case $1 in
+start) dummy_start;;
+stop) dummy_stop;;
+status) dummy_monitor;;
+*) dummy_usage
+ exit 1
+ ;;
+esac
+rc=$?
+echo "`basename $0` $1 : $rc"
+exit $rc
diff --git a/cts/support/Makefile.am b/cts/support/Makefile.am
new file mode 100644
index 0000000..33cfa6f
--- /dev/null
+++ b/cts/support/Makefile.am
@@ -0,0 +1,24 @@
+#
+# Copyright 2021 the Pacemaker project contributors
+#
+# The version control history for this file may have further details.
+#
+# This source code is licensed under the GNU General Public License version 2
+# or later (GPLv2+) WITHOUT ANY WARRANTY.
+#
+
+MAINTAINERCLEANFILES = Makefile.in
+
+# Commands intended to be run only via other commands
+halibdir = $(CRM_DAEMON_DIR)
+dist_halib_SCRIPTS = cts-support
+
+ctsdir = $(datadir)/$(PACKAGE)/tests/cts
+cts_DATA = pacemaker-cts-dummyd@.service
+dist_cts_DATA = cts.conf
+if BUILD_UPSTART
+dist_cts_DATA += pacemaker-cts-dummyd.conf
+endif
+cts_SCRIPTS = fence_dummy \
+ LSBDummy \
+ pacemaker-cts-dummyd
diff --git a/cts/support/cts-support.in b/cts/support/cts-support.in
new file mode 100644
index 0000000..de5b7d8
--- /dev/null
+++ b/cts/support/cts-support.in
@@ -0,0 +1,170 @@
+#!/bin/sh
+#
+# Installer for support files needed by Pacemaker's Cluster Test Suite
+#
+# Copyright 2018-2022 the Pacemaker project contributors
+#
+# The version control history for this file may have further details.
+#
+# This source code is licensed under the GNU General Public License version 2
+# or later (GPLv2+) WITHOUT ANY WARRANTY.
+#
+
+USAGE_TEXT="Usage: $0 <install|uninstall|--help>"
+
+HELP_TEXT="$USAGE_TEXT
+Commands (must be run as root):
+ install Install support files needed by Pacemaker CTS
+ uninstall Remove support files needed by Pacemaker CTS"
+
+# These constants must track crm_exit_t values
+CRM_EX_OK=0
+CRM_EX_ERROR=1
+CRM_EX_USAGE=64
+
+UNIT_DIR="@systemdsystemunitdir@"
+RUNTIME_UNIT_DIR="@runstatedir@/systemd/system"
+LIBEXEC_DIR="@libexecdir@/pacemaker"
+INIT_DIR="@INITDIR@"
+PCMK__FENCE_BINDIR="@PCMK__FENCE_BINDIR@"
+DATA_DIR="@datadir@/pacemaker/tests/cts"
+UPSTART_DIR="/etc/init"
+
+DUMMY_DAEMON="pacemaker-cts-dummyd"
+DUMMY_DAEMON_UNIT="pacemaker-cts-dummyd@.service"
+COROSYNC_RUNTIME_UNIT="corosync.service.d"
+COROSYNC_RUNTIME_CONF="cts.conf"
+
+LSB_DUMMY="LSBDummy"
+UPSTART_DUMMY="pacemaker-cts-dummyd.conf"
+FENCE_DUMMY="fence_dummy"
+FENCE_DUMMY_ALIASES="auto_unfence no_reboot no_on"
+
+# If the install directory doesn't exist, assume we're in a build directory.
+if [ ! -d "$DATA_DIR" ]; then
+ # If readlink supports -e (i.e. GNU), use it.
+ readlink -e / >/dev/null 2>/dev/null
+ if [ $? -eq 0 ]; then
+ DATA_DIR="$(dirname "$(readlink -e "$0")")"
+ else
+ DATA_DIR="$(dirname "$0")"
+ fi
+fi
+
+usage() {
+ echo "Error:" "$@"
+ echo "$USAGE_TEXT"
+ exit $CRM_EX_USAGE
+}
+
+must_be_root() {
+ if ! [ "$(id -u)" = "0" ]; then
+ usage "this command must be run as root"
+ return $CRM_EX_ERROR
+ fi
+ return $CRM_EX_OK
+}
+
+support_uninstall() {
+ must_be_root || return $CRM_EX_ERROR
+
+ if [ -e "$UNIT_DIR/$DUMMY_DAEMON_UNIT" ]; then
+ echo "Removing $UNIT_DIR/$DUMMY_DAEMON_UNIT ..."
+ rm -f "$UNIT_DIR/$DUMMY_DAEMON_UNIT"
+ systemctl daemon-reload # Ignore failure
+ fi
+
+ if [ -e "$RUNTIME_UNIT_DIR/$COROSYNC_RUNTIME_UNIT" ]; then
+ echo "Removing $RUNTIME_UNIT_DIR/$COROSYNC_RUNTIME_UNIT ..."
+ rm -rf "$RUNTIME_UNIT_DIR/$COROSYNC_RUNTIME_UNIT"
+ systemctl daemon-reload # Ignore failure
+ fi
+
+ for FILE in \
+ "$LIBEXEC_DIR/$DUMMY_DAEMON" \
+ "$UPSTART_DIR/$UPSTART_DUMMY" \
+ "$PCMK__FENCE_BINDIR/$FENCE_DUMMY" \
+ "$INIT_DIR/$LSB_DUMMY"
+ do
+ if [ -e "$FILE" ]; then
+ echo "Removing $FILE ..."
+ rm -f "$FILE"
+ fi
+ done
+ for ALIAS in $FENCE_DUMMY_ALIASES; do \
+ FILE="$PCMK__FENCE_BINDIR/fence_dummy_$ALIAS"
+ if [ -L "$FILE" ] || [ -e "$FILE" ]; then
+ echo "Removing $FILE ..."
+ rm -f "$FILE"
+ fi
+ done
+
+ return $CRM_EX_OK
+}
+
+support_install() {
+ support_uninstall || return $CRM_EX_ERROR
+ cd "$DATA_DIR" || return $CRM_EX_ERROR
+ if [ -d "$UNIT_DIR" ]; then
+
+ echo "Installing $DUMMY_DAEMON ..."
+ mkdir -p "$LIBEXEC_DIR"
+ install -m 0755 "$DUMMY_DAEMON" "$LIBEXEC_DIR" || return $CRM_EX_ERROR
+
+ echo "Installing $DUMMY_DAEMON_UNIT ..."
+ install -m 0644 "$DUMMY_DAEMON_UNIT" "$UNIT_DIR" || return $CRM_EX_ERROR
+ systemctl daemon-reload # Ignore failure
+ fi
+
+ if [ -d "$RUNTIME_UNIT_DIR" ]; then
+
+ echo "Installing $COROSYNC_RUNTIME_CONF to $RUNTIME_UNIT_DIR/$COROSYNC_RUNTIME_UNIT ..."
+ mkdir -p "$RUNTIME_UNIT_DIR/$COROSYNC_RUNTIME_UNIT"
+ install -m 0644 "$COROSYNC_RUNTIME_CONF" "$RUNTIME_UNIT_DIR/$COROSYNC_RUNTIME_UNIT" || return $CRM_EX_ERROR
+
+ systemctl daemon-reload # Ignore failure
+ fi
+
+ echo "Installing $FENCE_DUMMY to $PCMK__FENCE_BINDIR ..."
+ mkdir -p "$PCMK__FENCE_BINDIR"
+ install -m 0755 "$FENCE_DUMMY" "$PCMK__FENCE_BINDIR" || return $CRM_EX_ERROR
+ for alias in $FENCE_DUMMY_ALIASES; do \
+ echo "Installing fence_dummy_$alias to $PCMK__FENCE_BINDIR ..."
+ ln -s "$FENCE_DUMMY" "$PCMK__FENCE_BINDIR/fence_dummy_$alias"
+ if [ $? -ne 0 ]; then
+ return $CRM_EX_ERROR
+ fi
+ done
+
+ echo "Installing $LSB_DUMMY to $INIT_DIR ..."
+ mkdir -p "$INIT_DIR"
+ install -m 0755 "$LSB_DUMMY" "$INIT_DIR" || return $CRM_EX_ERROR
+
+ if [ -d "$UPSTART_DIR" ] && [ -f "$UPSTART_DUMMY" ]; then
+ echo "Installing $UPSTART_DUMMY to $UPSTART_DIR ..."
+ install -m 0644 "$UPSTART_DUMMY" "$UPSTART_DIR" || return $CRM_EX_ERROR
+ fi
+ return $CRM_EX_OK
+}
+
+COMMAND=""
+while [ $# -gt 0 ] ; do
+ case "$1" in
+ --help)
+ echo "$HELP_TEXT"
+ exit $CRM_EX_OK
+ ;;
+ install|uninstall)
+ COMMAND="$1"
+ shift
+ ;;
+ *)
+ usage "unknown option '$1'"
+ ;;
+ esac
+done
+case "$COMMAND" in
+ install) support_install ;;
+ uninstall) support_uninstall ;;
+ *) usage "must specify command" ;;
+esac
diff --git a/cts/support/cts.conf b/cts/support/cts.conf
new file mode 100644
index 0000000..2f59b89
--- /dev/null
+++ b/cts/support/cts.conf
@@ -0,0 +1,3 @@
+[Service]
+Restart=always
+RestartSec=70
diff --git a/cts/support/fence_dummy.in b/cts/support/fence_dummy.in
new file mode 100644
index 0000000..63948c4
--- /dev/null
+++ b/cts/support/fence_dummy.in
@@ -0,0 +1,528 @@
+#!@PYTHON@
+"""Dummy fence agent for testing
+"""
+
+__copyright__ = "Copyright 2012-2023 the Pacemaker project contributors"
+__license__ = "GNU General Public License version 2 or later (GPLv2+) WITHOUT ANY WARRANTY"
+
+import io
+import os
+import re
+import sys
+import time
+import random
+import atexit
+import getopt
+
+AGENT_VERSION = "4.1.0"
+OCF_VERSION = "1.0"
+SHORT_DESC = "Dummy fence agent"
+LONG_DESC = """fence_dummy is a fake fencing agent which reports success
+based on its mode (pass|fail|random) without doing anything."""
+
+# Short options used: difhmnoqsvBDHMRUV
+ALL_OPT = {
+ "quiet" : {
+ "getopt" : "q",
+ "help" : "",
+ "order" : 50
+ },
+ "verbose" : {
+ "getopt" : "v",
+ "longopt" : "verbose",
+ "help" : "-v, --verbose Verbose mode",
+ "required" : "0",
+ "shortdesc" : "Verbose mode",
+ "order" : 51
+ },
+ "debug" : {
+ "getopt" : "D:",
+ "longopt" : "debug-file",
+ "help" : "-D, --debug-file=[debugfile] Debugging to output file",
+ "required" : "0",
+ "shortdesc" : "Write debug information to given file",
+ "order" : 52
+ },
+ "version" : {
+ "getopt" : "V",
+ "longopt" : "version",
+ "help" : "-V, --version Display version information and exit",
+ "required" : "0",
+ "shortdesc" : "Display version information and exit",
+ "order" : 53
+ },
+ "help" : {
+ "getopt" : "h",
+ "longopt" : "help",
+ "help" : "-h, --help Display this help and exit",
+ "required" : "0",
+ "shortdesc" : "Display help and exit",
+ "order" : 54
+ },
+ "action" : {
+ "getopt" : "o:",
+ "longopt" : "action",
+ "help" : "-o, --action=[action] Action: validate-all, status, list, reboot (default), off or on",
+ "required" : "1",
+ "shortdesc" : "Fencing Action",
+ "default" : "reboot",
+ "order" : 1
+ },
+ "nodename" : {
+ "getopt" : "N:",
+ "longopt" : "nodename",
+ "help" : "-N, --nodename Node name of fence target (ignored)",
+ "required" : "0",
+ "shortdesc" : "The node name of fence target (ignored)",
+ "order" : 2
+ },
+ "mode": {
+ "getopt" : "M:",
+ "longopt" : "mode",
+ "required" : "0",
+ "help" : "-M, --mode=(pass|fail|random) Exit status to return for non-monitor operations",
+ "shortdesc" : "Whether fence operations should always pass, always fail, or fail at random",
+ "order" : 3
+ },
+ "monitor_mode" : {
+ "getopt" : "m:",
+ "longopt" : "monitor_mode",
+ "help" : "-m, --monitor_mode=(pass|fail|random) Exit status to return for monitor operations",
+ "required" : "0",
+ "shortdesc" : "Whether monitor operations should always pass, always fail, or fail at random",
+ "order" : 3
+ },
+ "random_sleep_range": {
+ "getopt" : "R:",
+ "required" : "0",
+ "longopt" : "random_sleep_range",
+ "help" : "-R, --random_sleep_range=[seconds] Sleep between 1 and [seconds] before returning",
+ "shortdesc" : "Wait randomly between 1 and [seconds]",
+ "order" : 3
+ },
+ "mock_dynamic_hosts" : {
+ "getopt" : "H:",
+ "longopt" : "mock_dynamic_hosts",
+ "help" : "-H, --mock_dynamic_hosts=[list] What to return when dynamically queried for possible targets",
+ "required" : "0",
+ "shortdesc" : "A list of hosts we can fence",
+ "order" : 3
+ },
+ "delay" : {
+ "getopt" : "f:",
+ "longopt" : "delay",
+ "help" : "-f, --delay [seconds] Wait X seconds before fencing is started",
+ "required" : "0",
+ "shortdesc" : "Wait X seconds before fencing is started",
+ "default" : "0",
+ "order" : 3
+ },
+ "monitor_delay" : {
+ "getopt" : "d:",
+ "longopt" : "monitor_delay",
+ "help" : "-d, --monitor_delay [seconds] Wait X seconds before monitor completes",
+ "required" : "0",
+ "shortdesc" : "Wait X seconds before monitor completes",
+ "default" : "0",
+ "order" : 3
+ },
+ "off_delay" : {
+ "getopt" : "F:",
+ "longopt" : "off_delay",
+ "help" : "-F, --off_delay [seconds] Wait additional X seconds before off action",
+ "required" : "0",
+ "shortdesc" : "Wait additional X seconds before off action",
+ "default" : "0",
+ "order" : 3
+ },
+ "plug" : {
+ "getopt" : "n:",
+ "longopt" : "plug",
+ "help" : "-n, --plug=[id] Physical plug number on device (ignored)",
+ "required" : "1",
+ "shortdesc" : "Ignored",
+ "order" : 4
+ },
+ "port" : {
+ "getopt" : "n:",
+ "longopt" : "plug",
+ "help" : "-n, --plug=[id] Physical plug number on device (ignored)",
+ "required" : "1",
+ "shortdesc" : "Ignored",
+ "order" : 4
+ },
+ "switch" : {
+ "getopt" : "s:",
+ "longopt" : "switch",
+ "help" : "-s, --switch=[id] Physical switch number on device (ignored)",
+ "required" : "0",
+ "shortdesc" : "Ignored",
+ "order" : 4
+ },
+ "nodeid" : {
+ "getopt" : "i:",
+ "longopt" : "nodeid",
+ "help" : "-i, --nodeid Corosync id of fence target (ignored)",
+ "required" : "0",
+ "shortdesc" : "Ignored",
+ "order" : 4
+ },
+ "uuid" : {
+ "getopt" : "U:",
+ "longopt" : "uuid",
+ "help" : "-U, --uuid UUID of the VM to fence (ignored)",
+ "required" : "0",
+ "shortdesc" : "Ignored",
+ "order" : 4
+ }
+}
+
+auto_unfence = False
+no_reboot = False
+no_on = False
+
+def agent():
+ """ Return name this file was run as. """
+
+ return os.path.basename(sys.argv[0])
+
+
+def fail_usage(message):
+ """ Print a usage message and exit. """
+
+ sys.exit("%s\nPlease use '-h' for usage" % message)
+
+
+def show_docs(options):
+ """ Handle informational options (display info and exit). """
+
+ device_opt = options["device_opt"]
+
+ if "-h" in options:
+ usage(device_opt)
+ sys.exit(0)
+
+ if "-o" in options and options["-o"].lower() == "metadata":
+ if not os.path.exists(__file__ + ".fail"):
+ metadata(device_opt, options)
+ else:
+ os.remove(__file__ + ".fail")
+ sys.exit(0)
+
+ if "-V" in options:
+ print(AGENT_VERSION)
+ sys.exit(0)
+
+
+def sorted_options(avail_opt):
+ """ Return a list of all options, in their internally specified order. """
+
+ sorted_list = [(key, ALL_OPT[key]) for key in avail_opt]
+ sorted_list.sort(key=lambda x: x[1]["order"])
+ return sorted_list
+
+
+def usage(avail_opt):
+ """ Print a usage message. """
+
+ print("Usage:")
+ print("\t" + agent() + " [options]")
+ print("Options:")
+
+ for dummy, value in sorted_options(avail_opt):
+ if len(value["help"]) != 0:
+ print(" " + value["help"])
+
+
+def metadata(avail_opt, options):
+ """ Print agent metadata. """
+
+ # This log is just for testing handling of stderr output
+ print("asked for fence_dummy metadata", file=sys.stderr)
+
+ print("""<?xml version="1.0" ?>
+<resource-agent name="%s" shortdesc="%s" version="%s">
+ <version>%s</version>
+ <longdesc>%s</longdesc>
+ <parameters>""" % (agent(), SHORT_DESC, AGENT_VERSION, OCF_VERSION, LONG_DESC))
+
+ for option, dummy in sorted_options(avail_opt):
+ if "shortdesc" in ALL_OPT[option]:
+ print(' <parameter name="' + option + '" unique="0" ' +
+ 'required="' + ALL_OPT[option]["required"] + '">')
+
+ default = ""
+ default_name_arg = "-" + ALL_OPT[option]["getopt"][:-1]
+ default_name_no_arg = "-" + ALL_OPT[option]["getopt"]
+
+ if "default" in ALL_OPT[option]:
+ default = 'default="%s"' % str(ALL_OPT[option]["default"])
+ elif default_name_arg in options:
+ if options[default_name_arg]:
+ try:
+ default = 'default="%s"' % options[default_name_arg]
+ except TypeError:
+ ## @todo/@note: Currently there is no clean way how to handle lists
+ ## we can create a string from it but we can't set it on command line
+ default = 'default="%s"' % str(options[default_name_arg])
+ elif default_name_no_arg in options:
+ default = 'default="true"'
+
+ mixed = ALL_OPT[option]["help"]
+ ## split it between option and help text
+ res = re.compile(r"^(.*--\S+)\s+", re.IGNORECASE | re.S).search(mixed)
+ if None != res:
+ mixed = res.group(1)
+ mixed = mixed.replace("<", "&lt;").replace(">", "&gt;")
+ print(' <getopt mixed="' + mixed + '" />')
+
+ if ALL_OPT[option]["getopt"].count(":") > 0:
+ print(' <content type="string" ' + default + ' />')
+ else:
+ print(' <content type="boolean" ' + default + ' />')
+
+ print(' <shortdesc lang="en">' + ALL_OPT[option]["shortdesc"] + '</shortdesc>')
+ print(' </parameter>')
+
+ print(' </parameters>\n <actions>')
+ if not no_on:
+ if auto_unfence:
+ attr_name = 'automatic'
+ else:
+ attr_name = 'on_target'
+ print(' <action name="on" ' + attr_name + '="1" />')
+ print(' <action name="off" />')
+ if not no_reboot:
+ print(' <action name="reboot" />')
+ print(' <action name="status" />')
+ print(' <action name="monitor" />')
+ print(' <action name="metadata" />')
+ print(' <action name="list" />')
+ print(' </actions>')
+ print('</resource-agent>')
+
+
+def option_longopt(option):
+ """ Return the getopt-compatible long-option name of the given option. """
+
+ if ALL_OPT[option]["getopt"].endswith(":"):
+ return ALL_OPT[option]["longopt"] + "="
+ else:
+ return ALL_OPT[option]["longopt"]
+
+
+def opts_from_command_line(argv, avail_opt):
+ """ Read options from command-line arguments. """
+
+ # Prepare list of options for getopt
+ getopt_string = ""
+ longopt_list = []
+ for k in avail_opt:
+ if k in ALL_OPT:
+ getopt_string += ALL_OPT[k]["getopt"]
+ else:
+ fail_usage("Parse error: unknown option '"+k+"'")
+
+ if k in ALL_OPT and "longopt" in ALL_OPT[k]:
+ longopt_list.append(option_longopt(k))
+
+ try:
+ opt, dummy = getopt.gnu_getopt(argv, getopt_string, longopt_list)
+ except getopt.GetoptError as error:
+ fail_usage("Parse error: " + error.msg)
+
+ # Transform longopt to short one which are used in fencing agents
+ old_opt = opt
+ opt = {}
+ for old_option in dict(old_opt).keys():
+ if old_option.startswith("--"):
+ for option in ALL_OPT.keys():
+ if "longopt" in ALL_OPT[option] and "--" + ALL_OPT[option]["longopt"] == old_option:
+ opt["-" + ALL_OPT[option]["getopt"].rstrip(":")] = dict(old_opt)[old_option]
+ else:
+ opt[old_option] = dict(old_opt)[old_option]
+
+ # Compatibility Layer (with what? probably not needed for fence_dummy)
+ new_opt = dict(opt)
+ if "-T" in new_opt:
+ new_opt["-o"] = "status"
+ if "-n" in new_opt:
+ new_opt["-m"] = new_opt["-n"]
+ opt = new_opt
+
+ return opt
+
+
+def opts_from_stdin(avail_opt):
+ """ Read options from standard input. """
+
+ opt = {}
+ name = ""
+ for line in sys.stdin.readlines():
+ line = line.strip()
+ if line.startswith("#") or (len(line) == 0):
+ continue
+
+ (name, value) = (line + "=").split("=", 1)
+ value = value[:-1]
+
+ # Compatibility Layer (with what? probably not needed for fence_dummy)
+ if name == "option":
+ name = "action"
+
+ if name not in avail_opt:
+ print("Parse error: Ignoring unknown option '%s'" % line,
+ file=sys.stderr)
+ continue
+
+ if ALL_OPT[name]["getopt"].endswith(":"):
+ opt["-"+ALL_OPT[name]["getopt"].rstrip(":")] = value
+ elif value.lower() in ["1", "yes", "on", "true"]:
+ opt["-"+ALL_OPT[name]["getopt"]] = "1"
+
+ return opt
+
+
+def process_input(avail_opt):
+ """ Set standard environment variables, and parse all options. """
+
+ # Set standard environment
+ os.putenv("LANG", "C")
+ os.putenv("LC_ALL", "C")
+
+ # Read options from command line or standard input
+ if len(sys.argv) > 1:
+ return opts_from_command_line(sys.argv[1:], avail_opt)
+ else:
+ return opts_from_stdin(avail_opt)
+
+
+def atexit_handler():
+ """ Close stdout on exit. """
+
+ try:
+ sys.stdout.close()
+ os.close(1)
+ except IOError:
+ sys.exit("%s failed to close standard output" % agent())
+
+
+def success_mode(options, option, default_value):
+ """ Return exit code specified by option. """
+
+ if option in options:
+ test_value = options[option]
+ else:
+ test_value = default_value
+
+ if test_value == "pass":
+ exitcode = 0
+ elif test_value == "fail":
+ exitcode = 1
+ else:
+ exitcode = random.randint(0, 1)
+
+ return exitcode
+
+
+def write_options(options):
+ """ Write out all options to debug file. """
+
+ try:
+ debugfile = io.open(options["-D"], 'at')
+ debugfile.write("### %s ###\n" % (time.strftime("%Y-%m-%d %H:%M:%S")))
+ for option in sorted(options):
+ debugfile.write("%s=%s\n" % (option, options[option]))
+ debugfile.write("###\n")
+ debugfile.close()
+ except IOError:
+ pass
+
+
+def main():
+ """ Make it so! """
+
+ global auto_unfence
+ global no_reboot
+ global no_on
+
+ # Meta-data can't take parameters, so we simulate different meta-data
+ # behavior based on the executable name (which can be a symbolic link).
+ if sys.argv[0].endswith("_auto_unfence"):
+ auto_unfence = True
+ elif sys.argv[0].endswith("_no_reboot"):
+ no_reboot = True
+ elif sys.argv[0].endswith("_no_on"):
+ no_on = True
+
+ device_opt = ALL_OPT.keys()
+
+ ## Defaults for fence agent
+ atexit.register(atexit_handler)
+ options = process_input(device_opt)
+ options["device_opt"] = device_opt
+ show_docs(options)
+
+ if "-o" in options:
+ action = options["-o"]
+ else:
+ action = "reboot"
+
+ # dump input to file
+ if "-D" in options and action != "validate-all":
+ write_options(options)
+
+ if "-f" in options and action != "validate-all":
+ val = int(options["-f"])
+ print("delay sleep for %d seconds" % val, file=sys.stderr)
+ time.sleep(val)
+
+ # random sleep for testing
+ if "-R" in options and action != "validate-all":
+ val = int(options["-R"])
+ ran = random.randint(1, val)
+ print("random sleep for %d seconds" % ran, file=sys.stderr)
+ time.sleep(ran)
+
+ if action == "monitor":
+ if "-d" in options:
+ time.sleep(int(options["-d"]))
+ exitcode = success_mode(options, "-m", "pass")
+
+ elif action == "list":
+ print("fence_dummy action (list) called", file=sys.stderr)
+ if "-H" in options:
+ print(options["-H"])
+ exitcode = 0
+ else:
+ print("dynamic hostlist requires mock_dynamic_hosts to be set",
+ file=sys.stderr)
+ exitcode = 1
+
+ elif action == "validate-all":
+ if "-f" in options:
+ val = int(options["-f"])
+ if val > 10:
+ exitcode = 1
+ else:
+ exitcode = 0
+ else:
+ exitcode = 1
+
+ elif action == "off":
+ if "-F" in options:
+ time.sleep(int(options["-F"]))
+ exitcode = success_mode(options, "-M", "random")
+
+ else:
+ exitcode = success_mode(options, "-M", "random")
+
+ # Ensure we generate some error output on failure exit.
+ if exitcode == 1:
+ print("simulated %s failure" % action, file=sys.stderr)
+
+ sys.exit(exitcode)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/cts/support/pacemaker-cts-dummyd.conf b/cts/support/pacemaker-cts-dummyd.conf
new file mode 100644
index 0000000..3ecb1ac
--- /dev/null
+++ b/cts/support/pacemaker-cts-dummyd.conf
@@ -0,0 +1,2 @@
+description "Dummy Upstart service for Pacemaker's Cluster Test Suite"
+exec dd if=/dev/random of=/dev/null
diff --git a/cts/support/pacemaker-cts-dummyd.in b/cts/support/pacemaker-cts-dummyd.in
new file mode 100644
index 0000000..453a644
--- /dev/null
+++ b/cts/support/pacemaker-cts-dummyd.in
@@ -0,0 +1,55 @@
+#!@PYTHON@
+""" Slow-starting idle daemon that notifies systemd when it starts
+"""
+
+__copyright__ = "Copyright 2014-2020 the Pacemaker project contributors"
+__license__ = "GNU General Public License version 2 or later (GPLv2+) WITHOUT ANY WARRANTY"
+
+import sys
+import time
+import signal
+import subprocess
+have_systemd_daemon = True
+try:
+ import systemd.daemon
+except ImportError:
+ have_systemd_daemon = False
+
+delay = None
+
+def parse_args():
+ global delay
+
+ # Lone argument is a number of seconds to delay start and stop
+ if len(sys.argv) > 0:
+ try:
+ delay = float(sys.argv[1])
+ except ValueError:
+ delay = None
+
+
+def twiddle():
+ global delay
+
+ if delay is not None:
+ time.sleep(delay)
+
+
+def bye(signum, frame):
+ twiddle()
+ sys.exit(0)
+
+
+if __name__ == "__main__":
+
+ parse_args()
+ signal.signal(signal.SIGTERM, bye)
+ twiddle()
+ if have_systemd_daemon:
+ systemd.daemon.notify("READY=1")
+ else:
+ subprocess.call(["systemd-notify", "READY=1"])
+
+ # This isn't a "proper" daemon, but that would be overkill for testing purposes
+ while True:
+ time.sleep(600.0)
diff --git a/cts/support/pacemaker-cts-dummyd@.service.in b/cts/support/pacemaker-cts-dummyd@.service.in
new file mode 100644
index 0000000..6531e46
--- /dev/null
+++ b/cts/support/pacemaker-cts-dummyd@.service.in
@@ -0,0 +1,9 @@
+[Unit]
+Description=Dummy daemon for Pacemaker CTS testing
+
+[Service]
+Type=notify
+ExecStart=@CRM_DAEMON_DIR@/pacemaker-cts-dummyd %i
+
+[Install]
+DefaultInstance=0