#!/bin/sh

prog="ctdb"

# Print a message and exit.
die ()
{
    echo "$1" >&2 ; exit ${2:-1}
}

not_implemented_exit_code=1

usage ()
{
    cat >&2 <<EOF
Usage: $prog [-X] cmd

A fake CTDB stub that prints items depending on the variables
FAKE_CTDB_PNN (default 0) depending on command-line options.
EOF
    exit 1
}

not_implemented ()
{
    echo "${prog}: command \"$1\" not implemented in stub" >&2
    exit $not_implemented_exit_code
}

# Don't set $POSIXLY_CORRECT here.
_temp=$(getopt -n "$prog" -o "Xvhn:" -l help -- "$@") || \
    usage

eval set -- "$_temp"

verbose=false
machine_readable=false
nodespec=""

args="$*"

while true ; do
    case "$1" in
	-X) machine_readable=true ; shift ;;
	-v) verbose=true ; shift ;;
	-n) nodespec="$2" ; shift 2 ;;
	--) shift ; break ;;
	-h|--help|*) usage ;; # * shouldn't happen, so this is reasonable.
    esac
done

[ $# -ge 1 ] || usage

setup_tickles ()
{
    # Make sure tickles file exists.
    tickles_file="$EVENTSCRIPTS_TESTS_VAR_DIR/fake-ctdb/tickles"
    mkdir -p $(dirname "$tickles_file")
    touch "$tickles_file"
}

ctdb_gettickles ()
{
    _ip="$1"
    _port="$2"

    setup_tickles

    echo "|source ip|port|destination ip|port|"
    while read _src _dst ; do
	if [ -z "$_ip" -o "$_ip" = "${_dst%:*}" ] ; then
	    if [ -z "$_port" -o "$_port" = "${_dst##*:}" ] ; then
		echo "|${_src%:*}|${_src##*:}|${_dst%:*}|${_dst##*:}|"
	    fi
	fi
    done <"$tickles_file"
}

ctdb_addtickle ()
{
    _src="$1"
    _dst="$2"

    setup_tickles

    if [ -n "$_dst" ] ; then
	echo "${_src} ${_dst}" >>"$tickles_file"
    else
	cat >>"$tickles_file"
    fi
}

ctdb_deltickle ()
{
    _src="$1"
    _dst="$2"

    setup_tickles

    if [ -n "$_dst" ] ; then
	_t=$(grep -F -v "${_src} $${_dst}" "$tickles_file")
    else
	_t=$(cat "$tickles_file")
	while read _src _dst ; do
	    _t=$(echo "$_t" | grep -F -v "${_src} ${_dst}")
	done
    fi
    echo "$_t" >"$tickles_file"
}

parse_nodespec ()
{
    if [ "$nodespec" = "all" ] ; then
	nodes="$(seq 0 $((FAKE_CTDB_NUMNODES - 1)) )"
    elif [ -n "$nodespec" ] ; then
	nodes="$(echo $nodespec | sed -e 's@,@ @g')"
    else
	node=$(ctdb_pnn)
    fi
}

# For testing backward compatibility...
for i in $CTDB_NOT_IMPLEMENTED ; do
    if [ "$i" = "$1" ] ; then
	not_implemented "$i"
    fi
done

ctdb_pnn ()
{
    # Defaults to 0
    echo "${FAKE_CTDB_PNN:-0}"
}

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

FAKE_CTDB_NODE_STATE="$FAKE_CTDB_STATE/node-state"
FAKE_CTDB_NODES_DISABLED="$FAKE_CTDB_NODE_STATE/0x4"

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

# NOTE: all nodes share $CTDB_PUBLIC_ADDRESSES

FAKE_CTDB_IP_LAYOUT="$FAKE_CTDB_STATE/ip-layout"

ip_reallocate ()
{
    touch "$FAKE_CTDB_IP_LAYOUT"

    (
	flock 0

	_pa="${CTDB_PUBLIC_ADDRESSES:-${CTDB_BASE}/public_addresses}"

	if [ ! -s "$FAKE_CTDB_IP_LAYOUT" ] ; then
	    sed -n -e 's@^\([^#][^/]*\)/.*@\1 -1@p' \
		"$_pa" >"$FAKE_CTDB_IP_LAYOUT"
	fi

	_t="${FAKE_CTDB_IP_LAYOUT}.new"

	_flags=""
	for _i in $(seq 0 $((FAKE_CTDB_NUMNODES - 1)) ) ; do
	    if ls "$FAKE_CTDB_STATE/node-state/"*"/$_i" >/dev/null 2>&1 ; then
		# Have non-zero flags
		_this=0
		for _j in "$FAKE_CTDB_STATE/node-state/"*"/$_i" ; do
		    _tf="${_j%/*}" # dirname
		    _f="${_tf##*/}" # basename
		    _this=$(( $_this | $_f ))
		done
	    else
		_this="0"
	    fi
	    _flags="${_flags}${_flags:+,}${_this}"
	done
	CTDB_TEST_LOGLEVEL=NOTICE \
	    "ctdb_takeover_tests" \
	    "ipalloc" "$_flags" <"$FAKE_CTDB_IP_LAYOUT" |
	    sort >"$_t"
	mv "$_t" "$FAKE_CTDB_IP_LAYOUT"
    ) <"$FAKE_CTDB_IP_LAYOUT"
}

ctdb_ip ()
{
    # If nobody has done any IP-fu then generate a layout.
    [ -f "$FAKE_CTDB_IP_LAYOUT" ] || ip_reallocate

    _mypnn=$(ctdb_pnn)

    if $machine_readable ; then
	if $verbose ; then
	    echo "|Public IP|Node|ActiveInterface|AvailableInterfaces|ConfiguredInterfaces|"
	else
	    echo "|Public IP|Node|"
	fi
    else
	echo "Public IPs on node ${_mypnn}"
    fi

    # Join public addresses file with $FAKE_CTDB_IP_LAYOUT, and
    # process output line by line...
    _pa="${CTDB_PUBLIC_ADDRESSES:-${CTDB_BASE}/public_addresses}"
    sed -e 's@/@ @' "$_pa" | sort | join - "$FAKE_CTDB_IP_LAYOUT" |
    while read _ip _bit _ifaces _pnn ; do
	if $verbose ; then
	    # If more than 1 interface, assume all addresses are on the 1st.
	    _first_iface="${_ifaces%%,*}"
	    # Only show interface if address is on this node.
	    _my_iface=""
	    if [ "$_pnn" = "$_mypnn" ]; then
		_my_iface="$_first_iface"
	    fi
	    if $machine_readable ; then
		echo "|${_ip}|${_pnn}|${_my_iface}|${_first_iface}|${_ifaces}|"
	    else
		echo "${_ip} node[${_pnn}] active[${_my_iface}] available[${_first_iface}] configured[[${_ifaces}]"
	    fi
	else
	    if $machine_readable ; then
		echo "|${_ip}|${_pnn}|"
	    else
		echo "${_ip} ${_pnn}"
	    fi
	fi
    done
}

ctdb_moveip ()
{
    _ip="$1"
    _target="$2"

    ip_reallocate  # should be harmless and ensures we have good state

    (
	flock 0

	_t="${FAKE_CTDB_IP_LAYOUT}.new"

	while read _i _pnn ; do
	    if [ "$_ip" = "$_i" ] ; then
		echo "$_ip $_target"
	    else
		echo "$_ip $_pnn"
	    fi
	done | sort >"$_t"
	mv "$_t" "$FAKE_CTDB_IP_LAYOUT"
    ) <"$FAKE_CTDB_IP_LAYOUT"
}

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

ctdb_enable ()
{
    parse_nodespec
    
    for _i in $nodes ; do
	rm -f "${FAKE_CTDB_NODES_DISABLED}/${_i}"
    done

    ip_reallocate
}

ctdb_disable ()
{
    parse_nodespec

    for _i in $nodes ; do
	mkdir -p "$FAKE_CTDB_NODES_DISABLED"
	touch "${FAKE_CTDB_NODES_DISABLED}/${_i}"
    done

    ip_reallocate
}

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

ctdb_shutdown ()
{
    echo "CTDB says BYE!"
}

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

# This is only used by the NAT and LVS gateway code at the moment, so
# use a hack.  Assume that $CTDB_NATGW_NODES or $CTDB_LVS_NODES
# contains all nodes in the cluster (which is what current tests
# assume).  Use the PNN to find the address from this file.  The NAT
# gateway code only used the address, so just mark the node healthy.
ctdb_nodestatus ()
{
    echo '|Node|IP|Disconnected|Banned|Disabled|Unhealthy|Stopped|Inactive|PartiallyOnline|ThisNode|'
    _line=$(( $FAKE_CTDB_PNN + 1 ))
    _ip=$(sed -e "${_line}p" "${CTDB_NATGW_NODES:-${CTDB_LVS_NODES}}")
    echo "|${FAKE_CTDB_PNN}|${_ip}|0|0|0|0|0|0|0|Y|"
}

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

ctdb_setvar ()
{
    shift
    _var="$1"

    for _i in $FAKE_CTDB_TUNABLES_OK ; do
	if [ "$_var" = "$_i" ] ; then
	    return 0
	fi
    done

    for _i in $FAKE_CTDB_TUNABLES_OBSOLETE ; do
	if [ "$_var" = "$_i" ] ; then
	    echo "Setting obsolete tunable variable '${_var}'"
	    return 0
	fi
    done

    echo "Unable to set tunable variable '${_var}'"
    return 1
}

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

_t_setup ()
{
    _t_dir="$EVENTSCRIPTS_TESTS_VAR_DIR/fake-ctdb/fake-tdb/$1"
    mkdir -p "$_t_dir"
}

_t_put ()
{
    echo "$2" >"${_t_dir}/$1"
}

_t_get ()
{
    cat "${_t_dir}/$1"
}

_t_del ()
{
    rm -f "${_t_dir}/$1"
}

ctdb_pstore ()
{
    _t_setup "$2"
    _t_put "$3" "$4"
}

ctdb_pdelete ()
{
    _t_setup "$2"
    _t_del "$3"
}

ctdb_pfetch ()
{
    _t_setup "$2"
    _t_get "$3" >"$4" 2>/dev/null
}

ctdb_ptrans ()
{
    _t_setup "$2"

    while IFS="" read _line ; do
	_k=$(echo "$_line" | sed -n -e 's@^"\([^"]*\)" "[^"]*"$@\1@p')
	_v=$(echo "$_line" | sed -e 's@^"[^"]*" "\([^"]*\)"$@\1@')
	[ -n "$_k" ] || die "ctdb ptrans: bad line \"${line}\""
	if [ -n "$_v" ] ; then
	    _t_put "$_k" "$_v"
	else
	    _t_del "$_k"
	fi
    done
}

ctdb_catdb ()
{
    _t_setup "$2"

    # This will break on keys with spaces but we don't have any of
    # those yet.
    _count=0
    for _i in "${_t_dir}/"* ; do
	[ -r "$_i" ] || continue
	_k="${_i##*/}" # basename
	_v=$(_t_get "$_k")
	_kn=$(echo -n "$_k" | wc -c)
	_vn=$(echo -n "$_v" | wc -c)
	cat <<EOF
key(${_kn}) = "${_k}"
dmaster: 0
rsn: 1
data(${_vn}) = "${_v}"

EOF
	_count=$(($_count + 1))
    done

    echo "Dumped ${_count} records"
}

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

case "$1" in
    gettickles) shift ; ctdb_gettickles "$@" ;;
    addtickle) shift ; ctdb_addtickle "$@" ;;
    deltickle) shift ; ctdb_deltickle "$@" ;;
    pstore)  ctdb_pstore  "$@" ;;
    pdelete) ctdb_pdelete "$@" ;;
    pfetch)  ctdb_pfetch  "$@" ;;
    ptrans)  ctdb_ptrans  "$@" ;;
    catdb)   ctdb_catdb   "$@" ;;
    ifaces)
	# Assume -Y.
	echo "|Name|LinkStatus|References|"
	_f="${CTDB_PUBLIC_ADDRESSES:-${CTDB_BASE}/public_addresses}"
	if [ -r "$_f" ] ; then
	    while read _ip _iface ; do
		case "_$ip" in
		    \#*) : ;;
		    *)
			_status=1
			# For now assume _iface contains only 1.
			if [ -f "{FAKE_CTDB_IFACES_DOWN}/${_iface}" ] ; then
			    _status=0
			fi
			# Nobody looks at references
			echo "|${_iface}|${_status}|0|"
		esac
	    done <"$_f" |
	    sort -u
	fi
	;;
    setifacelink)
	# Existence of file means CTDB thinks interface is down.
	_f="${FAKE_CTDB_IFACES_DOWN}/$2"
	case "$3" in
	    up)   rm -f "$_f" ;;
	    down) touch "$_f" ;;
	    *)
		echo "ctdb setifacelink: unsupported interface status $3"
		exit 1
	esac
	;;
    checktcpport)
	for _i in $FAKE_TCP_LISTEN ; do
	    if [ "$2" = "${_i##*:}" ] ; then
		exit 98
	    fi
	done

	exit 0
	;;
    scriptstatus)
	$machine_readable || not_implemented "$1, without -X"
	[ "$2" != "all" ] || not_implemented "scriptstatus all"
	# For now just assume everything is good.
	echo "|Type|Name|Code|Status|Start|End|Error Output...|"
	for _i in "$CTDB_BASE/events.d/"*.* ; do
	    _d1=$(date '+%s.%N')
	    _b="${_i##*/}" # basename

	    _f="$FAKE_CTDB_SCRIPTSTATUS/$_b"
	    if [ -r "$_f" ] ; then
		read _code _status _err_out <"$_f"
	    else
		_code="0"
		_status="OK"
		if [ ! -x "$_i" ] ; then
		    _status="DISABLED"
		    _code="-8"
		fi
		_err_out=""
	    fi
	    _d2=$(date '+%s.%N')
	    echo "|${2:-monitor}|${_b}|${_code}|${_status}|${_d1}|${_d2}|${_err_out}|"
	done
	;;
    gratarp) : ;;  # Do nothing for now
    ip)            ctdb_ip "$@" ;;
    pnn|xpnn)      ctdb_pnn ;;
    enable)        ctdb_enable "$@";;
    disable)       ctdb_disable "$@";;
    moveip)        ctdb_moveip "$@";;
    shutdown)      ctdb_shutdown "$@";;
    setvar)	   ctdb_setvar "$@" ;;
    nodestatus)	   ctdb_nodestatus "$@" ;;
    *) not_implemented "$1" ;;
esac
