#!/bin/bash

########################################################################
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#
########################################################################

set -e
set -o pipefail
ulimit -n 2048
set -m

NUM_MASTERS=1
NUM_TSERVERS=3
MASTER_RPC_PORT_BASE=8764
TSERVER_RPC_PORT_BASE=9870
TIME_SOURCE=system_unsync
BUILDDIR=""
CLUSTER_DIR="$PWD"
EXTRA_TSERVER_FLAGS=""
EXTRA_MASTER_FLAGS=""
ENABLE_TDE=""
RPC_IP="127.0.0.1"
HTTP_IP="127.0.0.1"
IP_CONFIG_MODE=""

function usage() {
cat << EOF
Usage:
start_kudu.sh [flags]
-h, --help          Print help
-m, --num-masters   Number of Kudu Masters to start
                    (default: $NUM_MASTERS)
-t, --num-tservers  Number of Kudu Tablet Servers to start
                    (default: $NUM_TSERVERS)
--rpc-port-master   RPC port of first Kudu Master;
                    HTTP port is the next number
                    (default: $MASTER_RPC_PORT_BASE)
--rpc-port-tserver  RPC port of first Kudu Tablet Server;
                    HTTP port is the next number
                    (default: $TSERVER_RPC_PORT_BASE)
--time_source       Time source for Kudu Masters and Tablet Servers
                    (default: $TIME_SOURCE)
-b, --builddir      Path to the Kudu build directory
                    (default: "$BUILDDIR")
-c, --clusterdir    Path to place Masters' and Tablet Servers' data and logs
                    (default: "$CLUSTER_DIR")
-e, --enable-tde    Enable transparent data encryption
                    (default: "$ENABLE_TDE")
-H, --host          IP address for RPC server endpoint
                    (default: $RPC_IP)
-W, --webhost       IP address for HTTP server endpoint
                    (default: $HTTP_IP)
-T, --tserver-flags Extra flags to be used on the tablet servers;
                    multiple flags can be specified if wrapped in ""s
                    (default: "$EXTRA_TSERVER_FLAGS")
-M, --master-flags  Extra flags to be used on the master servers;
                    multiple flags can be specified if wrapped in ""s
                    (default: "$EXTRA_MASTER_FLAGS")
EOF
}

while (( "$#" )); do
  case "$1" in
    -h|--help)
      usage
      exit 1
      ;;
    -m|--num-masters)
      NUM_MASTERS=$2
      shift 2
      ;;
    -t|--num-tservers)
      NUM_TSERVERS=$2
      shift 2
      ;;
    --rpc-port-master|--rpc-master)
      MASTER_RPC_PORT_BASE=$2
      shift 2
      ;;
    --rpc-port-tserver|--rpc-tserver)
      TSERVER_RPC_PORT_BASE=$2
      shift 2
      ;;
    --time_source)
      TIME_SOURCE=$2
      shift 2
      ;;
    -b|--builddir)
      BUILDDIR="$2"
      shift 2
      ;;
    -c|--clusterdir)
      CLUSTER_DIR=$2
      shift 2
      ;;
    -T|--tserver-flags)
      EXTRA_TSERVER_FLAGS=$2
      shift 2
      ;;
    -M|--master-flags)
      EXTRA_MASTER_FLAGS=$2
      shift 2
      ;;
    -e|--enable-tde)
      ENABLE_TDE=1
      shift 1
      ;;
    -H|--host)
      RPC_IP=$2
      shift 2
      ;;
    -W|--webhost)
      HTTP_IP=$2
      shift 2
      ;;
    --) # end argument parsing
      shift
      break
      ;;
    -*|--*=) # unsupported flags
      echo "Error: Unsupported flag $1" >&2
      usage
      exit 1
      ;;
    *) # positional arguments
      echo "Error: Unsupported argument $1" >&2
      usage
      exit 1
      ;;
  esac
done

if [ -z "$BUILDDIR" ]; then
  echo -n "Assuming that the script was started from the build directory. "
  echo "You can override this with -b|--builddir option."
  BUILDDIR="$PWD"
fi

# If $KUDU_HOME is not set or $KUDU_HOME/www doesn't exists, let's default to $BUILDDIR/../../www
# In case neither is available we'll issue a warning and won't set the --webserver_doc_root flag
# for kudu-master and kudu-tserver
if [ -z "$KUDU_HOME" ] || [ ! -d "$KUDU_HOME/www" ]; then
  WEBSERVER_DOC_ROOT="$BUILDDIR/../../www"
fi

if [ -n "$WEBSERVER_DOC_ROOT" ] && [ ! -d "$WEBSERVER_DOC_ROOT" ]; then
  echo  -n "Cannot find webroot directory $WEBSERVER_DOC_ROOT at "
  echo "\$KUDU_HOME/www or \$BUILDDIR/../../www"
fi

KUDUMASTER="$BUILDDIR/bin/kudu-master"
KUDUTSERVER="$BUILDDIR/bin/kudu-tserver"
echo $KUDUMASTER
echo $KUDUTSERVER

[ ! -x "$KUDUMASTER" ] && { echo "Cannot find $KUDUMASTER executable";  exit 1; }
[ ! -x "$KUDUTSERVER" ] && { echo "Cannot find $KUDUTSERVER executable";  exit 1; }


# Common steps before starting masters or tablet servers

# 1) Create "data", "wal" and "log" directories for a server before start

function create_dirs_and_set_vars() {
  root_dir="$CLUSTER_DIR/$1"
  dir_data="$root_dir/data"
  dir_wal="$root_dir/wal"
  dir_log="$root_dir/log"

  mkdir -p "$dir_data" "$dir_wal" "$dir_log"
}

# 2) Print information about interface ports

function set_port_vars_and_print() {
  RPC_PORT=$2
  HTTP_PORT=$3
  echo "Starting $1:"
  echo "  RPC  port $RPC_PORT"
  echo "  HTTP port $HTTP_PORT"
}

# Return a flag to set the hard memory limit for the Kudu server processes
# running at the same node. Each of the processes is able to set the hard
# memory limit based on the total amount of memory available, but such a
# provision assumes there is a single Kudu server process running at a node.
# Since there is going to be NUM_TSERVERS kudu-tserver and NUM_MASTERS
# kudu-master processes running, it's necessary to divide the available memory
# among them.
function get_memory_limit_hard_bytes_flag() {
  local num_processes=$1
  local mem_size_bytes=0
  if [[ "$OSTYPE" =~ ^linux ]]; then
    local mem_size_kb=$(grep -E '^MemTotal' /proc/meminfo | awk '{print $2}')
    mem_size_bytes=$((mem_size_kb * 1024))
  elif [[ "$OSTYPE" =~ ^darwin ]]; then
    mem_size_bytes=$(sysctl hw.memsize | awk '{print $2}')
  fi

  # Do not set the limit for a non-recognized OS.
  if [ $mem_size_bytes -eq 0 ]; then
    echo ""
    return
  fi

  # Allocate 80% of all available memory to be used by all the Kudu processes.
  local mem_limit_bytes=$((mem_size_bytes * 4 / 5))
  mem_limit_bytes=$((mem_limit_bytes / num_processes))
  echo "--memory_limit_hard_bytes=$mem_limit_bytes"
}

pids=()

# Start kudu-master process.
function start_master() {
  create_dirs_and_set_vars $1
  set_port_vars_and_print $1 $2 $3
  ARGS="$KUDUMASTER"
  ARGS="$ARGS --master_addresses=$MASTER_ADDRESSES"
  ARGS="$ARGS --fs_data_dirs=$dir_data"
  ARGS="$ARGS --fs_wal_dir=$dir_wal"
  ARGS="$ARGS --log_dir=$dir_log"
  ARGS="$ARGS --rpc_bind_addresses=$RPC_IP:$RPC_PORT"
  ARGS="$ARGS --time_source=$TIME_SOURCE"
  ARGS="$ARGS --unlock_unsafe_flags"
  ARGS="$ARGS --webserver_interface=$HTTP_IP"
  ARGS="$ARGS --webserver_port=$HTTP_PORT"
  if [ -n "$IP_CONFIG_MODE" ]; then
    ARGS="$ARGS --ip_config_mode=$IP_CONFIG_MODE"
    ARGS="$ARGS --unlock_experimental_flags=true"
  fi
  if [ -d "$WEBSERVER_DOC_ROOT" ]; then
    ARGS="$ARGS --webserver_doc_root=$WEBSERVER_DOC_ROOT"
  fi
  if [ -n "$ENABLE_TDE" ]; then
    ARGS="$ARGS --encrypt_data_at_rest=true"
  fi
  # NOTE: a kudu-master process doesn't usually consume a lot of memory,
  #       so the memory hard limit isn't set for them; if kudu-master memory
  #       consumption becomes an issue, provide the necessary flags for
  #       kudu-master processing using the --master-flags/-M command line
  #       option
  ARGS="$ARGS $EXTRA_MASTER_FLAGS"
  $ARGS &
  pids+=($!)
  echo "  PID $!"
}

# Start kudu-tserver process.
function start_tserver() {
  create_dirs_and_set_vars $1
  set_port_vars_and_print $1 $2 $3
  ARGS="$KUDUTSERVER"
  ARGS="$ARGS --fs_data_dirs=$dir_data"
  ARGS="$ARGS --fs_wal_dir=$dir_wal"
  ARGS="$ARGS --log_dir=$dir_log"
  ARGS="$ARGS --rpc_bind_addresses=$RPC_IP:$RPC_PORT"
  ARGS="$ARGS --time_source=$TIME_SOURCE"
  ARGS="$ARGS --unlock_unsafe_flags"
  ARGS="$ARGS --webserver_interface=$HTTP_IP"
  ARGS="$ARGS --webserver_port=$HTTP_PORT"
  ARGS="$ARGS --tserver_master_addrs=$4"
  if [ -n "$IP_CONFIG_MODE" ]; then
    ARGS="$ARGS --ip_config_mode=$IP_CONFIG_MODE"
    ARGS="$ARGS --unlock_experimental_flags=true"
  fi
  if [ -d "$WEBSERVER_DOC_ROOT" ]; then
    ARGS="$ARGS --webserver_doc_root=$WEBSERVER_DOC_ROOT"
  fi
  if [ -n "$ENABLE_TDE" ]; then
    ARGS="$ARGS --encrypt_data_at_rest=true"
  fi

  # If applicable, set the memory hard limit.
  local mem_limit_flag=$(get_memory_limit_hard_bytes_flag $NUM_TSERVERS)
  if [ -n $mem_limit_flag ]; then
    ARGS="$ARGS $mem_limit_flag"
  fi
  ARGS="$ARGS $EXTRA_TSERVER_FLAGS"
  $ARGS &
  pids+=($!)
  echo "  PID $!"
}

# Precompute the comma-separated list of master addresses.
MASTER_ADDRESSES=
for i in $(seq 0 $((NUM_MASTERS - 1))); do
  MASTER_RPC_PORT=$((MASTER_RPC_PORT_BASE + $i * 2))
  ADDR=$RPC_IP:$MASTER_RPC_PORT
  if [ $i -ne 0 ]; then
    MASTER_ADDRESSES="${MASTER_ADDRESSES},"
  fi
  MASTER_ADDRESSES="${MASTER_ADDRESSES}${ADDR}"
done

# Start masters
for i in $(seq 0 $((NUM_MASTERS - 1))); do
  MASTER_RPC_PORT=$((MASTER_RPC_PORT_BASE + $i * 2))
  MASTER_HTTP_PORT=$((MASTER_RPC_PORT + 1))
  start_master master-$i $MASTER_RPC_PORT $MASTER_HTTP_PORT
done

# Start tservers
for i in $(seq 0 $((NUM_TSERVERS - 1))); do
  TSERVER_RPC_PORT=$((TSERVER_RPC_PORT_BASE + $i * 2))
  TSERVER_HTTP_PORT=$((TSERVER_RPC_PORT + 1))
  start_tserver tserver-$i $TSERVER_RPC_PORT $TSERVER_HTTP_PORT $MASTER_ADDRESSES
done

# Show the status of the started processes.
ps -wwo args -p ${pids[@]}
