diff options
Diffstat (limited to '')
-rwxr-xr-x | heartbeat/iscsi | 516 |
1 files changed, 516 insertions, 0 deletions
diff --git a/heartbeat/iscsi b/heartbeat/iscsi new file mode 100755 index 0000000..d25aec2 --- /dev/null +++ b/heartbeat/iscsi @@ -0,0 +1,516 @@ +#!/bin/sh +# +# iSCSI OCF resource agent +# Description: manage iSCSI disks (add/remove) using open-iscsi +# +# Copyright Dejan Muhamedagic <dejan@suse.de> +# (C) 2007 Novell Inc. All Rights Reserved. +# +# 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. +# +# See usage() and meta_data() below for more details... +# +# OCF instance parameters: +# OCF_RESKEY_portal: the iSCSI portal address or host name (required) +# OCF_RESKEY_target: the iSCSI target (required) +# OCF_RESKEY_iscsiadm: iscsiadm program path (optional) +# OCF_RESKEY_discovery_type: discovery type (optional; default: sendtargets) +# OCF_RESKEY_try_recovery: wait for iSCSI recovery in monitor (optional; default: false) +# +# Initialization: + +: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat} +. ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs + +# Defaults +OCF_RESKEY_udev_default="yes" +OCF_RESKEY_iscsiadm_default="iscsiadm" +OCF_RESKEY_discovery_type_default="sendtargets" +OCF_RESKEY_try_recovery_default="false" + +: ${OCF_RESKEY_udev=${OCF_RESKEY_udev_default}} +: ${OCF_RESKEY_iscsiadm=${OCF_RESKEY_iscsiadm_default}} +: ${OCF_RESKEY_discovery_type=${OCF_RESKEY_discovery_type_default}} + +usage() { + methods=`iscsi_methods` + methods=`echo $methods | tr ' ' '|'` + cat <<EOF + usage: $0 {$methods} + + $0 manages an iSCSI target + + The 'start' operation starts (adds) the iSCSI target. + The 'stop' operation stops (removes) the iSCSI target. + The 'status' operation reports whether the iSCSI target is connected + The 'monitor' operation reports whether the iSCSI target is connected + The 'validate-all' operation reports whether the parameters are valid + The 'methods' operation reports on the methods $0 supports + +EOF +} + +meta_data() { + cat <<EOF +<?xml version="1.0"?> +<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd"> +<resource-agent name="iscsi" version="1.0"> +<version>1.0</version> + +<longdesc lang="en"> +OCF Resource Agent for iSCSI. Add (start) or remove (stop) iSCSI +targets. +</longdesc> +<shortdesc lang="en">Manages a local iSCSI initiator and its connections to iSCSI targets</shortdesc> + +<parameters> + +<parameter name="portal" unique="0" required="1"> +<longdesc lang="en"> +The iSCSI portal address in the form: {ip_address|hostname}[":"port] +</longdesc> +<shortdesc lang="en">Portal address</shortdesc> +<content type="string" /> +</parameter> + +<parameter name="target" unique="1" required="1"> +<longdesc lang="en"> +The iSCSI target IQN. +</longdesc> +<shortdesc lang="en">Target IQN</shortdesc> +<content type="string" /> +</parameter> + +<parameter name="discovery_type" unique="0" required="0"> +<longdesc lang="en"> +Target discovery type. Check the open-iscsi documentation for +supported discovery types. +</longdesc> +<shortdesc lang="en">Target discovery type</shortdesc> +<content type="string" default="${OCF_RESKEY_discovery_type_default}" /> +</parameter> + +<parameter name="iscsiadm" unique="0" required="0"> +<longdesc lang="en"> +open-iscsi administration utility binary. +</longdesc> +<shortdesc lang="en">iscsiadm binary</shortdesc> +<content type="string" default="${OCF_RESKEY_iscsiadm_default}" /> +</parameter> + +<parameter name="udev" unique="0" required="0"> +<longdesc lang="en"> +If the next resource depends on the udev creating a device then +we wait until it is finished. On a normally loaded host this +should be done quickly, but you may be unlucky. If you are not +using udev set this to "no", otherwise we will spin in a loop +until a timeout occurs. +</longdesc> +<shortdesc lang="en">udev</shortdesc> +<content type="string" default="${OCF_RESKEY_udev_default}" /> +</parameter> + +<parameter name="try_recovery" unique="0" required="0"> +<longdesc lang="en"> +If the iSCSI session exists but is currently inactive/broken, +which is most probably due to network problems, the iSCSI layer +will try to recover. If this parameter is set to true, we'll wait +for the recovery to succeed. In that case the monitor operation +can only time out so you should set the monitor op timeout +attribute appropriately. +</longdesc> +<shortdesc lang="en">On error wait for iSCSI recovery in monitor</shortdesc> +<content type="boolean" default="${OCF_RESKEY_try_recovery_default}" /> +</parameter> + +</parameters> + +<actions> +<action name="start" timeout="120s" /> +<action name="stop" timeout="120s" /> +<action name="status" timeout="30s" /> +<action name="monitor" depth="0" timeout="30s" interval="120s" /> +<action name="validate-all" timeout="5s" /> +<action name="methods" timeout="5s" /> +<action name="meta-data" timeout="5s" /> +</actions> +</resource-agent> +EOF +} + +iscsi_methods() { + cat <<EOF + start + stop + status + monitor + validate-all + methods + meta-data + usage +EOF +} + +# +# open-iscsi interface +# + +is_iscsid_running() { + ps -e -o cmd | grep -qs '[i]scsid' +} +open_iscsi_setup() { + discovery=open_iscsi_discovery + add_disk=open_iscsi_add + remove_disk=open_iscsi_remove + disk_status=open_iscsi_monitor + iscsiadm=${OCF_RESKEY_iscsiadm} + + have_binary ${iscsiadm} || + return 3 + if is_iscsid_running; then + return 0 + elif grep -qs '^iscsid.startup' /etc/iscsi/iscsid.conf; then + # apparently on RedHat (perhaps elsewhere?), there is a + # kind of iscsid autostart once root invokes some + # open_iscsi command; the iscsid.startup hook should take + # care of it; reported by m.richardson@ed.ac.uk (see also + # the discussion at the linux-ha-dev ML) + return 1 + else + ocf_exit_reason "iscsid not running; please start open-iscsi utilities" + return 2 + fi +} + +# +# discovery return codes: +# 0: ok (variable portal set) +# 1: target not found +# 2: target found but can't connect it unambigously +# 3: iscsiadm returned error +# +# open-iscsi >= "2.0-872" changed discovery semantics +# see http://www.mail-archive.com/open-iscsi@googlegroups.com/msg04883.html +# there's a new discoverydb command which should be used instead discovery + +open_iscsi_discovery() { + local output + local discovery_variant="discovery" + local options="" + local cmd + local version=`$iscsiadm --version | awk '{print $3}'` + + ocf_version_cmp "$version" "2.0-871" + if [ $? -eq 2 ]; then # newer than 2.0-871? + discovery_variant="discoverydb" + [ "$discovery_type" = "sendtargets" ] && + options="-D" + fi + cmd="$iscsiadm -m $discovery_variant -p $OCF_RESKEY_portal -t $discovery_type $options" + output=`$cmd` + if [ $? -ne 0 -o x = "x$output" ]; then + [ x != "x$output" ] && { + ocf_exit_reason "$cmd FAILED" + echo "$output" + } + return 3 + fi + PORTAL=`echo "$output" | + awk -v target="$OCF_RESKEY_target" ' + $NF==target{ + if( NF==3 ) portal=$2; # sles compat mode + else portal=$1; + sub(",.*","",portal); + print portal; + }'` + + case `echo "$PORTAL" | wc -w` in + 0) #target not found + echo "$output" + ocf_exit_reason "target $OCF_RESKEY_target not found at portal $OCF_RESKEY_portal" + return 1 + ;; + 1) #we're ok + return 0 + ;; + *) # handle multihome hosts reporting multiple portals + for p in $PORTAL; do + if [ "$OCF_RESKEY_portal" = "$p" ]; then + PORTAL="$OCF_RESKEY_portal" + return 0 + fi + done + echo "$output" + ocf_exit_reason "sorry, can't handle multihomed hosts unless you specify the portal exactly" + return 2 + ;; + esac +} +open_iscsi_add() { + $iscsiadm -m node -p $1 -T $2 -l +} +open_iscsi_get_session_id() { + local target="$1" + local portal="$2" + $iscsiadm -m session 2>/dev/null | + grep -E "$target($|[[:space:]])" | + grep -E "] $portal" | + awk '{print $2}' | tr -d '[]' +} +open_iscsi_remove() { + local target="$1" + local session_id + session_id=`open_iscsi_get_session_id "$target" "$OCF_RESKEY_portal"` + if [ "$session_id" ]; then + $iscsiadm -m session -r $session_id -u + else + ocf_exit_reason "cannot find session id for target $target" + return 1 + fi +} +# open_iscsi_monitor return codes: +# 0: target running (logged in) +# 1: target not running and target record exists +# 2: iscsiadm -m session error (unexpected) +# 3: target record does not exist (discovery necessary) +# +open_iscsi_monitor() { + local target="$1" + local session_id conn_state outp + local prev_state + local recov + + recov=${2:-$OCF_RESKEY_try_recovery} + session_id=`open_iscsi_get_session_id "$target" "$OCF_RESKEY_portal"` + prev_state="" + if [ -z "$session_id" ]; then + if $iscsiadm -m node -p $OCF_RESKEY_portal -T $target >/dev/null 2>&1; then + return 1 # record found + else + return 3 + fi + fi + while :; do + outp=`$iscsiadm -m session -r $session_id -P 1` || + return 2 + conn_state=`echo "$outp" | sed -n '/Connection State/s/.*: //p'` + # some drivers don't return connection state, in that case + # we'll assume that we're still connected + case "$conn_state" in + "LOGGED IN") + [ -n "$msg_logged" ] && + ocf_log info "connection state $conn_state. Session restored." + return 0;; + "Unknown"|"") # this is also probably OK + [ -n "$msg_logged" ] && + ocf_log info "connection state $conn_state. Session restored." + return 0;; + *) # failed + if [ "$__OCF_ACTION" != stop ] && ! ocf_is_probe && ocf_is_true $recov; then + if [ "$conn_state" != "$prev_state" ]; then + ocf_log warning "connection state $conn_state, waiting for recovery..." + prev_state="$conn_state" + fi + sleep 1 + else + ocf_exit_reason "iscsiadm output: $outp" + return 2 + fi + ;; + esac + done +} + +disk_discovery() { + discovery_type=${OCF_RESKEY_discovery_type} + $discovery # discover and setup the real portal string (address) + case $? in + 0) ;; + 1|2) exit $OCF_ERR_GENERIC ;; + 3) if ! is_iscsid_running; then + [ $setup_rc -eq 1 ] && + ocf_log warning "iscsid.startup probably not correctly set in /etc/iscsi/iscsid.conf" + exit $OCF_ERR_INSTALLED + fi + exit $OCF_ERR_GENERIC + ;; + esac +} + +# +# NB: this is udev specific! +# +wait_for_udev() { + dev=/dev/disk/by-path/ip-$PORTAL-iscsi-$OCF_RESKEY_target + while :; do + ls $dev* >/dev/null 2>&1 && break + ocf_log warning "waiting for udev to create $dev" + sleep 1 + done +} +iscsi_monitor() { + $disk_status $OCF_RESKEY_target $* + case $? in + 0) return $OCF_SUCCESS;; + 1|3) return $OCF_NOT_RUNNING;; + 2) return $OCF_ERR_GENERIC;; + esac +} +iscsi_start() { + local rc + $disk_status $OCF_RESKEY_target + rc=$? + if [ $rc -eq 3 ]; then + disk_discovery + $disk_status $OCF_RESKEY_target + rc=$? + fi + case $rc in + 0) + ocf_log info "iscsi $PORTAL $OCF_RESKEY_target already running" + return $OCF_SUCCESS + ;; + 1) + $add_disk $PORTAL $OCF_RESKEY_target || + return $OCF_ERR_GENERIC + case "$OCF_RESKEY_udev" in + [Yy]es) wait_for_udev || + return $OCF_ERR_GENERIC + ;; + *) ;; + esac + ;; + *) # the session exists, but it's broken + ocf_log warning "iscsi $PORTAL $OCF_RESKEY_target in failed state" + ;; + esac + iscsi_monitor 1 # enforce wait + if [ $? -eq $OCF_SUCCESS ]; then + return $OCF_SUCCESS + else + return $OCF_ERR_GENERIC + fi +} +iscsi_stop() { + iscsi_monitor + if [ $? -ne $OCF_NOT_RUNNING ] ; then + $remove_disk $OCF_RESKEY_target || + return $OCF_ERR_GENERIC + iscsi_monitor + if [ $? -ne $OCF_NOT_RUNNING ] ; then + return $OCF_ERR_GENERIC + else + return $OCF_SUCCESS + fi + else + ocf_log info "iscsi $OCF_RESKEY_target already stopped" + return $OCF_SUCCESS + fi +} + +# +# 'main' starts here... +# + +if [ $# -ne 1 ]; then + usage + exit $OCF_ERR_ARGS +fi + +# These operations don't require OCF instance parameters to be set +case "$1" in + meta-data) meta_data + exit $OCF_SUCCESS;; + usage) usage + exit $OCF_SUCCESS;; + methods) iscsi_methods + exit $OCF_SUCCESS;; +esac + +if [ x = "x$OCF_RESKEY_target" ]; then + ocf_exit_reason "target parameter not set" + exit $OCF_ERR_CONFIGURED +fi + +if [ x = "x$OCF_RESKEY_portal" ]; then + ocf_exit_reason "portal parameter not set" + exit $OCF_ERR_CONFIGURED +fi + +case `uname` in +Linux) setup=open_iscsi_setup +;; +*) ocf_log info "platform `uname` may not be supported" + setup=open_iscsi_setup +;; +esac + +PORTAL="$OCF_RESKEY_portal" # updated by discovery +LSB_STATUS_STOPPED=3 +$setup +setup_rc=$? +if [ $setup_rc -gt 1 ]; then + ocf_exit_reason "iscsi initiator utilities not installed or not setup" + case "$1" in + stop) exit $OCF_SUCCESS;; + monitor) exit $OCF_NOT_RUNNING;; + status) exit $LSB_STATUS_STOPPED;; + *) exit $OCF_ERR_INSTALLED;; + esac +fi + +if [ `id -u` != 0 ]; then + ocf_exit_reason "$0 must be run as root" + exit $OCF_ERR_PERM +fi + +# which method was invoked? +case "$1" in + start) + iscsi_start + ;; + stop) iscsi_stop + ;; + status) iscsi_monitor + rc=$? + case $rc in + $OCF_SUCCESS) + echo iscsi target $OCF_RESKEY_target running + ;; + $OCF_NOT_RUNNING) + echo iscsi target $OCF_RESKEY_target stopped + ;; + *) + echo iscsi target $OCF_RESKEY_target failed + ;; + esac + exit $rc + ;; + monitor) iscsi_monitor + ;; + validate-all) # everything already validated + # just exit successfully here. + exit $OCF_SUCCESS;; + *) iscsi_methods + exit $OCF_ERR_UNIMPLEMENTED;; +esac + +# +# vim:tabstop=4:shiftwidth=4:textwidth=0:wrapmargin=0 |