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

IMAGE_FEATURES="layering,exclusive-lock,object-map,fast-diff"

clone_v2_enabled() {
    image_spec=$1
    rbd info $image_spec | grep "clone-parent"
}

create_pools() {
    ceph osd pool create images 32
    rbd pool init images
    ceph osd pool create volumes 32
    rbd pool init volumes
}

delete_pools() {
    (ceph osd pool delete images images --yes-i-really-really-mean-it || true) >/dev/null 2>&1
    (ceph osd pool delete volumes volumes --yes-i-really-really-mean-it || true) >/dev/null 2>&1

}

recreate_pools() {
    delete_pools
    create_pools
}

delete_users() {
    (ceph auth del client.volumes || true) >/dev/null 2>&1
    (ceph auth del client.images || true) >/dev/null 2>&1

    (ceph auth del client.snap_none || true) >/dev/null 2>&1
    (ceph auth del client.snap_all || true) >/dev/null 2>&1
    (ceph auth del client.snap_pool || true) >/dev/null 2>&1
    (ceph auth del client.snap_profile_all || true) >/dev/null 2>&1
    (ceph auth del client.snap_profile_pool || true) >/dev/null 2>&1

    (ceph auth del client.mon_write || true) >/dev/null 2>&1
}

create_users() {
    ceph auth get-or-create client.volumes \
	mon 'profile rbd' \
	osd 'profile rbd pool=volumes, profile rbd-read-only pool=images' \
	mgr 'profile rbd pool=volumes, profile rbd-read-only pool=images' >> $KEYRING
    ceph auth get-or-create client.images mon 'profile rbd' osd 'profile rbd pool=images' >> $KEYRING

    ceph auth get-or-create client.snap_none mon 'allow r' >> $KEYRING
    ceph auth get-or-create client.snap_all mon 'allow r' osd 'allow w' >> $KEYRING
    ceph auth get-or-create client.snap_pool mon 'allow r' osd 'allow w pool=images' >> $KEYRING
    ceph auth get-or-create client.snap_pool_namespace mon 'allow r' osd 'allow w pool=images namespace=foo' >> $KEYRING
    ceph auth get-or-create client.snap_namespace mon 'allow r' osd 'allow w namespace=foo' >> $KEYRING
    ceph auth get-or-create client.snap_tag mon 'allow r' osd 'allow w tag fooapp *=*' >> $KEYRING
    ceph auth get-or-create client.snap_profile_all mon 'allow r' osd 'profile rbd' >> $KEYRING
    ceph auth get-or-create client.snap_profile_pool mon 'allow r' osd 'profile rbd pool=images' >> $KEYRING
    ceph auth get-or-create client.snap_profile_pool_namespace mon 'allow r' osd 'profile rbd pool=images namespace=foo' >> $KEYRING
    ceph auth get-or-create client.snap_profile_namespace mon 'allow r' osd 'profile rbd namespace=foo' >> $KEYRING

    ceph auth get-or-create client.mon_write mon 'allow *' >> $KEYRING
}

expect() {

  set +e

  local expected_ret=$1
  local ret

  shift
  cmd=$@

  eval $cmd
  ret=$?

  set -e

  if [[ $ret -ne $expected_ret ]]; then
    echo "ERROR: running \'$cmd\': expected $expected_ret got $ret"
    return 1
  fi

  return 0
}

test_images_access() {
    rbd -k $KEYRING --id images create --image-format 2 --image-feature $IMAGE_FEATURES -s 1 images/foo
    rbd -k $KEYRING --id images snap create images/foo@snap
    rbd -k $KEYRING --id images snap protect images/foo@snap
    rbd -k $KEYRING --id images snap unprotect images/foo@snap
    rbd -k $KEYRING --id images snap protect images/foo@snap
    rbd -k $KEYRING --id images export images/foo@snap - >/dev/null
    expect 16 rbd -k $KEYRING --id images snap rm images/foo@snap

    rbd -k $KEYRING --id volumes clone --image-feature $IMAGE_FEATURES images/foo@snap volumes/child

    if ! clone_v2_enabled images/foo; then
        expect 16 rbd -k $KEYRING --id images snap unprotect images/foo@snap
    fi

    expect 1 rbd -k $KEYRING --id volumes snap unprotect images/foo@snap
    expect 1 rbd -k $KEYRING --id images flatten volumes/child
    rbd -k $KEYRING --id volumes flatten volumes/child
    expect 1 rbd -k $KEYRING --id volumes snap unprotect images/foo@snap
    rbd -k $KEYRING --id images snap unprotect images/foo@snap

    expect 39 rbd -k $KEYRING --id images rm images/foo
    rbd -k $KEYRING --id images snap rm images/foo@snap
    rbd -k $KEYRING --id images rm images/foo
    rbd -k $KEYRING --id volumes rm volumes/child
}

test_volumes_access() {
    rbd -k $KEYRING --id images create --image-format 2 --image-feature $IMAGE_FEATURES -s 1 images/foo
    rbd -k $KEYRING --id images snap create images/foo@snap
    rbd -k $KEYRING --id images snap protect images/foo@snap

    # commands that work with read-only access
    rbd -k $KEYRING --id volumes info images/foo@snap
    rbd -k $KEYRING --id volumes snap ls images/foo
    rbd -k $KEYRING --id volumes export images/foo - >/dev/null
    rbd -k $KEYRING --id volumes cp images/foo volumes/foo_copy
    rbd -k $KEYRING --id volumes rm volumes/foo_copy
    rbd -k $KEYRING --id volumes children images/foo@snap
    rbd -k $KEYRING --id volumes lock list images/foo

    # commands that fail with read-only access
    expect 1 rbd -k $KEYRING --id volumes resize -s 2 images/foo --allow-shrink
    expect 1 rbd -k $KEYRING --id volumes snap create images/foo@2
    expect 1 rbd -k $KEYRING --id volumes snap rollback images/foo@snap
    expect 1 rbd -k $KEYRING --id volumes snap remove images/foo@snap
    expect 1 rbd -k $KEYRING --id volumes snap purge images/foo
    expect 1 rbd -k $KEYRING --id volumes snap unprotect images/foo@snap
    expect 1 rbd -k $KEYRING --id volumes flatten images/foo
    expect 1 rbd -k $KEYRING --id volumes lock add images/foo test
    expect 1 rbd -k $KEYRING --id volumes lock remove images/foo test locker
    expect 1 rbd -k $KEYRING --id volumes ls rbd

    # create clone and snapshot
    rbd -k $KEYRING --id volumes clone --image-feature $IMAGE_FEATURES images/foo@snap volumes/child
    rbd -k $KEYRING --id volumes snap create volumes/child@snap1
    rbd -k $KEYRING --id volumes snap protect volumes/child@snap1
    rbd -k $KEYRING --id volumes snap create volumes/child@snap2

    # make sure original snapshot stays protected
    if clone_v2_enabled images/foo; then
        rbd -k $KEYRING --id volumes flatten volumes/child
        rbd -k $KEYRING --id volumes snap rm volumes/child@snap2
        rbd -k $KEYRING --id volumes snap unprotect volumes/child@snap1
    else
        expect 16 rbd -k $KEYRING --id images snap unprotect images/foo@snap
        rbd -k $KEYRING --id volumes flatten volumes/child
        expect 16 rbd -k $KEYRING --id images snap unprotect images/foo@snap
        rbd -k $KEYRING --id volumes snap rm volumes/child@snap2
        expect 16 rbd -k $KEYRING --id images snap unprotect images/foo@snap
        expect 2 rbd -k $KEYRING --id volumes snap rm volumes/child@snap2
        rbd -k $KEYRING --id volumes snap unprotect volumes/child@snap1
        expect 16 rbd -k $KEYRING --id images snap unprotect images/foo@snap
    fi

    # clean up
    rbd -k $KEYRING --id volumes snap rm volumes/child@snap1
    rbd -k $KEYRING --id images snap unprotect images/foo@snap
    rbd -k $KEYRING --id images snap rm images/foo@snap
    rbd -k $KEYRING --id images rm images/foo
    rbd -k $KEYRING --id volumes rm volumes/child
}

create_self_managed_snapshot() {
  ID=$1
  POOL=$2

  cat << EOF | CEPH_ARGS="-k $KEYRING" python3
import rados

with rados.Rados(conffile="", rados_id="${ID}") as cluster:
  ioctx = cluster.open_ioctx("${POOL}")

  snap_id = ioctx.create_self_managed_snap()
  print ("Created snap id {}".format(snap_id))
EOF
}

remove_self_managed_snapshot() {
  ID=$1
  POOL=$2

  cat << EOF | CEPH_ARGS="-k $KEYRING" python3
import rados

with rados.Rados(conffile="", rados_id="mon_write") as cluster1, \
     rados.Rados(conffile="", rados_id="${ID}") as cluster2:
  ioctx1 = cluster1.open_ioctx("${POOL}")

  snap_id = ioctx1.create_self_managed_snap()
  print ("Created snap id {}".format(snap_id))

  ioctx2 = cluster2.open_ioctx("${POOL}")

  ioctx2.remove_self_managed_snap(snap_id)
  print ("Removed snap id {}".format(snap_id))
EOF
}

test_remove_self_managed_snapshots() {
    # Ensure users cannot create self-managed snapshots w/o permissions
    expect 1 create_self_managed_snapshot snap_none images
    expect 1 create_self_managed_snapshot snap_none volumes

    create_self_managed_snapshot snap_all images
    create_self_managed_snapshot snap_all volumes

    create_self_managed_snapshot snap_pool images
    expect 1 create_self_managed_snapshot snap_pool volumes

    create_self_managed_snapshot snap_pool_namespace images
    expect 1 create_self_managed_snapshot snap_pool_namespace volumes

    create_self_managed_snapshot snap_namespace images
    create_self_managed_snapshot snap_namespace volumes

    expect 1 create_self_managed_snapshot snap_tag images
    expect 1 create_self_managed_snapshot snap_tag volumes

    create_self_managed_snapshot snap_profile_all images
    create_self_managed_snapshot snap_profile_all volumes

    create_self_managed_snapshot snap_profile_pool images
    expect 1 create_self_managed_snapshot snap_profile_pool volumes

    create_self_managed_snapshot snap_profile_pool_namespace images
    expect 1 create_self_managed_snapshot snap_profile_pool_namespace volumes

    create_self_managed_snapshot snap_profile_namespace images
    create_self_managed_snapshot snap_profile_namespace volumes

    # Ensure users cannot delete self-managed snapshots w/o permissions
    expect 1 remove_self_managed_snapshot snap_none images
    expect 1 remove_self_managed_snapshot snap_none volumes

    remove_self_managed_snapshot snap_all images
    remove_self_managed_snapshot snap_all volumes

    remove_self_managed_snapshot snap_pool images
    expect 1 remove_self_managed_snapshot snap_pool volumes

    remove_self_managed_snapshot snap_pool_namespace images
    expect 1 remove_self_managed_snapshot snap_pool_namespace volumes

    remove_self_managed_snapshot snap_namespace images
    remove_self_managed_snapshot snap_namespace volumes

    expect 1 remove_self_managed_snapshot snap_tag images
    expect 1 remove_self_managed_snapshot snap_tag volumes

    remove_self_managed_snapshot snap_profile_all images
    remove_self_managed_snapshot snap_profile_all volumes

    remove_self_managed_snapshot snap_profile_pool images
    expect 1 remove_self_managed_snapshot snap_profile_pool volumes

    remove_self_managed_snapshot snap_profile_pool_namespace images
    expect 1 remove_self_managed_snapshot snap_profile_pool_namespace volumes

    remove_self_managed_snapshot snap_profile_namespace images
    remove_self_managed_snapshot snap_profile_namespace volumes
}

test_rbd_support() {
    # read-only commands should work on both pools
    ceph -k $KEYRING --id volumes rbd perf image stats volumes
    ceph -k $KEYRING --id volumes rbd perf image stats images

    # read/write commands should only work on 'volumes'
    rbd -k $KEYRING --id volumes create --image-format 2 --image-feature $IMAGE_FEATURES -s 1 volumes/foo
    ceph -k $KEYRING --id volumes rbd task add remove volumes/foo
    expect 13 ceph -k $KEYRING --id volumes rbd task add remove images/foo
}

cleanup() {
    rm -f $KEYRING
}

KEYRING=$(mktemp)
trap cleanup EXIT ERR HUP INT QUIT

delete_users
create_users

recreate_pools
test_images_access

recreate_pools
test_volumes_access

test_remove_self_managed_snapshots

test_rbd_support

delete_pools
delete_users

echo OK
exit 0
