#!/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
}

verbose=false
machine_readable=false
nodespec=""

args=""

# Options and command argument can appear in any order, so when
# getopts thinks it is done, process any non-option arguments and go
# around again.
while [ $# -gt 0 ] ; do
	while getopts "Xvhn:?" opt ; do
		case "$opt" in
		X) machine_readable=true ;;
		v) verbose=true ;;
		n) nodespec="$OPTARG" ;;
		\?|*) usage ;;
		esac
	done
	shift $((OPTIND - 1))

	# Anything left over must be a non-option arg
	if [ $# -gt 0 ] ; then
		args="${args}${args:+ }${1}"
		shift
	fi
done

[ -n "$args" ] || usage
set -- $args

setup_tickles ()
{
    # Make sure tickles file exists.
    tickles_file="${CTDB_TEST_TMP_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 public addresses file

FAKE_CTDB_IP_LAYOUT="$FAKE_CTDB_STATE/ip-layout"

ip_reallocate ()
{
    touch "$FAKE_CTDB_IP_LAYOUT"

    (
	flock 0

	_pa="${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_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 "$_i $_target"
	    else
		echo "$_i $_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 ()
{
    _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="${CTDB_TEST_TMP_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 "$1"
    _t_put "$2" "$3"
}

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

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

ctdb_ptrans ()
{
    _t_setup "$1"

    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 "$1"

    # 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"
}

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

FAKE_CTDB_IFACES_DOWN="${FAKE_CTDB_STATE}/ifaces-down"
rm -f "${FAKE_CTDB_IFACES_DOWN}"/*

ctdb_ifaces()
{
	_f="${CTDB_BASE}/public_addresses"

	if [ ! -f "$_f" ] ; then
		die "Public addresses file \"${_f}\" not found"
	fi

	# Assume -Y.
	echo "|Name|LinkStatus|References|"
	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
}

ctdb_setifacelink ()
{
	_iface="$1"
	_state="$2"

	mkdir -p "$FAKE_CTDB_IFACES_DOWN"

	# Existence of file means CTDB thinks interface is down.
	_f="${FAKE_CTDB_IFACES_DOWN}/${_iface}"

	case "$_state" in
	up)   rm -f "$_f" ;;
	down) touch "$_f" ;;
	*) die "ctdb setifacelink: unsupported interface status ${_state}" ;;
	esac
}

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

ctdb_checktcpport ()
{
	_port="$1"

	for _i in $FAKE_TCP_LISTEN ; do
		if [ "$_port" = "$_i" ] ; then
			exit 98
		fi
	done

	exit 0
}

ctdb_gratarp ()
{
	# Do nothing for now
	:
}

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

cmd="$1"
shift

func="ctdb_${cmd}"

# This could inadvertently run an external function instead of a local
# function.  However, this can only happen if testing a script
# containing a new ctdb command that is not implemented, so this is
# unlikely to do harm.
if type "$func" >/dev/null 2>&1 ; then
	"$func" "$@"
else
	not_implemented "$cmd"
fi
