#!/bin/sh ################################################################################ # # This file is part of the vhacs project. # Jerome Martin # ################################################################################ # # iSCSI target OCF resource agent # Creates an iSCSI target with given iqn and IP for a drbd resource. # The target is created with the excellent LIO-target software. # ################################################################################ # # OCF instance parameters: # OCF_RESKEY_ip: portal ip (required). # OCF_RESKEY_mask: portal ip network mask (required). # OCF_RESKEY_nic: network interface (required). # OCF_RESKEY_target: target IQN (required). # OCF_RESKEY_drbd_config: vhacs DRBD confguration file to use (required). # ################################################################################ # # Source common vhacs OCF RA code. . ${OCF_ROOT}/resource.d/vhacs/.ocf-vhacs # ################################################################################ # FIXME too many hardcoded values there RA_NAME=$(basename $0) IBLOCK_DEVICE_ID=0 PORT=3260 ISCSI_LUN=0 TPGT=1 TARGETCTL="/sbin/target-ctl" CRASHFILE="/var/crash/target.fault" AUTHD="/sbin/target-authd" EVENTD="/sbin/target-eventd" SETTNAME="/sbin/set-targetname" MODNAME="iscsi_target_mod" NODENAME="/proc/iscsi_target/target_nodename" node=$(uname -n) ################################################################################ # # RA agents using vhacs ocf_main() must declare the following functions : # # rsc_meta_data() # This function must implement ocf meta_data output for the RA. c.f. OCF spec. # See http://www.opencf.org/ # cgi-bin/viewcvs.cgi/specs/ra/resource-agent-api.txt?rev=HEAD # # rsc_start() # This function must attempt to start the resource, assuming it is stopped. # If any error arise, rsc_start() must immediately return $OCF_ERR_GENERIC. # If there are no errors, rsc_start() must return $OCF_SUCCESS. # # rsc_stop() # This function must attempt to stop the resource, assuming it is either # fully started or in a failed state. # If any error arise in the process, it must continue to stop remaining # resource components # It is critical that this function can be used as a cleanup means because # heartbeatv2 and pacemaker do not use the OCF recover function. # This function SHOULD NOT FAIL. Remember real status will be given by # the monitor function. # # rsc_monitor() # This function must check all resource components statuses. # Note that this function return code is authoritative concerning the status # returned to the cluster. rsc_start() and rsc_stop() return codes are never # trusted as proofs of resource status. Only rsc_monitor() is : # 0 Resource started: all of the resource components are started. # rsc_monitor() must return $OCF_SUCCESS. # 7 Resource cleanly stopped: all the resource components are stopped. # rsc_monitor() must return $OCF_NOT_RUNNING. # 1 Resource failed: some components SPECIFIC to the resource (a.k.a. not # components shared by several resources, like a daemon or kernel module) # are in a mixed state. rsc_monitor() must return $OCF_ERROR_GENERIC. # # rsc_setup() # This function must make sure the system environment is setup properly # before the resource is started, stopped or monitored. # It returns either $OCF_SUCCESS or $OCF_ERROR_GENERIC. # # Logging # As it is critical that logging allows tracing back issues without being # too verbose, commands to start/stop/check resource components might be # run using ocf_cmd "command to run". This will log command output to the RA # syslog in case of errors, but only report successes briefly. This should # alleviate the need for more log comments in start/stop/check functions. # Also, every time such a command fails via ocf_cmd(), $OCF_CMD_ERRCOUNT # will be incremented. An additional function, ocf_bkg() does the same but # starts command in the background and return without waiting for it to # terminate. # # Locking # In order to serialise resources concurrent operations on the same node, # the ocf_lock_RA() function can be used to wait until an exclusive lock can # be acquired for this RA instance. Use ocf_unlock_RA() when done with # those operations. In any case, an EXIT trap will release the lock. # ################################################################################ rsc_meta_data() { cat <<- EOF 1.0 OCF Resource Agent for exporting an iSCSI target via lio-targt. iscsi_target resource agent The ip address to use when exporting the target. The ip will by managed by this RA. IP address Network mask to the portal ip. Network mask The network device to use when exporting the target. It must already exist on the system. IP address IQN that the newly created iscsi nexus will export as target. Target IQN DRBD confguration file to use, must contain only one drbd resource. DRBD config file EOF } rsc_start() { # Create the IP address + send gratuitous ARPs rundir="/var/run/heartbeat/rsctmp/send_arp/" ocf_cmd "mkdir -p ${rundir}" arp_pidfile="${rundir}/send_arp-${OCF_RESKEY_ip}" send_arp="/usr/lib/heartbeat/send_arp -i 200 -r 10 -p ${arp_pidfile}" ocf_cmd "ip -f inet addr add ${OCF_RESKEY_ip}/${OCF_RESKEY_mask} \ dev ${OCF_RESKEY_nic}" || return ${OCF_ERR_GENERIC} ocf_cmd "ip link set ${OCF_RESKEY_nic} up" || return ${OCF_ERR_GENERIC} ocf_cmd "${send_arp} ${OCF_RESKEY_nic} ${OCF_RESKEY_ip} \ auto not_used not_used" || return ${OCF_ERR_GENERIC} # As we are dealing with globally unique indexes, lock the RA ocf_lock_RA # Get currently used 'iSCSI Host ID's (aka 'hba_id's) and 'iBlock Host ID's local TMP=$(mktemp) if [ $? -ne 0 ]; then ocf_log error "Cannot create temporary file for ${OCF_RESKEY_target}." return ${OCF_ERR_GENERIC} fi ocf_cmd "${TARGETCTL} listghbainfo > ${TMP}" || return ${OCF_ERR_GENERIC} taken_hba_ids="$(cat ${TMP} | awk '/ID:/ {print $4}')" taken_iblock_ids="$(cat ${TMP} | awk '/ID:/ {print $8}')" ocf_cmd "rm ${TMP}" # Find the first available hba_id free_hba_id="" for i in $(seq 0 254); do if echo $taken_hba_ids | grep -v -w ${i} &>/dev/null; then free_hba_id="${i}" break fi done if [ -z ${free_hba_id} ]; then ocf_log error "Cannot find a free hba_id for ${OCF_RESKEY_target}." return ${OCF_ERR_GENERIC} else ocf_log info "Using hba_id ${free_hba_id} for ${OCF_RESKEY_target}" fi # Find the first available iblock_id free_iblock_id="" for i in $(seq 0 254); do if echo $taken_iblock_ids | grep -v -w ${i} &>/dev/null; then free_iblock_id="${i}" break fi done if [ -z ${free_iblock_id} ]; then ocf_log error "Cannot find a free iblock_id for ${OCF_RESKEY_target}." return ${OCF_ERR_GENERIC} else ocf_log info "Using iblock_id ${free_iblock_id} for ${OCF_RESKEY_target}" fi # Now that we have free indexes, use them to create the virtual device and HBA ocf_cmd "${TARGETCTL} addhbatotarget hba_id=${free_hba_id} \ hba_type=4 iblock_host_id=${free_iblock_id}" || return ${OCF_ERR_GENERIC} ocf_cmd "${TARGETCTL} createvirtdev hba_id=${free_hba_id} \ iblock_major=${drbd_major} iblock_minor=${drbd_minor} \ iblock_device_id=${IBLOCK_DEVICE_ID}" || return ${OCF_ERR_GENERIC} # Done assigning globally unique indexes, unlock the RA ocf_unlock_RA # TIQN creation ocf_cmd "${TARGETCTL} coreaddtiqn \ targetname=${OCF_RESKEY_target}" || return ${OCF_ERR_GENERIC} # TPG creation and calls ocf_cmd "${TARGETCTL} addtpg tpgt=${TPGT} \ targetname=${OCF_RESKEY_target}" || return ${OCF_ERR_GENERIC} for attrib in \ "authentication=0" "demo_mode_lun_access=1" \ "generate_node_acls=1" "default_queue_depth=32"; do ocf_cmd "${TARGETCTL} settpgattrib tpgt=${TPGT} targetname=${OCF_RESKEY_target} \ ${attrib}" || return ${OCF_ERR_GENERIC} done ocf_cmd "${TARGETCTL} addnptotpg tpgt=${TPGT} \ targetname=${OCF_RESKEY_target} dev=${OCF_RESKEY_nic} \ ip=${OCF_RESKEY_ip} port=${PORT}" || return ${OCF_ERR_GENERIC} # Add lun to device ocf_cmd "${TARGETCTL} addluntodev tpgt=${TPGT} targetname=${OCF_RESKEY_target} hba_id=${free_hba_id} \ iscsi_lun=${ISCSI_LUN} iblock_major=147 iblock_minor=${drbd_minor}" || return ${OCF_ERR_GENERIC} # Enable tpgt ocf_cmd "${TARGETCTL} enabletpg tpgt=${TPGT} targetname=${OCF_RESKEY_target}" || return ${OCF_ERR_GENERIC} # If there where no errors, return success return ${OCF_SUCCESS} } rsc_stop () { # remove the ip ocf_log info "rsc_stop trying to stop the ip" ocf_cmd "ip -f inet addr del ${OCF_RESKEY_ip}/${OCF_RESKEY_mask} dev ${OCF_RESKEY_nic}" # Remove the target ocf_log info "rsc_stop trying to stop the tiqn" ocf_cmd "${TARGETCTL} coredeltiqn targetname=${OCF_RESKEY_target}" # Determine the hba_id of the resource and remove hba local TMP=$(mktemp) ${TARGETCTL} listghbadevinfo > ${TMP} local hba_id=$(cat ${TMP} | grep -e "Major:" -e "iSCSI Host ID:" | tr '\n' ' ' \ | sed 's/Major:/\nMajor:/g' | awk '{print $2,$4,$10}' | \ grep "${drbd_major} ${drbd_minor} " | awk '{print $3}') if [ ! -z ${hba_id} ]; then ocf_cmd "${TARGETCTL} delhbafromtarget tpgt=${TPGT} hba_id=${hba_id}" fi rm ${TMP} &>/dev/null # We've done all we could to stop that resource, let monitor check it return ${OCF_SUCCESS} } rsc_monitor () { # Reset checks count ocf_checks_reset # Check if IP is up ip -o -f inet addr show ${OCF_RESKEY_nic} | grep "inet ${OCF_RESKEY_ip}/${OCF_RESKEY_mask} " &>/dev/null ocf_checks_checkrc "Does IP ${OCF_RESKEY_ip}/${OCF_RESKEY_mask} exist on ${OCF_RESKEY_nic} ?" # Check if HBA is allocated for our drbd device local hba_id="" local TMP=$(mktemp) ${TARGETCTL} listghbadevinfo > $TMP hba_id=$(cat $TMP | grep -e "Major:" -e "iSCSI Host ID:" | tr '\n' " " | sed 's/Major:/\nMajor:/g' | \ awk '{print $2,$4,$10}' | grep "${drbd_major} ${drbd_minor} " | awk '{print $3}' | tr -d '\n') rm ${TMP} &>/dev/null [ ! -z ${hba_id} ] ocf_checks_checkrc "Does HBA exists for device drbd${drbd_minor} ?" # Check if TPG exists ${TARGETCTL} listtpginfo tpgt=${TPGT} targetname=${OCF_RESKEY_target} &>/dev/null ocf_checks_checkrc "Does TPG exist for ${OCF_RESKEY_target} ?" # Return checks result ocf_checks_result return $? } rsc_setup() { # determine the drbd device number from the config file # this works with vhacs-generated drbd config files, # with only one single resource defined and device parameter # within 4 lines of node definition start if [ ! -r ${OCF_RESKEY_drbd_config} ]; then ocf_log error "cannot read configuration file $OCF_RESKEY_drbd_config" return ${OCF_ERR_INSTALLED} fi echo "grep -A 4 \"on $node\" ${OCF_RESKEY_drbd_config} | awk '/device/ {print $2}' | tr -d ';' | sed 's/[a-zA-Z/]*\([0-9]*\)/\1/' " drbd_major=147 drbd_minor=$(grep -A 4 "on $node" ${OCF_RESKEY_drbd_config} | awk '/device/ {print $2}' | \ tr -d ';' | sed 's/[a-zA-Z/]*\([0-9]*\)/\1/') [ -z ${drbd_minor} ] && return ${OCF_ERR_GENERIC} return ${OCF_SUCCESS} } ################################################################################ # # Call the main ocf RA logic # ################################################################################ ocf_main $1