diff options
Diffstat (limited to '')
-rw-r--r-- | agents/amt/fence_amt.py | 128 | ||||
-rwxr-xr-x | agents/amt_ws/fence_amt_ws.py | 240 |
2 files changed, 368 insertions, 0 deletions
diff --git a/agents/amt/fence_amt.py b/agents/amt/fence_amt.py new file mode 100644 index 0000000..feec6e3 --- /dev/null +++ b/agents/amt/fence_amt.py @@ -0,0 +1,128 @@ +#!@PYTHON@ -tt + +import sys, re, os +import atexit +from pipes import quote +sys.path.append("@FENCEAGENTSLIBDIR@") +from fencing import * +from fencing import fail_usage, is_executable, run_command, run_delay + +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 \ +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 new file mode 100755 index 0000000..5e7452a --- /dev/null +++ b/agents/amt_ws/fence_amt_ws.py @@ -0,0 +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 \ +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() |