#!/bin/bash
#
# Copyright (C) 2009 Red Hat, Inc.
# Copyright (c) 2000-2006 Silicon Graphics, Inc.  All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

# we need common.config
if [ "$iam" != "check" ]
then
    if ! . ./common.config
        then
        echo "$iam: failed to source common.config"
        exit 1
    fi
fi

# make sure we have a standard umask
umask 022

# check if run as root
#
_need_to_be_root()
{
    local id=`id | $SED_PROG -e 's/(.*//' -e 's/.*=//'`
    if [ "$id" -ne 0 ]
    then
	_notrun "you need to be root (not uid=$id) to run this test"
    fi
}

# To remove directory successfully always, we have to rename it first
# so that new files are not created in the directory while we remove it.
_safe_remove()
{
    local dir=$1
    mv ${dir} ${dir}.tmp
    rm -rf ${dir}.tmp
}

# Do a command, log it to $seq.full, optionally test return status
# and die if command fails. If called with one argument _do executes the
# command, logs it, and returns its exit status. With two arguments _do
# first prints the message passed in the first argument, and then "done"
# or "fail" depending on the return status of the command passed in the
# second argument. If the command fails and the variable _do_die_on_error
# is set to "always" or the two argument form is used and _do_die_on_error
# is set to "message_only" _do will print an error message to
# $seq.out and exit.

_do()
{
    if [ $# -eq 1 ]; then
	_cmd=$1
    elif [ $# -eq 2 ]; then
	_note=$1
	_cmd=$2
	echo -n "$_note... "
    else
	echo "Usage: _do [note] cmd" 1>&2
	status=1; exit
    fi

    (eval "echo '---' \"$_cmd\"") >>$here/$seq.full
    (eval "$_cmd") >$tmp._out 2>&1; ret=$?
    cat $tmp._out >>$here/$seq.full
    if [ $# -eq 2 ]; then
	if [ $ret -eq 0 ]; then
	    echo "done"
	else
	    echo "fail"
	fi
    fi
    if [ $ret -ne 0  ] \
	&& [ "$_do_die_on_error" = "always" \
	    -o \( $# -eq 2 -a "$_do_die_on_error" = "message_only" \) ]
    then
	[ $# -ne 2 ] && echo
	eval "echo \"$_cmd\" failed \(returned $ret\): see $seq.full"
	status=1; exit
    fi

    return $ret
}

# bail out, setting up .notrun file
#
_notrun()
{
    echo "$*" >$seq.notrun
    echo "$seq not run: $*"
    status=0
    exit
}

# just plain bail out
#
_fail()
{
    echo "$*" | tee -a $here/$seq.full
    echo "(see $seq.full for details)"
    status=1
    exit 1
}

# this test requires that a specified command (executable) exists
#
_require_command()
{
    [ -n "`which $1`" ] || _notrun "$1 utility required, skipped this test"
    [ -x "`which $1`" ] || _notrun "$1 utility required, skipped this test"
}

_full_platform_details()
{
    os=`uname -s`
    host=`hostname -s`
    kernel=`uname -r`
    platform=`uname -m`
    echo "$os/$platform $host $kernel"
}

_die()
{
    echo $@
    exit 1
}

_cleanup()
{
    local i
    _kill_all_dogs
    _kill_all_sheeps

    _cleanup_devices

    mkdir -p $STORE
    for i in $STORE/?; do
        rm -rf $i
    done
    for i in $STORE/??; do
        rm -rf $i
    done

    if [[ "$DRIVER" == zoo* ]];then
        ../../tools/zk_control remove "/sheepdog/member"
        if [ $? -ne 0 ]; then
            _die "failed to remove members"
        fi
    fi
}

_count_sheep_processes()
{
    pgrep -f "$SHEEP_PROG $STORE/" -l | awk '{ $1=""; print }' | sort | uniq | wc -l
}

# Wait for the specified sheep to be stopped.  If no argument is
# passed, this waits for all the sheeps to be stopped
_wait_for_sheep_stop()
{
    local cnt
    for cnt in `seq 60`; do # wait at most 60 seconds
	# check sheep process
	if [ "$1" == "" ]; then
	    pgrep -f "$SHEEP_PROG $WD" > /dev/null
	else
	    pgrep -f "$SHEEP_PROG $STORE/$1 " > /dev/null
	fi
	if [ $? == 0 ]; then
	    sleep 1
	    continue
	fi

	# make sure that the sheep port is available
	if [ "$1" == "" ]; then
	    netstat -an | grep tcp | grep :70[0-9][0-9] | \
		grep LISTEN > /dev/null 2>&1
	else
	    netstat -an | grep tcp | grep :$((7000 + $1)) | \
		grep LISTEN > /dev/null 2>&1
	fi
	if [ $? != 0 ]; then
	    if [[ "$DRIVER" == zoo* ]];then
		if [ "$1" == "" ]; then
		    ../../tools/zk_control remove "/sheepdog/member"
		else
		    local path="/sheepdog/member/IPv4 ip:127.0.0.1 port:$((7000+$1))"
		    ../../tools/zk_control remove "$path"
		fi
		if [ $? -ne 0 ]; then
		    echo "failed to remove members"
		    break;
		fi
	    fi
	    return
	fi
	sleep 1
    done

    _die "sheep $1 is expected to be stopped, but not."
}

# wait for all sheep to join completely
_wait_for_sheep()
{
    local PORT=7000
    if [ $2 ]; then
        PORT=$((7000 + $2))
    fi
    while true; do
        if [ $(_count_sheep_processes) != $1 ]; then
            _die "should have $1, but have $(_count_sheep_processes) sheep"
        fi

        node_list="$($DOG node list -p $PORT 2> /dev/null)"

        if [ $? != 0 ]; then
            # sheep is not ready yet
            sleep 1
            continue
        fi

        nr_sheep="$(echo "$node_list" | wc -l)"
        nr_sheep=$(($nr_sheep-1))

        if [ ${nr_sheep} -eq $1 ]; then
            break
        fi
        sleep 1
    done
}

_valgrind_sheep()
{
    local dir=$(echo $1 | sed -e s/,.*//)
    mkdir ${dir} > /dev/null 2>&1

    # Dump a core file and stop the script on the first valgrind error.
    local db_cmd="gdb -nw --ex \"generate-core-file ${dir}/core\" %f %p > /dev/null; \
            echo \"core dumped (${dir}/core)\"; kill \$PPID"
    local opts="--db-attach=yes --db-command='${db_cmd}' \
            --suppressions=valgrind.supp ${VALGRIND_OPTIONS}"

    rm -f ${dir}/lock
    sh -c "echo y | valgrind ${opts} $SHEEP_PROG $* -l dst=stdout 2>> ${dir}/sheep.log &"

    # wait for sheep to start up
    while true; do
	pgrep -f "$SHEEP_PROG $1" > /dev/null
	if [ $? != 0 ]; then
	    # failed to start sheep $1
	    break
	fi
        if [ -a ${dir}/lock ]; then
            break
        fi
    done
}

_valgrind_dog()
{
    local logfile=$(mktemp)
    valgrind --log-file=$logfile --error-exitcode=99 ${VALGRIND_OPTIONS} $DOG_PROG $*
    local ret=$?
    if [ $ret == 99 ]; then
        cat $logfile 1>&2
    fi
    rm $logfile
    return $ret
}

_start_sheep()
{
    # ensure that sheep is not running
    local running=true
    local cnt
    for cnt in `seq 1 10`; do  # wait at most 10 seconds
        pgrep -f "$SHEEP_PROG $STORE/$1 " > /dev/null
        if [ $? != 0 ]; then
            running=false
            break
        fi
        sleep 1
    done

    if $running; then
        _die "sheep $1 is still running"
    fi

    if $MD && [ -z "$MD_STORE" ]; then
	MD_STORE=",$STORE/$1/d0,$STORE/$1/d1,$STORE/$1/d2"
    fi

    $SHEEP $STORE/$1$MD_STORE -z $1 -p $((7000+$1)) -c $DRIVER $SHEEP_OPTIONS $2

    if [ $? != 0 ]; then
        _die "cannot start sheep $1"
    fi
    unset MD_STORE
}

_kill_all_dogs()
{
    pkill -f "$DOG_PROG (cluster|vdi|node|debug)"

    while [ $? == 0 ]; do
        pgrep -f "$DOG_PROG (cluster|vdi|node|debug)" > /dev/null
    done
}

_kill_all_sheeps()
{
    pkill -f "$SHEEP_PROG $WD"

    _wait_for_sheep_stop
}

_kill_sheep()
{
    pkill -f "$SHEEP_PROG $STORE/$1 "

    if [ $? != 0 ]; then
        _die "cannot kill sheep $1"
    fi

    _wait_for_sheep_stop $1
}

_kill_all_dogs_force()
{
    pkill -9 -f "$DOG_PROG (cluster|vdi|node|debug)"

    while [ $? == 0 ]; do
        pgrep -f "$DOG_PROG (cluster|vdi|node|debug)" > /dev/null
    done
}

_kill_all_sheeps_force()
{
    pkill -9 -f "$SHEEP_PROG $WD"

    _wait_for_sheep_stop
}

_kill_sheep_force()
{
    pkill -9 -f "$SHEEP_PROG $STORE/$1 "

    if [ $? != 0 ]; then
        _die "cannot kill sheep $1"
    fi

    _wait_for_sheep_stop $1
}

_wait_for_sheep_recovery()
{
    while true; do
        sleep 2
        recovery_info="$($DOG node recovery -p $((7000+$1)))"

        if [ $? != 0 ]; then
            _die "failed to get recovery info"
        fi

        if [ $(echo "$recovery_info" | wc -l) -eq 2 ]; then
            break
        fi

    done
}

# This differ from kill(2) that we don't get the notification that connection is
# closed and most likely leave us half-open connections
_simulate_machine_down()
{
	# Drop the packet in/out this port
	iptables -A INPUT -p tcp --sport $((7000+$1)) -j DROP
	iptables -A INPUT -p tcp --dport $((7000+$1)) -j DROP

	sleep 3
	# Trigger the confchg because cluster driver doesn't know we'er offline
	_kill_sheep $1
	_wait_for_sheep_recovery 0
}

# Cleanup the iptables rules used to simulate machine down
_cleanup_machine_simulation()
{
	iptables -D INPUT -p tcp --sport $((7000+$1)) -j DROP
	iptables -D INPUT -p tcp --dport $((7000+$1)) -j DROP
}

_make_device()
{
    local idx=$1; shift
    local size=$1; shift
    local args=$@

    dd if=/dev/zero of=$STORE/$idx.img seek=$(($size - 1)) bs=1 count=1 > $seq.full 2>&1
    if [ $? != 0 ]; then
        _die "failed to create $STORE/$idx.img"
    fi

    mkops="-t ext4 -q -F -O ^has_journal"
    mntops="-o user_xattr"

    mkfs $mkops $STORE/$idx.img > $seq.full 2>&1
    mkdir $STORE/$idx
    mount -o loop $mntops $args $STORE/$idx.img $STORE/$idx
}

_cleanup_devices()
{
    local d
    for d in `mount | grep $WD | sort -r | uniq | awk '{print $3}'`; do
        umount -l $d
        rm -f $d.img
    done

    for d in `find /dev/ -name "loop*"`; do
        losetup -d $d &> /dev/null
    done
}

_list_data_obj()
{
    find $STORE/$1 | grep -E /00[0-9a-f]\{14\} | grep -v .stale
}

_list_vdi_obj()
{
    find $STORE/$1 | grep -E /8[0-9a-f]\{15\} | grep -v .stale
}

_list_vmstate_obj()
{
    find $STORE/$1 | grep -E /4[0-9a-f]\{15\} | grep -v .stale
}

_list_attr_obj()
{
    find $STORE/$1 | grep -E /2[0-9a-f]\{15\} | grep -v .stale
}

_list_ledger_obj()
{
    find $STORE/$1 | grep -E /08[0-9a-f]\{14\} | grep -v .stale
}

_list_stale_obj()
{
    find $STORE/$1 | grep -E /[0-9a-f]\{16\} | grep .stale
}

_stat_store()
{
    local pattern=$1
    local idx
    echo -e "STORE\tDATA\tVDI\tVMSTATE\tATTR\tLEDGER\tSTALE"
    for idx in `ls -d $STORE/$pattern | sed s#$STORE/##g`; do
	local data_obj=$(_list_data_obj $idx | wc -l)
	local vdi_obj=$(_list_vdi_obj $idx | wc -l)
	local vmstate_obj=$(_list_vmstate_obj $idx | wc -l)
	local attr_obj=$(_list_attr_obj $idx | wc -l)
	local ledger_obj=$(_list_ledger_obj $idx | wc -l)
	local stale_obj=$(_list_stale_obj $idx | wc -l)
	echo -e "$idx\t$data_obj\t$vdi_obj\t$vmstate_obj\t$attr_obj\t$ledger_obj\t$stale_obj"
    done
}

_node_info()
{
    _stat_store '?'
}

_md_info()
{
    _stat_store '?/d?'
}

_cluster_format()
{
    local args=$*
    local port_opt=$(echo $args | grep -oE '\-*p[^ ]* 7...')

    $DOG cluster format $args
    if [ $? != 0 ]; then
	_die "failed to format cluster"
    fi

    # wait for all the sheeps to be running
    local ports=$($DOG node list -r $port_opt | perl -ne 'print "$1\n" if /:(\d+)/')
    local port
    for port in $ports; do
	local cnt
	for cnt in `seq 10`; do # wait at most 10 seconds
	    $DOG cluster info -p $port | grep -E running > /dev/null
	    if [ $? == 0 ]; then
		continue 2
	    fi
	    sleep 1
	done
	_die "sheepdog was not able to start"
    done
}

_random()
{
	openssl enc -rc4 -pass pass:"$(date)" < /dev/zero 2>/dev/null
}

_one()
{
    yes $'\xFF' | tr -d "\n"
}

_kill_zk_session()
{
	local path="/sheepdog/member/IPv4 ip:127.0.0.1 port:$((7000+$1))"
	if [[ "$DRIVER" == zoo* ]]; then
		../../tools/zk_control kill "$path"
		if [ $? -ne 0 ]; then
			_die "failed to kill session"
		fi
	else
		_kill_sheep $1
		_start_sheep $1
	fi
}

_vdi_list()
{
	local args=$*
	$DOG vdi list $args | _filter_short_date
}

_vdi_create()
{
	if $EC; then
		$DOG vdi create -c 4:2 $*
	else
		$DOG vdi create $*
	fi
}

# make sure this script returns success
/bin/true
