#!/bin/sh
#
#	$Id: IPaddr2.in,v 1.24 2006/08/09 13:01:54 lars Exp $
#
#       OCF Resource Agent compliant IPaddr2 script.
#
# 	Based on work by Tuomo Soini, ported to the OCF RA API by Lars
# 	Marowsky-Brée. Implements Cluster Alias IP functionality too.
#
#	Cluster Alias IP cleanup, fixes and testing by Michael Schwartzkopff
#
#
# Copyright (c) 2003 Tuomo Soini
# Copyright (c) 2004-2006 SUSE LINUX AG, Lars Marowsky-Brée
#                    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.
#
#


# TODO: 
# - There ought to be an ocf_run_cmd function which does all logging,
#   timeout handling etc for us
# - Make this the standard IP address agent on Linux; the other
#   platforms simply should ignore the additional parameters OR can use
#   the legacy heartbeat resource script...
# - Check LVS <-> clusterip incompatibilities.
#
#	OCF parameters are as below

# Syno: From IPaddr2 and remove cluster ip, lvs

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

: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat}
. ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs
. $prefix/etc.defaults/rc.subr
. $prefix/etc.defaults/serv_conf_def.sh

SENDARP=$HA_BIN/send_arp
FINDIF=$HA_BIN/findif
ARPING=/usr/sbin/arping
SENDARPPIDDIR=$HA_RSCTMP

HA_INFO=$prefix/etc/ha.conf
DRBD_IP0=$(/bin/get_key_value $HA_INFO drbd_ip0)
SYNO_HA_BIN=$prefix"/sbin/synoha"
SYNO_HA_OCF_IP_SERV="ocf-ip-operation"
SERV_TYPE="NONE"
RESTART_IP_SENSITIVE_SERVICES="no"

IPADDR2_OCF_RUN="$HA_RSCTMP/ipaddr2_ocf.run"

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

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

<longdesc lang="en">
This Linux-specific resource manages IP alias IP addresses.
It can add an IP alias, or remove one.
In addition, it can implement Cluster Alias IP functionality
if invoked as a clone resource.
</longdesc>

<shortdesc lang="en">Manages virtual IPv4 addresses (Linux specific version)</shortdesc>

<parameters>

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

	exit $OCF_SUCCESS
}

ip_init() {
	local rc

	if [ X`uname -s` != "XLinux" ]; then
		ocf_log err "IPaddr2 only supported Linux."
		return $OCF_ERR_INSTALLED
	fi

	if [ X"$OCF_RESKEY_ip" = "X" ]; then
		ocf_log err "IP address (the ip parameter) is mandatory"
		return $OCF_ERR_CONFIGURED
	fi

	if
          case $__OCF_ACTION in
            start|stop)		ocf_is_root;;
            *)			true;;
          esac
        then
          : YAY!
        else
		ocf_log err "You must be root for $__OCF_ACTION operation."
		return $OCF_ERR_PERM
	fi

	BASEIP="$OCF_RESKEY_ip"
	NIC="$OCF_RESKEY_nic"
        # Note: We had a version out there for a while which used
        # netmask instead of cidr_netmask. Don't remove this aliasing code!
        if
          [ ! -z "$OCF_RESKEY_netmask" -a -z "$OCF_RESKEY_cidr_netmask" ]
        then
	  OCF_RESKEY_cidr_netmask=$OCF_RESKEY_netmask
	  export OCF_RESKEY_cidr_netmask
        fi
	NETMASK="$OCF_RESKEY_cidr_netmask"
	IFLABEL="$OCF_RESKEY_iflabel"


	ARP_INTERVAL_MS=${OCF_RESKEY_arp_interval:-200}
	ARP_REPEAT=${OCF_RESKEY_arp_count:-5}
	ARP_BACKGROUND=${OCF_RESKEY_arp_bg:-yes}
	ARP_NETMASK=${OCF_RESKEY_arp_mac:-ffffffffffff}

	# $FINDIF takes its parameters from the environment
	#
	NICINFO=`$FINDIF -C`
	rc=$?
	if
	  [ $rc -eq 0 ]
        then
	    NICINFO=`echo $NICINFO | sed -e 's/netmask\ //;s/broadcast\ //'`
	    NIC=`echo "$NICINFO" | cut -d" " -f1`
	    NETMASK=`echo "$NICINFO" | cut -d" " -f2`
	else
		# findif couldn't find the interface
		if ocf_is_probe; then
			ocf_log info "[$FINDIF -C] failed"
			return $OCF_NOT_RUNNING
		elif [ "$__OCF_ACTION" = stop ]; then
			ocf_log warn "[$FINDIF -C] failed"
			return $OCF_SUCCESS
		else
			ocf_log err "[$FINDIF -C] failed"
			return $rc
		fi
	fi
	
	SENDARPPIDFILE="$SENDARPPIDDIR/send_arp-$BASEIP"

	case $NIC in
	    *:*)
		IFLABEL=$NIC
		NIC=`echo $NIC | sed 's/:.*//'`
		;;
	    *)
		if [ -n "$IFLABEL" ]; then
			IFLABEL=${NIC}:${IFLABEL}
		fi
		;;
	esac

}

#
#	Find out which interface serves the given IP address
#	The argument is an IP address, and its output
#	is an interface name (e.g., "eth0").
#
find_interface() {
	#
	# List interfaces but exclude FreeS/WAN ipsecN virtual interfaces
	#
	local iface=`$IP2UTIL -o -f inet addr show | grep "\ $BASEIP/$NETMASK" \
		| cut -d ' ' -f2 | grep -v '^ipsec[0-9][0-9]*$'`
	echo $iface
	return 0
}

#
#        ifconfig ethx down
#
remove_interface() {
	CMD="/sbin/ifconfig $1 down"
	ocf_run -q $CMD
	return $OCF_SUCCESS
}

#
#        Delete an interface
#
delete_interface () {
	iflabel="$1"

	CMD="$IFCONFIG $iflabel down"

	ocf_run $CMD || return $OCF_ERR_GENERIC

	return $OCF_SUCCESS
}

#
#        Add an interface
#
add_interface () {
	ipaddr="$1"
	netmask="$2"
	iface="$3"
	label="$4"


	if [ -n "$label" ]; then
		CMD="$SYNONET_BIN --set_ip -4 $label add $ipaddr/$netmask"
	else
		CMD="$SYNONET_BIN --set_ip -4 $iface add $ipaddr/$netmask"
	fi

	ocf_log info "$CMD"
	if ! $CMD; then
		return $OCF_ERR_GENERIC
	fi

	CMD="$IFCONFIG $iface up"

	ocf_log info "$CMD"
	if ! $CMD; then
		return $OCF_ERR_GENERIC
	fi

	eval "$(ipcalc -n "$ipaddr/$netmask")"
	CMD="ip route replace $NETWORK/$netmask dev $iface proto kernel scope link src $ipaddr"
	if [ 1 -ne "$(ip route show "$NETWORK/$netmask" | wc -l)" ]; then
		CMD=": Skip modifying source address as there are more than one routing rule of the subnet"
	fi

	ocf_log info "$CMD"
	$CMD

	return $?
}

#
# Run send_arp to note peers about new mac address
#
run_send_arp() {
	ARGS="-i $ARP_INTERVAL_MS -r $ARP_REPEAT -p $SENDARPPIDFILE $NIC $BASEIP auto not_used not_used"
	ARPING_ARGS="-c $ARP_REPEAT -A -U -I $NIC $BASEIP"
	ocf_log info "$SENDARP $ARGS"
	case $ARP_BACKGROUND in
	yes) 
		($SENDARP $ARGS || ocf_log err "Could not send gratuitous arps" &) >&2
		($ARPING $ARPING_ARGS || ocf_log err "Could not send gratuitous arps by arping" &) >&2
		;;
	*)
		$SENDARP $ARGS || ocf_log err "Could not send gratuitous arps"
		$ARPING $ARPING_ARGS || ocf_log err "Could not send gratuitous arps by arping"
		;;
	esac
}

# Do we already serve this IP address?
#
# returns:
# ok = served (for CIP: + hash bucket)
# partial = served and no hash bucket (CIP only)
# partial2 = served and no CIP iptables rule
# no = nothing
#
ip_served() {
	if [ -z "$NIC" ]; then # no nic found or specified
		echo "no"
		return $OCF_SUCCESS
	fi

	cur_nic="`find_interface $BASEIP`"

	if [ -z "$cur_nic" ]; then
		echo "no"
		return $OCF_SUCCESS
	fi

		echo "ok"
		return $OCF_SUCCESS

	return $OCF_ERR_GENERIC
}

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

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

Expects to have a fully populated OCF RA-compliant environment set.
END
}

ip_start() {
	if [ -z "$NIC" ]; then # no nic found or specified
		return $OCF_ERR_CONFIGURED
	fi

	#
	#	Do we already service this IP address?
	#
	local ip_status=`ip_served`

	if [ "$ip_status" = "ok" ]; then
		return $OCF_SUCCESS
	fi
	
	if [ "$ip_status" = "no" ]; then

		if ! $SYNO_HA_BIN --service-skip $SERV_TYPE_START $SYNO_HA_OCF_IP_SERV ; then
			add_interface $BASEIP $NETMASK $NIC $IFLABEL
		
			if [ $? -ne 0 ]; then
				ocf_log err "$CMD failed."
				return $OCF_ERR_GENERIC
			fi
		fi
	fi

	case $NIC in
	lo*)
		: no need to run send_arp on loopback
		;;
	*)
	    if [ -x $SENDARP ]; then
		run_send_arp
	    fi
		;;
	esac

	return $OCF_SUCCESS
}

ip_stop() {
	local ip_del_if="yes"

	[ ! -f $SYNO_HA_UPG ] && $SYNO_HA_BIN --set-hostname-original &> /dev/null

	if [ -f "$SENDARPPIDFILE" ] ; then
		kill `cat "$SENDARPPIDFILE"`
		if [ $? -ne 0 ]; then
			ocf_log warn "Could not kill previously running send_arp for $BASEIP"
		else
			ocf_log info "killed previously running send_arp for $BASEIP"
			rm -f "$SENDARPPIDFILE"
		fi
	fi
	local ip_status=`ip_served`
	ocf_log info "IP status = $ip_status."

	if [ $ip_status = "no" ]; then
		: Requested interface not in use
		return $OCF_SUCCESS
	fi

	if [ "$ip_del_if" = "yes" ]; then
		if ! $SYNO_HA_BIN --service-skip $SERV_TYPE_STOP $SYNO_HA_OCF_IP_SERV ; then
			delete_interface $IFLABEL
			if [ $? -ne 0 ]; then
				return $OCF_ERR_GENERIC
			fi
		fi
	
	fi

	return $OCF_SUCCESS
}

ip_monitor() {
	# TODO: Implement more elaborate monitoring like checking for
	# interface health maybe via a daemon like FailSafe etc...

	local ip_status=`ip_served`
	case $ip_status in
	ok)
		return $OCF_SUCCESS
		;;
	partial|no|partial2)
		return $OCF_NOT_RUNNING
		;;
	*)
		# Errors on this interface?
		return $OCF_ERR_GENERIC
		;;
	esac
}

ip_validate() {
    check_binary $IFCONFIG

    ip_init


# $BASEIP, $NETMASK, $NIC and $IP_INC_GLOBAL have been checked within ip_init,
# do not bother here.


    if ocf_is_decimal "$ARP_INTERVAL_MS" && [ $ARP_INTERVAL_MS -gt 0 ]; then
	:
    else
	ocf_log err "Invalid OCF_RESKEY_arp_interval [$ARP_INTERVAL_MS]"
	return $OCF_ERR_CONFIGURED
    fi

    if ocf_is_decimal "$ARP_REPEAT" && [ $ARP_REPEAT -gt 0 ]; then
	:
    else
	ocf_log err "Invalid OCF_RESKEY_arp_count [$ARP_REPEAT]"
	return $OCF_ERR_CONFIGURED
    fi

}


case $__OCF_ACTION in
meta-data)	meta_data
		exit $OCF_SUCCESS
		;;
usage|help)	ip_usage
		exit $OCF_SUCCESS
		;;
esac

OCF_RESKEY_iflabel=$HA_ALIAS_NAME
OCF_RESKEY_ip=""
OCF_RESKEY_nic=""
OCF_RESKEY_cidr_netmask=""
export OCF_RESKEY_ip
export OCF_RESKEY_nic
export OCF_RESKEY_cidr_netmask
export OCF_RESKEY_iflabel

if [ "$__OCF_ACTION" = "start" ]; then
	SERV_TYPE=$SERV_TYPE_START
elif [ "$__OCF_ACTION" = "stop" ]; then
	SERV_TYPE=$SERV_TYPE_STOP
fi

# remove all HA interface for start/stop
if [ "$__OCF_ACTION" = "stop" ] || [ "$__OCF_ACTION" = "start" ]; then
	unset_haip_as_gateway_src
	if ! $SYNO_HA_BIN --service-skip $SERV_TYPE $SYNO_HA_OCF_IP_SERV ; then
		alias_list=$(ip -o address show label "*:HA" | grep -oE "[^[:space:]]+:HA")
		for iface in $alias_list; do
			orig_addr="$(ip -o -4 addr show primary dev "$iface" | awk '{print $4}' | cut -d"/" -f 1)"
			orig_mask="$(ip -o -4 addr show primary dev "$iface" | awk '{print $4}' | cut -d"/" -f 2)"

			eval "$(ipcalc -n "$orig_addr/$orig_mask")" # set NETWORK
			CMD="ip route replace $NETWORK/$orig_mask dev ${iface%:$OCF_RESKEY_iflabel} proto kernel scope link src $orig_addr"
			ocf_log info "$CMD"
			$CMD

			inet=$(ip addr show label "${iface}" | grep -oE 'inet [^[:space:]]+' | cut -d' ' -f 2)
			eval "$(ipcalc -m "$inet")" # set NETMASK
			old_address=$(printf "%s" "$inet" | cut -d "/" -f 1)
			synoha_log notice "Remove interface $iface $old_address/$NETMASK"
			remove_interface "$iface"
		done;
	fi
fi

if [ "$__OCF_ACTION" = "start" ]; then
	$SYNO_HA_BIN --check-and-set-bond-if
	$SYNO_HA_BIN --check-and-set-vlan-if
fi

if [ "$__OCF_ACTION" = "monitor" ]; then
	if ${HA_SBIN_DIR}/synoha --ip-is-unmanaged; then
		synoha_log notice "IPaddr2: IP unmanaged, skip monitor"
		exit $OCF_SUCCESS
	fi
fi

# for (ovs_)eth* and bond* individually
for interface in nic bonding ; do
# max 12 interfaces
for index in $(seq 0 1 11) ; do
if [ "$interface" = "nic" ] ; then
	# for ovs_eth* and eth* both
	if [ "$(get_key_value $HA_INFO "ha_if_enabled$index")" != "true" ] ; then
		continue
	fi
	OCF_RESKEY_ip=$(get_key_value $HA_INFO "ha_ip$index")
	OCF_RESKEY_nic=$(get_key_value $HA_INFO "ha_if$index")
	OCF_RESKEY_cidr_netmask=$(get_key_value $HA_INFO "ha_netmask$index")
else
	if [ "$(get_key_value $HA_INFO "ha_bond_if_enabled$index")" != "true" ] ; then
		continue
	fi
	OCF_RESKEY_ip=$(get_key_value $HA_INFO "ha_bond_ip$index")
	OCF_RESKEY_nic=$(get_key_value $HA_INFO "ha_bond_if$index")
	OCF_RESKEY_cidr_netmask=$(get_key_value $HA_INFO "ha_bond_netmask$index")
fi

	case $__OCF_ACTION in
	start)
		touch $IPADDR2_OCF_STARTED
		synoha_log notice "Setup interface $OCF_RESKEY_nic:$OCF_RESKEY_iflabel $OCF_RESKEY_ip/$OCF_RESKEY_cidr_netmask"
		#if [ -e $IPADDR2_OCF_RUN ]; then
		#	exit $OCF_SUCCESS
		#fi
		ip_validate
		ip_start
		if [ $OCF_SUCCESS != $? ]; then
			synoha_log "Failed to setup interface $OCF_RESKEY_nic:$OCF_RESKEY_iflabel"
		#	exit $OCF_NOT_RUNNING
		fi
		touch $IPADDR2_OCF_RUN
		;;
	stop)
		rm -fr $IPADDR2_OCF_STARTED
		ip_validate
		ip_stop
		rm -fr $IPADDR2_OCF_RUN
		;;
	status)
		if [ ! -e $IPADDR2_OCF_RUN ]; then
			exit $OCF_NOT_RUNNING
		fi
		ip_validate
		if [ "$ip_status" = "ok" ]; then
			echo "running"
		else
			echo "stopped"
			exit $OCF_NOT_RUNNING
		fi
		;;
	monitor)
		if [ ! -e $IPADDR2_OCF_RUN ]; then
			exit $OCF_NOT_RUNNING
		fi
		ip_validate
		ip_monitor
		if [ $OCF_SUCCESS != $? ]; then
			if ! $SYNO_HA_BIN --service-skip $SERV_TYPE_MON $SYNO_HA_OCF_IP_SERV ; then
				synoha_log "Error occurred on $OCF_RESKEY_nic:$OCF_RESKEY_iflabel"
				synoha_log "Try re-add interface $BASEIP $NETMASK $NIC $IFLABEL"
				if ! add_interface $BASEIP $NETMASK $NIC $IFLABEL; then
					synoha_log "Add interface failed"
				elif [ -x $SENDARP ]; then
					run_send_arp
				fi
				RESTART_IP_SENSITIVE_SERVICES="yes"
			fi
		fi
		;;
	validate-all)
		ip_validate
		;;
	*)
		ip_usage
		exit $OCF_ERR_UNIMPLEMENTED
		;;
	esac
done
done

if [ "$__OCF_ACTION" = "start" ]; then
	RULE=$(ip route | grep default)
	GATEWAY_INTERFACE=$(printf "%s" "$RULE" | cut -d ' ' -f 5)
	set_haip_as_gateway_src "$GATEWAY_INTERFACE"

	/sbin/initctl start network NETWORKING="no"
	if [ ! -f $SYNO_HA_UPG ] || $SYNOHA_BIN --upg-is-active ; then
		$SYNO_HA_BIN --set-hostname-ha &> /dev/null
	fi
	# set hostname does not add ha interface
	add_haip_to_hosts
	# set hostname will remove the heartbeat ip which is set by drbd start in rc.ha
	add_hbip_to_hosts
	# set default gateway as synchronized from active server
	update_default_gateway
fi
if [ "$__OCF_ACTION" = "stop" ]; then
	remove_haip_from_hosts
	# set hostname will remove the heartbeat ip which is set by drbd start in rc.ha
	add_hbip_to_hosts
	# Sync default gateway manually, just a workaround
	$RSYNC_PROG --port=$RSYNC_PORT -ltsR --password-file="$RSYNC_PW_FILE" /etc/iproute2/config/default-gateway root@$DRBD_IP_REMOTE::synoha_root
fi
if [ "yes" = "$RESTART_IP_SENSITIVE_SERVICES" ]; then
	$SYNOSERVICECFG_BIN --restart-by-type ip 5 &> /dev/null
fi

exit $OCF_SUCCESS

