diff --git a/fencing/fence_dummy b/fencing/fence_dummy index a44a9355e8..8137b8add7 100755 --- a/fencing/fence_dummy +++ b/fencing/fence_dummy @@ -1,419 +1,463 @@ #!/usr/bin/python """Dummy fence agent for testing """ # Pacemaker targets compatibility with Python 2.6+ and 3.2+ from __future__ import print_function, unicode_literals, absolute_import, division __copyright__ = "Copyright (C) 2012-2016 Andrew Beekhof " __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.0.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: ifhmnoqsvBDHMRUV -all_opt = { +ALL_OPT = { "quiet" : { "getopt" : "q", "help" : "", - "order" : 50 }, + "order" : 50 + }, "verbose" : { "getopt" : "v", "longopt" : "verbose", "help" : "-v, --verbose Verbose mode", "required" : "0", "shortdesc" : "Verbose mode", - "order" : 51 }, + "order" : 51 + }, "debug" : { "getopt" : "D:", - "longopt" : "debug-file", + "longopt" : "debug-file", "help" : "-D, --debug-file=[debugfile] Debugging to output file", "required" : "0", "shortdesc" : "Write debug information to given file", - "order" : 52 }, - "version" : { + "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 }, + "order" : 53 + }, "help" : { "getopt" : "h", "longopt" : "help", "help" : "-h, --help Display this help and exit", "required" : "0", "shortdesc" : "Display help and exit", - "order" : 54 }, + "order" : 54 + }, "action" : { "getopt" : "o:", "longopt" : "action", "help" : "-o, --action=[action] Action: status, list, reboot (default), off or on", "required" : "1", "shortdesc" : "Fencing Action", "default" : "reboot", - "order" : 1 }, + "order" : 1 + }, "nodename" : { "getopt" : "N:", "longopt" : "nodename", "help" : "-N, --nodename Node name of fence victim (ignored)", "required" : "0", "shortdesc" : "The node name of fence victim (ignored)", - "order" : 2 }, + "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 }, + "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 }, + "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 }, + "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 }, + "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 }, + "order" : 3 + }, "port" : { "getopt" : "n:", "longopt" : "plug", "help" : "-n, --plug=[id] Physical plug number on device (ignored)", "required" : "1", "shortdesc" : "Ignored", - "order" : 4 }, + "order" : 4 + }, "switch" : { "getopt" : "s:", "longopt" : "switch", "help" : "-s, --switch=[id] Physical switch number on device (ignored)", "required" : "0", "shortdesc" : "Ignored", - "order" : 4 }, + "order" : 4 + }, "nodeid" : { "getopt" : "i:", "longopt" : "nodeid", "help" : "-i, --nodeid Corosync id of fence victim (ignored)", "required" : "0", "shortdesc" : "Ignored", - "order" : 4 }, + "order" : 4 + }, "uuid" : { "getopt" : "U:", "longopt" : "uuid", "help" : "-U, --uuid UUID of the VM to fence (ignored)", "required" : "0", "shortdesc" : "Ignored", - "order" : 4 } + "order" : 4 + } } +def agent(): + """ Return name this file was run as. """ + + return os.path.basename(sys.argv[0]) + + def fail_usage(message): - sys.stderr.write("%s\nPlease use '-h' for usage\n" % message) - sys.exit(1) + """ 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: + 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): - global all_opt + """ Print a usage message. """ print("Usage:") - print("\t" + os.path.basename(sys.argv[0]) + " [options]") + print("\t" + agent() + " [options]") print("Options:") - sorted_list = [ (key, all_opt[key]) for key in avail_opt ] - sorted_list.sort(key=lambda x: x[1]["order"]) - - for key, value in sorted_list: + for dummy, value in sorted_options(avail_opt): if len(value["help"]) != 0: print(" " + value["help"]) def metadata(avail_opt, options): - global all_opt + """ Print agent metadata. """ # This log is just for testing handling of stderr output - sys.stderr.write("asked for fence_dummy metadata\n") - - sorted_list = [ (key, all_opt[key]) for key in avail_opt ] - sorted_list.sort(key=lambda x: x[1]["order"]) + print("asked for fence_dummy metadata", file=sys.stderr) print(""" %s %s -""" % (os.path.basename(sys.argv[0]), SHORT_DESC, - AGENT_VERSION, OCF_VERSION, LONG_DESC)) +""" % (agent(), SHORT_DESC, AGENT_VERSION, OCF_VERSION, LONG_DESC)) - for option, value in sorted_list: - if "shortdesc" in all_opt[option]: - print("\t") + for option, dummy in sorted_options(avail_opt): + if "shortdesc" in ALL_OPT[option]: + print("\t") default = "" - if "default" in all_opt[option]: - default = "default=\""+str(all_opt[option]["default"])+"\"" - elif ("-" + all_opt[option]["getopt"][:-1]) in options: - if options["-" + all_opt[option]["getopt"][:-1]]: + 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=\"" + options["-" + all_opt[option]["getopt"][:-1]] + "\"" + 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=\"" + str(options["-" + all_opt[option]["getopt"][:-1]]) +"\"" - elif ("-" + all_opt[option]["getopt"]) in options: - default = "default=\"true\" " + default = 'default="%s"' % str(options[default_name_arg]) + elif default_name_no_arg in options: + default = 'default="true"' - mixed = all_opt[option]["help"] + mixed = ALL_OPT[option]["help"] ## split it between option and help text - res = re.compile("^(.*--\S+)\s+", re.IGNORECASE | re.S).search(mixed) - if (None != res): + res = re.compile(r"^(.*--\S+)\s+", re.IGNORECASE | re.S).search(mixed) + if None != res: mixed = res.group(1) mixed = mixed.replace("<", "<").replace(">", ">") print("\t\t") - if all_opt[option]["getopt"].count(":") > 0: + if ALL_OPT[option]["getopt"].count(":") > 0: print("\t\t") else: print("\t\t") - - print("\t\t" + all_opt[option]["shortdesc"] + "") + + print("\t\t" + ALL_OPT[option]["shortdesc"] + "") print("\t") print(""" \t \t \t \t \t \t \t """) -def process_input(avail_opt): - global all_opt +def option_longopt(option): + """ Return the getopt-compatible long-option name of the given option. """ - ## - ## Set standard environment - ##### - os.putenv("LANG", "C") - os.putenv("LC_ALL", "C") + 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 - ##### + # Prepare list of options for getopt getopt_string = "" - longopt_list = [ ] + longopt_list = [] for k in avail_opt: - if k in all_opt: - getopt_string += all_opt[k]["getopt"] + 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]: - if all_opt[k]["getopt"].endswith(":"): - longopt_list.append(all_opt[k]["longopt"] + "=") - else: - longopt_list.append(all_opt[k]["longopt"]) + 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 - ## - ## Read options from command line or standard input - ##### + 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: - try: - opt, args = getopt.gnu_getopt(sys.argv[1:], 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 o in dict(old_opt).keys(): - if o.startswith("--"): - for x in all_opt.keys(): - if "longopt" in all_opt[x] and "--" + all_opt[x]["longopt"] == o: - opt["-" + all_opt[x]["getopt"].rstrip(":")] = dict(old_opt)[o] - else: - opt[o] = dict(old_opt)[o] - - ## Compatibility Layer - ##### - z = dict(opt) - if "-T" in z: - z["-o"] = "status" - if "-n" in z: - z["-m"] = z["-n"] - - opt = z - ## - ##### + return opts_from_command_line(sys.argv[1:], avail_opt) else: - 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 - ###### - if name == "option": - name = "action" - - ## - ###### - if name not in avail_opt: - sys.stderr.write("Parse error: Ignoring unknown option '"+line+"'\n") - continue - - if all_opt[name]["getopt"].endswith(":"): - opt["-"+all_opt[name]["getopt"].rstrip(":")] = value - elif ((value == "1") or (value.lower() == "yes") or (value.lower() == "on") or (value.lower() == "true")): - opt["-"+all_opt[name]["getopt"]] = "1" - return opt + return opts_from_stdin(avail_opt) def atexit_handler(): + """ Close stdout on exit. """ + try: sys.stdout.close() os.close(1) except IOError: - sys.stderr.write("%s failed to close standard output\n"%(sys.argv[0])) - sys.exit(1) + 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(): - global all_opt - device_opt = all_opt.keys() + """ 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) # dump input to file if "-D" in options: - try: - f = io.open(options["-D"], 'at') - f.write("### %s ###\n" % (time.strftime("%Y-%m-%d %H:%M:%S"))) - for v in sorted(options): - f.write("%s=%s\n" % (v, options[v])) - f.write("###\n") - f.close() - except IOError: - pass + write_options(options) if "-f" in options: val = int(options["-f"]) - sys.stderr.write("delay sleep for %d seconds\n" % val) + print("delay sleep for %d seconds" % val, file=sys.stderr) time.sleep(val) # random sleep for testing if "-R" in options: val = int(options["-R"]) ran = random.randint(1, val) - sys.stderr.write("random sleep for %d seconds\n" % ran) + print("random sleep for %d seconds" % ran, file=sys.stderr) time.sleep(ran) if "-o" in options: action = options["-o"] else: action = "action" if action == "monitor": exitcode = success_mode(options, "-m", "pass") elif action == "list": - sys.stderr.write("fence_dummy action (list) called\n") + print("fence_dummy action (list) called", file=sys.stderr) if "-H" in options: print(options["-H"]) exitcode = 0 else: - sys.stderr.write("were asked for hostlist but attribute mock_dynamic_hosts wasn't set\n") + print("dynamic hostlist requires mock_dynamic_hosts to be set", + file=sys.stderr) exitcode = 1 else: exitcode = success_mode(options, "-M", "random") # Ensure we generate some error output on failure exit. if exitcode == 1: - sys.stderr.write("simulated %s failure\n" % action) + print("simulated %s failure" % action, file=sys.stderr) sys.exit(exitcode) if __name__ == "__main__": main()