diff --git a/agents/alom/fence_alom.py b/agents/alom/fence_alom.py index a8e216f3..e593f16b 100644 --- a/agents/alom/fence_alom.py +++ b/agents/alom/fence_alom.py @@ -1,53 +1,53 @@ #!@PYTHON@ -tt # The Following Agent Has Been Tested On: # # Sun(tm) Advanced Lights Out Manager CMT v1.6.1 # as found on SUN T2000 Niagara import sys, re, time import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * def get_power_status(conn, options): conn.send_eol("showplatform") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) - status = re.search("standby", conn.before.lower()) + status = re.search(r"standby", conn.before.lower()) result = (status != None and "off" or "on") return result def set_power_status(conn, options): cmd_line = (options["--action"] == "on" and "poweron" or "poweroff -f -y") conn.send_eol(cmd_line) conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) # Get the machine some time between poweron and poweroff time.sleep(int(options["--power-timeout"])) def main(): device_opt = ["ipaddr", "login", "passwd", "cmd_prompt", "secure"] atexit.register(atexit_handler) all_opt["secure"]["default"] = "1" all_opt["cmd_prompt"]["default"] = [r"sc\>\ "] options = check_input(device_opt, process_input(device_opt)) options["telnet_over_ssh"] = 1 docs = {} docs["shortdesc"] = "Fence agent for Sun ALOM" docs["longdesc"] = "fence_alom is a Power Fencing agent \ which can be used with ALOM connected machines." docs["vendorurl"] = "http://www.sun.com" show_docs(options, docs) # Operate the fencing device conn = fence_login(options) result = fence_action(conn, options, set_power_status, get_power_status, None) fence_logout(conn, "logout") sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/amt/fence_amt.py b/agents/amt/fence_amt.py index 183bbc71..67e2112d 100644 --- a/agents/amt/fence_amt.py +++ b/agents/amt/fence_amt.py @@ -1,132 +1,132 @@ #!@PYTHON@ -tt import sys, re, os import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail_usage, is_executable, run_command, run_delay try: from shlex import quote except ImportError: from pipes import quote def get_power_status(_, options): output = amt_run_command(options, create_command(options, "status")) - match = re.search('Powerstate:[\\s]*(..)', str(output)) + match = re.search(r'Powerstate:[\s]*(..)', str(output)) status = match.group(1) if match else None if status == None: return "fail" elif status == "S0": # SO = on; S3 = sleep; S5 = off return "on" else: return "off" def set_power_status(_, options): amt_run_command(options, create_command(options, options["--action"])) return def reboot_cycle(_, options): (status, _, _) = run_command(options, create_command(options, "cycle")) return not bool(status) def amt_run_command(options, command, timeout=None): env = os.environ.copy() x = quote(options["--password"]) x = x[:-1] if x.endswith("'") else x x = x[1:] if x.startswith("'") else x env["AMT_PASSWORD"] = x # This is needed because setting the AMT_PASSWORD env # variable only works when no pipe is involved. E.g.: # - Broken: # $ AMT_PASSWORD='foobar' echo 'y' | /usr/bin/amttool nuc2 powerdown # 401 Unauthorized at /usr/bin/amttool line 129. # - Working: # $ AMT_PASSWORD='foobar' sh -c "(echo 'y' | /usr/bin/amttool nuc2 powerdown)" # execute: powerdown # result: pt_status: success newcommand = "sh -c \"(%s)\"" % command return run_command(options, newcommand, timeout, env) def create_command(options, action): cmd = options["--amttool-path"] # --ip / -a cmd += " " + options["--ip"] # --action / -o if action == "status": cmd += " info" elif action == "on": cmd = "echo \"y\"|" + cmd cmd += " powerup" elif action == "off": cmd = "echo \"y\"|" + cmd cmd += " powerdown" elif action == "cycle": cmd = "echo \"y\"|" + cmd cmd += " powercycle" if action in ["on", "off", "cycle"] and "--boot-option" in options: cmd += options["--boot-option"] # --use-sudo / -d if "--use-sudo" in options: cmd = options["--sudo-path"] + " " + cmd return cmd def define_new_opts(): all_opt["boot_option"] = { "getopt" : "b:", "longopt" : "boot-option", "help" : "-b, --boot-option=[option] " "Change the default boot behavior of the machine. (pxe|hd|hdsafe|cd|diag)", "required" : "0", "shortdesc" : "Change the default boot behavior of the machine.", "choices" : ["pxe", "hd", "hdsafe", "cd", "diag"], "order" : 1 } all_opt["amttool_path"] = { "getopt" : ":", "longopt" : "amttool-path", "help" : "--amttool-path=[path] Path to amttool binary", "required" : "0", "shortdesc" : "Path to amttool binary", "default" : "@AMTTOOL_PATH@", "order": 200 } def main(): atexit.register(atexit_handler) device_opt = ["ipaddr", "no_login", "passwd", "boot_option", "no_port", "sudo", "amttool_path", "method"] define_new_opts() all_opt["ipport"]["default"] = "16994" options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for AMT" docs["longdesc"] = "fence_amt is a Power Fencing agent \ which can be used with Intel AMT. This agent calls support software amttool\ (http://www.kraxel.org/cgit/amtterm/)." docs["vendorurl"] = "http://www.intel.com/" show_docs(options, docs) run_delay(options) if not is_executable(options["--amttool-path"]): fail_usage("Amttool not found or not accessible") result = fence_action(None, options, set_power_status, get_power_status, None, reboot_cycle) sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/apc/fence_apc.py b/agents/apc/fence_apc.py index bc52aa24..805b8314 100644 --- a/agents/apc/fence_apc.py +++ b/agents/apc/fence_apc.py @@ -1,263 +1,263 @@ #!@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, time import logging import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, fail_usage, EC_STATUS # Fix for connection timed out issue in: # https://bugzilla.redhat.com/show_bug.cgi?id=1342584 TIMEDOUT_DELAY = 0.5 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): + if None != re.compile(r'.* 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 None != re.compile(r'.* MasterSwitch plus 2', re.IGNORECASE | re.S).match(conn.before): if "--switch" not in options: fail_usage("Failed: You have to enter physical switch number") else: if "--switch" not in options: options["--switch"] = "1" - if None == re.compile('.*Outlet Management.*', re.IGNORECASE | re.S).match(conn.before): + if None == re.compile(r'.*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): + if None == re.compile(r'.*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 "] + 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)) time.sleep(TIMEDOUT_DELAY) conn.send_eol("") if exp_result != 0: break conn.send(chr(0o3)) 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 as e: logging.error("Failed: {}".format(str(e))) 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): + if None != re.compile(r'.* 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 None != re.compile(r'.* MasterSwitch plus 2', re.IGNORECASE | re.S).match(conn.before): if "--switch" not in options: fail_usage("Failed: You have to enter physical switch number") else: if "--switch" not in options: options["--switch"] = 1 - if None == re.compile('.*Outlet Management.*', re.IGNORECASE | re.S).match(conn.before): + if None == re.compile(r'.*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): + if None == re.compile(r'.*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): + if None == re.compile(r'.*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 "] + options["--command-prompt"], int(options["--shell-timeout"])): time.sleep(TIMEDOUT_DELAY) 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 to cancel :", int(options["--shell-timeout"])) conn.send_eol("YES") conn.log_expect("Press to continue...", int(options["--power-timeout"])) time.sleep(TIMEDOUT_DELAY) conn.send_eol("") conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) conn.send(chr(0o3)) 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 as e: logging.error("Failed: {}".format(str(e))) 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 a Power 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 (("--plug" in options) == 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", "7" ]): 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/azure_arm/fence_azure_arm.py b/agents/azure_arm/fence_azure_arm.py index 0dca8f30..227a7c2e 100755 --- a/agents/azure_arm/fence_azure_arm.py +++ b/agents/azure_arm/fence_azure_arm.py @@ -1,270 +1,270 @@ #!@PYTHON@ -tt import sys, re, pexpect import logging import atexit import xml.etree.ElementTree as ET sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail_usage, run_command, run_delay import azure_fence def get_nodes_list(clients, options): result = {} if clients: compute_client = clients[0] rgName = options["--resourceGroup"] vms = compute_client.virtual_machines.list(rgName) try: for vm in vms: result[vm.name] = ("", None) except Exception as e: fail_usage("Failed: %s" % e) return result def check_unfence(clients, options): if clients: compute_client = clients[0] network_client = clients[1] rgName = options["--resourceGroup"] try: vms = compute_client.virtual_machines.list(rgName) except Exception as e: fail_usage("Failed: %s" % e) for vm in vms: vmName = vm.name if azure_fence.get_network_state(compute_client, network_client, rgName, vmName) == "off": logging.info("Found fenced node " + vmName) # dont return "off" based on network-fencing status options.pop("--network-fencing", None) options["--plug"] = vmName if get_power_status(clients, options) == "off": logging.info("Unfencing " + vmName) options["--network-fencing"] = "" options["--action"] = "on" set_power_status(clients, options) options["--action"] = "monitor" def get_power_status(clients, options): vmstate = { "running": "on", "deallocated": "off", "stopped": "off" } logging.info("getting power status for VM " + options["--plug"]) if clients: compute_client = clients[0] rgName = options["--resourceGroup"] vmName = options["--plug"] if "--network-fencing" in options: network_client = clients[1] netState = azure_fence.get_network_state(compute_client, network_client, rgName, vmName) logging.info("Found network state of VM: " + netState) # return off quickly once network is fenced instead of waiting for vm state to change if options["--action"] == "off" and netState == "off": logging.info("Network fenced for " + vmName) return netState powerState = "unknown" try: vmStatus = compute_client.virtual_machines.get(rgName, vmName, expand="instanceView") except Exception as e: fail_usage("Failed: %s" % e) for status in vmStatus.instance_view.statuses: if status.code.startswith("PowerState"): powerState = status.code.split("/")[1] break vmState = vmstate.get(powerState, "unknown") logging.info("Found power state of VM: %s (%s)" % (vmState, powerState)) if "--network-fencing" in options and netState == "off": return "off" if options["--action"] != "on" and vmState != "off": return "on" if vmState == "on": return "on" return "off" def set_power_status(clients, options): logging.info("setting power status for VM " + options["--plug"] + " to " + options["--action"]) if clients: compute_client = clients[0] rgName = options["--resourceGroup"] vmName = options["--plug"] if "--network-fencing" in options: network_client = clients[1] if (options["--action"]=="off"): logging.info("Fencing network for " + vmName) azure_fence.set_network_state(compute_client, network_client, rgName, vmName, "block") elif (options["--action"]=="on"): logging.info("Unfencing network for " + vmName) azure_fence.set_network_state(compute_client, network_client, rgName, vmName, "unblock") if (options["--action"]=="off"): logging.info("Poweroff " + vmName + " in resource group " + rgName) try: # try new API version first compute_client.virtual_machines.begin_power_off(rgName, vmName, skip_shutdown=True) except AttributeError: # use older API verson if it fails logging.debug("Poweroff " + vmName + " did not work via 'virtual_machines.begin_power_off. Trying virtual_machines.power_off'.") compute_client.virtual_machines.power_off(rgName, vmName, skip_shutdown=True) elif (options["--action"]=="on"): logging.info("Starting " + vmName + " in resource group " + rgName) try: # try new API version first compute_client.virtual_machines.begin_start(rgName, vmName) except AttributeError: # use older API verson if it fails logging.debug("Starting " + vmName + " did not work via 'virtual_machines.begin_start. Trying virtual_machines.start'.") compute_client.virtual_machines.start(rgName, vmName) def define_new_opts(): all_opt["resourceGroup"] = { "getopt" : ":", "longopt" : "resourceGroup", "help" : "--resourceGroup=[name] Name of the resource group", "shortdesc" : "Name of resource group. Metadata service is used if the value is not provided.", "required" : "0", "order" : 2 } all_opt["tenantId"] = { "getopt" : ":", "longopt" : "tenantId", "help" : "--tenantId=[name] Id of the Azure Active Directory tenant", "shortdesc" : "Id of Azure Active Directory tenant.", "required" : "0", "order" : 3 } all_opt["subscriptionId"] = { "getopt" : ":", "longopt" : "subscriptionId", "help" : "--subscriptionId=[name] Id of the Azure subscription", "shortdesc" : "Id of the Azure subscription. Metadata service is used if the value is not provided.", "required" : "0", "order" : 4 } all_opt["network-fencing"] = { "getopt" : "", "longopt" : "network-fencing", "help" : "--network-fencing Use network fencing. See NOTE-section of\n\ metadata for required Subnet/Network Security\n\ Group configuration.", "shortdesc" : "Use network fencing. See NOTE-section for configuration.", "required" : "0", "order" : 5 } all_opt["msi"] = { "getopt" : "", "longopt" : "msi", "help" : "--msi Use Managed Service Identity instead of\n\ username and password. If specified,\n\ parameters tenantId, login and passwd are not\n\ allowed.", "shortdesc" : "Determines if Managed Service Identity should be used.", "required" : "0", "order" : 6 } all_opt["cloud"] = { "getopt" : ":", "longopt" : "cloud", "help" : "--cloud=[name] Name of the cloud you want to use. Supported\n\ values are china, germany, usgov, or stack. Do\n\ not use this parameter if you want to use\n\ public Azure.", "shortdesc" : "Name of the cloud you want to use.", "required" : "0", "order" : 7 } all_opt["metadata-endpoint"] = { "getopt" : ":", "longopt" : "metadata-endpoint", "help" : "--metadata-endpoint=[URL] URL to metadata endpoint (used when cloud=stack).", "shortdesc" : "URL to metadata endpoint (used when cloud=stack).", "required" : "0", "order" : 8 } # Main agent method def main(): compute_client = None network_client = None device_opt = ["login", "no_login", "no_password", "passwd", "port", "resourceGroup", "tenantId", "subscriptionId", "network-fencing", "msi", "cloud", "metadata-endpoint"] atexit.register(atexit_handler) define_new_opts() all_opt["power_timeout"]["default"] = "150" all_opt["login"]["help"] = "-l, --username=[appid] Application ID" all_opt["passwd"]["help"] = "-p, --password=[authkey] Authentication key" options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for Azure Resource Manager" docs["longdesc"] = "fence_azure_arm is a Power Fencing agent for Azure Resource Manager. It uses Azure SDK for Python to connect to Azure.\ \n.P\n\ For instructions to setup credentials see: https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal\ \n.P\n\ Username and password are application ID and authentication key from \"App registrations\".\ \n.P\n\ NOTE: NETWORK FENCING\n.br\n\ Network fencing requires an additional Subnet named \"fence-subnet\" for the Virtual Network using a Network Security Group with the following rules:\n.br\n\ +-----------+-----+-------------------------+------+------+-----+-----+--------+\n.br\n\ | DIRECTION | PRI | NAME | PORT | PROT | SRC | DST | ACTION |\n.br\n\ +-----------+-----+-------------------------+------+------+-----+-----+--------+\n.br\n\ | Inbound | 100 | FENCE_DENY_ALL_INBOUND | Any | Any | Any | Any | Deny |\n.br\n\ | Outbound | 100 | FENCE_DENY_ALL_OUTBOUND | Any | Any | Any | Any | Deny |\n.br\n\ +-----------+-----+-------------------------+------+------+-----+-----+--------+\ \n.P\n\ When using network fencing the reboot-action will cause a quick-return once the network has been fenced (instead of waiting for the off-action to succeed). It will check the status during the monitor-action, and request power-on when the shutdown operation is complete." docs["vendorurl"] = "http://www.microsoft.com" show_docs(options, docs) run_delay(options) try: config = azure_fence.get_azure_config(options) options["--resourceGroup"] = config.RGName compute_client = azure_fence.get_azure_compute_client(config) if "--network-fencing" in options: network_client = azure_fence.get_azure_network_client(config) except ImportError: fail_usage("Azure Resource Manager Python SDK not found or not accessible") except Exception as e: - fail_usage("Failed: %s" % re.sub("^, ", "", str(e))) + fail_usage("Failed: %s" % re.sub(r"^, ", r"", str(e))) if "--network-fencing" in options: # use off-action to quickly return off once network is fenced instead of # waiting for vm state to change if options["--action"] == "reboot": options["--action"] = "off" # check for devices to unfence in monitor-action elif options["--action"] == "monitor": check_unfence([compute_client, network_client], options) # Operate the fencing device result = fence_action([compute_client, network_client], options, set_power_status, get_power_status, get_nodes_list) sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/cdu/fence_cdu.py b/agents/cdu/fence_cdu.py index ba76e6d7..6373158e 100644 --- a/agents/cdu/fence_cdu.py +++ b/agents/cdu/fence_cdu.py @@ -1,176 +1,176 @@ #!@PYTHON@ -tt # fence_cdu - fence agent for a Sentry Switch CDU. # # Copyright (C) 2012 Canonical Ltd. # Copyright (C) 2021 SUSE Linux GmbH # # Authors: Andres Rodriguez # Thomas Renninger # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . ##### ## ## The Following Agent Has Been Tested On: ## ## Model Firmware ## +---------------------------------------------+ ## Sentry Switched CDU 6a ## Sentry Switched CDU 7.1c ## Sentry Switched CDU 7.1f ## Sentry Switched PDU 8.0i ## ## ##### import sys, re, pexpect, atexit, logging sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, EC_TIMED_OUT, run_command, frun, EC_STATUS def get_power_status(conn, options): exp_result = 0 outlets = {} try: if options["api-version"] == "8": conn.send("STATUS ALL\r\n") else: conn.send("STATUS\r\n") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) lines = conn.before.split("\n") if options["api-version"] == "8": # AA13 Arm-Console3 Wake On On Normal # AA14 Master_Outlet_14 Wake On On Normal - show_re = re.compile('(\w+)\s+(\S+)\s+(On|Idle On|Off|Wake On)\s+(On|Off)') + show_re = re.compile(r'(\w+)\s+(\S+)\s+(On|Idle On|Off|Wake On)\s+(On|Off)') else: # .A12 TowerA_Outlet12 On Idle On # .A12 test-01 On Idle On - show_re = re.compile('(\.\w+)\s+(\w+|\w+\W\w+)\s+(On|Off)\s+(On|Idle On|Off|Wake On)') + show_re = re.compile(r'(\.\w+)\s+(\w+|\w+\W\w+)\s+(On|Off)\s+(On|Idle On|Off|Wake On)') for line in lines: res = show_re.search(line) if res != None: plug_id = res.group(1) plug_name = res.group(2) print(plug_name) plug_state = res.group(3) if options["api-version"] == "8": plug_state = res.group(4) outlets[plug_name] = (plug_id, plug_state) except pexpect.EOF: fail(EC_CONNECTION_LOST) except pexpect.TIMEOUT: fail(EC_TIMED_OUT) try: (_, status) = outlets[options["--plug"]] return status.lower().strip() except KeyError: fail(EC_STATUS) def set_power_status(conn, options): outlets = {} action = { 'on' : "on", 'off': "off" }[options["--action"]] try: conn.send("LIST OUTLETS\r\n") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) lines = conn.before.split("\n") # if options["api-version"] == "8": # AA13 Arm-Console3 # AA14 Master_Outlet_14 # else: # .A12 TowerA_Outlet12 # .A12 test-01 - show_re = re.compile('(\S+)\s+(\w+|\w+\W\w+)\s+') + show_re = re.compile(r'(\S+)\s+(\w+|\w+\W\w+)\s+') for line in lines: res = show_re.search(line) if res != None: outlets[res.group(2)] = (res.group(1)) conn.send(action + " " + outlets[options["--plug"]] + "\r\n") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) except pexpect.EOF: fail(EC_CONNECTION_LOST) except pexpect.TIMEOUT: fail(EC_TIMED_OUT) def disconnect(conn): conn.sendline("LOGOUT") conn.close() def get_version(conn, options): api_ver = "6" sub = "a" minor = "" conn.send("VERSION\r\n") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) lines = conn.before.split("\n") - show_re = re.compile('Sentry Switched [PC]DU Version (\d)(.\d|)(\w)\r') + show_re = re.compile(r'Sentry Switched [PC]DU Version (\d)(.\d|)(\w)\r') for line in lines: res = show_re.search(line) if res != None: api_ver = res.group(1) if res.group(2): sub = res.group(2).lstrip(".") minor = res.group(3) return (api_ver, sub, minor) def main(): device_opt = [ "ipaddr", "login", "port", "switch", "passwd", "telnet" ] atexit.register(atexit_handler) options = check_input(device_opt, process_input(device_opt)) ## ## Fence agent specific defaults ##### options["--command-prompt"] = "Switched [PC]DU: " docs = { } docs["shortdesc"] = "Fence agent for a Sentry Switch CDU over telnet" docs["longdesc"] = "fence_cdu is a Power Fencing agent \ which can be used with the Sentry Switch CDU. It logs into the device \ via telnet and power's on/off an outlet." docs["vendorurl"] = "http://www.servertech.com" show_docs(options, docs) ## Support for --plug [switch]:[plug] notation that was used before opt_n = options.get("--plug") if opt_n and (-1 != opt_n.find(":")): (switch, plug) = opt_n.split(":", 1) options["--switch"] = switch; options["--plug"] = plug; ## ## Operate the fencing device #### conn = fence_login(options) (api_ver, sub, minor) = get_version(conn, options) options["api-version"] = api_ver logging.debug("Using API version: %s" % api_ver) if api_ver == "7": # disable output paging conn.sendline("set option more disabled") conn.log_expect(options["--command-prompt"], int(options["--login-timeout"])) result = fence_action(conn, options, set_power_status, get_power_status, get_power_status) ## ## Logout from system ## ## In some special unspecified cases it is possible that ## connection will be closed before we run close(). This is not ## a problem because everything is checked before. ###### atexit.register(disconnect, conn) sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/cisco_ucs/fence_cisco_ucs.py b/agents/cisco_ucs/fence_cisco_ucs.py index cada20d5..bcfabdba 100644 --- a/agents/cisco_ucs/fence_cisco_ucs.py +++ b/agents/cisco_ucs/fence_cisco_ucs.py @@ -1,198 +1,198 @@ #!@PYTHON@ -tt import sys, re import pycurl, io import logging import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, EC_STATUS, EC_LOGIN_DENIED, run_delay -RE_COOKIE = re.compile("", int(options["--shell-timeout"])) result = RE_GET_PNDN.search(res) if result == None: pndn = "" else: pndn = result.group(1) if pndn.strip() == "": if "--missing-as-off" in options: return "off" else: fail(EC_STATUS) res = send_command(options, "", int(options["--shell-timeout"])) result = RE_GET_PRESENCE.search(res) if result == None: fail(EC_STATUS) else: presence_status = result.group(1) if presence_status in ["missing", "mismatch"]: return "off" else: result = RE_GET_OPERPOWER.search(res) if result == None: fail(EC_STATUS) else: power_status = result.group(1) if power_status == "on": return "on" else: return "off" def set_power_status(conn, options): del conn action = { 'on' : "admin-up", 'off' : "admin-down" }[options["--action"]] send_command(options, "" + "" + "" + "", int(options["--shell-timeout"])) return def get_list(conn, options): del conn outlets = {} try: res = send_command(options, "", int(options["--shell-timeout"])) lines = res.split("", int(options_global["--shell-timeout"])) except Exception: pass def main(): global options_global device_opt = ["ipaddr", "login", "passwd", "ssl", "notls", "port", "web", "suborg", "missing_as_off"] atexit.register(atexit_handler) atexit.register(logout) define_new_opts() options_global = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for Cisco UCS" docs["longdesc"] = "fence_cisco_ucs is a Power Fencing agent which can be \ used with Cisco UCS to fence machines." docs["vendorurl"] = "http://www.cisco.com" show_docs(options_global, docs) run_delay(options_global) ### Login try: res = send_command(options_global, "", int(options_global["--login-timeout"])) result = RE_COOKIE.search(res) if result == None: ## Cookie is absenting in response fail(EC_LOGIN_DENIED) except Exception as e: logging.error("Failed: {}".format(str(e))) fail(EC_LOGIN_DENIED) options_global["cookie"] = result.group(1) ## ## Modify suborg to format /suborg if options_global["--suborg"] != "": options_global["--suborg"] = "/" + options_global["--suborg"].lstrip("/").rstrip("/") ## ## Fence operations #### result = fence_action(None, options_global, set_power_status, get_power_status, get_list) ## Logout is done every time at atexit phase sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/gce/fence_gce.py b/agents/gce/fence_gce.py index b8871038..759567c6 100644 --- a/agents/gce/fence_gce.py +++ b/agents/gce/fence_gce.py @@ -1,632 +1,632 @@ #!@PYTHON@ -tt # # Requires the googleapiclient and oauth2client # RHEL 7.x: google-api-python-client==1.6.7 python-gflags==2.0 pyasn1==0.4.8 rsa==3.4.2 pysocks==1.7.1 httplib2==0.19.0 # RHEL 8.x: pysocks==1.7.1 httplib2==0.19.0 # SLES 12.x: python-google-api-python-client python-oauth2client python-oauth2client-gce pysocks==1.7.1 httplib2==0.19.0 # SLES 15.x: python3-google-api-python-client python3-oauth2client pysocks==1.7.1 httplib2==0.19.0 # import atexit import logging import json import re import os import socket import sys import time from ssl import SSLError if sys.version_info >= (3, 0): # Python 3 imports. import urllib.parse as urlparse import urllib.request as urlrequest else: # Python 2 imports. import urllib as urlparse import urllib2 as urlrequest sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import fail_usage, run_delay, all_opt, atexit_handler, check_input, process_input, show_docs, fence_action, run_command try: import httplib2 import googleapiclient.discovery import socks try: from google.oauth2.credentials import Credentials as GoogleCredentials except: from oauth2client.client import GoogleCredentials except: pass VERSION = '1.0.5' ACTION_IDS = { 'on': 1, 'off': 2, 'reboot': 3, 'status': 4, 'list': 5, 'list-status': 6, 'monitor': 7, 'metadata': 8, 'manpage': 9, 'validate-all': 10 } USER_AGENT = 'sap-core-eng/fencegce/%s/%s/ACTION/%s' METADATA_SERVER = 'http://metadata.google.internal/computeMetadata/v1/' METADATA_HEADERS = {'Metadata-Flavor': 'Google'} INSTANCE_LINK = 'https://www.googleapis.com/compute/v1/projects/{}/zones/{}/instances/{}' def run_on_fail(options): if "--runonfail" in options: run_command(options, options["--runonfail"]) def fail_fence_agent(options, message): run_on_fail(options) fail_usage(message) def raise_fence_agent(options, message): run_on_fail(options) raise Exception(message) # # Will use baremetalsolution setting or the environment variable # FENCE_GCE_URI_REPLACEMENTS to replace the uri for calls to *.googleapis.com. # def replace_api_uri(options, http_request): uri_replacements = [] # put any env var replacements first, then baremetalsolution if in options if "FENCE_GCE_URI_REPLACEMENTS" in os.environ: logging.debug("FENCE_GCE_URI_REPLACEMENTS environment variable exists") env_uri_replacements = os.environ["FENCE_GCE_URI_REPLACEMENTS"] try: uri_replacements_json = json.loads(env_uri_replacements) if isinstance(uri_replacements_json, list): uri_replacements = uri_replacements_json else: logging.warning("FENCE_GCE_URI_REPLACEMENTS exists, but is not a JSON List") except ValueError as e: logging.warning("FENCE_GCE_URI_REPLACEMENTS exists but is not valid JSON") if "--baremetalsolution" in options: uri_replacements.append( { "matchlength": 4, - "match": "https://compute.googleapis.com/compute/v1/projects/(.*)/zones/(.*)/instances/(.*)/reset(.*)", - "replace": "https://baremetalsolution.googleapis.com/v1/projects/\\1/locations/\\2/instances/\\3:resetInstance\\4" + "match": r"https://compute.googleapis.com/compute/v1/projects/(.*)/zones/(.*)/instances/(.*)/reset(.*)", + "replace": r"https://baremetalsolution.googleapis.com/v1/projects/\1/locations/\2/instances/\3:resetInstance\4" }) for uri_replacement in uri_replacements: # each uri_replacement should have matchlength, match, and replace if "matchlength" not in uri_replacement or "match" not in uri_replacement or "replace" not in uri_replacement: logging.warning("FENCE_GCE_URI_REPLACEMENTS missing matchlength, match, or replace in %s" % uri_replacement) continue match = re.match(uri_replacement["match"], http_request.uri) if match is None or len(match.groups()) != uri_replacement["matchlength"]: continue replaced_uri = re.sub(uri_replacement["match"], uri_replacement["replace"], http_request.uri) - match = re.match("https:\/\/.*.googleapis.com", replaced_uri) + match = re.match(r"https:\/\/.*.googleapis.com", replaced_uri) if match is None or match.start() != 0: logging.warning("FENCE_GCE_URI_REPLACEMENTS replace is not " "targeting googleapis.com, ignoring it: %s" % replaced_uri) continue logging.debug("Replacing googleapis uri %s with %s" % (http_request.uri, replaced_uri)) http_request.uri = replaced_uri break return http_request def retry_api_execute(options, http_request): replaced_http_request = replace_api_uri(options, http_request) action = ACTION_IDS[options["--action"]] if options["--action"] in ACTION_IDS else 0 try: user_agent_header = USER_AGENT % (VERSION, options["image"], action) except ValueError: user_agent_header = USER_AGENT % (VERSION, options["image"], 0) replaced_http_request.headers["User-Agent"] = user_agent_header logging.debug("User agent set as %s" % (user_agent_header)) retries = 3 if options.get("--retries"): retries = int(options.get("--retries")) retry_sleep = 5 if options.get("--retrysleep"): retry_sleep = int(options.get("--retrysleep")) retry = 0 current_err = None while retry <= retries: if retry > 0: time.sleep(retry_sleep) try: return replaced_http_request.execute() except Exception as err: current_err = err logging.warning("Could not execute api call to: %s, retry: %s, " "err: %s" % (replaced_http_request.uri, retry, str(err))) retry += 1 raise current_err def translate_status(instance_status): "Returns on | off | unknown." if instance_status == "RUNNING": return "on" elif instance_status == "TERMINATED": return "off" return "unknown" def get_nodes_list(conn, options): result = {} plug = options["--plug"] if "--plug" in options else "" zones = options["--zone"] if "--zone" in options else "" if not zones: zones = get_zone(conn, options, plug) if "--plugzonemap" not in options else options["--plugzonemap"][plug] try: for zone in zones.split(","): instanceList = retry_api_execute(options, conn.instances().list( project=options["--project"], zone=zone)) for instance in instanceList["items"]: result[instance["id"]] = (instance["name"], translate_status(instance["status"])) except Exception as err: fail_fence_agent(options, "Failed: get_nodes_list: {}".format(str(err))) return result def get_power_status(conn, options): logging.debug("get_power_status") # if this is bare metal we need to just send back the opposite of the # requested action: if on send off, if off send on if "--baremetalsolution" in options: if options.get("--action") == "on": return "off" else: return "on" # If zone is not listed for an entry we attempt to get it automatically instance = options["--plug"] zone = get_zone(conn, options, instance) if "--plugzonemap" not in options else options["--plugzonemap"][instance] instance_status = get_instance_power_status(conn, options, instance, zone) # If any of the instances do not match the intended status we return the # the opposite status so that the fence agent can change it. if instance_status != options.get("--action"): return instance_status return options.get("--action") def get_instance_power_status(conn, options, instance, zone): try: instance = retry_api_execute( options, conn.instances().get(project=options["--project"], zone=zone, instance=instance)) return translate_status(instance["status"]) except Exception as err: fail_fence_agent(options, "Failed: get_instance_power_status: {}".format(str(err))) def check_for_existing_operation(conn, options, instance, zone, operation_type): logging.debug("check_for_existing_operation") if "--baremetalsolution" in options: # There is no API for checking in progress operations return False project = options["--project"] target_link = INSTANCE_LINK.format(project, zone, instance) query_filter = '(targetLink = "{}") AND (operationType = "{}") AND (status = "RUNNING")'.format(target_link, operation_type) result = retry_api_execute( options, conn.zoneOperations().list(project=project, zone=zone, filter=query_filter, maxResults=1)) if "items" in result and result["items"]: logging.info("Existing %s operation found", operation_type) return result["items"][0] def wait_for_operation(conn, options, zone, operation): if 'name' not in operation: logging.warning('Cannot wait for operation to complete, the' ' requested operation will continue asynchronously') return False wait_time = 0 project = options["--project"] while True: result = retry_api_execute(options, conn.zoneOperations().get( project=project, zone=zone, operation=operation['name'])) if result['status'] == 'DONE': if 'error' in result: raise_fence_agent(options, result['error']) return True if "--errortimeout" in options and wait_time > int(options["--errortimeout"]): raise_fence_agent(options, "Operation did not complete before the timeout.") if "--warntimeout" in options and wait_time > int(options["--warntimeout"]): logging.warning("Operation did not complete before the timeout.") if "--runonwarn" in options: run_command(options, options["--runonwarn"]) return False wait_time = wait_time + 1 time.sleep(1) def set_power_status(conn, options): logging.debug("set_power_status") instance = options["--plug"] # If zone is not listed for an entry we attempt to get it automatically zone = get_zone(conn, options, instance) if "--plugzonemap" not in options else options["--plugzonemap"][instance] set_instance_power_status(conn, options, instance, zone, options["--action"]) def set_instance_power_status(conn, options, instance, zone, action): logging.info("Setting power status of %s in zone %s", instance, zone) project = options["--project"] try: if action == "off": logging.info("Issuing poweroff of %s in zone %s", instance, zone) operation = check_for_existing_operation(conn, options, instance, zone, "stop") if operation and "--earlyexit" in options: return if not operation: operation = retry_api_execute( options, conn.instances().stop(project=project, zone=zone, instance=instance)) logging.info("Poweroff command completed, waiting for the operation to complete") if wait_for_operation(conn, options, zone, operation): logging.info("Poweroff of %s in zone %s complete", instance, zone) elif action == "on": logging.info("Issuing poweron of %s in zone %s", instance, zone) operation = check_for_existing_operation(conn, options, instance, zone, "start") if operation and "--earlyexit" in options: return if not operation: operation = retry_api_execute( options, conn.instances().start(project=project, zone=zone, instance=instance)) if wait_for_operation(conn, options, zone, operation): logging.info("Poweron of %s in zone %s complete", instance, zone) except Exception as err: fail_fence_agent(options, "Failed: set_instance_power_status: {}".format(str(err))) def power_cycle(conn, options): logging.debug("power_cycle") instance = options["--plug"] # If zone is not listed for an entry we attempt to get it automatically zone = get_zone(conn, options, instance) if "--plugzonemap" not in options else options["--plugzonemap"][instance] return power_cycle_instance(conn, options, instance, zone) def power_cycle_instance(conn, options, instance, zone): logging.info("Issuing reset of %s in zone %s", instance, zone) project = options["--project"] try: operation = check_for_existing_operation(conn, options, instance, zone, "reset") if operation and "--earlyexit" in options: return True if not operation: operation = retry_api_execute( options, conn.instances().reset(project=project, zone=zone, instance=instance)) logging.info("Reset command sent, waiting for the operation to complete") if wait_for_operation(conn, options, zone, operation): logging.info("Reset of %s in zone %s complete", instance, zone) return True except Exception as err: logging.exception("Failed: power_cycle") raise err def get_zone(conn, options, instance): logging.debug("get_zone"); project = options['--project'] fl = 'name="%s"' % instance request = replace_api_uri(options, conn.instances().aggregatedList(project=project, filter=fl)) while request is not None: response = request.execute() zones = response.get('items', {}) for zone in zones.values(): for inst in zone.get('instances', []): if inst['name'] == instance: return inst['zone'].split("/")[-1] request = replace_api_uri(options, conn.instances().aggregatedList_next( previous_request=request, previous_response=response)) raise_fence_agent(options, "Unable to find instance %s" % (instance)) def get_metadata(metadata_key, params=None, timeout=None): """Performs a GET request with the metadata headers. Args: metadata_key: string, the metadata to perform a GET request on. params: dictionary, the query parameters in the GET request. timeout: int, timeout in seconds for metadata requests. Returns: HTTP response from the GET request. Raises: urlerror.HTTPError: raises when the GET request fails. """ logging.debug("get_metadata"); timeout = timeout or 60 metadata_url = os.path.join(METADATA_SERVER, metadata_key) params = urlparse.urlencode(params or {}) url = '%s?%s' % (metadata_url, params) request = urlrequest.Request(url, headers=METADATA_HEADERS) request_opener = urlrequest.build_opener(urlrequest.ProxyHandler({})) return request_opener.open(request, timeout=timeout * 1.1).read().decode("utf-8") def define_new_opts(): all_opt["zone"] = { "getopt" : ":", "longopt" : "zone", "help" : "--zone=[name] Zone, e.g. us-central1-b", "shortdesc" : "Zone.", "required" : "0", "order" : 2 } all_opt["project"] = { "getopt" : ":", "longopt" : "project", "help" : "--project=[name] Project ID", "shortdesc" : "Project ID.", "required" : "0", "order" : 3 } all_opt["stackdriver-logging"] = { "getopt" : "", "longopt" : "stackdriver-logging", "help" : "--stackdriver-logging Enable Logging to Stackdriver", "shortdesc" : "Stackdriver-logging support.", "longdesc" : "If enabled IP failover logs will be posted to stackdriver logging.", "required" : "0", "order" : 4 } all_opt["baremetalsolution"] = { "getopt" : "", "longopt" : "baremetalsolution", "help" : "--baremetalsolution Enable on bare metal", "shortdesc" : "If enabled this is a bare metal offering from google.", "required" : "0", "order" : 5 } all_opt["apitimeout"] = { "getopt" : ":", "type" : "second", "longopt" : "apitimeout", "help" : "--apitimeout=[seconds] Timeout to use for API calls", "shortdesc" : "Timeout in seconds to use for API calls, default is 60.", "required" : "0", "default" : 60, "order" : 6 } all_opt["retries"] = { "getopt" : ":", "type" : "integer", "longopt" : "retries", "help" : "--retries=[retries] Number of retries on failure for API calls", "shortdesc" : "Number of retries on failure for API calls, default is 3.", "required" : "0", "default" : 3, "order" : 7 } all_opt["retrysleep"] = { "getopt" : ":", "type" : "second", "longopt" : "retrysleep", "help" : "--retrysleep=[seconds] Time to sleep between API retries", "shortdesc" : "Time to sleep in seconds between API retries, default is 5.", "required" : "0", "default" : 5, "order" : 8 } all_opt["serviceaccount"] = { "getopt" : ":", "longopt" : "serviceaccount", "help" : "--serviceaccount=[filename] Service account json file location e.g. serviceaccount=/somedir/service_account.json", "shortdesc" : "Service Account to use for authentication to the google cloud APIs.", "required" : "0", "order" : 9 } all_opt["plugzonemap"] = { "getopt" : ":", "longopt" : "plugzonemap", "help" : "--plugzonemap=[plugzonemap] Comma separated zone map when fencing multiple plugs", "shortdesc" : "Comma separated zone map when fencing multiple plugs.", "required" : "0", "order" : 10 } all_opt["proxyhost"] = { "getopt" : ":", "longopt" : "proxyhost", "help" : "--proxyhost=[proxy_host] The proxy host to use, if one is needed to access the internet (Example: 10.122.0.33)", "shortdesc" : "If a proxy is used for internet access, the proxy host should be specified.", "required" : "0", "order" : 11 } all_opt["proxyport"] = { "getopt" : ":", "type" : "integer", "longopt" : "proxyport", "help" : "--proxyport=[proxy_port] The proxy port to use, if one is needed to access the internet (Example: 3127)", "shortdesc" : "If a proxy is used for internet access, the proxy port should be specified.", "required" : "0", "order" : 12 } all_opt["earlyexit"] = { "getopt" : "", "longopt" : "earlyexit", "help" : "--earlyexit Return early if reset is already in progress", "shortdesc" : "If an existing reset operation is detected, the fence agent will return before the operation completes with a 0 return code.", "required" : "0", "order" : 13 } all_opt["warntimeout"] = { "getopt" : ":", "type" : "second", "longopt" : "warntimeout", "help" : "--warntimeout=[warn_timeout] Timeout seconds before logging a warning and returning a 0 status code", "shortdesc" : "If the operation is not completed within the timeout, the cluster operations are allowed to continue.", "required" : "0", "order" : 14 } all_opt["errortimeout"] = { "getopt" : ":", "type" : "second", "longopt" : "errortimeout", "help" : "--errortimeout=[error_timeout] Timeout seconds before failing and returning a non-zero status code", "shortdesc" : "If the operation is not completed within the timeout, cluster is notified of the operation failure.", "required" : "0", "order" : 15 } all_opt["runonwarn"] = { "getopt" : ":", "longopt" : "runonwarn", "help" : "--runonwarn=[run_on_warn] If a timeout occurs and warning is generated, run the supplied command", "shortdesc" : "If a timeout would occur while running the agent, then the supplied command is run.", "required" : "0", "order" : 16 } all_opt["runonfail"] = { "getopt" : ":", "longopt" : "runonfail", "help" : "--runonfail=[run_on_fail] If a failure occurs, run the supplied command", "shortdesc" : "If a failure would occur while running the agent, then the supplied command is run.", "required" : "0", "order" : 17 } def main(): conn = None device_opt = ["port", "no_password", "zone", "project", "stackdriver-logging", "method", "baremetalsolution", "apitimeout", "retries", "retrysleep", "serviceaccount", "plugzonemap", "proxyhost", "proxyport", "earlyexit", "warntimeout", "errortimeout", "runonwarn", "runonfail"] atexit.register(atexit_handler) define_new_opts() all_opt["power_timeout"]["default"] = "60" all_opt["method"]["default"] = "cycle" all_opt["method"]["help"] = "-m, --method=[method] Method to fence (onoff|cycle) (Default: cycle)" options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for GCE (Google Cloud Engine)" docs["longdesc"] = "fence_gce is a Power Fencing agent for GCE (Google Cloud " \ "Engine). It uses the googleapiclient library to connect to GCE.\n" \ "googleapiclient can be configured with Google SDK CLI or by " \ "executing 'gcloud auth application-default login'.\n" \ "For instructions see: https://cloud.google.com/compute/docs/tutorials/python-guide" docs["vendorurl"] = "http://cloud.google.com" show_docs(options, docs) run_delay(options) # Prepare logging if options.get('--verbose') is None: logging.getLogger('googleapiclient').setLevel(logging.ERROR) logging.getLogger('oauth2client').setLevel(logging.ERROR) if options.get('--stackdriver-logging') is not None and options.get('--plug'): try: import google.cloud.logging.handlers client = google.cloud.logging.Client() handler = google.cloud.logging.handlers.CloudLoggingHandler(client, name=options['--plug']) handler.setLevel(logging.INFO) formatter = logging.Formatter('gcp:stonith "%(message)s"') handler.setFormatter(formatter) root_logger = logging.getLogger() if options.get('--verbose') is None: root_logger.setLevel(logging.INFO) root_logger.addHandler(handler) except ImportError: logging.error('Couldn\'t import google.cloud.logging, ' 'disabling Stackdriver-logging support') # if apitimeout is defined we set the socket timeout, if not we keep the # socket default which is 60s if options.get("--apitimeout"): socket.setdefaulttimeout(options["--apitimeout"]) # Prepare cli try: serviceaccount = options.get("--serviceaccount") if serviceaccount: scope = ['https://www.googleapis.com/auth/cloud-platform'] logging.debug("using credentials from service account") try: from google.oauth2.service_account import Credentials as ServiceAccountCredentials credentials = ServiceAccountCredentials.from_service_account_file(filename=serviceaccount, scopes=scope) except ImportError: from oauth2client.service_account import ServiceAccountCredentials credentials = ServiceAccountCredentials.from_json_keyfile_name(serviceaccount, scope) else: try: from googleapiclient import _auth credentials = _auth.default_credentials(); except: credentials = GoogleCredentials.get_application_default() logging.debug("using application default credentials") if options.get("--proxyhost") and options.get("--proxyport"): proxy_info = httplib2.ProxyInfo( proxy_type=socks.PROXY_TYPE_HTTP, proxy_host=options.get("--proxyhost"), proxy_port=int(options.get("--proxyport"))) http = credentials.authorize(httplib2.Http(proxy_info=proxy_info)) conn = googleapiclient.discovery.build( 'compute', 'v1', http=http, cache_discovery=False) else: conn = googleapiclient.discovery.build( 'compute', 'v1', credentials=credentials, cache_discovery=False) except SSLError as err: fail_fence_agent(options, "Failed: Create GCE compute v1 connection: {}\n\nThis might be caused by old versions of httplib2.".format(str(err))) except Exception as err: fail_fence_agent(options, "Failed: Create GCE compute v1 connection: {}".format(str(err))) # Get project and zone if not options.get("--project"): try: options["--project"] = get_metadata('project/project-id') except Exception as err: fail_fence_agent(options, "Failed retrieving GCE project. Please provide --project option: {}".format(str(err))) try: image = get_metadata('instance/image') options["image"] = image[image.rindex('/')+1:] except Exception as err: options["image"] = "unknown" if "--baremetalsolution" in options: options["--zone"] = "none" # Populates zone automatically if missing from the command zones = [] if not "--zone" in options else options["--zone"].split(",") options["--plugzonemap"] = {} if "--plug" in options: for i, instance in enumerate(options["--plug"].split(",")): if len(zones) == 1: # If only one zone is specified, use it across all plugs options["--plugzonemap"][instance] = zones[0] continue if len(zones) - 1 >= i: # If we have enough zones specified with the --zone flag use the zone at # the same index as the plug options["--plugzonemap"][instance] = zones[i] continue try: # In this case we do not have a zone specified so we attempt to detect it options["--plugzonemap"][instance] = get_zone(conn, options, instance) except Exception as err: fail_fence_agent(options, "Failed retrieving GCE zone. Please provide --zone option: {}".format(str(err))) # Operate the fencing device result = fence_action(conn, options, set_power_status, get_power_status, get_nodes_list, power_cycle) sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/ilo/fence_ilo.py b/agents/ilo/fence_ilo.py index f30a1da2..9b9c0832 100644 --- a/agents/ilo/fence_ilo.py +++ b/agents/ilo/fence_ilo.py @@ -1,143 +1,143 @@ #!@PYTHON@ -tt ##### ## ## The Following Agent Has Been Tested On: ## ## iLO Version ## +---------------------------------------------+ ## iLO / firmware 1.91 / RIBCL 2.22 ## iLO2 / firmware 1.22 / RIBCL 2.22 ## iLO2 / firmware 1.50 / RIBCL 2.22 ##### import sys, os, re, pexpect import atexit from xml.sax.saxutils import quoteattr sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, EC_LOGIN_DENIED def get_power_status(conn, options): conn.send("\r\n") conn.send("\r\n") conn.send("\r\n") conn.log_expect("HOST_POWER=\"(.*?)\"", int(options["--power-timeout"])) status = conn.match.group(1) return status.lower().strip() def set_power_status(conn, options): conn.send("\r\n") conn.send("") if options.get("fw_processor", None) == "iLO2": if options["fw_version"] > 1.29: conn.send("\r\n") else: conn.send("\r\n") elif options["--ribcl-version"] < 2.21: conn.send("\r\n") else: if options["--action"] == "off": conn.send("\r\n") else: conn.send("\r\n") conn.send("\r\n") return def define_new_opts(): all_opt["ribcl"] = { "getopt" : "r:", "longopt" : "ribcl-version", "help" : "-r, --ribcl-version=[version] Force ribcl version to use", "required" : "0", "shortdesc" : "Force ribcl version to use", "order" : 1} def main(): device_opt = ["ipaddr", "login", "passwd", "ssl", "notls", "tls1.0", "ribcl"] atexit.register(atexit_handler) define_new_opts() all_opt["login_timeout"]["default"] = "10" all_opt["retry_on"]["default"] = "3" all_opt["ssl"]["default"] = "1" options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for HP iLO" docs["longdesc"] = "{} is a Power Fencing agent \ used for HP servers with the Integrated Light Out (iLO) PCI card.\ The agent opens an SSL connection to the iLO card. Once the SSL \ connection is established, the agent is able to communicate with \ the iLO card through an XML stream.".format(os.path.basename(__file__)) docs["vendorurl"] = "http://www.hp.com" docs["symlink"] = [("fence_ilo2", "Fence agent for HP iLO2")] show_docs(options, docs) ## ## Login and get version number #### conn = fence_login(options) try: conn.send("\r\n") conn.log_expect(["", ""], int(options["--login-timeout"])) except pexpect.TIMEOUT: fail(EC_LOGIN_DENIED) except pexpect.EOF: if "--tls1.0" in options: fail(EC_LOGIN_DENIED) options["--tls1.0"] = "1" conn.close() conn = fence_login(options) try: conn.send("\r\n") conn.log_expect(["", ""], int(options["--login-timeout"])) except pexpect.TIMEOUT: fail(EC_LOGIN_DENIED) except pexpect.EOF: fail(EC_LOGIN_DENIED) try: - version = re.compile("= 2: conn.send("\r\n") else: conn.send("\r\n") conn.send("\r\n") if options["--ribcl-version"] >= 2: conn.send("\r\n") conn.send("\r\n") conn.log_expect(r"", int(options["--shell-timeout"])) options["fw_version"] = float(re.compile(r"FIRMWARE_VERSION\s*=\s*\"(.*?)\"", re.IGNORECASE).search(conn.before).group(1)) options["fw_processor"] = re.compile(r"MANAGEMENT_PROCESSOR\s*=\s*\"(.*?)\"", re.IGNORECASE).search(conn.before).group(1) conn.send("\r\n") except pexpect.TIMEOUT: fail(EC_LOGIN_DENIED) except pexpect.EOF: fail(EC_LOGIN_DENIED) ## ## Fence operations #### result = fence_action(conn, options, set_power_status, get_power_status, None) sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/ilo_mp/fence_ilo_mp.py b/agents/ilo_mp/fence_ilo_mp.py index cc1c2dec..32530e94 100644 --- a/agents/ilo_mp/fence_ilo_mp.py +++ b/agents/ilo_mp/fence_ilo_mp.py @@ -1,59 +1,59 @@ #!@PYTHON@ -tt import sys, re import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * def get_power_status(conn, options): conn.send_eol("show /system1") - re_state = re.compile('EnabledState=(.*)', re.IGNORECASE) + re_state = re.compile(r'EnabledState=(.*)', re.IGNORECASE) conn.log_expect(re_state, int(options["--shell-timeout"])) status = conn.match.group(1).lower() if status.startswith("enabled"): return "on" else: return "off" def set_power_status(conn, options): if options["--action"] == "on": conn.send_eol("start /system1") else: conn.send_eol("stop -f /system1") conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) return def main(): device_opt = ["ipaddr", "login", "passwd", "secure", "cmd_prompt", "telnet"] atexit.register(atexit_handler) all_opt["cmd_prompt"]["default"] = ["MP>", "hpiLO->"] all_opt["power_wait"]["default"] = 5 options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for HP iLO MP" docs["longdesc"] = "fence_ilo_mp is a Power Fencing agent \ for HP iLO MP." docs["vendorurl"] = "http://www.hp.com" show_docs(options, docs) conn = fence_login(options) conn.send_eol("SMCLP") ## ## Fence operations #### result = fence_action(conn, options, set_power_status, get_power_status) fence_logout(conn, "exit") sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/ilo_ssh/fence_ilo_ssh.py b/agents/ilo_ssh/fence_ilo_ssh.py index 1d5be21e..6f1d3768 100644 --- a/agents/ilo_ssh/fence_ilo_ssh.py +++ b/agents/ilo_ssh/fence_ilo_ssh.py @@ -1,77 +1,77 @@ #!@PYTHON@ -tt import sys, os, re import atexit import logging sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * def get_power_status(conn, options): conn.send_eol("show /system1") - re_state = re.compile('EnabledState=(.*)', re.IGNORECASE) + re_state = re.compile(r'EnabledState=(.*)', re.IGNORECASE) conn.log_expect(re_state, int(options["--shell-timeout"])) status = conn.match.group(1).lower() if status.startswith("enabled"): return "on" else: return "off" def set_power_status(conn, options): if options["--action"] == "on": conn.send_eol("start /system1") else: conn.send_eol("power off hard") conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) return def reboot_cycle(conn, options): conn.send_eol("reset /system1 hard") conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) if get_power_status(conn, options) == "off": logging.error("Timed out waiting to power ON\n") return True def main(): device_opt = ["ipaddr", "login", "passwd", "secure", "cmd_prompt", "method", "telnet"] atexit.register(atexit_handler) all_opt["cmd_prompt"]["default"] = ["MP>", "hpiLO->"] all_opt["power_wait"]["default"] = 5 options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for HP iLO over SSH" docs["longdesc"] = "{} is a Power Fencing agent that connects to iLO device. It logs into \ device via ssh and reboot a specified outlet.\ \n.P\n\ WARNING: The monitor-action is prone to timeouts. Use the fence_ilo-equivalent \ to avoid this issue.".format(os.path.basename(__file__)) docs["vendorurl"] = "http://www.hp.com" docs["symlink"] = [("fence_ilo3_ssh", "Fence agent for HP iLO3 over SSH"), ("fence_ilo4_ssh", "Fence agent for HP iLO4 over SSH"), ("fence_ilo5_ssh", "Fence agent for HP iLO5 over SSH")] show_docs(options, docs) options["eol"] = "\r" conn = fence_login(options) conn.send_eol("SMCLP") ## ## Fence operations #### result = fence_action(conn, options, set_power_status, get_power_status, None, reboot_cycle) fence_logout(conn, "exit") sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/ipmilan/fence_ipmilan.py b/agents/ipmilan/fence_ipmilan.py index a47fbdd8..08318620 100644 --- a/agents/ipmilan/fence_ipmilan.py +++ b/agents/ipmilan/fence_ipmilan.py @@ -1,237 +1,237 @@ #!@PYTHON@ -tt import sys, re, os import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail_usage, is_executable, run_command, run_delay try: from shlex import quote except ImportError: from pipes import quote def get_power_status(_, options): output = _run_command(options, "status") - match = re.search('[Cc]hassis [Pp]ower is [\\s]*([a-zA-Z]{2,3})', str(output)) + match = re.search(r'[Cc]hassis [Pp]ower is [\s]*([a-zA-Z]{2,3})', str(output)) status = match.group(1) if match else None return status def set_power_status(_, options): _run_command(options, options["--action"]) return def reboot_cycle(_, options): output = _run_command(options, "cycle") - return bool(re.search('chassis power control: cycle', str(output).lower())) + return bool(re.search(r'chassis power control: cycle', str(output).lower())) def reboot_diag(_, options): output = _run_command(options, "diag") - return bool(re.search('chassis power control: diag', str(output).lower())) + return bool(re.search(r'chassis power control: diag', str(output).lower())) def _run_command(options, action): cmd, log_cmd = create_command(options, action) return run_command(options, cmd, log_command=log_cmd) def create_command(options, action): class Cmd: cmd = "" log = "" @classmethod def append(cls, cmd, log=None): cls.cmd += cmd cls.log += (cmd if log is None else log) # --use-sudo / -d if "--use-sudo" in options: Cmd.append(options["--sudo-path"] + " ") Cmd.append(options["--ipmitool-path"]) # --lanplus / -L if "--lanplus" in options and options["--lanplus"] in ["", "1"]: Cmd.append(" -I lanplus") else: Cmd.append(" -I lan") # --ip / -a Cmd.append(" -H " + options["--ip"]) # --port / -n if "--ipport" in options: Cmd.append(" -p " + options["--ipport"]) # --target if "--target" in options: Cmd.append(" -t " + options["--target"]) # --username / -l if "--username" in options and len(options["--username"]) != 0: Cmd.append(" -U " + quote(options["--username"])) # --auth / -A if "--auth" in options: Cmd.append(" -A " + options["--auth"]) # --password / -p if "--password" in options: Cmd.append(" -P " + quote(options["--password"]), " -P [set]") else: Cmd.append(" -P ''", " -P [set]") # --cipher / -C if "--cipher" in options: Cmd.append(" -C " + options["--cipher"]) if "--privlvl" in options: Cmd.append(" -L " + options["--privlvl"]) if "--hexadecimal-kg" in options: Cmd.append(" -y " + options["--hexadecimal-kg"]) if "--ipmitool-timeout" in options: Cmd.append(" -N " + options["--ipmitool-timeout"]) # --action / -o Cmd.append(" chassis power " + action) # --verbose-level if options["--verbose-level"] > 1: Cmd.append(" -" + "v" * (options["--verbose-level"] - 1)) return (Cmd.cmd, Cmd.log) def define_new_opts(): all_opt["lanplus"] = { "getopt" : "P", "longopt" : "lanplus", "help" : "-P, --lanplus Use Lanplus to improve security of connection", "required" : "0", "default" : "0", "shortdesc" : "Use Lanplus to improve security of connection", "order": 1 } all_opt["auth"] = { "getopt" : "A:", "longopt" : "auth", "help" : "-A, --auth=[auth] IPMI Lan Auth type (md5|password|none)", "required" : "0", "shortdesc" : "IPMI Lan Auth type.", "choices" : ["md5", "password", "none"], "order": 1 } all_opt["cipher"] = { "getopt" : "C:", "longopt" : "cipher", "help" : "-C, --cipher=[cipher] Ciphersuite to use (same as ipmitool -C parameter)", "required" : "0", "shortdesc" : "Ciphersuite to use (same as ipmitool -C parameter)", "order": 1 } all_opt["privlvl"] = { "getopt" : "L:", "longopt" : "privlvl", "help" : "-L, --privlvl=[level] " "Privilege level on IPMI device (callback|user|operator|administrator)", "required" : "0", "shortdesc" : "Privilege level on IPMI device", "default" : "administrator", "choices" : ["callback", "user", "operator", "administrator"], "order": 1 } all_opt["ipmitool_path"] = { "getopt" : ":", "longopt" : "ipmitool-path", "help" : "--ipmitool-path=[path] Path to ipmitool binary", "required" : "0", "shortdesc" : "Path to ipmitool binary", "default" : "@IPMITOOL_PATH@", "order": 200 } all_opt["ipmitool_timeout"] = { "getopt" : ":", "longopt" : "ipmitool-timeout", "help" : "--ipmitool-timeout=[timeout] Timeout (sec) for IPMI operation", "required" : "0", "shortdesc" : "Timeout (sec) for IPMI operation", "default" : "2", "order": 201 } all_opt["target"] = { "getopt" : ":", "longopt" : "target", "help" : "--target=[targetaddress] Bridge IPMI requests to the remote target address", "required" : "0", "shortdesc" : "Bridge IPMI requests to the remote target address", "order": 1 } all_opt["hexadecimal_kg"] = { "getopt" : ":", "longopt" : "hexadecimal-kg", "help" : "--hexadecimal-kg=[key] Hexadecimal-encoded Kg key for IPMIv2 authentication", "required" : "0", "shortdesc" : "Hexadecimal-encoded Kg key for IPMIv2 authentication", "order": 1 } def main(): atexit.register(atexit_handler) device_opt = ["ipaddr", "login", "no_login", "no_password", "passwd", "diag", "lanplus", "auth", "cipher", "privlvl", "sudo", "ipmitool_path", "ipmitool_timeout", "method", "target", "hexadecimal_kg"] define_new_opts() all_opt["power_wait"]["default"] = 2 if os.path.basename(sys.argv[0]) == "fence_ilo3": all_opt["power_wait"]["default"] = "4" all_opt["lanplus"]["default"] = "1" elif os.path.basename(sys.argv[0]) == "fence_ilo4": all_opt["lanplus"]["default"] = "1" elif os.path.basename(sys.argv[0]) == "fence_ilo5": all_opt["lanplus"]["default"] = "1" elif os.path.basename(sys.argv[0]) == "fence_ipmilanplus": all_opt["lanplus"]["default"] = "1" all_opt["ipport"]["default"] = "623" all_opt["method"]["help"] = "-m, --method=[method] Method to fence (onoff|cycle) (Default: onoff)\n" \ "WARNING! This fence agent might report success before the node is powered off. " \ "You should use -m/method onoff if your fence device works correctly with that option." options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for IPMI" docs["longdesc"] = "{} is a Power Fencing agent \ which can be used with machines controlled by IPMI. \ This agent calls support software ipmitool (http://ipmitool.sf.net/). \ WARNING! This fence agent might report success before the node is powered off. \ You should use -m/method onoff if your fence device works correctly with that option.".format(os.path.basename(__file__)) docs["vendorurl"] = "" docs["symlink"] = [("fence_ilo3", "Fence agent for HP iLO3"), ("fence_ilo4", "Fence agent for HP iLO4"), ("fence_ilo5", "Fence agent for HP iLO5"), ("fence_ipmilanplus", "Fence agent for IPMIv2 lanplus"), ("fence_imm", "Fence agent for IBM Integrated Management Module"), ("fence_idrac", "Fence agent for Dell iDRAC")] show_docs(options, docs) run_delay(options) if not is_executable(options["--ipmitool-path"]): fail_usage("Ipmitool not found or not accessible") reboot_fn = reboot_cycle if options["--action"] == "diag": # Diag is a special action that can't be verified so we will reuse reboot functionality # to minimize impact on generic library options["--action"] = "reboot" options["--method"] = "cycle" reboot_fn = reboot_diag result = fence_action(None, options, set_power_status, get_power_status, None, reboot_fn) sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/ironic/fence_ironic.py b/agents/ironic/fence_ironic.py index 76a9250f..efccebd2 100644 --- a/agents/ironic/fence_ironic.py +++ b/agents/ironic/fence_ironic.py @@ -1,134 +1,134 @@ #!@PYTHON@ -tt import atexit import logging import os import re import sys sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail_usage, is_executable, run_command, run_delay try: from shlex import quote except ImportError: from pipes import quote def get_name_or_uuid(options): return options["--uuid"] if "--uuid" in options else options["--plug"] def get_power_status(_, options): output = ironic_run_command(options, "status") stdout = output[1] - match = re.search('power[\\s]*([a-zA-Z]{2,3})', str(stdout)) + match = re.search(r'power[\s]*([a-zA-Z]{2,3})', str(stdout)) status = match.group(1) if match else None return status def set_power_status(_, options): ironic_run_command(options, options["--action"]) return def get_devices_list(_, options): nodes = {} output = ironic_run_command(options, "list") stdout = output[1] for line in stdout.splitlines(): uuid = "UUID" try: (uuid, name, state) = line.split(',') except ValueError: pass if "UUID" in uuid: continue # skip line header - match = re.search('power[\\s]*([a-zA-Z]{2,3})', state) + match = re.search(r'power[\s]*([a-zA-Z]{2,3})', state) status = match.group(1) if match else None nodes[uuid] = (name, status) return nodes def ironic_run_command(options, action, timeout=None): cmd = options["--openstack-path"] + " baremetal" env = os.environ.copy() # --username / -l if "--username" in options and len(options["--username"]) != 0: env["OS_USERNAME"] = options["--username"] # --password / -p if "--password" in options: env["OS_PASSWORD"] = options["--password"] # --tenant-name -t if "--tenant-name" in options: env["OS_TENANT_NAME"] = options["--tenant-name"] # --auth-url if "--auth-url" in options: env["OS_AUTH_URL"] = options["--auth-url"] # --action / -o if action == "status": cmd += " show %s -c power_state --format value" % (get_name_or_uuid(options)) elif action in ["on", "off"]: cmd += " power %s %s" % (action, get_name_or_uuid(options)) elif action == "list": cmd += " list -c 'Instance UUID' -c Name -c 'Power State' --format csv --quote minimal" logging.debug("cmd -> %s" % cmd) return run_command(options, cmd, timeout, env) def define_new_opts(): all_opt["auth-url"] = { "getopt" : ":", "longopt" : "auth-url", "help" : "--auth-url=[authurl] Auth URL", "required" : "1", "shortdesc" : "Keystone Admin Auth URL", "order": 1 } all_opt["tenant-name"] = { "getopt" : "t:", "longopt" : "tenant-name", "help" : "-t, --tenant-name=[tenant] Tenantname", "required" : "0", "shortdesc" : "Keystone Admin Tenant", "default": "admin", "order": 1 } all_opt["openstack-path"] = { "getopt" : ":", "longopt" : "openstack-path", "help" : "--openstack-path=[path] Path to openstack binary", "required" : "0", "shortdesc" : "Path to the OpenStack binary", "default" : "@OPENSTACK_PATH@", "order": 200 } def main(): atexit.register(atexit_handler) device_opt = ["login", "passwd", "port", "auth-url", "tenant-name", "openstack-path"] define_new_opts() options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for OpenStack's Ironic (Bare Metal as a service) service" docs["longdesc"] = "fence_ironic is a Power Fencing agent \ which can be used with machines controlled by the Ironic service. \ This agent calls the openstack CLI. \ WARNING! This fence agent is not intended for production use. Relying on a functional ironic service for fencing is not a good design choice." docs["vendorurl"] = "https://wiki.openstack.org/wiki/Ironic" show_docs(options, docs) run_delay(options) if not is_executable(options["--openstack-path"]): fail_usage("openstack tool not found or not accessible") result = fence_action(None, options, set_power_status, get_power_status, get_devices_list) sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/lpar/fence_lpar.py b/agents/lpar/fence_lpar.py index a18e1c9a..1703eb3e 100644 --- a/agents/lpar/fence_lpar.py +++ b/agents/lpar/fence_lpar.py @@ -1,197 +1,197 @@ #!@PYTHON@ -tt ##### ## ## The Following Agent Has Been Tested On: ## ## Version ## +---------------------------------------------+ ## Tested on HMC ## ##### import sys, re import atexit import logging sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, fail_usage, EC_STATUS_HMC ## ## Transformation to standard ON/OFF status if possible def _normalize_status(status): if status in ["Running", "Open Firmware", "Shutting Down", "Starting"]: status = "on" else: status = "off" return status def get_power_status(conn, options): if options["--hmc-version"] == "3": command = "lssyscfg -r lpar -m " + options["--managed"] + " -n " + options["--plug"] + " -F name,state\n" elif options["--hmc-version"] in ["4", "IVM"]: command = "lssyscfg -r lpar -m "+ options["--managed"] + \ " --filter 'lpar_names=" + options["--plug"] + "'\n" else: # Bad HMC Version cannot be reached fail(EC_STATUS_HMC) conn.send(command) # First line (command) may cause parsing issues if long conn.readline() conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) try: if options["--hmc-version"] == "3": - status = re.compile("^" + options["--plug"] + ",(.*?),.*$", + status = re.compile(r"^" + options["--plug"] + r",(.*?),.*$", re.IGNORECASE | re.MULTILINE).search(conn.before).group(1) elif options["--hmc-version"] in ["4", "IVM"]: - status = re.compile(",state=(.*?),", re.IGNORECASE).search(conn.before).group(1) + status = re.compile(r",state=(.*?),", re.IGNORECASE).search(conn.before).group(1) except AttributeError as e: logging.debug("Command on HMC failed: {}\n{}".format(command, str(e))) fail(EC_STATUS_HMC) return _normalize_status(status) def is_comanaged(conn, options): conn.send("lscomgmt -m " + options["--managed"] + "\n" ) conn.readline() conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) try: - cm = re.compile(",curr_master_mtms=(.*?),", re.IGNORECASE).search(conn.before).group(1) + cm = re.compile(r",curr_master_mtms=(.*?),", re.IGNORECASE).search(conn.before).group(1) except AttributeError as e: cm = False return cm 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") # First line (command) may cause parsing issues if long conn.readline() conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) elif options["--hmc-version"] in ["4", "IVM"]: if options["--action"] == "on": if is_comanaged(conn, options): profile = "" else: profile = " -f `lssyscfg -r lpar -F curr_profile " + \ " -m " + options["--managed"] + \ " --filter \"lpar_names=" + options["--plug"] + "\"`" conn.send("chsysstate -o on -r lpar" + " -m " + options["--managed"] + " -n " + options["--plug"] + profile + "\n") else: conn.send("chsysstate -o shutdown -r lpar --immed" + " -m " + options["--managed"] + " -n " + options["--plug"] + "\n") # First line (command) may cause parsing issues if long conn.readline() 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") ## We have to remove first line (command) conn.readline() conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) ## We have to remove next 2 lines (header) and last line (part of new prompt) #### - res = re.search("^(.+?\n){2}(.*)\n.*$", conn.before, re.S) + res = re.search(r"^(.+?\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"] in ["4", "IVM"]: sep = ":" if options["--hmc-version"] == "4" else "," conn.send("lssyscfg -r lpar -m " + options["--managed"] + " -F name" + sep + "state\n") ## We have to remove first line (command) conn.readline() conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) ## We have to remove last line (part of new prompt) #### - res = re.search("^(.*)\n.*$", conn.before, re.S) + res = re.search(r"^(.*)\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: try: (port, status) = outlet_line.rstrip().split(sep) except ValueError: fail_usage('Output does not match expected HMC version, try different one'); outlets[port] = ("", _normalize_status(status)) return outlets def define_new_opts(): all_opt["managed"] = { "getopt" : "s:", "longopt" : "managed", "help" : "-s, --managed=[id] Name of the managed system", "required" : "1", "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|ivm) (default: 4)", "required" : "0", "shortdesc" : "Force HMC version to use", "default" : "4", "choices" : ["3", "4", "ivm"], "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"] = "fence_lpar is a Power Fencing agent for IBM LPAR." docs["vendorurl"] = "http://www.ibm.com" show_docs(options, docs) if "--managed" not in options: 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/netio/fence_netio.py b/agents/netio/fence_netio.py index fc3bf9d8..948fc4ce 100755 --- a/agents/netio/fence_netio.py +++ b/agents/netio/fence_netio.py @@ -1,94 +1,94 @@ #!@PYTHON@ -tt import sys, re, pexpect import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fspawn, fail, EC_LOGIN_DENIED, run_delay def get_power_status(conn, options): conn.send_eol("port %s" % options["--plug"]) - re_status = re.compile("250 [01imt]") + re_status = re.compile(r"250 [01imt]") conn.log_expect(re_status, int(options["--shell-timeout"])) status = { "0" : "off", "1" : "on", "i" : "reboot", "m" : "manual", "t" : "timer" }[conn.after.split()[1]] return status def set_power_status(conn, options): action = { "on" : "1", "off" : "0", "reboot" : "i" }[options["--action"]] conn.send_eol("port %s %s" % (options["--plug"], action)) conn.log_expect("250 OK", int(options["--shell-timeout"])) def get_outlet_list(conn, options): result = {} try: # the NETIO-230B has 4 ports, counting start at 1 for plug in ["1", "2", "3", "4"]: conn.send_eol("port setup %s" % plug) conn.log_expect("250 .+", int(options["--shell-timeout"])) # the name is enclosed in "", drop those with [1:-1] name = conn.after.split()[1][1:-1] result[plug] = (name, "unknown") except Exception as exn: print(str(exn)) return result def main(): device_opt = ["ipaddr", "login", "passwd", "port", "telnet"] atexit.register(atexit_handler) all_opt["ipport"]["default"] = "1234" opt = process_input(device_opt) opt["eol"] = "\r\n" options = check_input(device_opt, opt) docs = {} docs["shortdesc"] = "Power Fencing agent for Koukaam NETIO-230B" docs["longdesc"] = "fence_netio is a Power Fencing agent which can be \ used with the Koukaam NETIO-230B Power Distribution Unit. It logs into \ device via telnet and reboots a specified outlet. Lengthy telnet connections \ should be avoided while a GFS cluster is running because the connection will \ block any necessary fencing actions." docs["vendorurl"] = "http://www.koukaam.se/" show_docs(options, docs) ## ## Operate the fencing device ## We can not use fence_login(), username and passwd are sent on one line #### run_delay(options) try: conn = fspawn(options, options["--telnet-path"]) conn.send("set binary\n") conn.send("open %s -%s\n"%(options["--ip"], options["--ipport"])) conn.read_nonblocking(size=100, timeout=int(options["--shell-timeout"])) conn.log_expect("100 HELLO .*", int(options["--shell-timeout"])) conn.send_eol("login %s %s" % (options["--username"], options["--password"])) conn.log_expect("250 OK", int(options["--shell-timeout"])) except pexpect.EOF: fail(EC_LOGIN_DENIED) except pexpect.TIMEOUT: fail(EC_LOGIN_DENIED) result = fence_action(conn, options, set_power_status, get_power_status, get_outlet_list) fence_logout(conn, "quit\n") sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/raritan/fence_raritan.py b/agents/raritan/fence_raritan.py index d3510e4a..c78a7b30 100644 --- a/agents/raritan/fence_raritan.py +++ b/agents/raritan/fence_raritan.py @@ -1,87 +1,87 @@ #!@PYTHON@ -tt import sys, re, pexpect import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fspawn, fail, EC_LOGIN_DENIED, run_delay def get_power_status(conn, options): conn.send_eol("show -d properties=powerState %s" % options["--plug"]) - re_status = re.compile(".*powerState is [12].*") + re_status = re.compile(r".*powerState is [12].*") conn.log_expect(re_status, int(options["--shell-timeout"])) status = { #"0" : "off", "1" : "on", "2" : "off", }[conn.after.split()[2]] return status def set_power_status(conn, options): action = { "on" : "on", "off" : "off", }[options["--action"]] conn.send_eol("set %s powerState=%s" % (options["--plug"], action)) def main(): device_opt = ["ipaddr", "login", "passwd", "port", "telnet"] atexit.register(atexit_handler) opt = process_input(device_opt) all_opt["ipport"]["default"] = "23" opt["eol"] = "\r\n" options = check_input(device_opt, opt) docs = {} docs["shortdesc"] = "Power Fencing agent for Raritan Dominion PX" docs["longdesc"] = "fence_raritan is a Power Fencing agent which can be \ used with the Raritan DPXS12-20 Power Distribution Unit. It logs into \ device via telnet and reboots a specified outlet. Lengthy telnet connections \ should be avoided while a GFS cluster is running because the connection will \ block any necessary fencing actions." docs["vendorurl"] = "http://www.raritan.com/" show_docs(options, docs) # add support also for delay before login which is very useful for 2-node clusters run_delay(options) # Convert pure port/plug number to /system1/outlet${plug} try: plug_int = int(options["--plug"]) options["--plug"] = "/system1/outlet" + str(plug_int) except ValueError: pass ## ## Operate the fencing device ## We can not use fence_login(), username and passwd are sent on one line #### try: conn = fspawn(options, options["--telnet-path"], encoding="latin1") conn.send("set binary\n") conn.send("open %s -%s\n"%(options["--ip"], options["--ipport"])) conn.read_nonblocking(size=100, timeout=int(options["--shell-timeout"])) conn.log_expect("Login.*", int(options["--shell-timeout"])) conn.send_eol("%s" % (options["--username"])) conn.log_expect("Password.*", int(options["--shell-timeout"])) conn.send_eol("%s" % (options["--password"])) conn.log_expect("clp.*", int(options["--shell-timeout"])) except pexpect.EOF: fail(EC_LOGIN_DENIED) except pexpect.TIMEOUT: fail(EC_LOGIN_DENIED) result = 0 if options["--action"] != "monitor": result = fence_action(conn, options, set_power_status, get_power_status) fence_logout(conn, "exit\n") sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/rhevm/fence_rhevm.py b/agents/rhevm/fence_rhevm.py index 1eb53bed..87cd4cf4 100644 --- a/agents/rhevm/fence_rhevm.py +++ b/agents/rhevm/fence_rhevm.py @@ -1,249 +1,249 @@ #!@PYTHON@ -tt import sys, re import pycurl, io import logging import atexit import tempfile sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, EC_FETCH_VM_UUID, run_delay -RE_GET_ID = re.compile("(.*?)", re.IGNORECASE) -RE_STATE = re.compile("(.*?)", re.IGNORECASE) -RE_GET_NAME = re.compile("(.*?)", re.IGNORECASE) +RE_GET_ID = re.compile(r"(.*?)", re.IGNORECASE) +RE_STATE = re.compile(r"(.*?)", re.IGNORECASE) +RE_GET_NAME = re.compile(r"(.*?)", re.IGNORECASE) def get_power_status(conn, options): del conn ### Obtain real ID from name res = send_command(options, "vms/?search=name%3D" + options["--plug"]) result = RE_GET_ID.search(res) if result == None: # Unable to obtain ID needed to access virtual machine fail(EC_FETCH_VM_UUID) options["id"] = result.group(2) if tuple(map(int, options["--api-version"].split(".")))[0] > 3: result = RE_STATUS.search(res) else: result = RE_STATE.search(res) if result == None: # We were able to parse ID so output is correct # in some cases it is possible that RHEV-M output does not # contain line. We can assume machine is OFF then return "off" else: status = result.group(1) if status.lower() == "down": return "off" else: return "on" def set_power_status(conn, options): del conn action = { 'on' : "start", 'off' : "stop" }[options["--action"]] url = "vms/" + options["id"] + "/" + action send_command(options, url, "POST") def get_list(conn, options): del conn outlets = {} try: res = send_command(options, "vms") lines = res.split(" 3: status = RE_STATUS.search(lines[i]).group(1) else: status = RE_STATE.search(lines[i]).group(1) outlets[name] = ("", status) except AttributeError: return {} except IndexError: return {} return outlets def send_command(opt, command, method="GET"): if opt["--api-version"] == "auto": opt["--api-version"] = "4" res = send_command(opt, "") - if re.search("Error", res): + if re.search(r"Error", res): opt["--api-version"] = "3" logging.debug("auto-detected API version: " + opt["--api-version"]) ## setup correct URL if "--ssl-secure" in opt or "--ssl-insecure" in opt: url = "https:" else: url = "http:" if "--api-path" in opt: api_path = opt["--api-path"] else: api_path = "/ovirt-engine/api" if "--disable-http-filter" in opt: http_filter = 'false' else: http_filter = 'true' url += "//" + opt["--ip"] + ":" + str(opt["--ipport"]) + api_path + "/" + command ## send command through pycurl conn = pycurl.Curl() web_buffer = io.BytesIO() conn.setopt(pycurl.URL, url.encode("UTF-8")) conn.setopt(pycurl.HTTPHEADER, [ "Version: {}".format(opt["--api-version"]), "Content-type: application/xml", "Accept: application/xml", "Prefer: persistent-auth", "Filter: {}".format(http_filter), ]) if "cookie" in opt: conn.setopt(pycurl.COOKIE, opt["cookie"]) else: conn.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC) conn.setopt(pycurl.USERPWD, opt["--username"] + ":" + opt["--password"]) if "--use-cookies" in opt: if "--cookie-file" in opt: cookie_file = opt["--cookie-file"] else: cookie_file = tempfile.gettempdir() + "/fence_rhevm_" + opt["--ip"] + "_" + opt["--username"] + "_cookie.dat" conn.setopt(pycurl.COOKIEFILE, cookie_file) conn.setopt(pycurl.COOKIEJAR, cookie_file) conn.setopt(pycurl.TIMEOUT, int(opt["--shell-timeout"])) if "--ssl-secure" in opt: conn.setopt(pycurl.SSL_VERIFYPEER, 1) conn.setopt(pycurl.SSL_VERIFYHOST, 2) elif "--ssl-insecure" in opt: conn.setopt(pycurl.SSL_VERIFYPEER, 0) conn.setopt(pycurl.SSL_VERIFYHOST, 0) if method == "POST": conn.setopt(pycurl.POSTFIELDS, "") conn.setopt(pycurl.WRITEFUNCTION, web_buffer.write) conn.perform() if "cookie" not in opt and "--use-cookies" in opt: cookie = "" for c in conn.getinfo(pycurl.INFO_COOKIELIST): tokens = c.split("\t",7) cookie = cookie + tokens[5] + "=" + tokens[6] + ";" opt["cookie"] = cookie result = web_buffer.getvalue().decode("UTF-8") logging.debug("url: %s\n", url.encode("UTF-8")) logging.debug("command: %s\n", command.encode("UTF-8")) logging.debug("result: %s\n", result.encode("UTF-8")) return result def define_new_opts(): all_opt["port"] = { "getopt" : "n:", "longopt" : "plug", "help" : "-n, --plug=[name] " "VM name in RHV", "required" : "1", "order" : 1} all_opt["use_cookies"] = { "getopt" : "", "longopt" : "use-cookies", "help" : "--use-cookies Reuse cookies for authentication", "required" : "0", "shortdesc" : "Reuse cookies for authentication", "order" : 1} all_opt["cookie_file"] = { "getopt" : ":", "longopt" : "cookie-file", "help" : "--cookie-file Path to cookie file for authentication\n" "\t\t\t\t (Default: /tmp/fence_rhevm_ip_username_cookie.dat)", "required" : "0", "shortdesc" : "Path to cookie file for authentication", "order" : 2} all_opt["api_version"] = { "getopt" : ":", "longopt" : "api-version", "help" : "--api-version " "Version of RHEV API (default: auto)", "required" : "0", "order" : 2, "default" : "auto", } all_opt["api_path"] = { "getopt" : ":", "longopt" : "api-path", "help" : "--api-path=[path] The path part of the API URL", "default" : "/ovirt-engine/api", "required" : "0", "shortdesc" : "The path part of the API URL", "order" : 3} all_opt["disable_http_filter"] = { "getopt" : "", "longopt" : "disable-http-filter", "help" : "--disable-http-filter Set HTTP Filter header to false", "required" : "0", "shortdesc" : "Set HTTP Filter header to false", "order" : 4} def main(): device_opt = [ "ipaddr", "login", "passwd", "ssl", "notls", "web", "port", "use_cookies", "cookie_file", "api_version", "api_path", "disable_http_filter", ] atexit.register(atexit_handler) define_new_opts() all_opt["power_wait"]["default"] = "1" all_opt["shell_timeout"]["default"] = "5" options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for RHEV-M REST API" docs["longdesc"] = "fence_rhevm is a Power Fencing agent which can be \ used with RHEV-M REST API to fence virtual machines." docs["vendorurl"] = "http://www.redhat.com" show_docs(options, docs) ## ## Fence operations #### run_delay(options) result = fence_action(None, options, set_power_status, get_power_status, get_list) sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/rsa/fence_rsa.py b/agents/rsa/fence_rsa.py index 79ed109e..6efde753 100644 --- a/agents/rsa/fence_rsa.py +++ b/agents/rsa/fence_rsa.py @@ -1,63 +1,63 @@ #!@PYTHON@ -tt ##### ## ## The Following Agent Has Been Tested On: ## Main GFEP25A & Boot GFBP25A ## ##### import sys, re import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * def get_power_status(conn, options): conn.send_eol("power state") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) - match = re.compile("Power: (.*)", re.IGNORECASE).search(conn.before) + match = re.compile(r"Power: (.*)", re.IGNORECASE).search(conn.before) if match != None: status = match.group(1) else: status = "undefined" return status.lower().strip() def set_power_status(conn, options): conn.send_eol("power " + options["--action"]) conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) def main(): device_opt = ["ipaddr", "login", "passwd", "cmd_prompt", "secure", "telnet"] atexit.register(atexit_handler) all_opt["login_timeout"]["default"] = 10 all_opt["cmd_prompt"]["default"] = [">"] # This device will not allow us to login even with LANG=C all_opt["ssh_options"]["default"] = "-F /dev/null" options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for IBM RSA" docs["longdesc"] = "fence_rsa is a Power Fencing agent \ which can be used with the IBM RSA II management interface. It \ logs into an RSA II device via telnet and reboots the associated \ machine. Lengthy telnet connections to the RSA II device should \ be avoided while a GFS cluster is running because the connection \ will block any necessary fencing actions." docs["vendorurl"] = "http://www.ibm.com" show_docs(options, docs) ## ## Operate the fencing device ###### conn = fence_login(options) result = fence_action(conn, options, set_power_status, get_power_status, None) fence_logout(conn, "exit") sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/wti/fence_wti.py b/agents/wti/fence_wti.py index ffa3d019..9c16f364 100644 --- a/agents/wti/fence_wti.py +++ b/agents/wti/fence_wti.py @@ -1,240 +1,240 @@ #!@PYTHON@ -tt ##### ## ## The Following Agent Has Been Tested On: ## ## Version Firmware ## +-----------------+---------------------------+ ## WTI RSM-8R4 ?? unable to find out ?? ## WTI MPC-??? ?? unable to find out ?? ## WTI IPS-800-CE v1.40h (no username) ('list' tested) ##### import sys, re, pexpect import atexit import time sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fspawn, fail, fail_usage, EC_LOGIN_DENIED def get_listing(conn, options, listing_command): listing = "" conn.send_eol(listing_command) if isinstance(options["--command-prompt"], list): re_all = list(options["--command-prompt"]) else: re_all = [options["--command-prompt"]] - re_next = re.compile("Enter: ", re.IGNORECASE) + re_next = re.compile(r"Enter: ", re.IGNORECASE) re_all.append(re_next) result = conn.log_expect(re_all, int(options["--shell-timeout"])) listing = conn.before if result == (len(re_all) - 1): conn.send_eol("") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) listing += conn.before return listing def get_plug_status(conn, options): listing = get_listing(conn, options, "/S") plug_section = 0 plug_index = -1 name_index = -1 status_index = -1 plug_header = list() outlets = {} for line in listing.splitlines(): if (plug_section == 2) and line.find("|") >= 0 and line.startswith("PLUG") == False: plug_line = [x.strip().lower() for x in line.split("|")] if len(plug_line) < len(plug_header): plug_section = -1 if ["list", "monitor"].count(options["--action"]) == 0 and \ options["--plug"].lower() == plug_line[plug_index]: return plug_line[status_index] else: ## We already believe that first column contains plug number if len(plug_line[0]) != 0: outlets[plug_line[0]] = (plug_line[name_index], plug_line[status_index]) elif plug_section == 1: plug_section = 2 elif line.upper().startswith("PLUG"): plug_section = 1 plug_header = [x.strip().lower() for x in line.split("|")] plug_index = plug_header.index("plug") name_index = plug_header.index("name") status_index = plug_header.index("status") if ["list", "monitor"].count(options["--action"]) == 1: return outlets else: return "PROBLEM" def get_plug_group_status_from_list(status_list): for status in status_list: if status == "on": return status return "off" def get_plug_group_status(conn, options): listing = get_listing(conn, options, "/SG") outlets = {} line_index = 0 status_index = -1 plug_index = -1 name_index = -1 lines = listing.splitlines() while line_index < len(lines) and line_index >= 0: line = lines[line_index] if line.find("|") >= 0 and line.lstrip().startswith("GROUP NAME") == False: plug_line = [x.strip().lower() for x in line.split("|")] if ["list", "monitor"].count(options["--action"]) == 0 and \ options["--plug"].lower() == plug_line[name_index]: plug_status = [] while line_index < len(lines) and line_index >= 0: plug_line = [x.strip().lower() for x in lines[line_index].split("|")] if len(plug_line) >= max(name_index, status_index) and \ len(plug_line[plug_index]) > 0 and \ (len(plug_line[name_index]) == 0 or options["--plug"].lower() == plug_line[name_index]): ## Firmware 1.43 does not have a valid value of plug on first line as only name is defined on that line if not "---" in plug_line[status_index]: plug_status.append(plug_line[status_index]) line_index += 1 else: line_index = -1 return get_plug_group_status_from_list(plug_status) else: ## We already believe that first column contains plug number if len(plug_line[0]) != 0: group_name = plug_line[0] plug_line_index = line_index + 1 plug_status = [] while plug_line_index < len(lines) and plug_line_index >= 0: plug_line = [x.strip().lower() for x in lines[plug_line_index].split("|")] if len(plug_line[name_index]) > 0: plug_line_index = -1 break if len(plug_line[plug_index]) > 0: plug_status.append(plug_line[status_index]) plug_line_index += 1 else: plug_line_index = -1 outlets[group_name] = (group_name, get_plug_group_status_from_list(plug_status)) line_index += 1 elif line.upper().lstrip().startswith("GROUP NAME"): plug_header = [x.strip().lower() for x in line.split("|")] name_index = plug_header.index("group name") plug_index = plug_header.index("plug") status_index = plug_header.index("status") line_index += 2 else: line_index += 1 if ["list", "monitor"].count(options["--action"]) == 1: results = {} for group, status in list(outlets.items()): results[group] = (group, status[0]) return results else: return "PROBLEM" def get_power_status(conn, options): if ["list"].count(options["--action"]) == 0: ret = get_plug_status(conn, options) if ret == "PROBLEM": ret = get_plug_group_status(conn, options) else: ret = dict(list(get_plug_status(conn, options).items()) + \ list(get_plug_group_status(conn, options).items())) return ret def set_power_status(conn, options): action = { 'on' : "/on", 'off': "/off" }[options["--action"]] conn.send_eol(action + " " + options["--plug"] + ",y") conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) def main(): device_opt = ["ipaddr", "login", "passwd", "no_login", "no_password", \ "cmd_prompt", "secure", "port", "telnet"] atexit.register(atexit_handler) all_opt["cmd_prompt"]["default"] = ["RSM>", "MPC>", "IPS>", "TPS>", "NBB>", "NPS>", "VMR>"] all_opt["login_timeout"]["default"] = "10" options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for WTI" docs["longdesc"] = "fence_wti is a Power Fencing agent \ which can be used with the WTI Network Power Switch (NPS). It logs \ into an NPS via telnet or ssh and boots a specified plug. \ Lengthy telnet connections to the NPS should be avoided while a GFS cluster \ is running because the connection will block any necessary fencing actions." docs["vendorurl"] = "http://www.wti.com" show_docs(options, docs) ## ## Operate the fencing device ## ## @note: if it possible that this device does not need either login, password or both of them ##### if "--ssh" not in options: try: if options["--action"] in ["off", "reboot"]: time.sleep(int(options["--delay"])) options["eol"] = "\r\n" conn = fspawn(options, options["--telnet-path"]) conn.send("set binary\n") conn.send("open %s -%s\n"%(options["--ip"], options["--ipport"])) - re_login = re.compile("(login: )|(Login Name: )|(username: )|(User Name :)", re.IGNORECASE) - re_prompt = re.compile("|".join(["(" + x + ")" for x in options["--command-prompt"]]), re.IGNORECASE) + re_login = re.compile(r"(login: )|(Login Name: )|(username: )|(User Name :)", re.IGNORECASE) + re_prompt = re.compile(r"|".join(["(" + x + ")" for x in options["--command-prompt"]]), re.IGNORECASE) result = conn.log_expect([re_login, "Password: ", re_prompt], int(options["--shell-timeout"])) if result == 0: if "--username" in options: conn.send_eol(options["--username"]) result = conn.log_expect([re_login, "Password: ", re_prompt], int(options["--shell-timeout"])) else: fail_usage("Failed: You have to set login name") if result == 1: if "--password" in options: conn.send_eol(options["--password"]) conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) else: fail_usage("Failed: You have to enter password or password script") except pexpect.EOF: fail(EC_LOGIN_DENIED) except pexpect.TIMEOUT: fail(EC_LOGIN_DENIED) else: conn = fence_login(options) result = fence_action(conn, options, set_power_status, get_power_status, get_power_status) fence_logout(conn, "/X") sys.exit(result) if __name__ == "__main__": main() diff --git a/lib/azure_fence.py.py b/lib/azure_fence.py.py index ab40b483..fcbc40ab 100644 --- a/lib/azure_fence.py.py +++ b/lib/azure_fence.py.py @@ -1,419 +1,419 @@ import logging, re, time from fencing import fail_usage FENCE_SUBNET_NAME = "fence-subnet" FENCE_INBOUND_RULE_NAME = "FENCE_DENY_ALL_INBOUND" FENCE_INBOUND_RULE_DIRECTION = "Inbound" FENCE_OUTBOUND_RULE_NAME = "FENCE_DENY_ALL_OUTBOUND" FENCE_OUTBOUND_RULE_DIRECTION = "Outbound" FENCE_STATE_OFF = "off" FENCE_STATE_ON = "on" FENCE_TAG_SUBNET_ID = "FENCE_TAG_SUBNET_ID" FENCE_TAG_IP_TYPE = "FENCE_TAG_IP_TYPE" FENCE_TAG_IP = "FENCE_TAG_IP" IP_TYPE_DYNAMIC = "Dynamic" MAX_RETRY = 10 RETRY_WAIT = 5 class AzureSubResource: Type = None Name = None class AzureResource: Id = None SubscriptionId = None ResourceGroupName = None ResourceName = None SubResources = [] class AzureConfiguration: RGName = None VMName = None SubscriptionId = None Cloud = None UseMSI = None Tenantid = None ApplicationId = None ApplicationKey = None Verbose = None def get_from_metadata(parameter): import requests try: r = requests.get('http://169.254.169.254/metadata/instance?api-version=2017-08-01', headers = {"Metadata":"true"}) logging.debug("metadata: " + str(r.json())) return str(r.json()["compute"][parameter]) except: logging.warning("Not able to use metadata service. Am I running in Azure?") return None def get_azure_resource(id): - match = re.match('(/subscriptions/([^/]*)/resourceGroups/([^/]*))(/providers/([^/]*/[^/]*)/([^/]*))?((/([^/]*)/([^/]*))*)', id) + match = re.match(r'(/subscriptions/([^/]*)/resourceGroups/([^/]*))(/providers/([^/]*/[^/]*)/([^/]*))?((/([^/]*)/([^/]*))*)', id) if not match: fail_usage("{get_azure_resource} cannot parse resource id %s" % id) logging.debug("{get_azure_resource} found %s matches for %s" % (len(match.groups()), id)) iGroup = 0 while iGroup < len(match.groups()): logging.debug("{get_azure_resource} group %s: %s" %(iGroup, match.group(iGroup))) iGroup += 1 resource = AzureResource() resource.Id = id resource.SubscriptionId = match.group(2) resource.SubResources = [] if len(match.groups()) > 3: resource.ResourceGroupName = match.group(3) logging.debug("{get_azure_resource} resource group %s" % resource.ResourceGroupName) if len(match.groups()) > 6: resource.ResourceName = match.group(6) logging.debug("{get_azure_resource} resource name %s" % resource.ResourceName) if len(match.groups()) > 7 and match.group(7): splits = match.group(7).split("/") logging.debug("{get_azure_resource} splitting subtypes '%s' (%s)" % (match.group(7), len(splits))) i = 1 # the string starts with / so the first split is empty while i < len(splits) - 1: logging.debug("{get_azure_resource} creating subresource with type %s and name %s" % (splits[i], splits[i+1])) subRes = AzureSubResource() subRes.Type = splits[i] subRes.Name = splits[i+1] resource.SubResources.append(subRes) i += 2 return resource def get_fence_subnet_for_config(ipConfig, network_client): subnetResource = get_azure_resource(ipConfig.subnet.id) logging.debug("{get_fence_subnet_for_config} testing virtual network %s in resource group %s for a fence subnet" %(subnetResource.ResourceName, subnetResource.ResourceGroupName)) vnet = network_client.virtual_networks.get(subnetResource.ResourceGroupName, subnetResource.ResourceName) return get_subnet(vnet, FENCE_SUBNET_NAME) def get_subnet(vnet, subnetName): for avSubnet in vnet.subnets: logging.debug("{get_subnet} searching subnet %s testing subnet %s" % (subnetName, avSubnet.name)) if (avSubnet.name.lower() == subnetName.lower()): logging.debug("{get_subnet} subnet found %s" % avSubnet) return avSubnet def test_fence_subnet(fenceSubnet, nic, network_client): logging.info("{test_fence_subnet}") testOk = True if not fenceSubnet: testOk = False logging.info("{test_fence_subnet} No fence subnet found for virtual network of network interface %s" % nic.id) else: if not fenceSubnet.network_security_group: testOk = False logging.info("{test_fence_subnet} Fence subnet %s has not network security group" % fenceSubnet.id) else: nsgResource = get_azure_resource(fenceSubnet.network_security_group.id) logging.info("{test_fence_subnet} Getting network security group %s in resource group %s" % (nsgResource.ResourceName, nsgResource.ResourceGroupName)) nsg = network_client.network_security_groups.get(nsgResource.ResourceGroupName, nsgResource.ResourceName) inboundRule = get_inbound_rule_for_nsg(nsg) outboundRule = get_outbound_rule_for_nsg(nsg) if not outboundRule: testOk = False logging.info("{test_fence_subnet} Network Securiy Group %s of fence subnet %s has no outbound security rule that blocks all traffic" % (nsgResource.ResourceName, fenceSubnet.id)) elif not inboundRule: testOk = False logging.info("{test_fence_subnet} Network Securiy Group %s of fence subnet %s has no inbound security rule that blocks all traffic" % (nsgResource.ResourceName, fenceSubnet.id)) return testOk def get_inbound_rule_for_nsg(nsg): return get_rule_for_nsg(nsg, FENCE_INBOUND_RULE_NAME, FENCE_INBOUND_RULE_DIRECTION) def get_outbound_rule_for_nsg(nsg): return get_rule_for_nsg(nsg, FENCE_OUTBOUND_RULE_NAME, FENCE_OUTBOUND_RULE_DIRECTION) def get_rule_for_nsg(nsg, ruleName, direction): logging.info("{get_rule_for_nsg} Looking for security rule %s with direction %s" % (ruleName, direction)) if not nsg: logging.info("{get_rule_for_nsg} Network security group not set") return None for rule in nsg.security_rules: logging.info("{get_rule_for_nsg} Testing a %s securiy rule %s" % (rule.direction, rule.name)) if (rule.access == "Deny") and (rule.direction == direction) \ and (rule.source_port_range == "*") and (rule.destination_port_range == "*") \ and (rule.protocol == "*") and (rule.destination_address_prefix == "*") \ and (rule.source_address_prefix == "*") and (rule.provisioning_state == "Succeeded") \ and (rule.priority == 100) and (rule.name == ruleName): logging.info("{get_rule_for_nsg} %s rule found" % direction) return rule return None def get_network_state(compute_client, network_client, rgName, vmName): result = FENCE_STATE_ON try: vm = compute_client.virtual_machines.get(rgName, vmName, "instanceView") allNICOK = True for nicRef in vm.network_profile.network_interfaces: nicresource = get_azure_resource(nicRef.id) nic = network_client.network_interfaces.get(nicresource.ResourceGroupName, nicresource.ResourceName) for ipConfig in nic.ip_configurations: logging.info("{get_network_state} Testing ip configuration %s" % ipConfig.name) fenceSubnet = get_fence_subnet_for_config(ipConfig, network_client) testOk = test_fence_subnet(fenceSubnet, nic, network_client) if not testOk: allNICOK = False elif fenceSubnet.id.lower() != ipConfig.subnet.id.lower(): logging.info("{get_network_state} IP configuration %s is not in fence subnet (ip subnet: %s, fence subnet: %s)" % (ipConfig.name, ipConfig.subnet.id.lower(), fenceSubnet.id.lower())) allNICOK = False if allNICOK: logging.info("{get_network_state} All IP configurations of all network interfaces are in the fence subnet. Declaring VM as off") result = FENCE_STATE_OFF except Exception as e: fail_usage("{get_network_state} Failed: %s" % e) return result def set_network_state(compute_client, network_client, rgName, vmName, operation): import msrestazure.azure_exceptions logging.info("{set_network_state} Setting state %s for %s in resource group %s" % (operation, vmName, rgName)) vm = compute_client.virtual_machines.get(rgName, vmName, "instanceView") operations = [] for nicRef in vm.network_profile.network_interfaces: for attempt in range(0, MAX_RETRY): try: nicresource = get_azure_resource(nicRef.id) nic = network_client.network_interfaces.get(nicresource.ResourceGroupName, nicresource.ResourceName) if not nic.tags and operation == "block": nic.tags = {} logging.info("{set_network_state} Searching for tags required to unfence this virtual machine") for ipConfig in nic.ip_configurations: if operation == "block": fenceSubnet = get_fence_subnet_for_config(ipConfig, network_client) testOk = test_fence_subnet(fenceSubnet, nic, network_client) if testOk: logging.info("{set_network_state} Changing subnet of ip config of nic %s" % nic.id) nic.tags[("%s_%s" % (FENCE_TAG_SUBNET_ID, ipConfig.name))] = ipConfig.subnet.id nic.tags[("%s_%s" % (FENCE_TAG_IP_TYPE, ipConfig.name))] = ipConfig.private_ip_allocation_method nic.tags[("%s_%s" % (FENCE_TAG_IP, ipConfig.name))] = ipConfig.private_ip_address ipConfig.subnet = fenceSubnet ipConfig.private_ip_allocation_method = IP_TYPE_DYNAMIC else: fail_usage("{set_network_state} Network interface id %s does not have a network security group." % nic.id) elif operation == "unblock": if not nic.tags: fail_usage("{set_network_state} IP configuration %s is missing the required resource tags (empty)" % ipConfig.name) subnetId = nic.tags.pop("%s_%s" % (FENCE_TAG_SUBNET_ID, ipConfig.name)) ipType = nic.tags.pop("%s_%s" % (FENCE_TAG_IP_TYPE, ipConfig.name)) ipAddress = nic.tags.pop("%s_%s" % (FENCE_TAG_IP, ipConfig.name)) if (subnetId and ipType and (ipAddress or (ipType.lower() == IP_TYPE_DYNAMIC.lower()))): logging.info("{set_network_state} tags found (subnetId: %s, ipType: %s, ipAddress: %s)" % (subnetId, ipType, ipAddress)) subnetResource = get_azure_resource(subnetId) vnet = network_client.virtual_networks.get(subnetResource.ResourceGroupName, subnetResource.ResourceName) logging.info("{set_network_state} looking for subnet %s" % len(subnetResource.SubResources)) oldSubnet = get_subnet(vnet, subnetResource.SubResources[0].Name) if not oldSubnet: fail_usage("{set_network_state} subnet %s not found" % subnetId) ipConfig.subnet = oldSubnet ipConfig.private_ip_allocation_method = ipType if ipAddress: ipConfig.private_ip_address = ipAddress else: fail_usage("{set_network_state} IP configuration %s is missing the required resource tags(subnetId: %s, ipType: %s, ipAddress: %s)" % (ipConfig.name, subnetId, ipType, ipAddress)) logging.info("{set_network_state} updating nic %s" % (nic.id)) op = network_client.network_interfaces.create_or_update(nicresource.ResourceGroupName, nicresource.ResourceName, nic) operations.append(op) break except msrestazure.azure_exceptions.CloudError as cex: logging.error("{set_network_state} CloudError in attempt %s '%s'" % (attempt, cex)) if cex.error and cex.error.error and cex.error.error.lower() == "PrivateIPAddressIsBeingCleanedUp": logging.error("{set_network_state} PrivateIPAddressIsBeingCleanedUp") time.sleep(RETRY_WAIT) except Exception as ex: logging.error("{set_network_state} Exception of type %s: %s" % (type(ex).__name__, ex)) break def get_azure_config(options): config = AzureConfiguration() config.RGName = options.get("--resourceGroup") config.VMName = options.get("--plug") config.SubscriptionId = options.get("--subscriptionId") config.Cloud = options.get("--cloud") config.MetadataEndpoint = options.get("--metadata-endpoint") config.UseMSI = "--msi" in options config.Tenantid = options.get("--tenantId") config.ApplicationId = options.get("--username") config.ApplicationKey = options.get("--password") config.Verbose = options.get("--verbose") if not config.RGName: logging.info("resourceGroup not provided. Using metadata service") config.RGName = get_from_metadata("resourceGroupName") if not config.SubscriptionId: logging.info("subscriptionId not provided. Using metadata service") config.SubscriptionId = get_from_metadata("subscriptionId") return config def get_azure_cloud_environment(config): cloud_environment = None if config.Cloud: if (config.Cloud.lower() == "china"): from msrestazure.azure_cloud import AZURE_CHINA_CLOUD cloud_environment = AZURE_CHINA_CLOUD elif (config.Cloud.lower() == "germany"): from msrestazure.azure_cloud import AZURE_GERMAN_CLOUD cloud_environment = AZURE_GERMAN_CLOUD elif (config.Cloud.lower() == "usgov"): from msrestazure.azure_cloud import AZURE_US_GOV_CLOUD cloud_environment = AZURE_US_GOV_CLOUD elif (config.Cloud.lower() == "stack"): from msrestazure.azure_cloud import get_cloud_from_metadata_endpoint cloud_environment = get_cloud_from_metadata_endpoint(config.MetadataEndpoint) return cloud_environment def get_azure_credentials(config): credentials = None cloud_environment = get_azure_cloud_environment(config) if config.UseMSI and cloud_environment: try: from azure.identity import ManagedIdentityCredential credentials = ManagedIdentityCredential(cloud_environment=cloud_environment) except ImportError: from msrestazure.azure_active_directory import MSIAuthentication credentials = MSIAuthentication(cloud_environment=cloud_environment) elif config.UseMSI: try: from azure.identity import ManagedIdentityCredential credentials = ManagedIdentityCredential() except ImportError: from msrestazure.azure_active_directory import MSIAuthentication credentials = MSIAuthentication() elif cloud_environment: try: # try to use new libraries ClientSecretCredential (azure.identity, based on azure.core) from azure.identity import ClientSecretCredential credentials = ClientSecretCredential( client_id = config.ApplicationId, client_secret = config.ApplicationKey, tenant_id = config.Tenantid, cloud_environment=cloud_environment ) except ImportError: # use old libraries ServicePrincipalCredentials (azure.common) if new one is not available from azure.common.credentials import ServicePrincipalCredentials credentials = ServicePrincipalCredentials( client_id = config.ApplicationId, secret = config.ApplicationKey, tenant = config.Tenantid, cloud_environment=cloud_environment ) else: try: # try to use new libraries ClientSecretCredential (azure.identity, based on azure.core) from azure.identity import ClientSecretCredential credentials = ClientSecretCredential( client_id = config.ApplicationId, client_secret = config.ApplicationKey, tenant_id = config.Tenantid ) except ImportError: # use old libraries ServicePrincipalCredentials (azure.common) if new one is not available from azure.common.credentials import ServicePrincipalCredentials credentials = ServicePrincipalCredentials( client_id = config.ApplicationId, secret = config.ApplicationKey, tenant = config.Tenantid ) return credentials def get_azure_compute_client(config): from azure.mgmt.compute import ComputeManagementClient cloud_environment = get_azure_cloud_environment(config) credentials = get_azure_credentials(config) if cloud_environment: if (config.Cloud.lower() == "stack") and not config.MetadataEndpoint: fail_usage("metadata-endpoint not specified") try: from azure.profiles import KnownProfiles if (config.Cloud.lower() == "stack"): client_profile = KnownProfiles.v2020_09_01_hybrid credential_scope = cloud_environment.endpoints.active_directory_resource_id + "/.default" else: client_profile = KnownProfiles.default credential_scope = cloud_environment.endpoints.resource_manager + "/.default" compute_client = ComputeManagementClient( credentials, config.SubscriptionId, base_url=cloud_environment.endpoints.resource_manager, profile=client_profile, credential_scopes=[credential_scope], ) except TypeError: compute_client = ComputeManagementClient( credentials, config.SubscriptionId, base_url=cloud_environment.endpoints.resource_manager ) else: compute_client = ComputeManagementClient( credentials, config.SubscriptionId ) return compute_client def get_azure_network_client(config): from azure.mgmt.network import NetworkManagementClient cloud_environment = get_azure_cloud_environment(config) credentials = get_azure_credentials(config) if cloud_environment: if (config.Cloud.lower() == "stack") and not config.MetadataEndpoint: fail_usage("metadata-endpoint not specified") try: from azure.profiles import KnownProfiles if (config.Cloud.lower() == "stack"): client_profile = KnownProfiles.v2020_09_01_hybrid credential_scope = cloud_environment.endpoints.active_directory_resource_id + "/.default" else: client_profile = KnownProfiles.default credential_scope = cloud_environment.endpoints.resource_manager + "/.default" network_client = NetworkManagementClient( credentials, config.SubscriptionId, base_url=cloud_environment.endpoints.resource_manager, profile=client_profile, credential_scopes=[credential_scope], ) except TypeError: network_client = NetworkManagementClient( credentials, config.SubscriptionId, base_url=cloud_environment.endpoints.resource_manager ) else: network_client = NetworkManagementClient( credentials, config.SubscriptionId ) return network_client diff --git a/lib/fencing.py.py b/lib/fencing.py.py index 3a60f53e..511eb268 100644 --- a/lib/fencing.py.py +++ b/lib/fencing.py.py @@ -1,1749 +1,1749 @@ #!@PYTHON@ -tt import sys, getopt, time, os, uuid, pycurl, stat import pexpect, re, syslog import logging import subprocess import threading import shlex import socket import textwrap import __main__ import itertools RELEASE_VERSION = "@RELEASE_VERSION@" __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 EC_FETCH_VM_UUID = 12 LOG_FORMAT = "%(asctime)-15s %(levelname)s: %(message)s" all_opt = { "help" : { "getopt" : "h", "longopt" : "help", "help" : "-h, --help Display this help and exit", "required" : "0", "shortdesc" : "Display help and exit", "order" : 55}, "version" : { "getopt" : "V", "longopt" : "version", "help" : "-V, --version Display version information and exit", "required" : "0", "shortdesc" : "Display version information and exit", "order" : 54}, "verbose" : { "getopt" : "v", "longopt" : "verbose", "help" : "-v, --verbose Verbose mode. " "Multiple -v flags can be stacked on the command line " "(e.g., -vvv) to increase verbosity.", "required" : "0", "order" : 51}, "verbose_level" : { "getopt" : ":", "longopt" : "verbose-level", "type" : "integer", "help" : "--verbose-level " "Level of debugging detail in output. Defaults to the " "number of --verbose flags specified on the command " "line, or to 1 if verbose=1 in a stonith device " "configuration (i.e., on stdin).", "required" : "0", "order" : 52}, "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" : 53}, "delay" : { "getopt" : ":", "longopt" : "delay", "type" : "second", "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", "type" : "integer", "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" : 1}, "diag" : { "getopt" : "", "help" : "", "order" : 1}, "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}, "plug_separator" : { "getopt" : ":", "longopt" : "plug-separator", "help" : "--plug-separator=[char] Separator for plug parameter when specifying more than 1 plug", "default" : ",", "required" : "0", "order" : 100}, "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", "type" : "second", "help" : "--login-timeout=[seconds] Wait X seconds for cmd prompt after login", "default" : "5", "required" : "0", "order" : 200}, "shell_timeout" : { "getopt" : ":", "longopt" : "shell-timeout", "type" : "second", "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", "type" : "second", "help" : "--power-timeout=[seconds] Test X seconds for status change after ON/OFF", "default" : "20", "required" : "0", "order" : 200}, "disable_timeout" : { "getopt" : ":", "longopt" : "disable-timeout", "help" : "--disable-timeout=[true/false] Disable timeout (true/false) (default: true when run from Pacemaker 2.0+)", "required" : "0", "order" : 200}, "power_wait" : { "getopt" : ":", "longopt" : "power-wait", "type" : "second", "help" : "--power-wait=[seconds] Wait X seconds after issuing ON/OFF", "default" : "0", "required" : "0", "order" : 200}, "stonith_status_sleep" : { "getopt" : ":", "longopt" : "stonith-status-sleep", "type" : "second", "help" : "--stonith-status-sleep=[seconds] Sleep X seconds between status calls during a STONITH action", "default" : "1", "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", "type" : "integer", "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" : "@TELNET_PATH@", "order": 300}, "ssh_path" : { "getopt" : ":", "longopt" : "ssh-path", "help" : "--ssh-path=[path] Path to ssh binary", "required" : "0", "default" : "@SSH_PATH@", "order": 300}, "gnutlscli_path" : { "getopt" : ":", "longopt" : "gnutlscli-path", "help" : "--gnutlscli-path=[path] Path to gnutls-cli binary", "required" : "0", "default" : "@GNUTLSCLI_PATH@", "order": 300}, "sudo_path" : { "getopt" : ":", "longopt" : "sudo-path", "help" : "--sudo-path=[path] Path to sudo binary", "required" : "0", "default" : "@SUDO_PATH@", "order": 300}, "snmpwalk_path" : { "getopt" : ":", "longopt" : "snmpwalk-path", "help" : "--snmpwalk-path=[path] Path to snmpwalk binary", "required" : "0", "default" : "@SNMPWALK_PATH@", "order" : 300}, "snmpset_path" : { "getopt" : ":", "longopt" : "snmpset-path", "help" : "--snmpset-path=[path] Path to snmpset binary", "required" : "0", "default" : "@SNMPSET_PATH@", "order" : 300}, "snmpget_path" : { "getopt" : ":", "longopt" : "snmpget-path", "help" : "--snmpget-path=[path] Path to snmpget binary", "required" : "0", "default" : "@SNMPGET_PATH@", "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", "verbose_level", "version", "action", "agent", "power_timeout", "shell_timeout", "login_timeout", "disable_timeout", "power_wait", "stonith_status_sleep", "retry_on", "delay", "plug_separator", "quiet"], "passwd" : ["passwd_script"], "sudo" : ["sudo_path"], "secure" : ["identity_file", "ssh_options", "ssh_path", "inet4_only", "inet6_only"], "telnet" : ["telnet_path"], "ipaddr" : ["ipport"], "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, **kwargs): if sys.version_info[0] > 2: kwargs.setdefault('encoding', 'utf-8') logging.info("Running command: %s", command) pexpect.spawn.__init__(self, command, **kwargs) self.opt = options def log_expect(self, pattern, timeout): result = self.expect(pattern, timeout if timeout != 0 else None) logging.debug("Received: %s", self.before + self.after) return result def read_nonblocking(self, size, timeout): return pexpect.spawn.read_nonblocking(self, size=100, timeout=timeout if timeout != 0 else None) 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 frun(command, timeout=30, withexitstatus=False, events=None, extra_args=None, logfile=None, cwd=None, env=None, **kwargs): if sys.version_info[0] > 2: kwargs.setdefault('encoding', 'utf-8') return pexpect.run(command, timeout=timeout if timeout != 0 else None, withexitstatus=withexitstatus, events=events, extra_args=extra_args, logfile=logfile, cwd=cwd, env=env, **kwargs) 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 opt in DEPENDENCY_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, stop=True): message = { EC_GENERIC_ERROR : "Failed: Generic error", 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.", EC_FETCH_VM_UUID : "Failed: Can not find VM UUID by its VM name given in the parameter." }[error_code] + "\n" logging.error("%s\n", message) if stop: 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(key=lambda x: x[1]["order"]) for key, value in sorted_list: if len(value["help"]) != 0: print(" " + _join_wrap([value["help"]], first_indent=3)) def metadata(options, 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)) if "longopt" in all_opt[key]] # Find keys that are going to replace inconsistent names mapping = dict([(opt["longopt"].replace("-", "_"), key) for (key, opt) in sorted_list if (key != opt["longopt"].replace("-", "_"))]) new_options = [(key, all_opt[mapping[key]]) for key in mapping] sorted_list.extend(new_options) sorted_list.sort(key=lambda x: (x[1]["order"], x[0])) if options["--action"] == "metadata": - docs["longdesc"] = re.sub("\\\\f[BPIR]|\.P|\.TP|\.br\n", "", docs["longdesc"]) + docs["longdesc"] = re.sub(r"\\f[BPIR]|\.P|\.TP|\.br\n", r"", docs["longdesc"]) print("") print("") for (symlink, desc) in docs.get("symlink", []): print("") print("" + docs["longdesc"] + "") print("" + docs["vendorurl"] + "") print("") for (key, opt) in sorted_list: info = "" if key in all_opt: if key != all_opt[key].get('longopt', key).replace("-", "_"): info = "deprecated=\"1\"" else: info = "obsoletes=\"%s\"" % (mapping.get(key)) if "help" in opt and len(opt["help"]) > 0: if info != "": info = " " + info print("\t") default = "" if "default" in opt: default = "default=\"" + _encode_html_entities(str(opt["default"])) + "\" " mixed = opt["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 opt: - shortdesc = re.sub(".*\s\s+", "", opt["help"][31:]) + shortdesc = re.sub(r".*\s\s+", r"", opt["help"][31:]) else: shortdesc = opt["shortdesc"] print("\t\t") if "choices" in opt: print("\t\t") for choice in opt["choices"]: print("\t\t\t") elif opt["getopt"].count(":") > 0: t = opt.get("type", "string") print("\t\t") else: print("\t\t") print("\t\t" + shortdesc + "") print("\t") print("") print("") (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" % (on_target, avail_opt.count("fabric_fencing"))) for action in available_actions: print("\t" % (action)) print("") print("") 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"] in ["metadata", "manpage"] or any(k in options for k in ("--help", "--version")): return options try: options["--verbose-level"] = int(options["--verbose-level"]) except ValueError: options["--verbose-level"] = -1 if options["--verbose-level"] < 0: logging.warning("Parse error: Option 'verbose_level' must " "be an integer greater than or equal to 0. " "Setting verbose_level to 0.") options["--verbose-level"] = 0 if options["--verbose-level"] == 0 and "--verbose" in options: logging.warning("Parse error: Ignoring option 'verbose' " "because it conflicts with verbose_level=0") del options["--verbose"] if options["--verbose-level"] > 0: # Ensure verbose key exists options["--verbose"] = 1 if "--verbose" in options: logging.getLogger().setLevel(logging.DEBUG) formatter = logging.Formatter(LOG_FORMAT) ## add logging to syslog logging.getLogger().addHandler(SyslogLibHandler()) if "--quiet" not in options: ## add logging to stderr stderrHandler = logging.StreamHandler(sys.stderr) stderrHandler.setFormatter(formatter) logging.getLogger().addHandler(stderrHandler) (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: if not _validate_input(options, False): fail_usage("validate-all failed") sys.exit(EC_OK) else: _validate_input(options, True) if "--debug-file" in options: try: debug_file = logging.FileHandler(options["--debug-file"]) debug_file.setLevel(logging.DEBUG) debug_file.setFormatter(formatter) 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 "--snmp-priv-passwd-script" in options: options["--snmp-priv-passwd"] = os.popen(options["--snmp-priv-passwd-script"]).read().rstrip() if "--password-script" in options: options["--password"] = os.popen(options["--password-script"]).read().rstrip() if "--ssl-secure" in options or "--ssl-insecure" in options: options["--ssl"] = "" if "--ssl" in options and "--ssl-insecure" not in options: options["--ssl-secure"] = "" if os.environ.get("PCMK_service") == "pacemaker-fenced" and "--disable-timeout" not in options: options["--disable-timeout"] = "1" if options.get("--disable-timeout", "").lower() in ["1", "yes", "on", "true"]: options["--power-timeout"] = options["--shell-timeout"] = options["--login-timeout"] = 0 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 "--plugs" in options 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 async_set_multi_power_fn(connection, options, set_power_fn, get_power_fn, retry_attempts): plugs = options["--plugs"] if "--plugs" in options 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 itertools.count(1): if get_multi_power_fn(connection, options, get_power_fn) != options["--action"]: time.sleep(int(options["--stonith-status-sleep"])) else: return True if int(options["--power-timeout"]) > 0 and _ >= int(options["--power-timeout"]): break return False def sync_set_multi_power_fn(connection, options, sync_set_power_fn, retry_attempts): success = True plugs = options["--plugs"] if "--plugs" in options else [""] for plug in plugs: try: options["--uuid"] = str(uuid.UUID(plug)) except ValueError: pass except KeyError: pass options["--plug"] = plug for retry in range(retry_attempts): if sync_set_power_fn(connection, options): break if retry == retry_attempts-1: success = False time.sleep(int(options["--power-wait"])) return success def set_multi_power_fn(connection, options, set_power_fn, get_power_fn, sync_set_power_fn, retry_attempts=1): if set_power_fn != None: if get_power_fn != None: return async_set_multi_power_fn(connection, options, set_power_fn, get_power_fn, retry_attempts) elif sync_set_power_fn != None: return sync_set_multi_power_fn(connection, options, sync_set_power_fn, retry_attempts) return False def multi_reboot_cycle_fn(connection, options, reboot_cycle_fn, retry_attempts=1): success = True plugs = options["--plugs"] if "--plugs" in options else [""] for plug in plugs: try: options["--uuid"] = str(uuid.UUID(plug)) except ValueError: pass except KeyError: pass options["--plug"] = plug for retry in range(retry_attempts): if reboot_cycle_fn(connection, options): break if retry == retry_attempts-1: success = False time.sleep(int(options["--power-wait"])) return success def show_docs(options, docs=None): device_opt = options["device_opt"] if docs == None: docs = {} docs["shortdesc"] = "Fence agent" docs["longdesc"] = "" if "--help" in options: usage(device_opt) sys.exit(0) if options.get("--action", "") in ["metadata", "manpage"]: if "port_as_ip" in device_opt: device_opt.remove("separator") metadata(options, device_opt, docs) sys.exit(0) if "--version" in options: print(RELEASE_VERSION) sys.exit(0) def fence_action(connection, options, set_power_fn, get_power_fn, get_outlet_list=None, reboot_cycle_fn=None, sync_set_power_fn=None): result = EC_OK try: if "--plug" in options: options["--plugs"] = options["--plug"].split(options["--plug-separator"]) ## 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 list(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": try: print(outlet_id + options["--separator"] + alias) except UnicodeEncodeError as e: print((outlet_id + options["--separator"] + alias).encode("utf-8")) elif options["--action"] == "list-status": try: print(outlet_id + options["--separator"] + alias + options["--separator"] + status) except UnicodeEncodeError as e: print((outlet_id + options["--separator"] + alias).encode("utf-8") + options["--separator"] + status) return result 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 result 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 result if options["--action"] == "on": if set_multi_power_fn(connection, options, set_power_fn, get_power_fn, sync_set_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, sync_set_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: try: power_on = multi_reboot_cycle_fn(connection, options, reboot_cycle_fn, 1 + int(options["--retry-on"])) except Exception as ex: # an error occured during reboot action logging.warning("%s", str(ex)) 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, sync_set_power_fn): fail(EC_WAITING_OFF) options["--action"] = "on" try: power_on = set_multi_power_fn(connection, options, set_power_fn, get_power_fn, sync_set_power_fn, int(options["--retry-on"])) except Exception as ex: # an error occured during power ON phase in reboot # fence action was completed succesfully even in that case logging.warning("%s", str(ex)) # switch back to original action for the case it is used lateron options["--action"] = "reboot" 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 as ex: logging.error("%s\n", str(ex)) fail(EC_TIMED_OUT) except socket.timeout as 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 "eol" not in options: options["eol"] = "\r\n" if "--command-prompt" in options and type(options["--command-prompt"]) is not list: options["--command-prompt"] = [options["--command-prompt"]] try: if "--ssl" in options: conn = _open_ssl_connection(options) elif "--ssh" in options and "--identity-file" not in options: conn = _login_ssh_with_password(options, re_login_string) elif "--ssh" in options and "--identity-file" in options: conn = _login_ssh_with_identity_file(options) else: conn = _login_telnet(options, re_login_string) except pexpect.EOF as exception: logging.debug("%s", str(exception)) fail(EC_LOGIN_DENIED) except pexpect.TIMEOUT as 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_commands(options, commands, timeout=None, env=None, log_command=None): # inspired by psutils.wait_procs (BSD License) def check_gone(proc, timeout): try: returncode = proc.wait(timeout=timeout) except subprocess.TimeoutExpired: pass else: if returncode is not None or not proc.is_running(): proc.returncode = returncode gone.add(proc) if timeout is None and "--power-timeout" in options: timeout = options["--power-timeout"] if timeout == 0: timeout = None if timeout is not None: timeout = float(timeout) time_start = time.time() procs = [] status = None pipe_stdout = "" pipe_stderr = "" for command in commands: logging.info("Executing: %s\n", log_command or command) try: process = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, # decodes newlines and in python3 also converts bytes to str universal_newlines=(sys.version_info[0] > 2)) except OSError: fail_usage("Unable to run %s\n" % command) procs.append(process) gone = set() alive = set(procs) while True: if alive: max_timeout = 2.0 / len(alive) for proc in alive: if timeout is not None: if time.time()-time_start >= timeout: # quickly go over the rest max_timeout = 0 check_gone(proc, max_timeout) alive = alive - gone if not alive: break if time.time()-time_start < 5.0: # give it at least 5s to get a complete answer # afterwards we're OK with a quorate answer continue if len(gone) > len(alive): good_cnt = 0 for proc in gone: if proc.returncode == 0: good_cnt += 1 # a positive result from more than half is fine if good_cnt > len(procs)/2: break if timeout is not None: if time.time() - time_start >= timeout: logging.debug("Stop waiting after %s\n", str(timeout)) break logging.debug("Done: %d gone, %d alive\n", len(gone), len(alive)) for proc in gone: if (status != 0): status = proc.returncode # hand over the best status we have # but still collect as much stdout/stderr feedback # avoid communicate as we know already process # is gone and it seems to block when there # are D state children we don't get rid off os.set_blocking(proc.stdout.fileno(), False) os.set_blocking(proc.stderr.fileno(), False) try: pipe_stdout += proc.stdout.read() except: pass try: pipe_stderr += proc.stderr.read() except: pass proc.stdout.close() proc.stderr.close() for proc in alive: proc.kill() if status is None: fail(EC_TIMED_OUT, stop=(int(options.get("retry", 0)) < 1)) status = EC_TIMED_OUT pipe_stdout = "" pipe_stderr = "timed out" logging.debug("%s %s %s\n", str(status), str(pipe_stdout), str(pipe_stderr)) return (status, pipe_stdout, pipe_stderr) 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, # decodes newlines and in python3 also converts bytes to str universal_newlines=(sys.version_info[0] > 2)) except OSError: fail_usage("Unable to run %s\n" % command) thread = threading.Thread(target=process.wait) thread.start() thread.join(timeout if timeout else None) if thread.is_alive(): process.kill() fail(EC_TIMED_OUT, stop=(int(options.get("retry", 0)) < 1)) 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, reserve=0, result=0): ## Delay is important for two-node clusters fencing ## but we do not need to delay 'status' operations ## and get us out quickly if we already know that we are gonna fail ## still wanna do something right before fencing? - reserve some time if options["--action"] in ["off", "reboot"] \ and options["--delay"] != "0" \ and result == 0 \ and reserve >= 0: time_left = 1 + int(options["--delay"]) - (time.time() - run_delay.time_start) - reserve if time_left > 0: logging.info("Delay %d second(s) before logging in to the fence device", time_left) time.sleep(time_left) # mark time when fence-agent is started run_delay.time_start = time.time() 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 OSError: pass except pexpect.ExceptionPexpect: pass def source_env(env_file): # POSIX: name shall not contain '=', value doesn't contain '\0' output = subprocess.check_output("source {} && env -0".format(env_file), shell=True, executable="/bin/sh") # replace env os.environ.clear() - os.environ.update(line.partition('=')[::2] for line in output.decode("utf-8").split('\0') if not re.match("^\s*$", line)) + os.environ.update(line.partition('=')[::2] for line in output.decode("utf-8").split('\0') if not re.match(r"^\s*$", line)) # 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 "--notls" in options: gnutls_opts = "--priority \"NORMAL:-VERS-TLS1.2:-VERS-TLS1.1:-VERS-TLS1.0:+VERS-SSL3.0\"" elif "--tls1.0" in options: 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 "--ssl-insecure" in options: 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 as ex: logging.error("%s\n", str(ex)) sys.exit(EC_GENERIC_ERROR) return conn def _login_ssh_with_identity_file(options): if "--inet6-only" in options: force_ipvx = "-6 " elif "--inet4-only" in options: 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 "--ssh-options" in 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 "--password" in options: 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) + re_pass = re.compile(r"(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) + re_pass = re.compile(r"(password)|(pass phrase)", re.IGNORECASE) if "--inet6-only" in options: force_ipvx = "-6 " elif "--inet4-only" in options: 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 "--ssh-options" in options: command += ' ' + options["--ssh-options"] conn = fspawn(options, command) if "telnet_over_ssh" in options: # 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 "default" in all_opt["ipport"]: 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 "default" in all_opt[opt] and not opt == "ipport": getopt_long = "--" + all_opt[opt]["longopt"] if getopt_long not in options: 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 "--username" not in options 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 "--ip" not in options and "--managed" not in options and "--target" not in options: 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 ("--password" in options or "--password-script" in options): valid_input = False fail_usage("Failed: You have to enter password or password script", stop) else: if not ("--password" in options or \ "--password-script" in options or "--identity-file" in options): valid_input = False fail_usage("Failed: You have to enter password, password script or identity file", stop) if "--ssh" not in options and "--identity-file" in options: valid_input = False fail_usage("Failed: You have to use identity file together with ssh connection (-x)", stop) if "--identity-file" in options 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 \ "--plug" not in options 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) 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) for failed_opt in _get_opts_with_invalid_types(options): valid_input = False if all_opt[failed_opt]["type"] == "second": fail_usage("Failed: The value you have entered for %s is not a valid time in seconds" % \ ("--" + all_opt[failed_opt]["longopt"]), stop) else: fail_usage("Failed: The value you have entered for %s is not a valid %s" % \ ("--" + all_opt[failed_opt]["longopt"], all_opt[failed_opt]["type"]), 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 k in all_opt and all_opt[k]["getopt"] != ":": # getopt == ":" means that opt is without short getopt, but has value getopt_string += all_opt[k]["getopt"] elif k not in all_opt: 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"]) return (getopt_string, longopt_list) def _parse_input_stdin(avail_opt): opt = {} name = "" mapping_longopt_names = dict([(all_opt[o].get("longopt"), o) for o in avail_opt]) 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] - value = re.sub("^\"(.*)\"$", "\\1", value) + value = re.sub(r"^\"(.*)\"$", r"\1", value) if name.replace("-", "_") in mapping_longopt_names: name = mapping_longopt_names[name.replace("-", "_")] elif name.replace("_", "-") in mapping_longopt_names: name = mapping_longopt_names[name.replace("_", "-")] 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" elif value.lower() in ["0", "no", "off", "false"]: opt["--"+all_opt[name]["longopt"]] = "0" else: logging.warning("Parse error: Ignoring option '%s' because it does not have value\n", name) opt.setdefault("--verbose-level", opt.get("--verbose", 0)) 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 as 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 = {} verbose_count = 0 for arg_name in [k for (k, v) in entered_opt]: all_key = [key for (key, value) in list(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] if all_key == "verbose": verbose_count += 1 long_opts.setdefault("--verbose-level", verbose_count) # 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 "choices" in all_opt[opt]: longopt = "--" + all_opt[opt]["longopt"] possible_values_upper = [y.upper() for y in all_opt[opt]["choices"]] if longopt in options: options[longopt] = options[longopt].upper() if not options["--" + all_opt[opt]["longopt"]] in possible_values_upper: options_failed.append(opt) return options_failed def _get_opts_with_invalid_types(options): options_failed = [] device_opt = options["device_opt"] for opt in device_opt: if "type" in all_opt[opt]: longopt = "--" + all_opt[opt]["longopt"] if longopt in options: if all_opt[opt]["type"] in ["integer", "second"]: try: number = int(options["--" + all_opt[opt]["longopt"]]) except ValueError: 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", "manpage", "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") if device_opt.count("diag"): available_actions.append("diag") return (available_actions, default_value) diff --git a/lib/fencing_snmp.py.py b/lib/fencing_snmp.py.py index f9e57689..55f5db2a 100644 --- a/lib/fencing_snmp.py.py +++ b/lib/fencing_snmp.py.py @@ -1,128 +1,128 @@ #!@PYTHON@ -tt # For example of use please see fence_cisco_mds import re, pexpect import logging from fencing import * from fencing import fail, fail_usage, EC_TIMED_OUT, run_delay, frun __all__ = ['FencingSnmp'] ## do not add code here. class FencingSnmp: def __init__(self, options): self.options = options run_delay(options) def quote_for_run(self, string): return string.replace(r"'", "'\\''") def complete_missed_params(self): mapping = [[ ['snmp-priv-passwd', 'password', '!snmp-sec-level'], 'self.options["--snmp-sec-level"]="authPriv"' ], [ ['!snmp-version', 'community', '!username', '!snmp-priv-passwd', '!password'], 'self.options["--snmp-version"]="2c"' ]] for val in mapping: e = val[0] res = True for item in e: if item[0] == '!' and "--" + item[1:] in self.options: res = False break if item[0] != '!' and "--" + item[0:] not in self.options: res = False break if res: exec(val[1]) def prepare_cmd(self, command): cmd = "%s -m '' -Oeqn "% (command) self.complete_missed_params() #mapping from our option to snmpcmd option mapping = (('snmp-version', 'v'), ('community', 'c')) for item in mapping: if "--" + item[0] in self.options: cmd += " -%s '%s'"% (item[1], self.quote_for_run(self.options["--" + item[0]])) # Some options make sense only for v3 (and for v1/2c can cause "problems") if ("--snmp-version" in self.options) and (self.options["--snmp-version"] == "3"): # Mapping from our options to snmpcmd options for v3 mapping_v3 = (('snmp-auth-prot', 'a'), ('snmp-sec-level', 'l'), ('snmp-priv-prot', 'x'), \ ('snmp-priv-passwd', 'X'), ('password', 'A'), ('username', 'u')) for item in mapping_v3: if "--"+item[0] in self.options: cmd += " -%s '%s'"% (item[1], self.quote_for_run(self.options["--" + item[0]])) force_ipvx = "" if "--inet6-only" in self.options: force_ipvx = "udp6:" if "--inet4-only" in self.options: force_ipvx = "udp:" cmd += " '%s%s%s'"% (force_ipvx, self.quote_for_run(self.options["--ip"]), "--ipport" in self.options and self.quote_for_run(":" + str(self.options["--ipport"])) or "") return cmd def run_command(self, command, additional_timeout=0): try: logging.debug("%s\n", command) (res_output, res_code) = frun(command, int(self.options["--shell-timeout"]) + int(self.options["--login-timeout"]) + additional_timeout, True) if res_code == None: fail(EC_TIMED_OUT) logging.debug("%s\n", res_output) - if (res_code != 0) or (re.search("^Error ", res_output, re.MULTILINE) != None): + if (res_code != 0) or (re.search(r"^Error ", res_output, re.MULTILINE) != None): fail_usage("Returned %d: %s"% (res_code, res_output)) except pexpect.ExceptionPexpect: fail_usage("Cannot run command %s"%(command)) return res_output def get(self, oid, additional_timeout=0): cmd = "%s '%s'"% (self.prepare_cmd(self.options["--snmpget-path"]), self.quote_for_run(oid)) output = self.run_command(cmd, additional_timeout).splitlines() return output[len(output)-1].split(None, 1) def set(self, oid, value, additional_timeout=0): mapping = ((int, 'i'), (str, 's')) type_of_value = '' for item in mapping: if isinstance(value, item[0]): type_of_value = item[1] break cmd = "%s '%s' %s '%s'" % (self.prepare_cmd(self.options["--snmpset-path"]), self.quote_for_run(oid), type_of_value, self.quote_for_run(str(value))) self.run_command(cmd, additional_timeout) def walk(self, oid, additional_timeout=0): cmd = "%s '%s'"% (self.prepare_cmd(self.options["--snmpwalk-path"]), self.quote_for_run(oid)) output = self.run_command(cmd, additional_timeout).splitlines() return [x.split(None, 1) for x in output if x.startswith(".")]