diff --git a/agents/aliyun/fence_aliyun.py b/agents/aliyun/fence_aliyun.py index c7785a2b..134cc5ab 100644 --- a/agents/aliyun/fence_aliyun.py +++ b/agents/aliyun/fence_aliyun.py @@ -1,208 +1,208 @@ #!@PYTHON@ -tt import sys import logging import atexit import json sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail_usage, run_delay try: from aliyunsdkcore import client from aliyunsdkcore.auth.credentials import EcsRamRoleCredential from aliyunsdkcore.profile import region_provider except ImportError as e: logging.warn("The 'aliyunsdkcore' module has been not installed or is unavailable, try to execute the command 'pip install aliyun-python-sdk-core --upgrade' to solve. error: %s" % e) try: from aliyunsdkecs.request.v20140526.DescribeInstancesRequest import DescribeInstancesRequest from aliyunsdkecs.request.v20140526.StartInstanceRequest import StartInstanceRequest from aliyunsdkecs.request.v20140526.StopInstanceRequest import StopInstanceRequest from aliyunsdkecs.request.v20140526.RebootInstanceRequest import RebootInstanceRequest except ImportError as e: logging.warn("The 'aliyunsdkecs' module has been not installed or is unavailable, try to execute the command 'pip install aliyun-python-sdk-ecs --upgrade' to solve. error: %s" % e) def _send_request(conn, request): logging.debug("send request action: %s" % request.get_action_name()) request.set_accept_format('json') try: response_str = conn.do_action_with_exception(request) except Exception as e: fail_usage("Failed: send request failed: Error: %s" % e) response_detail = json.loads(response_str) logging.debug("reponse: %s" % response_detail) return response_detail def start_instance(conn, instance_id): logging.debug("start instance %s" % instance_id) request = StartInstanceRequest() request.set_InstanceId(instance_id) _send_request(conn, request) def stop_instance(conn, instance_id): logging.debug("stop instance %s" % instance_id) request = StopInstanceRequest() request.set_InstanceId(instance_id) request.set_ForceStop('true') _send_request(conn, request) def reboot_instance(conn, instance_id): logging.debug("reboot instance %s" % instance_id) request = RebootInstanceRequest() request.set_InstanceId(instance_id) request.set_ForceStop('true') _send_request(conn, request) def get_status(conn, instance_id): logging.debug("get instance %s status" % instance_id) request = DescribeInstancesRequest() request.set_InstanceIds(json.dumps([instance_id])) response = _send_request(conn, request) instance_status = None if response is not None: instance_list = response.get('Instances').get('Instance') for item in instance_list: instance_status = item.get('Status') return instance_status def get_nodes_list(conn, options): logging.debug("start to get nodes list") result = {} request = DescribeInstancesRequest() request.set_PageSize(100) if "--filter" in options: filter_key = options["--filter"].split("=")[0].strip() filter_value = options["--filter"].split("=")[1].strip() params = request.get_query_params() params[filter_key] = filter_value request.set_query_params(params) response = _send_request(conn, request) if response is not None: instance_list = response.get('Instances').get('Instance') for item in instance_list: instance_id = item.get('InstanceId') instance_name = item.get('InstanceName') result[instance_id] = (instance_name, None) logging.debug("get nodes list: %s" % result) return result def get_power_status(conn, options): logging.debug("start to get power(%s) status" % options["--plug"]) state = get_status(conn, options["--plug"]) if state == "Running": status = "on" elif state == "Stopped": status = "off" else: status = "unknown" logging.debug("the power(%s) status is %s" % (options["--plug"], status)) return status def set_power_status(conn, options): logging.info("start to set power(%s) status to %s" % (options["--plug"], options["--action"])) if (options["--action"]=="off"): stop_instance(conn, options["--plug"]) elif (options["--action"]=="on"): start_instance(conn, options["--plug"]) elif (options["--action"]=="reboot"): reboot_instance(conn, options["--plug"]) def define_new_opts(): all_opt["region"] = { "getopt" : "r:", "longopt" : "region", "help" : "-r, --region=[name] Region, e.g. cn-hangzhou", "shortdesc" : "Region.", "required" : "0", "order" : 2 } all_opt["access_key"] = { "getopt" : "a:", "longopt" : "access-key", "help" : "-a, --access-key=[name] Access Key", "shortdesc" : "Access Key.", "required" : "0", "order" : 3 } all_opt["secret_key"] = { "getopt" : "s:", "longopt" : "secret-key", "help" : "-s, --secret-key=[name] Secret Key", "shortdesc" : "Secret Key.", "required" : "0", "order" : 4 } all_opt["ram_role"] = { "getopt": ":", "longopt": "ram-role", "help": "--ram-role=[name] Ram Role", "shortdesc": "Ram Role.", "required": "0", "order": 5 } all_opt["filter"] = { "getopt": ":", "longopt": "filter", "help": "--filter=[key=value] Filter (e.g. InstanceIds=[\"i-XXYYZZAA1\",\"i-XXYYZZAA2\"]", "shortdesc": "Filter for list-action.", "required": "0", "order": 6 } # Main agent method def main(): conn = None device_opt = ["port", "no_password", "region", "access_key", "secret_key", "ram_role", "filter"] atexit.register(atexit_handler) define_new_opts() all_opt["power_timeout"]["default"] = "60" options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for Aliyun (Aliyun Web Services)" - docs["longdesc"] = "fence_aliyun is an I/O Fencing agent for Aliyun" + docs["longdesc"] = "fence_aliyun is a Power Fencing agent for Aliyun." docs["vendorurl"] = "http://www.aliyun.com" show_docs(options, docs) run_delay(options) if "--region" in options: region = options["--region"] if "--access-key" in options and "--secret-key" in options: access_key = options["--access-key"] secret_key = options["--secret-key"] conn = client.AcsClient(access_key, secret_key, region) elif "--ram-role" in options: ram_role = options["--ram-role"] role = EcsRamRoleCredential(ram_role) conn = client.AcsClient(region_id=region, credential=role) else: fail_usage("Failed: User credentials are not set. Please set the Access Key and the Secret Key, or configure the RAM role.") # Use intranet endpoint to access ECS service try: region_provider.modify_point('Ecs', region, 'ecs.%s.aliyuncs.com' % region) except Exception as e: logging.warn("Failed: failed to modify endpoint to 'ecs.%s.aliyuncs.com': %s" % (region, e)) # Operate the fencing device result = fence_action(conn, options, set_power_status, get_power_status, get_nodes_list) sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/alom/fence_alom.py b/agents/alom/fence_alom.py index 7b03dc2a..a8e216f3 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()) 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 an I/O Fencing \ -agent which can be used with ALOM connected machines." + 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 80d3f74c..183bbc71 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)) 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 an I/O Fencing agent \ + 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/amt_ws/fence_amt_ws.py b/agents/amt_ws/fence_amt_ws.py index 5e7452a9..89500d4b 100755 --- a/agents/amt_ws/fence_amt_ws.py +++ b/agents/amt_ws/fence_amt_ws.py @@ -1,240 +1,240 @@ #!@PYTHON@ -tt # # Fence agent for Intel AMT (WS) based on code from the openstack/ironic project: # https://github.com/openstack/ironic/blob/master/ironic/drivers/modules/amt/power.py # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import sys import atexit import logging sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import run_delay, fail_usage, fail, EC_STATUS from xml.etree import ElementTree try: import pywsman except ImportError: pass POWER_ON='2' POWER_OFF='8' POWER_CYCLE='10' RET_SUCCESS = '0' CIM_PowerManagementService = ('http://schemas.dmtf.org/wbem/wscim/1/' 'cim-schema/2/CIM_PowerManagementService') CIM_ComputerSystem = ('http://schemas.dmtf.org/wbem/wscim/' '1/cim-schema/2/CIM_ComputerSystem') CIM_AssociatedPowerManagementService = ('http://schemas.dmtf.org/wbem/wscim/' '1/cim-schema/2/' 'CIM_AssociatedPowerManagementService') CIM_BootConfigSetting = ('http://schemas.dmtf.org/wbem/wscim/' '1/cim-schema/2/CIM_BootConfigSetting') CIM_BootSourceSetting = ('http://schemas.dmtf.org/wbem/wscim/' '1/cim-schema/2/CIM_BootSourceSetting') def xml_find(doc, namespace, item): if doc is None: return tree = ElementTree.fromstring(doc.root().string()) query = ('.//{%(namespace)s}%(item)s' % {'namespace': namespace, 'item': item}) return tree.find(query) def _generate_power_action_input(action): method_input = "RequestPowerStateChange_INPUT" address = 'http://schemas.xmlsoap.org/ws/2004/08/addressing' anonymous = ('http://schemas.xmlsoap.org/ws/2004/08/addressing/' 'role/anonymous') wsman = 'http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd' namespace = CIM_PowerManagementService doc = pywsman.XmlDoc(method_input) root = doc.root() root.set_ns(namespace) root.add(namespace, 'PowerState', action) child = root.add(namespace, 'ManagedElement', None) child.add(address, 'Address', anonymous) grand_child = child.add(address, 'ReferenceParameters', None) grand_child.add(wsman, 'ResourceURI', CIM_ComputerSystem) g_grand_child = grand_child.add(wsman, 'SelectorSet', None) g_g_grand_child = g_grand_child.add(wsman, 'Selector', 'ManagedSystem') g_g_grand_child.attr_add(wsman, 'Name', 'Name') return doc def get_power_status(_, options): client = pywsman.Client(options["--ip"], int(options["--ipport"]), \ '/wsman', 'http', 'admin', options["--password"]) namespace = CIM_AssociatedPowerManagementService client_options = pywsman.ClientOptions() doc = client.get(client_options, namespace) _SOAP_ENVELOPE = 'http://www.w3.org/2003/05/soap-envelope' item = 'Fault' fault = xml_find(doc, _SOAP_ENVELOPE, item) if fault is not None: logging.error("Failed to get power state for: %s port:%s", \ options["--ip"], options["--ipport"]) fail(EC_STATUS) item = "PowerState" try: power_state = xml_find(doc, namespace, item).text except AttributeError: logging.error("Failed to get power state for: %s port:%s", \ options["--ip"], options["--ipport"]) fail(EC_STATUS) if power_state == POWER_ON: return "on" elif power_state == POWER_OFF: return "off" else: fail(EC_STATUS) def set_power_status(_, options): client = pywsman.Client(options["--ip"], int(options["--ipport"]), \ '/wsman', 'http', 'admin', options["--password"]) method = 'RequestPowerStateChange' client_options = pywsman.ClientOptions() client_options.add_selector('Name', 'Intel(r) AMT Power Management Service') if options["--action"] == "on": target_state = POWER_ON elif options["--action"] == "off": target_state = POWER_OFF elif options["--action"] == "reboot": target_state = POWER_CYCLE if options["--action"] in ["on", "off", "reboot"] \ and "--boot-option" in options: set_boot_order(_, client, options) doc = _generate_power_action_input(target_state) client_doc = client.invoke(client_options, CIM_PowerManagementService, \ method, doc) item = "ReturnValue" return_value = xml_find(client_doc, CIM_PowerManagementService, item).text if return_value != RET_SUCCESS: logging.error("Failed to set power state: %s for: %s", \ options["--action"], options["--ip"]) fail(EC_STATUS) def set_boot_order(_, client, options): method_input = "ChangeBootOrder_INPUT" address = 'http://schemas.xmlsoap.org/ws/2004/08/addressing' anonymous = ('http://schemas.xmlsoap.org/ws/2004/08/addressing/' 'role/anonymous') wsman = 'http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd' namespace = CIM_BootConfigSetting if options["--boot-option"] == "PXE": device = "Intel(r) AMT: Force PXE Boot" elif options["--boot-option"] in ["HD", "HDSAFE"]: device = "Intel(r) AMT: Force Hard-drive Boot" elif options["--boot-option"] == "CD": device = "Intel(r) AMT: Force CD/DVD Boot" elif options["--boot-option"] == "DIAG": device = "Intel(r) AMT: Force Diagnostic Boot" else: logging.error('Boot device: %s not supported.', \ options["--boot-option"]) return method = 'ChangeBootOrder' client_options = pywsman.ClientOptions() client_options.add_selector('InstanceID', \ 'Intel(r) AMT: Boot Configuration 0') doc = pywsman.XmlDoc(method_input) root = doc.root() root.set_ns(namespace) child = root.add(namespace, 'Source', None) child.add(address, 'Address', anonymous) grand_child = child.add(address, 'ReferenceParameters', None) grand_child.add(wsman, 'ResourceURI', CIM_BootSourceSetting) g_grand_child = grand_child.add(wsman, 'SelectorSet', None) g_g_grand_child = g_grand_child.add(wsman, 'Selector', device) g_g_grand_child.attr_add(wsman, 'Name', 'InstanceID') if options["--boot-option"] == "hdsafe": g_g_grand_child = g_grand_child.add(wsman, 'Selector', 'True') g_g_grand_child.attr_add(wsman, 'Name', 'UseSafeMode') client_doc = client.invoke(client_options, CIM_BootConfigSetting, \ method, doc) item = "ReturnValue" return_value = xml_find(client_doc, CIM_BootConfigSetting, item).text if return_value != RET_SUCCESS: logging.error("Failed to set boot device to: %s for: %s", \ options["--boot-option"], options["--ip"]) fail(EC_STATUS) def reboot_cycle(_, options): status = set_power_status(_, options) return not bool(status) 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\n" " 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 } def main(): atexit.register(atexit_handler) device_opt = ["ipaddr", "no_login", "passwd", "boot_option", "no_port", "method"] define_new_opts() all_opt["ipport"]["default"] = "16992" options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for AMT (WS)" - docs["longdesc"] = "fence_amt_ws is an I/O Fencing agent \ + docs["longdesc"] = "fence_amt_ws is a Power Fencing agent \ which can be used with Intel AMT (WS). This agent requires \ the pywsman Python library which is included in OpenWSMAN. \ (http://openwsman.github.io/)." docs["vendorurl"] = "http://www.intel.com/" show_docs(options, docs) run_delay(options) 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 3ea0f37d..bc52aa24 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): switch = 1 if None != re.compile('.* 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): version = 2 else: version = 3 if None == re.compile('.*Outlet Control/Configuration.*', re.IGNORECASE | re.S).match(conn.before): admin = 0 else: admin = 1 if switch == 0: if version == 2: if admin == 0: conn.send_eol("2") else: conn.send_eol("3") else: conn.send_eol("2") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) conn.send_eol("1") else: conn.send_eol(options["--switch"]) while True: exp_result = conn.log_expect( ["Press "] + 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): 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 "--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): version = 2 else: version = 3 if None == re.compile('.*Outlet Control/Configuration.*', re.IGNORECASE | re.S).match(conn.before): admin2 = 0 else: admin2 = 1 if switch == 0: if version == 2: if admin2 == 0: conn.send_eol("2") else: conn.send_eol("3") else: conn.send_eol("2") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) if None == re.compile('.*2- Outlet Restriction.*', re.IGNORECASE | re.S).match(conn.before): admin3 = 0 else: admin3 = 1 conn.send_eol("1") else: conn.send_eol(options["--switch"]) while 0 == conn.log_expect( ["Press "] + 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 an I/O Fencing agent \ + 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/apc_snmp/fence_apc_snmp.py b/agents/apc_snmp/fence_apc_snmp.py index cd601662..1091f0da 100644 --- a/agents/apc_snmp/fence_apc_snmp.py +++ b/agents/apc_snmp/fence_apc_snmp.py @@ -1,232 +1,232 @@ #!@PYTHON@ -tt # The Following agent has been tested on: # - APC Switched Rack PDU - SNMP v1 # (MB:v3.7.0 PF:v2.7.0 PN:apc_hw02_aos_270.bin AF1:v2.7.3 # AN1:apc_hw02_aos_270.bin AF1:v2.7.3 AN1:apc_hw02_rpdu_273.bin MN:AP7930 HR:B2) # - APC Web/SNMP Management Card - SNMP v1 and v3 (noAuthNoPrivacy,authNoPrivacy, authPrivacy) # (MB:v3.8.6 PF:v3.5.8 PN:apc_hw02_aos_358.bin AF1:v3.5.7 # AN1:apc_hw02_aos_358.bin AF1:v3.5.7 AN1:apc_hw02_rpdu_357.bin MN:AP7900 HR:B2) # - APC Switched Rack PDU - SNMP v1 # (MB:v3.7.0 PF:v2.7.0 PN:apc_hw02_aos_270.bin AF1:v2.7.3 # AN1:apc_hw02_rpdu_273.bin MN:AP7951 HR:B2) # - Tripplite PDUMH20HVNET 12.04.0055 - SNMP v1, v2c, v3 # - Tripplite PDU15NETLX 15.5.4 - SNMP v1, v2c, v3 -import sys +import sys, os import atexit import logging sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail_usage from fencing_snmp import FencingSnmp ### CONSTANTS ### # oid defining fence device OID_SYS_OBJECT_ID = '.1.3.6.1.2.1.1.2.0' ### GLOBAL VARIABLES ### # Device - see ApcRPDU, ApcMSP, ApcMS, TripplitePDU, TrippliteLXPDU device = None # Port ID port_id = None # Switch ID switch_id = None # Classes describing Device params class TripplitePDU(object): # Rack PDU status_oid = '.1.3.6.1.4.1.850.10.2.3.5.1.2.1.%d' control_oid = '.1.3.6.1.4.1.850.10.2.3.5.1.4.1.%d' outlet_table_oid = '.1.3.6.1.4.1.850.10.2.3.5.1.5' ident_str = "Tripplite" state_on = 2 state_off = 1 turn_on = 2 turn_off = 1 has_switches = False class TrippliteLXPDU(object): # WEBCARDLX-based PDU status_oid = '.1.3.6.1.4.1.850.1.1.3.2.3.3.1.1.4.1.%d' control_oid = '.1.3.6.1.4.1.850.1.1.3.2.3.3.1.1.6.1.%d' outlet_table_oid = '.1.3.6.1.4.1.850.1.1.3.2.3.3.1.1.2.1' ident_str = "Tripplite LX" state_on = 2 state_off = 1 turn_on = 2 turn_off = 1 has_switches = False class ApcRPDU(object): # Rack PDU status_oid = '.1.3.6.1.4.1.318.1.1.12.3.5.1.1.4.%d' control_oid = '.1.3.6.1.4.1.318.1.1.12.3.3.1.1.4.%d' outlet_table_oid = '.1.3.6.1.4.1.318.1.1.12.3.5.1.1.2' ident_str = "APC rPDU" state_on = 1 state_off = 2 turn_on = 1 turn_off = 2 has_switches = False class ApcMSP(object): # Master Switch+ status_oid = '.1.3.6.1.4.1.318.1.1.6.7.1.1.5.%d.1.%d' control_oid = '.1.3.6.1.4.1.318.1.1.6.5.1.1.5.%d.1.%d' outlet_table_oid = '.1.3.6.1.4.1.318.1.1.6.7.1.1.4' ident_str = "APC Master Switch+" state_on = 1 state_off = 2 turn_on = 1 turn_off = 3 has_switches = True class ApcMS(object): # Master Switch - seems oldest, but supported on every APC PDU status_oid = '.1.3.6.1.4.1.318.1.1.4.4.2.1.3.%d' control_oid = '.1.3.6.1.4.1.318.1.1.4.4.2.1.3.%d' outlet_table_oid = '.1.3.6.1.4.1.318.1.1.4.4.2.1.4' ident_str = "APC Master Switch (fallback)" state_on = 1 state_off = 2 turn_on = 1 turn_off = 2 has_switches = False class ApcMS6(object): # Master Switch with 6.x firmware status_oid = '.1.3.6.1.4.1.318.1.1.4.4.2.1.3.%d' control_oid = '.1.3.6.1.4.1.318.1.1.12.3.3.1.1.4.%d' outlet_table_oid = '1.3.6.1.4.1.318.1.1.4.4.2.1.4' ident_str = "APC Master Switch with firmware v6.x" state_on = 1 state_off = 2 turn_on = 1 turn_off = 2 has_switches = False ### FUNCTIONS ### def apc_set_device(conn): global device agents_dir = {'.1.3.6.1.4.1.318.1.3.4.5':ApcRPDU, '.1.3.6.1.4.1.318.1.3.4.4':ApcMSP, '.1.3.6.1.4.1.850.1':TripplitePDU, '.1.3.6.1.4.1.850.1.1.1':TrippliteLXPDU, '.1.3.6.1.4.1.318.1.3.4.6':ApcMS6, None:ApcMS} # First resolve type of APC apc_type = conn.walk(OID_SYS_OBJECT_ID) if not ((len(apc_type) == 1) and (apc_type[0][1] in agents_dir)): apc_type = [[None, None]] device = agents_dir[apc_type[0][1]] logging.debug("Trying %s"%(device.ident_str)) def apc_resolv_port_id(conn, options): global port_id, switch_id if device == None: apc_set_device(conn) # Now we resolv port_id/switch_id if (options["--plug"].isdigit()) and ((not device.has_switches) or (options["--switch"].isdigit())): port_id = int(options["--plug"]) if device.has_switches: switch_id = int(options["--switch"]) else: table = conn.walk(device.outlet_table_oid, 30) for x in table: if x[1].strip('"') == options["--plug"]: t = x[0].split('.') if device.has_switches: port_id = int(t[len(t)-1]) switch_id = int(t[len(t)-3]) else: port_id = int(t[len(t)-1]) if port_id == None: fail_usage("Can't find port with name %s!"%(options["--plug"])) def get_power_status(conn, options): if port_id == None: apc_resolv_port_id(conn, options) oid = ((device.has_switches) and device.status_oid%(switch_id, port_id) or device.status_oid%(port_id)) (oid, status) = conn.get(oid) return status == str(device.state_on) and "on" or "off" def set_power_status(conn, options): if port_id == None: apc_resolv_port_id(conn, options) oid = ((device.has_switches) and device.control_oid%(switch_id, port_id) or device.control_oid%(port_id)) conn.set(oid, (options["--action"] == "on" and device.turn_on or device.turn_off)) def get_outlets_status(conn, options): result = {} if device == None: apc_set_device(conn) res_ports = conn.walk(device.outlet_table_oid, 30) for x in res_ports: t = x[0].split('.') port_num = ((device.has_switches) and "%s:%s"%(t[len(t)-3], t[len(t)-1]) or "%s"%(t[len(t)-1])) port_name = x[1].strip('"') port_status = "" result[port_num] = (port_name, port_status) return result # Main agent method def main(): device_opt = ["ipaddr", "login", "passwd", "no_login", "no_password", \ "port", "snmp_version", "snmp"] atexit.register(atexit_handler) all_opt["snmp_version"]["default"] = "1" all_opt["community"]["default"] = "private" options = check_input(device_opt, process_input(device_opt)) ## Support for -n [switch]:[plug] notation that was used before if ("--plug" in options) and (-1 != options["--plug"].find(":")): (switch, plug) = options["--plug"].split(":", 1) if switch.isdigit() and plug.isdigit(): options["--switch"] = switch options["--plug"] = plug if "--switch" not in options: options["--switch"] = "1" docs = {} docs["shortdesc"] = "Fence agent for APC, Tripplite PDU over SNMP" - docs["longdesc"] = "fence_apc_snmp is an I/O Fencing agent \ + docs["longdesc"] = "{} is a Power Fencing agent \ which can be used with the APC network power switch or Tripplite PDU devices.\ It logs into a device via SNMP and reboots a specified outlet. It supports \ -SNMP v1, v2c, v3 with all combinations of authenticity/privacy settings." +SNMP v1, v2c, v3 with all combinations of authenticity/privacy settings.".format(os.path.basename(__file__)) docs["vendorurl"] = "http://www.apc.com" docs["symlink"] = [("fence_tripplite_snmp", "Fence agent for Tripplife over SNMP")] show_docs(options, docs) # Operate the fencing device result = fence_action(FencingSnmp(options), options, set_power_status, get_power_status, get_outlets_status) sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/aws/fence_aws.py b/agents/aws/fence_aws.py index 0a375bbe..5b32106f 100644 --- a/agents/aws/fence_aws.py +++ b/agents/aws/fence_aws.py @@ -1,238 +1,238 @@ #!@PYTHON@ -tt import sys, re import logging import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, fail_usage, run_delay, EC_STATUS, SyslogLibHandler import requests from requests import HTTPError try: import boto3 from botocore.exceptions import ConnectionError, ClientError, EndpointConnectionError, NoRegionError except ImportError: pass logger = logging.getLogger() logger.propagate = False logger.setLevel(logging.INFO) logger.addHandler(SyslogLibHandler()) logging.getLogger('botocore.vendored').propagate = False def get_instance_id(options): try: token = requests.put('http://169.254.169.254/latest/api/token', headers={"X-aws-ec2-metadata-token-ttl-seconds" : "21600"}).content.decode("UTF-8") r = requests.get('http://169.254.169.254/latest/meta-data/instance-id', headers={"X-aws-ec2-metadata-token" : token}).content.decode("UTF-8") return r except HTTPError as http_err: logger.error('HTTP error occurred while trying to access EC2 metadata server: %s', http_err) except Exception as err: if "--skip-race-check" not in options: logger.error('A fatal error occurred while trying to access EC2 metadata server: %s', err) else: logger.debug('A fatal error occurred while trying to access EC2 metadata server: %s', err) return None def get_nodes_list(conn, options): logger.debug("Starting monitor operation") result = {} try: if "--filter" in options: filter_key = options["--filter"].split("=")[0].strip() filter_value = options["--filter"].split("=")[1].strip() filter = [{ "Name": filter_key, "Values": [filter_value] }] for instance in conn.instances.filter(Filters=filter): result[instance.id] = ("", None) else: for instance in conn.instances.all(): result[instance.id] = ("", None) except ClientError: fail_usage("Failed: Incorrect Access Key or Secret Key.") except EndpointConnectionError: fail_usage("Failed: Incorrect Region.") except ConnectionError as e: fail_usage("Failed: Unable to connect to AWS: " + str(e)) except Exception as e: logger.error("Failed to get node list: %s", e) logger.debug("Monitor operation OK: %s",result) return result def get_power_status(conn, options): logger.debug("Starting status operation") try: instance = conn.instances.filter(Filters=[{"Name": "instance-id", "Values": [options["--plug"]]}]) state = list(instance)[0].state["Name"] logger.debug("Status operation for EC2 instance %s returned state: %s",options["--plug"],state.upper()) if state == "running": return "on" elif state == "stopped": return "off" else: return "unknown" except ClientError: fail_usage("Failed: Incorrect Access Key or Secret Key.") except EndpointConnectionError: fail_usage("Failed: Incorrect Region.") except IndexError: fail(EC_STATUS) except Exception as e: logger.error("Failed to get power status: %s", e) fail(EC_STATUS) def get_self_power_status(conn, instance_id): try: instance = conn.instances.filter(Filters=[{"Name": "instance-id", "Values": [instance_id]}]) state = list(instance)[0].state["Name"] if state == "running": logger.debug("Captured my (%s) state and it %s - returning OK - Proceeding with fencing",instance_id,state.upper()) return "ok" else: logger.debug("Captured my (%s) state it is %s - returning Alert - Unable to fence other nodes",instance_id,state.upper()) return "alert" except ClientError: fail_usage("Failed: Incorrect Access Key or Secret Key.") except EndpointConnectionError: fail_usage("Failed: Incorrect Region.") except IndexError: return "fail" def set_power_status(conn, options): my_instance = get_instance_id(options) try: if (options["--action"]=="off"): if "--skip-race-check" in options or get_self_power_status(conn,my_instance) == "ok": conn.instances.filter(InstanceIds=[options["--plug"]]).stop(Force=True) logger.debug("Called StopInstance API call for %s", options["--plug"]) else: logger.debug("Skipping fencing as instance is not in running status") elif (options["--action"]=="on"): conn.instances.filter(InstanceIds=[options["--plug"]]).start() except Exception as e: logger.debug("Failed to power %s %s: %s", \ options["--action"], options["--plug"], e) fail(EC_STATUS) def define_new_opts(): all_opt["region"] = { "getopt" : "r:", "longopt" : "region", "help" : "-r, --region=[region] Region, e.g. us-east-1", "shortdesc" : "Region.", "required" : "0", "order" : 2 } all_opt["access_key"] = { "getopt" : "a:", "longopt" : "access-key", "help" : "-a, --access-key=[key] Access Key", "shortdesc" : "Access Key.", "required" : "0", "order" : 3 } all_opt["secret_key"] = { "getopt" : "s:", "longopt" : "secret-key", "help" : "-s, --secret-key=[key] Secret Key", "shortdesc" : "Secret Key.", "required" : "0", "order" : 4 } all_opt["filter"] = { "getopt" : ":", "longopt" : "filter", "help" : "--filter=[key=value] Filter (e.g. vpc-id=[vpc-XXYYZZAA]", "shortdesc": "Filter for list-action", "required": "0", "order": 5 } all_opt["boto3_debug"] = { "getopt" : "b:", "longopt" : "boto3_debug", "help" : "-b, --boto3_debug=[option] Boto3 and Botocore library debug logging", "shortdesc": "Boto Lib debug", "required": "0", "default": "False", "order": 6 } all_opt["skip_race_check"] = { "getopt" : "", "longopt" : "skip-race-check", "help" : "--skip-race-check Skip race condition check", "shortdesc": "Skip race condition check", "required": "0", "order": 7 } # Main agent method def main(): conn = None device_opt = ["port", "no_password", "region", "access_key", "secret_key", "filter", "boto3_debug", "skip_race_check"] atexit.register(atexit_handler) define_new_opts() all_opt["power_timeout"]["default"] = "60" options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for AWS (Amazon Web Services)" - docs["longdesc"] = "fence_aws is an I/O Fencing agent for AWS (Amazon Web\ + docs["longdesc"] = "fence_aws is a Power Fencing agent for AWS (Amazon Web\ Services). It uses the boto3 library to connect to AWS.\ \n.P\n\ boto3 can be configured with AWS CLI or by creating ~/.aws/credentials.\n\ For instructions see: https://boto3.readthedocs.io/en/latest/guide/quickstart.html#configuration" docs["vendorurl"] = "http://www.amazon.com" show_docs(options, docs) run_delay(options) if "--debug-file" in options: for handler in logger.handlers: if isinstance(handler, logging.FileHandler): logger.removeHandler(handler) lh = logging.FileHandler(options["--debug-file"]) logger.addHandler(lh) lhf = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') lh.setFormatter(lhf) lh.setLevel(logging.DEBUG) if options["--boto3_debug"].lower() not in ["1", "yes", "on", "true"]: boto3.set_stream_logger('boto3',logging.INFO) boto3.set_stream_logger('botocore',logging.CRITICAL) logging.getLogger('botocore').propagate = False logging.getLogger('boto3').propagate = False else: log_format = logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s') logging.getLogger('botocore').propagate = False logging.getLogger('boto3').propagate = False fdh = logging.FileHandler('/var/log/fence_aws_boto3.log') fdh.setFormatter(log_format) logging.getLogger('boto3').addHandler(fdh) logging.getLogger('botocore').addHandler(fdh) logging.debug("Boto debug level is %s and sending debug info to /var/log/fence_aws_boto3.log", options["--boto3_debug"]) region = options.get("--region") access_key = options.get("--access-key") secret_key = options.get("--secret-key") try: conn = boto3.resource('ec2', region_name=region, aws_access_key_id=access_key, aws_secret_access_key=secret_key) except Exception as e: fail_usage("Failed: Unable to connect to AWS: " + str(e)) # Operate the fencing device result = fence_action(conn, options, set_power_status, get_power_status, get_nodes_list) 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 515aae29..0dca8f30 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 an I/O Fencing agent for Azure Resource Manager. It uses Azure SDK for Python to connect to Azure.\ + 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))) 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/bladecenter/fence_bladecenter.py b/agents/bladecenter/fence_bladecenter.py index d670367f..2f2c65fc 100644 --- a/agents/bladecenter/fence_bladecenter.py +++ b/agents/bladecenter/fence_bladecenter.py @@ -1,105 +1,105 @@ #!@PYTHON@ -tt ##### ## ## The Following Agent Has Been Tested On: ## ## Model Firmware ## +--------------------+---------------------------+ ## (1) Main application BRET85K, rev 16 ## Boot ROM BRBR67D, rev 16 ## Remote Control BRRG67D, rev 16 ## ##### import sys, re import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, EC_STATUS, EC_GENERIC_ERROR def get_power_status(conn, options): node_cmd = r"system:blade\[" + options["--plug"] + r"\]>" conn.send_eol("env -T system:blade[" + options["--plug"] + "]") i = conn.log_expect([node_cmd, "system>"], int(options["--shell-timeout"])) if i == 1: ## Given blade number does not exist if "--missing-as-off" in options: return "off" else: fail(EC_STATUS) conn.send_eol("power -state") conn.log_expect(node_cmd, int(options["--shell-timeout"])) status = conn.before.splitlines()[-1] conn.send_eol("env -T system") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) return status.lower().strip() def set_power_status(conn, options): node_cmd = r"system:blade\[" + options["--plug"] + r"\]>" conn.send_eol("env -T system:blade[" + options["--plug"] + "]") i = conn.log_expect([node_cmd, "system>"], int(options["--shell-timeout"])) if i == 1: ## Given blade number does not exist if "--missing-as-off" in options: return else: fail(EC_GENERIC_ERROR) conn.send_eol("power -"+options["--action"]) conn.log_expect(node_cmd, int(options["--shell-timeout"])) conn.send_eol("env -T system") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) def get_blades_list(conn, options): outlets = {} node_cmd = "system>" conn.send_eol("env -T system") conn.log_expect(node_cmd, int(options["--shell-timeout"])) conn.send_eol("list -l 2") conn.log_expect(node_cmd, int(options["--shell-timeout"])) lines = conn.before.split("\r\n") filter_re = re.compile(r"^\s*blade\[(\d+)\]\s+(.*?)\s*$") for blade_line in lines: res = filter_re.search(blade_line) if res != None: outlets[res.group(1)] = (res.group(2), "") return outlets def main(): device_opt = ["ipaddr", "login", "passwd", "cmd_prompt", "secure", \ "port", "missing_as_off", "telnet"] atexit.register(atexit_handler) all_opt["power_wait"]["default"] = "10" all_opt["cmd_prompt"]["default"] = ["system>"] options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for IBM BladeCenter" - docs["longdesc"] = "fence_bladecenter is an I/O Fencing agent \ + docs["longdesc"] = "fence_bladecenter is a Power Fencing agent \ which can be used with IBM Bladecenters with recent enough firmware that \ includes telnet support. It logs into a Brocade chasis via telnet or ssh \ and uses the command line interface to power on and off blades." docs["vendorurl"] = "http://www.ibm.com" show_docs(options, docs) ## ## Operate the fencing device ###### conn = fence_login(options, "(username\s*:\s*)") result = fence_action(conn, options, set_power_status, get_power_status, get_blades_list) fence_logout(conn, "exit") sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/cdu/fence_cdu.py b/agents/cdu/fence_cdu.py index 483ac512..ba76e6d7 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)') 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)') 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+') 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') 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 an I/O Fencing agent \ + 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_mds/fence_cisco_mds.py b/agents/cisco_mds/fence_cisco_mds.py index fbb876a9..04cd1f84 100644 --- a/agents/cisco_mds/fence_cisco_mds.py +++ b/agents/cisco_mds/fence_cisco_mds.py @@ -1,94 +1,94 @@ #!@PYTHON@ -tt # The Following agent has been tested on: # - Cisco MDS UROS 9134 FC (1 Slot) Chassis ("1/2/4 10 Gbps FC/Supervisor-2") Motorola, e500v2 # with BIOS 1.0.16, kickstart 4.1(1c), system 4.1(1c) # - Cisco MDS 9124 (1 Slot) Chassis ("1/2/4 Gbps FC/Supervisor-2") Motorola, e500 # with BIOS 1.0.16, kickstart 4.1(1c), system 4.1(1c) import sys, re import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail_usage, array_to_dict from fencing_snmp import FencingSnmp ### CONSTANTS ### # Cisco admin status PORT_ADMIN_STATUS_OID = ".1.3.6.1.2.1.75.1.2.2.1.1" # IF-MIB trees for alias, status and port ALIASES_OID = ".1.3.6.1.2.1.31.1.1.1.18" PORTS_OID = ".1.3.6.1.2.1.2.2.1.2" ### GLOBAL VARIABLES ### # OID converted from fc port name (fc(x)/(y)) PORT_OID = "" ### FUNCTIONS ### # Convert cisco port name (fc(x)/(y)) to OID def cisco_port2oid(port): port = port.lower() nums = re.match(r'^fc(\d+)/(\d+)$', port) if nums and len(nums.groups()) == 2: return "%s.%d.%d"% (PORT_ADMIN_STATUS_OID, int(nums.group(1))+21, int(nums.group(2))-1) else: fail_usage("Mangled port number: %s"%(port)) def get_power_status(conn, options): (_, status) = conn.get(PORT_OID) return status == "1" and "on" or "off" def set_power_status(conn, options): conn.set(PORT_OID, (options["--action"] == "on" and 1 or 2)) def get_outlets_status(conn, options): result = {} res_fc = conn.walk(PORTS_OID, 30) res_aliases = array_to_dict(conn.walk(ALIASES_OID, 30)) fc_re = re.compile(r'^"fc\d+/\d+"$') for x in res_fc: if fc_re.match(x[1]): port_num = x[0].split('.')[-1] port_name = x[1].strip('"') port_alias = (port_num in res_aliases and res_aliases[port_num].strip('"') or "") port_status = "" result[port_name] = (port_alias, port_status) return result # Main agent method def main(): global PORT_OID device_opt = ["fabric_fencing", "ipaddr", "login", "passwd", "no_login", "no_password", \ "port", "snmp_version", "snmp"] atexit.register(atexit_handler) options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for Cisco MDS" - docs["longdesc"] = "fence_cisco_mds is an I/O Fencing agent \ + docs["longdesc"] = "fence_cisco_mds is a Power Fencing agent \ which can be used with any Cisco MDS 9000 series with SNMP enabled device." docs["vendorurl"] = "http://www.cisco.com" show_docs(options, docs) if not options["--action"] in ["list", "monitor"]: PORT_OID = cisco_port2oid(options["--plug"]) # Operate the fencing device result = fence_action(FencingSnmp(options), options, set_power_status, get_power_status, get_outlets_status) 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 b85379a7..cada20d5 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 an I/O Fencing agent which can be \ + 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/cyberpower_ssh/fence_cyberpower_ssh.py b/agents/cyberpower_ssh/fence_cyberpower_ssh.py index f0695d67..5878d64a 100755 --- a/agents/cyberpower_ssh/fence_cyberpower_ssh.py +++ b/agents/cyberpower_ssh/fence_cyberpower_ssh.py @@ -1,70 +1,70 @@ #!@PYTHON@ -tt ##### ## ## Fence agent for CyberPower based SSH-capable power strip ## Tested with CyberPower model PDU41001, ePDU Firmware version 1.2.0 ## ##### import sys, re, time import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, EC_STATUS def set_power_status(conn, options): conn.send_eol("oltctrl index " + options["--plug"] + " act delay" + options["--action"]) conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) def get_power_status(conn, options): outlets = {} conn.send_eol("oltsta show") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) lines = conn.before.split("\n") show_re = re.compile(r'(\s*)(\d)\s*(.*)\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)) if ["list", "monitor"].count(options["--action"]) == 1: return outlets else: try: (_,status) = outlets[options["--plug"]] return status.lower().strip() except KeyError: fail(EC_STATUS) def main(): device_opt = ["ipaddr", "login", "passwd", "cmd_prompt", "secure", \ "port"] atexit.register(atexit_handler) all_opt["cmd_prompt"]["default"] = ["\n>", "\nCyberPower >"] options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for CyberPower over ssh" - docs["longdesc"] = "fence_cyberpower_ssh is an I/O Fencing agent \ + docs["longdesc"] = "fence_cyberpower_ssh is a Power Fencing agent \ which can be used with the CyberPower network power switch. It logs into \ device via ssh and reboots a specified outlet. Lengthy ssh connections \ should be avoided while a GFS cluster is running because the connection \ will block any necessary fencing actions." docs["vendorurl"] = "http://www.cyberpower.com" show_docs(options, docs) ## ## Operate the fencing device #### conn = fence_login(options) result = fence_action(conn, options, set_power_status, get_power_status, get_power_status) fence_logout(conn, "exit") sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/docker/fence_docker.py b/agents/docker/fence_docker.py index 00440251..8042515a 100644 --- a/agents/docker/fence_docker.py +++ b/agents/docker/fence_docker.py @@ -1,161 +1,161 @@ #!@PYTHON@ -tt import atexit import sys import io import logging import pycurl import json sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import fail_usage, all_opt, fence_action, atexit_handler, check_input, process_input, show_docs, run_delay def get_power_status(conn, options): del conn status = send_cmd(options, "containers/%s/json" % options["--plug"]) if status is None: return None return "on" if status["State"]["Running"] else "off" def set_power_status(conn, options): del conn if options["--action"] == "on": send_cmd(options, "containers/%s/start" % options["--plug"], True) else: send_cmd(options, "containers/%s/kill" % options["--plug"], True) return def reboot_cycle(conn, options): del conn send_cmd(options, "containers/%s/restart" % options["--plug"], True) return get_power_status(conn, options) def get_list(conn, options): del conn output = send_cmd(options, "containers/json?all=1") containers = {} for container in output: containers[container["Id"]] = ({True:container["Names"][0][1:], False: container["Names"][0]}[container["Names"][0][0:1] == '/'], {True:"off", False: "on"}[container["Status"][:4].lower() == "exit"]) return containers def send_cmd(options, cmd, post = False): url = "http%s://%s:%s/v%s/%s" % ("s" if "--ssl-secure" in options or "--ssl-insecure" in options else "", options["--ip"], options["--ipport"], options["--api-version"], cmd) conn = pycurl.Curl() output_buffer = io.BytesIO() if logging.getLogger().getEffectiveLevel() < logging.WARNING: conn.setopt(pycurl.VERBOSE, True) conn.setopt(pycurl.HTTPGET, 1) conn.setopt(pycurl.URL, url.encode("ascii")) if post: conn.setopt(pycurl.POST, 1) conn.setopt(pycurl.POSTFIELDSIZE, 0) conn.setopt(pycurl.WRITEFUNCTION, output_buffer.write) conn.setopt(pycurl.TIMEOUT, int(options["--shell-timeout"])) if "--ssl-secure" in options: if not (set(("--tlscert", "--tlskey", "--tlscacert")) <= set(options)): fail_usage("Failed. If --ssl option is used, You have to also \ specify: --tlscert, --tlskey and --tlscacert") conn.setopt(pycurl.SSL_VERIFYPEER, 1) conn.setopt(pycurl.SSLCERT, options["--tlscert"]) conn.setopt(pycurl.SSLKEY, options["--tlskey"]) conn.setopt(pycurl.CAINFO, options["--tlscacert"]) elif "--ssl-insecure" in options: conn.setopt(pycurl.SSL_VERIFYPEER, 0) conn.setopt(pycurl.SSL_VERIFYHOST, 0) logging.debug("URL: " + url) try: conn.perform() result = output_buffer.getvalue().decode() return_code = conn.getinfo(pycurl.RESPONSE_CODE) logging.debug("RESULT [" + str(return_code) + \ "]: " + result) conn.close() if return_code == 200: return json.loads(result) except pycurl.error: logging.error("Connection failed") except: if result is not None: logging.error(result) logging.error("Cannot parse json") return None def main(): atexit.register(atexit_handler) all_opt["tlscert"] = { "getopt" : ":", "longopt" : "tlscert", "help" : "--tlscert " "Path to client certificate for TLS authentication", "required" : "0", "shortdesc" : "Path to client certificate (PEM format) \ for TLS authentication. Required if --ssl option is used.", "order": 2 } all_opt["tlskey"] = { "getopt" : ":", "longopt" : "tlskey", "help" : "--tlskey " "Path to client key for TLS authentication", "required" : "0", "shortdesc" : "Path to client key (PEM format) for TLS \ authentication. Required if --ssl option is used.", "order": 2 } all_opt["tlscacert"] = { "getopt" : ":", "longopt" : "tlscacert", "help" : "--tlscacert " "Path to CA certificate for TLS authentication", "required" : "0", "shortdesc" : "Path to CA certificate (PEM format) for \ TLS authentication. Required if --ssl option is used.", "order": 2 } all_opt["api_version"] = { "getopt" : ":", "longopt" : "api-version", "help" : "--api-version " "Version of Docker Remote API (default: 1.11)", "required" : "0", "order" : 2, "default" : "1.11", } device_opt = ["ipaddr", "no_password", "no_login", "port", "method", "web", "tlscert", "tlskey", "tlscacert", "ssl", "api_version"] all_opt["ssl"]["default"] = "1" options = check_input(device_opt, process_input(device_opt)) docs = { } docs["shortdesc"] = "Fence agent for Docker" - docs["longdesc"] = "fence_docker is I/O fencing agent which \ + docs["longdesc"] = "fence_docker is a Power Fencing agent which \ can be used with the Docker Engine containers. You can use this \ fence-agent without any authentication, or you can use TLS authentication \ (use --ssl option, more info about TLS authentication in docker: \ http://docs.docker.com/examples/https/)." docs["vendorurl"] = "www.docker.io" show_docs(options, docs) run_delay(options) result = fence_action(None, options, set_power_status, get_power_status, get_list, reboot_cycle) sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/drac/fence_drac.py b/agents/drac/fence_drac.py index be3e9a58..b7d33564 100644 --- a/agents/drac/fence_drac.py +++ b/agents/drac/fence_drac.py @@ -1,62 +1,62 @@ #!@PYTHON@ -tt import sys, re import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * def get_power_status(conn, options): conn.send_eol("getmodinfo") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) status = re.compile(r"\s+(on|off)\s+", re.IGNORECASE).search(conn.before).group(1) return status.lower().strip() def set_power_status(conn, options): action = { 'on' : "powerup", 'off': "powerdown" }[options["--action"]] conn.send_eol("serveraction -d 0 " + action) conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) def main(): device_opt = ["ipaddr", "login", "passwd", "cmd_prompt", "telnet"] atexit.register(atexit_handler) opt = process_input(device_opt) if "--username" in opt: all_opt["cmd_prompt"]["default"] = ["\\[" + opt["--username"] + "\\]# "] else: all_opt["cmd_prompt"]["default"] = ["\\[" "username" + "\\]# "] options = check_input(device_opt, opt) docs = {} - docs["shortdesc"] = "I/O Fencing agent for Dell DRAC IV" - docs["longdesc"] = "fence_drac is an I/O Fencing agent which can be used with \ + docs["shortdesc"] = "Power Fencing agent for Dell DRAC IV" + docs["longdesc"] = "fence_drac is a Power Fencing agent which can be used with \ the Dell Remote Access Card (DRAC). This card provides remote access to controlling \ power to a server. It logs into the DRAC through the telnet interface of the card. By \ default, the telnet interface is not enabled. To enable the interface, you will need \ to use the racadm command in the racser-devel rpm available from Dell. \ \ To enable telnet on the DRAC: \ \ [root]# racadm config -g cfgSerial -o cfgSerialTelnetEnable 1 \ \ [root]# racadm racreset \ " docs["vendorurl"] = "http://www.dell.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/drac5/fence_drac5.py b/agents/drac5/fence_drac5.py index 648ecd91..7b279217 100644 --- a/agents/drac5/fence_drac5.py +++ b/agents/drac5/fence_drac5.py @@ -1,147 +1,147 @@ #!@PYTHON@ -tt ##### ## ## The Following Agent Has Been Tested On: ## ## DRAC Version Firmware ## +-----------------+---------------------------+ ## DRAC 5 1.0 (Build 06.05.12) ## DRAC 5 1.21 (Build 07.05.04) ## ## @note: drac_version was removed ##### import sys, re, time import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail_usage def get_power_status(conn, options): if options["--drac-version"] == "DRAC MC": (_, status) = get_list_devices(conn, options)[options["--plug"]] else: if options["--drac-version"] == "DRAC CMC": conn.send_eol("racadm serveraction powerstatus -m " + options["--plug"]) elif options["--drac-version"] == "DRAC 5": conn.send_eol("racadm serveraction powerstatus") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) status = re.compile(r"(^|: )(ON|OFF|Powering ON|Powering OFF)\s*$", re.IGNORECASE | re.MULTILINE).search(conn.before).group(2) if status.lower().strip() in ["on", "powering on", "powering off"]: return "on" else: return "off" def set_power_status(conn, options): action = { 'on' : "powerup", 'off': "powerdown" }[options["--action"]] if options["--drac-version"] == "DRAC CMC": conn.send_eol("racadm serveraction " + action + " -m " + options["--plug"]) elif options["--drac-version"] == "DRAC 5": conn.send_eol("racadm serveraction " + action) elif options["--drac-version"] == "DRAC MC": conn.send_eol("racadm serveraction -s " + options["--plug"] + " " + action) ## Fix issue with double-enter [CR/LF] ## We need to read two additional command prompts (one from get + one from set command) conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) if len(conn.before.strip()) == 0: options["eol"] = options["eol"][:-1] conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) def get_list_devices(conn, options): outlets = {} if options["--drac-version"] == "DRAC CMC": conn.send_eol("getmodinfo") list_re = re.compile(r"^([^\s]*?)\s+Present\s*(ON|OFF)\s*.*$") conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) for line in conn.before.splitlines(): if list_re.search(line): outlets[list_re.search(line).group(1)] = ("", list_re.search(line).group(2)) elif options["--drac-version"] == "DRAC MC": conn.send_eol("getmodinfo") list_re = re.compile(r"^\s*([^\s]*)\s*---->\s*(.*?)\s+Present\s*(ON|OFF)\s*.*$") conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) for line in conn.before.splitlines(): if list_re.search(line): outlets[list_re.search(line).group(2)] = ("", list_re.search(line).group(3)) elif options["--drac-version"] == "DRAC 5": ## DRAC 5 can be used only for one computer ## standard fence library can't handle correctly situation ## when some fence devices supported by fence agent ## works with 'list' and other should returns 'N/A' print("N/A") return outlets def define_new_opts(): all_opt["drac_version"] = { "getopt" : "d:", "longopt" : "drac-version", "help" : "-d, --drac-version=[version] Force DRAC version to use (DRAC 5|DRAC CMC|DRAC MC)", "required" : "0", "shortdesc" : "Force DRAC version to use", "choices" : ["DRAC CMC", "DRAC MC", "DRAC 5"], "order" : 1} def main(): device_opt = ["ipaddr", "login", "passwd", "cmd_prompt", "secure", \ "drac_version", "port", "no_port", "telnet"] atexit.register(atexit_handler) define_new_opts() all_opt["cmd_prompt"]["default"] = [r"\$", r"DRAC\/MC:"] options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for Dell DRAC CMC/5" - docs["longdesc"] = "fence_drac5 is an I/O Fencing agent \ + docs["longdesc"] = "fence_drac5 is a Power Fencing agent \ which can be used with the Dell Remote Access Card v5 or CMC (DRAC). \ This device provides remote access to controlling power to a server. \ It logs into the DRAC through the telnet/ssh interface of the card. \ By default, the telnet interface is not enabled." docs["vendorurl"] = "http://www.dell.com" show_docs(options, docs) ## ## Operate the fencing device ###### conn = fence_login(options) if "--drac-version" not in options: ## autodetect from text issued by fence device if conn.before.find("CMC") >= 0: options["--drac-version"] = "DRAC CMC" elif conn.before.find("DRAC 5") >= 0: options["--drac-version"] = "DRAC 5" elif conn.after.find("DRAC/MC") >= 0: options["--drac-version"] = "DRAC MC" else: ## Assume this is DRAC 5 by default as we don't want to break anything options["--drac-version"] = "DRAC 5" if options["--drac-version"] in ["DRAC MC", "DRAC CMC"]: if "--plug" not in options and 0 == ["monitor", "list"].count(options["--action"]): fail_usage("Failed: You have to enter module name (-n)") result = fence_action(conn, options, set_power_status, get_power_status, get_list_devices) fence_logout(conn, "exit", 1) sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/eaton_snmp/fence_eaton_snmp.py b/agents/eaton_snmp/fence_eaton_snmp.py index 9fbc0563..83ec92a2 100644 --- a/agents/eaton_snmp/fence_eaton_snmp.py +++ b/agents/eaton_snmp/fence_eaton_snmp.py @@ -1,229 +1,229 @@ #!@PYTHON@ -tt # The Following agent has been tested on: # - Eaton ePDU Managed - SNMP v1 # EATON | Powerware ePDU model: Managed ePDU (PW104MA0UB99), firmware: 01.01.01 # - Eaton ePDU Switched - SNMP v1 # EATON | Powerware ePDU model: Switched ePDU (IPV3600), firmware: 2.0.K import sys import atexit import logging sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail_usage from fencing_snmp import FencingSnmp ### CONSTANTS ### # oid defining fence device OID_SYS_OBJECT_ID = '.1.3.6.1.2.1.1.2.0' ### GLOBAL VARIABLES ### # Device - see EatonManagedePDU, EatonSwitchedePDU device = None # Port ID port_id = None # Switch ID switch_id = None # Did we issue a set before get (to adjust OID with Switched ePDU) after_set = False # Classes describing Device params # Managed ePDU class EatonManagedePDU(object): status_oid = '.1.3.6.1.4.1.534.6.6.6.1.2.2.1.3.%d' control_oid = '.1.3.6.1.4.1.534.6.6.6.1.2.2.1.3.%d' outlet_table_oid = '.1.3.6.1.4.1.534.6.6.6.1.2.2.1.1' ident_str = "Eaton Managed ePDU" state_off = 0 state_on = 1 state_cycling = 2 # FIXME: not usable with fence-agents turn_off = 0 turn_on = 1 turn_cycle = 2 # FIXME: not usable with fence-agents has_switches = False # Switched ePDU (Pulizzi 2) # NOTE: sysOID reports "20677.1", while data are actually at "20677.2" class EatonSwitchedePDU(object): status_oid = '.1.3.6.1.4.1.20677.2.6.3.%d.0' control_oid = '.1.3.6.1.4.1.20677.2.6.2.%d.0' outlet_table_oid = '.1.3.6.1.4.1.20677.2.6.3' ident_str = "Eaton Switched ePDU" state_off = 2 state_on = 1 state_cycling = 0 # Note: this status doesn't exist on this device turn_off = 2 turn_on = 1 turn_cycle = 3 # FIXME: not usable with fence-agents has_switches = False ### FUNCTIONS ### def eaton_set_device(conn): global device agents_dir = {'.1.3.6.1.4.1.534.6.6.6':EatonManagedePDU, '.1.3.6.1.4.1.20677.1':EatonSwitchedePDU, '.1.3.6.1.4.1.20677.2':EatonSwitchedePDU} # First resolve type of Eaton eaton_type = conn.walk(OID_SYS_OBJECT_ID) if not ((len(eaton_type) == 1) and (eaton_type[0][1] in agents_dir)): eaton_type = [[None, None]] device = agents_dir[eaton_type[0][1]] logging.debug("Trying %s"%(device.ident_str)) def eaton_resolv_port_id(conn, options): global port_id, switch_id if device == None: eaton_set_device(conn) # Restore the increment, that was removed in main for ePDU Managed if device.ident_str == "Eaton Switched ePDU": options["--plug"] = str(int(options["--plug"]) + 1) # Now we resolv port_id/switch_id if options["--plug"].isdigit() and ((not device.has_switches) or (options["--switch"].isdigit())): port_id = int(options["--plug"]) if device.has_switches: switch_id = int(options["--switch"]) else: table = conn.walk(device.outlet_table_oid, 30) for x in table: if x[1].strip('"') == options["--plug"]: t = x[0].split('.') if device.has_switches: port_id = int(t[len(t)-1]) switch_id = int(t[len(t)-3]) else: if device.ident_str == "Eaton Switched ePDU": port_id = int(t[len(t)-3]) else: port_id = int(t[len(t)-1]) if port_id == None: # Restore index offset, to provide a valid error output on Managed ePDU if device.ident_str != "Eaton Switched ePDU": options["--plug"] = str(int(options["--plug"]) + 1) fail_usage("Can't find port with name %s!"%(options["--plug"])) def get_power_status(conn, options): global port_id, after_set if port_id == None: eaton_resolv_port_id(conn, options) # Ajust OID for Switched ePDU when the get is after a set if after_set and device.ident_str == "Eaton Switched ePDU": port_id -= 1 after_set = False oid = ((device.has_switches) and device.status_oid%(switch_id, port_id) or device.status_oid%(port_id)) try: (oid, status) = conn.get(oid) if status == str(device.state_on): return "on" elif status == str(device.state_off): return "off" else: return None except Exception: return None def set_power_status(conn, options): global port_id, after_set after_set = True if port_id == None: eaton_resolv_port_id(conn, options) # Controls start at #2 on Switched ePDU, since #1 is the global command if device.ident_str == "Eaton Switched ePDU": port_id = int(port_id)+1 oid = ((device.has_switches) and device.control_oid%(switch_id, port_id) or device.control_oid%(port_id)) conn.set(oid, (options["--action"] == "on" and device.turn_on or device.turn_off)) def get_outlets_status(conn, options): outletCount = 0 result = {} if device == None: eaton_set_device(conn) res_ports = conn.walk(device.outlet_table_oid, 30) for x in res_ports: outletCount += 1 status = x[1] t = x[0].split('.') # Plug indexing start from zero, so we substract '1' from the # user's given plug number if device.ident_str == "Eaton Managed ePDU": port_num = str(int(((device.has_switches) and "%s:%s"%(t[len(t)-3], t[len(t)-1]) or "%s"%(t[len(t)-1]))) + 1) # Plug indexing start from zero, so we add '1' # for the user's exposed plug number port_name = str(int(x[1].strip('"')) + 1) port_status = "" result[port_num] = (port_name, port_status) else: # Switched ePDU do not propose an outletCount OID! # Invalid status (ie value == '0'), retrieved via the walk, # means the outlet is absent port_num = str(outletCount) port_name = str(outletCount) port_status = "" if status != '0': result[port_num] = (port_name, port_status) return result # Main agent method def main(): device_opt = ["ipaddr", "login", "passwd", "no_login", "no_password", \ "port", "snmp_version", "snmp"] atexit.register(atexit_handler) all_opt["switch"]["default"] = 1 all_opt["power_wait"]["default"] = 2 all_opt["snmp_version"]["default"] = "1" all_opt["community"]["default"] = "private" options = check_input(device_opt, process_input(device_opt)) # Plug indexing start from zero on ePDU Managed, so we substract '1' from # the user's given plug number. # For Switched ePDU, we will add this back again later. if "--plug" in options and options["--plug"].isdigit(): options["--plug"] = str(int(options["--plug"]) - 1) docs = {} docs["shortdesc"] = "Fence agent for Eaton over SNMP" - docs["longdesc"] = "fence_eaton_snmp is an I/O Fencing agent \ + docs["longdesc"] = "fence_eaton_snmp is a Power Fencing agent \ which can be used with the Eaton network power switch. It logs \ into a device via SNMP and reboots a specified outlet. It supports \ SNMP v1 and v3 with all combinations of authenticity/privacy settings." docs["vendorurl"] = "http://powerquality.eaton.com" show_docs(options, docs) # Operate the fencing device result = fence_action(FencingSnmp(options), options, set_power_status, get_power_status, get_outlets_status) sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/eaton_ssh/fence_eaton_ssh.py b/agents/eaton_ssh/fence_eaton_ssh.py index 8e536a2e..91dfbce2 100644 --- a/agents/eaton_ssh/fence_eaton_ssh.py +++ b/agents/eaton_ssh/fence_eaton_ssh.py @@ -1,318 +1,318 @@ #!@PYTHON@ -tt """ Plug numbering starts with 1! There were no tests performed so far with daisy chained PDUs. Example usage: fence_eaton_ssh -v -a -l -p --login-timeout=60 --action status --plug 1 """ ##### ## ## The Following Agent Has Been Tested On: ## ## Model Firmware ## +---------------------------------------------+ ## EMAB04 04.02.0001 ##### import enum import sys import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, EC_STATUS, EC_LOGIN_DENIED class FenceEatonPowerActions(enum.Enum): """ Status of the plug on the PDU. """ ERROR = -1 OFF = 0 ON = 1 PENDING_OFF = 2 PENDING_ON = 3 def get_plug_names(conn, plug_ids, command_prompt, shell_timout): """ Get the names of plugs via their ID. :param conn: The "fspawn" object. :param plug_ids: The list of plug IDs. Plugs start with the ID 1. :param command_prompt: The characters that make up the base prompt. This is important to detect a finished command. :param shell_timeout: The maximum time the shell should wait for a response. :returns: The name of the requested plugs. """ # fspawn is subclassed from pexpect which is not correctly type annotated in all cases. result = {} full_node_mapping = {} conn.send_eol("get PDU.OutletSystem.Outlet[x].iName") conn.log_expect(command_prompt, shell_timout) result_plug_names = conn.before.split("\n") # type: ignore if len(result_plug_names) != 3: fail(EC_STATUS) plug_names = result_plug_names.split("|") for counter in range(1, len(plug_names)): full_node_mapping[counter] = plug_names[counter] for plug_id in plug_ids: result[plug_id] = full_node_mapping[plug_id] return result def get_plug_ids(conn, nodenames, command_prompt, shell_timout): """ Get the IDs that map to the given nodenames. Non existing names are skipped. :param conn: The "fspawn" object. :param nodenames: The list of human readable names that should be converted to IDs. :param command_prompt: The characters that make up the base prompt. This is important to detect a finished command. :param shell_timeout: The maximum time the shell should wait for a response. :returns: A dictionary - possibly empty - where the keys are the node names and the values are the node IDs. """ result = {} full_node_mapping = {} conn.send_eol("get PDU.OutletSystem.Outlet[x].iName") conn.log_expect(command_prompt, shell_timout) result_plug_names = conn.before.split("\n") # type: ignore if len(result_plug_names) != 3: fail(EC_STATUS) plug_names = result_plug_names.split("|") for counter in range(1, len(plug_names)): full_node_mapping[plug_names[counter]] = counter for node in nodenames: if node in full_node_mapping: result[node] = full_node_mapping[node] return result def get_plug_count(conn, command_prompt, shell_timout): """ Get the number of plugs that the PDU has. In case the PDU is daisy chained this also contains the plugs of the other PDUs. :param conn: The "fspawn" object. :param command_prompt: The characters that make up the base prompt. This is important to detect a finished command. :param shell_timeout: The maximum time the shell should wait for a response. :returns: The number of plugs that the PDU has. """ # fspawn is subclassed from pexpect which is not correctly type annotated in all cases. conn.send_eol("get PDU.OutletSystem.Outlet.Count") conn.log_expect(command_prompt, shell_timout) result_plug_count = conn.before.split("\n") # type: ignore if len(result_plug_count) != 3: fail(EC_STATUS) return int(result_plug_count[1].strip()) def get_plug_status(conn, plug_id, command_prompt, shell_timout): """ Get the current status of the plug. The return value of this doesn't account for operations that will act via schedules or a delay. As such the status is only valid at the time of retrieval. :param conn: The "fspawn" object. :param plug_id: The ID of the plug that should be powered off. Counting plugs starts at 1. :returns: The current status of the plug. """ # fspawn is subclassed from pexpect which is not correctly type annotated in all cases. conn.send_eol(f"get PDU.OutletSystem.Outlet[{plug_id}].PresentStatus.SwitchOnOff") conn.log_expect(command_prompt, shell_timout) result_plug_status = conn.before.split("\n") # type: ignore if len(result_plug_status) != 3: fail(EC_STATUS) if result_plug_status[1].strip() == "0": return FenceEatonPowerActions.OFF elif result_plug_status[1].strip() == "1": return FenceEatonPowerActions.ON else: return FenceEatonPowerActions.ERROR def power_on_plug(conn, plug_id, command_prompt, shell_timout, delay=0): """ Powers on a plug with an optional delay. :param conn: The "fspawn" object. :param plug_id: The ID of the plug that should be powered off. Counting plugs starts at 1. :param command_prompt: The characters that make up the base prompt. This is important to detect a finished command. :param shell_timeout: The maximum time the shell should wait for a response. :param delay: The delay in seconds. Passing "-1" aborts the power off action. """ conn.send_eol(f"set PDU.OutletSystem.Outlet[{plug_id}].DelayBeforeStartup {delay}") conn.log_expect(command_prompt, shell_timout) def power_off_plug(conn, plug_id, command_prompt, shell_timout, delay=0): """ Powers off a plug with an optional delay. :param conn: The "fspawn" object. :param plug_id: The ID of the plug that should be powered off. Counting plugs starts at 1. :param command_prompt: The characters that make up the base prompt. This is important to detect a finished command. :param shell_timeout: The maximum time the shell should wait for a response. :param delay: The delay in seconds. Passing "-1" aborts the power off action. """ conn.send_eol(f"set PDU.OutletSystem.Outlet[{plug_id}].DelayBeforeShutdown {delay}") conn.log_expect(command_prompt, shell_timout) def get_power_status(conn, options): """ Retrieve the power status for the requested plug. Since we have a serial like interface via SSH we need to parse the output of the SSH session manually. If abnormal behavior is detected the method will exit via "fail()". :param conn: The "fspawn" object. :param options: The option dictionary. :returns: In case there is an error this method does not return but instead calls "sys.exit". Otherwhise one of "off", "on" or "error" is returned. """ if conn is None: fail(EC_LOGIN_DENIED) requested_plug = options.get("--plug", "") if not requested_plug: fail(EC_STATUS) plug_status = get_plug_status( conn, # type: ignore int(requested_plug), options["--command-prompt"], int(options["--shell-timeout"]) ) if plug_status == FenceEatonPowerActions.OFF: return "off" elif plug_status == FenceEatonPowerActions.ON: return "on" else: return "error" def set_power_status(conn, options): """ Set the power status for the requested plug. Only resposible for powering on and off. If abnormal behavior is detected the method will exit via "fail()". :param conn: The "fspawn" object. :param options: The option dictionary. :returns: In case there is an error this method does not return but instead calls "sys.exit". """ if conn is None: fail(EC_LOGIN_DENIED) requested_plug = options.get("--plug", "") if not requested_plug: fail(EC_STATUS) requested_action = options.get("--action", "") if not requested_action: fail(EC_STATUS) if requested_action == "off": power_off_plug( conn, # type: ignore int(requested_plug), options["--command-prompt"], int(options["--shell-timeout"]) ) elif requested_action == "on": power_on_plug( conn, # type: ignore int(requested_plug), options["--command-prompt"], int(options["--shell-timeout"]) ) else: fail(EC_STATUS) def get_outlet_list(conn, options): """ Retrieves the list of plugs with their correspondin status. :param conn: The "fspawn" object. :param options: The option dictionary. :returns: Keys are the Plug IDs which each have a Tuple with the alias for the plug and its status. """ if conn is None: fail(EC_LOGIN_DENIED) result = {} plug_count = get_plug_count(conn, options["--command-prompt"], int(options["--shell-timeout"])) # type: ignore for counter in range(1, plug_count): plug_names = get_plug_names( conn, # type: ignore [counter], options["--command-prompt"], int(options["--shell-timeout"]) ) plug_status_enum = get_plug_status( conn, # type: ignore counter, options["--command-prompt"], int(options["--shell-timeout"]) ) if plug_status_enum == FenceEatonPowerActions.OFF: plug_status = "OFF" elif plug_status_enum == FenceEatonPowerActions.ON: plug_status = "ON" else: plug_status = None result[str(counter)] = (plug_names[counter], plug_status) return result def reboot_cycle(conn, options) -> None: """ Responsible for power cycling a machine. Not responsible for singular on and off actions. :param conn: The "fspawn" object. :param options: The option dictionary. """ requested_plug = options.get("--plug", "") if not requested_plug: fail(EC_STATUS) power_off_plug( conn, # type: ignore int(requested_plug), options["--command-prompt"], int(options["--shell-timeout"]) ) power_on_plug( conn, # type: ignore int(requested_plug), options["--command-prompt"], int(options["--shell-timeout"]) ) def main(): """ Main entrypoint for the fence_agent. """ device_opt = ["secure", "ipaddr", "login", "passwd", "port", "cmd_prompt"] atexit.register(atexit_handler) options = check_input(device_opt, process_input(device_opt)) options["--ssh"] = None options["--ipport"] = 22 options["--command-prompt"] = "pdu#0>" docs = {} docs["shortdesc"] = "Fence agent for Eaton ePDU G3 over SSH" - docs["longdesc"] = "fence_eaton_ssh is a fence agent that connects to Eaton ePDU devices. It logs into \ + docs["longdesc"] = "fence_eaton_ssh is a Power Fencing agent that connects to Eaton ePDU devices. It logs into \ device via ssh and reboot a specified outlet." docs["vendorurl"] = "https://www.eaton.com/" show_docs(options, docs) conn = fence_login(options) result = fence_action(conn, options, set_power_status, get_power_status, get_outlet_list, reboot_cycle) fence_logout(conn, "quit") sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/ecloud/fence_ecloud.py b/agents/ecloud/fence_ecloud.py index 0707e102..6c37494e 100644 --- a/agents/ecloud/fence_ecloud.py +++ b/agents/ecloud/fence_ecloud.py @@ -1,169 +1,169 @@ #!@PYTHON@ -tt # # Fence agent for eCloud and eCloud VPC # https://www.ans.co.uk/cloud-and-infrastructure/ecloud/ # # Copyright (c) 2022 ANS Group Limited # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library 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 # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see # . import sys import time import atexit import logging import requests sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import run_delay, fail_usage, fail, EC_TIMED_OUT API_BASE = "https://api.ukfast.io/ecloud" API_MONITOR = API_BASE + "/ping" API_VPC_INSTANCE_DATA = API_BASE + "/v2/instances/:ID" API_VPC_POWER_ON = API_BASE + "/v2/instances/:ID/power-on" API_VPC_POWER_OFF = API_BASE + "/v2/instances/:ID/power-off" API_V1_INSTANCE_DATA = API_BASE + "/v1/vms/:ID" API_V1_POWER_ON = API_BASE + "/v1/vms/:ID/power-on" API_V1_POWER_OFF = API_BASE + "/v1/vms/:ID/power-off" def set_power_fn(conn, options): logging.debug("setting power {}".format(options['--action'])) del conn action = options['--action'] vpc = options['--ecloud-vpc'] plug = options['--plug'] url = fence_url(vpc, action, plug) hdrs = headers(options['--apikey']) logging.info("executing '{}' action on '{}'".format(action, plug)) retries = 0 while True: resp = requests.put(url, headers=hdrs) if resp.status_code == 409: # If we attempt to power the instance back on too soon after powering it off, # e.g. during a reboot, the API will return a 409 because while the power status # has changed, the task is still executing. Retry the action until we exceed # retries or get a different status code. if retries >= 6: logging.error("timed out trying to execute '{}' action after repeated 409 codes from API", action) fail(EC_TIMED_OUT) time.sleep(2) retries += 1 continue if resp.status_code != 202: logging.error("unexpected status code '{}' from endpoint '{}': {}".format( resp.status_code, url, resp.text )) break def get_power_fn(conn, options): logging.debug("getting power state") del conn vpc = options['--ecloud-vpc'] plug = options['--plug'] url = instance_data_url(vpc, plug) hdrs = headers(options['--apikey']) resp = requests.get(url, headers=hdrs) if resp.status_code != 200: logging.error("unexpected status code ('{}') from endpoint '{}': {}".format( resp.status_code, url, resp.text )) return "bad status {}".format(resp.status_code) instance = resp.json()['data'] if vpc: logging.debug("power state return value: {}".format(instance['online'])) return "on" if instance['online'] else "off" else: if instance['power_status'] == "Online": return "on" elif instance['power_status'] == "Offline": return "off" else: # Could be 'Unknown' or other value return instance['power_status'] def headers(apikey): return { "Authorization": apikey, "User-Agent": "fence_ecloud" } def itp(url, plug): return url.replace(':ID', plug) def fence_url(vpc, action, plug): if action == "on": return itp(API_VPC_POWER_ON, plug) if vpc else itp(API_V1_POWER_ON, plug) if action == "off": return itp(API_VPC_POWER_OFF, plug) if vpc else itp(API_V1_POWER_OFF, plug) fail_usage("no available API configured for action '{}'".format(action)) def instance_data_url(vpc, plug): return itp(API_VPC_INSTANCE_DATA, plug) if vpc else itp(API_V1_INSTANCE_DATA, plug) def main(): device_opt = ["apikey", "port", "no_login", "no_password"] all_opt["apikey"] = { "getopt": ":", "longopt": "apikey", "help": "--apikey=[key] eCloud API Key", "required": "1", "shortdesc": "API Key", "order": 0, } all_opt["port"]["help"] = "-n, --plug=[instance] Instance ID (VPC) or server ID (v1)" atexit.register(atexit_handler) options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence Agent for ANS eCloud" - docs["longdesc"] = "fence_ecloud is a fence agent for use with the ANS \ + docs["longdesc"] = "fence_ecloud is a Power Fencing agent for use with the ANS \ eCloud platform which is compatible with eCloud VPC and eCloud v1." docs["vendorurl"] = "https://www.ans.co.uk" show_docs(options, docs) if options['--action'] in ['on', 'off', 'reboot', 'status']: plug = options['--plug'] options['--ecloud-vpc'] = True if not plug.startswith("i-"): options['--ecloud-vpc'] = False run_delay(options) fence_action(None, options, set_power_fn, get_power_fn) if __name__ == '__main__': main() diff --git a/agents/emerson/fence_emerson.py b/agents/emerson/fence_emerson.py index 2e65cda0..67b3a410 100644 --- a/agents/emerson/fence_emerson.py +++ b/agents/emerson/fence_emerson.py @@ -1,62 +1,62 @@ #!@PYTHON@ -tt import sys import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing_snmp import FencingSnmp ### CONSTANTS ### STATUSES_OID = ".1.3.6.1.4.1.476.1.42.3.8.50.20.1.95" CONTROL_OID = ".1.3.6.1.4.1.476.1.42.3.8.50.20.1.100" NAMES_OID = ".1.3.6.1.4.1.476.1.42.3.8.50.20.1.10" # Status constants returned as value from SNMP STATUS_DOWN = 1 STATUS_UP = 2 # Status constants to set as value to SNMP STATUS_SET_OFF = 0 STATUS_SET_ON = 1 def get_power_status(conn, options): (_, status) = conn.get("%s.%s"% (STATUSES_OID, options["--plug"])) return status == str(STATUS_UP) and "on" or "off" def set_power_status(conn, options): conn.set("%s.%s" % (CONTROL_OID, options["--plug"]), (options["--action"] == "on" and STATUS_SET_ON or STATUS_SET_OFF)) def get_outlets_status(conn, _): result = {} res_outlet = conn.walk(STATUSES_OID, 30) for outlet_info in res_outlet: port_num = ".".join(outlet_info[0].split('.')[-3:]) port_alias = conn.get("%s.%s"% (NAMES_OID, port_num))[1] port_status = (outlet_info[1] == str(STATUS_UP) and "on" or "off") result[port_num] = (port_alias, port_status) return result def main(): device_opt = ["ipaddr", "login", "passwd", "no_login", "no_password", \ "port", "snmp_version", "snmp"] atexit.register(atexit_handler) all_opt["power_wait"]["default"] = "5" options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for Emerson over SNMP" - docs["longdesc"] = "fence_emerson is an I/O Fencing agent \ - which can be used with MPX and MPH2 managed rack PDU." + docs["longdesc"] = "fence_emerson is a Power Fencing agent \ +which can be used with MPX and MPH2 managed rack PDU." docs["vendorurl"] = "http://www.emersonnetworkpower.com" show_docs(options, docs) # Operate the fencing device result = fence_action(FencingSnmp(options), options, set_power_status, get_power_status, get_outlets_status) sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/eps/fence_eps.py b/agents/eps/fence_eps.py index f0df8623..81e43953 100644 --- a/agents/eps/fence_eps.py +++ b/agents/eps/fence_eps.py @@ -1,129 +1,129 @@ #!@PYTHON@ -tt # The Following Agent Has Been Tested On: # ePowerSwitch 8M+ version 1.0.0.4 import sys, re import base64, string, socket import logging import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, fail_usage, EC_LOGIN_DENIED, EC_TIMED_OUT, run_delay if sys.version_info[0] > 2: import http.client as httplib else: import httplib # Run command on EPS device. # @param options Device options # @param params HTTP GET parameters (without ?) def eps_run_command(options, params): try: # New http connection conn = httplib.HTTPConnection(options["--ip"]) request_str = "/"+options["--page"] if params != "": request_str += "?"+params logging.debug("GET %s\n", request_str) conn.putrequest('GET', request_str) if "--username" in options: if "--password" not in options: options["--password"] = "" # Default is empty password # String for Authorization header auth_str = 'Basic ' + string.strip(base64.encodestring(options["--username"]+':'+options["--password"])) logging.debug("Authorization: %s\n", auth_str) conn.putheader('Authorization', auth_str) conn.endheaders() response = conn.getresponse() logging.debug("%d %s\n", response.status, response.reason) #Response != OK -> couldn't login if response.status != 200: fail(EC_LOGIN_DENIED) result = response.read() logging.debug("%s \n", result) conn.close() except socket.timeout: fail(EC_TIMED_OUT) except socket.error as e: logging.error("Failed: {}".format(str(e))) fail(EC_LOGIN_DENIED) return result def get_power_status(conn, options): del conn ret_val = eps_run_command(options, "") result = {} status = re.findall(r"p(\d{2})=(0|1)\s*\", ret_val.lower()) for out_num, out_stat in status: result[out_num] = ("", (out_stat == "1" and "on" or "off")) if not options["--action"] in ['monitor', 'list']: if not options["--plug"] in result: fail_usage("Failed: You have to enter existing physical plug!") else: return result[options["--plug"]][1] else: return result def set_power_status(conn, options): del conn eps_run_command(options, "P%s=%s"%(options["--plug"], (options["--action"] == "on" and "1" or "0"))) # Define new option def eps_define_new_opts(): all_opt["hidden_page"] = { "getopt" : "c:", "longopt" : "page", "help":"-c, --page=[page] Name of hidden page (default: hidden.htm)", "required" : "0", "shortdesc" : "Name of hidden page", "default" : "hidden.htm", "order": 1 } # Starting point of fence agent def main(): device_opt = ["ipaddr", "login", "passwd", "no_login", "no_password", \ "port", "hidden_page", "web"] atexit.register(atexit_handler) eps_define_new_opts() options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for ePowerSwitch" - docs["longdesc"] = "fence_eps is an I/O Fencing agent \ + docs["longdesc"] = "fence_eps is a Power Fencing agent \ which can be used with the ePowerSwitch 8M+ power switch to fence \ connected machines. Fence agent works ONLY on 8M+ device, because \ this is only one, which has support for hidden page feature. \ \n.TP\n\ Agent basically works by connecting to hidden page and pass \ appropriate arguments to GET request. This means, that hidden \ page feature must be enabled and properly configured." docs["vendorurl"] = "http://www.epowerswitch.com" show_docs(options, docs) run_delay(options) #Run fence action. Conn is None, beacause we always need open new http connection result = fence_action(None, options, set_power_status, get_power_status, get_power_status) sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/gce/fence_gce.py b/agents/gce/fence_gce.py index 2c815b84..b8871038 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" }) 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) 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 an I/O Fencing agent for GCE (Google Cloud " \ + 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/hds_cb/fence_hds_cb.py b/agents/hds_cb/fence_hds_cb.py index 375054cf..1a064644 100755 --- a/agents/hds_cb/fence_hds_cb.py +++ b/agents/hds_cb/fence_hds_cb.py @@ -1,132 +1,132 @@ #!@PYTHON@ -tt ##### ## ## The Following Agent Has Been Tested On: ## ## Model Modle/Firmware ## +--------------------+---------------------------+ ## (1) Main application CB2000/A0300-E-6617 ## ##### import sys, re import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * RE_STATUS_LINE = r"^([0-9]+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+).*$" def get_power_status(conn, options): #### Maybe should put a conn.log_expect here to make sure #### we have properly entered into the main menu conn.sendline("S") # Enter System Command Mode conn.log_expect("SVP>", int(options["--shell-timeout"])) conn.sendline("PC") # Enter partition control conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) result = {} # Status can now be obtained from the output of the PC # command. Line looks like the following: # "P Power Condition LID lamp Mode Auto power on" # "0 On Normal Off Basic Synchronized" # "1 On Normal Off Basic Synchronized" for line in conn.before.splitlines(): # populate the relevant fields based on regex partition = re.search(RE_STATUS_LINE, line) if partition != None: # find the blade number defined in args if partition.group(1) == options["--plug"]: result = partition.group(2).lower() # We must make sure we go back to the main menu as the # status is checked before any fencing operations are # executed. We could in theory save some time by staying in # the partition control, but the logic is a little cleaner # this way. conn.sendline("Q") # Back to system command mode conn.log_expect("SVP>", int(options["--shell-timeout"])) conn.sendline("EX") # Back to system console main menu conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) return result def set_power_status(conn, options): action = { 'on' : "P", 'off': "F", 'reboot' : "H", }[options["--action"]] conn.sendline("S") # Enter System Command Mode conn.log_expect("SVP>", int(options["--shell-timeout"])) conn.sendline("PC") # Enter partition control conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) conn.sendline("P") # Enter power control menu conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) conn.sendline(action) # Execute action from array above conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) conn.sendline(options["--plug"]) # Select blade number from args conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) conn.sendline("Y") # Confirm action conn.log_expect("Hit enter key.", int(options["--shell-timeout"])) conn.sendline("") # Press the any key conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) conn.sendline("Q") # Quit back to partition control conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) conn.sendline("Q") # Quit back to system command mode conn.log_expect("SVP>", int(options["--shell-timeout"])) conn.sendline("EX") # Quit back to system console menu conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) def get_blades_list(conn, options): outlets = {} conn.sendline("S") # Enter System Command Mode conn.log_expect("SVP>", int(options["--shell-timeout"])) conn.sendline("PC") # Enter partition control conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) # Status can now be obtained from the output of the PC # command. Line looks like the following: # "P Power Condition LID lamp Mode Auto power on" # "0 On Normal Off Basic Synchronized" # "1 On Normal Off Basic Synchronized" for line in conn.before.splitlines(): partition = re.search(RE_STATUS_LINE, line) if partition != None: outlets[partition.group(1)] = (partition.group(2), "") conn.sendline("Q") # Quit back to system command mode conn.log_expect("SVP>", int(options["--shell-timeout"])) conn.sendline("EX") # Quit back to system console menu conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) return outlets def main(): device_opt = ["ipaddr", "login", "passwd", "cmd_prompt", "secure", \ "port", "missing_as_off", "telnet"] atexit.register(atexit_handler) all_opt["power_wait"]["default"] = "5" all_opt["cmd_prompt"]["default"] = [r"\) :"] options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for Hitachi Compute Blade systems" - docs["longdesc"] = "fence_hds_cb is an I/O Fencing agent \ + docs["longdesc"] = "fence_hds_cb is a Power Fencing agent \ which can be used with Hitachi Compute Blades with recent enough firmware that \ includes telnet support." docs["vendorurl"] = "http://www.hds.com" show_docs(options, docs) ## ## Operate the fencing device ###### conn = fence_login(options) result = fence_action(conn, options, set_power_status, get_power_status, get_blades_list) fence_logout(conn, "X") sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/hpblade/fence_hpblade.py b/agents/hpblade/fence_hpblade.py index fbc89f61..eb8f459b 100644 --- a/agents/hpblade/fence_hpblade.py +++ b/agents/hpblade/fence_hpblade.py @@ -1,134 +1,134 @@ #!@PYTHON@ -tt ##### ## ## The Following Agent Has Been Tested On: ## * HP BladeSystem c7000 Enclosure ## * HP Integrity Superdome X (BL920s) ##### import sys, re import pexpect import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, EC_STATUS def get_enclosure_type(conn, options): conn.send_eol("show enclosure info") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) type_re=re.compile(r"^\s*Enclosure Type: (\w+)(.*?)\s*$") enclosure="unknown" for line in conn.before.splitlines(): res = type_re.search(line) if res != None: enclosure=res.group(1) if enclosure == "unknown": fail(EC_GENERIC_ERROR) return enclosure.lower().strip() def get_power_status(conn, options): if options["enc_type"] == "superdome": cmd_send = "parstatus -M -p " + options["--plug"] powrestr = "^partition:\\d\\s+:\\w+\\s+/(\\w+)\\s.*$" else: cmd_send = "show server status " + options["--plug"] powrestr = "^\\s*Power: (.*?)\\s*$" conn.send_eol(cmd_send) conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) power_re = re.compile(powrestr) status = "unknown" for line in conn.before.splitlines(): res = power_re.search(line) if res != None: if options["enc_type"] == "superdome": if res.group(1) == "DOWN": status = "off" else: status = "on" else: status = res.group(1) if status == "unknown": if "--missing-as-off" in options: return "off" else: fail(EC_STATUS) return status.lower().strip() def set_power_status(conn, options): if options["enc_type"] == "superdome": dev="partition " else: dev="server " if options["--action"] == "on": conn.send_eol("poweron " + dev + options["--plug"]) elif options["--action"] == "off": conn.send_eol("poweroff " + dev + options["--plug"] + " force") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) def get_instances_list(conn, options): outlets = {} if options["enc_type"] == "superdome": cmd_send = "parstatus -P -M" listrestr = "^partition:(\\d+)\\s+:\\w+\\s+/(\\w+)\\s+:OK.*?:(\\w+)\\s*$" else: cmd_send = "show server list" listrestr = "^\\s*(\\d+)\\s+(.*?)\\s+(.*?)\\s+OK\\s+(.*?)\\s+(.*?)\\s*$" conn.send_eol(cmd_send) conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) list_re = re.compile(listrestr) for line in conn.before.splitlines(): res = list_re.search(line) if res != None: if options["enc_type"] == "superdome": outlets[res.group(1)] = (res.group(3), res.group(2).lower()) else: outlets[res.group(1)] = (res.group(2), res.group(4).lower()) return outlets def main(): device_opt = ["ipaddr", "login", "passwd", "cmd_prompt", "secure", \ "port", "missing_as_off", "telnet"] atexit.register(atexit_handler) all_opt["cmd_prompt"]["default"] = ["c7000oa>"] all_opt["login_timeout"]["default"] = "10" options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for HP BladeSystem" - docs["longdesc"] = "fence_hpblade is an I/O Fencing agent \ + docs["longdesc"] = "fence_hpblade is a Power Fencing agent \ which can be used with HP BladeSystem and HP Integrity Superdome X. \ It logs into the onboard administrator of an enclosure via telnet or \ ssh and uses the command line interface to power blades or partitions \ on or off." docs["vendorurl"] = "http://www.hp.com" show_docs(options, docs) ## ## Operate the fencing device ###### options["eol"] = "\n" conn = fence_login(options) options["enc_type"] = get_enclosure_type(conn, options) result = fence_action(conn, options, set_power_status, get_power_status, get_instances_list) fence_logout(conn, "exit") sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/ibm_powervs/fence_ibm_powervs.py b/agents/ibm_powervs/fence_ibm_powervs.py index e65462cb..73dfe0ab 100755 --- a/agents/ibm_powervs/fence_ibm_powervs.py +++ b/agents/ibm_powervs/fence_ibm_powervs.py @@ -1,299 +1,299 @@ #!@PYTHON@ -tt import sys import pycurl, io, json import logging import atexit import time sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, run_delay, EC_LOGIN_DENIED, EC_STATUS state = { "ACTIVE": "on", "SHUTOFF": "off", "HARD_REBOOT": "on", "SOFT_REBOOT": "on", "ERROR": "unknown" } def get_token(conn, options): try: command = "identity/token" action = "grant_type=urn%3Aibm%3Aparams%3Aoauth%3Agrant-type%3Aapikey&apikey={}".format(options["--token"]) res = send_command(conn, command, "POST", action, printResult=False) except Exception as e: logging.debug("Failed: {}".format(e)) return "TOKEN_IS_MISSING_OR_WRONG" return res["access_token"] def get_list(conn, options): outlets = {} try: command = "cloud-instances/{}/pvm-instances".format(options["--instance"]) res = send_command(conn, command) except Exception as e: logging.debug("Failed: {}".format(e)) return outlets for r in res["pvmInstances"]: if options["--verbose-level"] > 1: logging.debug(json.dumps(r, indent=2)) outlets[r["pvmInstanceID"]] = (r["serverName"], state[r["status"]]) return outlets def get_power_status(conn, options): outlets = {} logging.debug("Info: getting power status for LPAR " + options["--plug"] + " instance " + options["--instance"]) try: command = "cloud-instances/{}/pvm-instances/{}".format( options["--instance"], options["--plug"]) res = send_command(conn, command) outlets[res["pvmInstanceID"]] = (res["serverName"], state[res["status"]]) if options["--verbose-level"] > 1: logging.debug(json.dumps(res, indent=2)) result = outlets[options["--plug"]][1] logging.debug("Info: Status: {}".format(result)) except KeyError as e: try: result = get_list(conn, options)[options["--plug"]][1] except KeyError as ex: logging.debug("Failed: Unable to get status for {}".format(ex)) fail(EC_STATUS) return result def set_power_status(conn, options): action = { "on" : '{"action" : "start"}', "off" : '{"action" : "immediate-shutdown"}', }[options["--action"]] logging.debug("Info: set power status to " + options["--action"] + " for LPAR " + options["--plug"] + " instance " + options["--instance"]) try: send_command(conn, "cloud-instances/{}/pvm-instances/{}/action".format( options["--instance"], options["--plug"]), "POST", action) except Exception as e: logging.debug("Failed: Unable to set power to {} for {}".format(options["--action"], e)) fail(EC_STATUS) def reboot_cycle(conn, options): action = { "reboot" : '{"action" : "hard-reboot"}', }[options["--action"]] logging.debug("Info: start reboot cycle with action " + options["--action"] + " for LPAR " + options["--plug"] + " instance " + options["--instance"]) try: send_command(conn, "cloud-instances/{}/pvm-instances/{}/action".format( options["--instance"], options["--plug"]), "POST", action) except Exception as e: result = get_power_status(conn, options) logging.debug("Info: Status {}".format(result)) if result == "off": return True else: logging.debug("Failed: Unable to cycle with {} for {}".format(options["--action"], e)) fail(EC_STATUS) return True def connect(opt, token): conn = pycurl.Curl() ## setup correct URL conn.base_url = "https://" + opt["--region"] + ".power-iaas.cloud.ibm.com/pcloud/v1/" if opt["--api-type"] == "private": conn.base_url = "https://private." + opt["--region"] + ".power-iaas.cloud.ibm.com/pcloud/v1/" if opt["--verbose-level"] < 3: conn.setopt(pycurl.VERBOSE, 0) conn.setopt(pycurl.CONNECTTIMEOUT,int(opt["--shell-timeout"])) conn.setopt(pycurl.TIMEOUT, int(opt["--shell-timeout"])) conn.setopt(pycurl.SSL_VERIFYPEER, 1) conn.setopt(pycurl.SSL_VERIFYHOST, 2) conn.setopt(pycurl.PROXY, "{}".format(opt["--proxy"])) # set auth token for later requests conn.setopt(pycurl.HTTPHEADER, [ "Content-Type: application/json", "Authorization: Bearer {}".format(token), "CRN: {}".format(opt["--crn"]), "User-Agent: curl", ]) return conn def auth_connect(opt): conn = pycurl.Curl() # setup correct URL conn.base_url = "https://iam.cloud.ibm.com/" if opt["--verbose-level"] > 1: conn.setopt(pycurl.VERBOSE, 1) conn.setopt(pycurl.CONNECTTIMEOUT,int(opt["--shell-timeout"])) conn.setopt(pycurl.TIMEOUT, int(opt["--shell-timeout"])) conn.setopt(pycurl.SSL_VERIFYPEER, 1) conn.setopt(pycurl.SSL_VERIFYHOST, 2) conn.setopt(pycurl.PROXY, "{}".format(opt["--proxy"])) # set auth token for later requests conn.setopt(pycurl.HTTPHEADER, [ "Content-type: application/x-www-form-urlencoded", "Accept: application/json", "User-Agent: curl", ]) return conn def disconnect(conn): conn.close() def send_command(conn, command, method="GET", action=None, printResult=True): url = conn.base_url + command conn.setopt(pycurl.URL, url.encode("ascii")) web_buffer = io.BytesIO() if method == "GET": conn.setopt(pycurl.POST, 0) if method == "POST": conn.setopt(pycurl.POSTFIELDS, action) if method == "DELETE": conn.setopt(pycurl.CUSTOMREQUEST, "DELETE") conn.setopt(pycurl.WRITEFUNCTION, web_buffer.write) try: conn.perform() except Exception as e: logging.error("send_command(): {}".format(e)) raise(e) rc = conn.getinfo(pycurl.HTTP_CODE) result = web_buffer.getvalue().decode("UTF-8") web_buffer.close() if rc != 200: if len(result) > 0: raise Exception("{}: {}".format(rc,result)) else: raise Exception("Remote returned {} for request to {}".format(rc, url)) if len(result) > 0: result = json.loads(result) logging.debug("url: {}".format(url)) logging.debug("method: {}".format(method)) logging.debug("response code: {}".format(rc)) if printResult: logging.debug("result: {}\n".format(result)) return result def define_new_opts(): all_opt["token"] = { "getopt" : ":", "longopt" : "token", "help" : "--token=[token] API Token", "required" : "1", "shortdesc" : "API Token", "order" : 0 } all_opt["crn"] = { "getopt" : ":", "longopt" : "crn", "help" : "--crn=[crn] CRN", "required" : "1", "shortdesc" : "CRN", "order" : 0 } all_opt["instance"] = { "getopt" : ":", "longopt" : "instance", "help" : "--instance=[instance] PowerVS Instance", "required" : "1", "shortdesc" : "PowerVS Instance", "order" : 0 } all_opt["region"] = { "getopt" : ":", "longopt" : "region", "help" : "--region=[region] Region", "required" : "1", "shortdesc" : "Region", "order" : 0 } all_opt["api-type"] = { "getopt" : ":", "longopt" : "api-type", "help" : "--api-type=[public|private] API-type: 'public' (default) or 'private'", "required" : "0", "shortdesc" : "API-type (public|private)", "order" : 0 } all_opt["proxy"] = { "getopt" : ":", "longopt" : "proxy", "help" : "--proxy=[http://:] Proxy: 'http://:'", "required" : "0", "shortdesc" : "Network proxy", "order" : 0 } def main(): device_opt = [ "token", "crn", "instance", "region", "api-type", "proxy", "port", "no_password", "method", ] atexit.register(atexit_handler) define_new_opts() all_opt["shell_timeout"]["default"] = "500" all_opt["power_timeout"]["default"] = "30" all_opt["power_wait"]["default"] = "1" all_opt["stonith_status_sleep"]["default"] = "2" all_opt["api-type"]["default"] = "private" all_opt["proxy"]["default"] = "" options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for IBM PowerVS" - docs["longdesc"] = """fence_ibm_powervs is an I/O Fencing agent which can be \ + docs["longdesc"] = """fence_ibm_powervs is a Power Fencing agent which can be \ used with IBM PowerVS to fence virtual machines.""" docs["vendorurl"] = "https://www.ibm.com" show_docs(options, docs) #### ## Fence operations #### run_delay(options) auth_conn = auth_connect(options) token = get_token(auth_conn, options) disconnect(auth_conn) conn = connect(options, token) atexit.register(disconnect, conn) result = fence_action(conn, options, set_power_status, get_power_status, get_list, reboot_cycle) sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/ibm_vpc/fence_ibm_vpc.py b/agents/ibm_vpc/fence_ibm_vpc.py index 84701058..035a3235 100755 --- a/agents/ibm_vpc/fence_ibm_vpc.py +++ b/agents/ibm_vpc/fence_ibm_vpc.py @@ -1,316 +1,316 @@ #!@PYTHON@ -tt import sys import pycurl, io, json import logging import atexit import hashlib sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, run_delay, EC_LOGIN_DENIED, EC_STATUS, EC_GENERIC_ERROR state = { "running": "on", "stopped": "off", "starting": "unknown", "stopping": "unknown", "restarting": "unknown", "pending": "unknown", } def get_list(conn, options): outlets = {} try: command = "instances?version=2021-05-25&generation=2&limit={}".format(options["--limit"]) res = send_command(conn, options, command) except Exception as e: logging.debug("Failed: Unable to get list: {}".format(e)) return outlets for r in res["instances"]: if options["--verbose-level"] > 1: logging.debug("Node:\n{}".format(json.dumps(r, indent=2))) logging.debug("Status: " + state[r["status"]]) outlets[r["id"]] = (r["name"], state[r["status"]]) return outlets def get_power_status(conn, options): try: command = "instances/{}?version=2021-05-25&generation=2".format(options["--plug"]) res = send_command(conn, options, command) result = state[res["status"]] if options["--verbose-level"] > 1: logging.debug("Result:\n{}".format(json.dumps(res, indent=2))) logging.debug("Status: " + result) except Exception as e: logging.debug("Failed: Unable to get status for {}: {}".format(options["--plug"], e)) fail(EC_STATUS) return result def set_power_status(conn, options): action = { "on" : '{"type" : "start"}', "off" : '{"type" : "stop"}', }[options["--action"]] try: command = "instances/{}/actions?version=2021-05-25&generation=2".format(options["--plug"]) send_command(conn, options, command, "POST", action, 201) except Exception as e: logging.debug("Failed: Unable to set power to {} for {}".format(options["--action"], e)) fail(EC_STATUS) def get_bearer_token(conn, options): import os, errno try: # FIPS requires usedforsecurity=False and might not be # available on all distros: https://bugs.python.org/issue9216 hash = hashlib.sha256(options["--apikey"].encode("utf-8"), usedforsecurity=False).hexdigest() except (AttributeError, TypeError): hash = hashlib.sha256(options["--apikey"].encode("utf-8")).hexdigest() file_path = options["--token-file"].replace("[hash]", hash) token = None if not os.path.isdir(os.path.dirname(file_path)): os.makedirs(os.path.dirname(file_path)) # For security, remove file with potentially elevated mode try: os.remove(file_path) except OSError: pass try: oldumask = os.umask(0) file_handle = os.open(file_path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600) except OSError as e: if e.errno == errno.EEXIST: # Failed as the file already exists. logging.error("Failed: File already exists: {}".format(e)) sys.exit(EC_GENERIC_ERROR) else: # Something unexpected went wrong logging.error("Failed: Unable to open file: {}".format(e)) sys.exit(EC_GENERIC_ERROR) else: # No exception, so the file must have been created successfully. with os.fdopen(file_handle, 'w') as file_obj: try: conn.setopt(pycurl.HTTPHEADER, [ "Content-Type: application/x-www-form-urlencoded", "User-Agent: curl", ]) token = send_command(conn, options, "https://iam.cloud.ibm.com/identity/token", "POST", "grant_type=urn:ibm:params:oauth:grant-type:apikey&apikey={}".format(options["--apikey"]))["access_token"] except Exception as e: logging.error("Failed: Unable to authenticate: {}".format(e)) fail(EC_LOGIN_DENIED) file_obj.write(token) finally: os.umask(oldumask) return token def set_bearer_token(conn, bearer_token): conn.setopt(pycurl.HTTPHEADER, [ "Content-Type: application/json", "Authorization: Bearer {}".format(bearer_token), "User-Agent: curl", ]) return conn def connect(opt): conn = pycurl.Curl() bearer_token = "" ## setup correct URL conn.base_url = "https://" + opt["--region"] + ".iaas.cloud.ibm.com/v1/" if opt["--verbose-level"] > 1: conn.setopt(pycurl.VERBOSE, 1) conn.setopt(pycurl.TIMEOUT, int(opt["--shell-timeout"])) conn.setopt(pycurl.SSL_VERIFYPEER, 1) conn.setopt(pycurl.SSL_VERIFYHOST, 2) conn.setopt(pycurl.PROXY, "{}".format(opt["--proxy"])) # get bearer token try: try: # FIPS requires usedforsecurity=False and might not be # available on all distros: https://bugs.python.org/issue9216 hash = hashlib.sha256(opt["--apikey"].encode("utf-8"), usedforsecurity=False).hexdigest() except (AttributeError, TypeError): hash = hashlib.sha256(opt["--apikey"].encode("utf-8")).hexdigest() f = open(opt["--token-file"].replace("[hash]", hash)) bearer_token = f.read() f.close() except IOError: bearer_token = get_bearer_token(conn, opt) # set auth token for later requests conn = set_bearer_token(conn, bearer_token) return conn def disconnect(conn): conn.close() def send_command(conn, options, command, method="GET", action=None, expected_rc=200): if not command.startswith("https"): url = conn.base_url + command else: url = command conn.setopt(pycurl.URL, url.encode("ascii")) web_buffer = io.BytesIO() if method == "GET": conn.setopt(pycurl.POST, 0) if method == "POST": conn.setopt(pycurl.POSTFIELDS, action) if method == "DELETE": conn.setopt(pycurl.CUSTOMREQUEST, "DELETE") conn.setopt(pycurl.WRITEFUNCTION, web_buffer.write) try: conn.perform() except Exception as e: raise(e) rc = conn.getinfo(pycurl.HTTP_CODE) # auth if token has expired if rc in [400, 401, 415]: tokenconn = pycurl.Curl() token = get_bearer_token(tokenconn, options) tokenconn.close() conn = set_bearer_token(conn, token) # flush web_buffer web_buffer.close() web_buffer = io.BytesIO() conn.setopt(pycurl.WRITEFUNCTION, web_buffer.write) try: conn.perform() except Exception as e: raise(e) rc = conn.getinfo(pycurl.HTTP_CODE) result = web_buffer.getvalue().decode("UTF-8") web_buffer.close() # actions (start/stop/reboot) report 201 when they've been created if rc != expected_rc: logging.debug("rc: {}, result: {}".format(rc, result)) if len(result) > 0: raise Exception("{}: {}".format(rc, result["value"]["messages"][0]["default_message"])) else: raise Exception("Remote returned {} for request to {}".format(rc, url)) if len(result) > 0: result = json.loads(result) logging.debug("url: {}".format(url)) logging.debug("method: {}".format(method)) logging.debug("response code: {}".format(rc)) logging.debug("result: {}\n".format(result)) return result def define_new_opts(): all_opt["apikey"] = { "getopt" : ":", "longopt" : "apikey", "help" : "--apikey=[key] API Key", "required" : "1", "shortdesc" : "API Key", "order" : 0 } all_opt["region"] = { "getopt" : ":", "longopt" : "region", "help" : "--region=[region] Region", "required" : "1", "shortdesc" : "Region", "order" : 0 } all_opt["proxy"] = { "getopt" : ":", "longopt" : "proxy", "help" : "--proxy=[http://:] Proxy: 'http://:'", "required" : "0", "default": "", "shortdesc" : "Network proxy", "order" : 0 } all_opt["limit"] = { "getopt" : ":", "longopt" : "limit", "help" : "--limit=[number] Limit number of nodes returned by API", "required" : "0", "default": 50, "shortdesc" : "Number of nodes returned by API", "order" : 0 } all_opt["token_file"] = { "getopt" : ":", "longopt" : "token-file", "help" : "--token-file=[path] Path to the token cache file\n" "\t\t\t\t (Default: @FENCETMPDIR@/fence_ibm_vpc/[hash].token)\n" "\t\t\t\t [hash] will be replaced by a hashed value", "required" : "0", "default": "@FENCETMPDIR@/fence_ibm_vpc/[hash].token", "shortdesc" : "Path to the token cache file", "order" : 0 } def main(): device_opt = [ "apikey", "region", "proxy", "limit", "token_file", "port", "no_password", ] atexit.register(atexit_handler) define_new_opts() all_opt["shell_timeout"]["default"] = "15" all_opt["power_timeout"]["default"] = "30" all_opt["power_wait"]["default"] = "1" options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for IBM Cloud VPC" - docs["longdesc"] = """fence_ibm_vpc is an I/O Fencing agent which can be \ + docs["longdesc"] = """fence_ibm_vpc is a Power Fencing agent which can be \ used with IBM Cloud VPC to fence virtual machines.""" docs["vendorurl"] = "https://www.ibm.com" show_docs(options, docs) #### ## Fence operations #### run_delay(options) conn = connect(options) atexit.register(disconnect, conn) result = fence_action(conn, options, set_power_status, get_power_status, get_list) sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/ibmblade/fence_ibmblade.py b/agents/ibmblade/fence_ibmblade.py index d623fff3..ca6e2179 100644 --- a/agents/ibmblade/fence_ibmblade.py +++ b/agents/ibmblade/fence_ibmblade.py @@ -1,72 +1,72 @@ #!@PYTHON@ -tt import sys import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing_snmp import FencingSnmp ### CONSTANTS ### # From fence_ibmblade.pl STATUSES_OID = ".1.3.6.1.4.1.2.3.51.2.22.1.5.1.1.4" # remoteControlBladePowerState CONTROL_OID = ".1.3.6.1.4.1.2.3.51.2.22.1.6.1.1.7" # powerOnOffBlade # Status constants returned as value from SNMP STATUS_DOWN = 0 STATUS_UP = 1 # Status constants to set as value to SNMP STATUS_SET_OFF = 0 STATUS_SET_ON = 1 ### FUNCTIONS ### def get_power_status(conn, options): (_, status) = conn.get("%s.%s"% (STATUSES_OID, options["--plug"])) return status == str(STATUS_UP) and "on" or "off" def set_power_status(conn, options): conn.set("%s.%s" % (CONTROL_OID, options["--plug"]), (options["--action"] == "on" and STATUS_SET_ON or STATUS_SET_OFF)) def get_outlets_status(conn, _): result = {} res_blades = conn.walk(STATUSES_OID, 30) for blade_info in res_blades: port_num = blade_info[0].split('.')[-1] port_alias = "" port_status = (blade_info[1] == str(STATUS_UP) and "on" or "off") result[port_num] = (port_alias, port_status) return result # Main agent method def main(): device_opt = ["ipaddr", "login", "passwd", "no_login", "no_password", \ "port", "snmp_version", "snmp"] atexit.register(atexit_handler) all_opt["snmp_version"]["default"] = "1" options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for IBM BladeCenter over SNMP" - docs["longdesc"] = "fence_ibmblade is an I/O Fencing agent \ + docs["longdesc"] = "fence_ibmblade is a Power Fencing agent \ which can be used with IBM BladeCenter chassis. It issues SNMP Set \ request to BladeCenter chassis, rebooting, powering up or down \ the specified Blade Server." docs["vendorurl"] = "http://www.ibm.com" show_docs(options, docs) # Operate the fencing device result = fence_action(FencingSnmp(options), options, set_power_status, get_power_status, get_outlets_status) sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/ibmz/fence_ibmz.py b/agents/ibmz/fence_ibmz.py index d477adeb..a4cc12d5 100644 --- a/agents/ibmz/fence_ibmz.py +++ b/agents/ibmz/fence_ibmz.py @@ -1,566 +1,566 @@ #!@PYTHON@ -tt # Copyright (c) 2020 IBM Corp. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library 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 # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see # . import atexit import logging import time import sys import requests from requests.packages import urllib3 sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail_usage, run_delay, EC_GENERIC_ERROR DEFAULT_POWER_TIMEOUT = '300' ERROR_NOT_FOUND = ("{obj_type} {obj_name} not found in this HMC. " "Attention: names are case-sensitive.") class ApiClientError(Exception): """ Base exception for all API Client related errors. """ class ApiClientRequestError(ApiClientError): """ Raised when an API request ends in error """ def __init__(self, req_method, req_uri, status, reason, message): self.req_method = req_method self.req_uri = req_uri self.status = status self.reason = reason self.message = message super(ApiClientRequestError, self).__init__() def __str__(self): return ( "API request failed, details:\n" "HTTP Request : {req_method} {req_uri}\n" "HTTP Response status: {status}\n" "Error reason: {reason}\n" "Error message: {message}\n".format( req_method=self.req_method, req_uri=self.req_uri, status=self.status, reason=self.reason, message=self.message) ) class APIClient(object): DEFAULT_CONFIG = { # how many connection-related errors to retry on 'connect_retries': 3, # how many times to retry on read errors (after request was sent to the # server) 'read_retries': 3, # http methods that should be retried 'method_whitelist': ['HEAD', 'GET', 'OPTIONS'], # limit of redirects to perform to avoid loops 'redirect': 5, # how long to wait while establishing a connection 'connect_timeout': 30, # how long to wait for asynchronous operations (jobs) to complete 'operation_timeout': 900, # how long to wait between bytes sent by the remote side 'read_timeout': 300, # default API port 'port': 6794, # validate ssl certificates 'ssl_verify': False, # load on activate is set in the HMC activation profile and therefore # no additional load is executed by the fence agent 'load_on_activate': False } LABEL_BY_OP_MODE = { 'classic': { 'nodes': 'logical-partitions', 'state-on': 'operating', 'start': 'load', 'stop': 'deactivate' }, 'dpm': { 'nodes': 'partitions', 'state-on': 'active', 'start': 'start', 'stop': 'stop' } } def __init__(self, host, user, passwd, config=None): self.host = host if not passwd: raise ValueError('Password cannot be empty') self.passwd = passwd if not user: raise ValueError('Username cannot be empty') self.user = user self._cpc_cache = {} self._session = None self._config = self.DEFAULT_CONFIG.copy() # apply user defined values if config: self._config.update(config) def _create_session(self): """ Create a new requests session and apply config values """ session = requests.Session() retry_obj = urllib3.Retry( # setting a total is necessary to cover SSL related errors total=max(self._config['connect_retries'], self._config['read_retries']), connect=self._config['connect_retries'], read=self._config['read_retries'], method_whitelist=self._config['method_whitelist'], redirect=self._config['redirect'] ) session.mount('http://', requests.adapters.HTTPAdapter( max_retries=retry_obj)) session.mount('https://', requests.adapters.HTTPAdapter( max_retries=retry_obj)) return session def _get_mode_labels(self, cpc): """ Return the map of labels that corresponds to the cpc operation mode """ if self.is_dpm_enabled(cpc): return self.LABEL_BY_OP_MODE['dpm'] return self.LABEL_BY_OP_MODE['classic'] def _get_partition(self, cpc, partition): """ Return the properties of the specified partition. Raises ValueError if it cannot be found. """ # HMC API's documentation says it'll return an empty array when no # matches are found but for a CPC in classic mode it returns in fact # 404, so we handle this accordingly. Remove the extra handling below # once this behavior has been fixed on the API's side. label_map = self._get_mode_labels(cpc) resp = self._request('get', '{}/{}?name={}'.format( self._cpc_cache[cpc]['object-uri'], label_map['nodes'], partition), valid_codes=[200, 404]) if label_map['nodes'] not in resp or not resp[label_map['nodes']]: raise ValueError(ERROR_NOT_FOUND.format( obj_type='LPAR/Partition', obj_name=partition)) return resp[label_map['nodes']][0] def _partition_switch_power(self, cpc, partition, action): """ Perform the API request to start (power on) or stop (power off) the target partition and wait for the job to finish. """ # retrieve partition's uri part_uri = self._get_partition(cpc, partition)['object-uri'] label_map = self._get_mode_labels(cpc) # in dpm mode the request must have empty body if self.is_dpm_enabled(cpc): body = None # in classic mode we make sure the operation is executed # even if the partition is already on else: body = {'force': True} # when powering on the partition must be activated first if action == 'start': op_uri = '{}/operations/activate'.format(part_uri) job_resp = self._request( 'post', op_uri, body=body, valid_codes=[202]) # always wait for activate otherwise the load (start) # operation will fail if self._config['operation_timeout'] == 0: timeout = self.DEFAULT_CONFIG['operation_timeout'] else: timeout = self._config['operation_timeout'] logging.debug( 'waiting for activate (timeout %s secs)', timeout) self._wait_for_job('post', op_uri, job_resp['job-uri'], timeout=timeout) if self._config['load_on_activate']: return # trigger the start job op_uri = '{}/operations/{}'.format(part_uri, label_map[action]) job_resp = self._request('post', op_uri, body=body, valid_codes=[202]) if self._config['operation_timeout'] == 0: return logging.debug('waiting for %s (timeout %s secs)', label_map[action], self._config['operation_timeout']) self._wait_for_job('post', op_uri, job_resp['job-uri'], timeout=self._config['operation_timeout']) def _request(self, method, uri, body=None, headers=None, valid_codes=None): """ Perform a request to the HMC API """ assert method in ('delete', 'head', 'get', 'post', 'put') url = 'https://{host}:{port}{uri}'.format( host=self.host, port=self._config['port'], uri=uri) if not headers: headers = {} if self._session is None: raise ValueError('You need to log on first') method = getattr(self._session, method) timeout = ( self._config['connect_timeout'], self._config['read_timeout']) response = method(url, json=body, headers=headers, verify=self._config['ssl_verify'], timeout=timeout) if valid_codes and response.status_code not in valid_codes: reason = '(no reason)' message = '(no message)' if response.headers.get('content-type') == 'application/json': try: json_resp = response.json() except ValueError: pass else: reason = json_resp.get('reason', reason) message = json_resp.get('message', message) else: message = '{}...'.format(response.text[:500]) raise ApiClientRequestError( response.request.method, response.request.url, response.status_code, reason, message) if response.status_code == 204: return dict() try: json_resp = response.json() except ValueError: raise ApiClientRequestError( response.request.method, response.request.url, response.status_code, '(no reason)', 'Invalid JSON content in response') return json_resp def _update_cpc_cache(self, cpc_props): self._cpc_cache[cpc_props['name']] = { 'object-uri': cpc_props['object-uri'], 'dpm-enabled': cpc_props.get('dpm-enabled', False) } def _wait_for_job(self, req_method, req_uri, job_uri, timeout): """ Perform API requests to check for job status until it has completed or the specified timeout is reached """ op_timeout = time.time() + timeout while time.time() < op_timeout: job_resp = self._request("get", job_uri) if job_resp['status'] == 'complete': if job_resp['job-status-code'] in (200, 201, 204): return raise ApiClientRequestError( req_method, req_uri, job_resp.get('job-status-code', '(no status)'), job_resp.get('job-reason-code', '(no reason)'), job_resp.get('job-results', {}).get( 'message', '(no message)') ) time.sleep(1) raise ApiClientError('Timed out while waiting for job completion') def cpc_list(self): """ Return a list of CPCs in the format {'name': 'cpc-name', 'status': 'operating'} """ list_resp = self._request("get", "/api/cpcs", valid_codes=[200]) ret = [] for cpc_props in list_resp['cpcs']: self._update_cpc_cache(cpc_props) ret.append({ 'name': cpc_props['name'], 'status': cpc_props['status']}) return ret def is_dpm_enabled(self, cpc): """ Return True if CPC is in DPM mode, False for classic mode """ if cpc in self._cpc_cache: return self._cpc_cache[cpc]['dpm-enabled'] list_resp = self._request("get", "/api/cpcs?name={}".format(cpc), valid_codes=[200]) if not list_resp['cpcs']: raise ValueError(ERROR_NOT_FOUND.format( obj_type='CPC', obj_name=cpc)) self._update_cpc_cache(list_resp['cpcs'][0]) return self._cpc_cache[cpc]['dpm-enabled'] def logon(self): """ Open a session with the HMC API and store its ID """ self._session = self._create_session() logon_body = {"userid": self.user, "password": self.passwd} logon_resp = self._request("post", "/api/sessions", body=logon_body, valid_codes=[200, 201]) self._session.headers["X-API-Session"] = logon_resp['api-session'] def logoff(self): """ Close/delete the HMC API session """ if self._session is None: return self._request("delete", "/api/sessions/this-session", valid_codes=[204]) self._cpc_cache = {} self._session = None def partition_list(self, cpc): """ Return a list of partitions in the format {'name': 'part-name', 'status': 'on'} """ label_map = self._get_mode_labels(cpc) list_resp = self._request( 'get', '{}/{}'.format( self._cpc_cache[cpc]['object-uri'], label_map['nodes']), valid_codes=[200]) status_map = {label_map['state-on']: 'on'} return [{'name': part['name'], 'status': status_map.get(part['status'].lower(), 'off')} for part in list_resp[label_map['nodes']]] def partition_start(self, cpc, partition): """ Power on a partition """ self._partition_switch_power(cpc, partition, 'start') def partition_status(self, cpc, partition): """ Return the current status of a partition (on or off) """ label_map = self._get_mode_labels(cpc) part_props = self._get_partition(cpc, partition) if part_props['status'].lower() == label_map['state-on']: return 'on' return 'off' def partition_stop(self, cpc, partition): """ Power off a partition """ self._partition_switch_power(cpc, partition, 'stop') def parse_plug(options): """ Extract cpc and partition from specified plug value """ try: cpc, partition = options['--plug'].strip().split('/', 1) except ValueError: fail_usage('Please specify nodename in format cpc/partition') cpc = cpc.strip() if not cpc or not partition: fail_usage('Please specify nodename in format cpc/partition') return cpc, partition def get_power_status(conn, options): logging.debug('executing get_power_status') status = conn.partition_status(*parse_plug(options)) return status def set_power_status(conn, options): logging.debug('executing set_power_status') if options['--action'] == 'on': conn.partition_start(*parse_plug(options)) elif options['--action'] == 'off': conn.partition_stop(*parse_plug(options)) else: fail_usage('Invalid action {}'.format(options['--action'])) def get_outlet_list(conn, options): logging.debug('executing get_outlet_list') result = {} for cpc in conn.cpc_list(): for part in conn.partition_list(cpc['name']): result['{}/{}'.format(cpc['name'], part['name'])] = ( part['name'], part['status']) return result def disconnect(conn): """ Close the API session """ try: conn.logoff() except Exception as exc: logging.exception('Logoff failed: ') sys.exit(str(exc)) def set_opts(): """ Define the options supported by this agent """ device_opt = [ "ipaddr", "ipport", "login", "passwd", "port", "connect_retries", "connect_timeout", "operation_timeout", "read_retries", "read_timeout", "ssl_secure", "load_on_activate", ] all_opt["ipport"]["default"] = APIClient.DEFAULT_CONFIG['port'] all_opt["power_timeout"]["default"] = DEFAULT_POWER_TIMEOUT port_desc = ("Physical plug id in the format cpc-name/partition-name " "(case-sensitive)") all_opt["port"]["shortdesc"] = port_desc all_opt["port"]["help"] = ( "-n, --plug=[id] {}".format(port_desc)) all_opt["connect_retries"] = { "getopt" : ":", "longopt" : "connect-retries", "help" : "--connect-retries=[number] How many times to " "retry on connection errors", "default" : APIClient.DEFAULT_CONFIG['connect_retries'], "type" : "integer", "required" : "0", "shortdesc" : "How many times to retry on connection errors", "order" : 2 } all_opt["read_retries"] = { "getopt" : ":", "longopt" : "read-retries", "help" : "--read-retries=[number] How many times to " "retry on errors related to reading from server", "default" : APIClient.DEFAULT_CONFIG['read_retries'], "type" : "integer", "required" : "0", "shortdesc" : "How many times to retry on read errors", "order" : 2 } all_opt["connect_timeout"] = { "getopt" : ":", "longopt" : "connect-timeout", "help" : "--connect-timeout=[seconds] How long to wait to " "establish a connection", "default" : APIClient.DEFAULT_CONFIG['connect_timeout'], "type" : "second", "required" : "0", "shortdesc" : "How long to wait to establish a connection", "order" : 2 } all_opt["operation_timeout"] = { "getopt" : ":", "longopt" : "operation-timeout", "help" : "--operation-timeout=[seconds] How long to wait for " "power operation to complete (0 = do not wait)", "default" : APIClient.DEFAULT_CONFIG['operation_timeout'], "type" : "second", "required" : "0", "shortdesc" : "How long to wait for power operation to complete", "order" : 2 } all_opt["read_timeout"] = { "getopt" : ":", "longopt" : "read-timeout", "help" : "--read-timeout=[seconds] How long to wait " "to read data from server", "default" : APIClient.DEFAULT_CONFIG['read_timeout'], "type" : "second", "required" : "0", "shortdesc" : "How long to wait for server data", "order" : 2 } all_opt["load_on_activate"] = { "getopt" : "", "longopt" : "load-on-activate", "help" : "--load-on-activate Rely on the HMC to perform " "a load operation on activation", "required" : "0", "order" : 3 } return device_opt def main(): """ Agent entry point """ # register exit handler used by pacemaker atexit.register(atexit_handler) # prepare accepted options device_opt = set_opts() # parse options provided on input options = check_input(device_opt, process_input(device_opt)) docs = { "shortdesc": "Fence agent for IBM z LPARs", "longdesc": ( - "fence_ibmz is a power fencing agent which uses the HMC Web " + "fence_ibmz is a Power Fencing agent which uses the HMC Web " "Services API to fence IBM z LPARs."), "vendorurl": "http://www.ibm.com" } show_docs(options, docs) run_delay(options) # set underlying library's logging and ssl config according to specified # options requests_log = logging.getLogger("urllib3") requests_log.propagate = True if "--verbose" in options: requests_log.setLevel(logging.DEBUG) if "--ssl-insecure" in options: urllib3.disable_warnings( category=urllib3.exceptions.InsecureRequestWarning) hmc_address = options["--ip"] hmc_userid = options["--username"] hmc_password = options["--password"] config = { 'connect_retries': int(options['--connect-retries']), 'read_retries': int(options['--read-retries']), 'operation_timeout': int(options['--operation-timeout']), 'connect_timeout': int(options['--connect-timeout']), 'read_timeout': int(options['--read-timeout']), 'port': int(options['--ipport']), 'ssl_verify': bool('--ssl-insecure' not in options), 'load_on_activate': bool('--load-on-activate' in options), } try: conn = APIClient(hmc_address, hmc_userid, hmc_password, config) conn.logon() atexit.register(disconnect, conn) result = fence_action(conn, options, set_power_status, get_power_status, get_outlet_list) except Exception: logging.exception('Exception occurred: ') result = EC_GENERIC_ERROR sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/ilo/fence_ilo.py b/agents/ilo/fence_ilo.py index 61245056..f30a1da2 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, re, pexpect +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"] = "fence_ilo is an I/O Fencing agent \ + 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." +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_moonshot/fence_ilo_moonshot.py b/agents/ilo_moonshot/fence_ilo_moonshot.py index 1923eeb1..92bc7452 100644 --- a/agents/ilo_moonshot/fence_ilo_moonshot.py +++ b/agents/ilo_moonshot/fence_ilo_moonshot.py @@ -1,65 +1,67 @@ #!@PYTHON@ -tt import sys import logging import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, EC_STATUS def get_power_status(conn, options): conn.send_eol("show node list") conn.log_expect(options["--command-prompt"], int(options["--shell-timeout"])) nodes = {} for line in conn.before.splitlines(): if len(line.split()) == 10: nodes[line.split()[1]] = ("", line.split()[8].lower().strip()) if ["list", "monitor"].count(options["--action"]) == 1: return nodes else: try: (_, status) = nodes[options["--plug"]] return status.lower() except KeyError as e: logging.error("Failed: {}".format(str(e))) fail(EC_STATUS) def set_power_status(conn, options): if options["--action"] == "on": conn.send_eol("set node power on %s" % (options["--plug"])) else: conn.send_eol("set node power off force %s" % (options["--plug"])) conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) return def main(): device_opt = ["ipaddr", "login", "passwd", "secure", "cmd_prompt", "port"] atexit.register(atexit_handler) all_opt["secure"]["default"] = "1" all_opt["cmd_prompt"]["default"] = ["MP>", "hpiLO->"] options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for HP Moonshot iLO" + docs["longdesc"] = "fence_ilo_moonshot is a Power Fencing agent \ +for HP Moonshot iLO." docs["longdesc"] = "" docs["vendorurl"] = "http://www.hp.com" show_docs(options, docs) conn = fence_login(options) ## ## Fence operations #### result = fence_action(conn, options, set_power_status, get_power_status, get_power_status) fence_logout(conn, "exit") sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/ilo_mp/fence_ilo_mp.py b/agents/ilo_mp/fence_ilo_mp.py index 1ae4d3ed..cc1c2dec 100644 --- a/agents/ilo_mp/fence_ilo_mp.py +++ b/agents/ilo_mp/fence_ilo_mp.py @@ -1,58 +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) 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"] = "" + 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 a27e3418..1d5be21e 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, re +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) 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"] = "fence_ilo_ssh is a fence agent that connects to iLO device. It logs into \ + 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." +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/intelmodular/fence_intelmodular.py b/agents/intelmodular/fence_intelmodular.py index 294ea4dd..e9c417a9 100644 --- a/agents/intelmodular/fence_intelmodular.py +++ b/agents/intelmodular/fence_intelmodular.py @@ -1,86 +1,86 @@ #!@PYTHON@ -tt # Tested with an Intel MFSYS25 using firmware package 2.6 Should work with an # MFSYS35 as well. # # Notes: # # The manual and firmware release notes says SNMP is read only. This is not # true, as per the MIBs that ship with the firmware you can write to # the bladePowerLed oid to control the servers. # # Thanks Matthew Kent for original agent and testing. import sys import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing_snmp import FencingSnmp ### CONSTANTS ### # From INTELCORPORATION-MULTI-FLEX-SERVER-BLADES-MIB.my that ships with # firmware updates STATUSES_OID = ".1.3.6.1.4.1.343.2.19.1.2.10.202.1.1.6" # Status constants returned as value from SNMP STATUS_UP = 2 STATUS_DOWN = 0 # Status constants to set as value to SNMP STATUS_SET_ON = 2 STATUS_SET_OFF = 3 ### FUNCTIONS ### def get_power_status(conn, options): (_, status) = conn.get("%s.%s"% (STATUSES_OID, options["--plug"])) return status == str(STATUS_UP) and "on" or "off" def set_power_status(conn, options): conn.set("%s.%s" % (STATUSES_OID, options["--plug"]), (options["--action"] == "on" and STATUS_SET_ON or STATUS_SET_OFF)) def get_outlets_status(conn, options): result = {} res_blades = conn.walk(STATUSES_OID, 30) for x in res_blades: port_num = x[0].split('.')[-1] port_alias = "" port_status = (x[1] == str(STATUS_UP) and "on" or "off") result[port_num] = (port_alias, port_status) return result # Main agent method def main(): device_opt = ["ipaddr", "login", "passwd", "no_login", "no_password", "port", "snmp_version", "snmp"] atexit.register(atexit_handler) options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for Intel Modular" - docs["longdesc"] = "fence_intelmodular is an I/O Fencing agent \ + docs["longdesc"] = "fence_intelmodular is a Power Fencing agent \ which can be used with Intel Modular device (tested on Intel MFSYS25, should \ work with MFSYS35 as well). \ \n.P\n\ Note: Since firmware update version 2.7, SNMP v2 write support is \ removed, and replaced by SNMP v3 support. So agent now has default \ SNMP version 3. If you are using older firmware, please supply -d \ for command line and snmp_version option for your cluster.conf." docs["vendorurl"] = "http://www.intel.com" show_docs(options, docs) # Operate the fencing device result = fence_action(FencingSnmp(options), options, set_power_status, get_power_status, get_outlets_status) sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/ipdu/fence_ipdu.py b/agents/ipdu/fence_ipdu.py index da34d2b6..f7093b8b 100644 --- a/agents/ipdu/fence_ipdu.py +++ b/agents/ipdu/fence_ipdu.py @@ -1,153 +1,153 @@ #!@PYTHON@ -tt # The Following agent has been tested on: # IBM iPDU model 46M4002 # Firmware release OPDP_sIBM_v01.2_1 # import sys import atexit import logging sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail_usage from fencing_snmp import FencingSnmp ### CONSTANTS ### # oid defining fence device OID_SYS_OBJECT_ID = '.1.3.6.1.2.1.1.2.0' ### GLOBAL VARIABLES ### # Device - see IBM iPDU device = None # Port ID port_id = None # Switch ID switch_id = None # Classes describing Device params class IBMiPDU(object): # iPDU status_oid = '.1.3.6.1.4.1.2.6.223.8.2.2.1.11.%d' control_oid = '.1.3.6.1.4.1.2.6.223.8.2.2.1.11.%d' outlet_table_oid = '.1.3.6.1.4.1.2.6.223.8.2.2.1.2' ident_str = "IBM iPDU" state_on = 1 state_off = 0 turn_on = 1 turn_off = 0 has_switches = False ### FUNCTIONS ### def ipdu_set_device(conn, options): global device agents_dir = {'.1.3.6.1.4.1.2.6.223':IBMiPDU, None:IBMiPDU} # First resolve type of PDU device pdu_type = conn.walk(OID_SYS_OBJECT_ID) if not ((len(pdu_type) == 1) and (pdu_type[0][1] in agents_dir)): pdu_type = [[None, None]] device = agents_dir[pdu_type[0][1]] logging.debug("Trying %s"%(device.ident_str)) def ipdu_resolv_port_id(conn, options): global port_id, switch_id if device == None: ipdu_set_device(conn, options) # Now we resolv port_id/switch_id if options["--plug"].isdigit() and ((not device.has_switches) or (options["--switch"].isdigit())): port_id = int(options["--plug"]) if device.has_switches: switch_id = int(options["--switch"]) else: table = conn.walk(device.outlet_table_oid, 30) for x in table: if x[1].strip('"') == options["--plug"]: t = x[0].split('.') if device.has_switches: port_id = int(t[len(t)-1]) switch_id = int(t[len(t)-3]) else: port_id = int(t[len(t)-1]) if port_id == None: fail_usage("Can't find port with name %s!"%(options["--plug"])) def get_power_status(conn, options): if port_id == None: ipdu_resolv_port_id(conn, options) oid = ((device.has_switches) and device.status_oid%(switch_id, port_id) or device.status_oid%(port_id)) (oid, status) = conn.get(oid) return status == str(device.state_on) and "on" or "off" def set_power_status(conn, options): if port_id == None: ipdu_resolv_port_id(conn, options) oid = ((device.has_switches) and device.control_oid%(switch_id, port_id) or device.control_oid%(port_id)) conn.set(oid, (options["--action"] == "on" and device.turn_on or device.turn_off)) def get_outlets_status(conn, options): result = {} if device == None: ipdu_set_device(conn, options) res_ports = conn.walk(device.outlet_table_oid, 30) for x in res_ports: t = x[0].split('.') port_num = ((device.has_switches) and "%s:%s"%(t[len(t)-3], t[len(t)-1]) or "%s"%(t[len(t)-1])) port_name = x[1].strip('"') port_status = "" result[port_num] = (port_name, port_status) return result # Main agent method def main(): global device device_opt = ["ipaddr", "login", "passwd", "no_login", "no_password", \ "port", "snmp_version", "snmp"] atexit.register(atexit_handler) all_opt["snmp_version"]["default"] = "3" all_opt["community"]["default"] = "private" all_opt["switch"]["default"] = "1" device = IBMiPDU options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for iPDU over SNMP" - docs["longdesc"] = "fence_ipdu is an I/O Fencing agent \ + docs["longdesc"] = "fence_ipdu is a Power Fencing agent \ which can be used with the IBM iPDU network power switch. It logs \ into a device via SNMP and reboots a specified outlet. It supports \ SNMP v3 with all combinations of authenticity/privacy settings." docs["vendorurl"] = "http://www.ibm.com" show_docs(options, docs) # Operate the fencing device result = fence_action(FencingSnmp(options), options, set_power_status, get_power_status, get_outlets_status) sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/ipmilan/fence_ipmilan.py b/agents/ipmilan/fence_ipmilan.py index 91e09ac7..a47fbdd8 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)) 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())) def reboot_diag(_, options): output = _run_command(options, "diag") return bool(re.search('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"] = "fence_ipmilan is an I/O Fencing agent \ + 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." +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 d0c9d9c1..76a9250f 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)) 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) 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 Fencing agent \ + 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/kubevirt/fence_kubevirt.py b/agents/kubevirt/fence_kubevirt.py index 8c27a033..e3817b0f 100755 --- a/agents/kubevirt/fence_kubevirt.py +++ b/agents/kubevirt/fence_kubevirt.py @@ -1,154 +1,154 @@ #!@PYTHON@ -tt import sys import logging import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, fail_usage, run_delay, EC_STATUS, EC_FETCH_VM_UUID try: from kubernetes.client.exceptions import ApiException except ImportError: logging.error("Couldn\'t import kubernetes.client.exceptions.ApiException - not found or not accessible") def _get_namespace(options): from kubernetes import config ns = options.get("--namespace") if ns is None: ns = config.kube_config.list_kube_config_contexts()[1]['context']['namespace'] return ns def get_nodes_list(conn, options): logging.debug("Starting list/monitor operation") result = {} try: apiversion = options.get("--apiversion") namespace = _get_namespace(options) include_uninitialized = True vm_api = conn.resources.get(api_version=apiversion, kind='VirtualMachine') vm_list = vm_api.get(namespace=namespace) for vm in vm_list.items: result[vm.metadata.name] = ("", None) except Exception as e: logging.error("Exception when calling VirtualMachine list: %s", e) return result def get_power_status(conn, options): logging.debug("Starting get status operation") try: apiversion = options.get("--apiversion") namespace = _get_namespace(options) name = options.get("--plug") vmi_api = conn.resources.get(api_version=apiversion, kind='VirtualMachineInstance') vmi = vmi_api.get(name=name, namespace=namespace) return translate_status(vmi.status.phase) except ApiException as e: if e.status == 404: try: vm_api = conn.resources.get(api_version=apiversion, kind='VirtualMachine') vm = vm_api.get(name=name, namespace=namespace) except ApiException as e: logging.error("VM %s doesn't exist", name) fail(EC_FETCH_VM_UUID) return "off" logging.error("Failed to get power status, with API Exception: %s", e) fail(EC_STATUS) except Exception as e: logging.error("Failed to get power status, with Exception: %s", e) fail(EC_STATUS) def translate_status(instance_status): if instance_status == "Running": return "on" return "unknown" def set_power_status(conn, options): logging.debug("Starting set status operation") try: apiversion= options.get("--apiversion") namespace = _get_namespace(options) name = options.get("--plug") action = 'start' if options["--action"] == "on" else 'stop' virtctl_vm_action(conn, action, namespace, name, apiversion) except Exception as e: logging.error("Failed to set power status, with Exception: %s", e) fail(EC_STATUS) def define_new_opts(): all_opt["namespace"] = { "getopt" : ":", "longopt" : "namespace", "help" : "--namespace=[namespace] Namespace of the KubeVirt machine", "shortdesc" : "Namespace of the KubeVirt machine.", "required" : "0", "order" : 2 } all_opt["kubeconfig"] = { "getopt" : ":", "longopt" : "kubeconfig", "help" : "--kubeconfig=[kubeconfig] Kubeconfig file path", "shortdesc": "Kubeconfig file path", "required": "0", "order": 4 } all_opt["apiversion"] = { "getopt" : ":", "longopt" : "apiversion", "help" : "--apiversion=[apiversion] Version of the KubeVirt API", "shortdesc" : "Version of the KubeVirt API.", "required" : "0", "default" : "kubevirt.io/v1", "order" : 5 } def virtctl_vm_action(conn, action, namespace, name, apiversion): path = '/apis/subresources.{api_version}/namespaces/{namespace}/virtualmachines/{name}/{action}' path = path.format(api_version=apiversion, namespace=namespace, name=name, action=action) return conn.request('put', path, header_params={'accept': '*/*'}) # Main agent method def main(): conn = None device_opt = ["port", "namespace", "kubeconfig", "ssl_insecure", "no_password", "apiversion"] atexit.register(atexit_handler) define_new_opts() all_opt["power_timeout"]["default"] = "40" options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for KubeVirt" - docs["longdesc"] = "fence_kubevirt is an I/O Fencing agent for KubeVirt." + docs["longdesc"] = "fence_kubevirt is a Power Fencing agent for KubeVirt." docs["vendorurl"] = "https://kubevirt.io/" show_docs(options, docs) run_delay(options) # Disable insecure-certificate-warning message if "--ssl-insecure" in options: import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) try: from kubernetes import config from openshift.dynamic import DynamicClient kubeconfig = options.get('--kubeconfig') k8s_client = config.new_client_from_config(config_file=kubeconfig) conn = DynamicClient(k8s_client) except ImportError: logging.error("Couldn\'t import kubernetes.config or " "openshift.dynamic.DynamicClient - not found or not accessible") # Operate the fencing device result = fence_action(conn, options, set_power_status, get_power_status, get_nodes_list) sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/ldom/fence_ldom.py b/agents/ldom/fence_ldom.py index 0cb3320b..78edd295 100644 --- a/agents/ldom/fence_ldom.py +++ b/agents/ldom/fence_ldom.py @@ -1,102 +1,102 @@ #!@PYTHON@ -tt ## ## The Following Agent Has Been Tested On - LDOM 1.0.3 ## The interface is backward compatible so it will work ## with 1.0, 1.0.1 and .2 too. ## ##### import sys, re, pexpect import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail_usage COMMAND_PROMPT_REG = r"\[PEXPECT\]$" COMMAND_PROMPT_NEW = "[PEXPECT]" # Start comunicating after login. Prepare good environment. def start_communication(conn, options): conn.send_eol("PS1='" + COMMAND_PROMPT_NEW + "'") res = conn.expect([pexpect.TIMEOUT, COMMAND_PROMPT_REG], int(options["--shell-timeout"])) if res == 0: #CSH stuff conn.send_eol("set prompt='" + COMMAND_PROMPT_NEW + "'") conn.log_expect(COMMAND_PROMPT_REG, int(options["--shell-timeout"])) def get_power_status(conn, options): start_communication(conn, options) conn.send_eol("ldm ls") conn.log_expect(COMMAND_PROMPT_REG, int(options["--shell-timeout"])) result = {} #This is status of mini finite automata. 0 = we didn't found NAME and STATE, 1 = we did fa_status = 0 for line in conn.before.splitlines(): domain = re.search(r"^(\S+)\s+(\S+)\s+.*$", line) if domain != None: if fa_status == 0 and domain.group(1) == "NAME" and domain.group(2) == "STATE": fa_status = 1 elif fa_status == 1: result[domain.group(1)] = ("", (domain.group(2).lower() == "bound" and "off" or "on")) if not options["--action"] in ['monitor', 'list']: if not options["--plug"] in result: fail_usage("Failed: You have to enter existing logical domain!") else: return result[options["--plug"]][1] else: return result def set_power_status(conn, options): start_communication(conn, options) cmd_line = "ldm "+ (options["--action"] == "on" and "start" or "stop -f") + " \"" + options["--plug"] + "\"" conn.send_eol(cmd_line) conn.log_expect(COMMAND_PROMPT_REG, int(options["--power-timeout"])) def main(): device_opt = ["ipaddr", "login", "passwd", "cmd_prompt", "secure", "port"] atexit.register(atexit_handler) all_opt["secure"]["default"] = "1" all_opt["cmd_prompt"]["default"] = [r"\ $"] options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for Sun LDOM" - docs["longdesc"] = "fence_ldom is an I/O Fencing agent \ + docs["longdesc"] = "fence_ldom is a Power Fencing agent \ which can be used with LDoms virtual machines. This agent works \ so, that run ldm command on host machine. So ldm must be directly \ runnable.\ \n.P\n\ Very useful parameter is -c (or cmd_prompt in stdin mode). This \ must be set to something, what is displayed after successful login \ to host machine. Default string is space on end of string (default \ for root in bash). But (for example) csh use ], so in that case you \ must use parameter -c with argument ]. Very similar situation is, \ if you use bash and login to host machine with other user than \ root. Than prompt is $, so again, you must use parameter -c." 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, get_power_status) fence_logout(conn, "logout") sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/lindy_pdu/fence_lindypdu.py b/agents/lindy_pdu/fence_lindypdu.py index 432b7415..f5128844 100644 --- a/agents/lindy_pdu/fence_lindypdu.py +++ b/agents/lindy_pdu/fence_lindypdu.py @@ -1,206 +1,206 @@ #!@PYTHON@ -tt # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library 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 # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see # . # The Following agent has been tested on: # Lindy PDU model 32657 # Firmware release s4.82-091012-1cb08s # Probably works on different models with same MIB .. but is better test on them # # (C) 2021 Daimonlab -- Damiano Scaramuzza (cesello) cesello@daimonlab.it import sys import atexit import logging sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail_usage from fencing_snmp import FencingSnmp ### CONSTANTS ### # oid defining fence device OID_SYS_OBJECT_ID = '.1.3.6.1.2.1.1.2.0' ### GLOBAL VARIABLES ### # Device - see Lindy PDU device = None # Port ID port_id = None # Switch ID switch_id = None # Classes describing Device params # Here I follow the MIBS specs that use "switch" and "plug" concepts but # the pdu really have one switch only and 8-16 plugs. # Probably the "switch" term is used for future uses or more advanced pdus class LindyPDU(object): # PDU status_oid = '.1.3.6.1.4.1.17420.1.2.9.%d.13.0' control_oid = '.1.3.6.1.4.1.17420.1.2.9.%d.13.0' outlet_table_oid = '.1.3.6.1.4.1.17420.1.2.9.%d.14' pdu_table_oid = '.1.3.6.1.4.1.17420.1.2.9' attached_pdus = '.1.3.6.1.4.1.17420.1.2.5.0' ident_str = "Lindy 32657 PDU" state_on = 1 state_off = 0 turn_on = 1 turn_off = 0 has_switches = True ### FUNCTIONS ### def lpdu_set_device(conn, options): global device agents_dir = {'.1.3.6.1.4.1.17420':LindyPDU} # First resolve type of PDU device pdu_type = conn.walk(OID_SYS_OBJECT_ID) if not ((len(pdu_type) == 1) and (pdu_type[0][1] in agents_dir)): pdu_type = [[None, None]] device = agents_dir[pdu_type[0][1]] logging.debug("Trying %s"%(device.ident_str)) def lpdu_resolv_port_id(conn, options): if device == None: lpdu_set_device(conn, options) port_id=switch_id=None # Now we resolv port_id/switch_id if options["--plug"].isdigit() and ((not device.has_switches) or (options["--switch"].isdigit())): port_id = int(options["--plug"]) if device.has_switches: switch_id = int(options["--switch"]) else: table = conn.walk(device.pdu_table_oid, 30) for x in table: if x[1].strip('"').split(',')[0] == options["--plug"]: t = x[0].split('.') if device.has_switches: port_id = int(t[len(t)-1]) switch_id = int(t[len(t)-3]) else: port_id = int(t[len(t)-1]) if port_id == None: fail_usage("Can't find port with name %s!"%(options["--plug"])) return (switch_id,port_id) def get_power_status(conn, options): (switch_id,port_id)=lpdu_resolv_port_id(conn, options) oid = ((device.has_switches) and device.status_oid%(switch_id) or device.status_oid%(port_id)) try: (oid, status) = conn.get(oid) # status is a comma separated string # one line only as "1,1,1,0,1,1,1,1". state=status.strip('"').split(',')[port_id-1] if state == str(device.state_on): return "on" elif state == str(device.state_off): return "off" else: return None except Exception: return None def set_power_status(conn, options): (switch_id,port_id)=lpdu_resolv_port_id(conn, options) oid = ((device.has_switches) and device.control_oid%(switch_id) or device.control_oid%(port_id)) (oid, status) = conn.get(oid) # status is a comma separated string state=status.strip('"').split(',') state[port_id-1]=str((options["--action"] == "on" and device.turn_on or device.turn_off)) conn.set(oid, ",".join(state)) def get_outlets_status(conn, options): result = {} pdu_id=[] if device == None: lpdu_set_device(conn, options) if (device.has_switches and options["--switch"].isdigit()): pdu_id.append(options["--switch"]) elif (device.has_switches): #search for all pdu pdus=conn.walk(device.attached_pdus, 30) pdus_info=pdus[0][1].strip('"').split(',') pdu_id=pdus_info[1:] else: #I really don't know what to do with this case. I haven't a different lindy pdu to test table_oid=device.pdu_table_oid for switch in pdu_id: table_oid = device.outlet_table_oid % int(switch) res_ports = conn.walk(table_oid, 30) status_oid=device.status_oid % int(switch) port_status=conn.walk(status_oid, 30) state=port_status[0][1].strip('"').split(',') for x in res_ports: t = x[0].split('.') port_num = ((device.has_switches) and "%s:%s"%(t[len(t)-4], t[len(t)-2]) or "%s"%(t[len(t)-2])) port_name = x[1].strip('"').split(',')[0] result[port_num] = (port_name, "on" if state[int(t[len(t)-2])-1]=='1' else "off") return result # Main agent method def main(): global device device_opt = ["ipaddr", "login", "passwd", "no_login", "no_password", \ "port", "snmp_version", "snmp","switch"] atexit.register(atexit_handler) all_opt["snmp_version"]["default"] = "1" all_opt["community"]["default"] = "public" all_opt["switch"]["default"] = "1" device = LindyPDU options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for Lindy over SNMP" - docs["longdesc"] = "fence_lindypdu is an I/O Fencing agent \ + docs["longdesc"] = "fence_lindypdu is a Power Fencing agent \ which can be used with the Lindy PDU network power switch. It logs \ into a device via SNMP and reboots a specified outlet. It supports \ SNMP v1 with all combinations of authenticity/privacy settings." docs["vendorurl"] = "http://www.lindy.co.uk" show_docs(options, docs) # Operate the fencing device result = fence_action(FencingSnmp(options), options, set_power_status, get_power_status, get_outlets_status) sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/lpar/fence_lpar.py b/agents/lpar/fence_lpar.py index 975971a5..a18e1c9a 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"] + ",(.*?),.*$", 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) 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) 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) 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) 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"] = "" + 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/mpath/fence_mpath.py b/agents/mpath/fence_mpath.py index 6976fee9..0e5d9ed3 100644 --- a/agents/mpath/fence_mpath.py +++ b/agents/mpath/fence_mpath.py @@ -1,341 +1,341 @@ #!@PYTHON@ -tt import sys import stat import re import os import time import logging import atexit import ctypes sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import fail_usage, run_command, atexit_handler, check_input, process_input, show_docs from fencing import fence_action, all_opt, run_delay def get_status(conn, options): del conn status = "off" for dev in options["devices"]: is_block_device(dev) if options["--plug"] in get_registration_keys(options, dev): status = "on" else: logging.debug("No registration for key "\ + options["--plug"] + " on device " + dev + "\n") if options["--action"] == "monitor": dev_read(options) return status def set_status(conn, options): del conn count = 0 if options["--action"] == "on": for dev in options["devices"]: is_block_device(dev) register_dev(options, dev) if options["--plug"] not in get_registration_keys(options, dev): count += 1 logging.debug("Failed to register key "\ + options["--plug"] + "on device " + dev + "\n") continue dev_write(options, dev) if get_reservation_key(options, dev) is None \ and not reserve_dev(options, dev) \ and get_reservation_key(options, dev) is None: count += 1 logging.debug("Failed to create reservation (key="\ + options["--plug"] + ", device=" + dev + ")\n") else: dev_keys = dev_read(options) for dev in options["devices"]: is_block_device(dev) if options["--plug"] in get_registration_keys(options, dev): preempt_abort(options, dev_keys[dev], dev) for dev in options["devices"]: if options["--plug"] in get_registration_keys(options, dev): count += 1 logging.debug("Failed to remove key "\ + options["--plug"] + " on device " + dev + "\n") continue if not get_reservation_key(options, dev): count += 1 logging.debug("No reservation exists on device " + dev + "\n") if count: logging.error("Failed to verify " + str(count) + " device(s)") sys.exit(1) # run command, returns dict, ret["rc"] = exit code; ret["out"] = output; # ret["err"] = error def run_cmd(options, cmd): ret = {} if "--use-sudo" in options: prefix = options["--sudo-path"] + " " else: prefix = "" (ret["rc"], ret["out"], ret["err"]) = run_command(options, prefix + cmd) ret["out"] = "".join([i for i in ret["out"] if i is not None]) ret["err"] = "".join([i for i in ret["err"] if i is not None]) return ret # check if device exist and is block device def is_block_device(dev): if not os.path.exists(dev): fail_usage("Failed: device \"" + dev + "\" does not exist") if not stat.S_ISBLK(os.stat(dev).st_mode): fail_usage("Failed: device \"" + dev + "\" is not a block device") # cancel registration def preempt_abort(options, host, dev): cmd = options["--mpathpersist-path"] + " -o --preempt-abort --prout-type=5 --param-rk=" + host +" --param-sark=" + options["--plug"] +" -d " + dev return not bool(run_cmd(options, cmd)["rc"]) def register_dev(options, dev): cmd = options["--mpathpersist-path"] + " -o --register --param-sark=" + options["--plug"] + " -d " + dev #cmd return code != 0 but registration can be successful return not bool(run_cmd(options, cmd)["rc"]) def reserve_dev(options, dev): cmd = options["--mpathpersist-path"] + " -o --reserve --prout-type=5 --param-rk=" + options["--plug"] + " -d " + dev return not bool(run_cmd(options, cmd)["rc"]) def get_reservation_key(options, dev): cmd = options["--mpathpersist-path"] + " -i -r -d " + dev out = run_cmd(options, cmd) if out["rc"]: fail_usage('Cannot get reservation key on device "' + dev + '": ' + out["err"]) match = re.search(r"\s+key\s*=\s*0x(\S+)\s+", out["out"], re.IGNORECASE) return match.group(1) if match else None def get_registration_keys(options, dev, fail=True): keys = [] cmd = options["--mpathpersist-path"] + " -i -k -d " + dev out = run_cmd(options, cmd) if out["rc"]: fail_usage('Cannot get registration keys on device "' + dev + '": ' + out["err"], fail) if not fail: return [] for line in out["out"].split("\n"): match = re.search(r"\s+0x(\S+)\s*", line) if match: keys.append(match.group(1)) return keys def dev_write(options, dev): file_path = options["--store-path"] + "/mpath.devices" if not os.path.isdir(options["--store-path"]): os.makedirs(options["--store-path"]) try: store_fh = open(file_path, "a+") except IOError: fail_usage("Failed: Cannot open file \""+ file_path + "\"") out = store_fh.read() if not re.search(r"^" + dev + r"\s+", out): store_fh.write(dev + "\t" + options["--plug"] + "\n") store_fh.close() def dev_read(options, fail=True): dev_key = {} file_path = options["--store-path"] + "/mpath.devices" try: store_fh = open(file_path, "r") except IOError: if fail: fail_usage("Failed: Cannot open file \"" + file_path + "\"") else: return None # get not empty lines from file for (device, key) in [line.strip().split() for line in store_fh if line.strip()]: dev_key[device] = key store_fh.close() return dev_key def mpath_check_get_options(options): try: f = open("/etc/sysconfig/stonith", "r") except IOError: return options match = re.findall(r"^\s*(\S*)\s*=\s*(\S*)\s*", "".join(f.readlines()), re.MULTILINE) for m in match: options[m[0].lower()] = m[1].lower() f.close() return options def mpath_check(hardreboot=False): if len(sys.argv) >= 3 and sys.argv[1] == "repair": return int(sys.argv[2]) options = {} options["--mpathpersist-path"] = "/usr/sbin/mpathpersist" options["--store-path"] = "@STORE_PATH@" options["--power-timeout"] = "5" options["retry"] = "0" options["retry-sleep"] = "1" options = mpath_check_get_options(options) if "verbose" in options and options["verbose"] == "yes": logging.getLogger().setLevel(logging.DEBUG) devs = dev_read(options, fail=False) if not devs: if "--suppress-errors" not in options: logging.error("No devices found") return 0 for dev, key in list(devs.items()): for n in range(int(options["retry"]) + 1): if n > 0: logging.debug("retry: " + str(n) + " of " + options["retry"]) if key in get_registration_keys(options, dev, fail=False): logging.debug("key " + key + " registered with device " + dev) return 0 else: logging.debug("key " + key + " not registered with device " + dev) if n < int(options["retry"]): time.sleep(float(options["retry-sleep"])) logging.debug("key " + key + " registered with any devices") if hardreboot == True: libc = ctypes.cdll['libc.so.6'] libc.reboot(0x1234567) return 2 def define_new_opts(): all_opt["devices"] = { "getopt" : "d:", "longopt" : "devices", "help" : "-d, --devices=[devices] List of devices to use for current operation", "required" : "0", "shortdesc" : "List of devices to use for current operation. Devices can \ be comma or space separated list of device-mapper multipath devices (eg. /dev/mapper/3600508b400105df70000e00000ac0000 or /dev/mapper/mpath1). \ Each device must support SCSI-3 persistent reservations.", "order": 1 } all_opt["key"] = { "getopt" : "k:", "longopt" : "key", "help" : "-k, --key=[key] Replaced by -n, --plug", "required" : "0", "shortdesc" : "Replaced by port/-n/--plug", "order": 1 } all_opt["suppress-errors"] = { "getopt" : "", "longopt" : "suppress-errors", "help" : "--suppress-errors Suppress error log. Suppresses error logging when run from the watchdog service before pacemaker starts.", "required" : "0", "shortdesc" : "Error log suppression.", "order": 4 } all_opt["mpathpersist_path"] = { "getopt" : ":", "longopt" : "mpathpersist-path", "help" : "--mpathpersist-path=[path] Path to mpathpersist binary", "required" : "0", "shortdesc" : "Path to mpathpersist binary", "default" : "@MPATH_PATH@", "order": 200 } all_opt["store_path"] = { "getopt" : ":", "longopt" : "store-path", "help" : "--store-path=[path] Path to directory containing cached keys", "required" : "0", "shortdesc" : "Path to directory where fence agent can store information", "default" : "@STORE_PATH@", "order": 200 } def main(): atexit.register(atexit_handler) device_opt = ["no_login", "no_password", "devices", "key", "sudo", \ "fabric_fencing", "on_target", "store_path", \ "suppress-errors", "mpathpersist_path", "force_on", "port", "no_port"] define_new_opts() all_opt["port"]["required"] = "0" all_opt["port"]["help"] = "-n, --plug=[key] Key to use for the current operation" all_opt["port"]["shortdesc"] = "Key to use for the current operation. \ This key should be unique to a node and have to be written in \ /etc/multipath.conf. For the \"on\" action, the key specifies the key use to \ register the local node. For the \"off\" action, this key specifies the key to \ be removed from the device(s)." # fence_mpath_check if os.path.basename(sys.argv[0]) == "fence_mpath_check": sys.exit(mpath_check()) elif os.path.basename(sys.argv[0]) == "fence_mpath_check_hardreboot": sys.exit(mpath_check(hardreboot=True)) options = check_input(device_opt, process_input(device_opt), other_conditions=True) # hack to remove list/list-status actions which are not supported options["device_opt"] = [ o for o in options["device_opt"] if o != "separator" ] # workaround to avoid regressions if "--key" in options: options["--plug"] = options["--key"] del options["--key"] elif "--help" not in options and options["--action"] in ["off", "on", \ "reboot", "status", "validate-all"] and "--plug" not in options: stop_after_error = False if options["--action"] == "validate-all" else True fail_usage("Failed: You have to enter plug number or machine identification", stop_after_error) docs = {} docs["shortdesc"] = "Fence agent for multipath persistent reservation" - docs["longdesc"] = "fence_mpath is an I/O fencing agent that uses SCSI-3 \ + docs["longdesc"] = "fence_mpath is an I/O Fencing agent that uses SCSI-3 \ persistent reservations to control access multipath devices. Underlying \ devices must support SCSI-3 persistent reservations (SPC-3 or greater) as \ well as the \"preempt-and-abort\" subcommand.\nThe fence_mpath agent works by \ having a unique key for each node that has to be set in /etc/multipath.conf. \ Once registered, a single node will become the reservation holder \ by creating a \"write exclusive, registrants only\" reservation on the \ device(s). The result is that only registered nodes may write to the \ device(s). When a node failure occurs, the fence_mpath agent will remove the \ key belonging to the failed node from the device(s). The failed node will no \ longer be able to write to the device(s). A manual reboot is required.\ \n.P\n\ When used as a watchdog device you can define e.g. retry=1, retry-sleep=2 and \ verbose=yes parameters in /etc/sysconfig/stonith if you have issues with it \ failing." docs["vendorurl"] = "https://www.sourceware.org/dm/" show_docs(options, docs) run_delay(options) # Input control BEGIN if options["--action"] == "validate-all": sys.exit(0) if not ("--devices" in options and options["--devices"]): fail_usage("Failed: No devices found") options["devices"] = [d for d in re.split("\s*,\s*|\s+", options["--devices"].strip()) if d] # Input control END result = fence_action(None, options, set_status, get_status) sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/netio/fence_netio.py b/agents/netio/fence_netio.py index 4fb59cff..fc3bf9d8 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]") 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"] = "I/O Fencing agent for Koukaam NETIO-230B" - docs["longdesc"] = "fence_netio is an I/O Fencing agent which can be \ + 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/openstack/fence_openstack.py b/agents/openstack/fence_openstack.py index 666016d7..e7aea086 100644 --- a/agents/openstack/fence_openstack.py +++ b/agents/openstack/fence_openstack.py @@ -1,381 +1,381 @@ #!@PYTHON@ -tt import atexit import logging import sys import os import urllib3 sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail_usage, run_delay, source_env try: from novaclient import client from novaclient.exceptions import Conflict, NotFound except ImportError: pass urllib3.disable_warnings(urllib3.exceptions.SecurityWarning) def translate_status(instance_status): if instance_status == "ACTIVE": return "on" elif instance_status == "SHUTOFF": return "off" return "unknown" def get_cloud(options): import yaml clouds_yaml = "~/.config/openstack/clouds.yaml" if not os.path.exists(os.path.expanduser(clouds_yaml)): clouds_yaml = "/etc/openstack/clouds.yaml" if not os.path.exists(os.path.expanduser(clouds_yaml)): fail_usage("Failed: ~/.config/openstack/clouds.yaml and /etc/openstack/clouds.yaml does not exist") clouds_yaml = os.path.expanduser(clouds_yaml) if os.path.exists(clouds_yaml): with open(clouds_yaml, "r") as yaml_stream: try: clouds = yaml.safe_load(yaml_stream) except yaml.YAMLError as exc: fail_usage("Failed: Unable to read: " + clouds_yaml) cloud = clouds.get("clouds").get(options["--cloud"]) if not cloud: fail_usage("Cloud: {} not found.".format(options["--cloud"])) return cloud def get_nodes_list(conn, options): logging.info("Running %s action", options["--action"]) result = {} response = conn.servers.list(detailed=True) if response is not None: for item in response: instance_id = item.id instance_name = item.name instance_status = item.status result[instance_id] = (instance_name, translate_status(instance_status)) return result def get_power_status(conn, options): logging.info("Running %s action on %s", options["--action"], options["--plug"]) server = None try: server = conn.servers.get(options["--plug"]) except NotFound as e: fail_usage("Failed: Not Found: " + str(e)) if server is None: fail_usage("Server %s not found", options["--plug"]) state = server.status status = translate_status(state) logging.info("get_power_status: %s (state: %s)" % (status, state)) return status def set_power_status(conn, options): logging.info("Running %s action on %s", options["--action"], options["--plug"]) action = options["--action"] server = None try: server = conn.servers.get(options["--plug"]) except NotFound as e: fail_usage("Failed: Not Found: " + str(e)) if server is None: fail_usage("Server %s not found", options["--plug"]) if action == "on": logging.info("Starting instance " + server.name) try: server.start() except Conflict as e: fail_usage(e) logging.info("Called start API call for " + server.id) if action == "off": logging.info("Stopping instance " + server.name) try: server.stop() except Conflict as e: fail_usage(e) logging.info("Called stop API call for " + server.id) if action == "reboot": logging.info("Rebooting instance " + server.name) try: server.reboot("HARD") except Conflict as e: fail_usage(e) logging.info("Called reboot hard API call for " + server.id) def nova_login(username, password, projectname, auth_url, user_domain_name, project_domain_name, ssl_insecure, cacert, apitimeout): legacy_import = False try: from keystoneauth1 import loading from keystoneauth1 import session as ksc_session from keystoneauth1.exceptions.discovery import DiscoveryFailure from keystoneauth1.exceptions.http import Unauthorized except ImportError: try: from keystoneclient import session as ksc_session from keystoneclient.auth.identity import v3 legacy_import = True except ImportError: fail_usage("Failed: Keystone client not found or not accessible") if not legacy_import: loader = loading.get_plugin_loader("password") auth = loader.load_from_options( auth_url=auth_url, username=username, password=password, project_name=projectname, user_domain_name=user_domain_name, project_domain_name=project_domain_name, ) else: auth = v3.Password( auth_url=auth_url, username=username, password=password, project_name=projectname, user_domain_name=user_domain_name, project_domain_name=project_domain_name, cacert=cacert, ) caverify=True if ssl_insecure: caverify=False elif cacert: caverify=cacert session = ksc_session.Session(auth=auth, verify=caverify, timeout=apitimeout) nova = client.Client("2", session=session, timeout=apitimeout) apiversion = None try: apiversion = nova.versions.get_current() except DiscoveryFailure as e: fail_usage("Failed: Discovery Failure: " + str(e)) except Unauthorized as e: fail_usage("Failed: Unauthorized: " + str(e)) except Exception as e: logging.error(e) logging.debug("Nova version: %s", apiversion) return nova def define_new_opts(): all_opt["auth-url"] = { "getopt": ":", "longopt": "auth-url", "help": "--auth-url=[authurl] Keystone Auth URL", "required": "0", "shortdesc": "Keystone Auth URL", "order": 2, } all_opt["project-name"] = { "getopt": ":", "longopt": "project-name", "help": "--project-name=[project] Tenant Or Project Name", "required": "0", "shortdesc": "Keystone Project", "default": "admin", "order": 3, } all_opt["user-domain-name"] = { "getopt": ":", "longopt": "user-domain-name", "help": "--user-domain-name=[domain] Keystone User Domain Name", "required": "0", "shortdesc": "Keystone User Domain Name", "default": "Default", "order": 4, } all_opt["project-domain-name"] = { "getopt": ":", "longopt": "project-domain-name", "help": "--project-domain-name=[domain] Keystone Project Domain Name", "required": "0", "shortdesc": "Keystone Project Domain Name", "default": "Default", "order": 5, } all_opt["cloud"] = { "getopt": ":", "longopt": "cloud", "help": "--cloud=[cloud] Openstack cloud (from ~/.config/openstack/clouds.yaml or /etc/openstack/clouds.yaml).", "required": "0", "shortdesc": "Cloud from clouds.yaml", "order": 6, } all_opt["openrc"] = { "getopt": ":", "longopt": "openrc", "help": "--openrc=[openrc] Path to the openrc config file", "required": "0", "shortdesc": "openrc config file", "order": 7, } all_opt["uuid"] = { "getopt": ":", "longopt": "uuid", "help": "--uuid=[uuid] Replaced by -n, --plug", "required": "0", "shortdesc": "Replaced by port/-n/--plug", "order": 8, } all_opt["cacert"] = { "getopt": ":", "longopt": "cacert", "help": "--cacert=[cacert] Path to the PEM file with trusted authority certificates (override global CA trust)", "required": "0", "shortdesc": "SSL X.509 certificates file", "default": "", "order": 9, } 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": 10, } def main(): conn = None device_opt = [ "login", "no_login", "passwd", "no_password", "auth-url", "project-name", "user-domain-name", "project-domain-name", "cloud", "openrc", "port", "no_port", "uuid", "ssl_insecure", "cacert", "apitimeout", ] atexit.register(atexit_handler) define_new_opts() all_opt["port"]["required"] = "0" all_opt["port"]["help"] = "-n, --plug=[UUID] UUID of the node to be fenced" all_opt["port"]["shortdesc"] = "UUID of the node to be fenced." all_opt["power_timeout"]["default"] = "60" options = check_input(device_opt, process_input(device_opt)) # workaround to avoid regressions if "--uuid" in options: options["--plug"] = options["--uuid"] del options["--uuid"] elif ("--help" not in options and options["--action"] in ["off", "on", "reboot", "status", "validate-all"] and "--plug" not in options): stop_after_error = False if options["--action"] == "validate-all" else True fail_usage( "Failed: You have to enter plug number or machine identification", stop_after_error, ) docs = {} docs["shortdesc"] = "Fence agent for OpenStack's Nova service" - docs["longdesc"] = "fence_openstack is a Fencing agent \ + docs["longdesc"] = "fence_openstack is a Power Fencing agent \ which can be used with machines controlled by the Openstack's Nova service. \ This agent calls the python-novaclient and it is mandatory to be installed " docs["vendorurl"] = "https://wiki.openstack.org/wiki/Nova" show_docs(options, docs) run_delay(options) if options.get("--cloud"): cloud = get_cloud(options) username = cloud.get("auth").get("username") password = cloud.get("auth").get("password") projectname = cloud.get("auth").get("project_name") auth_url = None try: auth_url = cloud.get("auth").get("auth_url") except KeyError: fail_usage("Failed: You have to set the Keystone service endpoint for authorization") user_domain_name = cloud.get("auth").get("user_domain_name") project_domain_name = cloud.get("auth").get("project_domain_name") caverify = cloud.get("verify") if caverify in [True, False]: options["--ssl-insecure"] = caverify else: options["--cacert"] = caverify elif options.get("--openrc"): if not os.path.exists(os.path.expanduser(options["--openrc"])): fail_usage("Failed: {} does not exist".format(options.get("--openrc"))) source_env(options["--openrc"]) env = os.environ username = env.get("OS_USERNAME") password = env.get("OS_PASSWORD") projectname = env.get("OS_PROJECT_NAME") auth_url = None try: auth_url = env["OS_AUTH_URL"] except KeyError: fail_usage("Failed: You have to set the Keystone service endpoint for authorization") user_domain_name = env.get("OS_USER_DOMAIN_NAME") project_domain_name = env.get("OS_PROJECT_DOMAIN_NAME") else: username = options["--username"] password = options["--password"] projectname = options["--project-name"] auth_url = None try: auth_url = options["--auth-url"] except KeyError: fail_usage("Failed: You have to set the Keystone service endpoint for authorization") user_domain_name = options["--user-domain-name"] project_domain_name = options["--project-domain-name"] ssl_insecure = "--ssl-insecure" in options cacert = options["--cacert"] apitimeout = options["--apitimeout"] try: conn = nova_login( username, password, projectname, auth_url, user_domain_name, project_domain_name, ssl_insecure, cacert, apitimeout, ) except Exception as e: fail_usage("Failed: Unable to connect to Nova: " + str(e)) # Operate the fencing device result = fence_action(conn, options, set_power_status, get_power_status, get_nodes_list) sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/ovh/fence_ovh.py b/agents/ovh/fence_ovh.py index 2b7eb864..f0ea67c6 100644 --- a/agents/ovh/fence_ovh.py +++ b/agents/ovh/fence_ovh.py @@ -1,164 +1,164 @@ #!@PYTHON@ -tt # Copyright 2013 Adrian Gibanel Lopez (bTactic) # Adrian Gibanel improved this script at 2013 to add verification of success and to output metadata # Based on: # This is a fence agent for use at OVH # As there are no other fence devices available, we must use OVH's SOAP API #Quick-and-dirty # assemled by Dennis Busch, secofor GmbH, Germany # This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License. import sys, time import shutil, tempfile import logging import atexit from datetime import datetime from suds.client import Client from suds.xsd.doctor import ImportDoctor, Import sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, fail_usage, EC_LOGIN_DENIED, run_delay OVH_RESCUE_PRO_NETBOOT_ID = '28' OVH_HARD_DISK_NETBOOT_ID = '1' STATUS_HARD_DISK_SLEEP = 240 # Wait 4 minutes to SO to boot STATUS_RESCUE_PRO_SLEEP = 150 # Wait 2 minutes 30 seconds to Rescue-Pro to run def define_new_opts(): all_opt["email"] = { "getopt" : "Z:", "longopt" : "email", "help" : "-Z, --email=[email] email for reboot message: admin@domain.com", "required" : "1", "shortdesc" : "Reboot email", "order" : 1} def netboot_reboot(conn, options, mode): # dedicatedNetbootModifyById changes the mode of the next reboot conn.service.dedicatedNetbootModifyById(options["session"], options["--plug"], mode, '', options["--email"]) # dedicatedHardRebootDo initiates a hard reboot on the given node conn.service.dedicatedHardRebootDo(options["session"], options["--plug"], 'Fencing initiated by cluster', '', 'en') conn.logout(options["session"]) def reboot_time(conn, options): result = conn.service.dedicatedHardRebootStatus(options["session"], options["--plug"]) tmpstart = datetime.strptime(result.start, '%Y-%m-%d %H:%M:%S') tmpend = datetime.strptime(result.end, '%Y-%m-%d %H:%M:%S') result.start = tmpstart result.end = tmpend return result def soap_login(options): imp = Import('http://schemas.xmlsoap.org/soap/encoding/') url = 'https://www.ovh.com/soapi/soapi-re-1.59.wsdl' imp.filter.add('http://soapi.ovh.com/manager') d = ImportDoctor(imp) tmp_dir = tempfile.mkdtemp() tempfile.tempdir = tmp_dir atexit.register(remove_tmp_dir, tmp_dir) try: soap = Client(url, doctor=d) session = soap.service.login(options["--username"], options["--password"], 'en', 0) except Exception as e: logging.error("Failed: {}".format(str(e))) fail(EC_LOGIN_DENIED) options["session"] = session return soap def remove_tmp_dir(tmp_dir): shutil.rmtree(tmp_dir) def main(): device_opt = ["login", "passwd", "port", "email", "no_status", "web"] atexit.register(atexit_handler) define_new_opts() options = check_input(device_opt, process_input(device_opt), other_conditions=True) docs = {} docs["shortdesc"] = "Fence agent for OVH" - docs["longdesc"] = "fence_ovh is an Power Fencing agent \ + docs["longdesc"] = "fence_ovh is a Power Fencing agent \ which can be used within OVH datecentre. \ Poweroff is simulated with a reboot into rescue-pro mode." docs["vendorurl"] = "http://www.ovh.net" show_docs(options, docs) if options["--action"] == "list": fail_usage("Action 'list' is not supported in this fence agent") if options["--action"] == "list-status": fail_usage("Action 'list-status' is not supported in this fence agent") if "--email" not in options: fail_usage("You have to enter e-mail address which is notified by fence agent") if options["--action"] == "validate-all": sys.exit(0) if options["--action"] != "monitor" and not options["--plug"].endswith(".ovh.net"): options["--plug"] += ".ovh.net" run_delay(options) conn = soap_login(options) if options["--action"] == 'monitor': try: conn.service.logout(options["session"]) except Exception: pass sys.exit(0) # Save datetime just before changing netboot before_netboot_reboot = datetime.now() if options["--action"] == 'off': # Reboot in Rescue-pro netboot_reboot(conn, options, OVH_RESCUE_PRO_NETBOOT_ID) time.sleep(STATUS_RESCUE_PRO_SLEEP) elif options["--action"] in ['on', 'reboot']: # Reboot from HD netboot_reboot(conn, options, OVH_HARD_DISK_NETBOOT_ID) time.sleep(STATUS_HARD_DISK_SLEEP) # Save datetime just after reboot after_netboot_reboot = datetime.now() # Verify that action was completed sucesfully reboot_t = reboot_time(conn, options) logging.debug("reboot_start_end.start: %s\n", reboot_t.start.strftime('%Y-%m-%d %H:%M:%S')) logging.debug("before_netboot_reboot: %s\n", before_netboot_reboot.strftime('%Y-%m-%d %H:%M:%S')) logging.debug("reboot_start_end.end: %s\n", reboot_t.end.strftime('%Y-%m-%d %H:%M:%S')) logging.debug("after_netboot_reboot: %s\n", after_netboot_reboot.strftime('%Y-%m-%d %H:%M:%S')) if reboot_t.start < after_netboot_reboot < reboot_t.end: result = 0 logging.debug("Netboot reboot went OK.\n") else: result = 1 logging.debug("ERROR: Netboot reboot wasn't OK.\n") try: conn.service.logout(options["session"]) except Exception: pass sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/powerman/fence_powerman.py b/agents/powerman/fence_powerman.py index 7aeeaf12..cdca5d36 100755 --- a/agents/powerman/fence_powerman.py +++ b/agents/powerman/fence_powerman.py @@ -1,257 +1,257 @@ #!@PYTHON@ -tt import os import time from datetime import datetime import sys import subprocess import re import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import is_executable, fail_usage, run_delay import logging #### important!!! ####### class PowerMan: """Python wrapper for calling powerman commands This class makes calls to a powerman deamon for a cluster of computers. The make-up of such a call looks something like: $ pm -h elssd1:10101