diff options
Diffstat (limited to '')
-rw-r--r-- | agents/mpath/fence_mpath.py | 341 |
1 files changed, 341 insertions, 0 deletions
diff --git a/agents/mpath/fence_mpath.py b/agents/mpath/fence_mpath.py new file mode 100644 index 0000000..ee81eab --- /dev/null +++ b/agents/mpath/fence_mpath.py @@ -0,0 +1,341 @@ +#!@PYTHON@ -tt + +import sys +import stat +import re +import os +import time +import logging +import atexit +import ctypes +sys.path.append("@FENCEAGENTSLIBDIR@") +from fencing import fail_usage, run_command, atexit_handler, check_input, process_input, show_docs +from fencing import fence_action, all_opt, run_delay + +def get_status(conn, options): + del conn + status = "off" + for dev in options["devices"]: + is_block_device(dev) + if options["--plug"] in get_registration_keys(options, dev): + status = "on" + else: + logging.debug("No registration for key "\ + + options["--plug"] + " on device " + dev + "\n") + + if options["--action"] == "monitor": + dev_read(options) + + return status + + +def set_status(conn, options): + del conn + count = 0 + if options["--action"] == "on": + for dev in options["devices"]: + is_block_device(dev) + + register_dev(options, dev) + if options["--plug"] not in get_registration_keys(options, dev): + count += 1 + logging.debug("Failed to register key "\ + + options["--plug"] + "on device " + dev + "\n") + continue + dev_write(options, dev) + + if get_reservation_key(options, dev) is None \ + and not reserve_dev(options, dev) \ + and get_reservation_key(options, dev) is None: + count += 1 + logging.debug("Failed to create reservation (key="\ + + options["--plug"] + ", device=" + dev + ")\n") + + else: + dev_keys = dev_read(options) + + for dev in options["devices"]: + is_block_device(dev) + + if options["--plug"] in get_registration_keys(options, dev): + preempt_abort(options, dev_keys[dev], dev) + + for dev in options["devices"]: + if options["--plug"] in get_registration_keys(options, dev): + count += 1 + logging.debug("Failed to remove key "\ + + options["--plug"] + " on device " + dev + "\n") + continue + + if not get_reservation_key(options, dev): + count += 1 + logging.debug("No reservation exists on device " + dev + "\n") + if count: + logging.error("Failed to verify " + str(count) + " device(s)") + sys.exit(1) + + +# run command, returns dict, ret["rc"] = exit code; ret["out"] = output; +# ret["err"] = error +def run_cmd(options, cmd): + ret = {} + + if "--use-sudo" in options: + prefix = options["--sudo-path"] + " " + else: + prefix = "" + + (ret["rc"], ret["out"], ret["err"]) = run_command(options, + prefix + cmd) + ret["out"] = "".join([i for i in ret["out"] if i is not None]) + ret["err"] = "".join([i for i in ret["err"] if i is not None]) + return ret + + +# check if device exist and is block device +def is_block_device(dev): + if not os.path.exists(dev): + fail_usage("Failed: device \"" + dev + "\" does not exist") + if not stat.S_ISBLK(os.stat(dev).st_mode): + fail_usage("Failed: device \"" + dev + "\" is not a block device") + +# cancel registration +def preempt_abort(options, host, dev): + cmd = options["--mpathpersist-path"] + " -o --preempt-abort --prout-type=5 --param-rk=" + host +" --param-sark=" + options["--plug"] +" -d " + dev + return not bool(run_cmd(options, cmd)["rc"]) + +def register_dev(options, dev): + cmd = options["--mpathpersist-path"] + " -o --register --param-sark=" + options["--plug"] + " -d " + dev + #cmd return code != 0 but registration can be successful + return not bool(run_cmd(options, cmd)["rc"]) + +def reserve_dev(options, dev): + cmd = options["--mpathpersist-path"] + " -o --reserve --prout-type=5 --param-rk=" + options["--plug"] + " -d " + dev + return not bool(run_cmd(options, cmd)["rc"]) + +def get_reservation_key(options, dev): + cmd = options["--mpathpersist-path"] + " -i -r -d " + dev + out = run_cmd(options, cmd) + if out["rc"]: + fail_usage('Cannot get reservation key on device "' + dev + + '": ' + out["err"]) + match = re.search(r"\s+key\s*=\s*0x(\S+)\s+", out["out"], re.IGNORECASE) + return match.group(1) if match else None + +def get_registration_keys(options, dev, fail=True): + keys = [] + cmd = options["--mpathpersist-path"] + " -i -k -d " + dev + out = run_cmd(options, cmd) + if out["rc"]: + fail_usage('Cannot get registration keys on device "' + dev + + '": ' + out["err"], fail) + if not fail: + return [] + for line in out["out"].split("\n"): + match = re.search(r"\s+0x(\S+)\s*", line) + if match: + keys.append(match.group(1)) + return keys + +def dev_write(options, dev): + file_path = options["--store-path"] + "/mpath.devices" + + if not os.path.isdir(options["--store-path"]): + os.makedirs(options["--store-path"]) + + try: + store_fh = open(file_path, "a+") + except IOError: + fail_usage("Failed: Cannot open file \""+ file_path + "\"") + out = store_fh.read() + if not re.search(r"^" + dev + r"\s+", out): + store_fh.write(dev + "\t" + options["--plug"] + "\n") + store_fh.close() + +def dev_read(options, fail=True): + dev_key = {} + file_path = options["--store-path"] + "/mpath.devices" + try: + store_fh = open(file_path, "r") + except IOError: + if fail: + fail_usage("Failed: Cannot open file \"" + file_path + "\"") + else: + return None + # get not empty lines from file + for (device, key) in [line.strip().split() for line in store_fh if line.strip()]: + dev_key[device] = key + store_fh.close() + return dev_key + +def mpath_check_get_options(options): + try: + f = open("/etc/sysconfig/stonith", "r") + except IOError: + return options + + match = re.findall(r"^\s*(\S*)\s*=\s*(\S*)\s*", "".join(f.readlines()), re.MULTILINE) + + for m in match: + options[m[0].lower()] = m[1].lower() + + f.close() + + return options + +def mpath_check(hardreboot=False): + if len(sys.argv) >= 3 and sys.argv[1] == "repair": + return int(sys.argv[2]) + options = {} + options["--mpathpersist-path"] = "/usr/sbin/mpathpersist" + options["--store-path"] = "@STORE_PATH@" + options["--power-timeout"] = "5" + options["retry"] = "0" + options["retry-sleep"] = "1" + options = mpath_check_get_options(options) + if "verbose" in options and options["verbose"] == "yes": + logging.getLogger().setLevel(logging.DEBUG) + devs = dev_read(options, fail=False) + if not devs: + if "--suppress-errors" not in options: + logging.error("No devices found") + return 0 + for dev, key in list(devs.items()): + for n in range(int(options["retry"]) + 1): + if n > 0: + logging.debug("retry: " + str(n) + " of " + options["retry"]) + if key in get_registration_keys(options, dev, fail=False): + logging.debug("key " + key + " registered with device " + dev) + return 0 + else: + logging.debug("key " + key + " not registered with device " + dev) + + if n < int(options["retry"]): + time.sleep(float(options["retry-sleep"])) + logging.debug("key " + key + " registered with any devices") + + if hardreboot == True: + libc = ctypes.cdll['libc.so.6'] + libc.reboot(0x1234567) + return 2 + +def define_new_opts(): + all_opt["devices"] = { + "getopt" : "d:", + "longopt" : "devices", + "help" : "-d, --devices=[devices] List of devices to use for current operation", + "required" : "0", + "shortdesc" : "List of devices to use for current operation. Devices can \ +be comma-separated list of device-mapper multipath devices (eg. /dev/mapper/3600508b400105df70000e00000ac0000 or /dev/mapper/mpath1). \ +Each device must support SCSI-3 persistent reservations.", + "order": 1 + } + all_opt["key"] = { + "getopt" : "k:", + "longopt" : "key", + "help" : "-k, --key=[key] Replaced by -n, --plug", + "required" : "0", + "shortdesc" : "Replaced by port/-n/--plug", + "order": 1 + } + all_opt["suppress-errors"] = { + "getopt" : "", + "longopt" : "suppress-errors", + "help" : "--suppress-errors Suppress error log. Suppresses error logging when run from the watchdog service before pacemaker starts.", + "required" : "0", + "shortdesc" : "Error log suppression.", + "order": 4 + } + all_opt["mpathpersist_path"] = { + "getopt" : ":", + "longopt" : "mpathpersist-path", + "help" : "--mpathpersist-path=[path] Path to mpathpersist binary", + "required" : "0", + "shortdesc" : "Path to mpathpersist binary", + "default" : "@MPATH_PATH@", + "order": 200 + } + all_opt["store_path"] = { + "getopt" : ":", + "longopt" : "store-path", + "help" : "--store-path=[path] Path to directory containing cached keys", + "required" : "0", + "shortdesc" : "Path to directory where fence agent can store information", + "default" : "@STORE_PATH@", + "order": 200 + } + +def main(): + atexit.register(atexit_handler) + + device_opt = ["no_login", "no_password", "devices", "key", "sudo", \ + "fabric_fencing", "on_target", "store_path", \ + "suppress-errors", "mpathpersist_path", "force_on", "port", "no_port"] + + define_new_opts() + + all_opt["port"]["required"] = "0" + all_opt["port"]["help"] = "-n, --plug=[key] Key to use for the current operation" + all_opt["port"]["shortdesc"] = "Key to use for the current operation. \ +This key should be unique to a node and have to be written in \ +/etc/multipath.conf. For the \"on\" action, the key specifies the key use to \ +register the local node. For the \"off\" action, this key specifies the key to \ +be removed from the device(s)." + + # fence_mpath_check + if os.path.basename(sys.argv[0]) == "fence_mpath_check": + sys.exit(mpath_check()) + elif os.path.basename(sys.argv[0]) == "fence_mpath_check_hardreboot": + sys.exit(mpath_check(hardreboot=True)) + + options = check_input(device_opt, process_input(device_opt), other_conditions=True) + + # hack to remove list/list-status actions which are not supported + options["device_opt"] = [ o for o in options["device_opt"] if o != "separator" ] + + # workaround to avoid regressions + if "--key" in options: + options["--plug"] = options["--key"] + del options["--key"] + elif "--help" not in options and options["--action"] in ["off", "on", \ + "reboot", "status", "validate-all"] and "--plug" not in options: + stop_after_error = False if options["--action"] == "validate-all" else True + fail_usage("Failed: You have to enter plug number or machine identification", stop_after_error) + + docs = {} + docs["shortdesc"] = "Fence agent for multipath persistent reservation" + docs["longdesc"] = "fence_mpath is an I/O fencing agent that uses SCSI-3 \ +persistent reservations to control access multipath devices. Underlying \ +devices must support SCSI-3 persistent reservations (SPC-3 or greater) as \ +well as the \"preempt-and-abort\" subcommand.\nThe fence_mpath agent works by \ +having a unique key for each node that has to be set in /etc/multipath.conf. \ +Once registered, a single node will become the reservation holder \ +by creating a \"write exclusive, registrants only\" reservation on the \ +device(s). The result is that only registered nodes may write to the \ +device(s). When a node failure occurs, the fence_mpath agent will remove the \ +key belonging to the failed node from the device(s). The failed node will no \ +longer be able to write to the device(s). A manual reboot is required.\ +\n.P\n\ +When used as a watchdog device you can define e.g. retry=1, retry-sleep=2 and \ +verbose=yes parameters in /etc/sysconfig/stonith if you have issues with it \ +failing." + docs["vendorurl"] = "https://www.sourceware.org/dm/" + show_docs(options, docs) + + run_delay(options) + + # Input control BEGIN + if options["--action"] == "validate-all": + sys.exit(0) + + if not ("--devices" in options and options["--devices"]): + fail_usage("Failed: No devices found") + + options["devices"] = [d for d in re.split("\s*,\s*|\s+", options["--devices"].strip()) if d] + # Input control END + + result = fence_action(None, options, set_status, get_status) + sys.exit(result) + +if __name__ == "__main__": + main() |