#!/usr/bin/env bash
set -e

. $(dirname $0)/../../standalone/ceph-helpers.sh

function list_tests()
{
  echo "AVAILABLE TESTS"
  for i in $TESTS; do
    echo "  $i"
  done
}

function usage()
{
  echo "usage: $0 [-h|-l|-t <testname> [-t <testname>...] [--lttng <testname>...] [--no-cleanup]]"
}

function ncommits()
{
  local image="$1"
  out="$(rbd journal inspect --image ${image} --verbose |& awk \
    '/entries inspected/ {print $1}')"
  if [ -z "$out" ]; then
    out='0'
  fi

  printf '%d' "$out"
}

function zero_ncommits()
{
  local i=0
  local image="$1"
  while [ "$(ncommits ${image})" -ne 0 ]; do
    i="$((i + 1))"
    if [ "$i" -gt 45 ]; then
      printf 'journal is not commited\n'
      rbd journal inspect --image ${image} --verbose
      exit 1
    fi

    sleep 1
  done
}

function wait_for_new_journal()
{
  local old="$1"
  local image="$2"
  local new="$(rbd journal status --image ${image})"
  local i=0
  while [ "$old" == "$new" ]; do
    i="$((i + 1))"
    new="$(rbd journal status --image ${image})"
    if [ "$i" -ge 30 ]; then
      return 1
    fi
    sleep 1
  done

  printf 'debug: %s\n' "$new" >&2
  printf '%s' "$new"
  return 0
}

test_rbd_journal_discard_replay()
{
  if ! rados lspools |& grep -q '^rbd$'; then
    ceph osd pool create rbd
  fi
  image=testrbdjournaldiscard

  rbd create --image-feature exclusive-lock --image-feature journaling \
    --size 128 ${image}
  local journal=$(rbd info ${image} --format=xml 2> /dev/null |
    $XMLSTARLET sel -t -v "//image/journal")
  test -n "${journal}"

  zero_ncommits ${image}

  if ! status="$(wait_for_new_journal ' ' ${image})"; then
    printf 'timeout\n'
    exit 1
  fi

  rbd journal info ${journal}
  rbd journal info --journal ${journal}
  rbd journal info --image ${image}

  rbd journal status ${journal}

  local count=1
  local bs=4096
  local status=""

  rbd-replay replay-${image}.bin

  zero_ncommits ${image}

  if ! status="$(wait_for_new_journal "$status" ${image})"; then
    printf 'timeout\n'
    exit 1
  fi
}

# modprobe nbd (on host)
# mknod /dev/nbd0 b 43 0 (in docker)
lttng_rbd_journal_discard()
{
  if ! rados lspools |& grep -q '^rbd$'; then
    ceph osd pool create rbd
  fi
  image=testrbdjournaldiscard
  device=/dev/nbd0
  lttng create -o ${image} ${image}
  lttng_destroy()
  {
    lttng destroy
  }
  trap lttng_destroy EXIT

  lttng enable-event -u 'librbd:*'
  lttng add-context -u -t pthread_id

  rbd create --image-feature exclusive-lock --image-feature journaling \
    --size 128 ${image}
  local journal=$(rbd info ${image} --format=xml 2> /dev/null |
    $XMLSTARLET sel -t -v "//image/journal")
  test -n "${journal}"

  zero_ncommits ${image}

  if ! status="$(wait_for_new_journal ' ' ${image})"; then
    printf 'timeout\n'
    exit 1
  fi

  rbd journal info ${journal}
  rbd journal info --journal ${journal}
  rbd journal info --image ${image}

  rbd journal status ${journal}

  (
    cat ceph.conf
    printf '\n[global]\n'
    printf '\t%s\n' \
      'rbd discard granularity bytes = 4096' \
      'rbd cache = false' \
      'rbd_tracing = true'
  ) > ceph-tracing.conf
  rbd-nbd --conf ceph-tracing.conf -d --device ${device} map ${image} &
  rbdpid=$!
  unmap()
  {
    set -x
    rbd-nbd -d --device ${device} unmap ${image}
    rm ceph-tracing.conf
    rbd remove ${image}
    lttng_destroy
  }
  trap unmap EXIT

  local arg=""
  if lsblk --help | grep -q noempty; then
    arg=" --noempty "
  fi
  set +e
  while true; do
    if ! kill -0 ${rbdpid}; then
      printf 'rbd-nbd died in the background\n'
      exit 1
    fi
    lsblk ${arg} ${device} > /dev/null 2>&1
    ret="$?"
    if [ "$ret" -eq '32' ]; then
      sleep 0.5
      continue
    elif [ "$ret" -ne '0' ]; then
      printf 'died waiting for %s to appear (%d)\n' "${device}" "${ret}"
      exit 1
    else
      break
    fi
    sleep 0.5
  done
  set -e

  lttng start
  lttng_stop()
  {
    lttng stop
    rm replay-${image}.bin
    rbd-replay-prep ${image}/ust/uid/*/* replay-${image}.bin
    unmap
  }
  trap lttng_stop EXIT

  mkfs.ext4 ${device}
  t="$(mktemp -d)"
  mount ${device} -o discard "$t"
  unmount()
  {
    umount ${device}
    rmdir ${t}
    lttng_stop
  }
  trap unmount EXIT

  local count=1
  local bs=4096
  local status=""

  dd if=/dev/random "of=$t/random" bs=${bs} count=1 oflag=sync

  zero_ncommits ${image}

  if ! status="$(wait_for_new_journal "$status" ${image})"; then
    printf 'timeout\n'
    exit 1
  fi

  rm "$t/random"
  sync "$t"

  zero_ncommits ${image}

  if ! status="$(wait_for_new_journal "$status" ${image})"; then
    printf 'timeout\n'
    exit 1
  fi

  fstrim "$t"

  zero_ncommits ${image}

  if ! status="$(wait_for_new_journal "$status" ${image})"; then
    printf 'timeout\n'
    exit 1
  fi

  dd if=/dev/random "of=$t/random2" bs=${bs} count=1 oflag=sync

  zero_ncommits ${image}

  if ! status="$(wait_for_new_journal "$status" ${image})"; then
    printf 'timeout\n'
    exit 1
  fi

  dd if=/dev/random "of=$t/random3" bs=${bs} count=1 oflag=sync

  zero_ncommits ${image}

  if ! status="$(wait_for_new_journal "$status" ${image})"; then
    printf 'timeout\n'
    exit 1
  fi
}

TESTS+=" rbd_journal_discard_replay"

#
# "main" follows
#

tests_to_run=()

cleanup=true

while [[ $# -gt 0 ]]; do
  opt=$1

  case "$opt" in
    "-l")
      do_list=1
      ;;
    "--no-cleanup")
      cleanup=false
      ;;
    "--lttng")
      shift
      if [[ -z "$1" ]]; then
        echo "missing argument to '--lttng'"
        usage
        exit 1
      fi
      lttng_to_run+=" $1"
      ;;
    "-t")
      shift
      if [[ -z "$1" ]]; then
        echo "missing argument to '-t'"
        usage
        exit 1
      fi
      tests_to_run+=" $1"
      ;;
    "-h")
      usage
      exit 0
      ;;
  esac
  shift
done

if [[ $do_list -eq 1 ]]; then
  list_tests
  exit 0
fi

TMPDIR=/tmp/rbd_journal$$
mkdir $TMPDIR
if $cleanup; then
  trap "rm -fr $TMPDIR" 0
fi

if test -z "$lttng_to_run"; then
  lttng_to_run=""
fi

if test -z "$tests_to_run"; then
  tests_to_run=""
fi

for i in $lttng_to_run; do
  set -x
  lttng_${i}
  set +x
done

for i in $tests_to_run; do
  set -x
  test_${i}
  set +x
done

echo OK
