#!/bin/bash
cvmfs_test_name="Update GeoIP Database"
cvmfs_test_autofs_on_startup=false
cvmfs_test_suites="quick"

# NOTE:
#  A full test requires a valid CVMFS_GEO_ACCOUNT_ID and CVMFS_GEO_LICENSE_KEY
#  in a /etc/cvmfs/server.local that is readable by $CVMFS_TEST_USER.
#  Test will be skipped with a warning if either CVMFS_GEO_DB_FILE is
#    set or there's no CVMFS_GEO_LICENSE_KEY but there is a system
#    geo DB in /usr/share/GeoIP.

#
# Location of the system-wide GeoIP databases
# configurable: to be changed if $CVMFS_UPDATEGEO_DIR or CVMFS_UPDATEGEO_DB
#    in cvmfs_server changes
#
CVMFS_TEST_595_GEODB="/var/lib/cvmfs-server/geo/GeoLite2-City.mmdb"
CVMFS_TEST_595_ATTEMPT_FILE="/var/lib/cvmfs-server/geo/.last_attempt_day"
CVMFS_TEST_595_SERVER_HOOKS="/etc/cvmfs/cvmfs_server_hooks.sh"

CVMFS_TEST_595_REPLICA_NAME=
CVMFS_TEST_595_GEODB_TOUCHED=0
CVMFS_TEST_595_GEODB_STASH=
CVMFS_TEST_595_GEODB_OWNER=
CVMFS_TEST_595_GEODB_GROUP=
CVMFS_TEST_595_SERVER_HOOKS_TOUCHED=0
CVMFS_TEST_595_SERVER_HOOKS_STASH=
cleanup() {
  echo "running cleanup... "
  [ -z $CVMFS_TEST_595_REPLICA_NAME ]            || sudo cvmfs_server rmfs -f $CVMFS_TEST_595_REPLICA_NAME
  [ $CVMFS_TEST_595_GEODB_TOUCHED -eq 0 ]        || sudo rm -f $CVMFS_TEST_595_GEODB
  [ -z $CVMFS_TEST_595_GEODB_STASH  ]            || cvmfs_unstash GEODB
  [ -z $CVMFS_TEST_595_GEODB_OWNER ]             || sudo chown ${CVMFS_TEST_595_GEODB_OWNER}:${CVMFS_TEST_595_GEODB_GROUP} $(dirname $CVMFS_TEST_595_GEODB)
  [ $CVMFS_TEST_595_SERVER_HOOKS_TOUCHED -eq 0 ] || sudo rm -f $CVMFS_TEST_595_SERVER_HOOKS
  [ -z $CVMFS_TEST_595_SERVER_HOOKS_STASH ]      || sudo cp -f $CVMFS_TEST_595_SERVER_HOOKS_STASH $CVMFS_TEST_595_SERVER_HOOKS
}


cvmfs_stash() {
  local var="CVMFS_TEST_595_$1"
  local source
  eval source="\$$var"
  if [ ! -f "$source" ]; then
    return
  fi
  local stash=$(pwd)/$(basename $source)
  echo "stash away '$source' prior to the test in '$stash'"
  sudo cp -f $source $stash || return 1
  eval ${var}_STASH=$stash
  sudo rm -f $source        || return 1
}

cvmfs_unstash()
{
  local var="CVMFS_TEST_595_$1"
  local source
  eval source="\$$var"
  local stash
  eval stash="\$${var}_STASH"
  sudo cp -f $stash $source
  sudo chown ${CVMFS_TEST_595_GEODB_OWNER}:${CVMFS_TEST_595_GEODB_GROUP} $source
}

recreate_server_hooks_from_stashed_if_needed() {
  sudo rm -f $CVMFS_TEST_595_SERVER_HOOKS || true
  if [ ! -z $CVMFS_TEST_595_SERVER_HOOKS_STASH ] && \
     [   -f $CVMFS_TEST_595_SERVER_HOOKS_STASH ]; then
    # copying over CVMFS_UPDATEGEO_URLBASE configuration if necessary
    grep -e 'CVMFS_UPDATEGEO_URLBASE='  $CVMFS_TEST_595_SERVER_HOOKS_STASH | sudo tee --append $CVMFS_TEST_595_SERVER_HOOKS || return 50
    grep -e 'CVMFS_UPDATEGEO_URLBASE6=' $CVMFS_TEST_595_SERVER_HOOKS_STASH | sudo tee --append $CVMFS_TEST_595_SERVER_HOOKS || return 51
  fi
}

cvmfs_run_test() {
  logfile=$1

  if [ ! -d `dirname $CVMFS_TEST_595_GEODB` ]; then
    echo "No geo database directory, disabled on this platform"
    CVMFS_GENERAL_WARNING_FLAG=1
    return 0
  fi

  if [ -f /etc/cvmfs/server.local ]; then
    . /etc/cvmfs/server.local
  fi
  if [ -n "$CVMFS_GEO_DB_FILE" ]; then
    echo "Geo database update disabled, skipping tests"
    CVMFS_GENERAL_WARNING_FLAG=1
    return 0
  fi

  if [ -z "$CVMFS_GEO_ACCOUNT_ID" -o -z "$CVMFS_GEO_LICENSE_KEY" ]; then
    if [ -z "$CVMFS_GEO_LICENSE_KEY" -a -f /usr/share/GeoIP/`basename $CVMFS_TEST_595_GEODB` ]; then
      echo "System geo database installed, skipping tests"
      CVMFS_GENERAL_WARNING_FLAG=1
      return 0
    fi
    ACCT_MSG=
    LICENSE_MSG=
    if [ -z "$CVMFS_GEO_ACCOUNT_ID" ]; then
      ACCT_MSG=", geo account ID"
    fi
    if [ -z "$CVMFS_GEO_LICENSE_KEY" ]; then
      LICENSE_MSG=", geo license key"
    fi
    echo The following are not found: db file${ACCT_MSG}${LICENSE_MSG}
    return 1
  fi

  # can't start out as root, it causes some of the tests below to fail
  echo "check if running as root"
  [ $(id -u) != 0 ] || return 2

  local repo_dir=/cvmfs/$CVMFS_TEST_REPO

  local scratch_dir=$(pwd)
  mkdir reference_dir
  local reference_dir=$scratch_dir/reference_dir

  local mnt_point="$(pwd)/mountpount"
  local replica_name="$(get_stratum1_name $CVMFS_TEST_REPO)"

  local geodb_dir=$(dirname $CVMFS_TEST_595_GEODB)
  echo "save the owner of $geodb_dir"
  CVMFS_TEST_595_GEODB_OWNER=$(stat --format='%U' $geodb_dir)
  CVMFS_TEST_595_GEODB_GROUP=$(stat --format='%G' $geodb_dir)

  echo "install a disaster cleanup function"
  trap cleanup EXIT HUP INT TERM || return $?

  cvmfs_stash GEODB
  cvmfs_stash SERVER_HOOKS

  echo "create initial $CVMFS_TEST_595_SERVER_HOOKS if necessary"
  recreate_server_hooks_from_stashed_if_needed || return 50

  echo "create a fresh repository named $CVMFS_TEST_REPO with user $CVMFS_TEST_USER"
  create_empty_repo $CVMFS_TEST_REPO $CVMFS_TEST_USER || return $?

  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  echo "check that there is no GeoIP database"
  [ ! -f $CVMFS_TEST_595_GEODB ] || return 3

  echo "create Stratum1 repository on the same machine"
  local create_s1_log="create_stratum1.log"
  load_repo_config $CVMFS_TEST_REPO
  create_stratum1 $replica_name                          \
                  $CVMFS_TEST_USER                       \
                  $CVMFS_STRATUM0                        \
                  /etc/cvmfs/keys/${CVMFS_TEST_REPO}.pub \
                  > $create_s1_log 2>&1 || return 5
  CVMFS_TEST_595_REPLICA_NAME=$replica_name
  CVMFS_TEST_595_GEODB_TOUCHED=1

  echo "check that there is a GeoIP database now"
  [ -f $CVMFS_TEST_595_GEODB ] || return 6

  echo "check the logging output for the GeoIP update strategy"
  cat $create_s1_log | grep "Installing GeoIP Database" || return 8

  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  echo "manually start a GeoIP database update as '$CVMFS_TEST_USER' (should fail)"
  local update_1_log="update_geodb_1.log"
  cvmfs_server update-geodb > $update_1_log 2>&1 && return 9

  echo "manually start a lazy GeoIP database update as '$CVMFS_TEST_USER' (should fail as well)"
  local update_2_log="update_geodb_2.log"
  rm -f $CVMFS_TEST_595_ATTEMPT_FILE
  cvmfs_server update-geodb -l > $update_2_log 2>&1 && return 10

  echo "check output logs for the expected error messages"
  cat $update_1_log | grep "not writable by $CVMFS_TEST_USER" || return 11
  cat $update_2_log | grep "not writable by $CVMFS_TEST_USER" || return 12

  echo "manually start a lazy GeoIP database update as 'root' (should work but not update)"
  local update_3_log="update_geodb_3.log"
  rm -f $CVMFS_TEST_595_ATTEMPT_FILE
  sudo -E cvmfs_server update-geodb -l > $update_3_log 2>&1 || return 13

  echo "manually start a GeoIP database update as 'root' (should work and update)"
  local update_4_log="update_geodb_4.log"
  sudo -E cvmfs_server update-geodb > $update_4_log 2>&1 || return 14

  echo "check output logs for the expected status messages"
  cat $update_4_log | grep "Updating GeoIP Database" || return 15

  echo "check repositories.json for geodb update UTC date"
  local rj=/srv/cvmfs/info/v1/repositories.json
  [[ "$(jq .last_geodb_update <$rj)" == *UTC* ]] || return 16

  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  local minutes_to_next_hour=$(( 60 - $(date +'%-M') ))
  if [ $minutes_to_next_hour -lt 3 ]; then
    echo "Avoiding potential race (only $minutes_to_next_hour minutes left to next full hour)"
    echo -n "Sleeping for $minutes_to_next_hour minutes..."
    sleep $(( 60 * $minutes_to_next_hour ))
    echo "done"
  fi

  local current_weekday=$(date +%w)
  local current_hour=$(date +%-k)
  local mindays=3
  local maxdays=5

  echo "configure the GeoIP database update policy"
  echo "  (mindays: $mindays | maxdays: $maxdays | weekday: $current_weekday | hour: $current_hour)"
  echo "CVMFS_UPDATEGEO_MINDAYS=$mindays"     | sudo tee --append $CVMFS_TEST_595_SERVER_HOOKS || return 17
  echo "CVMFS_UPDATEGEO_MAXDAYS=$maxdays"     | sudo tee --append $CVMFS_TEST_595_SERVER_HOOKS || return 18
  echo "CVMFS_UPDATEGEO_DAY=$current_weekday" | sudo tee --append $CVMFS_TEST_595_SERVER_HOOKS || return 19
  echo "CVMFS_UPDATEGEO_HOUR=$current_hour"   | sudo tee --append $CVMFS_TEST_595_SERVER_HOOKS || return 20
  echo
  echo "$CVMFS_TEST_595_SERVER_HOOKS"
  cat $CVMFS_TEST_595_SERVER_HOOKS
  echo
  CVMFS_TEST_595_SERVER_HOOKS_TOUCHED=1

  local old=$(( $mindays + 1 ))
  local very_old=$(( $maxdays + 1 ))
  echo "set mtime of the GeoIP database to $very_old days ago (very old)"
  sudo touch -d "$very_old days ago" $CVMFS_TEST_595_GEODB || return 21

  echo "do a lazy update as 'root' (should work and force the update)"
  local update_5_log="update_geodb_5.log"
  sudo rm -f $CVMFS_TEST_595_ATTEMPT_FILE
  sudo -E cvmfs_server update-geodb -l > $update_5_log 2>&1 || return 22

  echo "do a lazy update as 'root' (should work but not update again)"
  local update_6_log="update_geodb_6.log"
  sudo -E cvmfs_server update-geodb -l > $update_6_log 2>&1 || return 23

  echo "set mtime of the GeoIP database to $old days ago (old)"
  sudo touch -d "$old days ago" $CVMFS_TEST_595_GEODB || return 24

  echo "do a lazy update of older db without removing last attempt file (should not update)"
  local update_7_log="update_geodb_7.log"
  sudo -E cvmfs_server update-geodb -l > $update_7_log 2>&1 || return 25

  echo "do a lazy update as 'root' (should work and update)"
  local update_8_log="update_geodb_8.log"
  sudo rm -f $CVMFS_TEST_595_ATTEMPT_FILE
  sudo -E cvmfs_server update-geodb -l > $update_8_log 2>&1 || return 26

  echo "check output logs for the expected status messages"
  cat $update_5_log | grep -e "very old.* Updating"   || return 27
  test -z "`cat $update_6_log`"                       || return 28
  test -z "`cat $update_7_log`"                       || return 29
  cat $update_8_log | grep -e "is expired.* Updating" || return 30

  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  echo "set mtime of the GeoIP database to $old days ago (old)"
  sudo touch -d "$old days ago" $CVMFS_TEST_595_GEODB || return 31

  if [ $current_hour -lt 23 ]; then
    local next_hour=$(( $current_hour + 1 ))
    echo "change GeoIP database update policy (hour: $next_hour)"
    sudo sed -i -e "s/^\(CVMFS_UPDATEGEO_HOUR\)=.*$/\1=$next_hour/" $CVMFS_TEST_595_SERVER_HOOKS || return 32

    echo
    echo "$CVMFS_TEST_595_SERVER_HOOKS"
    cat $CVMFS_TEST_595_SERVER_HOOKS
    echo

    echo "do a lazy update as 'root' (should work but refrain from updating)"
    local update_10_log="update_geodb_10.log"
    sudo rm -f $CVMFS_TEST_595_ATTEMPT_FILE
    sudo -E cvmfs_server update-geodb -l > $update_10_log 2>&1 || return 33

    echo "check output logs for the expected status messages"
    cat $update_10_log | grep -e "waiting for install time slot" || return 34
  else
    echo "WARNING: It's nearly midnight cannot easily test the update time slot."
  fi

  echo "change GeoIP database update policy"
  local yesterday_weekday=$(( $current_weekday - 1 ))
  [ $yesterday_weekday -ge 0 ] || yesterday_weekday=6
  sudo sed -i -e "s/^\(CVMFS_UPDATEGEO_HOUR\)=.*$/\1=$current_hour/"     $CVMFS_TEST_595_SERVER_HOOKS || return 35
  sudo sed -i -e "s/^\(CVMFS_UPDATEGEO_DAY\)=.*$/\1=$yesterday_weekday/" $CVMFS_TEST_595_SERVER_HOOKS || return 36

  echo
  echo "$CVMFS_TEST_595_SERVER_HOOKS"
  cat $CVMFS_TEST_595_SERVER_HOOKS
  echo

  echo "do a lazy update as 'root' (should work but refrain from updating)"
  local update_11_log="update_geodb_11.log"
  sudo rm -f $CVMFS_TEST_595_ATTEMPT_FILE
  sudo -E cvmfs_server update-geodb -l > $update_11_log 2>&1 || return 37

  echo "check output logs for the expected status messages"
  cat $update_11_log | grep -e "waiting for install time slot" || return 38

  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  echo "change ownership of $CVMFS_TEST_595_GEODB to '$CVMFS_TEST_USER'"
  group="`id -g ${CVMFS_TEST_USER}`"
  sudo chown ${CVMFS_TEST_USER}:${group} $(dirname $CVMFS_TEST_595_GEODB) || return 39
  sudo chown ${CVMFS_TEST_USER}:${group} $CVMFS_TEST_595_GEODB            || return 40

  echo "try to update the GeoIP database as user '$CVMFS_TEST_USER' (should work and update)"
  local update_12_log="update_geodb_12.log"
  cvmfs_server update-geodb > $update_12_log 2>&1 || return 41

  echo "check output logs for the expected status messages"
  cat $update_12_log | grep -e "Updating GeoIP Database" || return 42

  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  echo "removing $CVMFS_TEST_595_SERVER_HOOKS"
  recreate_server_hooks_from_stashed_if_needed || return 43

  echo "create a stratum1 snapshot (should find database up to date)"
  local snapshot_1_log="snapshot_1.log"
  cvmfs_server snapshot $replica_name > $snapshot_1_log 2>&1 || return 44

  echo "move $CVMFS_TEST_595_GEODB to pwd"
  mv -f $CVMFS_TEST_595_GEODB . || return 45

  echo "try snapshot with a dbfile of NONE"
  local snapshot_2_log="snapshot_2.log"
  CVMFS_GEO_DB_FILE=NONE cvmfs_server snapshot $replica_name > $snapshot_2_log 2>&1 || return 46

  echo "try again with a dbfile of NONE (database should be up to date)"
  local snapshot_3_log="snapshot_3.log"
  CVMFS_GEO_DB_FILE=NONE cvmfs_server snapshot $replica_name > $snapshot_3_log 2>&1 || return 47

  echo "try snapshot with a dbfile pointing to pwd"
  local snapshot_4_log="snapshot_4.log"
  CVMFS_GEO_DB_FILE=$(pwd)/$(basename $CVMFS_TEST_595_GEODB) cvmfs_server snapshot $replica_name > $snapshot_4_log 2>&1 || return 48

  echo "change ownership of $(dirname $CVMFS_TEST_595_GEODB) to 'root'"
  sudo chown root:root $(dirname $CVMFS_TEST_595_GEODB) || return 49

  echo "create a stratum1 snapshot (geo update should fail due to permissions)"
  local snapshot_5_log="snapshot_5.log"
  cvmfs_server snapshot $replica_name > $snapshot_5_log 2>&1 || return 50

  echo "try unprivileged snapshot with auto updates disabled"
  local snapshot_6_log="snapshot_6.log"
  echo CVMFS_GEO_AUTO_UPDATE=false | sudo tee --append $CVMFS_TEST_595_SERVER_HOOKS || return 51
  cvmfs_server snapshot $replica_name > $snapshot_6_log 2>&1 || return 52

  echo "removing $CVMFS_TEST_595_SERVER_HOOKS"
  recreate_server_hooks_from_stashed_if_needed || return 53

  echo "check output logs for the expected status messages"
  test -z "`cat $snapshot_1_log | grep GeoIP`"                                   || return 61
  cat $snapshot_2_log | grep "Linking GeoIP Database"                            || return 62
  test -z "`cat $snapshot_3_log | grep GeoIP`"                                   || return 63
  cat $snapshot_4_log | grep "Linking GeoIP Database"                            || return 64
  cat $snapshot_5_log | grep -e "Directory.*not writable.*$CVMFS_TEST_USER"      || return 65
  cat $snapshot_6_log | grep -e "Directory.*not writable"                        && return 66

  return 0
}
