diff options
Diffstat (limited to 'heartbeat/exportfs')
-rwxr-xr-x | heartbeat/exportfs | 492 |
1 files changed, 492 insertions, 0 deletions
diff --git a/heartbeat/exportfs b/heartbeat/exportfs new file mode 100755 index 0000000..435a196 --- /dev/null +++ b/heartbeat/exportfs @@ -0,0 +1,492 @@ +#!/bin/sh +# exportfs +# +# Description: Manages nfs exported file system. +# +# (c) 2010 Ben Timby, Florian Haas, Dejan Muhamedagic, +# and Linux-HA contributors +# +# License: GNU General Public License v2 (GPLv2) and later + +####################################################################### +# Initialization: +: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat} +. ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs + +# Defaults +OCF_RESKEY_unlock_on_stop_default=1 +OCF_RESKEY_wait_for_leasetime_on_stop_default=0 +OCF_RESKEY_rmtab_backup_default=".rmtab" + +: ${OCF_RESKEY_unlock_on_stop=${OCF_RESKEY_unlock_on_stop_default}} +: ${OCF_RESKEY_wait_for_leasetime_on_stop=${OCF_RESKEY_wait_for_leasetime_on_stop_default}} +: ${OCF_RESKEY_rmtab_backup=${OCF_RESKEY_rmtab_backup_default}} +####################################################################### + +exportfs_meta_data() { + cat <<END +<?xml version="1.0"?> +<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd"> +<resource-agent name="exportfs" version="1.0"> +<version>1.0</version> + +<longdesc lang="en"> +Exportfs uses the exportfs command to add/remove nfs exports. +It does NOT manage the nfs server daemon. +It depends on Linux specific NFS implementation details, +so is considered not portable to other platforms yet. +</longdesc> + +<shortdesc lang="en"> +Manages NFS exports +</shortdesc> + +<parameters> + +<parameter name="clientspec" unique="0" required="1"> +<longdesc lang="en"> +The client specification allowing remote machines to mount the directory +(or directories) over NFS. + +Note: it follows the format defined in "man exportfs". For example, in +the use case to export the directory(-ies) for multiple subnets, please +do config a dedicated primitive for each subnet CIDR ip address, +and do not attempt to use multiple CIDR ip addresses in a space +separated list, like in /etc/exports. +</longdesc> +<shortdesc lang="en"> +Client ACL. +</shortdesc> +<content type="string" /> +</parameter> + +<parameter name="options" unique="0" required="0"> +<longdesc lang="en"> +The options to pass to exportfs for the exported directory +or directories. +</longdesc> +<shortdesc lang="en"> +Export options. +</shortdesc> +<content type="string" /> +</parameter> + +<parameter name="directory" unique="0" required="1"> +<longdesc lang="en"> +The directory or directories to be exported using NFS. Multiple +directories are separated by white space. +</longdesc> +<shortdesc lang="en"> +The directory or directories to export. +</shortdesc> +<content type="string" /> +</parameter> + +<parameter name="fsid" unique="0" required="0"> +<longdesc lang="en"> +The fsid option to pass to exportfs. This can be a unique positive +integer, a UUID (assuredly sans comma characters), or the special string +"root" which is functionally identical to numeric fsid of 0. +If multiple directories are being exported, then they are +assigned ids sequentially starting with this fsid (fsid, fsid+1, +fsid+2, ...). Obviously, in that case the fsid must be an +integer. +0 (root) identifies the export as the root of an NFSv4 +pseudofilesystem -- avoid this setting unless you understand its +special status. +This value will override any fsid provided via the options parameter. +</longdesc> +<shortdesc lang="en"> +Unique fsid within cluster or starting fsid for multiple exports. +</shortdesc> +<content type="string" /> +</parameter> + +<parameter name="unlock_on_stop"> +<longdesc lang="en"> +Relinquish NFS locks associated with this filesystem when the resource +stops. Enabling this parameter is highly recommended unless the path exported +by this ${__SCRIPT_NAME} resource is also exported by a different resource. + +Note: Unlocking is only possible on Linux systems where +/proc/fs/nfsd/unlock_filesystem exists and is writable. If your system does +not fulfill this requirement (on account of having an nonrecent kernel, +for example), you may set this parameter to 0 to silence the associated +warning. +</longdesc> +<shortdesc lang="en"> +Unlock filesystem on stop? +</shortdesc> +<content type="boolean" default="${OCF_RESKEY_unlock_on_stop_default}" /> +</parameter> + +<parameter name="wait_for_leasetime_on_stop"> +<longdesc lang="en"> +When stopping (unexporting), wait out the NFSv4 lease time. +Only after all leases have expired does the NFS kernel server +relinquish all server-side handles on the exported filesystem. +If this ${__SCRIPT_NAME} resource manages an export that resides +on a mount point designed to fail over along with the NFS export +itself, then enabling this parameter will ensure such failover +is working properly. Note that when this parameter is set, your +stop timeout MUST accommodate for the wait period. This parameter +is safe to disable if none of your NFS clients are using NFS +version 4 or later. +</longdesc> +<shortdesc lang="en"> +Ride out the NFSv4 lease time on resource stop? +</shortdesc> +<content type="boolean" default="${OCF_RESKEY_wait_for_leasetime_on_stop_default}" /> +</parameter> + +<parameter name="rmtab_backup"> +<longdesc lang="en"> +Back up those entries from the NFS rmtab that apply to the exported +directory, to the specified backup file. The filename is interpreted +as relative to the exported directory. This backup is required if +clients are connecting to the export via NFSv3 over TCP. Note that a +configured monitor operation is required for this functionality. + +To disable rmtab backups, set this parameter to the special +string "none". +</longdesc> +<shortdesc lang="en"> +Location of the rmtab backup, relative to directory. +</shortdesc> +<content type="string" default="${OCF_RESKEY_rmtab_backup_default}" /> +</parameter> +</parameters> + +<actions> +<action name="start" timeout="40s" /> +<action name="stop" timeout="120s" /> +<action name="monitor" depth="0" timeout="20s" interval="10s" /> +<action name="meta-data" timeout="5s" /> +<action name="validate-all" timeout="30s" /> +</actions> +</resource-agent> +END + +return $OCF_SUCCESS +} + +exportfs_methods() { + cat <<-EOF + start + stop + status + monitor + validate-all + methods + meta-data + usage + EOF +} + +reset_fsid() { + CURRENT_FSID=$OCF_RESKEY_fsid + [ -z "$CURRENT_FSID" ] && CURRENT_FSID=`echo "$OCF_RESKEY_options" | sed -n 's/.*fsid=\([^,]*\).*/\1/p'` + echo $CURRENT_FSID +} +bump_fsid() { + CURRENT_FSID=$((CURRENT_FSID+1)) +} +get_fsid() { + echo $CURRENT_FSID +} + +# run a function on all directories +forall() { + local func=$1 + shift 1 + local fast_exit="" + local dir rc=0 + if [ "$2" = fast_exit ]; then + fast_exit=1 + shift 1 + fi + reset_fsid + for dir in $OCF_RESKEY_directory; do + $func $dir "$@" + rc=$(($rc | $?)) + [ $NUMDIRS -gt 1 ] && bump_fsid + [ "$fast_exit" ] && continue + [ $rc -ne 0 ] && return $rc + done + return $rc +} + +backup_rmtab() { + local dir=$1 + local rmtab_backup + rmtab_backup="$dir/${OCF_RESKEY_rmtab_backup}" + if [ -r /var/lib/nfs/rmtab ]; then + grep ":$dir:" /var/lib/nfs/rmtab > ${rmtab_backup} + fi +} + +restore_rmtab() { + local dir=$1 + local rmtab_backup + rmtab_backup="$dir/${OCF_RESKEY_rmtab_backup}" + if [ -r ${rmtab_backup} ]; then + local tmpf=`mktemp` + sort -u ${rmtab_backup} /var/lib/nfs/rmtab > $tmpf && + install -o root -m 644 $tmpf /var/lib/nfs/rmtab + rm -f $tmpf + ocf_log debug "Restored `wc -l ${rmtab_backup}` rmtab entries from ${rmtab_backup}." + else + ocf_log warn "rmtab backup ${rmtab_backup} not found or not readable." + fi +} + +exportfs_usage() { + cat <<END + usage: $0 {start|stop|monitor|status|validate-all|meta-data} +END +} + +format_exports() { + # exportfs output wraps lines for long export directory names. + # We unwrap here with sed. + # We then do a literal match on the full line (grep -x -F) + exportfs | + sed -e '$! N; s/\n[[:space:]]\+/ /; t; s/[[:space:]]\+\([^[:space:]]\+\)\(\n\|$\)/ \1\2/g; P;D;' +} +is_exported() { + local dir=$1 + local spec=$2 + local rc + format_exports | grep -q -x -F "$dir $spec" + rc=$? + if [ $rc -ne 0 -a "$spec" = "*" ]; then + # on some platforms, exportfs may print + # "<world>" instead of "*" + format_exports | grep -q -x -F "$dir <world>" + rc=$? + fi + # log something only for monitors + if [ $rc -ne 0 -a "$__OCF_ACTION" = "monitor" ]; then + local sev="info" + ocf_is_probe || sev="err" + ocf_log $sev "$dir not exported to $spec (stopped)." + fi + return $rc +} + +exportfs_monitor () +{ + local spec + + if ! ha_pseudo_resource "${OCF_RESOURCE_INSTANCE}" monitor; then + return $OCF_NOT_RUNNING + fi + + # IPv6 addresses and networks are encased in brackets that need + # to be removed + case "$OCF_RESKEY_clientspec" in + *:*:*) + spec="$(echo "$OCF_RESKEY_clientspec" | tr -d '[]')" + ;; + *) + spec="$OCF_RESKEY_clientspec" + ;; + esac + + if forall is_exported "$spec"; then + if [ ${OCF_RESKEY_rmtab_backup} != "none" ]; then + forall backup_rmtab + fi + return $OCF_SUCCESS + else + return $OCF_NOT_RUNNING + fi +} + +testdir() { + if [ ! -d $1 ]; then + mkdir -p "$1" + if [ $? -ne 0 ]; then + ocf_exit_reason "Unable to create directory $1" + return 1 + fi + fi + return 0 +} +export_one() { + local dir=$1 + local opts sep + sep="" + if [ -n "$OCF_RESKEY_options" ]; then + opts="$OCF_RESKEY_options" + sep="," + fi + if echo "$opts" | grep fsid >/dev/null; then + #replace fsid in options list + opts=`echo "$opts" | sed "s,fsid=[^,]*,fsid=$(get_fsid),g"` + elif [ -n "$OCF_RESKEY_fsid" ]; then + #tack the fsid option onto our options list. + opts="${opts}${sep}fsid=$(get_fsid)" + fi + opts="-o $opts" + + # if any of directories fails to export we can exit + # immediately + ocf_run exportfs -v $opts "${OCF_RESKEY_clientspec}:$dir" + if [ $? -ne 0 ]; then + ocf_exit_reason "exportfs failed - exportfs -v $opts ${OCF_RESKEY_clientspec}:$dir" + exit $OCF_ERR_GENERIC + fi + + ocf_log info "directory $dir exported" + return $OCF_SUCCESS +} +exportfs_start () +{ + if ! forall testdir; then + return $OCF_ERR_INSTALLED + fi + + if exportfs_monitor; then + ocf_log debug "already exported" + return $OCF_SUCCESS + fi + ocf_log info "Exporting file system(s) ..." + + ha_pseudo_resource "${OCF_RESOURCE_INSTANCE}" start + forall export_one + + # Restore the rmtab to ensure smooth NFS-over-TCP failover + if [ ${OCF_RESKEY_rmtab_backup} != "none" ]; then + forall restore_rmtab + fi +} + +unlock_fs() { + local dir=$1 + local unlockfile + unlockfile=/proc/fs/nfsd/unlock_filesystem + if [ -w ${unlockfile} ]; then + echo "$dir" > ${unlockfile} + ocf_log info "Unlocked NFS export $dir" + else + ocf_log warn "Unable to unlock NFS export $dir, ${unlockfile} not found or not writable" + fi +} +wait_for_leasetime() { + local leasetimefile + local sleeptime + leasetimefile=/proc/fs/nfsd/nfsv4leasetime + if [ -r ${leasetimefile} ]; then + sleeptime=$((`cat ${leasetimefile}`+2)) + ocf_log info "Sleeping ${sleeptime} seconds to accommodate for NFSv4 lease expiry" + sleep ${sleeptime}s + else + ocf_log warn "Unable to read NFSv4 lease time from ${leasetimefile}, file not found or not readable" + fi +} +cleanup_export_cache() { + # see if the cache is blocking unexport + local contentfile=/proc/net/rpc/nfsd.export/content + local fsid_re + local i=1 + fsid_re="fsid=(echo `forall get_fsid`|sed 's/ /|/g')," + while :; do + grep -E -q "$fsid_re" $contentfile || + break + ocf_log info "Cleanup export cache ... (try $i)" + ocf_run exportfs -f + sleep 0.5 + i=$((i + 1)) + done +} +unexport_one() { + local dir=$1 + ocf_run exportfs -v -u ${OCF_RESKEY_clientspec}:$dir +} +exportfs_stop () +{ + local rc + + exportfs_monitor + if [ $? -eq $OCF_NOT_RUNNING ]; then + ocf_log debug "not exported" + return $OCF_SUCCESS + fi + + ocf_log info "Un-exporting file system ..." + + # Backup the rmtab to ensure smooth NFS-over-TCP failover + if [ ${OCF_RESKEY_rmtab_backup} != "none" ]; then + forall backup_rmtab + fi + + forall unexport_one + rc=$? + + if ocf_is_true ${OCF_RESKEY_unlock_on_stop}; then + forall unlock_fs + fi + + if ocf_is_true ${OCF_RESKEY_wait_for_leasetime_on_stop}; then + wait_for_leasetime + fi + + if [ $rc -eq 0 ]; then + cleanup_export_cache + ha_pseudo_resource "${OCF_RESOURCE_INSTANCE}" stop + + ocf_log info "Un-exported file system(s)" + return $OCF_SUCCESS + else + ocf_exit_reason "Failed to un-export file system(s)" + return $OCF_ERR_GENERIC + fi +} + +exportfs_validate_all () +{ + if echo "$OCF_RESKEY_fsid" | grep -q -F ','; then + ocf_exit_reason "$OCF_RESKEY_fsid cannot contain a comma" + return $OCF_ERR_CONFIGURED + fi + if [ $NUMDIRS -gt 1 ] && [ -n "$(reset_fsid)" ] && + ! ocf_is_decimal "$(reset_fsid)"; then + ocf_exit_reason "use integer fsid when exporting multiple directories" + return $OCF_ERR_CONFIGURED + fi +} + +for dir in $OCF_RESKEY_directory; do + # strip off trailing '/' from directory + dir=$(echo $dir | sed 's/\/*$//') + : ${dir:=/} + if [ -e "$dir" ] ; then + canonicalized_dir=$(readlink -f "$dir") + if [ $? -ne 0 ]; then + if [ "$__OCF_ACTION" != "stop" ]; then + ocf_exit_reason "Could not canonicalize $dir because readlink failed" + exit $OCF_ERR_GENERIC + fi + fi + else + case "$__OCF_ACTION" in + stop|monitor|validate-all) + canonicalized_dir="$dir" + ocf_log debug "$dir does not exist" + ;; + *) + ocf_exit_reason "$dir does not exist" + exit $OCF_ERR_CONFIGURED + ;; + esac + fi + directories="$directories$canonicalized_dir " +done + +OCF_RESKEY_directory="${directories%% }" + +NUMDIRS=`echo "$OCF_RESKEY_directory" | wc -w` +OCF_REQUIRED_PARAMS="directory clientspec" +OCF_REQUIRED_BINARIES="exportfs" +ocf_rarun $* |