#!/bin/bash
#

# Copyright (C) 2009, 2011, 2012 Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

set -e

# Allow overriding for tests
readonly LOCALSTATEDIR=${LOCALSTATEDIR:-${GANETI_ROOTDIR:-}/var}
readonly SYSCONFDIR=${SYSCONFDIR:-${GANETI_ROOTDIR:-}/etc}

readonly PKGLIBDIR=/usr/lib/ganeti
readonly LOG_DIR="$LOCALSTATEDIR/log/ganeti"
readonly RUN_DIR="$LOCALSTATEDIR/run/ganeti"
readonly DATA_DIR="$LOCALSTATEDIR/lib/ganeti"
readonly CONF_DIR="$SYSCONFDIR/ganeti"

readonly defaults_file="$SYSCONFDIR/default/ganeti"

# This is a list of all daemons and the order in which they're started. The
# order is important as there are dependencies between them. On shutdown,
# they're stopped in reverse order.
DAEMONS=(
  ganeti-noded
  ganeti-confd
  ganeti-wconfd
  ganeti-rapi
  ganeti-luxid
  ganeti-kvmd
  )

# This is the list of daemons that are loaded on demand; they should only be
# stopped, not started.
ON_DEMAND_DAEMONS=(
  ganeti-metad
  )

_mond_enabled() {
  [[ "True" == True ]]
}

if _mond_enabled; then
  DAEMONS+=( ganeti-mond )
fi

# The full list of all daemons we know about
ALL_DAEMONS=( ${DAEMONS[@]} ${ON_DEMAND_DAEMONS[@]} )

NODED_ARGS=
CONFD_ARGS=
WCONFD_ARGS=
LUXID_ARGS=
RAPI_ARGS=
MOND_ARGS=
METAD_ARGS=
KVMD_ARGS=

# Read defaults file if it exists
if [[ -s $defaults_file ]]; then
  . $defaults_file
fi

# Meant to facilitate use utilities in /etc/rc.d/init.d/functions in case
# start-stop-daemon is not available.
_ignore_error() {
  eval "$@" || :
}

_daemon_pidfile() {
  echo "$RUN_DIR/$1.pid"
}

_daemon_executable() {
  echo "/usr/sbin/$1"
}

_daemon_usergroup() {
  case "$1" in
    confd)
      echo "gnt-confd:gnt-confd"
      ;;
    wconfd)
      echo "gnt-masterd:gnt-confd"
      ;;
    luxid)
      echo "gnt-masterd:gnt-luxid"
      ;;
    rapi)
      echo "gnt-rapi:gnt-rapi"
      ;;
    noded)
      echo "root:root"
      ;;
    mond)
      echo "root:root"
      ;;
    metad)
      echo "gnt-metad:gnt-metad"
      ;;
    *)
      echo "root:gnt-daemons"
      ;;
  esac
}

# Specifies the additional capabilities needed by individual daemons
_daemon_caps() {
  case "$1" in
    metad)
      echo "cap_net_bind_service=+ep"
      ;;
    *)
      echo ""
      ;;
  esac
}

# Checks whether the local machine is part of a cluster
check_config() {
  local server_pem=$DATA_DIR/server.pem
  local fname

  for fname in $server_pem; do
    if [[ ! -f $fname ]]; then
      echo "Missing configuration file $fname" >&2
      return 1
    fi
  done

  return 0
}

# Checks the exit code of a daemon
check_exitcode() {
  if [[ "$#" -lt 1 ]]; then
    echo 'Missing exit code.' >&2
    return 1
  fi

  local rc="$1"; shift

  case "$rc" in
    0) ;;
    11)
      echo "not master"
    ;;
    *)
      echo "exit code $rc"
      return 1
    ;;
  esac

  return 0
}

# Checks if we should use systemctl to start/stop daemons
use_systemctl() {
  # Is systemd running as PID 1?
  [ -d /run/systemd/system ] || return 1

  type -p systemctl >/dev/null || return 1

  # Does systemd know about Ganeti at all?
  loadstate="$(systemctl show -pLoadState ganeti.target)"
  if [ "$loadstate" = "LoadState=loaded" ]; then
    return 0
  fi

  return 1
}

# Prints path to PID file for a daemon.
daemon_pidfile() {
  if [[ "$#" -lt 1 ]]; then
    echo 'Missing daemon name.' >&2
    return 1
  fi

  local name="$1"; shift

  _daemon_pidfile $name
}

# Prints path to daemon executable.
daemon_executable() {
  if [[ "$#" -lt 1 ]]; then
    echo 'Missing daemon name.' >&2
    return 1
  fi

  local name="$1"; shift

  _daemon_executable $name
}

# Prints a list of all daemons in the order in which they should be started
list_start_daemons() {
  local name

  for name in "${DAEMONS[@]}"; do
    echo "$name"
  done
}

# Prints a list of all daemons in the order in which they should be stopped
list_stop_daemons() {
  for name in "${ALL_DAEMONS[@]}"; do
    echo "$name"
  done | tac
}

# Checks whether a daemon name is known
is_daemon_name() {
  if [[ "$#" -lt 1 ]]; then
    echo 'Missing daemon name.' >&2
    return 1
  fi

  local name="$1"; shift

  for i in "${ALL_DAEMONS[@]}"; do
    if [[ "$i" == "$name" ]]; then
      return 0
    fi
  done

  echo "Unknown daemon name '$name'" >&2
  return 1
}

# Checks whether daemon is running
check() {
  if [[ "$#" -lt 1 ]]; then
    echo 'Missing daemon name.' >&2
    return 1
  fi

  local name="$1"; shift
  local pidfile=$(_daemon_pidfile $name)
  local daemonexec=$(_daemon_executable $name)

  if use_systemctl; then
    activestate="$(systemctl show -pActiveState "${name}.service")"
    if [ "$activestate" = "ActiveState=active" ]; then
      return 0
    else
      return 1
    fi
  elif type -p start-stop-daemon >/dev/null; then
    start-stop-daemon --stop --signal 0 --quiet \
      --pidfile $pidfile --name "$name"
  else
    _ignore_error status \
      -p $pidfile \
      $daemonexec
  fi
}

# Starts a daemon
start() {
  if [[ "$#" -lt 1 ]]; then
    echo 'Missing daemon name.' >&2
    return 1
  fi

  local name="$1"; shift
  # Convert daemon name to uppercase after removing "ganeti-" prefix
  local plain_name=${name#ganeti-}
  local ucname=$(tr a-z A-Z <<<$plain_name)
  local pidfile=$(_daemon_pidfile $name)
  local usergroup=$(_daemon_usergroup $plain_name)
  local daemonexec=$(_daemon_executable $name)

  if use_systemctl; then
    local onetime_conf="${DATA_DIR}/${name}.onetime.conf"
    echo "ONETIME_ARGS=$@" > "$onetime_conf"

    systemctl start "${name}.service"
    ret=$?

    rm -f "$onetime_conf"
    return $?
  fi

  # Read $<daemon>_ARGS and $EXTRA_<daemon>_ARGS
  eval local args="\"\$${ucname}_ARGS \$EXTRA_${ucname}_ARGS\""

  /usr/lib/ganeti/ensure-dirs

  # Grant capabilities to daemons that need them
  local daemoncaps=$(_daemon_caps $plain_name)
  if [[ "$daemoncaps" != "" ]]; then
    if type -p setcap >/dev/null; then
      setcap $daemoncaps $(readlink -f $daemonexec)
    else
      echo "setcap missing, could not set capabilities for $name." >&2
      return 1
    fi
  fi

  if type -p start-stop-daemon >/dev/null; then
    start-stop-daemon --start --quiet --oknodo \
      --pidfile $pidfile \
      --startas $daemonexec \
      --chuid $usergroup \
      -- $args "$@"
  else
    # TODO: Find a way to start daemon with a group, until then the group must
    # be removed
    _ignore_error daemon \
      --pidfile $pidfile \
      --user ${usergroup%:*} \
      $daemonexec $args "$@"
  fi

}

# Stops a daemon
stop() {
  if [[ "$#" -lt 1 ]]; then
    echo 'Missing daemon name.' >&2
    return 1
  fi

  local name="$1"; shift
  local pidfile=$(_daemon_pidfile $name)

  if use_systemctl; then
    systemctl stop "${name}.service"
  elif type -p start-stop-daemon >/dev/null; then
    start-stop-daemon --stop --quiet --oknodo --retry 30 \
      --pidfile $pidfile --remove-pidfile --name "$name"
  else
    _ignore_error killproc -p $pidfile $name
  fi
}

# Starts a daemon if it's not yet running
check_and_start() {
  local name="$1"

  if ! check $name; then
    if use_systemctl; then
      echo "${name} supervised by systemd but not running, will not restart."
      return 1
    fi

    start $name
  fi
}

# Starts the master role
start_master() {
  if use_systemctl; then
    systemctl start ganeti-master.target
  else
    start ganeti-wconfd
    start ganeti-rapi
    start ganeti-luxid
  fi
}

# Stops the master role
stop_master() {
  if use_systemctl; then
    systemctl stop ganeti-master.target
  else
    stop ganeti-luxid
    stop ganeti-rapi
    stop ganeti-wconfd
  fi
}

# Start all daemons
start_all() {
  use_systemctl && systemctl start ganeti.target
  # Fall through so that we detect any errors.

  for i in $(list_start_daemons); do
    local rc=0

    # Try to start daemon
    start $i || rc=$?

    if ! errmsg=$(check_exitcode $rc); then
      echo "$errmsg" >&2
      return 1
    fi
  done

  return 0
}

# Stop all daemons
stop_all() {
  if use_systemctl; then
    systemctl stop ganeti.target
  else
    for i in $(list_stop_daemons); do
      stop $i
    done
  fi
}

# SIGHUP a process to force re-opening its logfiles
rotate_logs() {
  if [[ "$#" -lt 1 ]]; then
    echo 'Missing daemon name.' >&2
    return 1
  fi

  local name="$1"; shift
  local pidfile=$(_daemon_pidfile $name)
  local daemonexec=$(_daemon_executable $name)

  if type -p start-stop-daemon >/dev/null; then
    start-stop-daemon --stop --signal HUP --quiet \
      --oknodo --pidfile $pidfile --name "$name"
  else
    _ignore_error killproc \
      -p $pidfile \
      $daemonexec -HUP
  fi
}

# SIGHUP all processes
rotate_all_logs() {
  for i in $(list_stop_daemons); do
    rotate_logs $i
  done
}

# Reloads the SSH keys
reload_ssh_keys() {
  /usr/sbin/service ssh restart
}

# Read /etc/rc.d/init.d/functions if start-stop-daemon not available
if ! type -p start-stop-daemon >/dev/null && \
   [[ -f /etc/rc.d/init.d/functions ]]; then
  _ignore_error . /etc/rc.d/init.d/functions
fi

if [[ "$#" -lt 1 ]]; then
  echo "Usage: $0 <action>" >&2
  exit 1
fi

orig_action=$1; shift

if [[ "$orig_action" == *_* ]]; then
  echo "Command must not contain underscores" >&2
  exit 1
fi

# Replace all dashes (-) with underlines (_)
action=${orig_action//-/_}

# Is it a known function?
if ! declare -F "$action" >/dev/null 2>&1; then
  echo "Unknown command: $orig_action" >&2
  exit 1
fi

# Call handler function
$action "$@"
