diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 06:50:17 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 06:50:17 +0000 |
commit | 86ed03f8adee56c050c73018537371c230a664a6 (patch) | |
tree | eae3d04cdf1c49848e5a671327ab38297f4acb0d /agents/autodetect | |
parent | Initial commit. (diff) | |
download | fence-agents-86ed03f8adee56c050c73018537371c230a664a6.tar.xz fence-agents-86ed03f8adee56c050c73018537371c230a664a6.zip |
Adding upstream version 4.12.1.upstream/4.12.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'agents/autodetect')
-rw-r--r-- | agents/autodetect/a.py | 8 | ||||
-rwxr-xr-x | agents/autodetect/autodetect.py | 255 | ||||
-rwxr-xr-x | agents/autodetect/autodetect_test.py | 33 | ||||
-rw-r--r-- | agents/autodetect/b.py | 2 | ||||
-rw-r--r-- | agents/autodetect/fence_apc.py | 259 | ||||
-rw-r--r-- | agents/autodetect/fence_bladecenter.py | 111 | ||||
-rw-r--r-- | agents/autodetect/fence_brocade.py | 78 | ||||
-rw-r--r-- | agents/autodetect/fence_ilo_moonshot.py | 69 | ||||
-rw-r--r-- | agents/autodetect/fence_lpar.py | 159 | ||||
-rw-r--r-- | agents/autodetect/fencing.py | 1393 |
10 files changed, 2367 insertions, 0 deletions
diff --git a/agents/autodetect/a.py b/agents/autodetect/a.py new file mode 100644 index 0000000..608d0f8 --- /dev/null +++ b/agents/autodetect/a.py @@ -0,0 +1,8 @@ +from b import myf + +def maf(): + return 5 + +def maf2(): + return myf() +
\ No newline at end of file diff --git a/agents/autodetect/autodetect.py b/agents/autodetect/autodetect.py new file mode 100755 index 0000000..24d9a73 --- /dev/null +++ b/agents/autodetect/autodetect.py @@ -0,0 +1,255 @@ +#!/usr/bin/python + +import pexpect +import re +import logging +import time +import sys +import fencing + +import fence_apc +import fence_bladecenter +import fence_brocade +import fence_rsa + +def check_agent(conn, options, found_prompt, prompts, test_fn, eol=None): + options["--action"] = "list" + options["--command-prompt"] = found_prompt + if any(x in options["--command-prompt"][0] for x in prompts): + options["--command-prompt"] = prompts + + return test_fn(conn, options) + return False + +def get_list(conn, options, found_prompt, prompts, list_fn, eol=None): + def test_fn(conn, options): + if len(list_fn(conn, options)) > 0: + return True + else: + return False + + return check_agent(conn, options, found_prompt, prompts, test_fn, eol) + +""" *************************** MAIN ******************************** """ + + +def detect_login_telnet(options): + options["--ipport"] = 23 + re_login_string = r"([\r\n])((?!Last )login\s*:)|((?!Last )Login Name: )|(username: )|(User Name :)" + re_login = re.compile(re_login_string, re.IGNORECASE) + re_pass = re.compile("(password)|(pass phrase)", re.IGNORECASE) + + options["eol"] = "\r\n" + conn = fencing.fspawn(options, options["--telnet-path"]) + conn.send("set binary\n") + conn.send("open %s -%s\n"%(options["--ip"], options["--ipport"])) + + conn.log_expect(re_login, int(options["--login-timeout"])) + conn.send_eol(options["--username"]) + + ## automatically change end of line separator + screen = conn.read_nonblocking(size=100, timeout=int(options["--shell-timeout"])) + if re_login.search(screen) != None: + options["eol"] = "\n" + conn.send_eol(options["--username"]) + conn.log_expect(re_pass, int(options["--login-timeout"])) + elif re_pass.search(screen) == None: + conn.log_expect(re_pass, int(options["--shell-timeout"])) + + try: + conn.send_eol(options["--password"]) + valid_password = conn.log_expect([re_login] + \ + [pexpect.TIMEOUT], int(options["--shell-timeout"])) + if valid_password == 0: + ## password is invalid or we have to change EOL separator + options["eol"] = "\r" + conn.send_eol("") + screen = conn.read_nonblocking(size=100, timeout=int(options["--shell-timeout"])) + ## after sending EOL the fence device can either show 'Login' or 'Password' + if re_login.search(conn.after + screen) != None: + conn.send_eol("") + conn.send_eol(options["--username"]) + conn.log_expect(re_pass, int(options["--login-timeout"])) + conn.send_eol(options["--password"]) + conn.log_expect(pexpect.TIMEOUT, int(options["--login-timeout"])) + except KeyError: + fencing.fail(fencing.EC_PASSWORD_MISSING) + + found_cmd_prompt = guess_prompt(conn, options, conn.before) + return (found_cmd_prompt, conn) + +def guess_prompt(conn, options, before=""): + time.sleep(2) + conn.send_eol("") + conn.send_eol("") + + conn.log_expect(pexpect.TIMEOUT, int(options["--login-timeout"])) + lines = re.split(r'\r|\n', before + conn.before) + logging.info("Cmd-prompt candidate: %s" % (lines[-1])) + if lines.count(lines[-1]) >= 3: + found_cmd_prompt = ["\n" + lines[-1]] + else: + if lines.count(lines[-1]) == 2: + conn.log_expect(lines[-1], int(options["--shell-timeout"])) + conn.log_expect(lines[-1], int(options["--shell-timeout"])) + options["eol"] = "\r" + conn.send_eol("") + time.sleep(0.1) + conn.send_eol("") + time.sleep(0.1) + conn.send_eol("") + time.sleep(0.1) + conn.log_expect(pexpect.TIMEOUT, int(options["--login-timeout"])) + lines = re.split(r'\r|\n', conn.before) + logging.info("Cmd-prompt candidate: %s" % (lines[1])) + if lines.count(lines[-1]) >= 3: + found_cmd_prompt = ["\n" + lines[-1]] + else: + print "Unable to obtain command prompt automatically" + sys.exit(1) + else: + print "Unable to obtain command prompt automatically" + print lines[-1] + print conn.before + sys.exit(1) + + conn.log_expect(found_cmd_prompt, int(options["--shell-timeout"])) + conn.log_expect(found_cmd_prompt, int(options["--shell-timeout"])) + conn.log_expect(found_cmd_prompt, int(options["--shell-timeout"])) + + # Handle situation when CR/LF is interpreted as ENTER, ENTER + # In such case we will have get two additional command prompts to get on right position + res = conn.log_expect([pexpect.TIMEOUT] + found_cmd_prompt, int(options["--shell-timeout"])) + if res > 0: + # @note: store that information? + print "CMD twice" + conn.log_expect(found_cmd_prompt, int(options["--shell-timeout"])) + return found_cmd_prompt + +def detect_login_ssh(options, version=2): + options["--ipport"] = 22 + if version == "1": + command = '%s %s@%s -p %s -1 -c blowfish -o PubkeyAuthentication=no' % (options["--ssh-path"], options["--username"], options["--ip"], options["--ipport"]) + else: + command = '%s %s@%s -p %s -o PubkeyAuthentication=no' % (options["--ssh-path"], options["--username"], options["--ip"], options["--ipport"]) + + conn = fencing.fspawn(options, command) + result = conn.log_expect(["ssword:", "Are you sure you want to continue connecting (yes/no)?"], int(options["--login-timeout"])) + if result == 1: + conn.send("yes\n") + conn.log_expect("ssword:", int(options["--login-timeout"])) + + conn.send(options["--password"] + "\n") + + found_cmd_prompt = guess_prompt(conn, options, conn.before) + return (found_cmd_prompt, conn) + +def detect_device(conn, options, found_cmd_prompt): + if get_list(conn, options, found_cmd_prompt, prompts=["\n>", "\napc>"], list_fn=fence_apc.get_power_status): + fencing.fence_logout(conn, "4") + return "fence_apc # older serie" + + if get_list(conn, options, found_cmd_prompt, prompts=["\n>", "\napc>"], list_fn=fence_apc.get_power_status5): + fencing.fence_logout(conn, "exit") + return "fence_apc # v5+" + + ## Test fence_lpar with list action (HMC version 3 and 4) + def test_lpar(conn, options): + conn.send_eol("lssyscfg; echo $?") + conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) + if "\n0\r\n" in conn.before: + return True + else: + return False + + if check_agent(conn, options, found_cmd_prompt, [r":~>", r"]\$", r"\$ "], test_lpar): + fencing.fence_logout(conn, "quit") + return "fence_lpar # 2" + + if get_list(conn, options, found_cmd_prompt, prompts=["system>"], list_fn=fence_bladecenter.get_blades_list): + fencing.fence_logout(conn, "exit") + return "fence_bladecenter #2" + + if get_list(conn, options, found_cmd_prompt, prompts=["> "], list_fn=fence_brocade.get_power_status, eol="\n"): + fencing.fence_logout(conn, "exit") + return "fence_brocade #2" + + if get_list(conn, options, found_cmd_prompt, prompts=["> "], list_fn=fence_rsa.get_power_status): + fencing.fence_logout(conn, "exit") + return "fence_rsa" + + return None + +# Test fence ilo moonshot +#cmd_possible = ["MP>", "hpiLO->"] +#options["--action"] = "list" +#options["--command-prompt"] = found_cmd_prompt +#options["eol"] = "\n" +#if any(x in options["--command-prompt"][0] for x in cmd_possible): +# options["--command-prompt"] = cmd_possible +# +# plugs = fence_ilo_moonshot.get_power_status(conn, options) +# if len(plugs) > 0: +# print "fence_ilo_moonshot # " +# fencing.fence_logout(conn, "exit") +# sys.exit(0) + +def xxx(): + ## login mechanism as in fencing.py.py - differences is that we do not know command prompt + #logging.getLogger().setLevel(logging.DEBUG) + options = {} + options["--ssh-path"] = "/usr/bin/ssh" + options["--telnet-path"] = "/usr/bin/telnet" + + # virtual machine + #options["--username"] = "marx" + #options["--ip"] = "localhost" + #options["--password"] = "batalion" + + # APC + #options["--username"] = "labuser" + #options["--ip"] = "pdu-bar.englab.brq.redhat.com" + #options["--password"] = "labuser" + + # LPAR + options["--username"] = "rhts" + options["--ip"] = "ppc-hmc-01.mgmt.lab.eng.bos.redhat.com" + #options["--ip"] = "ibm-js22-vios-02.rhts.eng.bos.redhat.com" + options["--password"] = "100yard-" + + # Bladecenter + options["--ip"] = "blade-mm.englab.brq.redhat.com" + + # Brocade + #options["--ip"] = "hp-fcswitch-01.lab.bos.redhat.com" + #options["--password"] = "password" + #options["--username"] = "admin" + + # iLO Moonshot - chova sa to divne + #options["--password"] = "Access@gis" + #options["--username"] = "rcuser" + #options["--ip"] = "hp-m1500-mgmt.gsslab.pnq.redhat.com" + + #options["--ip"] = "ibm-x3755-01-rsa.ovirt.rhts.eng.bos.redhat.com" + #options["--username"] = "USERID" + #options["--password"] = "PASSW0RD" + + options["--login-timeout"] = "10" + options["--shell-timeout"] = "5" + options["--power-timeout"] = "10" + + options["eol"] = "\r\n" + + (found_cmd_prompt, conn) = detect_login_telnet(options) + #(found_cmd_prompt, conn) = detect_login_ssh(options) + + res = detect_device(conn, options, found_cmd_prompt) + if not res is None: + print res + sys.exit(0) + else: + ## Nothing found + sys.exit(2) + +if __name__ == "__main__": + xxx() diff --git a/agents/autodetect/autodetect_test.py b/agents/autodetect/autodetect_test.py new file mode 100755 index 0000000..a18aaed --- /dev/null +++ b/agents/autodetect/autodetect_test.py @@ -0,0 +1,33 @@ +#!/usr/bin/python + +import unittest +import autodetect as detect + +class TestDetectDevice(unittest.TestCase): + options = {} + + def setUp(self): + self.options = {} + self.options["--ssh-path"] = "/usr/bin/ssh" + self.options["--telnet-path"] = "/usr/bin/telnet" + self.options["--login-timeout"] = "10" + self.options["--shell-timeout"] = "5" + self.options["--power-timeout"] = "10" + self.options["eol"] = "\r\n" + + def test_bladecenter(self): + self.options["--username"] = "rhts" + self.options["--password"] = "100yard-" + self.options["--ip"] = "blade-mm.englab.brq.redhat.com" + + (found_cmd_prompt, conn) = detect.detect_login_telnet(self.options) + res = detect.detect_device(conn, self.options, found_cmd_prompt) + self.assertEqual('fence_bladecenter', res) + + def test_apc5(self): + self.assertEqual('foo', 'foo') + self.options["c"] = "c" + print self.options + +if __name__ == "__main__": + unittest.main() diff --git a/agents/autodetect/b.py b/agents/autodetect/b.py new file mode 100644 index 0000000..fdfda59 --- /dev/null +++ b/agents/autodetect/b.py @@ -0,0 +1,2 @@ +def myf(): + return 3 diff --git a/agents/autodetect/fence_apc.py b/agents/autodetect/fence_apc.py new file mode 100644 index 0000000..c6dd106 --- /dev/null +++ b/agents/autodetect/fence_apc.py @@ -0,0 +1,259 @@ +#!/usr/bin/python -tt + +##### +## +## The Following Agent Has Been Tested On: +## +## Model Firmware +## +---------------------------------------------+ +## AP7951 AOS v2.7.0, PDU APP v2.7.3 +## AP7941 AOS v3.5.7, PDU APP v3.5.6 +## AP9606 AOS v2.5.4, PDU APP v2.7.3 +## +## @note: ssh is very slow on AP79XX devices protocol (1) and +## cipher (des/blowfish) have to be defined +##### + +import sys, re +import atexit +sys.path.append("@FENCEAGENTSLIBDIR@") +from fencing import * +from fencing import fail, fail_usage, EC_STATUS + +#BEGIN_VERSION_GENERATION +RELEASE_VERSION="New APC Agent - test release on steroids" +REDHAT_COPYRIGHT="" +BUILD_DATE="March, 2008" +#END_VERSION_GENERATION + +def get_power_status(conn, options): + exp_result = 0 + outlets = {} + + conn.send_eol("1") + conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) + + version = 0 + admin = 0 + switch = 0 + + if None != re.compile('.* MasterSwitch plus.*', re.IGNORECASE | re.S).match(conn.before): + switch = 1 + if None != re.compile('.* MasterSwitch plus 2', re.IGNORECASE | re.S).match(conn.before): + if not options.has_key("--switch"): + fail_usage("Failed: You have to enter physical switch number") + else: + if not options.has_key("--switch"): + options["--switch"] = "1" + + if None == re.compile('.*Outlet Management.*', re.IGNORECASE | re.S).match(conn.before): + version = 2 + else: + version = 3 + + if None == re.compile('.*Outlet Control/Configuration.*', re.IGNORECASE | re.S).match(conn.before): + admin = 0 + else: + admin = 1 + + if switch == 0: + if version == 2: + if admin == 0: + conn.send_eol("2") + else: + conn.send_eol("3") + else: + conn.send_eol("2") + conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) + conn.send_eol("1") + else: + conn.send_eol(options["--switch"]) + + while True: + exp_result = conn.log_expect( + ["Press <ENTER>"] + options["--command-prompt"], int(options["--shell-timeout"])) + lines = conn.before.split("\n") + show_re = re.compile(r'(^|\x0D)\s*(\d+)- (.*?)\s+(ON|OFF)\s*') + for line in lines: + res = show_re.search(line) + if res != None: + outlets[res.group(2)] = (res.group(3), res.group(4)) + conn.send_eol("") + if exp_result != 0: + break + conn.send(chr(03)) + conn.log_expect("- Logout", int(options["--shell-timeout"])) + conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) + + if ["list", "monitor"].count(options["--action"]) == 1: + return outlets + else: + try: + (_, status) = outlets[options["--plug"]] + return status.lower().strip() + except KeyError: + fail(EC_STATUS) + +def set_power_status(conn, options): + action = { + 'on' : "1", + 'off': "2" + }[options["--action"]] + + conn.send_eol("1") + conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) + + version = 0 + admin2 = 0 + admin3 = 0 + switch = 0 + + if None != re.compile('.* MasterSwitch plus.*', re.IGNORECASE | re.S).match(conn.before): + switch = 1 + ## MasterSwitch has different schema for on/off actions + action = { + 'on' : "1", + 'off': "3" + }[options["--action"]] + if None != re.compile('.* MasterSwitch plus 2', re.IGNORECASE | re.S).match(conn.before): + if not options.has_key("--switch"): + fail_usage("Failed: You have to enter physical switch number") + else: + if not options.has_key("--switch"): + options["--switch"] = 1 + + if None == re.compile('.*Outlet Management.*', re.IGNORECASE | re.S).match(conn.before): + version = 2 + else: + version = 3 + + if None == re.compile('.*Outlet Control/Configuration.*', re.IGNORECASE | re.S).match(conn.before): + admin2 = 0 + else: + admin2 = 1 + + if switch == 0: + if version == 2: + if admin2 == 0: + conn.send_eol("2") + else: + conn.send_eol("3") + else: + conn.send_eol("2") + conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) + if None == re.compile('.*2- Outlet Restriction.*', re.IGNORECASE | re.S).match(conn.before): + admin3 = 0 + else: + admin3 = 1 + conn.send_eol("1") + else: + conn.send_eol(options["--switch"]) + + while 0 == conn.log_expect( + ["Press <ENTER>"] + options["--command-prompt"], int(options["--shell-timeout"])): + conn.send_eol("") + + conn.send_eol(options["--plug"]+"") + conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) + + if switch == 0: + if admin2 == 1: + conn.send_eol("1") + conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) + if admin3 == 1: + conn.send_eol("1") + conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) + else: + conn.send_eol("1") + conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) + + conn.send_eol(action) + conn.log_expect("Enter 'YES' to continue or <ENTER> to cancel :", int(options["--shell-timeout"])) + conn.send_eol("YES") + conn.log_expect("Press <ENTER> to continue...", int(options["--power-timeout"])) + conn.send_eol("") + conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) + conn.send(chr(03)) + conn.log_expect("- Logout", int(options["--shell-timeout"])) + conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) + +def get_power_status5(conn, options): + outlets = {} + + conn.send_eol("olStatus all") + + conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) + lines = conn.before.split("\n") + + show_re = re.compile(r'^\s*(\d+): (.*): (On|Off)\s*$', re.IGNORECASE) + + for line in lines: + res = show_re.search(line) + if res != None: + outlets[res.group(1)] = (res.group(2), res.group(3)) + + if ["list", "monitor"].count(options["--action"]) == 1: + return outlets + else: + try: + (_, status) = outlets[options["--plug"]] + return status.lower().strip() + except KeyError: + fail(EC_STATUS) + +def set_power_status5(conn, options): + action = { + 'on' : "olOn", + 'off': "olOff" + }[options["--action"]] + + conn.send_eol(action + " " + options["--plug"]) + conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) + +def main(): + device_opt = ["ipaddr", "login", "passwd", "cmd_prompt", "secure", \ + "port", "switch", "telnet"] + + atexit.register(atexit_handler) + + all_opt["cmd_prompt"]["default"] = ["\n>", "\napc>"] + all_opt["ssh_options"]["default"] = "-1 -c blowfish" + + options = check_input(device_opt, process_input(device_opt)) + + docs = {} + docs["shortdesc"] = "Fence agent for APC over telnet/ssh" + docs["longdesc"] = "fence_apc is an I/O Fencing agent \ +which can be used with the APC network power switch. It logs into device \ +via telnet/ssh and reboots a specified outlet. Lengthy telnet/ssh connections \ +should be avoided while a GFS cluster is running because the connection \ +will block any necessary fencing actions." + docs["vendorurl"] = "http://www.apc.com" + show_docs(options, docs) + + ## Support for --plug [switch]:[plug] notation that was used before + if (options.has_key("--plug") == 1) and (-1 != options["--plug"].find(":")): + (switch, plug) = options["--plug"].split(":", 1) + options["--switch"] = switch + options["--plug"] = plug + + ## + ## Operate the fencing device + #### + conn = fence_login(options) + + ## Detect firmware version (ASCII menu vs command-line interface) + ## and continue with proper action + #### + result = -1 + firmware_version = re.compile(r'\s*v(\d)*\.').search(conn.before) + if (firmware_version != None) and (firmware_version.group(1) in [ "5", "6" ]): + result = fence_action(conn, options, set_power_status5, get_power_status5, get_power_status5) + else: + result = fence_action(conn, options, set_power_status, get_power_status, get_power_status) + + fence_logout(conn, "4") + sys.exit(result) + +if __name__ == "__main__": + main() diff --git a/agents/autodetect/fence_bladecenter.py b/agents/autodetect/fence_bladecenter.py new file mode 100644 index 0000000..d72c07f --- /dev/null +++ b/agents/autodetect/fence_bladecenter.py @@ -0,0 +1,111 @@ +#!/usr/bin/python -tt + +##### +## +## The Following Agent Has Been Tested On: +## +## Model Firmware +## +--------------------+---------------------------+ +## (1) Main application BRET85K, rev 16 +## Boot ROM BRBR67D, rev 16 +## Remote Control BRRG67D, rev 16 +## +##### + +import sys, re +import atexit +sys.path.append("@FENCEAGENTSLIBDIR@") +from fencing import * +from fencing import fail, EC_STATUS, EC_GENERIC_ERROR + +#BEGIN_VERSION_GENERATION +RELEASE_VERSION="New Bladecenter Agent - test release on steroids" +REDHAT_COPYRIGHT="" +BUILD_DATE="March, 2008" +#END_VERSION_GENERATION + +def get_power_status(conn, options): + node_cmd = r"system:blade\[" + options["--plug"] + r"\]>" + + conn.send_eol("env -T system:blade[" + options["--plug"] + "]") + i = conn.log_expect([node_cmd, "system>"], int(options["--shell-timeout"])) + if i == 1: + ## Given blade number does not exist + if options.has_key("--missing-as-off"): + return "off" + else: + fail(EC_STATUS) + conn.send_eol("power -state") + conn.log_expect(node_cmd, int(options["--shell-timeout"])) + status = conn.before.splitlines()[-1] + conn.send_eol("env -T system") + conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) + + return status.lower().strip() + +def set_power_status(conn, options): + node_cmd = r"system:blade\[" + options["--plug"] + r"\]>" + + conn.send_eol("env -T system:blade[" + options["--plug"] + "]") + i = conn.log_expect([node_cmd, "system>"], int(options["--shell-timeout"])) + if i == 1: + ## Given blade number does not exist + if options.has_key("--missing-as-off"): + return + else: + fail(EC_GENERIC_ERROR) + + conn.send_eol("power -"+options["--action"]) + conn.log_expect(node_cmd, int(options["--shell-timeout"])) + conn.send_eol("env -T system") + conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) + +def get_blades_list(conn, options): + outlets = {} + + node_cmd = "system>" + + conn.send_eol("env -T system") + conn.log_expect(node_cmd, int(options["--shell-timeout"])) + conn.send_eol("list -l 2") + conn.log_expect(node_cmd, int(options["--shell-timeout"])) + + lines = conn.before.split("\r\n") + filter_re = re.compile(r"^\s*blade\[(\d+)\]\s+(.*?)\s*$") + for blade_line in lines: + res = filter_re.search(blade_line) + if res != None: + outlets[res.group(1)] = (res.group(2), "") + + return outlets + +def main(): + device_opt = ["ipaddr", "login", "passwd", "cmd_prompt", "secure", \ + "port", "missing_as_off", "telnet"] + + atexit.register(atexit_handler) + + all_opt["power_wait"]["default"] = "10" + all_opt["cmd_prompt"]["default"] = ["system>"] + + options = check_input(device_opt, process_input(device_opt)) + + docs = {} + docs["shortdesc"] = "Fence agent for IBM BladeCenter" + docs["longdesc"] = "fence_bladecenter is an I/O Fencing agent \ +which can be used with IBM Bladecenters with recent enough firmware that \ +includes telnet support. It logs into a Brocade chasis via telnet or ssh \ +and uses the command line interface to power on and off blades." + docs["vendorurl"] = "http://www.ibm.com" + show_docs(options, docs) + + ## + ## Operate the fencing device + ###### + conn = fence_login(options, "(username\s*:\s*)") + result = fence_action(conn, options, set_power_status, get_power_status, get_blades_list) + fence_logout(conn, "exit") + sys.exit(result) + +if __name__ == "__main__": + main() diff --git a/agents/autodetect/fence_brocade.py b/agents/autodetect/fence_brocade.py new file mode 100644 index 0000000..5257bcc --- /dev/null +++ b/agents/autodetect/fence_brocade.py @@ -0,0 +1,78 @@ +#!/usr/bin/python -tt + +import sys, re +import atexit +sys.path.append("@FENCEAGENTSLIBDIR@") +from fencing import * +from fencing import fail, EC_STATUS + +#BEGIN_VERSION_GENERATION +RELEASE_VERSION="New Brocade Agent - test release on steroids" +REDHAT_COPYRIGHT="" +BUILD_DATE="March, 20013" +#END_VERSION_GENERATION + +def set_power_status(conn, options): + action = { + 'on' : "portCfgPersistentEnable", + 'off': "portCfgPersistentDisable" + }[options["--action"]] + + conn.send_eol(action + " " + options["--plug"]) + conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) + +def get_power_status(conn, options): + line_re = re.compile(r'=========', re.IGNORECASE) + outlets = {} + in_index = False + + conn.send_eol("switchshow") + conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) + for line in str(conn.before).split("\n"): + if line_re.search(line): + in_index = True + elif in_index and line.lstrip()[0].isdigit(): + tokens = line.lstrip().split() + status = "off" if len(tokens) > 7 and tokens[7] == "Disabled" else "on" + outlets[tokens[0]] = ("", status) + + if ["list", "monitor"].count(options["--action"]) == 0: + (_, status) = outlets[options["--plug"]] + return status + else: + return outlets + +def main(): + device_opt = ["ipaddr", "login", "passwd", "cmd_prompt", "secure", \ + "port", "fabric_fencing", "telnet"] + + atexit.register(atexit_handler) + + all_opt["cmd_prompt"]["default"] = ["> "] + + options = check_input(device_opt, process_input(device_opt)) + options["eol"] = "\n" + + docs = {} + docs["shortdesc"] = "Fence agent for HP Brocade over telnet/ssh" + docs["longdesc"] = "fence_brocade is an I/O Fencing agent which can be used with Brocade FC switches. \ +It logs into a Brocade switch via telnet and disables a specified port. Disabling the port which a machine is \ +connected to effectively fences that machine. Lengthy telnet connections to the switch should be avoided while \ +a GFS cluster is running because the connection will block any necessary fencing actions. \ +\ +After a fence operation has taken place the fenced machine can no longer connect to the Brocade FC switch. \ +When the fenced machine is ready to be brought back into the GFS cluster (after reboot) the port on the Brocade \ +FC switch needs to be enabled. This can be done by running fence_brocade and specifying the enable action" + docs["vendorurl"] = "http://www.brocade.com" + show_docs(options, docs) + + ## + ## Operate the fencing device + #### + conn = fence_login(options) + result = fence_action(conn, options, set_power_status, get_power_status, get_power_status) + fence_logout(conn, "exit") + sys.exit(result) + +if __name__ == "__main__": + main() diff --git a/agents/autodetect/fence_ilo_moonshot.py b/agents/autodetect/fence_ilo_moonshot.py new file mode 100644 index 0000000..e161ac6 --- /dev/null +++ b/agents/autodetect/fence_ilo_moonshot.py @@ -0,0 +1,69 @@ +#!/usr/bin/python -tt + +import sys +import atexit +sys.path.append("@FENCEAGENTSLIBDIR@") +from fencing import * +from fencing import fail, EC_STATUS + +#BEGIN_VERSION_GENERATION +RELEASE_VERSION="" +REDHAT_COPYRIGHT="" +BUILD_DATE="" +#END_VERSION_GENERATION + +def get_power_status(conn, options): + conn.send_eol("show node list") + conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) + + nodes = {} + for line in conn.before.splitlines(): + if len(line.split()) == 10: + nodes[line.split()[1]] = ("", line.split()[8].lower().strip()) + + if ["list", "monitor"].count(options["--action"]) == 1: + return nodes + else: + try: + (_, status) = nodes[options["--plug"]] + return status.lower() + except KeyError: + fail(EC_STATUS) + +def set_power_status(conn, options): + if options["--action"] == "on": + conn.send_eol("set node power on %s" % (options["--plug"])) + else: + conn.send_eol("set node power off force %s" % (options["--plug"])) + + conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) + + return + +def main(): + device_opt = ["ipaddr", "login", "passwd", "secure", "cmd_prompt", "port"] + + atexit.register(atexit_handler) + + all_opt["secure"]["default"] = "1" + all_opt["cmd_prompt"]["default"] = ["MP>", "hpiLO->"] + + options = check_input(device_opt, process_input(device_opt)) + + docs = {} + docs["shortdesc"] = "Fence agent for HP Moonshot iLO" + docs["longdesc"] = "" + docs["vendorurl"] = "http://www.hp.com" + show_docs(options, docs) + + conn = fence_login(options) + + ## + ## Fence operations + #### + result = fence_action(conn, options, set_power_status, get_power_status, get_power_status) + fence_logout(conn, "exit") + sys.exit(result) + +if __name__ == "__main__": + main() diff --git a/agents/autodetect/fence_lpar.py b/agents/autodetect/fence_lpar.py new file mode 100644 index 0000000..6676e1c --- /dev/null +++ b/agents/autodetect/fence_lpar.py @@ -0,0 +1,159 @@ +#!/usr/bin/python -tt + +##### +## +## The Following Agent Has Been Tested On: +## +## Version +## +---------------------------------------------+ +## Tested on HMC +## +##### + +import sys, re +import atexit +sys.path.append("@FENCEAGENTSLIBDIR@") +from fencing import * +from fencing import fail, fail_usage, EC_STATUS_HMC + +#BEGIN_VERSION_GENERATION +RELEASE_VERSION="" +REDHAT_COPYRIGHT="" +BUILD_DATE="" +#END_VERSION_GENERATION + +def get_power_status(conn, options): + if options["--hmc-version"] == "3": + conn.send("lssyscfg -r lpar -m " + options["--managed"] + " -n " + options["--plug"] + " -F name,state\n") + conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) + + try: + status = re.compile("^" + options["--plug"] + ",(.*?),.*$", + re.IGNORECASE | re.MULTILINE).search(conn.before).group(1) + except AttributeError: + fail(EC_STATUS_HMC) + elif options["--hmc-version"] == "4": + conn.send("lssyscfg -r lpar -m "+ options["--managed"] + + " --filter 'lpar_names=" + options["--plug"] + "'\n") + conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) + + try: + status = re.compile(",state=(.*?),", re.IGNORECASE).search(conn.before).group(1) + except AttributeError: + fail(EC_STATUS_HMC) + + ## + ## Transformation to standard ON/OFF status if possible + if status in ["Running", "Open Firmware", "Shutting Down", "Starting"]: + status = "on" + else: + status = "off" + + return status + +def set_power_status(conn, options): + if options["--hmc-version"] == "3": + conn.send("chsysstate -o " + options["--action"] + " -r lpar -m " + options["--managed"] + + " -n " + options["--plug"] + "\n") + conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) + elif options["--hmc-version"] == "4": + if options["--action"] == "on": + conn.send("chsysstate -o on -r lpar -m " + options["--managed"] + + " -n " + options["--plug"] + + " -f `lssyscfg -r lpar -F curr_profile " + + " -m " + options["--managed"] + + " --filter \"lpar_names=" + options["--plug"] + "\"`\n") + else: + conn.send("chsysstate -o shutdown -r lpar --immed" + + " -m " + options["--managed"] + " -n " + options["--plug"] + "\n") + conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) + +def get_lpar_list(conn, options): + outlets = {} + if options["--hmc-version"] == "3": + conn.send("query_partition_names -m " + options["--managed"] + "\n") + conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) + + ## We have to remove first 3 lines (command + header) and last line (part of new prompt) + #### + res = re.search("^.+?\n(.+?\n){2}(.*)\n.*$", conn.before, re.S) + + if res == None: + fail_usage("Unable to parse output of list command") + + lines = res.group(2).split("\n") + for outlet_line in lines: + outlets[outlet_line.rstrip()] = ("", "") + elif options["--hmc-version"] == "4": + conn.send("lssyscfg -r lpar -m " + options["--managed"] + + " -F name:state\n") + conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) + + ## We have to remove first line (command) and last line (part of new prompt) + #### + res = re.search("^.+?\n(.*)\n.*$", conn.before, re.S) + + if res == None: + fail_usage("Unable to parse output of list command") + + lines = res.group(1).split("\n") + for outlet_line in lines: + (port, status) = outlet_line.split(":") + outlets[port] = ("", status) + + return outlets + +def define_new_opts(): + all_opt["managed"] = { + "getopt" : "s:", + "longopt" : "managed", + "help" : "-s, --managed=[id] Name of the managed system", + "required" : "0", + "shortdesc" : "Managed system name", + "order" : 1} + all_opt["hmc_version"] = { + "getopt" : "H:", + "longopt" : "hmc-version", + "help" : "-H, --hmc-version=[version] Force HMC version to use: (3|4) (default: 4)", + "required" : "0", + "shortdesc" : "Force HMC version to use", + "default" : "4", + "choices" : ["3", "4"], + "order" : 1} + +def main(): + device_opt = ["ipaddr", "login", "passwd", "secure", "cmd_prompt", \ + "port", "managed", "hmc_version"] + + atexit.register(atexit_handler) + + define_new_opts() + + all_opt["login_timeout"]["default"] = "15" + all_opt["secure"]["default"] = "1" + all_opt["cmd_prompt"]["default"] = [r":~>", r"]\$", r"\$ "] + + options = check_input(device_opt, process_input(device_opt), other_conditions = True) + + docs = {} + docs["shortdesc"] = "Fence agent for IBM LPAR" + docs["longdesc"] = "" + docs["vendorurl"] = "http://www.ibm.com" + show_docs(options, docs) + + if not options.has_key("--managed"): + fail_usage("Failed: You have to enter name of managed system") + + if options["--action"] == "validate-all": + sys.exit(0) + + ## + ## Operate the fencing device + #### + conn = fence_login(options) + result = fence_action(conn, options, set_power_status, get_power_status, get_lpar_list) + fence_logout(conn, "quit\r\n") + sys.exit(result) + +if __name__ == "__main__": + main() diff --git a/agents/autodetect/fencing.py b/agents/autodetect/fencing.py new file mode 100644 index 0000000..ea21ace --- /dev/null +++ b/agents/autodetect/fencing.py @@ -0,0 +1,1393 @@ +#!/usr/bin/python -tt + +import sys, getopt, time, os, uuid, pycurl, stat +import pexpect, re, syslog +import logging +import subprocess +import threading +import shlex +import exceptions +import socket +import textwrap +import __main__ + +## do not add code here. +#BEGIN_VERSION_GENERATION +RELEASE_VERSION="4.0.21.23-eaa13" +BUILD_DATE="(built Wed Nov 4 13:28:43 CET 2015)" +REDHAT_COPYRIGHT="Copyright (C) Red Hat, Inc. 2004-2010 All rights reserved." +#END_VERSION_GENERATION + +__all__ = ['atexit_handler', 'check_input', 'process_input', 'all_opt', 'show_docs', + 'fence_login', 'fence_action', 'fence_logout'] + +EC_OK = 0 +EC_GENERIC_ERROR = 1 +EC_BAD_ARGS = 2 +EC_LOGIN_DENIED = 3 +EC_CONNECTION_LOST = 4 +EC_TIMED_OUT = 5 +EC_WAITING_ON = 6 +EC_WAITING_OFF = 7 +EC_STATUS = 8 +EC_STATUS_HMC = 9 +EC_PASSWORD_MISSING = 10 +EC_INVALID_PRIVILEGES = 11 + +all_opt = { + "help" : { + "getopt" : "h", + "longopt" : "help", + "help" : "-h, --help Display this help and exit", + "required" : "0", + "shortdesc" : "Display help and exit", + "order" : 54}, + "version" : { + "getopt" : "V", + "longopt" : "version", + "help" : "-V, --version Display version information and exit", + "required" : "0", + "shortdesc" : "Display version information and exit", + "order" : 53}, + "verbose" : { + "getopt" : "v", + "longopt" : "verbose", + "help" : "-v, --verbose Verbose mode", + "required" : "0", + "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}, + "delay" : { + "getopt" : ":", + "longopt" : "delay", + "help" : "--delay=[seconds] Wait X seconds before fencing is started", + "required" : "0", + "default" : "0", + "order" : 200}, + "agent" : { + "getopt" : "", + "help" : "", + "order" : 1}, + "web" : { + "getopt" : "", + "help" : "", + "order" : 1}, + "force_on" : { + "getopt" : "", + "help" : "", + "order" : 1}, + "action" : { + "getopt" : "o:", + "longopt" : "action", + "help" : "-o, --action=[action] Action: status, reboot (default), off or on", + "required" : "1", + "shortdesc" : "Fencing action", + "default" : "reboot", + "order" : 1}, + "fabric_fencing" : { + "getopt" : "", + "help" : "", + "order" : 1}, + "ipaddr" : { + "getopt" : "a:", + "longopt" : "ip", + "help" : "-a, --ip=[ip] IP address or hostname of fencing device", + "required" : "1", + "order" : 1}, + "ipport" : { + "getopt" : "u:", + "longopt" : "ipport", + "help" : "-u, --ipport=[port] TCP/UDP port to use for connection", + "required" : "0", + "shortdesc" : "TCP/UDP port to use for connection with device", + "order" : 1}, + "login" : { + "getopt" : "l:", + "longopt" : "username", + "help" : "-l, --username=[name] Login name", + "required" : "?", + "order" : 1}, + "no_login" : { + "getopt" : "", + "help" : "", + "order" : 1}, + "no_password" : { + "getopt" : "", + "help" : "", + "order" : 1}, + "no_port" : { + "getopt" : "", + "help" : "", + "order" : 1}, + "no_status" : { + "getopt" : "", + "help" : "", + "order" : 1}, + "no_on" : { + "getopt" : "", + "help" : "", + "order" : 1}, + "no_off" : { + "getopt" : "", + "help" : "", + "order" : 1}, + "telnet" : { + "getopt" : "", + "help" : "", + "order" : ""}, + "passwd" : { + "getopt" : "p:", + "longopt" : "password", + "help" : "-p, --password=[password] Login password or passphrase", + "required" : "0", + "order" : 1}, + "passwd_script" : { + "getopt" : "S:", + "longopt" : "password-script", + "help" : "-S, --password-script=[script] Script to run to retrieve password", + "required" : "0", + "order" : 1}, + "identity_file" : { + "getopt" : "k:", + "longopt" : "identity-file", + "help" : "-k, --identity-file=[filename] Identity file (private key) for SSH", + "required" : "0", + "order" : 1}, + "cmd_prompt" : { + "getopt" : "c:", + "longopt" : "command-prompt", + "help" : "-c, --command-prompt=[prompt] Force Python regex for command prompt", + "required" : "0", + "order" : 1}, + "secure" : { + "getopt" : "x", + "longopt" : "ssh", + "help" : "-x, --ssh Use SSH connection", + "required" : "0", + "order" : 1}, + "ssh_options" : { + "getopt" : ":", + "longopt" : "ssh-options", + "help" : "--ssh-options=[options] SSH options to use", + "required" : "0", + "order" : 1}, + "ssl" : { + "getopt" : "z", + "longopt" : "ssl", + "help" : "-z, --ssl Use SSL connection with verifying certificate", + "required" : "0", + "order" : 1}, + "ssl_insecure" : { + "getopt" : "", + "longopt" : "ssl-insecure", + "help" : "--ssl-insecure Use SSL connection without verifying certificate", + "required" : "0", + "order" : 1}, + "ssl_secure" : { + "getopt" : "", + "longopt" : "ssl-secure", + "help" : "--ssl-secure Use SSL connection with verifying certificate", + "required" : "0", + "order" : 1}, + "notls" : { + "getopt" : "t", + "longopt" : "notls", + "help" : "-t, --notls " + "Disable TLS negotiation and force SSL3.0. " + "This should only be used for devices that do not support TLS1.0 and up.", + "required" : "0", + "order" : 1}, + "tls1.0" : { + "getopt" : "", + "longopt" : "tls1.0", + "help" : "--tls1.0 " + "Disable TLS negotiation and force TLS1.0. " + "This should only be used for devices that do not support TLS1.1 and up.", + "required" : "0", + "order" : 1}, + "port" : { + "getopt" : "n:", + "longopt" : "plug", + "help" : "-n, --plug=[id] " + "Physical plug number on device, UUID or identification of machine", + "required" : "1", + "order" : 1}, + "switch" : { + "getopt" : "s:", + "longopt" : "switch", + "help" : "-s, --switch=[id] Physical switch number on device", + "required" : "0", + "order" : 1}, + "exec" : { + "getopt" : "e:", + "longopt" : "exec", + "help" : "-e, --exec=[command] Command to execute", + "required" : "0", + "order" : 1}, + "vmware_type" : { + "getopt" : "d:", + "longopt" : "vmware_type", + "help" : "-d, --vmware_type=[type] Type of VMware to connect", + "required" : "0", + "order" : 1}, + "vmware_datacenter" : { + "getopt" : "s:", + "longopt" : "vmware-datacenter", + "help" : "-s, --vmware-datacenter=[dc] VMWare datacenter filter", + "required" : "0", + "order" : 2}, + "snmp_version" : { + "getopt" : "d:", + "longopt" : "snmp-version", + "help" : "-d, --snmp-version=[version] Specifies SNMP version to use (1|2c|3)", + "required" : "0", + "shortdesc" : "Specifies SNMP version to use", + "choices" : ["1", "2c", "3"], + "order" : 1}, + "community" : { + "getopt" : "c:", + "longopt" : "community", + "help" : "-c, --community=[community] Set the community string", + "required" : "0", + "order" : 1}, + "snmp_auth_prot" : { + "getopt" : "b:", + "longopt" : "snmp-auth-prot", + "help" : "-b, --snmp-auth-prot=[prot] Set authentication protocol (MD5|SHA)", + "required" : "0", + "shortdesc" : "Set authentication protocol", + "choices" : ["MD5", "SHA"], + "order" : 1}, + "snmp_sec_level" : { + "getopt" : "E:", + "longopt" : "snmp-sec-level", + "help" : "-E, --snmp-sec-level=[level] " + "Set security level (noAuthNoPriv|authNoPriv|authPriv)", + "required" : "0", + "shortdesc" : "Set security level", + "choices" : ["noAuthNoPriv", "authNoPriv", "authPriv"], + "order" : 1}, + "snmp_priv_prot" : { + "getopt" : "B:", + "longopt" : "snmp-priv-prot", + "help" : "-B, --snmp-priv-prot=[prot] Set privacy protocol (DES|AES)", + "required" : "0", + "shortdesc" : "Set privacy protocol", + "choices" : ["DES", "AES"], + "order" : 1}, + "snmp_priv_passwd" : { + "getopt" : "P:", + "longopt" : "snmp-priv-passwd", + "help" : "-P, --snmp-priv-passwd=[pass] Set privacy protocol password", + "required" : "0", + "order" : 1}, + "snmp_priv_passwd_script" : { + "getopt" : "R:", + "longopt" : "snmp-priv-passwd-script", + "help" : "-R, --snmp-priv-passwd-script Script to run to retrieve privacy password", + "required" : "0", + "order" : 1}, + "inet4_only" : { + "getopt" : "4", + "longopt" : "inet4-only", + "help" : "-4, --inet4-only Forces agent to use IPv4 addresses only", + "required" : "0", + "order" : 1}, + "inet6_only" : { + "getopt" : "6", + "longopt" : "inet6-only", + "help" : "-6, --inet6-only Forces agent to use IPv6 addresses only", + "required" : "0", + "order" : 1}, + "separator" : { + "getopt" : "C:", + "longopt" : "separator", + "help" : "-C, --separator=[char] Separator for CSV created by 'list' operation", + "default" : ",", + "required" : "0", + "order" : 100}, + "login_timeout" : { + "getopt" : ":", + "longopt" : "login-timeout", + "help" : "--login-timeout=[seconds] Wait X seconds for cmd prompt after login", + "default" : "5", + "required" : "0", + "order" : 200}, + "shell_timeout" : { + "getopt" : ":", + "longopt" : "shell-timeout", + "help" : "--shell-timeout=[seconds] Wait X seconds for cmd prompt after issuing command", + "default" : "3", + "required" : "0", + "order" : 200}, + "power_timeout" : { + "getopt" : ":", + "longopt" : "power-timeout", + "help" : "--power-timeout=[seconds] Test X seconds for status change after ON/OFF", + "default" : "20", + "required" : "0", + "order" : 200}, + "power_wait" : { + "getopt" : ":", + "longopt" : "power-wait", + "help" : "--power-wait=[seconds] Wait X seconds after issuing ON/OFF", + "default" : "0", + "required" : "0", + "order" : 200}, + "missing_as_off" : { + "getopt" : "", + "longopt" : "missing-as-off", + "help" : "--missing-as-off Missing port returns OFF instead of failure", + "required" : "0", + "order" : 200}, + "retry_on" : { + "getopt" : ":", + "longopt" : "retry-on", + "help" : "--retry-on=[attempts] Count of attempts to retry power on", + "default" : "1", + "required" : "0", + "order" : 201}, + "session_url" : { + "getopt" : "s:", + "longopt" : "session-url", + "help" : "-s, --session-url URL to connect to XenServer on", + "required" : "1", + "order" : 1}, + "sudo" : { + "getopt" : "", + "longopt" : "use-sudo", + "help" : "--use-sudo Use sudo (without password) when calling 3rd party software", + "required" : "0", + "order" : 205}, + "method" : { + "getopt" : "m:", + "longopt" : "method", + "help" : "-m, --method=[method] Method to fence (onoff|cycle) (Default: onoff)", + "required" : "0", + "shortdesc" : "Method to fence", + "default" : "onoff", + "choices" : ["onoff", "cycle"], + "order" : 1}, + "telnet_path" : { + "getopt" : ":", + "longopt" : "telnet-path", + "help" : "--telnet-path=[path] Path to telnet binary", + "required" : "0", + "default" : "/usr/bin/telnet", + "order": 300}, + "ssh_path" : { + "getopt" : ":", + "longopt" : "ssh-path", + "help" : "--ssh-path=[path] Path to ssh binary", + "required" : "0", + "default" : "/usr/bin/ssh", + "order": 300}, + "gnutlscli_path" : { + "getopt" : ":", + "longopt" : "gnutlscli-path", + "help" : "--gnutlscli-path=[path] Path to gnutls-cli binary", + "required" : "0", + "default" : "/usr/bin/gnutls-cli", + "order": 300}, + "sudo_path" : { + "getopt" : ":", + "longopt" : "sudo-path", + "help" : "--sudo-path=[path] Path to sudo binary", + "required" : "0", + "default" : "/usr/bin/sudo", + "order": 300}, + "snmpwalk_path" : { + "getopt" : ":", + "longopt" : "snmpwalk-path", + "help" : "--snmpwalk-path=[path] Path to snmpwalk binary", + "required" : "0", + "default" : "/usr/bin/snmpwalk", + "order" : 300}, + "snmpset_path" : { + "getopt" : ":", + "longopt" : "snmpset-path", + "help" : "--snmpset-path=[path] Path to snmpset binary", + "required" : "0", + "default" : "/usr/bin/snmpset", + "order" : 300}, + "snmpget_path" : { + "getopt" : ":", + "longopt" : "snmpget-path", + "help" : "--snmpget-path=[path] Path to snmpget binary", + "required" : "0", + "default" : "/usr/bin/snmpget", + "order" : 300}, + "snmp": { + "getopt" : "", + "help" : "", + "order" : 1}, + "port_as_ip": { + "getopt" : "", + "longopt" : "port-as-ip", + "help" : "--port-as-ip Make \"port/plug\" to be an alias to IP address", + "required" : "0", + "order" : 200}, + "on_target": { + "getopt" : "", + "help" : "", + "order" : 1}, + "quiet": { + "getopt" : "q", + "longopt": "quiet", + "help" : "-q, --quiet Disable logging to stderr. Does not affect --verbose or --debug-file or logging to syslog.", + "required" : "0", + "order" : 50} +} + +# options which are added automatically if 'key' is encountered ("default" is always added) +DEPENDENCY_OPT = { + "default" : ["help", "debug", "verbose", "version", "action", "agent", \ + "power_timeout", "shell_timeout", "login_timeout", "power_wait", "retry_on", \ + "delay", "quiet"], + "passwd" : ["passwd_script"], + "sudo" : ["sudo_path"], + "secure" : ["identity_file", "ssh_options", "ssh_path"], + "telnet" : ["telnet_path"], + "ipaddr" : ["ipport", "inet4_only", "inet6_only"], + "port" : ["separator"], + "ssl" : ["ssl_secure", "ssl_insecure", "gnutlscli_path"], + "snmp" : ["snmp_auth_prot", "snmp_sec_level", "snmp_priv_prot", \ + "snmp_priv_passwd", "snmp_priv_passwd_script", "community", \ + "snmpset_path", "snmpget_path", "snmpwalk_path"] + } + +class fspawn(pexpect.spawn): + def __init__(self, options, command): + logging.info("Running command: %s", command) + pexpect.spawn.__init__(self, command) + self.opt = options + + def log_expect(self, pattern, timeout): + result = self.expect(pattern, timeout) + logging.debug("Received: %s", str(self.before) + str(self.after)) + return result + + def send(self, message): + logging.debug("Sent: %s", message) + return pexpect.spawn.send(self, message) + + # send EOL according to what was detected in login process (telnet) + def send_eol(self, message): + return self.send(message + self.opt["eol"]) + +def atexit_handler(): + try: + sys.stdout.close() + os.close(1) + except IOError: + logging.error("%s failed to close standard output\n", sys.argv[0]) + sys.exit(EC_GENERIC_ERROR) + +def _add_dependency_options(options): + ## Add also options which are available for every fence agent + added_opt = [] + for opt in options + ["default"]: + if DEPENDENCY_OPT.has_key(opt): + added_opt.extend([y for y in DEPENDENCY_OPT[opt] if options.count(y) == 0]) + + if not "port" in (options + added_opt) and \ + not "nodename" in (options + added_opt) and \ + "ipaddr" in (options + added_opt): + added_opt.append("port_as_ip") + all_opt["port"]["help"] = "-n, --plug=[ip] IP address or hostname of fencing device " \ + "(together with --port-as-ip)" + + return added_opt + +def fail_usage(message="", stop=True): + if len(message) > 0: + logging.error("%s\n", message) + if stop: + logging.error("Please use '-h' for usage\n") + sys.exit(EC_GENERIC_ERROR) + +def fail(error_code): + message = { + EC_LOGIN_DENIED : "Unable to connect/login to fencing device", + EC_CONNECTION_LOST : "Connection lost", + EC_TIMED_OUT : "Connection timed out", + EC_WAITING_ON : "Failed: Timed out waiting to power ON", + EC_WAITING_OFF : "Failed: Timed out waiting to power OFF", + EC_STATUS : "Failed: Unable to obtain correct plug status or plug is not available", + EC_STATUS_HMC : "Failed: Either unable to obtain correct plug status, " + "partition is not available or incorrect HMC version used", + EC_PASSWORD_MISSING : "Failed: You have to set login password", + EC_INVALID_PRIVILEGES : "Failed: The user does not have the correct privileges to do the requested action." + }[error_code] + "\n" + logging.error("%s\n", message) + sys.exit(EC_GENERIC_ERROR) + +def usage(avail_opt): + print "Usage:" + print "\t" + os.path.basename(sys.argv[0]) + " [options]" + print "Options:" + + sorted_list = [(key, all_opt[key]) for key in avail_opt] + sorted_list.sort(lambda x, y: cmp(x[1]["order"], y[1]["order"])) + + for key, value in sorted_list: + if len(value["help"]) != 0: + print " " + _join_wrap([value["help"]], first_indent=3) + +def metadata(avail_opt, docs): + # avail_opt has to be unique, if there are duplicities then they should be removed + sorted_list = [(key, all_opt[key]) for key in list(set(avail_opt))] + sorted_list.sort(lambda x, y: cmp(x[0], y[0])) + sorted_list.sort(lambda x, y: cmp(x[1]["order"], y[1]["order"])) + + print "<?xml version=\"1.0\" ?>" + print "<resource-agent name=\"" + os.path.basename(sys.argv[0]) + \ + "\" shortdesc=\"" + docs["shortdesc"] + "\" >" + for (symlink, desc) in docs.get("symlink", []): + print "<symlink name=\"" + symlink + "\" shortdesc=\"" + desc + "\"/>" + print "<longdesc>" + docs["longdesc"] + "</longdesc>" + print "<vendor-url>" + docs["vendorurl"] + "</vendor-url>" + print "<parameters>" + for option, _ in sorted_list: + if all_opt[option].has_key("help") and len(all_opt[option]["help"]) > 0: + print "\t<parameter name=\"" + option + "\" unique=\"0\" required=\"" + all_opt[option]["required"] + "\">" + + default = "" + if all_opt[option].has_key("default"): + default = "default=\"" + _encode_html_entities(str(all_opt[option]["default"])) + "\" " + + 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 = _encode_html_entities(mixed) + + if not "shortdesc" in all_opt[option]: + shortdesc = re.sub("\s\s+", " ", all_opt[option]["help"][31:]) + else: + shortdesc = all_opt[option]["shortdesc"] + + print "\t\t<getopt mixed=\"" + mixed + "\" />" + if all_opt[option].has_key("choices"): + print "\t\t<content type=\"select\" "+default+" >" + for choice in all_opt[option]["choices"]: + print "\t\t\t<option value=\"%s\" />" % (choice) + print "\t\t</content>" + elif all_opt[option]["getopt"].count(":") > 0: + print "\t\t<content type=\"string\" "+default+" />" + else: + print "\t\t<content type=\"boolean\" "+default+" />" + print "\t\t<shortdesc lang=\"en\">" + shortdesc + "</shortdesc>" + print "\t</parameter>" + print "</parameters>" + print "<actions>" + + (available_actions, _) = _get_available_actions(avail_opt) + + if "on" in available_actions: + available_actions.remove("on") + on_target = ' on_target="1"' if avail_opt.count("on_target") else '' + print "\t<action name=\"on\"%s automatic=\"%d\"/>" % (on_target, avail_opt.count("fabric_fencing")) + + for action in available_actions: + print "\t<action name=\"%s\" />" % (action) + print "</actions>" + print "</resource-agent>" + +def process_input(avail_opt): + avail_opt.extend(_add_dependency_options(avail_opt)) + + # @todo: this should be put elsewhere? + os.putenv("LANG", "C") + os.putenv("LC_ALL", "C") + + if "port_as_ip" in avail_opt: + avail_opt.append("port") + + if len(sys.argv) > 1: + opt = _parse_input_cmdline(avail_opt) + else: + opt = _parse_input_stdin(avail_opt) + + if "--port-as-ip" in opt and "--plug" in opt: + opt["--ip"] = opt["--plug"] + + return opt + +## +## This function checks input and answers if we want to have same answers +## in each of the fencing agents. It looks for possible errors and run +## password script to set a correct password +###### +def check_input(device_opt, opt, other_conditions = False): + device_opt.extend(_add_dependency_options(device_opt)) + + options = dict(opt) + options["device_opt"] = device_opt + + _update_metadata(options) + options = _set_default_values(options) + options["--action"] = options["--action"].lower() + + ## In special cases (show help, metadata or version) we don't need to check anything + ##### + # OCF compatibility + if options["--action"] == "meta-data": + options["--action"] = "metadata" + + if options["--action"] == "metadata" or any(options.has_key(k) for k in ("--help", "--version")): + return options + + if options.has_key("--verbose"): + logging.getLogger().setLevel(logging.DEBUG) + + ## add logging to syslog + logging.getLogger().addHandler(SyslogLibHandler()) + if not options.has_key("--quiet"): + ## add logging to stderr + logging.getLogger().addHandler(logging.StreamHandler(sys.stderr)) + + (acceptable_actions, _) = _get_available_actions(device_opt) + + if 1 == device_opt.count("fabric_fencing"): + acceptable_actions.extend(["enable", "disable"]) + + if 0 == acceptable_actions.count(options["--action"]): + fail_usage("Failed: Unrecognised action '" + options["--action"] + "'") + + ## Compatibility layer + ##### + if options["--action"] == "enable": + options["--action"] = "on" + if options["--action"] == "disable": + options["--action"] = "off" + + + if options["--action"] == "validate-all" and not other_conditions: + _validate_input(options, False) + sys.exit(EC_OK) + else: + _validate_input(options, True) + + if options.has_key("--debug-file"): + try: + debug_file = logging.FileHandler(options["--debug-file"]) + debug_file.setLevel(logging.DEBUG) + logging.getLogger().addHandler(debug_file) + except IOError: + logging.error("Unable to create file %s", options["--debug-file"]) + fail_usage("Failed: Unable to create file " + options["--debug-file"]) + + if options.has_key("--snmp-priv-passwd-script"): + options["--snmp-priv-passwd"] = os.popen(options["--snmp-priv-passwd-script"]).read().rstrip() + + if options.has_key("--password-script"): + options["--password"] = os.popen(options["--password-script"]).read().rstrip() + + return options + +## Obtain a power status from possibly more than one plug +## "on" is returned if at least one plug is ON +###### +def get_multi_power_fn(connection, options, get_power_fn): + status = "off" + plugs = options["--plugs"] if options.has_key("--plugs") else [""] + + for plug in plugs: + try: + options["--uuid"] = str(uuid.UUID(plug)) + except ValueError: + pass + except KeyError: + pass + + options["--plug"] = plug + plug_status = get_power_fn(connection, options) + if plug_status != "off": + status = plug_status + + return status + +def set_multi_power_fn(connection, options, set_power_fn, get_power_fn, retry_attempts=1): + plugs = options["--plugs"] if options.has_key("--plugs") else [""] + + for _ in range(retry_attempts): + for plug in plugs: + try: + options["--uuid"] = str(uuid.UUID(plug)) + except ValueError: + pass + except KeyError: + pass + + options["--plug"] = plug + set_power_fn(connection, options) + time.sleep(int(options["--power-wait"])) + + for _ in xrange(int(options["--power-timeout"])): + if get_multi_power_fn(connection, options, get_power_fn) != options["--action"]: + time.sleep(1) + else: + return True + return False + +def show_docs(options, docs=None): + device_opt = options["device_opt"] + + if docs == None: + docs = {} + docs["shortdesc"] = "Fence agent" + docs["longdesc"] = "" + + if options.has_key("--help"): + usage(device_opt) + sys.exit(0) + + if options.get("--action", "") == "metadata": + if "port_as_ip" in device_opt: + device_opt.remove("separator") + metadata(device_opt, docs) + sys.exit(0) + + if options.has_key("--version"): + print __main__.RELEASE_VERSION, __main__.BUILD_DATE + print __main__.REDHAT_COPYRIGHT + sys.exit(0) + +def fence_action(connection, options, set_power_fn, get_power_fn, get_outlet_list=None, reboot_cycle_fn=None): + result = 0 + + try: + if options.has_key("--plug"): + options["--plugs"] = options["--plug"].split(",") + + ## Process options that manipulate fencing device + ##### + if (options["--action"] in ["list", "list-status"]) or \ + ((options["--action"] == "monitor") and 1 == options["device_opt"].count("port") and \ + 0 == options["device_opt"].count("port_as_ip")): + + if 0 == options["device_opt"].count("port"): + print "N/A" + elif get_outlet_list == None: + ## @todo: exception? + ## This is just temporal solution, we will remove default value + ## None as soon as all existing agent will support this operation + print "NOTICE: List option is not working on this device yet" + else: + options["--original-action"] = options["--action"] + options["--action"] = "list" + outlets = get_outlet_list(connection, options) + options["--action"] = options["--original-action"] + del options["--original-action"] + + ## keys can be numbers (port numbers) or strings (names of VM, UUID) + for outlet_id in outlets.keys(): + (alias, status) = outlets[outlet_id] + if status is None or (not status.upper() in ["ON", "OFF"]): + status = "UNKNOWN" + status = status.upper() + + if options["--action"] == "list": + print outlet_id + options["--separator"] + alias + elif options["--action"] == "list-status": + print outlet_id + options["--separator"] + alias + options["--separator"] + status + + return + + if options["--action"] == "monitor" and not "port" in options["device_opt"] and "no_status" in options["device_opt"]: + # Unable to do standard monitoring because 'status' action is not available + return 0 + + status = None + if not "no_status" in options["device_opt"]: + status = get_multi_power_fn(connection, options, get_power_fn) + if status != "on" and status != "off": + fail(EC_STATUS) + + if options["--action"] == status: + if not (status == "on" and "force_on" in options["device_opt"]): + print "Success: Already %s" % (status.upper()) + return 0 + + if options["--action"] == "on": + if set_multi_power_fn(connection, options, set_power_fn, get_power_fn, 1 + int(options["--retry-on"])): + print "Success: Powered ON" + else: + fail(EC_WAITING_ON) + elif options["--action"] == "off": + if set_multi_power_fn(connection, options, set_power_fn, get_power_fn): + print "Success: Powered OFF" + else: + fail(EC_WAITING_OFF) + elif options["--action"] == "reboot": + power_on = False + if options.get("--method", "").lower() == "cycle" and reboot_cycle_fn is not None: + for _ in range(1, 1 + int(options["--retry-on"])): + if reboot_cycle_fn(connection, options): + power_on = True + break + + if not power_on: + fail(EC_TIMED_OUT) + + else: + if status != "off": + options["--action"] = "off" + if not set_multi_power_fn(connection, options, set_power_fn, get_power_fn): + fail(EC_WAITING_OFF) + + options["--action"] = "on" + + try: + power_on = set_multi_power_fn(connection, options, set_power_fn, get_power_fn, int(options["--retry-on"])) + except Exception, ex: + # an error occured during power ON phase in reboot + # fence action was completed succesfully even in that case + logging.warning("%s", str(ex)) + + if power_on == False: + # this should not fail as node was fenced succesfully + logging.error('Timed out waiting to power ON\n') + + print "Success: Rebooted" + elif options["--action"] == "status": + print "Status: " + status.upper() + if status.upper() == "OFF": + result = 2 + elif options["--action"] == "monitor": + pass + except pexpect.EOF: + fail(EC_CONNECTION_LOST) + except pexpect.TIMEOUT: + fail(EC_TIMED_OUT) + except pycurl.error, ex: + logging.error("%s\n", str(ex)) + fail(EC_TIMED_OUT) + except socket.timeout, ex: + logging.error("%s\n", str(ex)) + fail(EC_TIMED_OUT) + + return result + +def fence_login(options, re_login_string=r"(login\s*: )|((?!Last )Login Name: )|(username: )|(User Name :)"): + run_delay(options) + + if not options.has_key("eol"): + options["eol"] = "\r\n" + + if options.has_key("--command-prompt") and type(options["--command-prompt"]) is not list: + options["--command-prompt"] = [options["--command-prompt"]] + + try: + if options.has_key("--ssl"): + conn = _open_ssl_connection(options) + elif options.has_key("--ssh") and not options.has_key("--identity-file"): + conn = _login_ssh_with_password(options, re_login_string) + elif options.has_key("--ssh") and options.has_key("--identity-file"): + conn = _login_ssh_with_identity_file(options) + else: + conn = _login_telnet(options, re_login_string) + except pexpect.EOF, exception: + logging.debug("%s", str(exception)) + fail(EC_LOGIN_DENIED) + except pexpect.TIMEOUT, exception: + logging.debug("%s", str(exception)) + fail(EC_LOGIN_DENIED) + return conn + +def is_executable(path): + if os.path.exists(path): + stats = os.stat(path) + if stat.S_ISREG(stats.st_mode) and os.access(path, os.X_OK): + return True + return False + +def run_command(options, command, timeout=None, env=None, log_command=None): + if timeout is None and "--power-timeout" in options: + timeout = options["--power-timeout"] + if timeout is not None: + timeout = float(timeout) + + logging.info("Executing: %s\n", log_command or command) + + try: + process = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) + except OSError: + fail_usage("Unable to run %s\n" % command) + + thread = threading.Thread(target=process.wait) + thread.start() + thread.join(timeout) + if thread.is_alive(): + process.kill() + fail(EC_TIMED_OUT) + + status = process.wait() + + (pipe_stdout, pipe_stderr) = process.communicate() + process.stdout.close() + process.stderr.close() + + logging.debug("%s %s %s\n", str(status), str(pipe_stdout), str(pipe_stderr)) + + return (status, pipe_stdout, pipe_stderr) + +def run_delay(options): + ## Delay is important for two-node clusters fencing but we do not need to delay 'status' operations + if options["--action"] in ["off", "reboot"]: + logging.info("Delay %s second(s) before logging in to the fence device", options["--delay"]) + time.sleep(int(options["--delay"])) + +def fence_logout(conn, logout_string, sleep=0): + # Logout is not required part of fencing but we should attempt to do it properly + # In some cases our 'exit' command is faster and we can not close connection as it + # was already closed by fencing device + try: + conn.send_eol(logout_string) + time.sleep(sleep) + conn.close() + except exceptions.OSError: + pass + except pexpect.ExceptionPexpect: + pass + +# Convert array of format [[key1, value1], [key2, value2], ... [keyN, valueN]] to dict, where key is +# in format a.b.c.d...z and returned dict has key only z +def array_to_dict(array): + return dict([[x[0].split(".")[-1], x[1]] for x in array]) + +## Own logger handler that uses old-style syslog handler as otherwise everything is sourced +## from /dev/syslog +class SyslogLibHandler(logging.StreamHandler): + """ + A handler class that correctly push messages into syslog + """ + def emit(self, record): + syslog_level = { + logging.CRITICAL:syslog.LOG_CRIT, + logging.ERROR:syslog.LOG_ERR, + logging.WARNING:syslog.LOG_WARNING, + logging.INFO:syslog.LOG_INFO, + logging.DEBUG:syslog.LOG_DEBUG, + logging.NOTSET:syslog.LOG_DEBUG, + }[record.levelno] + + msg = self.format(record) + + # syslos.syslog can not have 0x00 character inside or exception is thrown + syslog.syslog(syslog_level, msg.replace("\x00", "\n")) + return + +def _open_ssl_connection(options): + gnutls_opts = "" + ssl_opts = "" + + if options.has_key("--notls"): + gnutls_opts = "--priority \"NORMAL:-VERS-TLS1.2:-VERS-TLS1.1:-VERS-TLS1.0:+VERS-SSL3.0\"" + elif options.has_key("--tls1.0"): + gnutls_opts = "--priority \"NORMAL:-VERS-TLS1.2:-VERS-TLS1.1:+VERS-TLS1.0:%LATEST_RECORD_VERSION\"" + + # --ssl is same as the --ssl-secure; it means we want to verify certificate in these cases + if options.has_key("--ssl-insecure"): + ssl_opts = "--insecure" + + command = '%s %s %s --crlf -p %s %s' % \ + (options["--gnutlscli-path"], gnutls_opts, ssl_opts, options["--ipport"], options["--ip"]) + try: + conn = fspawn(options, command) + except pexpect.ExceptionPexpect, ex: + logging.error("%s\n", str(ex)) + sys.exit(EC_GENERIC_ERROR) + + return conn + +def _login_ssh_with_identity_file(options): + if options.has_key("--inet6-only"): + force_ipvx = "-6 " + elif options.has_key("--inet4-only"): + force_ipvx = "-4 " + else: + force_ipvx = "" + + command = '%s %s %s@%s -i %s -p %s' % \ + (options["--ssh-path"], force_ipvx, options["--username"], options["--ip"], \ + options["--identity-file"], options["--ipport"]) + if options.has_key("--ssh-options"): + command += ' ' + options["--ssh-options"] + + conn = fspawn(options, command) + + result = conn.log_expect(["Enter passphrase for key '" + options["--identity-file"] + "':", \ + "Are you sure you want to continue connecting (yes/no)?"] + \ + options["--command-prompt"], int(options["--login-timeout"])) + if result == 1: + conn.sendline("yes") + result = conn.log_expect( + ["Enter passphrase for key '" + options["--identity-file"]+"':"] + \ + options["--command-prompt"], int(options["--login-timeout"])) + if result == 0: + if options.has_key("--password"): + conn.sendline(options["--password"]) + conn.log_expect(options["--command-prompt"], int(options["--login-timeout"])) + else: + fail_usage("Failed: You have to enter passphrase (-p) for identity file") + + return conn + +def _login_telnet(options, re_login_string): + re_login = re.compile(re_login_string, re.IGNORECASE) + re_pass = re.compile("(password)|(pass phrase)", re.IGNORECASE) + + conn = fspawn(options, options["--telnet-path"]) + conn.send("set binary\n") + conn.send("open %s -%s\n"%(options["--ip"], options["--ipport"])) + + conn.log_expect(re_login, int(options["--login-timeout"])) + conn.send_eol(options["--username"]) + + ## automatically change end of line separator + screen = conn.read_nonblocking(size=100, timeout=int(options["--shell-timeout"])) + if re_login.search(screen) != None: + options["eol"] = "\n" + conn.send_eol(options["--username"]) + conn.log_expect(re_pass, int(options["--login-timeout"])) + elif re_pass.search(screen) == None: + conn.log_expect(re_pass, int(options["--shell-timeout"])) + + try: + conn.send_eol(options["--password"]) + valid_password = conn.log_expect([re_login] + \ + options["--command-prompt"], int(options["--shell-timeout"])) + if valid_password == 0: + ## password is invalid or we have to change EOL separator + options["eol"] = "\r" + conn.send_eol("") + screen = conn.read_nonblocking(size=100, timeout=int(options["--shell-timeout"])) + ## after sending EOL the fence device can either show 'Login' or 'Password' + if re_login.search(conn.after + screen) != None: + conn.send_eol("") + conn.send_eol(options["--username"]) + conn.log_expect(re_pass, int(options["--login-timeout"])) + conn.send_eol(options["--password"]) + conn.log_expect(options["--command-prompt"], int(options["--login-timeout"])) + except KeyError: + fail(EC_PASSWORD_MISSING) + + return conn + +def _login_ssh_with_password(options, re_login_string): + re_login = re.compile(re_login_string, re.IGNORECASE) + re_pass = re.compile("(password)|(pass phrase)", re.IGNORECASE) + + if options.has_key("--inet6-only"): + force_ipvx = "-6 " + elif options.has_key("--inet4-only"): + force_ipvx = "-4 " + else: + force_ipvx = "" + + command = '%s %s %s@%s -p %s -o PubkeyAuthentication=no' % \ + (options["--ssh-path"], force_ipvx, options["--username"], options["--ip"], options["--ipport"]) + if options.has_key("--ssh-options"): + command += ' ' + options["--ssh-options"] + + conn = fspawn(options, command) + + if options.has_key("telnet_over_ssh"): + # This is for stupid ssh servers (like ALOM) which behave more like telnet + # (ignore name and display login prompt) + result = conn.log_expect( \ + [re_login, "Are you sure you want to continue connecting (yes/no)?"], + int(options["--login-timeout"])) + if result == 1: + conn.sendline("yes") # Host identity confirm + conn.log_expect(re_login, int(options["--login-timeout"])) + + conn.sendline(options["--username"]) + conn.log_expect(re_pass, int(options["--login-timeout"])) + else: + result = conn.log_expect( \ + ["ssword:", "Are you sure you want to continue connecting (yes/no)?"], + int(options["--login-timeout"])) + if result == 1: + conn.sendline("yes") + conn.log_expect("ssword:", int(options["--login-timeout"])) + + conn.sendline(options["--password"]) + conn.log_expect(options["--command-prompt"], int(options["--login-timeout"])) + + return conn + +# +# To update metadata, we change values in all_opt +def _update_metadata(options): + device_opt = options["device_opt"] + + if device_opt.count("login") and device_opt.count("no_login") == 0: + all_opt["login"]["required"] = "1" + else: + all_opt["login"]["required"] = "0" + + if device_opt.count("port_as_ip"): + all_opt["ipaddr"]["required"] = "0" + all_opt["port"]["required"] = "0" + + (available_actions, default_value) = _get_available_actions(device_opt) + all_opt["action"]["default"] = default_value + + actions_with_default = \ + [x if not x == all_opt["action"]["default"] else x + " (default)" for x in available_actions] + all_opt["action"]["help"] = \ + "-o, --action=[action] Action: %s" % (_join_wrap(actions_with_default, last_separator=" or ")) + + if device_opt.count("ipport"): + default_value = None + default_string = None + + if all_opt["ipport"].has_key("default"): + default_value = all_opt["ipport"]["default"] + elif device_opt.count("web") and device_opt.count("ssl"): + default_value = "80" + default_string = "(default 80, 443 if --ssl option is used)" + elif device_opt.count("telnet") and device_opt.count("secure"): + default_value = "23" + default_string = "(default 23, 22 if --ssh option is used)" + else: + tcp_ports = {"community" : "161", "secure" : "22", "telnet" : "23", "web" : "80", "ssl" : "443"} + # all cases where next command returns multiple results are covered by previous blocks + protocol = [x for x in ["community", "secure", "ssl", "web", "telnet"] if device_opt.count(x)][0] + default_value = tcp_ports[protocol] + + if default_string is None: + all_opt["ipport"]["help"] = "-u, --ipport=[port] TCP/UDP port to use (default %s)" % \ + (default_value) + else: + all_opt["ipport"]["help"] = "-u, --ipport=[port] TCP/UDP port to use\n" + " "*40 + default_string + +def _set_default_values(options): + if "ipport" in options["device_opt"]: + if not "--ipport" in options: + if "default" in all_opt["ipport"]: + options["--ipport"] = all_opt["ipport"]["default"] + elif "community" in options["device_opt"]: + options["--ipport"] = "161" + elif "--ssh" in options or all_opt["secure"].get("default", "0") == "1": + options["--ipport"] = "22" + elif "--ssl" in options or all_opt["ssl"].get("default", "0") == "1": + options["--ipport"] = "443" + elif "--ssl-secure" in options or all_opt["ssl_secure"].get("default", "0") == "1": + options["--ipport"] = "443" + elif "--ssl-insecure" in options or all_opt["ssl_insecure"].get("default", "0") == "1": + options["--ipport"] = "443" + elif "web" in options["device_opt"]: + options["--ipport"] = "80" + elif "telnet" in options["device_opt"]: + options["--ipport"] = "23" + + if "--ipport" in options: + all_opt["ipport"]["default"] = options["--ipport"] + + for opt in options["device_opt"]: + if all_opt[opt].has_key("default") and not opt == "ipport": + getopt_long = "--" + all_opt[opt]["longopt"] + if not options.has_key(getopt_long): + options[getopt_long] = all_opt[opt]["default"] + + return options + +# stop = True/False : exit fence agent when problem is encountered +def _validate_input(options, stop = True): + device_opt = options["device_opt"] + valid_input = True + + if not options.has_key("--username") and \ + device_opt.count("login") and (device_opt.count("no_login") == 0): + valid_input = False + fail_usage("Failed: You have to set login name", stop) + + if device_opt.count("ipaddr") and not options.has_key("--ip") and not options.has_key("--managed"): + valid_input = False + fail_usage("Failed: You have to enter fence address", stop) + + if device_opt.count("no_password") == 0: + if 0 == device_opt.count("identity_file"): + if not (options.has_key("--password") or options.has_key("--password-script")): + valid_input = False + fail_usage("Failed: You have to enter password or password script", stop) + else: + if not (options.has_key("--password") or \ + options.has_key("--password-script") or options.has_key("--identity-file")): + valid_input = False + fail_usage("Failed: You have to enter password, password script or identity file", stop) + + if not options.has_key("--ssh") and options.has_key("--identity-file"): + valid_input = False + fail_usage("Failed: You have to use identity file together with ssh connection (-x)", stop) + + if options.has_key("--identity-file") and not os.path.isfile(options["--identity-file"]): + valid_input = False + fail_usage("Failed: Identity file " + options["--identity-file"] + " does not exist", stop) + + if (0 == ["list", "list-status", "monitor"].count(options["--action"])) and \ + not options.has_key("--plug") and device_opt.count("port") and \ + device_opt.count("no_port") == 0 and not device_opt.count("port_as_ip"): + valid_input = False + fail_usage("Failed: You have to enter plug number or machine identification", stop) + + if options.has_key("--plug") and len(options["--plug"].split(",")) > 1 and \ + options.has_key("--method") and options["--method"] == "cycle": + valid_input = False + fail_usage("Failed: Cannot use --method cycle for more than 1 plug", stop) + + for failed_opt in _get_opts_with_invalid_choices(options): + valid_input = False + fail_usage("Failed: You have to enter a valid choice for %s from the valid values: %s" % \ + ("--" + all_opt[failed_opt]["longopt"], str(all_opt[failed_opt]["choices"])), stop) + + return valid_input + +def _encode_html_entities(text): + return text.replace("&", "&").replace('"', """).replace('<', "<"). \ + replace('>', ">").replace("'", "'") + +def _prepare_getopt_args(options): + getopt_string = "" + longopt_list = [] + for k in options: + if all_opt.has_key(k) and all_opt[k]["getopt"] != ":": + # getopt == ":" means that opt is without short getopt, but has value + getopt_string += all_opt[k]["getopt"] + elif not all_opt.has_key(k): + fail_usage("Parse error: unknown option '"+k+"'") + + if all_opt.has_key(k) and all_opt[k].has_key("longopt"): + if all_opt[k]["getopt"].endswith(":"): + longopt_list.append(all_opt[k]["longopt"] + "=") + else: + longopt_list.append(all_opt[k]["longopt"]) + + return (getopt_string, longopt_list) + +def _parse_input_stdin(avail_opt): + 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 avail_opt.count(name) == 0 and name in ["nodename"]: + continue + elif avail_opt.count(name) == 0: + logging.warning("Parse error: Ignoring unknown option '%s'\n", line) + continue + + if all_opt[name]["getopt"].endswith(":"): + opt["--"+all_opt[name]["longopt"].rstrip(":")] = value + elif value.lower() in ["1", "yes", "on", "true"]: + opt["--"+all_opt[name]["longopt"]] = "1" + else: + logging.warning("Parse error: Ignoring option '%s' because it does not have value\n", name) + return opt + +def _parse_input_cmdline(avail_opt): + filtered_opts = {} + _verify_unique_getopt(avail_opt) + (getopt_string, longopt_list) = _prepare_getopt_args(avail_opt) + + try: + (entered_opt, left_arg) = getopt.gnu_getopt(sys.argv[1:], getopt_string, longopt_list) + if len(left_arg) > 0: + logging.warning("Unused arguments on command line: %s" % (str(left_arg))) + except getopt.GetoptError, error: + fail_usage("Parse error: " + error.msg) + + for opt in avail_opt: + filtered_opts.update({opt : all_opt[opt]}) + + # Short and long getopt names are changed to consistent "--" + long name (e.g. --username) + long_opts = {} + for arg_name in dict(entered_opt).keys(): + all_key = [key for (key, value) in filtered_opts.items() \ + if "--" + value.get("longopt", "") == arg_name or "-" + value.get("getopt", "").rstrip(":") == arg_name][0] + long_opts["--" + filtered_opts[all_key]["longopt"]] = dict(entered_opt)[arg_name] + + # This test is specific because it does not apply to input on stdin + if "port_as_ip" in avail_opt and not "--port-as-ip" in long_opts and "--plug" in long_opts: + fail_usage("Parser error: option -n/--plug is not recognized") + + return long_opts + +# for ["John", "Mary", "Eli"] returns "John, Mary and Eli" +def _join2(words, normal_separator=", ", last_separator=" and "): + if len(words) <= 1: + return "".join(words) + else: + return last_separator.join([normal_separator.join(words[:-1]), words[-1]]) + +def _join_wrap(words, normal_separator=", ", last_separator=" and ", first_indent=42): + x = _join2(words, normal_separator, last_separator) + wrapper = textwrap.TextWrapper() + wrapper.initial_indent = " "*first_indent + wrapper.subsequent_indent = " "*40 + wrapper.width = 85 + wrapper.break_on_hyphens = False + wrapper.break_long_words = False + wrapped_text = "" + for line in wrapper.wrap(x): + wrapped_text += line + "\n" + return wrapped_text.lstrip().rstrip("\n") + +def _get_opts_with_invalid_choices(options): + options_failed = [] + device_opt = options["device_opt"] + + for opt in device_opt: + if all_opt[opt].has_key("choices"): + longopt = "--" + all_opt[opt]["longopt"] + possible_values_upper = [y.upper() for y in all_opt[opt]["choices"]] + if options.has_key(longopt): + options[longopt] = options[longopt].upper() + if not options["--" + all_opt[opt]["longopt"]] in possible_values_upper: + options_failed.append(opt) + return options_failed + +def _verify_unique_getopt(avail_opt): + used_getopt = set() + + for opt in avail_opt: + getopt_value = all_opt[opt].get("getopt", "").rstrip(":") + if getopt_value and getopt_value in used_getopt: + fail_usage("Short getopt for %s (-%s) is not unique" % (opt, getopt_value)) + else: + used_getopt.add(getopt_value) + +def _get_available_actions(device_opt): + available_actions = ["on", "off", "reboot", "status", "list", "list-status", \ + "monitor", "metadata", "validate-all"] + default_value = "reboot" + + if device_opt.count("fabric_fencing"): + available_actions.remove("reboot") + default_value = "off" + if device_opt.count("no_status"): + available_actions.remove("status") + if device_opt.count("no_on"): + available_actions.remove("on") + if device_opt.count("no_off"): + available_actions.remove("off") + if not device_opt.count("separator"): + available_actions.remove("list") + available_actions.remove("list-status") + + return (available_actions, default_value) |