xref: /linux/tools/testing/selftests/ublk/test_common.sh (revision 69050f8d6d075dc01af7a5f2f550a8067510366f)
1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3
4# Derive TID from script name: test_<type>_<num>.sh -> <type>_<num>
5# Can be overridden in test script after sourcing this file
6TID=$(basename "$0" .sh)
7TID=${TID#test_}
8
9UBLK_SKIP_CODE=4
10
11_have_program() {
12	if command -v "$1" >/dev/null 2>&1; then
13		return 0
14	fi
15	return 1
16}
17
18# Sleep with awareness of parallel execution.
19# Usage: _ublk_sleep <normal_secs> <parallel_secs>
20_ublk_sleep() {
21	if [ "${JOBS:-1}" -gt 1 ]; then
22		sleep "$2"
23	else
24		sleep "$1"
25	fi
26}
27
28_get_disk_dev_t() {
29	local dev_id=$1
30	local dev
31	local major
32	local minor
33
34	dev=/dev/ublkb"${dev_id}"
35	major="0x"$(stat -c '%t' "$dev")
36	minor="0x"$(stat -c '%T' "$dev")
37
38	echo $(( (major & 0xfff) << 20 | (minor & 0xfffff) ))
39}
40
41_get_disk_size()
42{
43	lsblk -b -o SIZE -n "$1"
44}
45
46_run_fio_verify_io() {
47	fio --name=verify --rw=randwrite --direct=1 --ioengine=libaio \
48		--bs=8k --iodepth=32 --verify=crc32c --do_verify=1 \
49		--verify_state_save=0 "$@" > /dev/null
50}
51
52_create_backfile() {
53	local index=$1
54	local new_size=$2
55	local old_file
56	local new_file
57
58	old_file="${UBLK_BACKFILES[$index]}"
59	[ -f "$old_file" ] && rm -f "$old_file"
60
61	new_file=$(mktemp ${UBLK_TEST_DIR}/ublk_file_"${new_size}"_XXXXX)
62	truncate -s "${new_size}" "${new_file}"
63	UBLK_BACKFILES["$index"]="$new_file"
64}
65
66_remove_files() {
67	local file
68
69	for file in "${UBLK_BACKFILES[@]}"; do
70		[ -f "$file" ] && rm -f "$file"
71	done
72	[ -f "$UBLK_TMP" ] && rm -f "$UBLK_TMP"
73}
74
75_create_tmp_dir() {
76	local my_file;
77
78	my_file=$(mktemp -d ${UBLK_TEST_DIR}/ublk_dir_XXXXX)
79	echo "$my_file"
80}
81
82_remove_tmp_dir() {
83	local dir=$1
84
85	[ -d "$dir" ] && rmdir "$dir"
86}
87
88_mkfs_mount_test()
89{
90	local dev=$1
91	local err_code=0
92	local mnt_dir;
93
94	mnt_dir=$(_create_tmp_dir)
95	mkfs.ext4 -F "$dev" > /dev/null 2>&1
96	err_code=$?
97	if [ $err_code -ne 0 ]; then
98		return $err_code
99	fi
100
101	mount -t ext4 "$dev" "$mnt_dir" > /dev/null 2>&1
102	umount "$dev"
103	err_code=$?
104	_remove_tmp_dir "$mnt_dir"
105	if [ $err_code -ne 0 ]; then
106		return $err_code
107	fi
108}
109
110_check_root() {
111	local ksft_skip=4
112
113	if [ $UID != 0 ]; then
114		echo please run this as root >&2
115		exit $ksft_skip
116	fi
117}
118
119_get_ublk_dev_state() {
120	${UBLK_PROG} list -n "$1" | grep "state" | awk '{print $11}'
121}
122
123_get_ublk_daemon_pid() {
124	${UBLK_PROG} list -n "$1" | grep "pid" | awk '{print $7}'
125}
126
127_prep_test() {
128	_check_root
129	local type=$1
130	shift 1
131	modprobe ublk_drv > /dev/null 2>&1
132	local base_dir=${TMPDIR:-./ublktest-dir}
133	mkdir -p "$base_dir"
134	UBLK_TEST_DIR=$(mktemp -d ${base_dir}/${TID}.XXXXXX)
135	UBLK_TMP=$(mktemp ${UBLK_TEST_DIR}/ublk_test_XXXXX)
136	[ "$UBLK_TEST_QUIET" -eq 0 ] && echo "ublk $type: $*"
137	echo "ublk selftest: $TID starting at $(date '+%F %T')" | tee /dev/kmsg
138}
139
140_remove_test_files()
141{
142	local files=$*
143
144	for file in ${files}; do
145		[ -f "${file}" ] && rm -f "${file}"
146	done
147}
148
149_show_result()
150{
151	if [ "$UBLK_TEST_SHOW_RESULT" -ne 0 ]; then
152		if [ "$2" -eq 0 ]; then
153			echo "$1 : [PASS]"
154		elif [ "$2" -eq 4 ]; then
155			echo "$1 : [SKIP]"
156		else
157			echo "$1 : [FAIL]"
158		fi
159	fi
160	if [ "$2" -ne 0 ]; then
161		_remove_files
162		exit "$2"
163	fi
164	return 0
165}
166
167# don't call from sub-shell, otherwise can't exit
168_check_add_dev()
169{
170	local tid=$1
171	local code=$2
172
173	if [ "${code}" -ne 0 ]; then
174		_show_result "${tid}" "${code}"
175	fi
176}
177
178_cleanup_test() {
179	if [ -f "${UBLK_TEST_DIR}/.ublk_devs" ]; then
180		while read -r dev_id; do
181			${UBLK_PROG} del -n "${dev_id}"
182		done < "${UBLK_TEST_DIR}/.ublk_devs"
183		rm -f "${UBLK_TEST_DIR}/.ublk_devs"
184	fi
185
186	_remove_files
187	rmdir ${UBLK_TEST_DIR}
188	echo "ublk selftest: $TID done at $(date '+%F %T')" | tee /dev/kmsg
189}
190
191_have_feature()
192{
193	if  $UBLK_PROG "features" | grep "$1" > /dev/null 2>&1; then
194		return 0
195	fi
196	return 1
197}
198
199_create_ublk_dev() {
200	local dev_id;
201	local cmd=$1
202	local settle=$2
203
204	shift 2
205
206	if [ ! -c /dev/ublk-control ]; then
207		return ${UBLK_SKIP_CODE}
208	fi
209	if echo "$@" | grep -q "\-z"; then
210		if ! _have_feature "ZERO_COPY"; then
211			return ${UBLK_SKIP_CODE}
212		fi
213	fi
214
215	if ! dev_id=$("${UBLK_PROG}" "$cmd" "$@" | grep "dev id" | awk -F '[ :]' '{print $3}'); then
216		echo "fail to add ublk dev $*"
217		return 255
218	fi
219
220	if [ "$settle" = "yes" ]; then
221		udevadm settle --timeout=20
222	fi
223
224	if [[ "$dev_id" =~ ^[0-9]+$ ]]; then
225		echo "$dev_id" >> "${UBLK_TEST_DIR}/.ublk_devs"
226		echo "${dev_id}"
227	else
228		return 255
229	fi
230}
231
232_add_ublk_dev() {
233	_create_ublk_dev "add" "yes" "$@"
234}
235
236_add_ublk_dev_no_settle() {
237	_create_ublk_dev "add" "no" "$@"
238}
239
240_recover_ublk_dev() {
241	local dev_id
242	local state
243
244	dev_id=$(_create_ublk_dev "recover" "yes" "$@")
245	for ((j=0;j<100;j++)); do
246		state=$(_get_ublk_dev_state "${dev_id}")
247		[ "$state" == "LIVE" ] && break
248		sleep 1
249	done
250	echo "$state"
251}
252
253# quiesce device and return ublk device state
254__ublk_quiesce_dev()
255{
256	local dev_id=$1
257	local exp_state=$2
258	local state
259
260	if ! ${UBLK_PROG} quiesce -n "${dev_id}"; then
261		state=$(_get_ublk_dev_state "${dev_id}")
262		return "$state"
263	fi
264
265	for ((j=0;j<100;j++)); do
266		state=$(_get_ublk_dev_state "${dev_id}")
267		[ "$state" == "$exp_state" ] && break
268		sleep 1
269	done
270	echo "$state"
271}
272
273# kill the ublk daemon and return ublk device state
274__ublk_kill_daemon()
275{
276	local dev_id=$1
277	local exp_state=$2
278	local daemon_pid
279	local state
280
281	daemon_pid=$(_get_ublk_daemon_pid "${dev_id}")
282	state=$(_get_ublk_dev_state "${dev_id}")
283
284	for ((j=0;j<100;j++)); do
285		[ "$state" == "$exp_state" ] && break
286		kill -9 "$daemon_pid" > /dev/null 2>&1
287		sleep 1
288		state=$(_get_ublk_dev_state "${dev_id}")
289	done
290	echo "$state"
291}
292
293_ublk_del_dev() {
294	local dev_id=$1
295
296	${UBLK_PROG} del -n "${dev_id}"
297
298	# Remove from tracking file
299	if [ -f "${UBLK_TEST_DIR}/.ublk_devs" ]; then
300		sed -i "/^${dev_id}$/d" "${UBLK_TEST_DIR}/.ublk_devs"
301	fi
302}
303
304__remove_ublk_dev_return() {
305	local dev_id=$1
306
307	_ublk_del_dev "${dev_id}"
308	local res=$?
309	udevadm settle --timeout=20
310	return ${res}
311}
312
313__run_io_and_remove()
314{
315	local dev_id=$1
316	local size=$2
317	local kill_server=$3
318
319	fio --name=job1 --filename=/dev/ublkb"${dev_id}" --ioengine=libaio \
320		--rw=randrw --norandommap --iodepth=256 --size="${size}" --numjobs="$(nproc)" \
321		--runtime=20 --time_based > /dev/null 2>&1 &
322	fio --name=batchjob --filename=/dev/ublkb"${dev_id}" --ioengine=io_uring \
323		--rw=randrw --norandommap --iodepth=256 --size="${size}" \
324		--numjobs="$(nproc)" --runtime=20 --time_based \
325		--iodepth_batch_submit=32 --iodepth_batch_complete_min=32 \
326		--force_async=7 > /dev/null 2>&1 &
327	sleep 2
328	if [ "${kill_server}" = "yes" ]; then
329		local state
330		state=$(__ublk_kill_daemon "${dev_id}" "DEAD")
331		if [ "$state" != "DEAD" ]; then
332			echo "device isn't dead($state) after killing daemon"
333			return 255
334		fi
335	fi
336	if ! __remove_ublk_dev_return "${dev_id}"; then
337		echo "delete dev ${dev_id} failed"
338		return 255
339	fi
340	wait
341}
342
343run_io_and_remove()
344{
345	local size=$1
346	local dev_id
347	shift 1
348
349	dev_id=$(_add_ublk_dev "$@")
350	_check_add_dev "$TID" $?
351
352	[ "$UBLK_TEST_QUIET" -eq 0 ] && echo "run ublk IO vs. remove device(ublk add $*)"
353	if ! __run_io_and_remove "$dev_id" "${size}" "no"; then
354		echo "/dev/ublkc$dev_id isn't removed"
355		exit 255
356	fi
357}
358
359run_io_and_kill_daemon()
360{
361	local size=$1
362	local dev_id
363	shift 1
364
365	dev_id=$(_add_ublk_dev "$@")
366	_check_add_dev "$TID" $?
367
368	[ "$UBLK_TEST_QUIET" -eq 0 ] && echo "run ublk IO vs kill ublk server(ublk add $*)"
369	if ! __run_io_and_remove "$dev_id" "${size}" "yes"; then
370		echo "/dev/ublkc$dev_id isn't removed res ${res}"
371		exit 255
372	fi
373}
374
375run_io_and_recover()
376{
377	local size=$1
378	local action=$2
379	local state
380	local dev_id
381
382	shift 2
383	dev_id=$(_add_ublk_dev "$@")
384	_check_add_dev "$TID" $?
385
386	fio --name=job1 --filename=/dev/ublkb"${dev_id}" --ioengine=libaio \
387		--rw=randread --iodepth=256 --size="${size}" --numjobs=4 \
388		--runtime=20 --time_based > /dev/null 2>&1 &
389	sleep 4
390
391	if [ "$action" == "kill_daemon" ]; then
392		state=$(__ublk_kill_daemon "${dev_id}" "QUIESCED")
393	elif [ "$action" == "quiesce_dev" ]; then
394		state=$(__ublk_quiesce_dev "${dev_id}" "QUIESCED")
395	fi
396	if [ "$state" != "QUIESCED" ]; then
397		echo "device isn't quiesced($state) after $action"
398		return 255
399	fi
400
401	state=$(_recover_ublk_dev -n "$dev_id" "$@")
402	if [ "$state" != "LIVE" ]; then
403		echo "faile to recover to LIVE($state)"
404		return 255
405	fi
406
407	if ! __remove_ublk_dev_return "${dev_id}"; then
408		echo "delete dev ${dev_id} failed"
409		return 255
410	fi
411	wait
412}
413
414
415_ublk_test_top_dir()
416{
417	cd "$(dirname "$0")" && pwd
418}
419
420METADATA_SIZE_PROG="$(_ublk_test_top_dir)/metadata_size"
421
422_get_metadata_size()
423{
424	local dev_id=$1
425	local field=$2
426
427	"$METADATA_SIZE_PROG" "/dev/ublkb$dev_id" | grep "$field" | grep -o "[0-9]*"
428}
429
430UBLK_PROG=$(_ublk_test_top_dir)/kublk
431UBLK_TEST_QUIET=1
432UBLK_TEST_SHOW_RESULT=1
433UBLK_BACKFILES=()
434export UBLK_PROG
435export UBLK_TEST_QUIET
436export UBLK_TEST_SHOW_RESULT
437