#!/bin/bash
#
#
#       An OCF RA for conntrackd
#	http://conntrack-tools.netfilter.org/
#
# Copyright (c) 2011 Dominik Klein
#
# 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.
#

#######################################################################
# Initialization:

: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat}
. ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs

#######################################################################

OCF_RESKEY_binary_default=conntrackd
OCF_RESKEY_config_default=/etc/conntrackd/conntrackd.conf
: ${OCF_RESKEY_binary=${OCF_RESKEY_binary_default}}
: ${OCF_RESKEY_config=${OCF_RESKEY_config_default}}

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

<longdesc lang="en">
Master/Slave OCF Resource Agent for conntrackd
</longdesc>

<shortdesc lang="en">This resource agent manages conntrackd</shortdesc>

<parameters>
<parameter name="conntrackd">
<longdesc lang="en">Name of the conntrackd executable.
If conntrackd is installed and available in the default PATH, it is sufficient to configure the name of the binary
For example "my-conntrackd-binary-version-0.9.14"
If conntrackd is installed somewhere else, you may also give a full path
For example "/packages/conntrackd-0.9.14/sbin/conntrackd"
</longdesc>
<shortdesc lang="en">Name of the conntrackd executable</shortdesc>
<content type="string" default="$OCF_RESKEY_binary_default"/>
</parameter>

<parameter name="config">
<longdesc lang="en">Full path to the conntrackd.conf file.
For example "/packages/conntrackd-0.9.14/etc/conntrackd/conntrackd.conf"</longdesc>
<shortdesc lang="en">Path to conntrackd.conf</shortdesc>
<content type="string" default="$OCF_RESKEY_config_default"/>
</parameter>
</parameters>

<actions>
<action name="start"   timeout="30" />
<action name="promote"	 timeout="30" />
<action name="demote"	timeout="30" />
<action name="notify"	timeout="30" />
<action name="stop"    timeout="30" />
<action name="monitor" timeout="20" interval="20" role="Slave" />
<action name="monitor" timeout="20" interval="10" role="Master" />
<action name="meta-data"  timeout="5" />
<action name="validate-all"  timeout="30" />
</actions>
</resource-agent>
END
}

meta_expect()
{
        local what=$1 whatvar=OCF_RESKEY_CRM_meta_${1//-/_} op=$2 expect=$3
        local val=${!whatvar}
        if [[ -n $val ]]; then
                # [, not [[, or it won't work ;)
                [ $val $op $expect ] && return
        fi
        ocf_log err "meta parameter misconfigured, expected $what $op $expect, but found ${val:-unset}."
        exit $OCF_ERR_CONFIGURED
}

conntrackd_is_master() {
	# You can't query conntrackd whether it is master or slave. It can be both at the same time. 
	# This RA creates a statefile during promote and enforces master-max=1 and clone-node-max=1
	ha_pseudo_resource $statefile monitor
}

conntrackd_set_master_score() {
	${HA_SBIN_DIR}/crm_master -Q -l reboot -v $1
}

conntrackd_monitor() {
	rc=$OCF_NOT_RUNNING
	# It does not write a PID file, so check with pgrep
	pgrep -f $OCF_RESKEY_binary && rc=$OCF_SUCCESS
	if [ "$rc" -eq "$OCF_SUCCESS" ]; then
		# conntrackd is running 
		# now see if it acceppts queries
		if ! $OCF_RESKEY_binary -C $OCF_RESKEY_config -s > /dev/null 2>&1; then
			rc=$OCF_ERR_GENERIC
			ocf_log err "conntrackd is running but not responding to queries"
		fi
		if conntrackd_is_master; then
			rc=$OCF_RUNNING_MASTER
			# Restore master setting on probes
	                if [ $OCF_RESKEY_CRM_meta_interval -eq 0 ]; then
				conntrackd_set_master_score $master_score
			fi
		else
			# Restore master setting on probes
	                if [ $OCF_RESKEY_CRM_meta_interval -eq 0 ]; then
				conntrackd_set_master_score $slave_score
			fi
		fi
	fi
	return $rc
}

conntrackd_start() {
        rc=$OCF_ERR_GENERIC

        # Keep trying to start the resource;
        # wait for the CRM to time us out if this fails
	while :; do
		conntrackd_monitor
		status=$?
		case "$status" in 
		$OCF_SUCCESS)
			rc=$OCF_SUCCESS
			conntrackd_set_master_score $slave_score
			break
			;;
		$OCF_NOT_RUNNING)
			ocf_log info "Starting conntrackd"
			$OCF_RESKEY_binary -C $OCF_RESKEY_config -d
			;;
		$OCF_RUNNING_MASTER)
			ocf_log warn "conntrackd already in master mode, demoting."
			ha_pseudo_resource $statefile stop
			;;
		$OCF_ERR_GENERIC)
			ocf_log err "conntrackd start failed"
			rc=$OCF_ERR_GENERIC
			break
			;;
		esac
	done
	return $rc
}

conntrackd_stop() {
        rc=$OCF_ERR_GENERIC

        # Keep trying to bring down the resource;
        # wait for the CRM to time us out if this fails
        while :; do
                conntrackd_monitor
                status=$?
                case "$status" in
                $OCF_SUCCESS)
			ocf_log info "Stopping conntrackd"
                        $OCF_RESKEY_binary -C $OCF_RESKEY_config -k
                        ;;
                $OCF_NOT_RUNNING)
                        rc=$OCF_SUCCESS
                        break
                        ;;
                $OCF_RUNNING_MASTER)
                        ocf_log warn "conntrackd still master"
			;;
                esac
        done
        return $rc

}

conntrackd_validate_all() {
	check_binary "$OCF_RESKEY_binary"
	if ! [ -e "$OCF_RESKEY_config" ]; then
		ocf_log err "Config FILE $OCF_RESKEY_config does not exist"
		return $OCF_ERR_INSTALLED
	fi
        meta_expect master-node-max = 1
        meta_expect master-max = 1
        meta_expect clone-node-max = 1
        meta_expect clone-max = 2
	
	return $OCF_SUCCESS
}

conntrackd_promote() {
	rc=$OCF_SUCCESS
	if ! conntrackd_is_master; then
		# -c = Commit the external cache to the kernel
		# -f = Flush internal and external cache
		# -R = resync with the kernel table
		# -B = send a bulk update on the line
		for parm in c f R B; do
			if ! $OCF_RESKEY_binary -C $OCF_RESKEY_config -$parm; then
				ocf_log err "$OCF_RESKEY_binary -C $OCF_RESKEY_config -$parm failed during promote."
				rc=$OCF_ERR_GENERIC
				break
			fi
		done
		ha_pseudo_resource $statefile start
		conntrackd_set_master_score $master_score
	fi
	return $rc
}

conntrackd_demote() {
	rc=$OCF_SUCCESS
	if conntrackd_is_master; then
		# -t = shorten kernel timers to remove zombies
		# -n = request a resync from the others
		for parm in t n; do
			if ! $OCF_RESKEY_binary -C $OCF_RESKEY_config -$parm; then
                        	ocf_log err "$OCF_RESKEY_binary -C $OCF_RESKEY_config -$parm failed during demote."
                        	rc=$OCF_ERR_GENERIC
                        	break
                	fi
        	done
		ha_pseudo_resource $statefile stop
		conntrackd_set_master_score $slave_score
	fi
	return $rc
}

conntrackd_notify() {
	hostname=$(hostname)
	# OCF_RESKEY_CRM_meta_notify_master_uname is a whitespace separated list of master hostnames
	for master in $OCF_RESKEY_CRM_meta_notify_master_uname; do
		# if we are the master and an instance was just started on another node:
		# send a bulk update to allow failback
		if [ "$hostname" = "$master" -a "$OCF_RESKEY_CRM_meta_notify_type" = "post" -a "$OCF_RESKEY_CRM_meta_notify_operation" = "start" -a "$OCF_RESKEY_CRM_meta_notify_start_uname" != "$hostname" ]; then
			ocf_log info "Sending bulk update in post start to peers to allow failback"
			$OCF_RESKEY_binary -C $OCF_RESKEY_config -B
		fi
	done
	for tobepromoted in $OCF_RESKEY_CRM_meta_notify_promote_uname; do
		# if there is a promote action to be executed on another node:
		# send a bulk update to allow failback
		if [ "$hostname" != "$tobepromoted" -a "$OCF_RESKEY_CRM_meta_notify_type" = "pre" -a "$OCF_RESKEY_CRM_meta_notify_operation" = "promote" ]; then
			ocf_log info "Sending bulk update in pre promote to peers to allow failback"
			$OCF_RESKEY_binary -C $OCF_RESKEY_config -B
		fi
	done
}

conntrackd_usage() {
        cat <<EOF
usage: $0 {start|stop|promote|demote|monitor|validate-all|meta-data}
Expects to have a fully populated OCF RA-compliant environment set.
EOF
}

statefile=conntrackd.${OCF_RESOURCE_INSTANCE}.master

master_score=1000
slave_score=100

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

case $__OCF_ACTION in
meta-data)
	meta_data
	exit $OCF_SUCCESS
	;;
usage)
	conntrackd_usage
	exit $OCF_SUCCESS
esac

# Everything except usage and meta-data must pass the validate test
conntrackd_validate_all || exit

case $__OCF_ACTION in
start)
	conntrackd_start
	;;
stop)
	conntrackd_stop
	;;
promote)
	conntrackd_promote
	;;
demote)
	conntrackd_demote
	;;
status|monitor)
	conntrackd_monitor
	;;
notify)
	conntrackd_notify
	;;
validate-all)
	;;
*)
	conntrackd_usage
	exit $OCF_ERR_UNIMPLEMENTED
esac
# exit code is the exit code (return code) of the last command (shell function)
