summaryrefslogtreecommitdiffstats
path: root/lib/plugins/stonith/external/riloe
diff options
context:
space:
mode:
Diffstat (limited to 'lib/plugins/stonith/external/riloe')
-rw-r--r--lib/plugins/stonith/external/riloe530
1 files changed, 530 insertions, 0 deletions
diff --git a/lib/plugins/stonith/external/riloe b/lib/plugins/stonith/external/riloe
new file mode 100644
index 0000000..ce98847
--- /dev/null
+++ b/lib/plugins/stonith/external/riloe
@@ -0,0 +1,530 @@
+#!/usr/bin/env python
+#
+# Stonith module for RILOE Stonith device
+#
+# Copyright (c) 2004 Alain St-Denis <alain.st-denis@ec.gc.ca>
+#
+# Modified by Alan Robertson <alanr@unix.sh> for STONITH external compatibility.
+#
+# Extended and merged by Tijl Van den broeck <subspawn@gmail.com>
+# with ilo-v2 script from Guy Coates
+#
+# Cleanup by Andrew Beekhof <abeekhof@suse.de>
+#
+# Rewritten by Dejan Muhamedagic <dejan@suse.de>
+# Now, the plugin actually reads replies from iLO.
+#
+# Extended by Jochen Roeder <jochen.roeder@novell.com>
+# to enable access via proxies
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+import sys
+import os
+import socket
+import subprocess
+import xml.dom.minidom
+import httplib
+import time
+import re
+
+def log_msg(level,msg):
+ subprocess.call("ha_log.sh %s '%s'" % (level,msg), shell=True)
+def my_err(msg):
+ log_msg("err", msg)
+def my_warn(msg):
+ log_msg("warn", msg)
+def my_debug(msg):
+ log_msg("debug", msg)
+def fatal(msg):
+ my_err(msg)
+ sys.exit(1)
+
+argv = sys.argv
+
+try:
+ cmd = argv[1]
+except IndexError:
+ my_err("Not enough arguments")
+ sys.exit(1)
+
+legacy_RI_HOST = os.environ.get('RI_HOST', '')
+legacy_RI_HOSTRI = os.environ.get('RI_HOSTRI', '')
+legacy_RI_LOGIN = os.environ.get('RI_LOGIN', 'Administrator')
+legacy_RI_PASSWORD = os.environ.get('RI_PASSWORD', '')
+
+reset_ok = os.environ.get('ilo_can_reset', '0')
+ilo_protocol = os.environ.get('ilo_protocol', '1.2')
+power_method = os.environ.get('ilo_powerdown_method', 'power')
+
+realhost = os.environ.get('hostlist', legacy_RI_HOST)
+rihost = os.environ.get('ilo_hostname', legacy_RI_HOSTRI)
+ilouser = os.environ.get('ilo_user', legacy_RI_LOGIN)
+ilopass = os.environ.get('ilo_password', legacy_RI_PASSWORD)
+iloproxyhost = os.environ.get('ilo_proxyhost', '')
+try:
+ iloproxyport = int(os.environ.get('ilo_proxyport', 3128))
+except ValueError:
+ my_err("ilo_proxyport is not a number")
+ sys.exit(1)
+
+xmlinfo = '''<parameters>
+ <parameter name="hostlist" unique="1" required="1">
+ <content type="string"/>
+ <shortdesc lang="en">ilo target hostname</shortdesc>
+ <longdesc lang="en">
+ Contains the hostname that the ilo controls
+ </longdesc>
+ </parameter>
+<parameter name="ilo_hostname" unique="1" required="1">
+ <content type="string"/>
+ <shortdesc lang="en">ilo device hostname</shortdesc>
+ <longdesc lang="en">
+ The hostname of the ilo device
+ </longdesc>
+ </parameter>
+<parameter name="ilo_user" unique="0" required="1">
+ <content type="string" default="Administrator"/>
+ <shortdesc lang="en">ilo user</shortdesc>
+ <longdesc lang="en">
+ The user for connecting to the ilo device
+ </longdesc>
+ </parameter>
+<parameter name="ilo_password" unique="0" required="1">
+ <content type="string" default=""/>
+ <shortdesc lang="en">password</shortdesc>
+ <longdesc lang="en">
+ The password for the ilo device user
+ </longdesc>
+ </parameter>
+<parameter name="ilo_can_reset" unique="0" required="0">
+ <content type="string" default="0"/>
+ <shortdesc lang="en">Device can reset</shortdesc>
+ <longdesc lang="en">
+ Does the ILO device support RESET commands (hint: older ones cannot)
+ </longdesc>
+ </parameter>
+<parameter name="ilo_protocol" unique="0" required="0">
+ <content type="string" default="1.2"/>
+ <shortdesc lang="en">ILO Protocol</shortdesc>
+ <longdesc lang="en">
+ Protocol version supported by the ILO device.
+ Known supported versions: 1.2, 2.0
+ </longdesc>
+ </parameter>
+<parameter name="ilo_powerdown_method" unique="0" required="0">
+ <content type="string" default="power"/>
+ <shortdesc lang="en">Power down method</shortdesc>
+ <longdesc lang="en">
+ The method to powerdown the host in question.
+ * button - Emulate holding down the power button
+ * power - Emulate turning off the machines power
+
+ NB: A button request takes around 20 seconds. The power method
+ about half a minute.
+ </longdesc>
+ </parameter>
+<parameter name="ilo_proxyhost" unique="0" required="0">
+ <content type="string" default=""/>
+ <shortdesc lang="en">Proxy hostname</shortdesc>
+ <longdesc lang="en">
+ proxy hostname if required to access ILO board
+ </longdesc>
+ </parameter>
+<parameter name="ilo_proxyport" unique="0" required="0">
+ <content type="string" default="3128"/>
+ <shortdesc lang="en">Proxy port</shortdesc>
+ <longdesc lang="en">
+ proxy port if required to access ILO board
+ parameter will be ignored if proxy hostname is not set
+ </longdesc>
+ </parameter>
+
+</parameters>'''
+
+info = {
+ 'getinfo-devid': 'iLO2',
+ 'getinfo-devname': 'ilo2 ' + rihost,
+ 'getinfo-devdescr': 'HP/COMPAQ iLO2 STONITH device',
+ 'getinfo-devurl': 'http://www.hp.com/',
+ 'gethosts': realhost,
+ 'getinfo-xml': xmlinfo
+}
+
+if cmd in info:
+ print info[cmd]
+ sys.exit(0)
+
+if cmd == 'getconfignames':
+ for arg in [ "hostlist", "ilo_hostname", "ilo_user", "ilo_password", "ilo_can_reset", "ilo_protocol", "ilo_powerdown_method", "ilo_proxyhost", "ilo_proxyport"]:
+ print arg
+ sys.exit(0)
+
+if not rihost:
+ fatal("ILO device hostname not specified")
+
+if not realhost:
+ fatal("Host controlled by this ILO device not specified")
+
+if not power_method in ("power","button"):
+ my_err('unknown power method %s, setting to "power"')
+ power_method = "power"
+
+# XML elements
+E_RIBCL = "RIBCL"
+E_LOGIN = "LOGIN"
+E_SERVER_INFO = "SERVER_INFO"
+
+# power mgmt methods
+E_RESET = "RESET_SERVER" # error if powered off
+E_COLD_BOOT = "COLD_BOOT_SERVER" # error if powered off
+E_WARM_BOOT = "WARM_BOOT_SERVER" # error if powered off
+E_PRESS_BUTTON = "PRESS_PWR_BTN"
+E_HOLD_BUTTON = "HOLD_PWR_BTN"
+
+# get/set status elements
+E_SET_POWER = "SET_HOST_POWER"
+E_GET_PSTATUS = "GET_HOST_POWER_STATUS"
+
+# whatever this means, but we have to use it to get good XML
+E_LOCFG = "LOCFG"
+LOCFG_VER = '2.21'
+
+# attributes
+A_VERSION = "VERSION" # ilo_protocol
+A_USER = "USER_LOGIN"
+A_PWD = "PASSWORD"
+A_MODE = "MODE" # info mode (read or write)
+A_POWER_SW = "HOST_POWER" # "Y" or "N"
+A_POWER_STATE = "HOST_POWER" # "ON" or "OFF"
+
+def new_power_req(tag, name = None, value = None):
+ '''
+ Create a new RIBCL request (as XML).
+ '''
+ my_debug("creating power request: %s,%s,%s"%(tag,name,value))
+ doc = xml.dom.minidom.Document()
+ locfg = doc.createElement(E_LOCFG)
+ locfg.setAttribute(A_VERSION,LOCFG_VER)
+ ribcl = doc.createElement(E_RIBCL)
+ ribcl.setAttribute(A_VERSION,ilo_protocol)
+ login = doc.createElement(E_LOGIN)
+ login.setAttribute(A_USER,ilouser)
+ login.setAttribute(A_PWD,ilopass)
+ serv_info = doc.createElement(E_SERVER_INFO)
+ # read or write, it doesn't really matter, i.e. even if we
+ # say "write" that doesn't mean we can't read
+ serv_info.setAttribute(A_MODE,"write")
+ doc.appendChild(locfg)
+ locfg.appendChild(ribcl)
+ ribcl.appendChild(login)
+ login.appendChild(serv_info)
+ el_node = doc.createElement(tag)
+ if name:
+ el_node.setAttribute(name,value)
+ serv_info.appendChild(el_node)
+ s = doc.toprettyxml()
+ doc.unlink()
+ # work around an iLO bug: last line containing "</LOCFG>"
+ # produces a syntax error
+ lines = s.split('\n')
+ return '\n'.join(lines[:-2])
+
+E_RESPONSE = "RESPONSE"
+E_HOST_POWER = "GET_HOST_POWER"
+A_STATUS = "STATUS"
+# documentation mentions both; better safe than sorry
+A_MSG = "MSG"
+A_MSG2 = "MESSAGE"
+
+def is_element(xmlnode):
+ return xmlnode.nodeType == xmlnode.ELEMENT_NODE
+
+def read_resp(node):
+ '''
+ Check if the RESPONSE XML is OK.
+ '''
+ msg = ""
+ str_status = ""
+ for attr in node.attributes.keys():
+ if attr == A_STATUS:
+ str_status = node.getAttribute(attr)
+ elif attr == A_MSG:
+ msg = node.getAttribute(attr)
+ elif attr == A_MSG2:
+ msg = node.getAttribute(attr)
+ else:
+ my_warn("unexpected attribute %s in %s" % (attr,E_RESPONSE))
+ if not str_status:
+ my_err("no status in response")
+ return -1
+ try:
+ status = int(str_status,16)
+ except ValueError:
+ my_err("unexpected status %s in response" % str_status)
+ return -1
+ if status != 0:
+ my_err("%s (rc: %s)"%(msg,str_status))
+ return -1
+ return 0
+
+def read_power(node):
+ '''
+ Read the power from the XML node. Set the global power
+ variable correspondingly.
+ '''
+ global power
+ for attr in node.attributes.keys():
+ if attr == A_POWER_STATE:
+ power_state = node.getAttribute(attr).upper()
+ else:
+ my_warn("unexpected attribute %s in %s" % (attr,node.tagName))
+ if not power_state:
+ my_err("no %s attribute in %s" % (A_POWER_STATE,node.tagName))
+ return -1
+ if power_state not in ("ON","OFF"):
+ my_err("unexpected value for %s: %s" % (A_POWER_STATE,power_state))
+ return -1
+ power = (power_state == "ON")
+ my_debug("Host has power: %s"%power)
+ return 0
+
+el_parsers = {
+ E_RESPONSE:read_resp,
+ E_HOST_POWER:read_power
+}
+def proc_resp(doc):
+ '''
+ Process one iLO reply. Real work is done in el_parsers.
+ '''
+ ribcl = doc.childNodes[0]
+ if not is_element(ribcl) or ribcl.tagName != E_RIBCL:
+ my_err("unexpected top element in response")
+ return -1
+ for child in ribcl.childNodes:
+ if not is_element(child):
+ continue
+ if child.tagName in el_parsers:
+ rc = el_parsers[child.tagName](child)
+ if rc != 0:
+ return -1
+ else:
+ my_warn("unexpected element in response: %s" % child.toxml())
+ return 0
+
+def open_ilo(host):
+ # open https connection
+ try:
+ if iloproxyhost != "" and iloproxyport != 0:
+ proxy=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
+ proxy.connect((iloproxyhost, iloproxyport))
+ proxy_connect='CONNECT %s:%s HTTP/1.1\r\n'%(host,443)
+ user_agent='User-Agent: python\r\n'
+ proxy_pieces=proxy_connect+user_agent+'\r\n'
+ proxy.sendall(proxy_pieces)
+ response=proxy.recv(8192)
+ status=response.split()[1]
+ if status!=str(200):
+ fatal("Error status=: %s" %(response))
+ import ssl
+ sock = ssl.wrap_socket(proxy)
+ h=httplib.HTTPConnection('localhost')
+ h.sock=sock
+ return h
+ else:
+ return httplib.HTTPSConnection(host)
+ except socket.gaierror, msg:
+ fatal("%s: %s" %(msg,host))
+ except socket.sslerror, msg:
+ fatal("%s for %s" %(msg,host))
+ except socket.error, msg:
+ fatal("%s while talking to %s" %(msg,host))
+ except ImportError, msg:
+ fatal("ssl support missing (%s)" %msg)
+
+def send_request(req,proc_f):
+ '''
+ 1. After every request, the iLO closes the connection.
+ 2. For every request, there are multiple replies. Each reply
+ is an XML document. Most of replies are just a kind of
+ (verbose) XML "OK".
+ '''
+ t_begin = time.time()
+ c = open_ilo(rihost)
+ try:
+ c.send(req+'\r\n')
+ except socket.error, msg:
+ fatal("%s, while talking to %s" %(msg,rihost))
+ t_end = time.time()
+ my_debug("request sent in %0.2f s" % ((t_end-t_begin)))
+
+ t_begin = time.time()
+ result = []
+ while True:
+ try:
+ reply = c.sock.recv(1024)
+ if not reply:
+ break
+ result.append(reply)
+ except socket.error, msg:
+ if msg[0] == 6: # connection closed
+ break
+ my_err("%s, while talking to %s" %(msg,rihost))
+ return -1
+ c.close()
+ t_end = time.time()
+
+ if not result:
+ fatal("no response from %s within %0.2f s"%(rihost,(t_end-t_begin)))
+ for reply in result:
+ # work around the iLO bug, i.e. element RIBCL closed twice
+ if re.search("</RIBCL", reply) and re.search("<RIBCL.*/>", reply):
+ reply = re.sub("<(RIBCL.*)/>", r"<\1>", reply)
+ try:
+ doc = xml.dom.minidom.parseString(reply)
+ except xml.parsers.expat.ExpatError,msg:
+ fatal("malformed response: %s\n%s"%(msg,reply))
+ rc = proc_f(doc)
+ doc.unlink()
+ if rc != 0:
+ break
+ my_debug("iLO processed request (rc=%d) in %0.2f s" % (rc,(t_end-t_begin)))
+ return rc
+
+def manage_power(cmd):
+ '''
+ Before trying to send a request we have to check the power
+ state.
+ '''
+ rc = 0
+ req = ''
+ # it won't do to turn it on if it's already on!
+ if cmd == "on" and not power:
+ req = new_power_req(E_SET_POWER,A_POWER_SW,"Y")
+ # also to turn it off if it's already off
+ elif cmd == "off" and power:
+ req = new_power_req(E_SET_POWER,A_POWER_SW,"N")
+ elif cmd == "cold_boot" and power:
+ req = new_power_req(E_COLD_BOOT)
+ elif cmd == "warm_boot" and power:
+ req = new_power_req(E_WARM_BOOT)
+ elif cmd == "reset":
+ if power:
+ req = new_power_req(E_RESET)
+ # reset doesn't work if the host's off
+ else:
+ req = new_power_req(E_SET_POWER,A_POWER_SW,"Y")
+ if req:
+ rc = send_request(req,proc_resp)
+ return rc
+def power_on():
+ '''
+ Update the power variable without checking the power state.
+ The iLO is slow at times to report the actual power state, so
+ we assume that it changed if the request succeeded.
+ '''
+ rc = manage_power("on")
+ if rc == 0:
+ global power
+ power = True
+ return rc
+def power_off():
+ rc = manage_power("off")
+ if rc == 0:
+ global power
+ power = False
+ return rc
+def cold_boot():
+ rc = manage_power("cold_boot")
+ return rc
+def warm_boot():
+ rc = manage_power("warm_boot")
+ return rc
+def reset():
+ rc = manage_power("reset")
+ if rc == 0:
+ global power
+ power = True
+ return rc
+def hold_button():
+ '''
+ Hold the power button. Got this error message when tried
+ without the TOGGLE attribute:
+ Command without TOGGLE="Yes" attribute is ignored
+ when host power is off. (rc: 0x0054)
+ Didn't find any documentation about TOGGLE.
+ '''
+ if power:
+ req = new_power_req(E_HOLD_BUTTON)
+ else:
+ req = new_power_req(E_HOLD_BUTTON,"TOGGLE","Yes")
+ rc = send_request(req,proc_resp)
+ return rc
+def read_power_state():
+ req = new_power_req(E_GET_PSTATUS)
+ rc = send_request(req,proc_resp)
+ return rc
+
+def reset_power():
+ '''
+ Three methods to reset:
+ - hold power button
+ - reset (only if host has power and user said that reset is ok)
+ - power off/on
+ '''
+ do_power_on = False
+ if power_method == 'button':
+ rc = hold_button()
+ elif reset_ok != '0':
+ if power:
+ return reset()
+ else:
+ return power_on()
+ else:
+ do_power_on = True
+ rc = power_off()
+ if rc == 0:
+ rc = read_power_state()
+ if do_power_on:
+ while rc == 0 and power: # wait for the power state to go off
+ time.sleep(5)
+ rc = read_power_state()
+ if rc == 0 and do_power_on and not power:
+ rc = power_on()
+ return rc
+
+# track state of host power
+power = -1
+
+todo = {
+'reset':reset_power,
+'on':power_on,
+'off':power_off,
+'cold':cold_boot,
+'warm':warm_boot,
+'status':lambda: 0 # just return 0, we already read the state
+}
+
+rc = read_power_state()
+if rc == 0:
+ if cmd in todo:
+ rc = todo[cmd]()
+ else:
+ fatal('Invalid command: %s' % cmd)
+if rc != 0:
+ fatal("request failed")
+sys.exit(rc)
+
+# vi:ts=4:sw=4:et: