#!/bin/sh
# nfsserver
#
# Description: Manages nfs server as OCF resource
# by hxinwei@gmail.com
# License: GNU General Public License v2 (GPLv2) and later

if [ -n "$OCF_DEBUG_LIBRARY" ]; then
    . $OCF_DEBUG_LIBRARY
else
    : ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat}
. ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs
fi

DEFAULT_INIT_SCRIPT="/etc/init.d/nfsserver"
DEFAULT_NOTIFY_CMD="/sbin/sm-notify"
DEFAULT_NOTIFY_FOREGROUND="false"
DEFAULT_RPCPIPEFS_DIR="/var/lib/nfs/rpc_pipefs"

nfsserver_meta_data() {
	cat <<END
<?xml version="1.0"?>
<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
<resource-agent name="nfsserver">
<version>1.0</version>

<longdesc lang="en">
Nfsserver helps to manage the Linux nfs server as a failover-able resource in Linux-HA.
It depends on Linux specific NFS implementation details, so is considered not portable to other platforms yet.
</longdesc>

<shortdesc lang="en">Manages an NFS server</shortdesc>

<parameters>

<parameter name="nfs_init_script" unique="0" required="0">
<longdesc lang="en">
The default init script shipped with the Linux distro.
The nfsserver resource agent offloads the start/stop/monitor work to the init script because the procedure to start/stop/monitor nfsserver varies on different Linux distro.
</longdesc>
<shortdesc lang="en">
Init script for nfsserver
</shortdesc>
<content type="string" default="$DEFAULT_INIT_SCRIPT" />
</parameter>

<parameter name="nfs_notify_cmd" unique="0" required="0">
<longdesc lang="en">
The tool to send out NSM reboot notification, it should be either sm-notify or rpc.statd.
Failover of nfsserver can be considered as rebooting to different machines.
The nfsserver resource agent use this command to notify all clients about the happening of failover.
</longdesc>
<shortdesc lang="en">
The tool to send out notification.
</shortdesc>
<content type="string" default="$DEFAULT_NOTIFY_CMD" />
</parameter>

<parameter name="nfs_notify_foreground" unique="0" required="0">
<longdesc lang="en">
Keeps the notify tool attached to its controlling terminal and running in the foreground.
</longdesc>
<shortdesc lang="en">
Keeps the notify tool running in the foreground.
</shortdesc>
<content type="boolean" default="$DEFAULT_NOTIFY_FOREGROUND" />
</parameter>

<parameter name="nfs_smnotify_retry_time" unique="0" required="0">
<longdesc lang="en">
Specifies the length of sm-notify retry time, in minutes, to continue retrying notifications to unresponsive hosts.  
If this option is not specified, sm-notify attempts to send notifications for 15 minutes. Specifying a value of 0 
causes sm-notify to continue sending notifications to unresponsive peers until it is manually killed.
</longdesc>
<shortdesc lang="en">
Specifies the length of sm-notify retry time(minutes).
</shortdesc>
<content type="integer" default="" />
</parameter>

<parameter name="nfs_shared_infodir" unique="0" required="1">
<longdesc lang="en">
The nfsserver resource agent will save nfs related information in this specific directory.
And this directory must be able to fail-over before nfsserver itself.
</longdesc>
<shortdesc lang="en">
Directory to store nfs server related information.
</shortdesc>
<content type="string" default="" />
</parameter>

<parameter name="nfs_ip" unique="0" required="1">
<longdesc lang="en">
Comma separated list of floating IP addresses used to access the nfs service
</longdesc>
<shortdesc lang="en">
IP addresses.
</shortdesc>
<content type="string"/>
</parameter>

<parameter name="rpcpipefs_dir" unique="0" required="0">
<longdesc lang="en">
The mount point for the sunrpc file system. Default is $DEFAULT_RPCPIPEFS_DIR . 
This script will mount(bind) nfs_shared_infodir on /var/lib/nfs/ (can not be changed),
and this script will mount the sunrpc file system on $DEFAULT_RPCPIPEFS_DIR (default, can be changed by this parameter).
If you want to move only rpc_pipefs/ (e.g. to keep rpc_pipefs/ local) from default , please set this value.
</longdesc>
<shortdesc lang="en">
The mount point for the sunrpc file system.
</shortdesc>
<content type="string" default="$DEFAULT_RPCPIPEFS_DIR" />
</parameter>

</parameters>

<actions>
<action name="start"   timeout="40" />
<action name="stop"    timeout="20s" />
<action name="monitor" depth="0"  timeout="20s" interval="10" />
<action name="meta-data"  timeout="5" />
<action name="validate-all"  timeout="30" />
</actions>
</resource-agent>
END

return $OCF_SUCCESS
}

nfsserver_usage() {
	cat <<END
		usage: $0 {start|stop|monitor|status|validate-all|meta-data}
END
}

if [ $# -ne 1 ]; then
	nfsserver_usage
	exit $OCF_ERR_ARGS
fi

case $__OCF_ACTION in
	meta-data)  nfsserver_meta_data
		exit $OCF_SUCCESS
		;;
	usage|help) nfsserver_usage
		exit $OCF_SUCCESS
		;;
	*)
		;;	
esac

fp="$OCF_RESKEY_nfs_shared_infodir"
: ${OCF_RESKEY_nfs_init_script="$DEFAULT_INIT_SCRIPT"}
: ${OCF_RESKEY_nfs_notify_cmd="$DEFAULT_NOTIFY_CMD"}
: ${OCF_RESKEY_nfs_notify_foreground="$DEFAULT_NOTIFY_FOREGROUND"}

if [ -z ${OCF_RESKEY_rpcpipefs_dir} ]; then
	rpcpipefs_make_dir=$fp/rpc_pipefs
	rpcpipefs_umount_dir=${DEFAULT_RPCPIPEFS_DIR}
else
	rpcpipefs_make_dir=${OCF_RESKEY_rpcpipefs_dir}
	rpcpipefs_umount_dir=${OCF_RESKEY_rpcpipefs_dir}
fi

nfsserver_monitor ()
{
	fn=`mktemp`
	${OCF_RESKEY_nfs_init_script} status > $fn 2>&1 
	rc=$?
	ocf_log debug `cat $fn`
	rm -f $fn

#Adapte LSB status code to OCF return code
	if [ $rc -eq 0 ]; then
		return $OCF_SUCCESS
	elif [ $rc -eq 3 ]; then
		return $OCF_NOT_RUNNING
	else
		return $OCF_ERR_GENERIC
	fi
}

prepare_directory ()
{
	[ -d "$fp" ] || mkdir -p $fp
	[ -d "$rpcpipefs_make_dir" ] || mkdir -p $rpcpipefs_make_dir
	[ -d "$fp/sm" ] || mkdir -p $fp/sm
	[ -d "$fp/sm.ha" ] || mkdir -p $fp/sm.ha
	[ -d "$fp/sm.bak" ] || mkdir -p $fp/sm.bak
	[ -d "$fp/v4recovery" ] || mkdir -p $fp/v4recovery
}

is_bound ()
{
	mount | grep -q "$1 on $2 type none (.*bind)"
	return $?
}

bind_tree ()
{
	if is_bound $fp /var/lib/nfs; then
		ocf_log debug "$fp is already bound to /var/lib/nfs"
		return 0
	fi
	mount --bind $fp /var/lib/nfs
}

unbind_tree ()
{
	if `mount | grep -q " on $rpcpipefs_umount_dir"`; then
		umount -t rpc_pipefs $rpcpipefs_umount_dir
	fi
	if is_bound $fp /var/lib/nfs; then
		umount /var/lib/nfs
	fi
}

nfsserver_start ()
{
	if nfsserver_monitor; then
		ocf_log debug "NFS server is already started"
		return $OCF_SUCCESS
	fi

	prepare_directory
	bind_tree

	rm -rf /var/lib/nfs/sm.ha/* > /dev/null 2>&1
	cp -rf /var/lib/nfs/sm /var/lib/nfs/sm.bak /var/lib/nfs/state /var/lib/nfs/sm.ha > /dev/null 2>&1

	ocf_log info "Starting NFS server ..."

	fn=`mktemp`
	${OCF_RESKEY_nfs_init_script} start > $fn 2>&1
	rc=$?
	ocf_log debug `cat $fn`
	rm -f $fn

	if [ $rc -ne 0 ]; then
		ocf_log err "Failed to start NFS server"
		return $rc
	fi	

	#Notify the nfs server has been moved or rebooted
	#The init script do that already, but with the hostname, which may be ignored by client
	#we have to do it again with the nfs_ip 
	local opts

	case ${OCF_RESKEY_nfs_notify_cmd##*/} in 
	sm-notify)
		# run in foreground, if requested
		if ocf_is_true "$OCF_RESKEY_nfs_notify_foreground"; then
			opts="-d"
		fi

		if [ -n "$OCF_RESKEY_nfs_smnotify_retry_time" ]; then
			opts="$opts -m $OCF_RESKEY_nfs_smnotify_retry_time"
		fi

		opts="$opts -f -v"
		;;

	rpc.statd)
		if ocf_is_true "$OCF_RESKEY_nfs_notify_foreground"; then
			opts="-F"
		fi
		opts="$opts -n"
		;;

	esac

	rm -rf /var/lib/nfs/sm.ha.save > /dev/null 2>&1
	cp -rf /var/lib/nfs/sm.ha /var/lib/nfs/sm.ha.save > /dev/null 2>&1
	for ip in `echo ${OCF_RESKEY_nfs_ip} | sed 's/,/ /g'`; do
	  ${OCF_RESKEY_nfs_notify_cmd} $opts $ip -P /var/lib/nfs/sm.ha
	  rm -rf /var/lib/nfs/sm.ha > /dev/null 2>&1
	  cp -rf /var/lib/nfs/sm.ha.save /var/lib/nfs/sm.ha > /dev/null 2>&1
	done


	ocf_log info "NFS server started"
	return $OCF_SUCCESS
}

nfsserver_stop ()
{
	ocf_log info "Stopping NFS server ..."

	fn=`mktemp`
	${OCF_RESKEY_nfs_init_script} stop > $fn 2>&1
	rc=$?
	ocf_log debug `cat $fn`
	rm -f $fn

	if [ $rc -eq 0 ]; then
		unbind_tree 
		ocf_log info "NFS server stopped"
		return $OCF_SUCCESS
	fi
	ocf_log err "Failed to stop NFS server"
	return $rc
}

nfsserver_validate ()
{
	check_binary ${OCF_RESKEY_nfs_init_script}
	check_binary ${OCF_RESKEY_nfs_notify_cmd}

	if [ x = x"${OCF_RESKEY_nfs_ip}" ]; then
		ocf_log err "nfs_ip not set"
		exit $OCF_ERR_CONFIGURED
	fi

	if [ x = "x$OCF_RESKEY_nfs_shared_infodir" ]; then
		ocf_log err "nfs_shared_infodir not set"
		exit $OCF_ERR_CONFIGURED
	fi

	if [ -n "$OCF_RESKEY_nfs_smnotify_retry_time" ]; then
		if ! ocf_is_decimal "$OCF_RESKEY_nfs_smnotify_retry_time"; then
			ocf_log err "Invalid nfs_smnotify_retry_time [$OCF_RESKEY_nfs_smnotify_retry_time]"
			exit $OCF_ERR_CONFIGURED
		fi
	fi

	case ${OCF_RESKEY_nfs_notify_cmd##*/} in
	sm-notify|rpc.statd) ;;
	*)
		ocf_log err "Invalid nfs_notify_cmd [$OCF_RESKEY_nfs_notify_cmd]"
		exit $OCF_ERR_CONFIGURED
		;;
	esac

	return $OCF_SUCCESS
}

if [ -n "$OCF_RESKEY_CRM_meta_clone" ]; then
	ocf_log err "THIS RA DO NOT SUPPORT CLONE MODE!"
	exit $OCF_ERR_CONFIGURED
fi

nfsserver_validate

case $__OCF_ACTION in
	start)      nfsserver_start
		;;
	stop)       nfsserver_stop
		;;
	monitor)    nfsserver_monitor
		;;
	validate-all)   exit $OCF_SUCCESS
		;;
	*)      nfsserver_usage
	exit $OCF_ERR_UNIMPLEMENTED
	;;
esac

