summaryrefslogtreecommitdiffstats
path: root/agents/stonith
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--agents/stonith/Makefile.am19
-rwxr-xr-xagents/stonith/fence_legacy.in277
-rwxr-xr-xagents/stonith/fence_watchdog.in284
3 files changed, 580 insertions, 0 deletions
diff --git a/agents/stonith/Makefile.am b/agents/stonith/Makefile.am
new file mode 100644
index 0000000..e231775
--- /dev/null
+++ b/agents/stonith/Makefile.am
@@ -0,0 +1,19 @@
+#
+# Copyright 2003-2023 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.
+#
+
+include $(top_srcdir)/mk/common.mk
+include $(top_srcdir)/mk/man.mk
+
+sbin_SCRIPTS = fence_watchdog
+
+if BUILD_LHA_SUPPORT
+sbin_SCRIPTS += fence_legacy
+endif
+
+CLEANFILES = $(man7_MANS) $(man8_MANS)
diff --git a/agents/stonith/fence_legacy.in b/agents/stonith/fence_legacy.in
new file mode 100755
index 0000000..c0eeca1
--- /dev/null
+++ b/agents/stonith/fence_legacy.in
@@ -0,0 +1,277 @@
+#!@PYTHON@
+
+__copyright__ = "Copyright 2018-2023 the Pacemaker project contributors"
+__license__ = "GNU General Public License version 2 or later (GPLv2+) WITHOUT ANY WARRANTY"
+
+import os
+import sys
+import argparse
+import subprocess
+
+# These imports allow running from a source checkout after running `make`.
+# Note that while this doesn't necessarily mean it will successfully run tests,
+# but being able to see --help output can be useful.
+if os.path.exists("@abs_top_srcdir@/python"):
+ sys.path.insert(0, "@abs_top_srcdir@/python")
+
+if os.path.exists("@abs_top_builddir@/python") and "@abs_top_builddir@" != "@abs_top_srcdir@":
+ sys.path.insert(0, "@abs_top_builddir@/python")
+
+from pacemaker.exitstatus import ExitStatus
+
+VERSION = "1.1.0"
+
+USAGE = """Helper that presents a Pacemaker-style interface for Linux-HA stonith plugins
+
+Should never be invoked by the user directly
+
+
+Usage: fence_legacy [options]
+
+Options:
+ -h usage
+ -t <sub agent> sub agent
+ -n <name> nodename
+ -o <string> Action: on | off | reset (default) | stat | hostlist
+ -s <stonith> stonith command
+ -q quiet mode
+ -V version"""
+
+META_DATA = """<?xml version="1.0" ?>
+<resource-agent name="fence_pcmk" shortdesc="Helper that presents a Pacemaker-style interface for Linux-HA stonith plugins">
+<longdesc>
+This agent should never be invoked by the user directly.
+</longdesc>
+<vendor-url>https://www.clusterlabs.org/</vendor-url>
+<parameters>
+ <parameter name="action" unique="1" required="1">
+ <getopt mixed="-o &lt;action&gt;" />
+ <content type="string" default="disable" />
+ <shortdesc lang="en">Fencing Action</shortdesc>
+ </parameter>
+ <parameter name="port" unique="1" required="1">
+ <getopt mixed="-n &lt;id&gt;" />
+ <content type="string" />
+ <shortdesc lang="en">Physical plug number or name of virtual machine</shortdesc>
+ </parameter>
+ <parameter name="help" unique="1" required="0">
+ <getopt mixed="-h" />
+ <content type="string" />
+ <shortdesc lang="en">Display help and exit</shortdesc>
+ </parameter>
+</parameters>
+<actions>
+ <action name="enable" />
+ <action name="disable" />
+ <action name="reboot" />
+ <action name="off" />
+ <action name="on" />
+ <action name="status" />
+ <action name="list" />
+ <action name="metadata" />
+</actions>
+</resource-agent>"""
+
+ACTIONS = [
+ "on",
+ "off",
+ "reset",
+ "reboot",
+ "stat",
+ "status",
+ "metadata",
+ "monitor",
+ "list",
+ "hostlist",
+ "poweroff",
+ "poweron"
+]
+
+
+def parse_cli_options():
+ """ Return parsed command-line options (as argparse namespace) """
+
+
+ # Don't add standard help option, so we can format it how we want
+ parser = argparse.ArgumentParser(add_help=False)
+
+ parser.add_argument("-t", metavar="SUBAGENT", dest="subagent",
+ nargs=1, default="none", help="sub-agent")
+
+ parser.add_argument("-n", metavar="NODE", dest="node",
+ nargs=1, default="", help="name of target node")
+
+ # The help text here is consistent with the original version, though
+ # perhaps all actions should be listed.
+ parser.add_argument("-o", metavar="ACTION", dest="action",
+ nargs=1, choices=ACTIONS, default="reset",
+ help="action: on | off | reset (default) | stat | hostlist")
+
+ parser.add_argument("-s", metavar="COMMAND", dest="command",
+ nargs=1, default="stonith", help="stonith command")
+
+ parser.add_argument("-q", dest="quiet", action="store_true",
+ help="quiet mode")
+
+ parser.add_argument("-h", "--help", action="store_true",
+ help="show usage and exit")
+
+ # Don't use action="version", because that printed to stderr before
+ # Python 3.4, and help2man doesn't like that.
+ parser.add_argument("-V", "--version", action="store_true",
+ help="show version and exit")
+
+ return parser.parse_args()
+
+
+def parse_stdin_options(options):
+ """ Update options namespace with options parsed from stdin """
+
+ nlines = 0
+ for line in sys.stdin:
+ # Remove leading and trailing whitespace
+ line = line.strip()
+
+ # Skip blank lines and comments
+ if line == "" or line[0] == "#":
+ continue
+
+ nlines = nlines + 1
+
+ # Parse option name and value (allow whitespace around equals sign)
+ try:
+ (name, value) = line.split("=", 1)
+ name = name.rstrip()
+ if name == "":
+ raise ValueError
+ except ValueError:
+ print("parse error: illegal name in option %d" % nlines,
+ file=sys.stderr)
+ sys.exit(ExitStatus.INVALID_PARAM)
+ value = value.lstrip()
+
+ if name == "plugin":
+ options.subagent = value
+
+ elif name in [ "option", "action" ]:
+ options.action = value
+
+ elif name == "nodename":
+ options.node = value
+ os.environ[name] = value
+
+ elif name == "stonith":
+ options.command = value
+
+ elif name != "agent": # agent is used by fenced
+ os.environ[name] = value
+
+
+def normalize_options(options):
+ """ Use string rather than list of one string """
+
+ if not hasattr(options.subagent, "strip"):
+ options.subagent = options.subagent[0]
+
+ if not hasattr(options.node, "strip"):
+ options.node = options.node[0]
+
+ if not hasattr(options.action, "strip"):
+ options.action = options.action[0]
+
+ if not hasattr(options.command, "strip"):
+ options.command = options.command[0]
+
+
+def build_command(options):
+ """ Return command to execute (as list of arguments) """
+
+ if options.action in [ "hostlist", "list" ]:
+ extra_args = [ "-l" ]
+
+ elif options.action in [ "monitor", "stat", "status" ]:
+ extra_args = [ "-S" ]
+
+ else:
+ if options.node == "":
+ if not options.quiet:
+ print("failed: no plug number")
+ sys.exit(ExitStatus.ERROR)
+ extra_args = [ "-T", options.action, options.node ]
+
+ return [ options.command, "-t", options.subagent, "-E" ] + extra_args
+
+
+def handle_local_options(options):
+ """ Handle options that don't require the fence agent """
+
+ if options.help:
+ print(USAGE)
+ sys.exit(ExitStatus.OK)
+
+ if options.version:
+ print(VERSION)
+ sys.exit(ExitStatus.OK)
+
+
+def remap_action(options):
+ """ Pre-process requested action """
+
+ options.action = options.action.lower()
+
+ if options.action == "metadata":
+ print(META_DATA)
+ sys.exit(ExitStatus.OK)
+
+ elif options.action in [ "hostlist", "list" ]:
+ options.quiet = True
+
+ # Remap accepted aliases to their actual commands
+
+ elif options.action == "reboot":
+ options.action = "reset"
+
+ elif options.action == "poweron":
+ options.action = "on"
+
+ elif options.action == "poweroff":
+ options.action = "off"
+
+
+def execute_command(options, cmd):
+ """ Execute command and return its exit status """
+
+ if not options.quiet:
+ print("Performing: " + " ".join(cmd))
+ return subprocess.call(cmd)
+
+
+def handle_result(options, status):
+ """ Process fence agent result """
+
+ if status == 0:
+ message = "success"
+ exitcode = ExitStatus.OK
+ else:
+ message = "failed"
+ exitcode = ExitStatus.ERROR
+ if not options.quiet:
+ print("%s: %s %d" % (message, options.node, status))
+ sys.exit(exitcode)
+
+
+def main():
+ """ Execute an LHA-style fence agent """
+
+ options = parse_cli_options()
+ handle_local_options(options)
+ normalize_options(options)
+ parse_stdin_options(options)
+ remap_action(options)
+ cmd = build_command(options)
+ status = execute_command(options, cmd)
+ handle_result(options, status)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/agents/stonith/fence_watchdog.in b/agents/stonith/fence_watchdog.in
new file mode 100755
index 0000000..f43ab87
--- /dev/null
+++ b/agents/stonith/fence_watchdog.in
@@ -0,0 +1,284 @@
+#!@PYTHON@
+"""Dummy watchdog fence agent for providing meta-data for the pacemaker internal agent
+"""
+
+__copyright__ = "Copyright 2012-2022 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 atexit
+import getopt
+
+AGENT_VERSION = "1.0.0"
+SHORT_DESC = "Dummy watchdog fence agent"
+LONG_DESC = """fence_watchdog just provides
+meta-data - actual fencing is done by the pacemaker internal watchdog agent."""
+
+ALL_OPT = {
+ "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: metadata",
+ "required" : "1",
+ "shortdesc" : "Fencing Action",
+ "default" : "metadata",
+ "order" : 1
+ },
+ "nodename" : {
+ "getopt" : "N:",
+ "longopt" : "nodename",
+ "help" : "-N, --nodename Node name of fence target (ignored)",
+ "required" : "0",
+ "shortdesc" : "Ignored",
+ "order" : 2
+ },
+ "plug" : {
+ "getopt" : "n:",
+ "longopt" : "plug",
+ "help" : "-n, --plug=[id] Physical plug number on device (ignored)",
+ "required" : "1",
+ "shortdesc" : "Ignored",
+ "order" : 4
+ }
+}
+
+
+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":
+ metadata(device_opt, options)
+ 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(LONG_DESC)
+ print()
+ 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. """
+
+ print("""<?xml version="1.0" ?>
+<resource-agent name="%s" shortdesc="%s">
+<longdesc>%s</longdesc>
+<parameters>""" % (agent(), SHORT_DESC, LONG_DESC))
+
+ for option, dummy in sorted_options(avail_opt):
+ if "shortdesc" in ALL_OPT[option]:
+ print(' <parameter name="' + option +
+ '" 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>')
+ print(' <action name="on" />')
+ print(' <action name="off" />')
+ print(' <action name="reboot" />')
+ print(' <action name="monitor" />')
+ print(' <action name="list" />')
+ print(' <action name="metadata" />')
+ 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]
+
+ 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]
+
+ 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 main():
+ """ Make it so! """
+
+ 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)
+
+ print("Watchdog fencing may be initiated only by the cluster, not this agent.",
+ file=sys.stderr)
+
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()