#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2020 SUSE Linux Products GmbH. All Rights Reserved.
#
# FS QA Test No. ceph/001
#
# Test remote copy operation (CEPH_OSD_OP_COPY_FROM) with several combinations
# of both object sizes and copy sizes.  It also uses several combinations of
# copy ranges.  For example, copying the 1st object in the src file into
# 1) the beginning (1st object) of dst file, 2) the end (last object) of dst
# file and 3) the middle of the dst file.
#
. ./common/preamble
_begin_fstest auto quick copy_range

# get standard environment
. common/filter
. common/attr
. common/reflink

# real QA test starts here
_supported_fs ceph

_require_xfs_io_command "copy_range"
_exclude_test_mount_option "test_dummy_encryption"
_require_attrs
_require_test

workdir=$TEST_DIR/test-$seq
rm -rf $workdir
mkdir $workdir

cluster_fsid=$(_ceph_get_cluster_fsid)
client_id=$(_ceph_get_client_id)
metrics_dir="$DEBUGFS_MNT/ceph/$cluster_fsid.$client_id/metrics"

check_range()
{
	local file=$1
	local off0=$2
	local off1=$3
	local val=$4
	_read_range $file $off0 $off1 | grep -v -q $val
	[ $? -eq 0 ] && echo "file $file is not '$val' in [ $off0 $off1 ]"
}

#
# The metrics file has the following fields:
#   1. item
#   2. total
#   3. avg_sz(bytes)
#   4. min_sz(bytes)
#   5. max_sz(bytes)
#   6. total_sz(bytes)
get_copyfrom_total_copies()
{
	local total=0

	if [ -d $metrics_dir ]; then
		total=$(grep copyfrom $metrics_dir/size | tr -s '[:space:]' | cut -d ' ' -f 2)
	fi
	echo $total
}
get_copyfrom_total_size()
{
	local total=0

	if [ -d $metrics_dir ]; then
		total=$(grep copyfrom $metrics_dir/size | tr -s '[:space:]' | cut -d ' ' -f 6)
	fi
	echo $total
}

# This function checks that the metrics file has the expected values for number
# of remote object copies and the total size of the copies.  For this, it
# expects a input:
#   $1 - initial number copies in metrics file (field 'total')
#   $2 - initial total size in bytes in metrics file (field 'total_sz')
#   $3 - object size used for copies
#   $4 - number of remote objects copied
check_copyfrom_metrics()
{
	local c0=$1
	local s0=$2
	local objsz=$3
	local copies=$4
	local c1=$(get_copyfrom_total_copies)
	local s1=$(get_copyfrom_total_size)
	local hascopyfrom=$(_fs_options $TEST_DEV | grep "copyfrom")
	local sum

	if [ ! -d "$metrics_dir" ]; then
		return # skip metrics check if debugfs isn't mounted
	fi
	if [ -z "$hascopyfrom" ]; then
		return # ... or if we don't have copyfrom mount option
	fi

	sum=$(($c0+$copies))
	if [ $sum -ne $c1 ]; then
		echo "Wrong number of remote copies.  Expected $sum, got $c1"
	fi
	sum=$(($s0+$copies*$objsz))
	if [ $sum -ne $s1 ]; then
		echo "Wrong size of remote copies.  Expected $sum, got $s1"
	fi
}

run_copy_range_tests()
{
	total_copies=$(get_copyfrom_total_copies)
	total_size=$(get_copyfrom_total_size)
	objsz=$1
	halfobj=$(($objsz / 2))
	file="$workdir/file-$objsz"
	copy="$workdir/copy-$objsz"
	dest="$workdir/dest-$objsz"

	# create files and set file layout, which needs to be done before
	# writing any data
	_ceph_create_file_layout $file $objsz 1 $objsz
	_ceph_create_file_layout $copy $objsz 1 $objsz
	_ceph_create_file_layout $dest $objsz 1 $objsz

	# file containing 3 objects with 'aaaa|bbbb|cccc'
	$XFS_IO_PROG -c "pwrite -S 0x61 0 $objsz" $file >> $seqres.full 2>&1
	$XFS_IO_PROG -c "pwrite -S 0x62 $objsz $objsz" $file >> $seqres.full 2>&1
	$XFS_IO_PROG -c "pwrite -S 0x63 $(($objsz * 2)) $objsz" $file >> $seqres.full 2>&1

	echo "  Copy whole file (3 objects):"
	echo "    aaaa|bbbb|cccc => aaaa|bbbb|cccc"
	$XFS_IO_PROG -c "copy_range -s 0 -d 0 -l $(($objsz * 3)) $file" "$copy"
	cmp $file $copy

	echo "  Copy single object to beginning:"
	# dest file with 3 objects with 'dddd|dddd|dddd'
	$XFS_IO_PROG -c "pwrite -S 0x64 0 $(($objsz * 3))" $dest >> $seqres.full 2>&1

	echo "    dddd|dddd|dddd => aaaa|dddd|dddd"
	$XFS_IO_PROG -c "copy_range -s 0 -d 0 -l $objsz $file" "$dest"
	check_range $dest 0 $objsz 61
	check_range $dest $objsz $(($objsz * 2)) 64

	echo "    aaaa|dddd|dddd => bbbb|dddd|dddd"
	$XFS_IO_PROG -c "copy_range -s $objsz -d 0 -l $objsz $file" "$dest"
	check_range $dest 0 $objsz 62
	check_range $dest $objsz $(($objsz * 2)) 64

	echo "    bbbb|dddd|dddd => cccc|dddd|dddd"
	$XFS_IO_PROG -c "copy_range -s $(($objsz * 2)) -d 0 -l $objsz $file" "$dest"
	check_range $dest 0 $objsz 63
	check_range $dest $objsz $(($objsz * 2)) 64

	echo "  Copy single object to middle:"

	echo "    cccc|dddd|dddd => cccc|aaaa|dddd"
	$XFS_IO_PROG -c "copy_range -s 0 -d $objsz -l $objsz $file" "$dest"
	check_range $dest 0 $objsz 63
	check_range $dest $objsz $objsz 61
	check_range $dest $(($objsz * 2)) $objsz 64

	echo "    cccc|aaaa|dddd => cccc|bbbb|dddd"
	$XFS_IO_PROG -c "copy_range -s $objsz -d $objsz -l $objsz $file" "$dest"
	check_range $dest 0 $objsz 63
	check_range $dest $objsz $objsz 62
	check_range $dest $(($objsz * 2)) $objsz 64

	echo "    cccc|bbbb|dddd => cccc|cccc|dddd"
	$XFS_IO_PROG -c "copy_range -s $((objsz * 2)) -d $objsz -l $objsz $file" "$dest"
	check_range $dest 0 $objsz 63
	check_range $dest $objsz $objsz 63
	check_range $dest $(($objsz * 2)) $objsz 64

	echo "  Copy single object to end:"

	echo "    cccc|cccc|dddd => cccc|cccc|aaaa"
	$XFS_IO_PROG -c "copy_range -s 0 -d $(($objsz * 2)) -l $objsz $file" "$dest"
	check_range $dest 0 $(($objsz * 2)) 63
	check_range $dest $(($objsz * 2)) $objsz 61

	echo "    cccc|cccc|aaaa => cccc|cccc|bbbb"
	$XFS_IO_PROG -c "copy_range -s $objsz -d $(($objsz * 2)) -l $objsz $file" "$dest"
	check_range $dest 0 $(($objsz * 2)) 63
	check_range $dest $(($objsz * 2)) $objsz 62

	echo "    cccc|cccc|aaaa => cccc|cccc|cccc"
	$XFS_IO_PROG -c "copy_range -s $(($objsz * 2)) -d $(($objsz * 2)) -l $objsz $file" "$dest"
	check_range $dest 0 $(($objsz * 3)) 63

	echo "  Copy 2 objects to beginning:"

	echo "    cccc|cccc|cccc => aaaa|bbbb|cccc"
	$XFS_IO_PROG -c "copy_range -s 0 -d 0 -l $(($objsz * 2)) $file" "$dest"
	cmp $file $dest

	echo "    aaaa|bbbb|cccc => bbbb|cccc|cccc"
	$XFS_IO_PROG -c "copy_range -s $objsz -d 0 -l $(($objsz * 2)) $file" "$dest"
	check_range $dest 0 $objsz 62
	check_range $dest $objsz $(($objsz * 2)) 63

	echo "  Copy 2 objects to end:"

	echo "    bbbb|cccc|cccc => bbbb|aaaa|bbbb"
	$XFS_IO_PROG -c "copy_range -s 0 -d $objsz -l $(($objsz * 2)) $file" "$dest"
	check_range $dest 0 $objsz 62
	check_range $dest $objsz $objsz 61
	check_range $dest $(($objsz * 2)) $objsz 62

	echo "    bbbb|aaaa|bbbb => bbbb|bbbb|cccc"
	$XFS_IO_PROG -c "copy_range -s $objsz -d $objsz -l $(($objsz * 2)) $file" "$dest"
	check_range $dest 0 $(($objsz * 2)) 62
	check_range $dest $(($objsz * 2)) $objsz 63

	echo "  Append 1 object:"

	echo "    bbbb|bbbb|cccc => bbbb|bbbb|cccc|aaaa"
	$XFS_IO_PROG -c "copy_range -s 0 -d $(($objsz * 3)) -l $objsz $file" "$dest"
	check_range $dest 0 $(($objsz * 2)) 62
	check_range $dest $(($objsz * 2)) $objsz 63
	check_range $dest $(($objsz * 3)) $objsz 61

	echo "  Cross object boundary (no full object copy)"
	echo "    dddd|dddd|dddd|dddd => ddaa|aadd|dddd|dddd"
	$XFS_IO_PROG -c "pwrite -S 0x64 0 $(($objsz * 4))" $dest >> $seqres.full 2>&1
	$XFS_IO_PROG -c "copy_range -s 0 -d $halfobj -l $objsz $file" "$dest"
	check_range $dest 0 $halfobj 64
	check_range $dest $halfobj $objsz 61
	check_range $dest $(($objsz + $halfobj)) $(($objsz * 2 + $halfobj)) 64

	echo "    dddd|dddd|dddd|dddd => ddaa|bbdd|dddd|dddd"
	$XFS_IO_PROG -c "pwrite -S 0x64 0 $(($objsz * 4))" $dest >> $seqres.full 2>&1
	$XFS_IO_PROG -c "copy_range -s $halfobj -d $halfobj -l $objsz $file" "$dest"
	check_range $dest 0 $halfobj 64
	check_range $dest $halfobj $halfobj 61
	check_range $dest $objsz $halfobj 62
	check_range $dest $(($objsz + $halfobj)) $(($objsz * 2 + $halfobj)) 64

	echo "  Cross object boundaries (with full object copy)"
	echo "    dddd|dddd|dddd|dddd => ddaa|bbbb|dddd|dddd"
	$XFS_IO_PROG -c "pwrite -S 0x64 0 $(($objsz * 4))" $dest >> $seqres.full 2>&1
	$XFS_IO_PROG -c "copy_range -s $halfobj -d $halfobj -l $(($objsz + $halfobj)) $file" "$dest"
	check_range $dest 0 $halfobj 64
	check_range $dest $halfobj $halfobj 61
	check_range $dest $objsz $objsz 62
	check_range $dest $(($objsz * 2)) $(($objsz * 2)) 64

	echo "    dddd|dddd|dddd|dddd => ddaa|bbbb|ccdd|dddd"
	$XFS_IO_PROG -c "pwrite -S 0x64 0 $(($objsz * 4))" $dest >> $seqres.full 2>&1
	$XFS_IO_PROG -c "copy_range -s $halfobj -d $halfobj -l $(($objsz * 2)) $file" "$dest"
	check_range $dest 0 $halfobj 64
	check_range $dest $halfobj $halfobj 61
	check_range $dest $objsz $objsz 62
	check_range $dest $(($objsz * 2)) $halfobj 63
	check_range $dest $(($objsz * 2 + $halfobj)) $(($objsz + $halfobj)) 64

	echo "    dddd|dddd|dddd|dddd => dddd|aaaa|bbdd|dddd"
	$XFS_IO_PROG -c "pwrite -S 0x64 0 $(($objsz * 4))" $dest >> $seqres.full 2>&1
	$XFS_IO_PROG -c "copy_range -s 0 -d $objsz -l $(($objsz + $halfobj)) $file" "$dest"
	check_range $dest 0 $objsz 64
	check_range $dest $objsz $objsz 61
	check_range $dest $(($objsz * 2)) $halfobj 62
	check_range $dest $(($objsz * 2 + $halfobj)) $(($objsz + $halfobj)) 64

	echo "  Cross object boundaries (with 2 full object copies)"
	echo "    dddd|dddd|dddd|dddd => ddaa|aabb|bbcc|ccdd"
	$XFS_IO_PROG -c "pwrite -S 0x64 0 $(($objsz * 4))" $dest >> $seqres.full 2>&1
	$XFS_IO_PROG -c "copy_range -s 0 -d $halfobj -l $(($objsz * 3)) $file" "$dest"
	check_range $dest 0 $halfobj 64
	check_range $dest $halfobj $objsz 61
	check_range $dest $(($objsz + $halfobj)) $objsz 62
	check_range $dest $(($objsz * 2 + $halfobj)) $objsz 63
	check_range $dest $(($objsz * 3 + $halfobj)) $halfobj 64

	# Confirm that we've done a total of 24 object copies
	check_copyfrom_metrics $total_copies $total_size $objsz 24
}

echo "Object size: 65536" # CEPH_MIN_STRIPE_UNIT
run_copy_range_tests 65536
echo "Object size: 1M"
run_copy_range_tests 1048576
echo "Object size: 4M"
run_copy_range_tests 4194304
# the max object size is 1TB, but by default OSDs only accept a max of 128M objects
echo "Object size: 128M"
run_copy_range_tests 134217728

# success, all done
status=0
exit
