summaryrefslogtreecommitdiffstats
path: root/lib/plugins/stonith/external
diff options
context:
space:
mode:
Diffstat (limited to 'lib/plugins/stonith/external')
-rw-r--r--lib/plugins/stonith/external/Makefile.am33
-rw-r--r--lib/plugins/stonith/external/drac5.in113
-rw-r--r--lib/plugins/stonith/external/dracmc-telnet377
-rwxr-xr-xlib/plugins/stonith/external/hetzner139
-rw-r--r--lib/plugins/stonith/external/hmchttp218
-rw-r--r--lib/plugins/stonith/external/ibmrsa157
-rw-r--r--lib/plugins/stonith/external/ibmrsa-telnet320
-rw-r--r--lib/plugins/stonith/external/ipmi276
-rwxr-xr-xlib/plugins/stonith/external/ippower9258.in316
-rw-r--r--lib/plugins/stonith/external/kdumpcheck.in274
-rw-r--r--lib/plugins/stonith/external/libvirt298
-rw-r--r--lib/plugins/stonith/external/nut302
-rw-r--r--lib/plugins/stonith/external/rackpdu280
-rw-r--r--lib/plugins/stonith/external/riloe530
-rw-r--r--lib/plugins/stonith/external/ssh.in176
-rwxr-xr-xlib/plugins/stonith/external/vcenter280
-rw-r--r--lib/plugins/stonith/external/vmware216
-rw-r--r--lib/plugins/stonith/external/xen0253
-rwxr-xr-xlib/plugins/stonith/external/xen0-ha-dom0-stonith-helper72
-rwxr-xr-xlib/plugins/stonith/external/xen0-ha.in96
20 files changed, 4726 insertions, 0 deletions
diff --git a/lib/plugins/stonith/external/Makefile.am b/lib/plugins/stonith/external/Makefile.am
new file mode 100644
index 0000000..42e0046
--- /dev/null
+++ b/lib/plugins/stonith/external/Makefile.am
@@ -0,0 +1,33 @@
+# Makefile.am for OCF RAs
+#
+# Author: Sun Jing Dong
+# Copyright (C) 2004 IBM
+#
+# 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; either version 2
+# of the License, or (at your option) any later version.
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+MAINTAINERCLEANFILES = Makefile.in
+
+EXTRA_DIST = drac5 dracmc-telnet ibmrsa-telnet ipmi rackpdu vmware vcenter xen0 \
+ xen0-ha-dom0-stonith-helper kdumpcheck nut
+
+extdir = $(stonith_ext_plugindir)
+
+helperdir = $(stonith_plugindir)
+
+ext_SCRIPTS = drac5 dracmc-telnet ibmrsa ibmrsa-telnet ipmi riloe ssh vmware vcenter rackpdu xen0 hmchttp \
+ xen0-ha kdumpcheck ippower9258 nut libvirt \
+ hetzner
+
+helper_SCRIPTS = xen0-ha-dom0-stonith-helper
diff --git a/lib/plugins/stonith/external/drac5.in b/lib/plugins/stonith/external/drac5.in
new file mode 100644
index 0000000..218cbd3
--- /dev/null
+++ b/lib/plugins/stonith/external/drac5.in
@@ -0,0 +1,113 @@
+#!/bin/sh
+#
+# External STONITH module for DRAC5 adapters.
+#
+# Author: Jun Wang
+# License: GNU General Public License (GPL)
+#
+
+trap 'if [ -n "$outf" ]; then ha_log.sh err "`cat $outf`"; rm -f "$outf"; fi' 0
+outf=`mktemp` || {
+ ha_log.sh err "mktemp failed"
+ exit 1
+}
+
+sshlogin() {
+ if [ x = "x$ipaddr" -o x = "x$userid" ]
+ then
+ ha_log.sh err "ipaddr or userid missing; check configuration"
+ return 1
+ fi
+ @SSH@ -q -x -n $userid@$ipaddr racadm serveraction "$1" >$outf 2>&1
+}
+
+drac_reset() {
+ sshlogin hardreset
+}
+
+drac_on() {
+ sshlogin poweron
+}
+
+drac_off() {
+ sshlogin poweroff
+}
+
+drac_status() {
+ sshlogin powerstatus
+}
+
+case $1 in
+gethosts)
+ echo $hostname
+ ;;
+on)
+ drac_poweron
+ ;;
+off)
+ drac_poweroff
+ ;;
+reset)
+ drac_reset
+ ;;
+status)
+ drac_status
+ ;;
+getconfignames)
+ for i in hostname ipaddr userid; do
+ echo $i
+ done
+ ;;
+getinfo-devid)
+ echo "DRAC5 STONITH device"
+ ;;
+getinfo-devname)
+ echo "DRAC5 STONITH device"
+ ;;
+getinfo-devdescr)
+ echo "DRAC5 host reset/poweron/poweroff"
+ ;;
+getinfo-devurl)
+ echo "http://www.dell.com"
+ ;;
+getinfo-xml)
+ cat <<EOF
+<parameters>
+
+<parameter name="hostname" unique="1">
+<content type="string" />
+<shortdesc lang="en">
+Hostname
+</shortdesc>
+<longdesc lang="en">
+The hostname of the host to be managed by this STONITH device
+</longdesc>
+</parameter>
+
+<parameter name="ipaddr" unique="1">
+<content type="string" />
+<shortdesc lang="en">
+IP Address
+</shortdesc>
+<longdesc lang="en">
+The IP address of the STONITH device
+</longdesc>
+</parameter>
+
+<parameter name="userid" unique="1">
+<content type="string" />
+<shortdesc lang="en">
+Login
+</shortdesc>
+<longdesc lang="en">
+The username used for logging in to the STONITH device
+</longdesc>
+</parameter>
+
+</parameters>
+EOF
+ ;;
+*)
+ exit 1
+ ;;
+esac
diff --git a/lib/plugins/stonith/external/dracmc-telnet b/lib/plugins/stonith/external/dracmc-telnet
new file mode 100644
index 0000000..d993961
--- /dev/null
+++ b/lib/plugins/stonith/external/dracmc-telnet
@@ -0,0 +1,377 @@
+#!/usr/bin/env python
+# vim: set filetype=python
+#######################################################################
+#
+# dracmc-telnet - External stonith plugin for HAv2 (http://linux-ha.org/wiki)
+# Connects to Dell Drac/MC Blade Enclosure via a Cyclades
+# terminal server with telnet and switches power of named
+# blade servers appropriatelly.
+#
+# Required parameters:
+# nodename: The name of the server you want to touch on your network
+# cyclades_ip: The IP address of the cyclades terminal server
+# cyclades_port: The port for telnet to access on the cyclades (i.e. 7032)
+# servername: The DRAC/MC server name of the blade (i.e. Server-7)
+# username: The login user name for the DRAC/MC
+# password: The login password for the DRAC/MC
+#
+# Author: Alex Tsariounov <alext@novell.com>
+#
+# Based on ibmrsa-telnet external stonith plugin by Andreas Mock
+# (andreas.mock@web.de), Copyright by Adreas Mock and released as part
+# of HAv2.
+#
+# History:
+# 2009-10-12 First release.
+#
+# Copyright (c) 2009 Novell, Inc.
+# All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of version 2 or later of the GNU General Public
+# License as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it would be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#
+# Further, this software is distributed without any warranty that it is
+# free of the rightful claim of any third person regarding infringement
+# or the like. Any license provided herein, whether implied or
+# otherwise, applies only to this software file. Patent licenses, if
+# any, provided herein do not apply to combinations of this program with
+# other software, or any other product whatsoever.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write the Free Software Foundation,
+# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
+#
+#######################################################################
+import sys
+import os
+import time
+import telnetlib
+import random
+import subprocess
+
+LOGINRETRIES = 10
+
+class TimeoutException(Exception):
+ def __init__(self, value=None):
+ Exception.__init__(self)
+ self.value = value
+
+ def __str__(self):
+ return repr(self.value)
+
+class DracMC(telnetlib.Telnet):
+ def __init__(self, *args, **kwargs):
+ telnetlib.Telnet.__init__(self)
+ self._timeout = 4
+ self._loggedin = 0
+ self._history = []
+ self._appl = os.path.basename(sys.argv[0])
+ self._server = args[0]
+
+ def _get_timestamp(self):
+ ct = time.time()
+ msecs = (ct - long(ct)) * 1000
+ return "%s,%03d" % (time.strftime("%Y-%m-%d %H:%M:%S",
+ time.localtime(ct)), msecs)
+
+ def write(self, buffer):
+ self._history.append(self._get_timestamp() + ': WRITE: ' + repr(buffer))
+ telnetlib.Telnet.write(self, buffer)
+
+ def read_until(self, what, timeout=2):
+ line = telnetlib.Telnet.read_until(self, what, timeout)
+ self._history.append(self._get_timestamp() + ': READ : ' + repr(line))
+ if not line.endswith(what):
+ raise TimeoutException("Timeout while waiting for '%s'." % (what, ))
+ return line
+
+ def login(self, user, passwd):
+ time.sleep(0.3)
+ try:
+ line = self.read_until('Login: ', self._timeout)
+ self.write(user)
+ self.write('\r')
+ line = self.read_until('Password: ', self._timeout)
+ self.write(passwd)
+ self.write('\r')
+ except:
+ self.write("\r")
+ line = self.read_until('Login: ', self._timeout)
+ self.write(user)
+ self.write('\r')
+ line = self.read_until('Password: ', self._timeout)
+ self.write(passwd)
+ self.write('\r')
+ try:
+ line = self.read_until('DRAC/MC:', self._timeout)
+ except:
+ self.write("\r")
+ line = self.read_until('DRAC/MC:', self._timeout)
+
+ def hardreset(self):
+ self.write('serveraction -s %s hardreset\r' % self._server)
+ line = self.read_until('OK', 10)
+ line = self.read_until('DRAC/MC:', self._timeout)
+
+ def powercycle(self):
+ self.write('serveraction -s %s powercycle\r' % self._server)
+ line = self.read_until('OK', 10)
+ line = self.read_until('DRAC/MC:', self._timeout)
+
+ def on(self):
+ self.write('serveraction -s %s powerup\r' % self._server)
+ line = self.read_until('OK', 10)
+ line = self.read_until('DRAC/MC:', self._timeout)
+
+ def off(self):
+ self.write('serveraction -s %s powerdown\r' % self._server)
+ line = self.read_until('OK', 10)
+ line = self.read_until('DRAC/MC:', self._timeout)
+
+ def exit(self):
+ self.write('exit\r')
+
+ def get_history(self):
+ return "\n".join(self._history)
+
+
+class DracMCStonithPlugin:
+ def __init__(self):
+ # define the external stonith plugin api
+ self._required_cmds = \
+ 'reset gethosts status getconfignames getinfo-devid ' \
+ 'getinfo-devname getinfo-devdescr getinfo-devurl ' \
+ 'getinfo-xml'
+ self._optional_cmds = 'on off'
+ self._required_cmds_list = self._required_cmds.split()
+ self._optional_cmds_list = self._optional_cmds.split()
+
+ # who am i
+ self._appl = os.path.basename(sys.argv[0])
+
+ # telnet connection object
+ self._connection = None
+
+ # the list of configuration names
+ self._confignames = ['nodename', 'cyclades_ip', 'cyclades_port',
+ 'servername', 'username', 'password']
+
+ # catch the parameters provided by environment
+ self._parameters = {}
+ for name in self._confignames:
+ try:
+ self._parameters[name] = os.environ.get(name, '').split()[0]
+ except IndexError:
+ self._parameters[name] = ''
+
+ def _get_timestamp(self):
+ ct = time.time()
+ msecs = (ct - long(ct)) * 1000
+ return "%s,%03d" % (time.strftime("%Y-%m-%d %H:%M:%S",
+ time.localtime(ct)), msecs)
+
+ def _echo_debug(self, *args):
+ subprocess.call("ha_log.sh debug '%s'" % ' '.join(args), shell=True)
+
+ def echo(self, *args):
+ what = ''.join([str(x) for x in args])
+ sys.stdout.write(what)
+ sys.stdout.write('\n')
+ sys.stdout.flush()
+ self._echo_debug("STDOUT:", what)
+
+ def echo_log(self, level, *args):
+ subprocess.call("ha_log.sh %s '%s'" % (level,' '.join(args)), shell=True)
+
+ def _get_connection(self):
+ if not self._connection:
+ c = DracMC(self._parameters['servername'])
+ self._echo_debug("Connecting to '%s:%s'" %
+ (self._parameters['cyclades_ip'],
+ self._parameters['cyclades_port']))
+ tries = 0
+ while tries < LOGINRETRIES:
+ try:
+ c.open(self._parameters['cyclades_ip'],
+ self._parameters['cyclades_port'])
+ c.login(self._parameters['username'],
+ self._parameters['password'])
+ except Exception, args:
+ if "Connection reset by peer" in str(args):
+ self._echo_debug("Someone is already logged in... retry=%s" % tries)
+ c.close()
+ time.sleep(random.uniform(1.0, 5.0))
+ else:
+ raise
+ else:
+ break
+ tries += 1
+
+ if tries == LOGINRETRIES:
+ c.close()
+ raise Exception("Could not log in to %s:%s" %
+ (self._parameters['cyclades_ip'],
+ self._parameters['cyclades_port']))
+ self._connection = c
+
+ def _end_connection(self):
+ if self._connection:
+ self._connection.exit()
+ self._connection.close()
+
+ def reset(self):
+ self._get_connection()
+ # self._connection.hardreset()
+ self._connection.powercycle()
+ self._end_connection()
+ self._echo_debug(self._connection.get_history())
+ self.echo_log("info", "Reset of node '%s' done" %
+ (self._parameters['nodename'],))
+ return(0)
+
+ def on(self):
+ self._get_connection()
+ self._connection.on()
+ self._end_connection()
+ self._echo_debug(self._connection.get_history())
+ self.echo_log("info", "Switched node '%s' ON" %
+ (self._parameters['nodename'],))
+ return(0)
+
+ def off(self):
+ self._get_connection()
+ self._connection.off()
+ self._end_connection()
+ self._echo_debug(self._connection.get_history())
+ self.echo_log("info", "Switched node '%s' OFF" %
+ (self._parameters['nodename'],))
+ return(0)
+
+ def gethosts(self):
+ self.echo(self._parameters['nodename'])
+ return(0)
+
+ def status(self):
+ self._get_connection()
+ self._end_connection()
+ self._echo_debug(self._connection.get_history())
+ return(0)
+
+ def getconfignames(self):
+ for name in ['nodename', 'cyclades_ip', 'cyclades_port', 'servername',
+ 'username', 'password']:
+ self.echo(name)
+ return(0)
+
+ def getinfo_devid(self):
+ self.echo("External Stonith Plugin for Dell DRAC/MC via Cyclades")
+ return(0)
+
+ def getinfo_devname(self):
+ self.echo("External Stonith Plugin for Dell Drac/MC connecting "
+ "via Telnet to a Cyclades port")
+ return(0)
+
+ def getinfo_devdescr(self):
+ self.echo("External stonith plugin for HAv2 which connects to "
+ "a Dell DRAC/MC connected via a Cyclades port with telnet. "
+ "Commands to turn on/off power and to reset server are sent "
+ "appropriately. "
+ "(c) 2009 by Novell, Inc. (alext@novell.com)")
+ return(0)
+
+ def getinfo_devurl(self):
+ self.echo("http://support.dell.com/support/edocs/software/smdrac3/dracmc/1.3/en/index.htm")
+
+ def getinfo_xml(self):
+ info = """<parameters>
+ <parameter name="nodename" unique="1" required="1">
+ <content type="string" />
+ <shortdesc lang="en">nodename to shoot</shortdesc>
+ <longdesc lang="en">
+ Name of the node to be stonithed.
+ </longdesc>
+ </parameter>
+ <parameter name="cyclades_ip" unique="1" required="1">
+ <content type="string" />
+ <shortdesc lang="en">hostname or ip address of cyclades</shortdesc>
+ <longdesc lang="en">
+ Hostname or IP address of Cyclades connected to DRAC/MC.
+ </longdesc>
+ </parameter>
+ <parameter name="cyclades_port" unique="1" required="1">
+ <content type="string" />
+ <shortdesc lang="en">telnet port to use on cyclades</shortdesc>
+ <longdesc lang="en">
+ Port used with the Cyclades telnet interface which is connected to the DRAC/MC.
+ </longdesc>
+ </parameter>
+ <parameter name="servername" unique="1" required="1">
+ <content type="string" />
+ <shortdesc lang="en">DRAC/MC name of blade to be stonithed</shortdesc>
+ <longdesc lang="en">
+ Name of server blade to be stonithed on the DRAC/MC (example: Server-7)
+ </longdesc>
+ </parameter>
+ <parameter name="username" unique="1" required="1">
+ <content type="string" />
+ <shortdesc lang="en">username to login on the DRAC/MC</shortdesc>
+ <longdesc lang="en">
+ Username to login to the DRAC/MC once connected via the Cyclades port.
+ </longdesc>
+ </parameter>
+ <parameter name="password" unique="1" required="1">
+ <content type="string" />
+ <shortdesc lang="en">password to login on the DRAC/MC</shortdesc>
+ <longdesc lang="en">
+ Password to login to the DRAC/MC once connected via the Cyclades port.
+ </longdesc>
+ </parameter>
+ </parameters>
+ """
+ self.echo(info)
+ return(0)
+
+ def not_implemented(self, cmd):
+ self.echo_log("err", "Command '%s' not implemented." % (cmd,))
+ return(1)
+
+ def usage(self):
+ usage = "Call me with one of the allowed commands: %s, %s" % (
+ ', '.join(self._required_cmds_list),
+ ', '.join(self._optional_cmds_list))
+ return usage
+
+ def process(self, argv):
+ self._echo_debug("========== Start =============")
+ if len(argv) < 1:
+ self.echo_log("err", 'At least one commandline argument required.')
+ return(1)
+ cmd = argv[0]
+ self._echo_debug("cmd:", cmd)
+ if cmd not in self._required_cmds_list and \
+ cmd not in self._optional_cmds_list:
+ self.echo_log("err", "Command '%s' not supported." % (cmd,))
+ return(1)
+ try:
+ cmd = cmd.lower().replace('-', '_')
+ func = getattr(self, cmd, self.not_implemented)
+ rc = func()
+ return(rc)
+ except Exception, args:
+ self.echo_log("err", 'Exception raised:', str(args))
+ if self._connection:
+ self.echo_log("err", self._connection.get_history())
+ self._connection.close()
+ return(1)
+
+
+if __name__ == '__main__':
+ stonith = DracMCStonithPlugin()
+ rc = stonith.process(sys.argv[1:])
+ sys.exit(rc)
diff --git a/lib/plugins/stonith/external/hetzner b/lib/plugins/stonith/external/hetzner
new file mode 100755
index 0000000..2b3e675
--- /dev/null
+++ b/lib/plugins/stonith/external/hetzner
@@ -0,0 +1,139 @@
+#!/bin/sh
+#
+# External STONITH module for Hetzner.
+#
+# Copyright (c) 2011 MMUL S.a.S. - Raoul Scarazzini <rasca@mmul.it>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of version 2 of the GNU General Public License as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it would be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#
+# Further, this software is distributed without any warranty that it is
+# free of the rightful claim of any third person regarding infringement
+# or the like. Any license provided herein, whether implied or
+# otherwise, applies only to this software file. Patent licenses, if
+# any, provided herein do not apply to combinations of this program with
+# other software, or any other product whatsoever.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write the Free Software Foundation,
+# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
+#
+
+# Read parameters from config file, format is based upon the hetzner OCF resource agent
+# developed by Kumina: http://blog.kumina.nl/2011/02/hetzner-failover-ip-ocf-script/
+conf_file="/etc/hetzner.cfg"
+
+case $1 in
+ get*) ;; # don't print errors if conf_file not present
+ *)
+ user=`sed -n 's/^user.*=\ *//p' $conf_file`
+ pass=`sed -n 's/^pass.*=\ *//p' $conf_file`
+ ;;
+esac
+
+hetzner_server="https://robot-ws.your-server.de"
+
+check_http_response() {
+ # If the response is 200 then return 0
+ if [ $1 = 200 ]
+ then
+ return 0
+ else
+ # If the response is not 200 then display a description of the problem and return 1
+ case $1 in
+ 400) ha_log.sh err "INVALID_INPUT - Invalid input parameters"
+ ;;
+ 404) ha_log.sh err "SERVER_NOT_FOUND - Server with ip $remote_ip not found"
+ ;;
+ 409) ha_log.sh err "RESET_MANUAL_ACTIVE - There is already a running manual reset"
+ ;;
+ 500) ha_log.sh err "RESET_FAILED - Resetting failed due to an internal error"
+ ;;
+ esac
+ return 1
+ fi
+}
+
+case $1 in
+gethosts)
+ echo $hostname
+ exit 0
+ ;;
+on)
+ # Can't really be implemented because Hetzner's webservice cannot power on a system
+ ha_log.sh err "Power on is not available since Hetzner's webservice can't do this operation."
+ exit 1
+ ;;
+off)
+ # Can't really be implemented because Hetzner's webservice cannot power on a system
+ ha_log.sh err "Power off is not available since Hetzner's webservice can't do this operation."
+ exit 1
+ ;;
+reset)
+ # Launching the reset action via webservice
+ check_http_response $(curl --silent -o /dev/null -w '%{http_code}' -u $user:$pass $hetzner_server/reset/$remote_ip -d type=hw)
+ exit $?
+ ;;
+status)
+ # Check if we can contact the webservice
+ check_http_response "$(curl --silent -o /dev/null -w '%{http_code}' -u $user:$pass $hetzner_server/server/$remote_ip)"
+ exit $?
+ ;;
+getconfignames)
+ echo "hostname"
+ echo "remote_ip"
+ exit 0
+ ;;
+getinfo-devid)
+ echo "Hetzner STONITH device"
+ exit 0
+ ;;
+getinfo-devname)
+ echo "Hetzner STONITH external device"
+ exit 0
+ ;;
+getinfo-devdescr)
+ echo "Hetzner host reset"
+ echo "Manages the remote webservice for reset a remote server."
+ exit 0
+ ;;
+getinfo-devurl)
+ echo "http://wiki.hetzner.de/index.php/Robot_Webservice_en"
+ exit 0
+ ;;
+getinfo-xml)
+ cat << HETZNERXML
+<parameters>
+<parameter name="hostname" unique="1" required="1">
+<content type="string" />
+<shortdesc lang="en">
+Hostname
+</shortdesc>
+<longdesc lang="en">
+The name of the host to be managed by this STONITH device.
+</longdesc>
+</parameter>
+
+<parameter name="remote_ip" unique="1" required="1">
+<content type="string" />
+<shortdesc lang="en">
+Remote IP
+</shortdesc>
+<longdesc lang="en">
+The address of the remote IP that manages this server.
+</longdesc>
+</parameter>
+</parameters>
+HETZNERXML
+ exit 0
+ ;;
+*)
+ ha_log.sh err "Don't know what to do for '$remote_ip'"
+ exit 1
+ ;;
+esac
diff --git a/lib/plugins/stonith/external/hmchttp b/lib/plugins/stonith/external/hmchttp
new file mode 100644
index 0000000..9d111bc
--- /dev/null
+++ b/lib/plugins/stonith/external/hmchttp
@@ -0,0 +1,218 @@
+#!/bin/sh
+# External STONITH module for HMC web console
+#
+# Copyright (c) 2007 Xinwei Hu <hxinwei@gmail.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of version 2 of the GNU General Public License as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it would be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#
+# Further, this software is distributed without any warranty that it is
+# free of the rightful claim of any third person regarding infringement
+# or the like. Any license provided herein, whether implied or
+# otherwise, applies only to this software file. Patent licenses, if
+# any, provided herein do not apply to combinations of this program with
+# other software, or any other product whatsoever.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write the Free Software Foundation,
+# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
+#
+
+#set -x
+hostlist=`echo $hostlist | tr ',' ' '`
+
+trap '[ ! -e "$COOKIEFILE" ] || rm -f "$COOKIEFILE"' 0
+COOKIEFILE=`mktemp` || exit 1
+
+: ${CURLBIN="/usr/bin/curl"}
+: ${user=admin}
+: ${password=admin}
+
+check_parameter() {
+ if [ ! -x $CURLBIN ]
+ then
+ ha_log.sh err "Curl can't be found in normal place. Set CURLBIN to override the default value"
+ exit 1
+ fi
+
+ if [ -z $hmc_ipaddr ]
+ then
+ ha_log.sh err "The address of HMC web console is not specified"
+ exit 1
+ fi
+}
+
+HMCUSERNAME=$user
+HMCPASSWORD=$password
+
+HMC_LOGIN_COMMAND="$CURLBIN -3 -k -c $COOKIEFILE -d user=$HMCUSERNAME -d password=$HMCPASSWORD -d lang=0 -d submit=Log+in "
+HMC_LOGOUT_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d submit=Log+out "
+HMC_TOC_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d form=2 "
+HMC_POWERON_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d form=60 -d sp=255 -d is=0 -d om=4 -d id=1 -d ip=2 -d plt=1 -d pm=0 -d on=Save+settings+and+power+on "
+HMC_POWERSTATE_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d form=60 "
+HMC_POWEROFF_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d form=30 -d submit=Continue "
+HMC_REBOOT_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d form=74 -d submit=Continue "
+
+hmc_login() {
+ iamin=0
+ while [ $iamin -eq 0 ]; do
+ $HMC_LOGIN_COMMAND https://$hmc_ipaddr/cgi-bin/cgi >/dev/null 2>&1
+ $HMC_TOC_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null | grep -q "Too many users"
+ iamin=$?
+ sleep 2
+ done
+}
+hmc_logout() {
+ $HMC_LOGOUT_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null
+}
+
+hmc_reboot() {
+ check_parameter
+ $HMC_REBOOT_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null
+}
+hmc_poweron() {
+ r=1
+ while [ 0 -ne $r ]; do
+ $HMC_POWERON_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null | grep -q "Operation completed successfully"
+ r=$?
+ done
+}
+hmc_poweroff() {
+ check_parameter
+ $HMC_POWEROFF_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null
+}
+hmc_powerstate() {
+ check_parameter
+ r=`$HMC_POWERSTATE_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null| grep "Current system power state:" | sed 's/<br>//g' | awk '{print $5}'`
+ echo $r
+}
+
+hmc_poweroffon() {
+ check_parameter
+ hmc_poweroff
+ while [ 1 ]; do
+ r=`hmc_powerstate`
+ ha_log.sh debug "power state: $r"
+ if [ $r = "Off" ]; then
+ break
+ fi
+ sleep 5
+ done
+ sleep 3
+ hmc_poweron
+}
+
+case $1 in
+gethosts)
+ for h in $hostlist; do
+ echo $h
+ done
+ exit 0
+ ;;
+status)
+ if
+ ping -w1 -c1 "$hmc_ipaddr" 2>&1
+ then
+ exit 0
+ fi
+ exit 1
+ ;;
+getconfignames)
+ for f in hostlist hmc_ipaddr user password; do
+ echo $f
+ done
+ exit 0
+ ;;
+getinfo-devid)
+ echo "HMC web console STONITH device"
+ exit 0
+ ;;
+getinfo-devname)
+ echo "HMC web console STONITH external device"
+ exit 0
+ ;;
+getinfo-devdescr)
+ echo "HMC web console based host power control"
+ echo "Use for i5, p5, pSeries and OpenPower systems that are managed via "
+ echo "web console through a direct connection to system's HMC port."
+ exit 0
+ ;;
+getinfo-devurl)
+ echo "http://www.ibm.com"
+ exit 0
+ ;;
+getinfo-xml)
+ cat << HMCXML
+<parameters>
+
+<parameter name="hostlist" unique="1" required="1">
+<content type="string"/>
+<shortdesc lang="en">Hostlist</shortdesc>
+<longdesc lang="en">
+The list of hosts that the STONITH device controls
+</longdesc>
+</parameter>
+
+<parameter name="hmc_ipaddr" unique="1" required="1">
+<content type="string"/>
+<shortdesc lang="en">HMC IPAddr</shortdesc>
+<longdesc lang="en">
+The IP address of the HMC web console
+</longdesc>
+</parameter>
+
+<parameter name="user" unique="1" required="1">
+<content type="string"/>
+<shortdesc lang="en">User</shortdesc>
+<longdesc lang="en">
+User name to log into HMC web console
+</longdesc>
+</parameter>
+
+<parameter name="password" unique="1" required="1">
+<content type="string"/>
+<shortdesc lang="en">Password</shortdesc>
+<longdesc lang="en">
+The password of user name to log into HMC web console
+</longdesc>
+</parameter>
+
+</parameters>
+HMCXML
+ exit 0
+ ;;
+esac
+
+case $1 in
+on|off|reset|powerstate|poweroffon)
+ hmc_login
+ case $1 in
+ on)
+ hmc_poweron $hmc_ipaddr
+ ;;
+ off)
+ hmc_poweroff $hmc_ipaddr
+ ;;
+ reset)
+# hmc_reboot $hmc_ipaddr
+ hmc_poweroffon $hmc_ipaddr
+ ;;
+ powerstate)
+ hmc_powerstate
+ ;;
+ poweroffon)
+ hmc_poweroffon
+ ;;
+ esac
+ hmc_logout
+ exit 0
+ ;;
+*)
+ exit 1
+ ;;
+esac
diff --git a/lib/plugins/stonith/external/ibmrsa b/lib/plugins/stonith/external/ibmrsa
new file mode 100644
index 0000000..7408465
--- /dev/null
+++ b/lib/plugins/stonith/external/ibmrsa
@@ -0,0 +1,157 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Dejan Muhamedagic <dmuhamedagic@at.ibm.com>, IBM Austria
+#
+# External STONITH module for IBM RSA adapters.
+# External STONITH module for IBM BMC.
+# This STONITH module depends on IBMmpcli.
+#
+
+trap 'rm -f "$outf"' 0
+outf=`mktemp` || {
+ ha_log.sh err 'mktemp failed'
+ exit 1
+}
+
+chkmpcli() {
+ test -x /opt/IBMmpcli/bin/MPCLI.sh
+}
+mpcli() {
+ chkmpcli || {
+ ha_log.sh err "IBM mpcli not installed"
+ return 1
+ }
+ if [ x = "x$ipaddr" -o x = "x$userid" -o x = "x$passwd" ]
+ then
+ ha_log.sh err "ipaddr, userid, or passwd missing; check configuration"
+ return 1
+ fi
+ type=${type:-"ibm"}
+
+ goodstg="SUCCESS"
+ failstg="FAILURE"
+ (
+ echo "logonip -h $ipaddr -u $userid -p $passwd -t $type"
+ echo "outputfile $outf"
+ cat
+ ) | /opt/IBMmpcli/bin/MPCLI.sh | grep -w $goodstg >/dev/null 2>&1
+ rc=$?
+ grep -w $failstg $outf >/dev/null
+ if [ $rc -eq 0 -a $? -eq 1 ]; then
+ return 0
+ else
+ ha_log.sh err "MPCLI.sh failed: `cat $outf`"
+ return 1
+ fi
+}
+ibmrsa_reboot() {
+ echo restart -now | mpcli
+}
+ibmrsa_poweron() {
+ echo poweron | mpcli
+}
+ibmrsa_poweroff() {
+ echo poweroff | mpcli
+}
+ibmrsa_status() {
+ echo | mpcli
+}
+
+hostname=`echo ${hostname} | tr ',' ' '`
+
+case $1 in
+gethosts)
+ echo $hostname
+ ;;
+on)
+ ibmrsa_poweron
+ ;;
+off)
+ ibmrsa_poweroff
+ ;;
+reset)
+ ibmrsa_reboot
+ ;;
+status)
+ ibmrsa_status
+ ;;
+getconfignames)
+ for i in hostname ipaddr userid passwd type; do
+ echo $i
+ done
+ ;;
+getinfo-devid)
+ echo "IBM MP STONITH device"
+ ;;
+getinfo-devname)
+ echo "IBM MP STONITH device"
+ ;;
+getinfo-devdescr)
+ echo "IBM MP host reboot/poweron/poweroff"
+ ;;
+getinfo-devurl)
+ echo "http://www.ibm.com"
+ ;;
+getinfo-xml)
+ cat <<EOF
+<parameters>
+
+<parameter name="hostname" unique="1" required="1">
+<content type="string" />
+<shortdesc lang="en">
+Hostname
+</shortdesc>
+<longdesc lang="en">
+The hostname of the host to be managed by this STONITH device
+</longdesc>
+</parameter>
+
+<parameter name="ipaddr" unique="1" required="1">
+<content type="string" />
+<shortdesc lang="en">
+IP Address
+</shortdesc>
+<longdesc lang="en">
+The IP address of the STONITH device
+</longdesc>
+</parameter>
+
+<parameter name="userid" unique="0" required="1">
+<content type="string" />
+<shortdesc lang="en">
+Login
+</shortdesc>
+<longdesc lang="en">
+The username used to login into the STONITH device
+</longdesc>
+</parameter>
+
+<parameter name="passwd" unique="0" required="1">
+<content type="string" />
+<shortdesc lang="en">
+Password
+</shortdesc>
+<longdesc lang="en">
+The password used to login into the STONITH device
+</longdesc>
+</parameter>
+
+<parameter name="type" unique="0" required="1">
+<content type="string" />
+<shortdesc lang="en">
+Management processor type
+</shortdesc>
+<longdesc lang="en">
+The type of the management processor. Possible values are
+"ibm" (default, typically used for RSA) and "ipmi"
+(for IPMI compliant processors such as BMC).
+</longdesc>
+</parameter>
+
+</parameters>
+EOF
+ ;;
+*)
+ exit 1
+ ;;
+esac
diff --git a/lib/plugins/stonith/external/ibmrsa-telnet b/lib/plugins/stonith/external/ibmrsa-telnet
new file mode 100644
index 0000000..4d75d9a
--- /dev/null
+++ b/lib/plugins/stonith/external/ibmrsa-telnet
@@ -0,0 +1,320 @@
+#!/usr/bin/python
+# vim: set filetype=python
+#######################################################################
+#
+# ibmrsa-telnet - External stonith plugin for HAv2 (http://linux-ha.org/wiki)
+# Connects to IBM RSA Board via telnet and switches power
+# of server appropriately.
+#
+# Author: Andreas Mock (andreas.mock@web.de)
+#
+# History:
+# 2007-10-19 Fixed bad commandline handling in case of stonithing
+# 2007-10-11 First release.
+#
+# Comment: Please send bug fixes and enhancements.
+# I hope the functionality of communicating via telnet is encapsulated
+# enough so that someone can use it for similar purposes.
+#
+# Description: IBM offers Remote Supervisor Adapters II for several
+# servers. These RSA boards can be accessed in different ways.
+# One of that is via telnet. Once logged in you can use 'help' to
+# show all available commands. With 'power' you can reset, power on and
+# off the controlled server. This command is used in combination
+# with python's standard library 'telnetlib' to do it automatically.
+#
+# cib-snippet: Please see README.ibmrsa-telnet for examples.
+#
+# Copyright (c) 2007 Andreas Mock (andreas.mock@web.de)
+# All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of version 2 or later of the GNU General Public
+# License as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it would be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#
+# Further, this software is distributed without any warranty that it is
+# free of the rightful claim of any third person regarding infringement
+# or the like. Any license provided herein, whether implied or
+# otherwise, applies only to this software file. Patent licenses, if
+# any, provided herein do not apply to combinations of this program with
+# other software, or any other product whatsoever.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write the Free Software Foundation,
+# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
+#
+#######################################################################
+import sys
+import os
+import time
+import telnetlib
+import subprocess
+
+class TimeoutException(Exception):
+ def __init__(self, value=None):
+ Exception.__init__(self)
+ self.value = value
+
+ def __str__(self):
+ return repr(self.value)
+
+class RSABoard(telnetlib.Telnet):
+ def __init__(self, *args, **kwargs):
+ telnetlib.Telnet.__init__(self, *args, **kwargs)
+ self._timeout = 10
+ self._loggedin = 0
+ self._history = []
+ self._appl = os.path.basename(sys.argv[0])
+
+ def _get_timestamp(self):
+ ct = time.time()
+ msecs = (ct - long(ct)) * 1000
+ return "%s,%03d" % (time.strftime("%Y-%m-%d %H:%M:%S",
+ time.localtime(ct)), msecs)
+
+ def write(self, buffer, nolog = False):
+ self._history.append(self._get_timestamp() + ': WRITE: ' +
+ (nolog and '******' or repr(buffer)))
+ telnetlib.Telnet.write(self, buffer)
+
+ def expect(self, what, timeout=20):
+ line = telnetlib.Telnet.expect(self, what, timeout)
+ self._history.append(self._get_timestamp() + ': READ : ' + repr(line))
+ if not line:
+ raise TimeoutException("Timeout while waiting for '%s'." % (what, ))
+ return line
+
+ def login(self, user, passwd):
+ time.sleep(1)
+ line = self.expect(['\nlogin : ', '\nusername: '], self._timeout)
+ self.write(user)
+ self.write('\r')
+ line = self.expect(['\nPassword: ', '\npassword: '], self._timeout)
+ self.write(passwd, nolog = True)
+ self.write('\r')
+ line = self.expect(['\nsystem>', '> '], self._timeout)
+
+ def reset(self):
+ self.write('power cycle\r')
+ line = self.expect(['\nok'], self._timeout)
+ line = self.expect(['\nsystem>', '> '], self._timeout)
+
+ def on(self):
+ self.write('power on\r')
+ line = self.expect(['\nok'], self._timeout)
+ line = self.expect(['\nsystem>', '> '], self._timeout)
+
+ def off(self):
+ self.write('power off\r')
+ line = self.expect(['\nok'], self._timeout)
+ line = self.expect(['\nsystem>', '> '], self._timeout)
+
+ def exit(self):
+ self.write('exit\r')
+
+ def get_history(self):
+ return "\n".join(self._history)
+
+
+class RSAStonithPlugin:
+ def __init__(self):
+ # define the external stonith plugin api
+ self._required_cmds = \
+ 'reset gethosts status getconfignames getinfo-devid ' \
+ 'getinfo-devname getinfo-devdescr getinfo-devurl ' \
+ 'getinfo-xml'
+ self._optional_cmds = 'on off'
+ self._required_cmds_list = self._required_cmds.split()
+ self._optional_cmds_list = self._optional_cmds.split()
+
+ # who am i
+ self._appl = os.path.basename(sys.argv[0])
+
+ # telnet connection object
+ self._connection = None
+
+ # the list of configuration names
+ self._confignames = ['nodename', 'ip_address', 'username', 'password']
+
+ # catch the parameters provided by environment
+ self._parameters = {}
+ for name in self._confignames:
+ try:
+ self._parameters[name] = os.environ.get(name, '').split()[0]
+ except IndexError:
+ self._parameters[name] = ''
+
+ def _get_timestamp(self):
+ ct = time.time()
+ msecs = (ct - long(ct)) * 1000
+ return "%s,%03d" % (time.strftime("%Y-%m-%d %H:%M:%S",
+ time.localtime(ct)), msecs)
+
+ def _echo_debug(self, *args):
+ self.echo_log('debug', *args)
+
+ def echo(self, *args):
+ what = ''.join([str(x) for x in args])
+ sys.stdout.write(what)
+ sys.stdout.write('\n')
+ sys.stdout.flush()
+ self._echo_debug("STDOUT:", what)
+
+ def echo_log(self, level, *args):
+ subprocess.call(('ha_log.sh', level) + args)
+
+ def _get_connection(self):
+ if not self._connection:
+ c = RSABoard()
+ self._echo_debug("Connect to '%s'" %
+ (self._parameters['ip_address'],))
+ c.open(self._parameters['ip_address'])
+ self._echo_debug("Connection established")
+ c.login(self._parameters['username'],
+ self._parameters['password'])
+ self._connection = c
+
+ def _end_connection(self):
+ if self._connection:
+ self._connection.exit()
+ self._connection.close()
+
+ def reset(self):
+ self._get_connection()
+ self._connection.reset()
+ self._end_connection()
+ self._echo_debug(self._connection.get_history())
+ self.echo_log("info", "Reset of node '%s' done" %
+ (self._parameters['nodename'],))
+ return(0)
+
+ def on(self):
+ self._get_connection()
+ self._connection.on()
+ self._end_connection()
+ self._echo_debug(self._connection.get_history())
+ self.echo_log("info", "Switched node '%s' ON" %
+ (self._parameters['nodename'],))
+ return(0)
+
+ def off(self):
+ self._get_connection()
+ self._connection.off()
+ self._end_connection()
+ self._echo_debug(self._connection.get_history())
+ self.echo_log("info", "Switched node '%s' OFF" %
+ (self._parameters['nodename'],))
+ return(0)
+
+ def gethosts(self):
+ self.echo(self._parameters['nodename'])
+ return(0)
+
+ def status(self):
+ self._get_connection()
+ self._end_connection()
+ self._echo_debug(self._connection.get_history())
+ return(0)
+
+ def getconfignames(self):
+ for name in ['nodename', 'ip_address', 'username', 'password']:
+ self.echo(name)
+ return(0)
+
+ def getinfo_devid(self):
+ self.echo("External Stonith Plugin for IBM RSA Boards")
+ return(0)
+
+ def getinfo_devname(self):
+ self.echo("External Stonith Plugin for IBM RSA Boards connecting "
+ "via Telnet")
+ return(0)
+
+ def getinfo_devdescr(self):
+ self.echo("External stonith plugin for HAv2 which connects to "
+ "a RSA board on IBM servers via telnet. Commands to "
+ "turn on/off power and to reset server are sent "
+ "appropriately. "
+ "(c) 2007 by Andreas Mock (andreas.mock@web.de)")
+ return(0)
+
+ def getinfo_devurl(self):
+ self.echo("http://www.ibm.com/Search/?q=remote+supervisor+adapter")
+
+ def getinfo_xml(self):
+ info = """<parameters>
+ <parameter name="nodename" unique="1" required="1">
+ <content type="string" />
+ <shortdesc lang="en">nodename to shoot</shortdesc>
+ <longdesc lang="en">
+ Name of the node which has to be stonithed in case.
+ </longdesc>
+ </parameter>
+ <parameter name="ip_address" unique="1" required="1">
+ <content type="string" />
+ <shortdesc lang="en">hostname or ip address of RSA</shortdesc>
+ <longdesc lang="en">
+ Hostname or ip address of RSA board used to reset node.
+ </longdesc>
+ </parameter>
+ <parameter name="username" unique="1" required="1">
+ <content type="string" />
+ <shortdesc lang="en">username to login on RSA board</shortdesc>
+ <longdesc lang="en">
+ Username to login on RSA board.
+ </longdesc>
+ </parameter>
+ <parameter name="password" unique="1" required="1">
+ <content type="string" />
+ <shortdesc lang="en">password to login on RSA board</shortdesc>
+ <longdesc lang="en">
+ Password to login on RSA board.
+ </longdesc>
+ </parameter>
+ </parameters>
+ """
+ self.echo(info)
+ return(0)
+
+ def not_implemented(self, cmd):
+ self.echo_log("err", "Command '%s' not implemented." % (cmd,))
+ return(1)
+
+ def usage(self):
+ usage = "Call me with one of the allowed commands: %s, %s" % (
+ ', '.join(self._required_cmds_list),
+ ', '.join(self._optional_cmds_list))
+ return usage
+
+ def process(self, argv):
+ self._echo_debug("========== Start =============")
+ if len(argv) < 1:
+ self.echo_log("err", 'At least one commandline argument required.')
+ return(1)
+ cmd = argv[0]
+ self._echo_debug("cmd:", cmd)
+ if cmd not in self._required_cmds_list and \
+ cmd not in self._optional_cmds_list:
+ self.echo_log("err", "Command '%s' not supported." % (cmd,))
+ return(1)
+ try:
+ cmd = cmd.lower().replace('-', '_')
+ func = getattr(self, cmd, self.not_implemented)
+ rc = func()
+ return(rc)
+ except Exception, args:
+ self.echo_log("err", 'Exception raised:', str(args))
+ if self._connection:
+ self.echo_log("err", self._connection.get_history())
+ self._connection.close()
+ return(1)
+
+
+if __name__ == '__main__':
+ stonith = RSAStonithPlugin()
+ rc = stonith.process(sys.argv[1:])
+ sys.exit(rc)
diff --git a/lib/plugins/stonith/external/ipmi b/lib/plugins/stonith/external/ipmi
new file mode 100644
index 0000000..abadd5a
--- /dev/null
+++ b/lib/plugins/stonith/external/ipmi
@@ -0,0 +1,276 @@
+#!/bin/sh
+#
+# External STONITH module using IPMI.
+# This modules uses uses the ipmitool program available from
+# http://ipmitool.sf.net/ for actual communication with the
+# managed device.
+#
+# Copyright (c) 2007 Martin Bene <martin.bene@icomedias.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of version 2 of the GNU General Public License as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it would be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#
+# Further, this software is distributed without any warranty that it is
+# free of the rightful claim of any third person regarding infringement
+# or the like. Any license provided herein, whether implied or
+# otherwise, applies only to this software file. Patent licenses, if
+# any, provided herein do not apply to combinations of this program with
+# other software, or any other product whatsoever.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+# Initialization -- fix locale settings so we can parse output from
+# binaries if we need it
+LANG=C
+LC_ALL=C
+
+RESET="power reset"
+POWEROFF="power off"
+POWERON="power on"
+STATUS="power status"
+IPMITOOL=${ipmitool:-"`which ipmitool 2>/dev/null`"}
+
+have_ipmi() {
+ test -x "${IPMITOOL}"
+}
+
+# Wrapper function for ipmitool that sets the correct host IP address,
+# username, and password, and invokes ipmitool with any arguments
+# passed in
+run_ipmitool() {
+ local ipmitool_opts privlvl=""
+ have_ipmi || {
+ ha_log.sh err "ipmitool not installed"
+ return 1
+ }
+ if [ -z "${ipaddr}" -o -z "${userid}" -o -z "${passwd}" ]; then
+ ha_log.sh err "ipaddr, userid or passwd missing; check configuration"
+ return 1
+ fi
+
+ if [ -z "${interface}" ]; then
+ # default to "lan" interface
+ interface="lan"
+ fi
+ if [ -n "${priv}" ]; then
+ # default to "lan" interface
+ privlvl="-L $priv"
+ fi
+
+ ipmitool_opts="-I ${interface} -H ${ipaddr} $privlvl"
+
+ case "${passwd_method}" in
+ param|'')
+ passwd_method=param
+ M="-P"
+ ;;
+ env)
+ M="-E"
+ ;;
+ file)
+ M="-f"
+ ;;
+ *)
+ ha_log.sh err "invalid passwd_method: \"${passwd_method}\""
+ return 1
+ esac
+
+ action="$*"
+
+ if [ $passwd_method = env ]
+ then
+ IPMI_PASSWORD="${passwd}" ${IPMITOOL} $ipmitool_opts -U "${userid}" -E ${action}
+ else
+ ${IPMITOOL} $ipmitool_opts -U "${userid}" $M "${passwd}" ${action}
+ fi 2>&1
+}
+
+# Yet another convenience wrapper that invokes run_ipmitool, captures
+# its output, logs the output, returns either 0 (on success) or 1 (on
+# any error)
+do_ipmi() {
+ if outp=`run_ipmitool $*`; then
+ ha_log.sh debug "ipmitool output: `echo $outp`"
+ return 0
+ else
+ ha_log.sh err "error executing ipmitool: `echo $outp`"
+ return 1
+ fi
+}
+
+# Check if the managed node is powered on. To do so, issue the "power
+# status" command. Should return either "Chassis Power is on" or
+# "Chassis Power is off".
+ipmi_is_power_on() {
+ local outp
+ outp=`run_ipmitool ${STATUS}`
+ case "${outp}" in
+ *on)
+ return 0
+ ;;
+ *off)
+ return 1
+ ;;
+ esac
+}
+
+
+case ${1} in
+gethosts)
+ echo $hostname
+ exit 0
+ ;;
+on)
+ do_ipmi "${POWERON}"
+ exit
+ ;;
+off)
+ do_ipmi "${POWEROFF}"
+ exit
+ ;;
+reset)
+ if ipmi_is_power_on; then
+ do_ipmi "${RESET}"
+ else
+ do_ipmi "${POWERON}"
+ fi
+ exit
+ ;;
+status)
+ # "status" reflects the status of the stonith _device_, not
+ # the managed node. Hence, only check if we can contact the
+ # IPMI device with "power status" command, don't pay attention
+ # to whether the node is in fact powered on or off.
+ do_ipmi "${STATUS}"
+ exit $?
+ ;;
+getconfignames)
+ for i in hostname ipaddr userid passwd interface; do
+ echo $i
+ done
+ exit 0
+ ;;
+getinfo-devid)
+ echo "IPMI STONITH device"
+ exit 0
+ ;;
+getinfo-devname)
+ echo "IPMI STONITH external device"
+ exit 0
+ ;;
+getinfo-devdescr)
+ echo "ipmitool based power management. Apparently, the power off"
+ echo "method of ipmitool is intercepted by ACPI which then makes"
+ echo "a regular shutdown. If case of a split brain on a two-node"
+ echo "it may happen that no node survives. For two-node clusters"
+ echo "use only the reset method."
+ exit 0
+ ;;
+getinfo-devurl)
+ echo "http://ipmitool.sf.net/"
+ exit 0
+ ;;
+getinfo-xml)
+ cat << IPMIXML
+<parameters>
+<parameter name="hostname" unique="1">
+<content type="string" />
+<shortdesc lang="en">
+Hostname
+</shortdesc>
+<longdesc lang="en">
+The name of the host to be managed by this STONITH device.
+</longdesc>
+</parameter>
+
+<parameter name="ipaddr" unique="1">
+<content type="string" />
+<shortdesc lang="en">
+IP Address
+</shortdesc>
+<longdesc lang="en">
+The IP address of the STONITH device.
+</longdesc>
+</parameter>
+
+<parameter name="userid" unique="0">
+<content type="string" />
+<shortdesc lang="en">
+Login
+</shortdesc>
+<longdesc lang="en">
+The username used for logging in to the STONITH device.
+</longdesc>
+</parameter>
+
+<parameter name="passwd" unique="0">
+<content type="string" />
+<shortdesc lang="en">
+Password
+</shortdesc>
+<longdesc lang="en">
+The password used for logging in to the STONITH device.
+</longdesc>
+</parameter>
+
+<parameter name="passwd_method" unique="0">
+<content type="string" default="param"/>
+<shortdesc lang="en">
+Method for passing passwd parameter
+</shortdesc>
+<longdesc lang="en">
+Method for passing the passwd parameter to ipmitool
+ param: pass as parameter (-P)
+ env: pass via environment (-E)
+ file: value of "passwd" is actually a file name, pass with (-f)
+</longdesc>
+</parameter>
+
+<parameter name="interface" unique="0">
+<content type="string" default="lan"/>
+<shortdesc lang="en">
+IPMI interface
+</shortdesc>
+<longdesc lang="en">
+IPMI interface to use, such as "lan" or "lanplus".
+</longdesc>
+</parameter>
+
+<parameter name="priv" unique="0">
+<content type="string" default=""/>
+<shortdesc lang="en">
+The privilege level of the user.
+</shortdesc>
+<longdesc lang="en">
+The privilege level of the user, for instance OPERATOR. If
+unspecified the privilege level is ADMINISTRATOR. See
+ipmitool(1) -L option for more information.
+</longdesc>
+</parameter>
+
+<parameter name="ipmitool" unique="0">
+<content type="string" default=""/>
+<shortdesc lang="en">
+IPMI command(ipmitool)
+</shortdesc>
+<longdesc lang="en">
+Specify the full path to IPMI command.
+</longdesc>
+</parameter>
+
+</parameters>
+IPMIXML
+ exit 0
+ ;;
+*)
+ exit 1
+ ;;
+esac
diff --git a/lib/plugins/stonith/external/ippower9258.in b/lib/plugins/stonith/external/ippower9258.in
new file mode 100755
index 0000000..6ae7e02
--- /dev/null
+++ b/lib/plugins/stonith/external/ippower9258.in
@@ -0,0 +1,316 @@
+#!/bin/sh
+#
+# External STONITH module using IP Power 9258 or compatible devices.
+#
+# Copyright (c) 2010 Helmut Weymann (Helmut (at) h-weymann (dot) de)
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of version 2 of the GNU General Public License as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it would be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#
+# Further, this software is distributed without any warranty that it is
+# free of the rightful claim of any third person regarding infringement
+# or the like. Any license provided herein, whether implied or
+# otherwise, applies only to this software file. Patent licenses, if
+# any, provided herein do not apply to combinations of this program with
+# other software, or any other product whatsoever.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write the Free Software Foundation,
+# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
+#
+
+#
+# Basic commands & parameters independent from individual device
+
+DEVICE="IP Power 9258"
+IPPowerOn="1"
+IPPowerOff="0"
+IPGetPower="Set.cmd?CMD=GetPower"
+IPSetPower="Set.cmd?CMD=SetPower"
+IPPort_name="P"
+IPPort0=60
+HTTP_COMMAND="wget -q -O - --"
+LOG_ERROR="ha_log.sh err"
+LOG_WARNING="ha_log.sh warn"
+LOG_INFO="ha_log.sh info"
+LOG_DEBUG="ha_log.sh debug"
+MY_COOKIES="cookies.txt"
+MY_TEMPFILE="temp.htm"
+PORT_STATUS="iocontrol.htm"
+UNDEFINED_HOSTNAME="*not-defined*"
+
+#
+# check MY_ROOT_PATH for IP Power 9258 and create it if necessary
+MY_ROOT_PATH="@GLUE_STATE_DIR@/heartbeat/rsctmp/ippower9258"
+
+#
+# script functions
+#
+
+get_challenge() {
+ #
+ # device sends a challenge for md5 encryption of username, password and challenge
+ send_web_command - "http://$deviceip/" | grep Challenge | grep input | cut -d '"' -f 6
+}
+
+get_cookie_from_device(){
+ # the form on the login page has these fields:
+ # Username, Password, Challenge, Response, ScreenWidth
+ #
+ challenge=`get_challenge`
+ response=`echo -n "$username$password$challenge" | md5sum | cut -b -32`
+ postdata="Username=$username&Password=&Challenge=&Response=$response&ScreenWidth=1024"
+ send_web_command " $MY_PATH/$MY_TEMPFILE --post-data=$postdata" "http://$deviceip/tgi/login.tgi"
+ if grep -qs "Invalid User name or Password" $MY_PATH/$MY_TEMPFILE
+ then
+ $LOG_ERROR "Login to device $deviceip failed."
+ $LOG_ERROR "Received Challenge = <<<$challenge>>>."
+ $LOG_ERROR "Sent postdata = <<<$postdata>>>."
+ exit 1
+ fi
+}
+
+get_data_from_device() {
+ # If successful all device info is available in MY_PATH
+ rm -f "$MY_PATH/$PORT_STATUS"
+ send_web_command "$MY_PATH/$PORT_STATUS" "http://$deviceip/$PORT_STATUS"
+ if grep -qs "Cookie Time Out" $MY_PATH/$PORT_STATUS
+ then
+ $LOG_ERROR "received no port data from $deviceip (Cookie Time Out)"
+ exit 1
+ fi
+}
+
+send_http_request() {
+ # ececution of http commands supported by the device
+ $HTTP_COMMAND "http://$username:$password@$deviceip/$1"
+}
+
+send_web_command(){
+ # ececution of web commands through the web-interface
+ WEB_COMMAND="wget -q --keep-session-cookies"
+ WEB_COMMAND="$WEB_COMMAND --load-cookies $MY_PATH/$MY_COOKIES"
+ WEB_COMMAND="$WEB_COMMAND --save-cookies $MY_PATH/$MY_COOKIES"
+ $WEB_COMMAND -O $1 -- $2
+}
+
+name2port() {
+ local name=$1
+ local i=$IPPort0
+ for h in $device_hostlist ; do
+ if [ $h = $name ]; then
+ echo $IPPort_name$i
+ return
+ fi
+ i=`expr $i + 1`
+ done
+ echo "invalid"
+}
+
+set_port() {
+ #
+ # port status is always set. Even if requested status is current status.
+ # host status is not considered.
+ local host=$1
+ local requested_status=$2 # 0 or 1
+ local port=`name2port $host`
+ if [ "$port" = "invalid" ]
+ then
+ $LOG_ERROR "Host $host is not in hostlist ($hostlist) for $deviceip."
+ exit 1
+ fi
+ ret=`send_http_request "$IPSetPower+$port=$requested_status" | cut -b 11`
+ if [ "$ret" != "$requested_status" ]
+ then
+ $LOG_ERROR "$DEVICE at $deviceip responds with wrong status $ret for host $host at port $port."
+ exit 1
+ fi
+ return 0
+}
+
+build_device_hostlist() {
+ #
+ # hostnames are available from http://$deviceip/iocontrol.htm"
+ # check for number of ports
+ #
+ device_hostlist=$(
+ w3m -dump $MY_PATH/$PORT_STATUS | grep 'Power[1-8]' |
+ sed 's/[^[]*\[//;s/\].*//;s/ *//' |
+ while read h; do
+ [ -z "$h" ] &&
+ echo $UNDEFINED_HOSTNAME ||
+ echo $h
+ done
+ )
+ if [ x = x"$device_hostlist" ]; then
+ $LOG_ERROR "cannot get hostlist for $deviceip"
+ exit 1
+ fi
+ $LOG_DEBUG "Got new hostlist ($device_hostlist) from $deviceip"
+}
+
+filter_device_hostlist() {
+ # check the given hostlist against the device hostlist
+ local host
+ for host in $device_hostlist; do
+ [ "$host" != "$UNDEFINED_HOSTNAME" ] &&
+ echo $host
+ done
+}
+
+check_hostlist() {
+ # check the given hostlist against the device hostlist
+ local cnt=`echo "$hostlist" | wc -w`
+ local cnt2=0
+ local host
+ for host in $hostlist; do
+ if [ `name2port $host` != "invalid" ]; then
+ cnt2=$((cnt2+1))
+ else
+ $LOG_ERROR "host $host not defined at $deviceip"
+ fi
+ done
+ [ $cnt -ne $cnt2 ] &&
+ exit 1
+}
+
+get_http_status() {
+ pattern="P60=[01],P61=[01],P62=[01],P63=[01],P64=[01],P65=[01],P66=[01],P67=[01]"
+ ret=`send_http_request "$IPGetPower" | grep $pattern`
+ if [ "X$ret" = "X" ]
+ then
+ $LOG_ERROR "$DEVICE at $deviceip returns invalid or no string."
+ exit 1
+ fi
+}
+
+hostlist=`echo $hostlist | tr ',' ' '`
+case $1 in
+gethosts|on|off|reset|status)
+ # need environment from stonithd
+ # and device information from individual device
+ #
+ # default device username is admin
+ # IP Power 9258 does not allow user management.
+ #
+ if [ "X$username" = "X" ]
+ then
+ username="admin"
+ fi
+
+ mkdir -p $MY_ROOT_PATH
+ tmp_path="$deviceip" # ensure a simple unique pathname
+ MY_PATH="$MY_ROOT_PATH/$tmp_path"
+ mkdir -p $MY_PATH
+ get_cookie_from_device
+ get_data_from_device
+ build_device_hostlist
+ if [ "X$hostlist" = "X" ]; then
+ hostlist="`filter_device_hostlist`"
+ else
+ check_hostlist
+ fi
+ ;;
+*)
+ # the client is asking for meta-data
+ ;;
+esac
+
+target=`echo $2 | sed 's/[.].*//'`
+# the necessary actions for stonithd
+case $1 in
+gethosts)
+ echo $hostlist
+ ;;
+on)
+ set_port $target $IPPowerOn
+ ;;
+off)
+ set_port $target $IPPowerOff
+ ;;
+reset)
+ set_port $target $IPPowerOff
+ sleep 5
+ set_port $target $IPPowerOn
+ ;;
+status)
+ # werify http command interface
+ get_http_status
+ ;;
+getconfignames)
+ # return all the config names
+ for ipparam in deviceip username password hostlist
+ do
+ echo $ipparam
+ done
+ ;;
+getinfo-devid)
+ echo "IP Power 9258"
+ ;;
+getinfo-devname)
+ echo "IP Power 9258 power switch"
+ ;;
+getinfo-devdescr)
+ echo "Power switch IP Power 9258 with 4 or 8 power outlets."
+ echo "WARNING: It is different from IP Power 9258 HP"
+ ;;
+getinfo-devurl)
+ echo "http://www.aviosys.com/manual.htm"
+ ;;
+getinfo-xml)
+ cat << IPPOWERXML
+<parameters>
+<parameter name="deviceip" unique="1" required="1">
+<content type="string" />
+<shortdesc lang="en">
+IP address or hostname of the device.
+</shortdesc>
+<longdesc lang="en">
+The IP Address or the hostname of the device.
+</longdesc>
+</parameter>
+
+<parameter name="password" unique="0" required="1">
+<content type="string" />
+<shortdesc lang="en">
+Password
+</shortdesc>
+<longdesc lang="en">
+The password to log in with.
+</longdesc>
+</parameter>
+
+<parameter name="hostlist" unique="0" required="0">
+<content type="string" />
+<shortdesc lang="en">
+Hostlist
+</shortdesc>
+<longdesc lang="en">
+The list of hosts that the device controls.
+If you leave this list empty, we will retrieve the hostnames from the device.
+</longdesc>
+</parameter>
+
+<parameter name="username" unique="0" required="0">
+<content type="string" default="admin"/>
+<shortdesc lang="en">
+Account Name
+</shortdesc>
+<longdesc lang="en">
+The user to log in with.
+</longdesc>
+</parameter>
+
+</parameters>
+IPPOWERXML
+ ;;
+*)
+ $LOG_ERROR "Unexpected command $1 for $DEVICE at $deviceip."
+ exit 1;
+ ;;
+esac
diff --git a/lib/plugins/stonith/external/kdumpcheck.in b/lib/plugins/stonith/external/kdumpcheck.in
new file mode 100644
index 0000000..7f3f752
--- /dev/null
+++ b/lib/plugins/stonith/external/kdumpcheck.in
@@ -0,0 +1,274 @@
+#!/bin/sh
+#
+# External STONITH module to check kdump.
+#
+# Copyright (c) 2008 NIPPON TELEGRAPH AND TELEPHONE CORPORATION
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of version 2 of the GNU General Public License as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it would be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#
+# Further, this software is distributed without any warranty that it is
+# free of the rightful claim of any third person regarding infringement
+# or the like. Any license provided herein, whether implied or
+# otherwise, applies only to this software file. Patent licenses, if
+# any, provided herein do not apply to combinations of this program with
+# other software, or any other product whatsoever.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write the Free Software Foundation,
+# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
+#
+
+SSH_COMMAND="@SSH@ -q -x -o PasswordAuthentication=no -o StrictHostKeyChecking=no -n"
+#Set default user name.
+USERNAME="kdumpchecker"
+#Initialize identity file-path options for ssh command
+IDENTITY_OPTS=""
+
+#Rewrite the hostlist to accept "," as a delimeter for hostnames too.
+hostlist=`echo ${hostlist} | tr ',' ' '`
+
+##
+# Check the parameter hostlist is set or not.
+# If not, exit with 6 (ERR_CONFIGURED).
+##
+check_hostlist() {
+ if [ -z "${hostlist}" ]; then
+ ha_log.sh err "hostlist is empty"
+ exit 6 #ERR_CONFIGURED
+ fi
+}
+
+##
+# Set kdump check user name to USERNAME.
+# always return 0.
+##
+get_username() {
+ kdump_conf="/etc/kdump.conf"
+
+ if [ ! -f "${kdump_conf}" ]; then
+ ha_log.sh debug "${kdump_conf} doesn't exist"
+ return 0
+ fi
+
+ tmp=""
+ while read config_opt config_val; do
+ if [ "${config_opt}" = "kdump_check_user" ]; then
+ tmp="${config_val}"
+ fi
+ done < "${kdump_conf}"
+ if [ -n "${tmp}" ]; then
+ USERNAME="${tmp}"
+ fi
+
+ ha_log.sh debug "kdump check user name is ${USERNAME}."
+}
+
+##
+# Check the specified or default identity file exists or not.
+# If not, exit with 6 (ERR_CONFIGURED).
+##
+check_identity_file() {
+ IDENTITY_OPTS=""
+ if [ -n "${identity_file}" ]; then
+ if [ ! -f "${identity_file}" ]; then
+ ha_log.sh err "${identity_file} doesn't exist."
+ exit 6 #ERR_CONFIGURED
+ fi
+ IDENTITY_OPTS="-i ${identity_file}"
+ else
+ flg_file_exists=0
+ homedir=`eval echo "~${USERNAME}"`
+ for filename in "${homedir}/.ssh/id_rsa" \
+ "${homedir}/.ssh/id_dsa" \
+ "${homedir}/.ssh/identity"
+ do
+ if [ -f "${filename}" ]; then
+ flg_file_exists=1
+ IDENTITY_OPTS="${IDENTITY_OPTS} -i ${filename}"
+ fi
+ done
+ if [ ${flg_file_exists} -eq 0 ]; then
+ ha_log.sh err "${USERNAME}'s identity file for ssh command" \
+ " doesn't exist."
+ exit 6 #ERR_CONFIGURED
+ fi
+ fi
+}
+
+##
+# Check the user to check doing kdump exists or not.
+# If not, exit with 6 (ERR_CONFIGURED).
+##
+check_user_existence() {
+
+ # Get kdump check user name and check whether he exists or not.
+ grep -q "^${USERNAME}:" /etc/passwd > /dev/null 2>&1
+ ret=$?
+ if [ ${ret} != 0 ]; then
+ ha_log.sh err "user ${USERNAME} doesn't exist." \
+ "please confirm \"kdump_check_user\" setting in /etc/kdump.conf." \
+ "(default user name is \"kdumpchecker\")"
+ exit 6 #ERR_CONFIGURED
+ fi
+}
+
+##
+# Check the target node is kdumping or not.
+# arg1 : target node name.
+# ret : 0 -> the target is kdumping.
+# : 1 -> the target is _not_ kdumping.
+# : else -> failed to check.
+##
+check_kdump() {
+ target_node="$1"
+
+ # Get kdump check user name.
+ get_username
+ check_user_existence
+ exec_cmd="${SSH_COMMAND} -l ${USERNAME}"
+
+ # Specify kdump check user's identity file for ssh command.
+ check_identity_file
+ exec_cmd="${exec_cmd} ${IDENTITY_OPTS}"
+
+ # Now, check the target!
+ # In advance, Write the following setting at the head of
+ # kdump_check_user's public key in authorized_keys file on target node.
+ # command="test -s /proc/vmcore", \
+ # no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty
+ ha_log.sh debug "execute the command [${exec_cmd} ${target_node}]."
+ ${exec_cmd} ${target_node} > /dev/null 2>&1
+ ret=$?
+ ha_log.sh debug "the command's result is ${ret}."
+
+ #ret -> 0 : vmcore file's size is not zero. the node is kdumping.
+ #ret -> 1 : the node is _not_ kdumping (vmcore didn't exist or
+ # its size is zero). It still needs to be STONITH'ed.
+ #ret -> 255 : ssh command is failed.
+ # else : Maybe command strings in authorized_keys is wrong...
+ return ${ret}
+}
+
+###
+#
+# Main function.
+#
+###
+case $1 in
+gethosts)
+ check_hostlist
+ for hostname in ${hostlist} ; do
+ echo "${hostname}"
+ done
+ exit 0
+ ;;
+on)
+ # This plugin does only check whether a target node is kdumping or not.
+ exit 1
+ ;;
+reset|off)
+ check_hostlist
+ ret=1
+ h_target=`echo $2 | tr A-Z a-z`
+ for hostname in ${hostlist}
+ do
+ hostname=`echo $hostname | tr A-Z a-z`
+ if [ "${hostname}" != "$h_target" ]; then
+ continue
+ fi
+ while [ 1 ]
+ do
+ check_kdump "$2"
+ ret=$?
+ if [ ${ret} -ne 255 ]; then
+ exit ${ret}
+ fi
+ #255 means ssh command itself is failed.
+ #For example, connection failure as if network doesn't start yet
+ #in 2nd kernel on the target node.
+ #So, retry to check after a little while.
+ sleep 1
+ done
+ done
+ exit ${ret}
+ ;;
+status)
+ check_hostlist
+ for hostname in ${hostlist}
+ do
+ if ping -w1 -c1 "${hostname}" 2>&1 | grep "unknown host"
+ then
+ exit 1
+ fi
+ done
+ get_username
+ check_user_existence
+ check_identity_file
+ exit 0
+ ;;
+getconfignames)
+ echo "hostlist identity_file"
+ exit 0
+ ;;
+getinfo-devid)
+ echo "kdump check STONITH device"
+ exit 0
+ ;;
+getinfo-devname)
+ echo "kdump check STONITH external device"
+ exit 0
+ ;;
+getinfo-devdescr)
+ echo "ssh-based kdump checker"
+ echo "To check whether a target node is dumping or not."
+ exit 0
+ ;;
+getinfo-devurl)
+ echo "kdump -> http://lse.sourceforge.net/kdump/"
+ echo "ssh -> http://openssh.org"
+ exit 0
+ ;;
+getinfo-xml)
+ cat << SSHXML
+<parameters>
+<parameter name="hostlist" unique="1" required="1">
+<content type="string" />
+<shortdesc lang="en">
+Hostlist
+</shortdesc>
+<longdesc lang="en">
+The list of hosts that the STONITH device controls
+</longdesc>
+</parameter>
+
+<parameter name="identity_file" unique="1" required="0">
+<content type="string" />
+<shortdesc lang="en">
+Identity file's full path for kdump check user
+</shortdesc>
+<longdesc lang="en">
+The full path of kdump check user's identity file for ssh command.
+The identity in the specified file have to be restricted to execute
+only the following command.
+"test -s /proc/vmcore"
+Default: kdump check user's default identity file path.
+NOTE: You can specify kdump check user name in /etc/kdump.conf.
+ The parameter name is "kdump_check_user".
+ Default user is "kdumpchecker".
+</longdesc>
+</parameter>
+
+</parameters>
+SSHXML
+ exit 0
+ ;;
+*)
+ exit 1
+ ;;
+esac
diff --git a/lib/plugins/stonith/external/libvirt b/lib/plugins/stonith/external/libvirt
new file mode 100644
index 0000000..494b048
--- /dev/null
+++ b/lib/plugins/stonith/external/libvirt
@@ -0,0 +1,298 @@
+#!/bin/sh
+#
+# External STONITH module for a libvirt managed hypervisor (kvm/Xen).
+# Uses libvirt as a STONITH device to control guest.
+#
+# Copyright (c) 2010 Holger Teutsch <holger.teutsch@web.de>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of version 2 of the GNU General Public License as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it would be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#
+# Further, this software is distributed without any warranty that it is
+# free of the rightful claim of any third person regarding infringement
+# or the like. Any license provided herein, whether implied or
+# otherwise, applies only to this software file. Patent licenses, if
+# any, provided herein do not apply to combinations of this program with
+# other software, or any other product whatsoever.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+# start a domain
+libvirt_start() {
+ out=$($VIRSH -c $hypervisor_uri start $domain_id 2>&1)
+ if [ $? -eq 0 ]
+ then
+ ha_log.sh notice "Domain $domain_id was started"
+ return 0
+ fi
+
+ $VIRSH -c $hypervisor_uri dominfo $domain_id 2>&1 |
+ egrep -q '^State:.*(running|idle)|already active'
+ if [ $? -eq 0 ]
+ then
+ ha_log.sh notice "Domain $domain_id is already active"
+ return 0
+ fi
+
+ ha_log.sh err "Failed to start domain $domain_id"
+ ha_log.sh err "$out"
+ return 1
+}
+# reboot a domain
+# return
+# 0: success
+# 1: error
+libvirt_reboot() {
+ local rc out
+ out=$($VIRSH -c $hypervisor_uri reboot $domain_id 2>&1)
+ rc=$?
+ if [ $rc -eq 0 ]
+ then
+ ha_log.sh notice "Domain $domain_id was rebooted"
+ return 0
+ fi
+ ha_log.sh err "Failed to reboot domain $domain_id (exit code: $rc)"
+ ha_log.sh err "$out"
+ return 1
+}
+
+# stop a domain
+# return
+# 0: success
+# 1: error
+# 2: was already stopped
+libvirt_stop() {
+ out=$($VIRSH -c $hypervisor_uri destroy $domain_id 2>&1)
+ if [ $? -eq 0 ]
+ then
+ ha_log.sh notice "Domain $domain_id was stopped"
+ return 0
+ fi
+
+ $VIRSH -c $hypervisor_uri dominfo $domain_id 2>&1 |
+ egrep -q '^State:.*shut off|not found|not running'
+ if [ $? -eq 0 ]
+ then
+ ha_log.sh notice "Domain $domain_id is already stopped"
+ return 2
+ fi
+
+ ha_log.sh err "Failed to stop domain $domain_id"
+ ha_log.sh err "$out"
+ return 1
+}
+
+# get status of stonith device (*NOT* of the domain).
+# If we can retrieve some info from the hypervisor
+# the stonith device is OK.
+libvirt_status() {
+ out=$($VIRSH -c $hypervisor_uri version 2>&1)
+ if [ $? -eq 0 ]
+ then
+ return 0
+ fi
+
+ ha_log.sh err "Failed to get status for $hypervisor_uri"
+ ha_log.sh err "$out"
+ return 1
+}
+
+# check config and set variables
+# does not return on error
+libvirt_check_config() {
+ VIRSH=`which virsh 2>/dev/null`
+
+ if [ ! -x "$VIRSH" ]
+ then
+ ha_log.sh err "virsh not installed"
+ exit 1
+ fi
+
+ if [ -z "$hostlist" -o -z "$hypervisor_uri" ]
+ then
+ ha_log.sh err "hostlist or hypervisor_uri missing; check configuration"
+ exit 1
+ fi
+
+ case "$reset_method" in
+ power_cycle|reboot) : ;;
+ *)
+ ha_log.sh err "unrecognized reset_method: $reset_method"
+ exit 1
+ ;;
+ esac
+}
+
+# set variable domain_id for the host specified as arg
+libvirt_set_domain_id ()
+{
+ for h in $hostlist
+ do
+ case $h in
+ $1:*)
+ domain_id=`expr $h : '.*:\(.*\)'`
+ return
+ ;;
+
+ $1)
+ domain_id=$1
+ return
+ esac
+ done
+
+ ha_log.sh err "Should never happen: Called for host $1 but $1 is not in $hostlist."
+ exit 1
+}
+
+libvirt_info() {
+cat << LVIRTXML
+<parameters>
+<parameter name="hostlist" unique="1" required="1">
+<content type="string" />
+<shortdesc lang="en">
+List of hostname[:domain_id]..
+</shortdesc>
+<longdesc lang="en">
+List of controlled hosts: hostname[:domain_id]..
+The optional domain_id defaults to the hostname.
+</longdesc>
+</parameter>
+
+<parameter name="hypervisor_uri" required="1">
+<content type="string" />
+<shortdesc lang="en">
+Hypervisor URI
+</shortdesc>
+<longdesc lang="en">
+URI for connection to the hypervisor.
+driver[+transport]://[username@][hostlist][:port]/[path][?extraparameters]
+e.g.
+qemu+ssh://my_kvm_server.mydomain.my/system (uses ssh for root)
+xen://my_kvm_server.mydomain.my/ (uses TLS for client)
+
+virsh must be installed (e.g. libvir-client package) and access control must
+be configured for your selected URI.
+</longdesc>
+</parameter>
+
+<parameter name="reset_method" required="0">
+<content type="string" default="power_cycle"/>
+<shortdesc lang="en">
+How to reset a guest.
+</shortdesc>
+<longdesc lang="en">
+A guest reset may be done by a sequence of off and on commands
+(power_cycle) or by the reboot command. Which method works
+depend on the hypervisor and guest configuration management.
+</longdesc>
+</parameter>
+</parameters>
+LVIRTXML
+exit 0
+}
+
+#############
+# Main code #
+#############
+
+# don't fool yourself when testing with stonith(8)
+# and transport ssh
+unset SSH_AUTH_SOCK
+
+# support , as a separator as well
+hostlist=`echo $hostlist| sed -e 's/,/ /g'`
+
+reset_method=${reset_method:-"power_cycle"}
+
+case $1 in
+ gethosts)
+ hostnames=`echo $hostlist|sed -e 's/:[^ ]*//g'`
+ for h in $hostnames
+ do
+ echo $h
+ done
+ exit 0
+ ;;
+
+ on)
+ libvirt_check_config
+ libvirt_set_domain_id $2
+
+ libvirt_start
+ exit $?
+ ;;
+
+ off)
+ libvirt_check_config
+ libvirt_set_domain_id $2
+
+ libvirt_stop
+ [ $? = 1 ] && exit 1
+ exit 0
+ ;;
+
+ reset)
+ libvirt_check_config
+ libvirt_set_domain_id $2
+
+ if [ "$reset_method" = "power_cycle" ]; then
+ libvirt_stop
+ [ $? = 1 ] && exit 1
+ sleep 2
+ libvirt_start
+ else
+ libvirt_reboot
+ fi
+ exit $?
+ ;;
+
+ status)
+ libvirt_check_config
+ libvirt_status
+ exit $?
+ ;;
+
+ getconfignames)
+ echo "hostlist hypervisor_uri reboot_method"
+ exit 0
+ ;;
+
+ getinfo-devid)
+ echo "libvirt STONITH device"
+ exit 0
+ ;;
+
+ getinfo-devname)
+ echo "libvirt STONITH external device"
+ exit 0
+ ;;
+
+ getinfo-devdescr)
+ echo "libvirt-based host reset for Xen/KVM guest domain through hypervisor"
+ exit 0
+ ;;
+
+ getinfo-devurl)
+ echo "http://libvirt.org/uri.html http://linux-ha.org/wiki"
+ exit 0
+ ;;
+
+ getinfo-xml)
+ libvirt_info
+ echo 0;
+ ;;
+
+ *)
+ exit 1
+ ;;
+esac
+
+# vi:et:ts=4:sw=4
diff --git a/lib/plugins/stonith/external/nut b/lib/plugins/stonith/external/nut
new file mode 100644
index 0000000..9e51bb8
--- /dev/null
+++ b/lib/plugins/stonith/external/nut
@@ -0,0 +1,302 @@
+#!/bin/sh
+
+# External STONITH module that uses the NUT daemon to control an external UPS.
+# See the comments below, and the various NUT man pages, for how this
+# script works. It should work unchanged with most modern "smart" APC UPSes in
+# a Redhat/Fedora/RHEL-style distribution with the nut package installed.
+
+# Author: William Seligman <seligman@nevis.columbia.edu>
+# License: GPLv2
+
+# As you're designing your UPS and STONITH set-up, it may help to consider that
+# there can be potentially three computers involved:
+# 1) the machine running this STONITH module;
+# 2) the machine being controlled by this STONITH module ($hostname);
+# 3) the machine that can send commands to the UPS.
+
+# On my cluster, all the UPSes have SNMP smartcards, so every host can communicate
+# with every UPS; in other words, machines (1) and (3) are the same. If your UPSes
+# are controlled via serial or USB connections, then you might have a
+# situation in which $hostname is plugged into a UPS, which has a serial connection
+# to some master "power-control" computer, and can potentially be STONITHed
+# by any other machine in your cluster.
+
+# In general, you'll probably need the nut daemon running on both the hosts (1) and
+# (3) in the above list. The NUT daemon will also have to run on (2) if you want the
+# reset command to gracefully reboot $hostname.
+
+# The NUT command default locations. In the RHEL-type nut packages, these binaries
+# are in /usr/bin.
+RHELUPSCMD="/usr/bin/upscmd"
+RHELUPSC="/usr/bin/upsc"
+
+# Defaults for APC smart UPSes:
+
+# Reset = reboot $hostname; this will be a graceful reboot if the host
+# is running NUT and monitoring $ups.
+APCRESET="shutdown.return"
+
+# Poweroff = turn off $hostname immediately by cutting the power on $ups.
+# For a graceful shutdown, use shutdown.stayoff instead of load.off,
+# but it might take a few minutes to shutdown in this way.
+APCPOWEROFF="load.off"
+
+# Poweron = turn on the power to $ups, which will presumably turn on $hostname.
+# (Did you set $hostname's BIOS to boot up on AC power restore, as opposed to
+# "last state"?)
+APCPOWERON="load.on"
+
+# Status = returns a short string with the $ups status; OL = on-line, OFF = off-line, etc.
+APCSTATUSVAR="ups.status"
+
+
+# Stick in the defaults, if needed.
+if [ -z "${poweron}" ]; then
+ poweron=${APCPOWERON}
+fi
+if [ -z "${poweroff}" ]; then
+ poweroff=${APCPOWEROFF}
+fi
+if [ -z "${reset}" ]; then
+ reset=${APCRESET}
+fi
+if [ -z "${statusvar}" ]; then
+ statusvar=${APCSTATUSVAR}
+fi
+if [ -z "${upscmd}" ]; then
+ upscmd=${RHELUPSCMD}
+fi
+if [ -z "${upsc}" ]; then
+ upsc=${RHELUPSC}
+fi
+
+
+# Define the command to fetch the UPS status.
+STATUSCMD="${upsc} ${ups} ${statusvar}"
+
+usage() {
+ echo "Usage: $0 {on|off|reset|status|gethosts|getconfignames|getinfo-devid|getinfo-devname|getinfo-devdescr|getinfo-devurl|getinfo-xml}"
+}
+
+# Can we find the NUT binary?
+have_nut() {
+ test -x "${upscmd}"
+}
+have_upsc() {
+ test -x "${upsc}"
+}
+
+do_nut() {
+ have_nut || {
+ echo "Can't find NUT upscmd command"
+ return 1
+ }
+ if [ -z "${username}" -o -z "${password}" -o -z "${ups}" ]; then
+ echo "username, password or ups name missing; check configuration"
+ return 1
+ fi
+ # Execute the command given in argument 1.
+ ${upscmd} -u ${username} -p ${password} ${ups} ${1} || {
+ echo "error executing nut command"
+ return 1
+ }
+}
+
+case ${1} in
+gethosts)
+ echo ${hostname}
+ exit 0
+ ;;
+on)
+ result=1
+ do_nut "${poweron}"
+ result=$?
+ exit ${result}
+ ;;
+off)
+ result=1
+ do_nut "${poweroff}"
+ result=$?
+ exit ${result}
+ ;;
+reset)
+ result=1
+ do_nut "${reset}"
+ result=$?
+ exit $result
+ ;;
+status)
+ have_upsc || {
+ echo "Can't find NUT upsc command"
+ exit 1
+ }
+ ${STATUSCMD}
+ exit $?
+ ;;
+getconfignames)
+ echo "hostname ups username password poweron poweroff reset statusvar upscmd upsc"
+ exit 0
+ ;;
+getinfo-devid)
+ echo "NUT STONITH device"
+ exit 0
+ ;;
+getinfo-devname)
+ echo "NUT STONITH external device"
+ exit 0
+ ;;
+getinfo-devdescr)
+ echo "A STONITH device based on NUT (Network UPS Tools)."
+ echo " "
+ echo "For this STONITH script to work, the following conditions have"
+ echo "to be met:"
+ echo " "
+ echo "- NUT has to be installed on both the host running this script"
+ echo " and the host that controls the UPS (on RHEL systems, NUT is"
+ echo " in packages nut and nut-client) and the nut daemon services"
+ echo " (normally called the ups or upsd service) must be running"
+ echo " on both systems."
+ echo " "
+ echo "- The UPS name has to be defined in ups.conf on the host"
+ echo " that controls the UPS."
+ echo " "
+ echo "- The username/password to access the UPS must be defined in"
+ echo " upsd.users on the host that controls the UPS, with the instcmds"
+ echo " for poweron, poweroff, and reset allowed."
+ echo " "
+ echo "- The host that is running this script must be allowed access"
+ echo " via upsd.conf and upsd.users on the host the controls the UPS."
+ echo " "
+ echo "On RHEL systems, the files listed above are in /etc/ups."
+ echo " "
+ echo "The defaults will probably work with APC UPS devices. It might"
+ echo "work on others; 'upscmd -l (ups)' and 'upsc (ups)' will list"
+ echo "the commands and variables, and you can change the values"
+ echo "for poweron, poweroff, reset, and statusvar to suit your UPS."
+ echo "Change upscmd and upsc if your NUT binaries are not in /usr/bin."
+ exit 0
+ ;;
+getinfo-devurl)
+ echo "http://www.networkupstools.org/"
+ exit 0
+ ;;
+getinfo-xml)
+cat << nutXML
+<parameters>
+
+<parameter name="hostname" unique="1" required="1">
+<content type="string" default="" />
+<shortdesc lang="en">Hostname</shortdesc>
+<longdesc lang="en">
+The name of the host to be managed by this STONITH device.
+The nut daemon must be running on the host controllng the
+UPS _and_ on the host running this script; this script does
+not start/stop the daemons for you.
+</longdesc>
+</parameter>
+
+<parameter name="ups" required="1">
+<content type="string" default="" />
+<shortdesc lang="en">UPS name</shortdesc>
+<longdesc lang="en">
+The name of the UPS as defined in ups.conf on the host
+controlling the UPS. The format for this option is
+upsname[@controlhost[:port]]. The default controlhost is
+"localhost".
+</longdesc>
+</parameter>
+
+<parameter name="username" required="1">
+<content type="string" default="" />
+<shortdesc lang="en">Username</shortdesc>
+<longdesc lang="en">
+The username used for accessing the UPS. This is defined in
+upsd.conf on the host controlling the UPS.
+</longdesc>
+</parameter>
+
+<parameter name="password" required="1">
+<content type="string" default="" />
+<shortdesc lang="en">Password</shortdesc>
+<longdesc lang="en">
+The password used for logging in to the UPS for the host
+controlling the UPS, as defined in upsd.conf on that host.
+</longdesc>
+</parameter>
+
+<parameter name="poweron">
+<content type="string" default="$APCPOWERON" />
+<shortdesc lang="en">UPS Power On command</shortdesc>
+<longdesc lang="en">
+The NUT hardware command to turn on the UPS. The default
+should work for most "smart" APC UPSes. For a list of
+commands that your UPS can support, type 'upscmd -l (ups)'
+on the command line.</longdesc>
+</parameter>
+
+<parameter name="poweroff">
+<content type="string" default="$APCPOWEROFF" />
+<shortdesc lang="en">UPS Power Off command</shortdesc>
+<longdesc lang="en">
+The NUT hardware command to turn off the UPS. On most APC
+"smart" UPSes, the command shutdown.stayoff will result
+in a graceful shutdown, provided the host is running the
+nut daemon, but this might take a few minutes; load.off
+will cut the power immediately. For a list of commands that
+your UPS can support, type 'upscmd -l (ups)' on the command
+line.
+</longdesc>
+</parameter>
+
+<parameter name="reset">
+<content type="string" default="$APCRESET" />
+<shortdesc lang="en">UPS Reset command</shortdesc>
+<longdesc lang="en">
+The NUT hardware command to reset the host. On most APC
+"smart" UPSes, the command shutdown.return will result
+in a graceful shutdown, with power restored after perhaps
+a short interval. For a list of commands that your UPS can
+ support, type 'upscmd -l (ups)' on the command line.
+</longdesc>
+</parameter>
+
+<parameter name="statusvar">
+<content type="string" default="$APCSTATUSVAR" />
+<shortdesc lang="en">UPS Status variable</shortdesc>
+<longdesc lang="en">
+The NUT variable that returns the status of the UPS. On APC
+UPSes, the value of ups.status will be "OL" if the UPS is
+"on-line." For a list of variables that your UPS supports,
+type 'upsc (ups)' on the command line.
+</longdesc>
+</parameter>
+
+<parameter name="upscmd">
+<content type="string" default="$RHELUPSCMD" />
+<shortdesc lang="en">upscmd binary location</shortdesc>
+<longdesc lang="en">
+The full path to the NUT binary command 'upscmd'. On RHEL
+systems with the nut RPM installed, this location is
+/usr/bin/upscmd.
+</longdesc>
+</parameter>
+
+<parameter name="upsc">
+<content type="string" default="$RHELUPSC" />
+<shortdesc lang="en">upsc binary location</shortdesc>
+<longdesc lang="en">
+The full path to the NUT binary command 'upsc'. On RHEL
+systems with the nut RPM installed, this location is
+/usr/bin/upsc.
+</longdesc>
+</parameter>
+
+</parameters>
+nutXML
+exit 0
+;;
+*)
+ usage
+ exit 1
+ ;;
+esac
diff --git a/lib/plugins/stonith/external/rackpdu b/lib/plugins/stonith/external/rackpdu
new file mode 100644
index 0000000..7d0e20b
--- /dev/null
+++ b/lib/plugins/stonith/external/rackpdu
@@ -0,0 +1,280 @@
+#!/bin/sh
+#
+# External STONITH module for APC Switched Rack PDU
+#
+# Copyright (c) 2008 Sergey Maznichenko <msergeyb@gmail.com> <inbox@it-consultant.su>
+# Version 1.2
+#
+# See http://www.it-consultant.su/rackpdu
+# for additional information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of version 2 of the GNU General Public License as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it would be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#
+# Further, this software is distributed without any warranty that it is
+# free of the rightful claim of any third person regarding infringement
+# or the like. Any license provided herein, whether implied or
+# otherwise, applies only to this software file. Patent licenses, if
+# any, provided herein do not apply to combinations of this program with
+# other software, or any other product whatsoever.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write the Free Software Foundation,
+# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
+#
+
+SWITCH_ON="1"
+SWITCH_OFF="2"
+SWITCH_RESET="3"
+
+DEFAULT_NAMES_OID=".1.3.6.1.4.1.318.1.1.12.3.3.1.1.2"
+DEFAULT_COMMAND_OID=".1.3.6.1.4.1.318.1.1.12.3.3.1.1.4"
+
+if [ -z "$oid" ]; then
+ oid=$DEFAULT_COMMAND_OID
+fi
+
+if [ -z "$names_oid" ]; then
+ names_oid=$DEFAULT_NAMES_OID
+fi
+
+if [ -z "$outlet_config" ]; then
+ outlet_config="none"
+fi
+
+GetOutletNumber() {
+ local nodename=$1
+
+ if [ "$outlet_config" != "none" ]; then
+ # Get outlet number from file
+
+ if [ -f "$outlet_config" ]; then
+ local outlet_num=`grep $nodename $outlet_config | tr -d ' ' | cut -f2 -d'='`
+ if [ -z "$outlet_num" ]; then
+ ha_log.sh err "Outlet number not found for node $nodename. Check configuration file $outlet_config"
+ return 0
+ fi
+ return $outlet_num
+ else
+ ha_log.sh err "File $outlet_config not found."
+ return 0
+ fi
+ else
+ # Get outlet number from device
+
+ local outlet_num=1
+ local snmp_result
+ snmp_result=`snmpwalk -v1 -c $community $pduip $names_oid 2>&1`
+ if [ $? -ne 0 ]; then
+ ha_log.sh err "snmpwalk $community $pduip $names_oid failed. Result: $snmp_result"
+ return 0
+ fi
+
+ local names
+ names=`echo "$snmp_result" | cut -f2 -d'"' | tr ' ' '_' | tr '\012' ' '`
+ for name in $names; do
+ if [ "$name" != "$nodename" ]; then
+ local outlet_num=`expr $outlet_num + 1`
+ continue
+ fi
+
+ return $outlet_num
+ done
+
+ ha_log.sh err "Outlet number not found for node $nodename. Result: $snmp_result"
+ return 0
+ fi
+}
+
+SendCommand() {
+
+ local host=$1
+ local command=$2
+
+ GetOutletNumber $host
+ local outlet=$?
+
+ if [ $outlet -gt 0 ]; then
+ local set_result
+ set_result=`snmpset -v1 -c $community $pduip $oid.$outlet i $command 2>&1`
+ if [ $? -ne 0 ]; then
+ ha_log.sh err "Write SNMP to $pduip value $oid.$outlet=$command failed. Result: $set_result"
+ return 1
+ fi
+ if echo "$set_result" | grep -qs "Timeout"; then
+ ha_log.sh err "Write SNMP to $pduip value $oid.$outlet=$command timed out. Result: $set_result"
+ return 1
+ fi
+ return 0
+ else
+ return 1
+ fi
+}
+
+hostlist=`echo $hostlist | tr ',' ' '`
+incommand=$1
+innode=$2
+
+case $incommand in
+gethosts)
+ if [ "$hostlist" = "AUTO" ]; then
+ snmp_result=`snmpwalk -v1 -c $community $pduip $names_oid 2>&1`
+ if [ $? -ne 0 ]; then
+ ha_log.sh err "snmpwalk $community $pduip $names_oid failed. Result: $snmp_result"
+ exit 1
+ fi
+ if echo "$snmp_result" | grep -qs "Timeout"; then
+ ha_log.sh err "snmpwalk $community $pduip $names_oid timed out. Result: $snmp_result"
+ exit 1
+ else
+ hostlist=`echo "$snmp_result" | cut -f2 -d'"' | tr ' ' '_' | tr '\012' ' '`
+ fi
+ fi
+
+ for h in $hostlist ; do
+ echo $h
+ done
+
+ exit 0
+ ;;
+on)
+ if
+ SendCommand $innode $SWITCH_ON
+ then
+ exit 0
+ else
+ exit 1
+ fi
+ ;;
+off)
+ if
+ SendCommand $innode $SWITCH_OFF
+ then
+ exit 0
+ else
+ exit 1
+ fi
+ ;;
+reset)
+ if
+ SendCommand $innode $SWITCH_RESET
+ then
+ exit 0
+ else
+ exit 1
+ fi
+ ;;
+status)
+ if [ -z "$pduip" ]; then
+ exit 1
+ fi
+
+ if ping -w1 -c1 $pduip >/dev/null 2>&1; then
+ exit 0
+ else
+ exit 1
+ fi
+ ;;
+getconfignames)
+ echo "hostlist pduip community"
+ exit 0
+ ;;
+getinfo-devid)
+ echo "rackpdu STONITH device"
+ exit 0
+ ;;
+getinfo-devname)
+ echo "rackpdu STONITH external device"
+ exit 0
+ ;;
+getinfo-devdescr)
+ echo "APC Switched Rack PDU"
+ exit 0
+ ;;
+getinfo-devurl)
+ echo "http://www.apcc.com/products/family/index.cfm?id=30"
+ exit 0
+ ;;
+getinfo-xml)
+ cat << PDUXML
+<parameters>
+ <parameter name="hostlist" unique="1" required="1">
+ <content type="string" default="AUTO" />
+ <shortdesc lang="en">Hostlist</shortdesc>
+ <longdesc lang="en">
+The list of hosts that the STONITH device controls (comma or space separated).
+If you set value of this parameter to AUTO, list of hosts will be get from Rack PDU device.
+ </longdesc>
+ </parameter>
+
+ <parameter name="pduip" unique="1" required="1">
+ <content type="string" />
+ <shortdesc lang="en">Name or IP address of Rack PDU device.</shortdesc>
+ <longdesc lang="en">Name or IP address of Rack PDU device.</longdesc>
+ </parameter>
+
+ <parameter name="community" unique="1" required="1">
+ <content type="string" default="private" />
+ <shortdesc lang="en">Name of write community.</shortdesc>
+ <longdesc lang="en">Name of write community.</longdesc>
+ </parameter>
+
+ <parameter name="oid" unique="1" required="0">
+ <content type="string" />
+ <shortdesc lang="en">
+ The OID without the outlet number.
+ </shortdesc>
+ <longdesc lang="en">
+The SNMP OID for the PDU. minus the outlet number.
+Try .1.3.6.1.4.1.318.1.1.12.3.3.1.1.4 (default value)
+or use mib from ftp://ftp.apcc.com/apc/public/software/pnetmib/mib/
+Varies on different APC hardware and firmware.
+Warning! No dot at the end of OID
+ </longdesc>
+ </parameter>
+
+ <parameter name="names_oid" unique="1" required="0">
+ <content type="string" />
+ <shortdesc lang="en">The OID for getting names of outlets.</shortdesc>
+ <longdesc lang="en">
+The SNMP OID for getting names of outlets.
+It is required to recognize outlet number by nodename.
+Try ".1.3.6.1.4.1.318.1.1.12.3.3.1.1.2" (default value)
+or use mib from ftp://ftp.apcc.com/apc/public/software/pnetmib/mib/
+Names of nodes must be equal names of outlets, in other way use outlet_config parameter.
+If you set 'names_oid' parameter then parameter outlet_config must not be use.
+Varies on different APC hardware and firmware.
+Warning! No dot at the end of OID
+ </longdesc>
+ </parameter>
+
+ <parameter name="outlet_config" unique="1" required="0">
+ <content type="string" />
+ <shortdesc lang="en">Configuration file. Other way to recognize outlet number by nodename.</shortdesc>
+ <longdesc lang="en">
+Configuration file. Other way to recognize outlet number by nodename.
+Configuration file which contains
+node_name=outlet_number
+strings.
+
+Example:
+server1=1
+server2=2
+
+If you use outlet_config parameter then names_oid parameter can have any value and it is not uses.
+ </longdesc>
+ </parameter>
+
+</parameters>
+PDUXML
+ exit 0
+ ;;
+*)
+ exit 1
+ ;;
+esac
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:
diff --git a/lib/plugins/stonith/external/ssh.in b/lib/plugins/stonith/external/ssh.in
new file mode 100644
index 0000000..2a8eb73
--- /dev/null
+++ b/lib/plugins/stonith/external/ssh.in
@@ -0,0 +1,176 @@
+#!/bin/sh
+#
+# External STONITH module for ssh.
+#
+# Copyright (c) 2004 SUSE LINUX AG - Lars Marowsky-Bree <lmb@suse.de>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of version 2 of the GNU General Public License as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it would be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#
+# Further, this software is distributed without any warranty that it is
+# free of the rightful claim of any third person regarding infringement
+# or the like. Any license provided herein, whether implied or
+# otherwise, applies only to this software file. Patent licenses, if
+# any, provided herein do not apply to combinations of this program with
+# other software, or any other product whatsoever.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write the Free Software Foundation,
+# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
+#
+
+SSH_COMMAND="@SSH@ -q -x -o PasswordAuthentication=no -o StrictHostKeyChecking=no -n -l root"
+#SSH_COMMAND="@SSH@ -q -x -n -l root"
+
+REBOOT_COMMAND="echo 'sleep 2; @REBOOT@ @REBOOT_OPTIONS@' | SHELL=/bin/sh at now >/dev/null 2>&1"
+
+# Warning: If you select this poweroff command, it'll physically
+# power-off the machine, and quite a number of systems won't be remotely
+# revivable.
+# TODO: Probably should touch a file on the server instead to just
+# prevent heartbeat et al from being started after the reboot.
+# POWEROFF_COMMAND="echo 'sleep 2; /sbin/poweroff -nf' | SHELL=/bin/sh at now >/dev/null 2>&1"
+POWEROFF_COMMAND="echo 'sleep 2; @REBOOT@ @REBOOT_OPTIONS@' | SHELL=/bin/sh at now >/dev/null 2>&1"
+
+# Rewrite the hostlist to accept "," as a delimeter for hostnames too.
+hostlist=`echo $hostlist | tr ',' ' '`
+
+is_host_up() {
+ for j in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ do
+ if
+ ping -w1 -c1 "$1" >/dev/null 2>&1
+ then
+ sleep 1
+ else
+ return 1
+ fi
+ done
+ return 0
+}
+
+
+case $1 in
+gethosts)
+ for h in $hostlist ; do
+ echo $h
+ done
+ exit 0
+ ;;
+on)
+ # Can't really be implemented because ssh cannot power on a system
+ # when it is powered off.
+ exit 1
+ ;;
+off)
+ # Shouldn't really be implemented because if ssh cannot power on a
+ # system, it shouldn't be allowed to power it off.
+ exit 1
+ ;;
+reset)
+ h_target=`echo $2 | tr A-Z a-z`
+ for h in $hostlist
+ do
+ h=`echo $h | tr A-Z a-z`
+ [ "$h" != "$h_target" ] &&
+ continue
+ if
+ case ${livedangerously} in
+ [Yy]*) is_host_up $h;;
+ *) true;;
+ esac
+ then
+ $SSH_COMMAND "$2" "$REBOOT_COMMAND"
+ # Good thing this is only for testing...
+ if
+ is_host_up $h
+ then
+ exit 1
+ else
+ exit 0
+ fi
+ else
+ # well... Let's call it successful, after all this is only for testing...
+ exit 0
+ fi
+ done
+ exit 1
+ ;;
+status)
+ if
+ [ -z "$hostlist" ]
+ then
+ exit 1
+ fi
+ for h in $hostlist
+ do
+ if
+ ping -w1 -c1 "$h" 2>&1 | grep "unknown host"
+ then
+ exit 1
+ fi
+ done
+ exit 0
+ ;;
+getconfignames)
+ echo "hostlist"
+ exit 0
+ ;;
+getinfo-devid)
+ echo "ssh STONITH device"
+ exit 0
+ ;;
+getinfo-devname)
+ echo "ssh STONITH external device"
+ exit 0
+ ;;
+getinfo-devdescr)
+ echo "ssh-based host reset"
+ echo "Fine for testing, but not suitable for production!"
+ echo "Only reboot action supported, no poweroff, and, surprisingly enough, no poweron."
+ exit 0
+ ;;
+getinfo-devurl)
+ echo "http://openssh.org"
+ exit 0
+ ;;
+getinfo-xml)
+ cat << SSHXML
+<parameters>
+<parameter name="hostlist" unique="1" required="1">
+<content type="string" />
+<shortdesc lang="en">
+Hostlist
+</shortdesc>
+<longdesc lang="en">
+The list of hosts that the STONITH device controls
+</longdesc>
+</parameter>
+
+<parameter name="livedangerously" unique="0" required="0">
+<content type="enum" />
+<shortdesc lang="en">
+Live Dangerously!!
+</shortdesc>
+<longdesc lang="en">
+Set to "yes" if you want to risk your system's integrity.
+Of course, since this plugin isn't for production, using it
+in production at all is a bad idea. On the other hand,
+setting this parameter to yes makes it an even worse idea.
+Viva la Vida Loca!
+</longdesc>
+</parameter>
+
+</parameters>
+SSHXML
+ exit 0
+ ;;
+*)
+ exit 1
+ ;;
+esac
diff --git a/lib/plugins/stonith/external/vcenter b/lib/plugins/stonith/external/vcenter
new file mode 100755
index 0000000..71a6302
--- /dev/null
+++ b/lib/plugins/stonith/external/vcenter
@@ -0,0 +1,280 @@
+#!/usr/bin/env perl
+#
+# External STONITH module for VMWare vCenter/ESX
+#
+# Author: Nhan Ngo Dinh
+# License: GNU General Public License (GPL)
+#
+
+require 5.010;
+
+use strict;
+use warnings;
+
+sub dielog {
+ my $msg = "[";
+ $msg .= "$ARGV[0]" if defined($ARGV[0]);
+ $msg .= " $ARGV[1]" if defined($ARGV[1]);
+ $msg .= "]";
+ ( $_ ) = @_;
+ $msg .= " $_";
+ system("ha_log.sh", "err", "$msg");
+ die();
+}
+
+# Define command groups
+my @configCommands = qw{getconfignames getinfo-devid getinfo-devname getinfo-devdescr getinfo-devurl getinfo-xml};
+my @actionCommands = qw{reset on off};
+my @netCommands = (@actionCommands, qw{status gethosts listvms});
+
+# Process command line arguments
+my $command = $ARGV[0] || dielog("No command specified\n");
+
+# Command belongs to the group of commands that do not require any connection to VMware vCenter
+if ($command ~~ @configCommands) {
+ if ($command eq "getconfignames") {
+ print "VI_SERVER\nVI_PORTNUMBER\nVI_PROTOCOL\nVI_SERVICEPATH\nVI_CREDSTORE\nHOSTLIST\nRESETPOWERON\n";
+ }
+ elsif ($command eq "getinfo-devid") {
+ print "VMware vCenter STONITH device\n";
+ }
+ elsif ($command eq "getinfo-devname") {
+ print "VMware vCenter STONITH device\n";
+ }
+ elsif ($command eq "getinfo-devdescr") {
+ print "VMWare vCenter STONITH device\n";
+ }
+ elsif ($command eq "getinfo-devurl") {
+ print "http://www.vmware.com/\n";
+ }
+ elsif ($command eq "getinfo-xml") {
+ print q{<parameters>
+<parameter name="HOSTLIST" required="1">
+<content type="string"/>
+<shortdesc lang="en">List of hosts and virtual machines (required)</shortdesc>
+<longdesc lang="en">
+The list of hosts that the VMware vCenter STONITH device controls.
+Syntax is:
+ hostname1[=VirtualMachineName1];hostname2[=VirtualMachineName2]
+
+NOTE: omit =VirtualMachineName if hostname and virtual machine names are identical
+
+Example:
+ cluster1=VMCL1;cluster2=VMCL2
+</longdesc>
+</parameter>
+<parameter name="VI_SERVER">
+<content type="string" default="localhost"/>
+<shortdesc lang="en">VMware vCenter address</shortdesc>
+<longdesc lang="en">
+The VMware vCenter address
+</longdesc>
+</parameter>
+<parameter name="VI_PROTOCOL">
+<content type="string" default="https"/>
+<shortdesc lang="en">VMware vCenter protocol</shortdesc>
+<longdesc lang="en">
+The VMware vCenter protocol
+</longdesc>
+</parameter>
+<parameter name="VI_PORTNUMBER">
+<content type="string" default="443"/>
+<shortdesc lang="en">VMware vCenter port number</shortdesc>
+<longdesc lang="en">
+The VMware vCenter port number
+</longdesc>
+</parameter>
+<parameter name="VI_SERVICEPATH">
+<content type="string" default="/sdk"/>
+<shortdesc lang="en">VMware vCenter service path</shortdesc>
+<longdesc lang="en">
+The VMware vCenter services path
+</longdesc>
+</parameter>
+<parameter name="VI_CREDSTORE" required="1">
+<content type="string"/>
+<shortdesc lang="en">VMware vCenter credentials store file</shortdesc>
+<longdesc lang="en">
+VMware vCenter credentials store file
+</longdesc>
+</parameter>
+<parameter name="RESETPOWERON">
+<content type="string" default="1"/>
+<shortdesc lang="en">PowerOnVM on reset</shortdesc>
+<longdesc lang="en">
+Enable/disable a PowerOnVM on reset when the target virtual machine is off
+Allowed values: 0, 1
+</longdesc>
+</parameter>
+<parameter name="PERL_LWP_SSL_VERIFY_HOSTNAME">
+<content type="string"/>
+<shortdesc lang="en">Enable or disable SSL hostname verification</shortdesc>
+<longdesc lang="en">
+To disable SSL hostname verification set this option to 0.
+To enable hostname verification, set this option to 1.
+This option is actually part of the LWP Perl library.
+See LWP(3pm) for more information.
+</longdesc>
+</parameter>
+</parameters>} . "\n";
+ }
+ else { dielog("Invalid command specified: $command\n"); }
+}
+
+# Command belongs to the group of commands that require connecting to VMware vCenter
+elsif ($command ~~ @netCommands) {
+
+ eval { require VMware::VIRuntime; }
+ or dielog("Missing perl module VMware::VIRuntime. Download and install 'VMware Infrastructure (VI) Perl Toolkit', available at http://www.vmware.com/support/developer/viperltoolkit/ \n");
+
+ # A valid VI_CREDSTORE is required to avoid interactive prompt
+ ( exists $ENV{'VI_CREDSTORE'} ) || dielog("VI_CREDSTORE not specified\n");
+
+ # HOSTLIST is mandatory
+ exists $ENV{'HOSTLIST'} || dielog("HOSTLIST not specified\n");
+
+ # Parse HOSTLIST to %host_to_vm and %vm_to_host
+ my @hostlist = split(';', $ENV{'HOSTLIST'});
+ my %host_to_vm = ();
+ my %vm_to_host = ();
+ foreach my $host (@hostlist) {
+ my @config = split(/=/, $host);
+ my $key = $config[0]; my $value = $config[1];
+ if (!defined($value)) { $value = $config[0]; }
+ $host_to_vm{$key} = $value;
+ $vm_to_host{(lc $value)} = $key;
+ }
+
+ eval {
+ # VI API: reads options from the environment variables into appropriate data structures for validation.
+ Opts::parse();
+ # VI API: ensures that input values from environment variable are complete, consistent and valid.
+ Opts::validate();
+ # VI API: establishes a session with the VirtualCenter Management Server or ESX Server Web service
+ Util::connect();
+ };
+ if ($@) {
+ # This is just a placeholder for any error handling procedure
+ dielog($@);
+ }
+
+ # Command belongs to the group of commands that performs actions on Virtual Machines
+ if ($command ~~ @actionCommands) {
+
+ my $targetHost = $ARGV[1] || dielog("No target specified\n");
+
+ # Require that specified target host exists in the specified HOSTLIST
+ if (exists $host_to_vm{$targetHost}) {
+
+ my $vm;
+ my $esx;
+ eval {
+ # VI API: searches the inventory tree for a VirtualMachine managed entity whose name matches
+ # the name of the virtual machine assigned to the target host in HOSTLIST
+ $vm = Vim::find_entity_view(view_type => "VirtualMachine", filter => { name => qr/^\Q$host_to_vm{$targetHost}\E/i });
+ if (!defined $vm) {
+ dielog("Machine $targetHost was not found");
+ }
+
+ # VI API: retrieves the properties of the managed object reference runtime.host of the VirtualMachine
+ # managed entity obtained by the previous command
+ # NOTE: This is essentially a workaround to vSphere Perl SDK
+ # to allow pointing to the right HostSystem. This is probably
+ # done by changing the current HostSystem in the Web Service
+ # session context. WARNING: Do not use the same session for any
+ # other concurrent operation.
+ $esx = Vim::get_view(mo_ref => $vm->{"runtime"}{"host"})->name;
+ };
+ if ($@) {
+ if (ref($@) eq "SoapFault") { dielog("$@->detail\n"); }
+ dielog($@);
+ }
+
+ my $powerState = $vm->get_property('runtime.powerState')->val;
+ if ($powerState eq "suspended") {
+ # This implementation assumes that suspending a cluster node can cause
+ # severe failures on shared resources, thus any failover operation should
+ # be blocked.
+ dielog("Machine $esx:$vm->{'name'} is in a suspended state\n");
+ }
+
+ eval {
+ if ($command eq "reset") {
+ if ($powerState eq "poweredOn") {
+ $vm->ResetVM();
+ system("ha_log.sh", "info", "Machine $esx:$vm->{'name'} has been reset");
+ } else {
+ system("ha_log.sh", "warn", "Tried to ResetVM $esx:$vm->{'name'} that was $powerState");
+ # Start a virtual machine on reset only if explicitly allowed by RESETPOWERON
+ if ($powerState eq "poweredOff" && (! exists $ENV{'RESETPOWERON'} || $ENV{'RESETPOWERON'} ne 0)) {
+ $vm->PowerOnVM();
+ system("ha_log.sh", "info", "Machine $esx:$vm->{'name'} has been powered on");
+ } else {
+ dielog("Could not complete $esx:$vm->{'name'} power cycle");
+ }
+ }
+ }
+ elsif ($command eq "off") {
+ if ($powerState eq "poweredOn") {
+ $vm->PowerOffVM();
+ system("ha_log.sh", "info", "Machine $esx:$vm->{'name'} has been powered off");
+ } else {
+ system("ha_log.sh", "warn", "Tried to PowerOffVM $esx:$vm->{'name'} that was $powerState");
+
+ }
+ }
+ elsif ($command eq "on") {
+ if ($powerState eq "poweredOff") {
+ $vm->PowerOnVM();
+ system("ha_log.sh", "info", "Machine $esx:$vm->{'name'} has been powered on");
+ } else {
+ system("ha_log.sh", "warn", "Tried to PowerOnVM $esx:$vm->{'name'} that was $powerState");
+ }
+ }
+ else { dielog("Invalid command specified: $command\n"); }
+ };
+ if ($@) {
+ if (ref($@) eq "SoapFault") { dielog("$@->detail\n"); }
+ dielog($@);
+ }
+
+ } else { dielog("Invalid target specified\n"); }
+ } else {
+ # Command belongs to the group of commands that lookup the status of VMware vCenter and/or virtual machines
+ if ($command eq "status") {
+ # we already connect to the vcenter, no need to do
+ # anything else in status
+ ;
+ }
+ elsif ($command eq "gethosts") {
+ foreach my $key (keys(%host_to_vm)) {
+ print "$key \n";
+ }
+ }
+ elsif ($command eq "listvms") {
+ eval {
+ # VI API: Searches the inventory tree for all VirtualMachine managed objects
+ my $vms = Vim::find_entity_views(view_type => "VirtualMachine");
+ if (defined $vms) {
+ printf(STDERR "%-50s %-20s\n", "VM Name", "Power state");
+ print STDERR "-" x 70 . "\n";
+ foreach my $vm (@$vms) {
+ my $powerState = $vm->get_property('runtime.powerState')->val;
+ printf("%-50s %-20s\n", $vm->{name}, $powerState);
+ }
+ }
+ };
+ }
+ else { dielog("Invalid command specified: $command\n"); }
+ }
+ eval {
+ Util::disconnect();
+ };
+ if ($@) {
+ # This is just a placeholder for any error handling procedure
+ dielog($@);
+ }
+}
+else { dielog("Invalid command specified: $command\n"); }
+
+exit(0);
diff --git a/lib/plugins/stonith/external/vmware b/lib/plugins/stonith/external/vmware
new file mode 100644
index 0000000..55966ba
--- /dev/null
+++ b/lib/plugins/stonith/external/vmware
@@ -0,0 +1,216 @@
+#!/usr/bin/perl
+# External STONITH module for VMWare Server Guests
+#
+# Copyright (c) 2004 SUSE LINUX AG - Andrew Beekhof <abeekhof@suse.de>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of version 2 of the GNU General Public License as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it would be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#
+# Further, this software is distributed without any warranty that it is
+# free of the rightful claim of any third person regarding infringement
+# or the like. Any license provided herein, whether implied or
+# otherwise, applies only to this software file. Patent licenses, if
+# any, provided herein do not apply to combinations of this program with
+# other software, or any other product whatsoever.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write the Free Software Foundation,
+# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
+#
+
+sub supply_default
+{
+ my $name = $_[0];
+ my $value = $_[1];
+
+ if ( defined $ENV{$name} ) {
+ #print "Set: $name=$ENV{$name}\n";
+ } else {
+ $ENV{$name} = $value;
+ #print "Default: $name=$ENV{$name}\n";
+ }
+}
+
+sub vmware_command
+{
+ my $config = $_[0];
+ my $action = $_[1];
+ my @lines;
+
+ my $device = $ENV{'device_host'};
+
+ if ( $device =~ /localhost/ ) {
+ @lines = readpipe "vmware-cmd $config $action";
+
+ } else {
+ @lines = readpipe "ssh $device \"vmware-cmd \\\"$config\\\" $action\"";
+ }
+
+ #print @lines;
+ return @lines;
+}
+
+sub is_host_active
+{
+ my $config = config_for_host($_[0]);
+ my @lines = vmware_command($config, "getstate");
+ foreach $line (@lines) {
+ if ( $line =~ /getstate.* = on/ ) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+sub supported_hosts
+{
+ my $line;
+ my @lines;
+
+ if ( defined $ENV{'host_map'} ) {
+ @lines = split(/;/, $ENV{'host_map'});
+ foreach $line (@lines){
+ @config = split(/=/, $line);
+ $host = $config[0];
+ if ( is_host_active($host) == 1 ) {
+ print "$host\n";
+ }
+ }
+
+ } else {
+ @lines = vmware_command("-l");
+ foreach $line (@lines){
+ my @elements = split(/\//, $line);
+ $host = $elements[$#elements-1];
+ if ( is_host_active($host) == 1 ) {
+ print "$host\n";
+ }
+ }
+ }
+}
+
+sub config_for_host
+{
+ my $line;
+ my @lines;
+ my $target = $_[0];
+
+ if ( defined $ENV{'host_map'} ) {
+ @lines = split(/;/, $ENV{'host_map'});
+ foreach $line (@lines){
+ if ( $line =~ /^$target=/ ) {
+ @config = split(/=/, $line);
+ return $config[1];
+ }
+ }
+
+ } else {
+ @lines = vmware_command("-l");
+
+ foreach $line (@lines){
+ if ( $line =~ /\/$target\// ) {
+ chop($line);
+ return $line;
+ }
+ }
+ }
+}
+
+$command = $ARGV[0];
+if ( defined $ARGV[1] ) {
+ $targetHost = $ARGV[1];
+}
+
+supply_default("device_host", "localhost");
+
+if ( $command =~ /^gethosts$/ ) {
+ supported_hosts;
+
+} elsif ( $command =~ /^getconfignames$/ ) {
+ print "device_host\n";
+
+} elsif ( $command =~ /^getinfo-devid$/ ) {
+ print "VMware Server STONITH device\n";
+} elsif ( $command =~ /^getinfo-devname$/ ) {
+ print "VMware Server STONITH device\n";
+} elsif ( $command =~ /^getinfo-devdescr$/ ) {
+ print "VMware Server STONITH device\n";
+} elsif ( $command =~ /^getinfo-devurl$/ ) {
+ print "http://www.vmware.com/";
+
+} elsif ( $command =~ /^on$/ ) {
+ $config = config_for_host($targetHost);
+ my @lines = vmware_command($config, "start hard");
+ print @lines;
+
+} elsif ( $command =~ /^off$/ ) {
+ $config = config_for_host($targetHost);
+ my @lines = vmware_command($config, "stop hard");
+ print @lines;
+
+} elsif ( $command =~ /^reset$/ ) {
+ $config = config_for_host($targetHost);
+ my @lines = vmware_command($config, "reset hard");
+ print @lines;
+
+} elsif ( $command =~ /^status$/ ) {
+ my $rc = 7;
+ my $device = $ENV{'device_host'};
+ if ( $device =~ /localhost/ ) {
+ $rc = 0;
+ # TODO: Check for the vmware process
+ print "Local version: always running\n";
+
+ } else {
+ print "Remote version: running ping\n";
+ @lines = readpipe "ping -c1 $device";
+ print @lines;
+
+ foreach $line ( @lines ) {
+ if ( $line =~ /0% packet loss/ ) {
+ $rc = 0;
+ last;
+ }
+ }
+ }
+ exit($rc);
+
+} elsif ( $command =~ /^getinfo-xml$/ ) {
+ $metadata = <<END;
+<parameters>
+<parameter name="host_map" unique="0" required="0">
+<content type="string" />
+<shortdesc lang="en">
+Host Map
+</shortdesc>
+<longdesc lang="en">
+A mapping of hostnames to config paths supported by this device.
+Eg. host1=/config/path/1;host2=/config/path/2;host3=/config/path/3;
+</longdesc>
+</parameter>
+<parameter name="device_host" unique="0" required="0">
+<content type="string" />
+<shortdesc lang="en">
+Device Host
+</shortdesc>
+<longdesc lang="en">
+The machine _hosting_ the virtual machines
+</longdesc>
+</parameter>
+</parameters>
+END
+
+print $metadata;
+
+} else {
+ print "Command $command: not supported\n";
+ exit(3); # Not implemented
+}
+
+
+exit(0);
diff --git a/lib/plugins/stonith/external/xen0 b/lib/plugins/stonith/external/xen0
new file mode 100644
index 0000000..ef1ee40
--- /dev/null
+++ b/lib/plugins/stonith/external/xen0
@@ -0,0 +1,253 @@
+#!/bin/sh
+#
+# External STONITH module for Xen Dom0 through ssh.
+#
+# Description: Uses Xen Dom0 Domain as a STONITH device
+# to control DomUs.
+#
+#
+# Author: Serge Dubrouski (sergeyfd@gmail.com)
+# Inspired by Lars Marowsky-Bree's external/ssh agent.
+#
+# Copyright 2007 Serge Dubrouski <sergeyfd@gmail.com>
+# License: GNU General Public License (GPL)
+#
+
+STOP_COMMAND="xm destroy"
+START_COMMAND="xm create"
+DUMP_COMMAND="xm dump-core"
+DEFAULT_XEN_DIR="/etc/xen"
+SSH_COMMAND="/usr/bin/ssh -q -x -n"
+
+# Rewrite the hostlist to accept "," as a delimeter for hostnames too.
+hostlist=`echo $hostlist | tr ',' ' '`
+
+CheckIfDead() {
+ for j in 1 2 3 4 5
+ do
+ if ! ping -w1 -c1 "$1" >/dev/null 2>&1
+ then
+ return 0
+ fi
+ sleep 1
+ done
+
+ return 1
+}
+
+CheckHostList() {
+ if [ "x" = "x$hostlist" ]
+ then
+ ha_log.sh err "hostlist isn't set"
+ exit 1
+ fi
+}
+
+CheckDom0() {
+ if [ "x" = "x$dom0" ]
+ then
+ ha_log.sh err "dom0 isn't set"
+ exit 1
+ fi
+}
+
+RunCommand() {
+ CheckHostList
+ CheckDom0
+
+ for h in $hostlist
+ do
+ CIFS=$IFS
+ IFS=:
+ read node cfg << -!
+$h
+-!
+ IFS=$CIFS
+
+ if [ "x" = "x$node" ]
+ then
+ ha_log.sh err "Syntax error in host list"
+ exit 1
+ fi
+
+ if [ "x" = "x$cfg" ]
+ then
+ cfg="${DEFAULT_XEN_DIR}/${node}.cfg"
+ fi
+
+ if [ "$node" != "$1" ]
+ then
+ continue
+ fi
+
+ case $2 in
+ stop)
+ kill_node=`$SSH_COMMAND $dom0 "grep ^[[:space:]]*name $cfg" | cut -f 2 -d '=' | sed -e 's,",,g'`
+ if [ "x" = "x$kill_node" ]
+ then
+ ha_log.sh err "Couldn't find a node name to stop"
+ exit 1
+ fi
+
+ if [ "x$run_dump" != "x" ]
+ then
+ #Need to run core dump
+ if [ "x$dump_dir" != "x" ]
+ then
+ #Dump with the specified core file
+ TIMESTAMP=`date +%Y-%m%d-%H%M.%S`
+ DOMAINNAME=`printf "%s" $kill_node`
+ COREFILE=$dump_dir/$TIMESTAMP-$DOMAINNAME.core
+ $SSH_COMMAND $dom0 "(mkdir -p $dump_dir; $DUMP_COMMAND $kill_node $COREFILE) >/dev/null 2>&1"
+ else
+ $SSH_COMMAND $dom0 "$DUMP_COMMAND $kill_node >/dev/null 2>&1"
+ fi
+ fi
+ $SSH_COMMAND $dom0 "(sleep 2; $STOP_COMMAND $kill_node) >/dev/null 2>&1 &"
+ break;;
+ start)
+ $SSH_COMMAND $dom0 "(sleep 2; $START_COMMAND $cfg) >/dev/null 2>&1 &"
+ break;;
+ esac
+ exit 0
+ done
+}
+
+
+# Main code
+
+case $1 in
+gethosts)
+ CheckHostList
+
+ for h in $hostlist ; do
+ CIFS=$IFS
+ IFS=:
+ read node cfg << -!
+$h
+-!
+ IFS=$CIFS
+
+ echo $node
+ done
+ exit 0
+ ;;
+on)
+ RunCommand $2 start
+ exit $?
+ ;;
+off)
+ if RunCommand $2 stop
+ then
+ if CheckIfDead $2
+ then
+ exit 0
+ fi
+ fi
+
+ exit 1
+ ;;
+reset)
+ RunCommand $2 stop
+
+ if CheckIfDead $2
+ then
+ RunCommand $2 start
+ exit 0
+ fi
+
+ exit 1
+ ;;
+status)
+ CheckHostList
+
+ for h in $hostlist
+ do
+ CIFS=$IFS
+ IFS=:
+ read node cfg << -!
+$h
+-!
+ IFS=$CIFS
+
+ echo $node
+ if ping -w1 -c1 "$node" 2>&1 | grep "unknown host"
+ then
+ exit 1
+ fi
+ done
+ exit 0
+ ;;
+getconfignames)
+ echo "hostlist dom0"
+ exit 0
+ ;;
+getinfo-devid)
+ echo "xen0 STONITH device"
+ exit 0
+ ;;
+getinfo-devname)
+ echo "xen0 STONITH external device"
+ exit 0
+ ;;
+getinfo-devdescr)
+ echo "ssh-based host reset for Xen DomU trough Dom0"
+ echo "Fine for testing, but not really suitable for production!"
+ exit 0
+ ;;
+getinfo-devurl)
+ echo "http://openssh.org http://www.xensource.com/ http://linux-ha.org/wiki"
+ exit 0
+ ;;
+getinfo-xml)
+ cat << SSHXML
+<parameters>
+<parameter name="hostlist" unique="1" required="1">
+<content type="string" />
+<shortdesc lang="en">
+Hostlist
+</shortdesc>
+<longdesc lang="en">
+The list of controlled nodes in a format node[:config_file].
+For example: "node1:/opt/xen/node1.cfg node2"
+If config file isn't set it defaults to /etc/xen/{node_name}.cfg
+</longdesc>
+</parameter>
+<parameter name="dom0" unique="1" required="1">
+<content type="string" />
+<shortdesc lang="en">
+Dom0
+</shortdesc>
+<longdesc lang="en">
+Name of the Dom0 Xen node. Root user shall be able to ssh to that node.
+</longdesc>
+</parameter>
+<parameter name="run_dump" unique="0" required="0">
+<content type="string" />
+<shortdesc lang="en">
+Run dump-core
+</shortdesc>
+<longdesc lang="en">
+If set plugin will call "xm dump-core" before killing DomU
+</longdesc>
+</parameter>
+<parameter name="dump_dir" unique="1" required="0">
+<content type="string" />
+<shortdesc lang="en">
+Run dump-core with the specified directory
+</shortdesc>
+<longdesc lang="en">
+This parameter can indicate the dump destination.
+Should be set as a full path format, ex.) "/var/log/dump"
+The above example would dump the core, like;
+/var/log/dump/2009-0316-1403.37-domU.core
+</longdesc>
+</parameter>
+</parameters>
+SSHXML
+ exit 0
+ ;;
+*)
+ exit 1
+ ;;
+esac
diff --git a/lib/plugins/stonith/external/xen0-ha-dom0-stonith-helper b/lib/plugins/stonith/external/xen0-ha-dom0-stonith-helper
new file mode 100755
index 0000000..b313f8b
--- /dev/null
+++ b/lib/plugins/stonith/external/xen0-ha-dom0-stonith-helper
@@ -0,0 +1,72 @@
+#!/bin/bash
+# Author: Lars Marowsky-Bree
+#
+# Copyright 2008 Lars Marowsky-Bree
+# License: GNU General Public License (GPL)
+
+# This is not an external/stonith plugin by itself, but instead a helper
+# script which gets installed in Dom0.
+
+# TODO:
+# - Error handling
+# - How to handle if the DomU resource doesn't exist?
+# - Does this truly work with split-brain?
+# - Is the handling of non-existent resources adequate?
+# ...
+# Basically: more testing. This is proof-of-concept and works, but deserves
+# validation.
+
+CMD="$1"
+DOMU="$2"
+TIMEOUT="$3"
+
+# Make sure the timeout is an integer:
+if [ "0$TIMEOUT" -eq 0 ]; then
+ TIMEOUT=300
+fi
+
+SetTargetRole() {
+ local new_role="$1"
+ crm_resource -r $DOMU --meta -p target_role -v $new_role
+
+ local timeout="$TIMEOUT"
+
+ # We only need to wait for "stopped".
+ if [ "$new_role" != "stopped" ]; then
+ return 0
+ fi
+
+ while [ $timeout -gt 0 ]; do
+ local rc
+ crm_resource -W -r $DOMU 2>&1 | grep -q "is NOT running"
+ rc=$?
+ if [ $rc -eq 0 ]; then
+ return 0
+ fi
+ timeout=$[timeout-1];
+ sleep 1
+ done
+ return 1
+}
+
+
+case $CMD in
+on) SetTargetRole started
+ exit $?
+ ;;
+off) SetTargetRole stopped
+ exit $?
+ ;;
+reset) SetTargetRole stopped || exit 1
+ SetTargetRole started
+ exit $?
+ ;;
+status) exit 0
+ ;;
+*) ha_log.sh err "Called with unknown command: $CMD"
+ exit 1
+ ;;
+esac
+
+exit 1
+
diff --git a/lib/plugins/stonith/external/xen0-ha.in b/lib/plugins/stonith/external/xen0-ha.in
new file mode 100755
index 0000000..cb42cbc
--- /dev/null
+++ b/lib/plugins/stonith/external/xen0-ha.in
@@ -0,0 +1,96 @@
+#!/bin/bash
+#
+# This STONITH script integrates a cluster running within DomUs
+# with the CRM/Pacemaker cluster running in Dom0.
+#
+# Author: Lars Marowsky-Bree
+# Copyright: 2008 Lars Marowsky-Bree
+# License: GNU General Public License (GPL)
+#
+
+SSH_COMMAND="@SSH@ -q -x -n"
+HVM_HELPER="@stonith_plugindir@/xen0-ha-dom0-stonith-helper"
+
+# Rewrite the hostlist to accept "," as a delimeter for hostnames too.
+hostlist=`echo $hostlist | tr ',' ' '`
+
+# Runs a command on the host, waiting for it to return
+RunHVMCommand() {
+ $SSH_COMMAND $dom0_cluster_ip "$HVM_HELPER $1 $2 $stop_timeout"
+}
+
+# Main code
+case $1 in
+gethosts)
+ echo $hostlist
+ exit 0
+ ;;
+on|off|reset|status)
+ RunHVMCommand $1 $2
+ exit $?
+ ;;
+getconfignames)
+ echo "hostlist dom0_cluster_ip timeout"
+ exit 0
+ ;;
+getinfo-devid)
+ echo "xen0-ha DomU/Dom0 device"
+ exit 0
+ ;;
+getinfo-devname)
+ echo "xen0-ha DomU/Dom0 external device"
+ exit 0
+ ;;
+getinfo-devdescr)
+ echo "Allows STONITH to control DomUs managed by a CRM/Pacemaker Dom0."
+ echo "Requires Xen + CRM/Pacemaker at both layers."
+ echo "Proof-of-concept code!"
+ exit 0
+ ;;
+getinfo-devurl)
+ echo "http://linux-ha.org/wiki/DomUClusters"
+ exit 0
+ ;;
+getinfo-xml)
+ cat << SSHXML
+<parameters>
+<parameter name="hostlist" unique="1" required="1">
+<content type="string" />
+<shortdesc lang="en">
+Hostlist
+</shortdesc>
+<longdesc lang="en">
+The list of controlled DomUs, separated by whitespace.
+These must be configured as Xen RA resources with a name with a matching
+id.
+For example: "xen-1 xen-2 xen-3"
+</longdesc>
+</parameter>
+<parameter name="dom0_cluster_ip" unique="1" required="1">
+<content type="string" />
+<shortdesc lang="en">
+Dom0 cluster ip
+</shortdesc>
+<longdesc lang="en">
+The cluster IP address associated with Dom0.
+Root user must be able to ssh to that node.
+</longdesc>
+</parameter>
+<parameter name="stop_timeout">
+<content type="integer" />
+<shortdesc lang="en">
+Stop timeout
+</shortdesc>
+<longdesc lang="en">
+The timeout, in seconds, for which to wait for Dom0 to report that the
+DomU has been stopped, before aborting with a failure.
+</longdesc>
+</parameter>
+</parameters>
+SSHXML
+ exit 0
+ ;;
+*)
+ exit 1
+ ;;
+esac