#!/bin/bash
#
# Copyright 2016 Google Inc.
#
# Licensed 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.
#
# Helping script which runs arbitrary server process (e.g. memcached or
# redis-server) in a temporary directory on random available port.
#
# All you have to do is to `source` this script and pass command which runs
# server (arguments possible). It will be directly evaluated 'as-is' with eval
# builtin. An EXIT trap will be automatically set up which kills server and
# removes temporary working directory. If there was an EXIT trap installed
# already (e.g. this script has been run before), script fails immediately. As a
# workaround, you can start that script in a subshell.
#
# WARNING: server should not daemonize itself, otherwise script won't be able
# to grasp pid and kill the server.
#
# WARNING: one should not use `exec` after calling this script because `exec`
# will effectively remove bash's EXIT trap which terminates the server.
#
# After the script ends, you have server running in background and can access
# SERVER_PID, SERVER_PORT and SERVER_WORKDIR variables if further configuration
# is needed.
#
# This scripts assumes that first argument is a valid command and that quoted
# $SERVER_PORT occurs somewhere in arguments. This is not necessary for it to
# work, but allows us to do extra sanity checks.
#
# TODO(yeputons): make starting several servers with consecutive runs of script
# possible.

set -e
set -u

source $(dirname "$BASH_SOURCE")/shell_utils.sh || exit 1

# Check amount of arguments
if [[ "$#" == 0 ]]; then
  echo "Usage: $0 <server-start-command> [<arguments...>]" >&2
  exit 1
fi

# Probe command
if ! which "$1" >/dev/null; then
  echo "Unable to locate $1." >&2
  echo "Try running 'sudo apt-get install $1'." >&2
  exit 1
fi

# Sanity check: if command does not use $SERVER_PORT, why would server listen on
# the port we chose?
if ! ( echo "$@" | grep --quiet '$SERVER_PORT'  ); then
  echo 'Looks like you do not use $SERVER_PORT in server command.'   >&2
  echo 'If you do, make sure to use single quotes around so it is'   >&2
  echo 'substituted inside the script, not when you call the script' >&2
  exit 1
fi

# Check that there is no existing EXIT trap, otherwise we would override it
if [[ -n "$(trap -p EXIT)" ]]; then
  echo "EXIT trap is already set up, failing" >&2
  exit 1
fi

SERVER_CMD="$1"
SERVER_NAME=$(basename "$1")
SERVER_PID=""
SERVER_PORT=""
SERVER_WORKDIR=$(mktemp -d)

# Function called on bash's termination to clean up
function kill_server {
  if [[ -n "$SERVER_PID" ]]; then
    echo "Killing $SERVER_NAME on port $SERVER_PORT running with pid=$SERVER_PID"
    kill "$SERVER_PID" || true
  fi
  rm -rf "$SERVER_WORKDIR" || true
}
trap 'kill_server' EXIT

# Pick random ports until we successfully can run server.
while [[ -z "$SERVER_PORT" ]]; do
  # Pick a port between 1024 and 32767 inclusive.
  SERVER_PORT=$((($RANDOM % 31744) + 1024))

  # First check netstat -tan to see if somone is already listening on this port.
  if netstat -tan 2>/dev/null | grep --quiet ":$SERVER_PORT .* LISTEN"; then
    continue
  fi

  echo "Trying to start $SERVER_NAME on port $SERVER_PORT"

  # & is quoted because we want run command in background, not whole eval
  eval "$@" "&"

  SERVER_PID="$!"

  echo -n "Waiting for server..."
  if ! wait_cmd_with_timeout 2 \
    'netstat -tanp 2>/dev/null |' \
    'grep ":$SERVER_PORT .* LISTEN .* $SERVER_PID/$SERVER_NAME" >/dev/null'
  then
    echo
    echo "$SERVER_NAME does not listen on port $SERVER_PORT after two" \
         "seconds, killing it"
    kill "$SERVER_PID" || true
    SERVER_PID=""
    SERVER_PORT=""
  else
    echo
  fi
done

echo $SERVER_NAME is up and running on port $SERVER_PORT with pid=$SERVER_PID
