#!/bin/bash
#
#
#  An OCF RA for conntrackd in namespace
#  http://conntrack-tools.netfilter.org/
#
# Originaly created by Dominik Klein
#
# Patched by Mirantis
#
# 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_ns_default=vrouter
OCF_RESKEY_bridge_default=br-mgmt
OCF_RESKEY_host_interface_default=mgmt-conntrd
OCF_RESKEY_ns_interface_default=conntrd

: ${HA_LOGTAG="ocf-ns_conntrackd"}
: ${HA_LOGFACILITY="daemon"}
# For users of versions prior to 1.2:
# Map renamed parameter "conntrackd" to "binary" if in use
: ${OCF_RESKEY_binary=${OCF_RESKEY_conntrackd-${OCF_RESKEY_binary_default}}}
: ${OCF_RESKEY_config=${OCF_RESKEY_config_default}}
: ${OCF_RESKEY_ns=${OCF_RESKEY_ns_default}}
: ${OCF_RESKEY_bridge=${OCF_RESKEY_bridge_default}}
: ${OCF_RESKEY_host_interface=${OCF_RESKEY_host_interface_default}}
: ${OCF_RESKEY_ns_interface=${OCF_RESKEY_ns_interface_default}}

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

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

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

<parameters>
<parameter name="binary">
<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>

<parameter name="ns">
<longdesc lang="en">Network namespace in which ns_interface
will be configured.
</longdesc>
<shortdesc lang="en">Network namespace.</shortdesc>
<content type="string" default="$OCF_RESKEY_ns_default"/>
</parameter>

<parameter name="bridge">
<longdesc lang="en">Bridge to which host_interface will be added.</longdesc>
<shortdesc lang="en">Bridge to which host_interface will be added.</shortdesc>
<content type="string" default="$OCF_RESKEY_bridge_default"/>
</parameter>

<parameter name="host_interface">
<longdesc lang="en">Host interface in veth pair.</longdesc>
<shortdesc lang="en">Host interface in veth pair.</shortdesc>
<content type="string" default="$OCF_RESKEY_host_interface_default"/>
</parameter>

<parameter name="ns_interface">
<longdesc lang="en">Network namespace interface in veth pair.
This is the interface used to send synchronization messges.
</longdesc>
<shortdesc lang="en">Network namespace interface in veth pair</shortdesc>
<content type="string" default="$OCF_RESKEY_ns_interface_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_exit_reason "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
}

add_to_bridge() {
  brctl show $OCF_RESKEY_bridge | grep $OCF_RESKEY_host_interface
  if [[ $rc != 0 ]] ; then
    ocf_run brctl addif $OCF_RESKEY_bridge $OCF_RESKEY_host_interface
    ocf_run ifconfig $OCF_RESKEY_host_interface 0.0.0.0
  fi
}

get_veth_pair() {
  ip netns list | grep "$OCF_RESKEY_ns"

  [[ $? != 0 ]] && ip netns add $OCF_RESKEY_ns && ip netns exec $OCF_RESKEY_ns ip link set up dev lo

  ocf_run ip netns exec $OCF_RESKEY_ns ip link show $OCF_RESKEY_ns_interface 2>/dev/null
  rc=$?

  # create pair (tail's can't be alone) and attach tail to the net.namespace
  if [[ $rc != 0 ]] ; then
    local last_octet=$(ifconfig $OCF_RESKEY_bridge 2>/dev/null|awk '/inet addr:/ {print $2}'|sed 's/addr://' | awk -F. '{print $NF}')

    ocf_run ip link add $OCF_RESKEY_host_interface type veth peer name $OCF_RESKEY_ns_interface
    ocf_run ip link set dev $OCF_RESKEY_ns_interface netns $OCF_RESKEY_ns
    ocf_run ip netns exec $OCF_RESKEY_ns ip link set up dev $OCF_RESKEY_ns_interface
    ocf_run ip link set up dev $OCF_RESKEY_host_interface
    ocf_run ip netns exec $OCF_RESKEY_ns ip addr add 240.1.0.${last_octet}/24 dev $OCF_RESKEY_ns_interface
  fi

  add_to_bridge

  return 0
}

conntrackd_monitor() {

  get_veth_pair

  rc=$OCF_NOT_RUNNING
  # It does not write a PID file, so check the socket exists after
  # extracting its path from the configuration file
  local conntrack_socket=$(awk '/^[ \t]*UNIX[ \t]*{/,/^[ \t]*}/ { if ($1 == "Path") { print $2 } }' $OCF_RESKEY_config)
  [ -S "$conntrack_socket" ] && rc=$OCF_SUCCESS
  if [ "$rc" -eq "$OCF_SUCCESS" ]; then
    # conntrackd is running
    # now see if it acceppts queries
    if ! ip netns exec $OCF_RESKEY_ns $OCF_RESKEY_binary -C $OCF_RESKEY_config -s > /dev/null 2>&1; then
      rc=$OCF_ERR_GENERIC
      ocf_exit_reason "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)
      conntrackd_set_master_score $slave_score
      # -n = request resync from the others
      if ! ip netns exec $OCF_RESKEY_ns $OCF_RESKEY_binary -C $OCF_RESKEY_config -n; then
        ocf_exit_reason "$OCF_RESKEY_binary -C $OCF_RESKEY_config -n failed during start."
        rc=$OCF_ERR_GENERIC
      else
        rc=$OCF_SUCCESS
      fi
      break
      ;;
    $OCF_NOT_RUNNING)
      ocf_log info "Starting conntrackd"
      ip netns exec $OCF_RESKEY_ns $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_exit_reason "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_ERR_GENERIC)
      ocf_log info "Stopping conntrackd"
                        ip netns exec $OCF_RESKEY_ns $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_exit_reason "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

  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 ! ip netns exec $OCF_RESKEY_ns $OCF_RESKEY_binary -C $OCF_RESKEY_config -$parm; then
        ocf_exit_reason "$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 ! ip netns exec $OCF_RESKEY_ns $OCF_RESKEY_binary -C $OCF_RESKEY_config -$parm; then
                          ocf_exit_reason "$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"
      ip netns exec $OCF_RESKEY_ns $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"
      ip netns exec $OCF_RESKEY_ns $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//:[0-9]*}.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)
