#!/bin/bash -e

SELF=$(cd $(dirname $0); pwd -P)/$(basename $0)
# Copyright (C) 2007, 2008 Codership Oy <info@codership.com>
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

# WHAT IT IS:
# This script starts mysqld from Galera test distribution. It may be adapted
# to be run from /etc/init.d directory for automatic server startup.
#
# USAGE:
# GCS_ADDRESS=<address> mysqld_galera start|stop|restart|check|create
#
# By default empty backend is "dummy".
#
# 'check' command takes options: [database [table_to_ignore] [table_to_ignore]...]
#
# USEFUL ENVIRONMENT VARIABLES:
#
# MY_CNF         - specifies the configuration file
# GCS_ADDRESS    - overrides setting in my.cnf
# MYSQL_PORT     - port to listen for client connections (default: 3306)
# LIBGALERA      - location of the galera shared library file
#
# Convention: mysql pid file is stored in mysql data dir under name 'mysql.pid'
#
# Normally only the following parameters need to be changed:

# Where mysql data directory is located (by default - inside mysql installation)
# MYSQL_DATA_DIR

# Where mysql installation is located (by default determined from this script location)
MYSQL_BASE_DIR=${MYSQL_BASE_DIR:-"$(dirname $SELF)/mysql"}
GALERA_BASE_DIR=${GALERA_BASE_DIR:-"$(dirname $SELF)/galera"}

# MySQL configuration file
MY_CNF=${MYSQL_CNF:-"$MYSQL_BASE_DIR/etc/my.cnf"}
if test -s "$MY_CNF"
then
    DEFAULTS_OPTION=" --defaults-file=$MY_CNF "
    my_cnf_datadir=$(grep ^datadir $MY_CNF | sed s/[^/]*//)
else
    DEFAULTS_OPTION=" --no-defaults "
fi

# If it was not given explicitely, use the one from my.cnf
MYSQL_DATA_DIR=${MYSQL_DATA_DIR:-"$my_cnf_datadir"}
# If it was not found in my.cnf, use distribution default
MYSQL_DATA_DIR=${MYSQL_DATA_DIR:-"$MYSQL_BASE_DIR/var"}

MYSQLD_USER=$(whoami)

#=======================================================================
##
## Tweak the following if needed
##
# use mysqld server directly, better not have automatic restarting
MYSQLD="$MYSQL_BASE_DIR/sbin/mysqld"
[ ! -x "$MYSQLD" -a -x "${MYSQLD}-debug" ] && MYSQLD=${MYSQLD}-debug
MYSQLADMIN="$MYSQL_BASE_DIR/bin/mysqladmin"
# Port, socket and pid files
MYSQL_PORT=${MYSQL_PORT:-3306}
MYSQL_SOCKET=${MYSQL_SOCKET:-"$MYSQL_DATA_DIR/mysqld.sock"}
MYSQL_PID=${MYSQL_PID:-"$MYSQL_DATA_DIR/mysqld.pid"}
# Shutdown wait timeout.
MYSQL_SHUTDOWN_WAIT=60
#============= Nothing servicable below ================================

# User to run as if started under superuser
#if test "$MYSQLD_USER" = "root"
#then
#    MYSQLD_USER=mysql
#fi
ROOT_USER=${ROOT_USER:-"-uroot"}
ROOT_PSWD=${ROOT_PSWD:-"-prootpass"}

# Mandatory stuff
INNODB_OPTS=" --default-storage-engine=InnoDB "

# --debug terribly affects performance
#DEBUG_OPTS=" --debug "
#DEBUG_OPTS=" --debug=d,galera,wsdb:t:i:o,$MYSQL_DATA_DIR/mysqld.trc"

SKIP_RECOVERY=0
START_POSITION=""

err_log="$MYSQL_DATA_DIR/mysqld.err"
mysql_log="$MYSQL_DATA_DIR/mysqld.log"


usage() { cat - << EOF

usage: mysql-galera [options] command

Options:
    -c|--command      : command to execute (status)
    --data_dir        : location for mysql data directories
    --db              : name of database to check
    -d|--debug        : enable debug output
    --donor           : desired state transfer donor
    -g|--gcs_address  : address of gcs server (spread://locahost:4803)
    --gdb             : run under gdb
    -i|--ignore_table : table to ignore in checking
    -l|--log          : enable mysql query log
    --mysql-opt       : an option to the server to follow
    -p|--password     : mysql root user password
    --plugin          : use innodb plugin
    --slave_threads   : number of consurrent ws appliers (1)
    -u|--user         : mysql root user
    --valgrind        : run under valgrind
    --ws_level        : RBR (default) or SQL
    -P|--port         : port for MySQL client connections
    -S|--socket       : location of mysqld socket
    --skip-recovery   : skip recovery phase
    --start-position  : start position passed to server

Commands:
check                 : check cosistency of database given with --db option
start                 : start servers
stop                  : stop servers
restart               : stop and start servers
status                : show running servers

EOF
}

# Checks if a process with a given PID is still running
find_pid()
{
    ps ax | grep mysqld | grep -w ^\ *$1 > /dev/null
}

get_status()
{
    local stat_var=$1
    set -o pipefail
    mysql $ROOT_USER $ROOT_PSWD --socket $MYSQL_SOCKET  --skip-column-names \
          --reconnect -Be "SET wsrep_on=0; SHOW STATUS LIKE '$stat_var'" |  \
          cut -f 2
}

# Loop until wsrep_ready == ON
wait_for_wsrep_ready()
{
    echo -n "Waiting for wsrep_ready"

    local pid=$1
    local ret=1
    local ready

    while find_pid $pid
    do
        echo -n "."
        sleep 1
    # mysql connection can be interrupted by SST code,
    # don't fail on it rigth away
        ready=$(get_status "wsrep_ready") && \
        if [ $ret ] && [ "$ready" == "ON" ]; then ret=0; break; fi
    done

    if [ $ret ]
    then
        echo " Done"
    else
        echo " Failed"
    fi

    return $ret
}

check_gcs_address()
{
    local addr="$1"

    if [ -n "$addr" ]
    then
        case "$addr" in
        "gcomm://"*)
            # we could be checking address here, but generally it does not
            # have to exist yet.
            ;;
        "dummy://"*)
            ;;
        *)
            echo "Cluster address should start with either 'gcomm://' or 'dummy://'. Server not started." >&2
            exit 1
        esac
    fi
}

wsrep_start_position_opt=""

# Run mysqld with --wsrep-recover and parse recovered position from log.
# Position will be stored in wsrep_start_position_opt global.
wsrep_recovery() {
  cmd="$@"
  wr_logfile=$(mktemp -t wsrep.XXXXXXXXXX)
  echo "WSREP: Running position recovery"
  set +e
  [ "$OS" == "Darwin" ] && export LD_LIBRARY_PATH
  $cmd --log_error=$wr_logfile --wsrep-recover
  [ "$OS" == "Darwin" ] && export -n LD_LIBRARY_PATH
  rp=$(grep "WSREP: Recovered position:" $wr_logfile)
  if [ -z "$rp" ]; then
    skipped=$(grep WSREP $wr_logfile | grep "skipping position recovery")
    if [ -z "$skipped" ]; then
      echo "WSREP: Failed to recover position: " \
          `cat $wr_logfile`;
    else
      echo "WSREP: Position recovery skipped"
    fi
  else
    start_pos=$(echo $rp | sed 's/.*WSREP\:\ Recovered\ position://' \
        | sed 's/^[ \t]*//')
    wsrep_start_position_opt="--wsrep_start_position=$start_pos"
    echo "WSREP: Recovered position $start_pos"
  fi
  set -e
  rm $wr_logfile
}


galera_start()
{
    local failed

    if ! test -x $MYSQLD
    then
        echo "$MYSQLD executable not found"
        exit -1
    fi

    if test -f $MYSQL_PID
    then
        echo "Found existing '$MYSQL_PID'. Please run '$0 stop'"
        exit -1;
    fi

#    if [ -n "$MYSQL_LOG" ]
#    then
#        LOGGING_OPTS=" --general_log=1 --log_output=FILE "
#    fi

    if [ -n "$WS_LEVEL" ]
    then
        RBR_OPTS=" --binlog_format=$WS_LEVEL "
    fi

    WSREP=${WSREP:-"$GALERA_BASE_DIR/lib/libgalera_smm.so"}
    if test -f $WSREP || test $WSREP == "none"
    then
        WSREP_OPTS="--wsrep_provider=$WSREP"
    else
        echo "WSREP driver '$WSREP' not found"
        exit -1
    fi

    if test -n "$GCS_ADDRESS"
    then
        check_gcs_address $GCS_ADDRESS
        WSREP_OPTS="$WSREP_OPTS --wsrep_cluster_address=$GCS_ADDRESS"
    fi

    if test -n "$WSREP_SST_DONOR"
    then
        WSREP_OPTS="$WSREP_OPTS --wsrep_sst_donor=$WSREP_SST_DONOR"
    fi

    if test -n "$SLAVE_THREADS"
    then
        WSREP_OPTS="$WSREP_OPTS --wsrep_slave_threads=$SLAVE_THREADS"
    fi

    if test -f "$MYSQL_DATA_DIR/core"
    then
        mv "$MYSQL_DATA_DIR/core" "$MYSQL_DATA_DIR/core.old"
    fi

    echo -n "Starting mysqld instance with data dir $MYSQL_DATA_DIR and listening at port $MYSQL_PORT and socket $MYSQL_SOCKET..."
    ulimit -n 4096 # This is normally allowed for non-privileged users

    if test $SKIP_RECOVERY = 0
    then
        wsrep_recovery $MYSQLD \
            $DEFAULTS_OPTION \
            --user="$MYSQLD_USER" \
            --basedir="$MYSQL_BASE_DIR" \
            --datadir="$MYSQL_DATA_DIR" \
            --plugin-dir=lib/mysql/plugin \
            --pid-file="$MYSQL_PID" \
            --port=$MYSQL_PORT \
            --socket=$MYSQL_SOCKET \
            --skip-external-locking \
            --log_error=$err_log \
            $MYSQLD_OPTS \
            $INNODB_OPTS \
            $WSREP_OPTS \
            $DEBUG_OPTS \
            $LOGGING_OPTS \
            $RBR_OPTS \
            $PLUGIN_OPTS
    else
        echo "skipping recovery"
        if test -n "$START_POSITION"
        then
            wsrep_start_position_opt="--wsrep-start-position=$START_POSITION"
        fi
    fi

    [ "$OS" == "Darwin" ] && export LD_LIBRARY_PATH
    if test -z $GDB
    then
        nohup $VALGRIND $MYSQLD \
            $DEFAULTS_OPTION \
            --user="$MYSQLD_USER" \
            --basedir="$MYSQL_BASE_DIR" \
            --datadir="$MYSQL_DATA_DIR" \
            --plugin-dir=lib/mysql/plugin \
            --pid-file="$MYSQL_PID" \
            --port=$MYSQL_PORT \
            --socket=$MYSQL_SOCKET \
            --skip-external-locking \
            --log_error=$err_log \
            $MYSQLD_OPTS \
            $INNODB_OPTS \
            $WSREP_OPTS \
            $DEBUG_OPTS \
            $LOGGING_OPTS \
            $RBR_OPTS \
            $PLUGIN_OPTS \
            $wsrep_start_position_opt \
            1>/dev/null 2>>$err_log &
    else
        $GDB --args $MYSQLD \
            $DEFAULTS_OPTION \
            --user="$MYSQLD_USER" \
            --basedir="$MYSQL_BASE_DIR" \
            --datadir="$MYSQL_DATA_DIR" \
            --plugin-dir=lib/mysql/plugin \
            --pid-file="$MYSQL_PID" \
            --port=$MYSQL_PORT \
            --socket=$MYSQL_SOCKET \
            --skip-external-locking \
            --log_error=$err_log \
            $MYSQLD_OPTS \
            $INNODB_OPTS \
            $WSREP_OPTS \
            $DEBUG_OPTS \
            $LOGGING_OPTS \
            $RBR_OPTS \
            $PLUGIN_OPTS \
            $wsrep_start_position_opt
    fi
    my_pid=$!
    [ "$OS" == "Darwin" ] && export -n LD_LIBRARY_PATH

#    echo "Waiting for pid file"
    while ! test -r $MYSQL_PID
    do
        sleep 1
        if find_pid $my_pid
        then
            # process is alive, wait for pid file
            echo -n "."
        else
            failed="yes"
            break
        fi
    done

    if test "$failed" != "yes"
    then
        echo " Done (PID:$(cat $MYSQL_PID))"
    else
        echo " Failed (PID:$my_pid)"
        return 1
    fi

    wait_for_wsrep_ready $my_pid
}

galera_stop()
{
    # check pid file
    if test -r $MYSQL_PID
    then
        # check if corresponding mysqld is running
#        if ps axc | grep mysqld | grep $(cat $MYSQL_PID) >/dev/null 2>&1
        local my_pid=$(cat $MYSQL_PID)
        if find_pid $my_pid
        then
            echo -n "Killing PID $my_pid"
            kill $my_pid
            # wait for pid file to disappear
            for second in $(seq 1 $MYSQL_SHUTDOWN_WAIT)
            do
                echo -n "."
                sleep 1
                if test ! -r $MYSQL_PID
                then
                    break
                fi
            done
            echo ""
            if test "$second" = "$MYSQL_SHUTDOWN_WAIT"
            then
                echo -n "Failed to stop mysqld safely. Killing with -9... "
                kill -9 $my_pid
            fi
        else
            echo -n "Removing stale PID file $MYSQL_PID... "
        fi

        rm -f $MYSQL_PID
        echo "Done"
    else
        echo "PID file not found: $MYSQL_PID"
    fi
}

galera_restart()
{
    galera_stop
    galera_start
}

galera_status()
{
    if test -f $MYSQL_PID
    then
        local my_pid=$(cat $MYSQL_PID)
        if find_pid $my_pid
        then
            echo "mysqld running with PID: $my_pid"
        else
            echo "Found existing '$MYSQL_PID', but mysqld is not running"
        fi

        exit 0;
    else
        echo "no PID file: '$MYSQL_PID'"
    fi
}

dump()
{
#local ROUTINES="--routines" # don't dump routines yet, will cause false err.
#local OPTIONS="--create-options" gives false positives on AUTO_INCREMENT tbls
# --flush-logs --lock-all-tables # this blocks waiting for all trx to complete
                                 # thus impossible to use with -STOP/CONT
local DUMP_OPTIONS=" --skip-opt --compact --quick --order-by-primary \
                     $OPTIONS --set-charset --skip-comments $ROUTINES "
DB=${DB:-"--all-databases"}
#set -x
mysqldump $DUMP_OPTIONS $ROOT_USER $ROOT_PSWD --socket $MYSQL_SOCKET  \
          $IGNORE_TABLES $DB
#set +x
}

wait_for_last_committed()
{
    local lc
    local new_lc

    lc=$(get_status "wsrep_last_committed") 

    while [ 1 ]
    do
        sleep 1
        new_lc=$(get_status "wsrep_last_committed")
        if [ "$lc" == "$new_lc" ]; then break; fi
        lc="$new_lc"
    done
}

checksum()
{
wait_for_last_committed

set -o pipefail
if [ "$OS" == "Darwin" -o "$OS" == "FreeBSD" ]; then
    CS=$(dump | md5)"  -" || return $?
else
    CS=$(dump | md5sum) || return $?
fi

echo $CS
}

# to use valgrind or not
VALGRIND=${VALGRIND:-""}

# write set level, SQL, RBR or ROW
WS_LEVEL=""

#DB="test" # use 'test' database if none given
# in 5.6 the following tables are non-deterministic
IGNORE_TABLES=\
"--ignore-table=mysql.innodb_table_stats --ignore-table=mysql.innodb_index_stats"

# to use innodb plugin or not
PLUGIN_OPTS=""

if [ $# -eq 0 ]; then usage; exit 1; fi

while [ $# -gt 0 ]; do
case $1 in
    -h|--help)
            usage
            exit 0
            ;;
    -d|--debug)
            DEBUG_OPTS=" --wsrep_debug=1 "
            ;;
    --dbug)
            DBUG_OPTS=" --debug=d,galera,wsdb:t:i:o"
            ;;
    -l|--log)
            LOGGING_OPTS=" --general_log=1 --log_output=FILE "
#            MYSQL_LOG="log"
            ;;
    --valgrind)
            VALGRIND="valgrind --log-file=$MYSQL_DATA_DIR/vg.log --leak-check=full --track-origins=yes"
            # to force deallocation in std::string and STL containers
            export GLIBCXX_FORCE_NEW=1
            ;;
    --gdb)
            GDB="gdb"
            ;;
    -g|--gcs_address)
            GCS_ADDRESS=$2
            shift
            ;;
    --donor)
            WSREP_SST_DONOR=$2
            shift
            ;;
    --slave_threads)
            SLAVE_THREADS=$2
            shift
            ;;
    --db)
            DB=$2
            shift
            ;;
    -i|--ignore_table)
            IGNORE_TABLES=" $IGNORE_TABLES --ignore-table=$DB.$2 "
            shift
            ;;
    --ws_level)
            WS_LEVEL=$2
            shift
            ;;
    -u|--user)
            ROOT_USER="-u$2"
            shift
            ;;
    -p|--password)
            ROOT_PSWD="-p$2"
            shift
            ;;
    --plugin)
            PLUGIN_OPTS="--ignore_builtin_innodb --plugin-load=innodb=ha_innodb_plugin.so;innodb_trx=ha_innodb_plugin.so;innodb_locks=ha_innodb_plugin.so;innodb_lock_waits=ha_innodb_plugin.so;innodb_cmp=ha_innodb_plugin.so;innodb_cmp_reset=ha_innodb_plugin.so;innodb_cmpmem=ha_innodb_plugin.so;innodb_cmpmem_reset=ha_innodb_plugin.so "
            ;;
    --data_dir)
            MYSQL_DATA_DIR=$2
            shift
            ;;
    -c|--command)
            COMMAND=$2
            ;;
    --mysql-opt)
            MYSQLD_OPTS="$MYSQLD_OPTS $2"
            shift
            ;;
    -P|--port)
            MYSQL_PORT="$2"
            shift
            ;;
    -S|--socket)
            MYSQL_SOCKET="$2"
            shift
            ;;
    --skip-recovery)
            SKIP_RECOVERY=1
            ;;
    --start-position)
            START_POSITION="$2"
            shift
            ;;
'dump')
            COMMAND="dump"
            ;;
'check')
            COMMAND="checksum"
            ;;
'start')
            COMMAND=galera_start
            ;;
'stop')
            COMMAND=galera_stop
            ;;
'restart')
            COMMAND=galera_restart
            ;;
'status')
            COMMAND=galera_status
            ;;
'create')
            COMMAND="create_data_dir $2"
            shift
            ;;
    *)
        # must be command
            echo "error parsing: $@"
            usage
            exit 1
            ;;
esac
shift
done

if [ -z "$COMMAND" ]
then
    usage >&2
    exit 1
fi

OS=$(uname)
export LD_LIBRARY_PATH=$MYSQL_BASE_DIR/lib/mysql:$LD_LIBRARY_PATH
[ "$OS" == "FreeBSD" ] && LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib/gcc48
[ "$OS" == "Darwin"  ] && export -n LD_LIBRARY_PATH
export PATH=$MYSQL_BASE_DIR/bin:$PATH

$COMMAND

#
