#!/bin/bash

# make-cluster.sh
# This is a simple script used to automatically spin up a Redis cluster instance
# simplifying the process of running unit tests.
#
# Usage:
#   ./make-cluster.sh start [host]
#   ./make-cluster.sh stop [host]
#

BASEDIR=`pwd`
NODEDIR=$BASEDIR/nodes
MAPFILE=$NODEDIR/nodemap

REDIS_BINARY=${REDIS_BINARY:-redis-server}

# Host, nodes, replicas, ports, etc.  Change if you want different values
HOST="127.0.0.1"
NOASK=0
NODES=12
REPLICAS=3
START_PORT=7000
END_PORT=`expr $START_PORT + $NODES`

# Helper to determine if we have an executable
checkExe() {
    if ! hash $1 > /dev/null 2>&1; then
        echo "Error:  Must have $1 on the path!"
        exit 1
    fi
}

# Run a command and output what we're running
verboseRun() {
    echo "Running: $@"
    $@
}

# Spawn a specific redis instance, cluster enabled 
spawnNode() {
    # ACL file if we have one
    if [ ! -z "$ACLFILE" ]; then
        ACLARG="--aclfile $ACLFILE"
    fi

    # Attempt to spawn the node
    verboseRun "$REDIS_BINARY" --cluster-enabled yes --dir $NODEDIR --port $PORT \
        --cluster-config-file node-$PORT.conf --daemonize yes --save \'\' \
        --bind $HOST --dbfilename node-$PORT.rdb $ACLARG

    # Abort if we can't spin this instance
    if [ $? -ne 0 ]; then 
        echo "Error:  Can't spawn node at port $PORT."
        exit 1
    fi
}

# Spawn nodes from start to end port
spawnNodes() {
    for PORT in `seq $START_PORT $END_PORT`; do
        # Attempt to spawn the node
        spawnNode $PORT

        # Add this host:port to our nodemap so the tests can get seeds
        echo "$HOST:$PORT" >> $MAPFILE
    done
}

# Check to see if any nodes are running
checkNodes() {
    echo -n "Checking port availability "
    
    for PORT in `seq $START_PORT $END_PORT`; do
        redis-cli -p $PORT ping > /dev/null 2>&1
        if [ $? -eq 0 ]; then
            echo "FAIL"
            echo "Error:  There appears to be an instance running at port $PORT"
            exit 1
        fi
    done
    
    echo "OK"
}

# Create our 'node' directory if it doesn't exist and clean out any previous
# configuration files from a previous run.
cleanConfigInfo() {
    verboseRun mkdir -p $NODEDIR
    verboseRun rm -f $NODEDIR/*

    if [ -f "$ACLFILE" ]; then
        cp $ACLFILE $NODEDIR/$ACLFILE
    fi
}

# Initialize our cluster with redis-trib.rb
initCluster() {
    TRIBARGS=""
    for PORT in `seq $START_PORT $END_PORT`; do
        TRIBARGS="$TRIBARGS $HOST:$PORT"
    done

    if [[ ! -z "$USER" ]]; then
        USERARG="--user $USER"
    fi
    if [[ ! -z "$PASS" ]]; then
        PASSARG="-a $PASS"
    fi

    if [[ "$1" -eq "1" ]]; then
        echo yes | redis-cli $USERARG $PASSARG -p $START_PORT --cluster create $TRIBARGS --cluster-replicas $REPLICAS
    else
        verboseRun redis-cli $USERARG $PASSARG -p $START_PORT --cluster create $TRIBARGS --cluster-replicas $REPLICAS
    fi

    if [ $? -ne 0 ]; then
        echo "Error:  Couldn't create cluster!"
        exit 1
    fi
}

# Attempt to spin up our cluster
startCluster() {
    # Make sure none of these nodes are already running
    checkNodes

    # Clean out node configuration, etc
    cleanConfigInfo

    # Attempt to spawn the nodes
    spawnNodes

    # Attempt to initialize the cluster
    initCluster $1
}

# Shut down nodes in our cluster
stopCluster() {
    for PORT in `seq $START_PORT $END_PORT`; do
        verboseRun redis-cli -p $PORT SHUTDOWN NOSAVE > /dev/null 2>&1
    done
}

# Shut down nodes by killing them
killCluster() {
    for PORT in `seq $START_PORT $END_PORT`; do
        PID=$(ps aux|grep [r]edis-server|grep $PORT|awk '{print $2}')
        echo -n "Killing $PID: "
        if kill $PID; then
            echo "OK"
        else
            echo "ERROR"
        fi
    done
}

printUsage() {
    echo "Usage: make-cluster [OPTIONS] <start|stop|kill>"
    echo
    echo "  Options"
    echo
    echo "  -u Redis username to use when spawning cluster"
    echo "  -p Redis password to use when spawning cluster"
    echo "  -a Redis acl filename to use when spawning cluster"
    echo "  -y Automatically send 'yes' when starting cluster"
    echo "  -h This message"
    echo
    exit 0
}

checkExe "$REDIS_BINARY"

while getopts "u:p:a:hy" OPT; do
    case $OPT in
        h)
            printUsage
            ;;
        a)
            if [ ! -f "$OPTARG" ]; then
                echo "Error:  '$OPTARG' is not a filename!"
                exit -1
            fi
            ACLFILE=$OPTARG
            ;;
        u)
            USER=$OPTARG
            ;;
        p)
            PASS=$OPTARG
            ;;
        h)
            HOST=$OPTARG
            ;;
        y)
            NOASK=1
            ;;
        *)
            echo "Unknown option: $OPT"
            exit 1
            ;;
    esac
done

shift "$((OPTIND - 1))"

if [[ $# -lt 1 ]]; then
    echo "Error:  Must pass an operation (start or stop)"
    exit -1
fi

case "$1" in
    start)
        startCluster $NOASK
        ;;
    stop)
        stopCluster
        ;;
    kill)
        killCluster
        ;;
    *)
        echo "Usage: make-cluster.sh [options] <start|stop>"
        exit 1
        ;;
esac
