#!/bin/sh
#
#       OCF Resource Agent compliant script.
#       Monitor the vitality of a local network interface.
#
# 	Based on the work by Robert Euhus and Lars Marowsky-Bre.
#
#	Transfered from Ipaddr2 into ethmonitor by Alexander Krauth
#
# Copyright (c) 2011 Robert Euhus, Alexander Krauth, Lars Marowsky-Bre
#                    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.
#
#     OCF parameters are as below
#
#	OCF_RESKEY_interface
#	OCF_RESKEY_multiplicator
#	OCF_RESKEY_name
#       OCF_RESKEY_repeat_count
#	OCF_RESKEY_repeat_interval
#	OCF_RESKEY_pktcnt_timeout
#	OCF_RESKEY_arping_count
#	OCF_RESKEY_arping_timeout
#	OCF_RESKEY_arping_cache_entries
#
#   TODO: Check against IPv6
#
#######################################################################
# Initialization:

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

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

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

<longdesc lang="en">
Monitor the vitality of a local network interface.

You may setup this RA as a clone resource to monitor the network interfaces on different nodes, with the same interface name.
This is not related to the IP adress or the network on which a interface is configured.
You may use this RA to move resources away from a node, which has a faulty interface or prevent moving resources to such a node.
This gives you independend control of the resources, without involving cluster intercommunication. But it requires your nodes to have more than one network interface.

The resource configuration requires a monitor operation, because the monitor does the main part of the work.
In addition to the resource configuration, you need to configure some location contraints, based on a CIB attribute value.
The name of the attribute value is configured in the 'name' option of this RA.

Example constraint configuration:
location loc_connected_node my_resource_grp \
        rule $id="rule_loc_connected_node" -INF: ethmonitor eq 0

The ethmonitor works in 3 different modes to test the interface vitality.
1. call ip to see if the link status is up (if link is down -> error)
2. call ip an watch the RX counter (if packages come around in a certain time -> success)
3. call arping to check wether any of the IPs found in the lokal ARP cache answers an ARP REQUEST (one answer -> success)
4. return error
</longdesc>
<shortdesc lang="en">Monitors network interfaces</shortdesc>

<parameters>
<parameter name="interface" unique="1" required="1">
<longdesc lang="en">
The name of the network interface which should be monitored (e.g. eth0).
</longdesc>
<shortdesc lang="en">Network interface name</shortdesc>
<content type="string" default=""/>
</parameter>

<parameter name="name" unique="1">
<longdesc lang="en">
The name of the CIB attribute to set.  This is the name to be used in the constraints. Defaults to "ethmonitor-'interface_name'".
</longdesc>
<shortdesc lang="en">Attribute name</shortdesc>
<content type="string" default=""/>
</parameter>

<parameter name="multiplier" unique="0" >
<longdesc lang="en">
Multiplier for the value of the CIB attriobute specified in parameter name. 
</longdesc>
<shortdesc lang="en">Multiplier for result variable</shortdesc>
<content type="integer" default="1"/>
</parameter>

<parameter name="repeat_count">
<longdesc lang="en">
Specify how often the interface will be monitored, before the status is set to failed. You need to set the timeout of the monitoring operation to at least repeat_count * repeat_interval
</longdesc>
<shortdesc lang="en">Monitor repeat count</shortdesc>
<content type="integer" default="5"/>
</parameter>

<parameter name="repeat_interval">
<longdesc lang="en">
Specify how long to wait in seconds between the repeat_counts.
</longdesc>
<shortdesc lang="en">Monitor repeat interval in seconds</shortdesc>
<content type="integer" default="10"/>
</parameter>

<parameter name="pktcnt_timeout">
<longdesc lang="en">
Timeout for the RX packet counter. Stop listening for packet counter changes after the given number of seconds.
</longdesc>
<shortdesc lang="en">packet counter timeout</shortdesc>
<content type="integer" default="5"/>
</parameter>

<parameter name="arping_count">
<longdesc lang="en">
Number of ARP REQUEST packets to send for every IP.
Usually one ARP REQUEST (arping) is send
</longdesc>
<shortdesc lang="en">Number of arpings per IP</shortdesc>
<content type="integer" default="1"/>
</parameter>

<parameter name="arping_timeout">
<longdesc lang="en">
Time in seconds to wait for ARP REQUESTs (all packets of arping_count).
This is to limit the time for arp requests, to be able to send requests to more than one node, without running in the monitor operation timeout.
</longdesc>
<shortdesc lang="en">Timeout for arpings per IP</shortdesc>
<content type="integer" default="1"/>
</parameter>

<parameter name="arping_cache_entries">
<longdesc lang="en">
Maximum number of IPs from ARP cache list to check for ARP REQUEST (arping) answers. Newest entries are tried first.
</longdesc>
<shortdesc lang="en">Number of ARP cache entries to try</shortdesc>
<content type="integer" default="5"/>
</parameter>

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

	exit $OCF_SUCCESS
}

#
#	Return true, if the interface exists
#
is_interface() {
	#
	# List interfaces but exclude FreeS/WAN ipsecN virtual interfaces
	#
	local iface=`$IP2UTIL -o -f inet addr show | grep " $1 " \
		| cut -d ' ' -f2 | sort -u | grep -v '^ipsec[0-9][0-9]*$'`
        [ "$iface" != "" ]
}

if_init() {
	local rc

	if [ X"$OCF_RESKEY_interface" = "X" ]; then
		ocf_log err "Interface name (the interface parameter) is mandatory"
		exit $OCF_ERR_CONFIGURED
	fi

	NIC="$OCF_RESKEY_interface"

	if is_interface $NIC
	then
	  case "$NIC" in
	    *:*) ocf_log err "Do not specify a virtual interface : $OCF_RESKEY_interface"
	         exit $OCF_ERR_CONFIGURED;;
	    *)  ;;
	  esac
	else
	  case $__OCF_ACTION in
	    validate-all) ocf_log err "Interface $NIC does not exist"
                            exit $OCF_ERR_CONFIGURED;;
	    *)   	    ocf_log warn "Interface $NIC does not exist"
                            ## It might be a bond interface which is temporarily not available, therefore we want to continue here
	                    ;;
	  esac
	fi

	: ${OCF_RESKEY_multiplier:="1"}
	if ! ocf_is_decimal "$OCF_RESKEY_multiplier"; then
		ocf_log err "Invalid OCF_RESKEY_multiplier [$OCF_RESKEY_multiplier]"
		exit $OCF_ERR_CONFIGURED
	fi
	
	ATTRNAME=${OCF_RESKEY_name:-"ethmonitor-$NIC"}
	
        REP_COUNT=${OCF_RESKEY_repeat_count:-5}
	if ! ocf_is_decimal "$REP_COUNT" -o [ $REP_COUNT -lt 1 ]; then
		ocf_log err "Invalid OCF_RESKEY_repeat_count [$REP_COUNT]"
		exit $OCF_ERR_CONFIGURED
        fi
	REP_INTERVAL_S=${OCF_RESKEY_repeat_interval:-10}
	if ! ocf_is_decimal "$REP_INTERVAL_S"; then
		ocf_log err "Invalid OCF_RESKEY_repeat_interval [$REP_INTERVAL_S]"
		exit $OCF_ERR_CONFIGURED
	fi
	: ${OCF_RESKEY_pktcnt_timeout:="5"}
	if ! ocf_is_decimal "$OCF_RESKEY_pktcnt_timeout"; then
		ocf_log err "Invalid OCF_RESKEY_pktcnt_timeout [$OCF_RESKEY_pktcnt_timeout]"
		exit $OCF_ERR_CONFIGURED
	fi
	: ${OCF_RESKEY_arping_count:="1"}
	if ! ocf_is_decimal "$OCF_RESKEY_arping_count"; then
		ocf_log err "Invalid OCF_RESKEY_arping_count [$OCF_RESKEY_arping_count]"
		exit $OCF_ERR_CONFIGURED
	fi
	: ${OCF_RESKEY_arping_timeout:="1"}
	if ! ocf_is_decimal "$OCF_RESKEY_arping_timeout"; then
		ocf_log err "Invalid OCF_RESKEY_arping_timeout [$OCF_RESKEY_arping_count]"
		exit $OCF_ERR_CONFIGURED
	fi
	: ${OCF_RESKEY_arping_cache_entries:="5"}
	if ! ocf_is_decimal "$OCF_RESKEY_arping_cache_entries"; then
		ocf_log err "Invalid OCF_RESKEY_arping_cache_entries [$OCF_RESKEY_arping_cache_entries]"
		exit $OCF_ERR_CONFIGURED
	fi
  return $OCF_SUCCESS
}

# get the link status on $NIC
# asks ip about running (up) interfaces, returns the number of matching interface names that are up
get_link_status () {
	$IP2UTIL -o link show up dev "$NIC" | grep -c "$NIC"
}

# returns the number of received rx packets on $NIC
get_rx_packets () {
	ocf_log debug "$IP2UTIL -o -s link show dev $NIC"
	$IP2UTIL -o -s link show dev "$NIC" \
		| sed 's/.* RX: [^0-9]*[0-9]* *\([0-9]*\) .*/\1/'
		# the first number after RX: ist the # of bytes ,
		# the second is the # of packets received
}

# watch for packet counter changes for max. OCF_RESKEY_pktcnt_timeout seconds
# returns immedeately with return code 0 if any packets were received


#
#	The total set of WAS processes (single server only)
#
WAS_procs() {
  ps_long | grep -i "config=$1"  | grep -i java | cut -d' ' -f1
}



#
# methods: What methods/operations do we support?
#
WAS_methods() {
  cat <<-!
	start
	stop
	status
	methods
	validate-all
	meta-data
	usage
	!
  if
    have_binary $WGET
  then
    echo monitor
  fi
}

#
#	Return WAS status (silently)
#
WAS_status() {
  WASPorts=`GetWASPorts $1`
  PortsInUse=`CheckWASPortsInUse $WASPorts`
  case $PortsInUse in
    0)	false;;
    *)	true;;
  esac
}

#
#	Report on WAS status to stdout...
#
WAS_report_status() {
  WASPorts=`GetWASPorts $1`
  PortCount=`echo $WASPorts | wc -w`
  PortCount=`echo $PortCount`
  PortsInUse=`CheckWASPortsInUse $WASPorts`
  case $PortsInUse in
    0)	ocf_log debug "WAS: server $1 is stopped."; return $OCF_NOT_RUNNING;;
    *)
	pids=`WASPIDs $WASPorts`
	if
	  [ $PortsInUse -ge $PortCount ]
	then
    	  ocf_log debug "WAS: server $1 is running (pid" $pids "et al)."
	else
    	  ocf_log debug "WAS: server $1 is running (pid $pids et al) but not listening on all ports."
        fi
	return $OCF_SUCCESS;;
  esac
}

#
#	Monitor WAS - does it really seem to be working?
#
#	For this we invoke the snoop applet via wget.
#
#	This is actually faster than WAS_status above...
#
WAS_monitor() {
  trap '[ -z "$tmpfile" ] || rmtempfile "$tmpfile"' 0
  tmpfile=`maketempfile` || return 1
  SnoopPort=`GetWASSnoopPort $1`
  output=`$WGET -nv -O$tmpfile  http://localhost:$SnoopPort/servlet/snoop 2>&1`
  rc=$?
  if
    [ $rc -eq 0 ]
  then
    if
      grep -i 'user-agent.*Wget' $tmpfile >/dev/null
    then
      : OK
    else
      ocf_log "err" "WAS: $1: no user-agent from snoop application"
      rc=$OCF_ERR_GENERIC
    fi
  else
    ocf_log "err" "WAS: $1: wget failure: $output"
    rc=$OCF_ERR_GENERIC
  fi
  return $rc
}

#
#	Start WAS instance
#
WAS_start() {
# Launch Arguments:
#
#      -configFile      <configFile>
#      -nodeName        <nodeName>
#      -serverName      <serverName>
#      -oltEnabled
#      -oltHost         <hostname>
#      -oltPort         <port>
#      -debugEnabled
#      -jdwpPort        <port>
#      -debugSource     <sourcePath>
#      -serverTrace     <traceString>
#      -serverTraceFile <traceFile>
#      -script          [<scriptFile>]
#      -platform        <platformName>
#      -noExecute
#      -help
  if
    [ -x $WASBIN/startServer.sh ]
  then
    cmd="$WASBIN/startServer.sh -configFile $1"
  else
    cmd="$WASBIN/startupServer.sh"
  fi
    
  if
    ocf_run $cmd
  then
    if
      WAS_wait_4_start $STARTTIME "$@"
    then
      #true
      return $OCF_SUCCESS
    else
      ocf_log "err" "WAS server $1 did not start correctly"
      return $OCF_ERR_GENERIC
    fi
  else
    #false
    return $OCF_ERR_GENERIC
  fi
}

#
#	Wait for WAS to actually start up.
#
#	It seems to take between 30 and 60 seconds for it to
#	start up on a trivial WAS instance.
#
WAS_wait_4_start() {
  max=$1
  retries=0
  shift
  while
    [ $retries -lt $max ]
  do
    if
      WAS_status "$@"
    then
      return $OCF_SUCCESS
    else
      sleep 1
    fi
    retries=`expr $retries + 1`
  done
  WAS_status "$@"
}
  

#
#	Shut down WAS
#
WAS_stop() {
  # They don't return good return codes...
  # And, they seem to allow anyone to stop WAS (!)
  if
    [ -x $WASBIN/stopServer.sh ]
  then
    ocf_run $WASBIN/stopServer.sh -configFile $1
  else
    WASPorts=`GetWASPorts $1`
    kill `WASPIDs $WASPorts`
  fi
  if
    WAS_status $1
  then
    ocf_log "err" "WAS: $1 did not stop correctly"
    #false
    return $OCF_ERR_GENERIC
  else
    #true
    return $OCF_SUCCESS
  fi
}

#
#	Check if the port is valid
#
CheckPort() {
  ocf_is_decimal "$1" && [ $1 -gt 0 ]
}

WAS_validate_all() {
  if [ -x $WASBIN/startServer.sh ]; then
  # $arg should be config file
	if [ ! -f "$arg" ]; then
	    ocf_log err "Configuration file [$arg] does not exist"
	    exit $OCF_ERR_ARGS
	fi

  # $arg should specify a valid port number at the very least
	local WASPorts=`GetWASPorts $arg`
	if [ -z "$WASPorts" ]; then
	    ocf_log err "No port number specified in configuration file [$arg]"
	    exit $OCF_ERR_CONFIGURED
	fi

	local port
	local have_valid_port=false
	for port in $WASPorts; do
	    if CheckPort $port; then
		have_valid_port=true
		break
	    fi
	done
	if [ "false" = "$have_valid_port" ]; then
	    ocf_log err "No valid port number specified in configuration file [$arg]"
	    exit $OCF_ERR_CONFIGURED
	fi
	   
  elif [ -x $WASBIN/startupServer.sh ]; then
  # $arg should be port number
	if CheckPort "$arg"; then 
	    ocf_log err "Port number is required but [$arg] is not valid port number"
	    exit $OCF_ERR_ARGS
	fi
  else
  # Do not know hot to validate_all
	ocf_log warn "Do not know how to validate-all, assuming validation OK"
	return $OCF_SUCCESS
  fi
}
#
#	'main' starts here...
#

if
  ( [ $# -ne 1 ] )
then
  usage
  exit $OCF_ERR_ARGS
fi

#
#	Supply default configuration parameter(s)
#

if
  ( [ -z $OCF_RESKEY_config ] && [ -z $OCF_RESKEY_port ] )
then
  if
    [ -f $DEFAULT ]
  then
    arg=$DEFAULT
  else
    arg=$DEFAULT_WASPORTS
  fi
elif
  [ ! -z $OCF_RESKEY_config ]  
then
  arg=$OCF_RESKEY_config
else
  arg=$OCF_RESKEY_port
fi

if
  [ ! -f $arg ]
then
  case $arg in
    [0-9]*)	;;	# ig