diff --git a/tests/nvmeof-mp/.gitignore b/tests/nvmeof-mp/.gitignore
new file mode 100644
index 000000000000..ecb6268a585b
--- /dev/null
+++ b/tests/nvmeof-mp/.gitignore
@@ -0,0 +1 @@
+multipath.conf
diff --git a/tests/nvmeof-mp/001 b/tests/nvmeof-mp/001
new file mode 100755
index 000000000000..01e303710ef9
--- /dev/null
+++ b/tests/nvmeof-mp/001
@@ -0,0 +1,50 @@
+#!/bin/bash
+#
+# Copyright (c) 2016-2018 Western Digital Corporation or its affiliates
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc.
+
+. tests/nvmeof-mp/rc
+
+DESCRIPTION="Log in and log out"
+QUICK=1
+
+count_devices() {
+	local d devs=0
+
+	for d in /sys/class/nvme-fabrics/ctl/*/*/device; do
+		[ -d "$d" ] && ((devs++))
+	done
+	echo $devs
+}
+
+wait_for_devices() {
+	local expected=1 i devices
+
+	use_blk_mq y || return $?
+	for ((i=0;i<100;i++)); do
+		devices=$(count_devices)
+		[ "$devices" -ge $expected ] && break
+		sleep .1
+	done
+	echo "count_devices(): $devices <> $expected" >>"$FULL"
+	echo "count_devices(): $devices <> $expected"
+	[ "$devices" -ge $expected ]
+}
+
+test() {
+	trap 'trap "" EXIT; teardown' EXIT
+	setup && wait_for_devices && echo Passed
+}
diff --git a/tests/nvmeof-mp/001.out b/tests/nvmeof-mp/001.out
new file mode 100644
index 000000000000..2ce8d1700ec4
--- /dev/null
+++ b/tests/nvmeof-mp/001.out
@@ -0,0 +1,3 @@
+Configured NVMe target driver
+count_devices(): 1 <> 1
+Passed
diff --git a/tests/nvmeof-mp/002 b/tests/nvmeof-mp/002
new file mode 100755
index 000000000000..84253574f4aa
--- /dev/null
+++ b/tests/nvmeof-mp/002
@@ -0,0 +1,50 @@
+#!/bin/bash
+#
+# Copyright (c) 2016-2018 Western Digital Corporation or its affiliates
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc.
+
+. tests/nvmeof-mp/rc
+
+DESCRIPTION="File I/O on top of multipath concurrently with logout and login (mq)"
+TIMED=1
+
+test_disconnect_repeatedly() {
+	local dev fio_status m
+
+	use_blk_mq y || return $?
+	dev=$(get_bdev 0) || return $?
+	m=$(mountpoint 0) || return $?
+	create_filesystem "$dev" || return $?
+	mount_and_check "$dev" "$m" || return $?
+	# shellcheck disable=SC2064
+	trap "unmount_and_check $m" RETURN
+	simulate_network_failure_loop "$dev" "$TIMEOUT" &
+	run_fio --verify=md5 --rw=randwrite --bs=4K --loops=$((10**6)) \
+		--iodepth=64 --group_reporting --sync=1 --direct=1 \
+		--ioengine=libaio --directory="$m" --runtime="${TIMEOUT}" \
+		--name=data-integrity-test-mq --thread --numjobs=16 \
+		--output="${RESULTS_DIR}/nvmeof-mp/fio-output-002.txt" \
+		>>"$FULL"
+	fio_status=$?
+	wait
+	return $fio_status
+}
+
+test() {
+	: "${TIMEOUT:=30}"
+	trap 'trap "" EXIT; teardown' EXIT
+	setup && test_disconnect_repeatedly && echo Passed
+}
diff --git a/tests/nvmeof-mp/002.out b/tests/nvmeof-mp/002.out
new file mode 100644
index 000000000000..a7d4cb9bcb29
--- /dev/null
+++ b/tests/nvmeof-mp/002.out
@@ -0,0 +1,2 @@
+Configured NVMe target driver
+Passed
diff --git a/tests/nvmeof-mp/004 b/tests/nvmeof-mp/004
new file mode 100755
index 000000000000..87fe98d4407c
--- /dev/null
+++ b/tests/nvmeof-mp/004
@@ -0,0 +1,50 @@
+#!/bin/bash
+#
+# Copyright (c) 2016-2018 Western Digital Corporation or its affiliates
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc.
+
+. tests/nvmeof-mp/rc
+
+DESCRIPTION="File I/O on top of multipath concurrently with logout and login (sq-on-mq)"
+TIMED=1
+
+test_disconnect_repeatedly() {
+	local dev fio_status m
+
+	use_blk_mq n || return $?
+	dev=$(get_bdev 0) || return $?
+	m=$(mountpoint 0) || return $?
+	create_filesystem "$dev" || return $?
+	mount_and_check "$dev" "$m" || return $?
+	# shellcheck disable=SC2064
+	trap "unmount_and_check $m" RETURN
+	simulate_network_failure_loop "$dev" "$TIMEOUT" &
+	run_fio --verify=md5 --rw=randwrite --bs=4K --loops=$((10**6)) \
+		--iodepth=64 --group_reporting --sync=1 --direct=1 \
+		--ioengine=libaio --directory="$m" \
+		--name=data-integrity-test-02-sq-on-mq --thread \
+		--numjobs=16 --runtime="${TIMEOUT}" \
+		--output="${RESULTS_DIR}/nvmeof-mp/fio-output-004.txt" >>"$FULL"
+	fio_status=$?
+	wait
+	return $fio_status
+}
+
+test() {
+	: "${TIMEOUT:=30}"
+	trap 'trap "" EXIT; teardown' EXIT
+	setup && test_disconnect_repeatedly && echo Passed
+}
diff --git a/tests/nvmeof-mp/004.out b/tests/nvmeof-mp/004.out
new file mode 100644
index 000000000000..a7d4cb9bcb29
--- /dev/null
+++ b/tests/nvmeof-mp/004.out
@@ -0,0 +1,2 @@
+Configured NVMe target driver
+Passed
diff --git a/tests/nvmeof-mp/005 b/tests/nvmeof-mp/005
new file mode 100755
index 000000000000..3bd6934bba07
--- /dev/null
+++ b/tests/nvmeof-mp/005
@@ -0,0 +1,40 @@
+#!/bin/bash
+#
+# Copyright (c) 2016-2018 Western Digital Corporation or its affiliates
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc.
+
+. tests/nvmeof-mp/rc
+
+DESCRIPTION="Direct I/O with large transfer sizes and bs=4M"
+QUICK=1
+
+test_large_transfer_size() {
+	local dev m
+
+	use_blk_mq y || return $?
+	dev=$(get_bdev 0) || return $?
+	run_fio --verify=md5 --rw=randwrite --bs=4M --loops=$((10**6)) \
+		--iodepth=4 --group_reporting --sync=1 --direct=1 \
+		--ioengine=libaio \
+		--filename="$dev" --name=large-io-test --thread --numjobs=1 \
+		--runtime=10 --output="${RESULTS_DIR}/nvmeof-mp/fio-output-005.txt" \
+		>>"$FULL"
+}
+
+test() {
+	trap 'trap "" EXIT; teardown' EXIT
+	setup && test_large_transfer_size && echo Passed
+}
diff --git a/tests/nvmeof-mp/005.out b/tests/nvmeof-mp/005.out
new file mode 100644
index 000000000000..a7d4cb9bcb29
--- /dev/null
+++ b/tests/nvmeof-mp/005.out
@@ -0,0 +1,2 @@
+Configured NVMe target driver
+Passed
diff --git a/tests/nvmeof-mp/006 b/tests/nvmeof-mp/006
new file mode 100755
index 000000000000..796e7c6233db
--- /dev/null
+++ b/tests/nvmeof-mp/006
@@ -0,0 +1,40 @@
+#!/bin/bash
+#
+# Copyright (c) 2016-2018 Western Digital Corporation or its affiliates
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc.
+
+. tests/nvmeof-mp/rc
+
+DESCRIPTION="Direct I/O with large transfer sizes and bs=8M"
+QUICK=1
+
+test_large_transfer_size() {
+	local dev m
+
+	use_blk_mq y || return $?
+	dev=$(get_bdev 0) || return $?
+	run_fio --verify=md5 --rw=randwrite --bs=8M --loops=$((10**6)) \
+		--iodepth=4 --group_reporting --sync=1 --direct=1 \
+		--ioengine=libaio \
+		--filename="$dev" --name=large-io-test --thread --numjobs=1 \
+		--runtime=10 --output="${RESULTS_DIR}/nvmeof-mp/fio-output-006.txt" \
+		>>"$FULL"
+}
+
+test() {
+	trap 'trap "" EXIT; teardown' EXIT
+	setup && test_large_transfer_size && echo Passed
+}
diff --git a/tests/nvmeof-mp/006.out b/tests/nvmeof-mp/006.out
new file mode 100644
index 000000000000..a7d4cb9bcb29
--- /dev/null
+++ b/tests/nvmeof-mp/006.out
@@ -0,0 +1,2 @@
+Configured NVMe target driver
+Passed
diff --git a/tests/nvmeof-mp/009 b/tests/nvmeof-mp/009
new file mode 100755
index 000000000000..00849d6a1a3e
--- /dev/null
+++ b/tests/nvmeof-mp/009
@@ -0,0 +1,40 @@
+#!/bin/bash
+#
+# Copyright (c) 2016-2018 Western Digital Corporation or its affiliates
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc.
+
+. tests/nvmeof-mp/rc
+
+DESCRIPTION="Buffered I/O with large transfer sizes and bs=4M"
+QUICK=1
+
+test_large_transfer_size() {
+	local dev m
+
+	use_blk_mq y || return $?
+	dev=$(get_bdev 0) || return $?
+	run_fio --verify=md5 --rw=randwrite --bs=4M --loops=$((10**6)) \
+		--iodepth=4 --group_reporting --sync=1 --direct=0 \
+		--ioengine=libaio \
+		--filename="$dev" --name=large-io-test --thread --numjobs=1 \
+		--runtime=10 --output="${RESULTS_DIR}/nvmeof-mp/fio-output-009.txt" \
+		>>"$FULL"
+}
+
+test() {
+	trap 'trap "" EXIT; teardown' EXIT
+	setup && test_large_transfer_size && echo Passed
+}
diff --git a/tests/nvmeof-mp/009.out b/tests/nvmeof-mp/009.out
new file mode 100644
index 000000000000..a7d4cb9bcb29
--- /dev/null
+++ b/tests/nvmeof-mp/009.out
@@ -0,0 +1,2 @@
+Configured NVMe target driver
+Passed
diff --git a/tests/nvmeof-mp/010 b/tests/nvmeof-mp/010
new file mode 100755
index 000000000000..70bc422bd46c
--- /dev/null
+++ b/tests/nvmeof-mp/010
@@ -0,0 +1,40 @@
+#!/bin/bash
+#
+# Copyright (c) 2016-2018 Western Digital Corporation or its affiliates
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc.
+
+. tests/nvmeof-mp/rc
+
+DESCRIPTION="Buffered I/O with large transfer sizes and bs=8M"
+QUICK=1
+
+test_large_transfer_size() {
+	local dev m
+
+	use_blk_mq y || return $?
+	dev=$(get_bdev 0) || return $?
+	run_fio --verify=md5 --rw=randwrite --bs=8M --loops=$((10**6)) \
+		--iodepth=4 --group_reporting --sync=1 --direct=0 \
+		--ioengine=libaio \
+		--filename="$dev" --name=large-io-test --thread --numjobs=1 \
+		--runtime=10 --output="${RESULTS_DIR}/nvmeof-mp/fio-output-010.txt" \
+		>>"$FULL"
+}
+
+test() {
+	trap 'trap "" EXIT; teardown' EXIT
+	setup && test_large_transfer_size && echo Passed
+}
diff --git a/tests/nvmeof-mp/010.out b/tests/nvmeof-mp/010.out
new file mode 100644
index 000000000000..a7d4cb9bcb29
--- /dev/null
+++ b/tests/nvmeof-mp/010.out
@@ -0,0 +1,2 @@
+Configured NVMe target driver
+Passed
diff --git a/tests/nvmeof-mp/011 b/tests/nvmeof-mp/011
new file mode 100755
index 000000000000..4dfe275afeb0
--- /dev/null
+++ b/tests/nvmeof-mp/011
@@ -0,0 +1,46 @@
+#!/bin/bash
+#
+# Copyright (c) 2016-2018 Western Digital Corporation or its affiliates
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc.
+
+. tests/nvmeof-mp/rc
+
+DESCRIPTION="Block I/O on top of multipath concurrently with logout and login"
+TIMED=1
+
+test_disconnect_repeatedly() {
+	local dev fio_status m
+
+	use_blk_mq y || return $?
+	dev=$(get_bdev 0) || return $?
+	simulate_network_failure_loop "$dev" "$TIMEOUT" &
+	run_fio --verify=md5 --rw=randwrite --bs=4K --loops=10000 \
+		--ioengine=libaio --iodepth=64 --iodepth_batch=32 \
+		--group_reporting --sync=1 --direct=1 --filename="$dev" \
+		--name=data-integrity-test-06 --thread --numjobs=1 \
+		--runtime="${TIMEOUT}" \
+		--output="${RESULTS_DIR}/nvmeof-mp/fio-output-011.txt" \
+		>>"$FULL"
+	fio_status=$?
+	wait
+	return $fio_status
+}
+
+test() {
+	: "${TIMEOUT:=30}"
+	trap 'trap "" EXIT; teardown' EXIT
+	setup && test_disconnect_repeatedly && echo Passed
+}
diff --git a/tests/nvmeof-mp/011.out b/tests/nvmeof-mp/011.out
new file mode 100644
index 000000000000..a7d4cb9bcb29
--- /dev/null
+++ b/tests/nvmeof-mp/011.out
@@ -0,0 +1,2 @@
+Configured NVMe target driver
+Passed
diff --git a/tests/nvmeof-mp/012 b/tests/nvmeof-mp/012
new file mode 100755
index 000000000000..f00c49d20188
--- /dev/null
+++ b/tests/nvmeof-mp/012
@@ -0,0 +1,53 @@
+#!/bin/bash
+#
+# Copyright (c) 2018 Western Digital Corporation or its affiliates
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc.
+
+. tests/nvmeof-mp/rc
+
+DESCRIPTION="dm-mpath on top of multiple I/O schedulers"
+QUICK=1
+
+test_io_schedulers() {
+	local dev m
+
+	# Load all I/O scheduler kernel modules
+	for m in "/lib/modules/$(uname -r)/kernel/block/"*.ko; do
+		modprobe "$(basename "$m")" >&/dev/null
+	done
+	for mq in y n; do
+		use_blk_mq ${mq} || return $?
+		dev=$(get_bdev 0) || return $?
+		for sched in noop deadline bfq cfq; do
+			set_scheduler "$(basename "$(readlink -f "${dev}")")" $sched \
+				      >>"$FULL" 2>&1 || continue
+			echo "I/O scheduler: $sched; use_blk_mq: $mq" >>"$FULL"
+			run_fio --verify=md5 --rw=randwrite --bs=4K --size=64K \
+				--ioengine=libaio --iodepth=64 \
+				--iodepth_batch=32 --group_reporting --sync=1 \
+				--direct=1 --filename="$dev" \
+				--name=${sched} --thread --numjobs=1 \
+				--output="${RESULTS_DIR}/nvmeof-mp/012-${sched}-${mq}.txt" \
+				>>"$FULL" ||
+				return $?
+		done
+	done
+}
+
+test() {
+	trap 'trap "" EXIT; teardown' EXIT
+	setup && test_io_schedulers && echo Passed
+}
diff --git a/tests/nvmeof-mp/012.out b/tests/nvmeof-mp/012.out
new file mode 100644
index 000000000000..a7d4cb9bcb29
--- /dev/null
+++ b/tests/nvmeof-mp/012.out
@@ -0,0 +1,2 @@
+Configured NVMe target driver
+Passed
diff --git a/tests/nvmeof-mp/getuid_callout b/tests/nvmeof-mp/getuid_callout
new file mode 100755
index 000000000000..ac4bbca9b6ab
--- /dev/null
+++ b/tests/nvmeof-mp/getuid_callout
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+if [ "${1#nvme}" != "$1" ]; then
+	wwid=$(<"/sys/block/$1/wwid")
+	echo "${wwid#uuid.}"
+else
+	for e in /usr/lib/udev/scsi_id /lib/udev/scsi_id; do
+		[ -e "$e" ] && break
+	done
+	"$e" --whitelisted "/dev/$1"
+fi
diff --git a/tests/nvmeof-mp/multipath.conf.in b/tests/nvmeof-mp/multipath.conf.in
new file mode 100644
index 000000000000..cda6620c14c4
--- /dev/null
+++ b/tests/nvmeof-mp/multipath.conf.in
@@ -0,0 +1,28 @@
+defaults {
+	find_multipaths		no
+	user_friendly_names	yes
+	queue_without_daemon	no
+	getuid_callout          "$PWD/tests/nvmeof-mp/getuid_callout %n"
+}
+devices {
+	device {
+		vendor		"LIO-ORG|SCST_BIO|FUSIONIO"
+		product		".*"
+		features	"1 queue_if_no_path"
+		path_checker	tur
+	}
+}
+blacklist {
+	device {
+		vendor	"ATA|QEMU"
+	}
+	device {
+		vendor	"Linux"
+		product	"scsi_debug"
+	}
+	devnode	"^nullb.*"
+}
+blacklist_exceptions {
+	property	".*"
+	devnode		"^nvme"
+}
diff --git a/tests/nvmeof-mp/rc b/tests/nvmeof-mp/rc
new file mode 100755
index 000000000000..582dc5bf2ec3
--- /dev/null
+++ b/tests/nvmeof-mp/rc
@@ -0,0 +1,936 @@
+#!/bin/bash
+#
+# Copyright (c) 2018 Western Digital Corporation or its affiliates
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc.
+
+. common/rc
+
+namespace=(1)
+memtotal=$(sed -n 's/^MemTotal:[[:blank:]]*\([0-9]*\)[[:blank:]]*kB$/\1/p' /proc/meminfo)
+max_ramdisk_size=$((1<<25))
+ramdisk_size=$((memtotal*(1024/16)))  # in bytes
+if [ $ramdisk_size -gt $max_ramdisk_size ]; then
+	ramdisk_size=$max_ramdisk_size
+fi
+elevator=none
+filesystem_type=ext4
+fio_aux_path=/tmp/fio-state-files
+nvme_subsysnqn="nvme-test"
+nvme_port=7777
+ini_timeout=1
+
+expand() {
+	sed "s,\$PWD,$PWD,g" <"$1" >"$2"
+}
+
+group_requires() {
+	local m name p required_modules
+
+	# Since the nvmeof-mp tests are based on the dm-mpath driver, these
+	# tests are incompatible with the NVME_MULTIPATH kernel configuration
+	# option.
+	_have_kernel_option NVME_MULTIPATH && exit 1
+
+	_have_configfs || return $?
+	required_modules=(
+		dm_multipath
+		dm_queue_length
+		dm_service_time
+		null_blk
+		rdma_cm
+		ib_ipoib
+		ib_umad
+		nvme-rdma
+		nvmet-rdma
+		rdma_rxe
+		scsi_dh_alua
+		scsi_dh_emc
+		scsi_dh_rdac
+	)
+	for m in "${required_modules[@]}"; do
+		_have_module "$m" || return $?
+	done
+
+	for p in mkfs.ext4 mkfs.xfs multipath multipathd pidof sg_reset; do
+		_have_program "$p" || return $?
+	done
+
+	_have_root || return $?
+
+	_have_kernel_option DM_UEVENT || return $?
+
+	for name in multipathd multipathd; do
+		if pidof "$name" >/dev/null; then
+			SKIP_REASON="$name must be stopped before the nvmeof-mp tests are run"
+			return 1
+		fi
+	done
+	if [ tests/nvmeof-mp/multipath.conf.in -nt \
+	     tests/nvmeof-mp/multipath.conf ]; then
+		expand tests/nvmeof-mp/multipath.conf.in \
+		       tests/nvmeof-mp/multipath.conf
+	fi
+	if [ -e /etc/multipath.conf ] &&
+	    ! diff -q /etc/multipath.conf tests/nvmeof-mp/multipath.conf >&/dev/null
+	then
+		SKIP_REASON="/etc/multipath.conf already exists"
+		return 1
+	fi
+}
+
+# Log out, set dm and SCSI use_blk_mq parameters and log in. $1: device mapper
+# use_blk_mq mode; $2..${$#}: NVMeOF initiator kernel module parameters.
+use_blk_mq() {
+	local dm_mode=$1 kmod_params
+
+	shift
+	kmod_params=("$@")
+
+	(
+		cd /sys/module/dm_mod/parameters || return $?
+		if [ -e use_blk_mq ]; then
+			echo "$dm_mode" >use_blk_mq || return $?
+		fi
+	)
+
+	log_out &&
+		remove_mpath_devs &&
+		stop_client &&
+		start_client "${kmod_params[@]}" &&
+		log_in
+}
+
+get_ipv4_addr() {
+	ip -4 -o addr show dev "$1" |
+		sed -n 's/.*[[:blank:]]inet[[:blank:]]*\([^[:blank:]/]*\).*/\1/p'
+}
+
+# Convert e.g. ::1 into 0000:0000:0000:0000:0000:0000:0000:0001.
+expand_ipv6_addr() {
+	awk -F : 'BEGIN{left=1} { for(i=1;i<=NF;i++) { a=substr("0000", 1+length($i)) $i; if ($i == "") left=0; else if (left) pre = pre ":" a; else suf = suf ":" a }; mid=substr(":0000:0000:0000:0000:0000:0000:0000:0000", (pre!="")+length(pre)+length(suf)); print substr(pre,2) mid suf}'
+}
+
+get_ipv6_addr() {
+	ip -6 -o addr show dev "$1" |
+		sed -n 's/.*[[:blank:]]inet6[[:blank:]]*\([^[:blank:]/]*\).*/\1/p'
+}
+
+# Whether or not $1 is a number.
+is_number() {
+	[ "$1" -eq "0$1" ] 2>/dev/null
+}
+
+# Check whether a device is an RDMA device. An example argument:
+# /sys/devices/pci0000:00/0000:00:03.0/0000:04:00.0
+is_rdma_device() {
+	local d i inode1 inode2
+
+	inode1=$(stat -c %i "$1")
+	# echo "inode1 = $inode1"
+	for i in /sys/class/infiniband/*; do
+		d=/sys/class/infiniband/"$(readlink "$i")"
+		d=$(dirname "$(dirname "$d")")
+		inode2=$(stat -c %i "$d")
+		# echo "inode2 = $inode2"
+		if [ "$inode1" = "$inode2" ]; then
+			return
+		fi
+	done
+	false
+}
+
+# Lists RDMA capable network interface names, e.g. ib0 ib1.
+rdma_network_interfaces() {
+	(
+		cd /sys/class/net &&
+			for i in *; do
+				[ -e "$i" ] || continue
+				# Skip IPoIB (ARPHRD_INFINIBAND) network
+				# interfaces.
+				[ "$(<"$i"/type)" = 32 ] && continue
+				[ -L "$i/device" ] || continue
+				d=$(readlink "$i/device" 2>/dev/null)
+				if [ -n "$d" ] && is_rdma_device "$i/$d"; then
+					echo "$i"
+				fi
+			done
+	)
+}
+
+log_in() {
+	local i ipv4_addr
+
+	[ -c /dev/nvme-fabrics ] &&
+		for i in $(rdma_network_interfaces); do
+			ipv4_addr=$(get_ipv4_addr "$i")
+			if [ -n "${ipv4_addr}" ]; then
+				{ echo -n "transport=rdma,traddr=${ipv4_addr},trsvcid=${nvme_port},nqn=$nvme_subsysnqn" > /dev/nvme-fabrics; } 2>>"$FULL"
+			fi
+		done &&
+		echo reconfigure | multipathd -k >&/dev/null
+}
+
+log_out() {
+	local c
+
+	for c in /sys/class/nvme-fabrics/ctl/*/delete_controller; do
+		[ -e "$c" ] && echo 1 > "$c" &
+	done
+	wait
+}
+
+# Check whether any stacked block device holds block device $1. If so, echo
+# the name of the holder.
+held_by() {
+	local d e dev=$1
+
+	while [ -L "$dev" ]; do
+		dev=$(realpath "$dev")
+	done
+	dev=${dev%/dev/}
+	for d in /sys/class/block/*/holders/*; do
+		[ -e "$d" ] || continue
+		e=$(basename "$d")
+		if [ "$e" = "$dev" ]; then
+			echo "/dev/$(basename "$(dirname "$(dirname "$d")")")"
+		fi
+	done
+}
+
+# System uptime in seconds.
+uptime_s() {
+	local a b
+
+	echo "$(</proc/uptime)" | { read -r a b && echo "${a%%.*}"; }
+}
+
+# Sleep until either $1 seconds have elapsed or until the deadline $2 has been
+# reached. Return 1 if and only if the deadline has been met.
+sleep_until() {
+	local duration=$1 deadline=$2 u
+
+	u=$(uptime_s)
+	if [ $((u + duration)) -le "$deadline" ]; then
+		sleep "$duration"
+	else
+		[ "$deadline" -gt "$u" ] && sleep $((deadline - u))
+		return 1
+	fi
+}
+
+# Simulate network failures for device $1 during $2 seconds.
+simulate_network_failure_loop() {
+	local d dev="$1" duration="$2" deadline i rc=0 s
+
+	[ -e "$dev" ] || return $?
+	[ -n "$duration" ] || return $?
+	deadline=$(($(uptime_s) + duration))
+	s=5
+	while [ $rc = 0 ]; do
+		sleep_until 5 ${deadline} || break
+		for d in $(held_by "$dev"); do
+			echo 1 >"$d/device/reset_controller"
+		done
+	done
+
+	for ((i=0;i<5;i++)); do
+		log_in && break
+		sleep 1
+	done
+}
+
+# Kill all processes that have opened block device $1.
+stop_bdev_users() {
+	[ -n "$1" ] || return $?
+	lsof -F p "$1" 2>/dev/null | while read -r line; do
+		p="${line#p}"
+		if [ "$p" != "$line" ]; then
+			echo -n " (pid $p)" >>"$FULL"
+			kill -9 "$p"
+		fi
+	done
+}
+
+# RHEL 6 dmsetup accepts mpath<n> but not /dev/dm-<n> as its first argument.
+# Hence this function that converts /dev/dm-<n> into mpath<n>.
+dev_to_mpath() {
+	local d e mm
+
+	d="${1#/dev/mapper/}";
+	if [ "$d" != "$1" ]; then
+		echo "$d"
+		return 0
+	fi
+
+	[ -e "$1" ] || return $?
+
+	if [ -L "$1" ]; then
+		e=$(readlink -f "$1")
+	else
+		e="$1"
+	fi
+	if ! mm=$(stat -c %t:%T "$e"); then
+		echo "stat $1 -> $e failed"
+		return 1
+	fi
+
+	for d in /dev/mapper/mpath*; do
+		if [ -L "$d" ]; then
+			e=$(readlink -f "$d")
+		elif [ -e "$d" ]; then
+			e="$d"
+		else
+			continue
+		fi
+		if [ "$(stat -c %t:%T "$e")" = "$mm" ]; then
+			basename "$d"
+			return 0
+		fi
+	done
+	return 1
+}
+
+# Modify mpath device $1 to fail_if_no_path mode, unmount the filesystem on top
+# of it and remove the mpath device.
+remove_mpath_dev() {
+	local cmd dm i output t1 t2
+
+	{
+		for ((i=10;i>0;i--)); do
+			cmd="dm=\$(dev_to_mpath \"$1\")"
+			if ! eval "$cmd"; then
+				echo "$cmd: failed"
+			else
+				t1=$(dmsetup table "$dm")
+				cmd="dmsetup message $dm 0 fail_if_no_path"
+				if ! eval "$cmd"; then
+					echo "$cmd: failed"
+				else
+					t2=$(dmsetup table "$dm")
+					if echo "$t2" | grep -qw queue_if_no_path; then
+						echo "$dm: $t1 -> $t2"
+					fi
+					echo "Attempting to unmount /dev/mapper/$dm"
+					umount "/dev/mapper/$dm"
+					cmd="dmsetup remove $dm"
+					if ! output=$(eval "$cmd" 2>&1); then
+						echo "$cmd: $output; retrying"
+					else
+						echo "done"
+						break
+					fi
+				fi
+			fi
+			if [ ! -e "$1" ]; then
+				break
+			fi
+			ls -l "$1"
+			stop_bdev_users "$(readlink -f "$1")"
+			sleep .5
+		done
+		if [ $i = 0 ]; then
+			echo "failed"
+			return 1
+		fi
+	} &>>"$FULL"
+}
+
+# Check whether one or more arguments contain stale device nodes (/dev/...).
+mpath_has_stale_dev() {
+	local d
+
+	for d in "$@"; do
+		if [ "${d/://}" != "$d" ]; then
+			grep -qw "$d" /sys/class/block/*/dev 2>/dev/null ||
+				return 0
+		fi
+	done
+
+	return 1
+}
+
+# Check whether multipath definition $1 includes the queue_if_no_path keyword.
+is_qinp_def() {
+	case "$1" in
+		" 3 queue_if_no_path queue_mode mq ")
+			return 0;;
+		" 1 queue_if_no_path ")
+			return 0;;
+		*)
+			return 1;;
+	esac
+}
+
+remove_nvme_mpath_devs() {
+	local dm h
+
+	for h in /sys/class/block/nvme*/holders/*; do
+		[ -e "$h" ] || continue
+		d=$(basename "$(dirname "$(dirname "$h")")")
+		dm=/dev/$(basename "$h")
+		{
+			echo -n "NVME dev $d: removing $dm: "
+			dmsetup remove "$(dev_to_mpath "$dm")" && echo "done"
+		} &>> "$FULL"
+	done
+	{
+		# Find all multipaths with one or more deleted devices and
+		# remove these
+		echo "Examining all multipaths"
+		dmsetup table | while read -r mpdev fs ls type def; do
+			echo "$fs $ls" >/dev/null
+			# shellcheck disable=SC2086
+			if [ "$type" = multipath ] &&
+				   { is_qinp_def "$def" ||
+					     mpath_has_stale_dev $def; }; then
+				echo "${mpdev%:}"
+			fi
+		done |
+			sort -u |
+			while read -r mpdev; do
+				mpdev="/dev/mapper/$mpdev"
+				echo -n "removing $mpdev: "
+				if ! remove_mpath_dev "$mpdev"; then
+					echo "failed"
+				fi
+			done
+		echo "Finished examining multipaths"
+	} &>> "$FULL"
+}
+
+remove_mpath_devs() {
+	remove_nvme_mpath_devs
+}
+
+# Arguments: module to unload ($1) and retry count ($2).
+unload_module() {
+	local i m=$1 rc=${2:-1}
+
+	[ ! -e "/sys/module/$m" ] && return 0
+	for ((i=rc;i>0;i--)); do
+		modprobe -r "$m"
+		[ ! -e "/sys/module/$m" ] && return 0
+		sleep .1
+	done
+	return 1
+}
+
+start_nvme_client() {
+	modprobe nvme dyndbg=+pmf &&
+		modprobe nvme-core dyndbg=+pmf &&
+		modprobe nvme-fabrics dyndbg=+pmf &&
+		modprobe nvme-rdma dyndbg=+pmf &&
+		mkdir -p "$(mountpoint 0)"
+}
+
+stop_nvme_client() {
+	unload_module nvme_rdma &&
+		unload_module nvme
+}
+
+# Load the initiator kernel driver with kernel module parameters $1..$n.
+start_client() {
+	start_nvme_client "$@"
+}
+
+stop_client() {
+	stop_nvme_client
+}
+
+# Load the configfs kernel module and mount it.
+mount_configfs() {
+	if [ ! -e /sys/module/configfs ]; then
+		modprobe configfs || return $?
+	fi
+	if ! mount | grep -qw configfs; then
+		mount -t configfs none /sys/kernel/config || return $?
+	fi
+}
+
+# Get the name of the initiator device node that communicates with target
+# device $1. $1 is an index in the $namespace array.
+get_nvme_bdev() {
+	(
+		cd /sys/kernel/config/nvmet/subsystems || return $?
+		uuid=$(<"${nvme_subsysnqn}/namespaces/${namespace[$1]}/device_uuid")
+		by_id=/dev/disk/by-id/nvme-uuid.$uuid
+		[ -e "${by_id}" ] && readlink -f "${by_id}"
+	)
+}
+
+# Get a the uuid or wwid of device $1. $1 is an index in the $namespace array.
+get_bdev_uid() {
+	local i=$1
+
+	is_number "$i" || return $?
+	bdev=$(get_nvme_bdev "$i") || return $?
+	echo "$(<"/sys/block/${bdev#/dev/}/wwid")"
+}
+
+# Set scheduler of block device $1 to $2.
+set_scheduler() {
+	local b=$1 p s=$2
+
+	p=/sys/class/block/$b/queue/scheduler
+	if [ -e "/sys/block/$b/mq" ]; then
+		case "$s" in
+			noop)        s=none;;
+			deadline)    s=mq-deadline;;
+			bfq)         s=bfq;;
+		esac
+	else
+		case "$s" in
+			none)        s=noop;;
+			mq-deadline) s=deadline;;
+			bfq-mq)      s=bfq;;
+		esac
+	fi
+	if ! echo "$s" > "$p"; then
+		echo "Changing scheduler of $b from $(<"$p") into $s failed"
+		return 1
+	fi
+}
+
+# Get a /dev/... path that points at dm device number $1. $1 is an index in
+# the $namespace array.
+get_bdev() {
+	local b d dev h i=$1 j realdev
+
+	is_number "$i" || return $?
+	echo reconfigure | multipathd -k >&/dev/null
+	for ((j=0;j<50;j++)); do
+		dev="/dev/disk/by-id/dm-uuid-mpath-$(get_bdev_uid "$i")" ||
+			continue
+		[ -e "$dev" ] && break
+		sleep .1
+	done
+	if [ ! -e "$dev" ]; then
+		echo "$dev: not found"
+		return 1
+	fi
+	if [ ! -L "$dev" ]; then
+		echo "$dev: not a soft link"
+		return 1
+	fi
+	realdev=$(readlink "$dev" 2>/dev/null || echo "?")
+	echo "Using $dev -> ${realdev}" >>"$FULL"
+	for ((j=0; j<50; j++)); do
+		blockdev --getbsz "$dev" >&/dev/null && break
+		echo reconfigure | multipathd -k >& /dev/null
+		sleep .1
+	done
+	if ! blockdev --getbsz "$dev" >&/dev/null; then
+		return 1
+	fi
+	b=$(basename "$realdev")
+	set_scheduler "$b" "${elevator}"
+	for d in /sys/class/block/*"/holders/$b"; do
+		[ -e "$d" ] || continue
+		h="$(basename "$(dirname "$(dirname "$d")")")"
+		set_scheduler "$h" "${elevator}"
+		if [ -e "/sys/class/block/$h/device/timeout" ]; then
+			echo $ini_timeout > "/sys/class/block/$h/device/timeout"
+		fi
+	done
+	echo "$dev"
+}
+
+function mountpoint() {
+	if [ -z "$TMPDIR" ]; then
+		echo "Error: \$TMPDIR has not been set." 1>&2
+		exit 1
+	fi
+	if [ -z "$1" ]; then
+		echo "Error: missing argument" 1>&2
+		exit 1
+	fi
+	echo "$TMPDIR/mnt$1"
+}
+
+all_primary_gids() {
+	find /sys/devices -name infiniband | while read -r p; do
+		cat "$p"/*/ports/*/gids/0
+	done | grep -v ':0000:0000:0000:0000$'
+}
+
+# Check whether or not an rdma_rxe instance has been associated with network
+# interface $1.
+has_rdma_rxe() {
+	local f
+
+	for f in /sys/class/infiniband/*/parent; do
+		if [ -e "$f" ] && [ "$(<"$f")" = "$1" ]; then
+			return 0
+		fi
+	done
+
+	return 1
+}
+
+# Load the rdma_rxe kernel module and associate it with all network interfaces.
+start_rdma_rxe() {
+	{
+		modprobe rdma_rxe || return $?
+		(
+			cd /sys/class/net &&
+				for i in *; do
+					if [ -e "$i" ] && ! has_rdma_rxe "$i"; then
+						echo "$i" > /sys/module/rdma_rxe/parameters/add
+					fi
+				done
+		)
+	} >>"$FULL"
+}
+
+# Dissociate the rdma_rxe kernel module from all network interfaces and unload
+# the rdma_rxe kernel module.
+stop_rdma_rxe() {
+	(
+		cd /sys/class/net &&
+			for i in *; do
+				if [ -e "$i" ] && has_rdma_rxe "$i"; then
+					{ echo "$i" > /sys/module/rdma_rxe/parameters/remove; } \
+						2>/dev/null
+				fi
+			done
+	)
+	if ! unload_module rdma_rxe; then
+		echo "Unloading rdma_rxe failed"
+		return 1
+	fi
+}
+
+scsi_debug_dev_path() {
+	local bd="" d
+
+	for d in /sys/bus/pseudo/drivers/scsi_debug/adapter*/host*/target*/*/block/*; do
+		[ -e "$d" ] || continue
+		bd=${d/*\//}
+	done
+	[ -n "$bd" ] || return 1
+	echo "/dev/$bd"
+}
+
+configure_nvmet_port() {
+	local p=$1 ipv4_addr=$2 i
+
+	echo "Configuring $p with address $ipv4_addr as an NVMeOF target port" \
+	     >>"$FULL"
+	(
+		cd /sys/kernel/config/nvmet/ports &&
+			for ((i=1;1;i++)); do [ -e "$i" ] || break; done &&
+			mkdir "$i" &&
+			cd "$i" &&
+			echo ipv4            > addr_adrfam &&
+			echo rdma            > addr_trtype &&
+			echo -n "$ipv4_addr" > addr_traddr &&
+			echo -n ${nvme_port} > addr_trsvcid
+	)
+}
+
+start_nvme_target() {
+	local d i ipv4_addr num_ports=0
+
+	modprobe nvme dyndbg=+pmf &&
+		modprobe nvmet-rdma dyndbg=+pmf &&
+		sleep .1 &&
+		(
+			cd /sys/kernel/config/nvmet/subsystems &&
+				mkdir ${nvme_subsysnqn} &&
+				cd ${nvme_subsysnqn} &&
+				cd namespaces &&
+				mkdir "${namespace[0]}" &&
+				cd "${namespace[0]}" &&
+				echo 00000000-0000-0000-0000-000000000000 >device_nguid &&
+				echo -n /dev/nullb0 >device_path &&
+				echo 1 >enable &&
+				cd ../.. &&
+				echo 1 >attr_allow_any_host
+		) && for i in $(rdma_network_interfaces); do
+			ipv4_addr=$(get_ipv4_addr "$i")
+			if [ -n "${ipv4_addr}" ]; then
+				configure_nvmet_port "$i" "${ipv4_addr}"
+				((num_ports++))
+				true
+			fi
+		done &&
+		if [ $num_ports = 0 ]; then
+			echo "No NVMeOF target ports"
+			false
+		fi && (
+			cd /sys/kernel/config/nvmet/ports &&
+				for i in *; do
+					[ -e "$i" ] && (
+						cd "$i/subsystems" &&
+							ln -s "../../../subsystems/${nvme_subsysnqn}" .
+					)
+				done
+		)
+	echo "Configured NVMe target driver"
+}
+
+stop_nvme_target() {
+	local d
+
+	(
+		cd /sys/kernel/config/nvmet 2>/dev/null &&
+			rm -f -- ports/*/subsystems/* &&
+			for d in {*/*/*/*,*/*}; do
+				[ -e "$d" ] &&
+					[ "$(basename "$(dirname "$d")")" != ana_groups ] &&
+					rmdir "$d"
+			done
+	)
+	unload_module nvmet_rdma &&
+		unload_module nvmet &&
+		unload_null_blk
+}
+
+start_target() {
+	start_rdma_rxe
+	(
+		echo "RDMA interfaces:"
+		cd /sys/class/infiniband &&
+			for i in *; do
+				[ -e "$i" ] || continue
+				for p in "$i/ports/"*; do
+					echo "$i, port $(basename "$p"): $(<"$p/gids/0")"
+				done
+			done
+	) &>>"$FULL"
+	start_nvme_target
+}
+
+stop_target() {
+	stop_nvme_target || return $?
+	stop_rdma_rxe || return $?
+}
+
+# Look up the block device below the filesystem for directory $1.
+block_dev_of_dir() {
+	df "$1" | {
+		read -r header
+		echo "$header" >/dev/null
+		read -r blockdev rest
+		echo "$blockdev"
+	}
+}
+
+create_filesystem() {
+	local dev=$1
+
+	case "$filesystem_type" in
+		ext4)
+			mkfs.ext4 -F -O ^has_journal -q "$dev";;
+		xfs)
+			mkfs.xfs -f -q "$dev";;
+		*)
+			return 1;;
+	esac
+}
+
+is_mountpoint() {
+	[ -n "$1" ] &&
+		[ -d "$1" ] &&
+		[ "$(block_dev_of_dir "$1")" != \
+					     "$(block_dev_of_dir "$(dirname "$1")")" ]
+}
+
+# Execute mount "$@" and check whether the mount command has succeeded by
+# verifying whether after mount has finished that ${$#} is a mountpoint.
+mount_and_check() {
+	local dir last
+
+	dir=$(for last; do :; done; echo "$last")
+	mount "$@"
+	if ! is_mountpoint "$dir"; then
+		echo "Error: mount $* failed"
+		return 1
+	fi
+}
+
+# Unmount the filesystem mounted at mountpoint $1. In contrast with the umount
+# command, this function does not accept a block device as argument.
+unmount_and_check() {
+	local bd m=$1 mp
+
+	if is_mountpoint "$m"; then
+		bd=$(block_dev_of_dir "$m")
+		mp=$(dev_to_mpath "$bd") 2>/dev/null
+		if [ -n "$mp" ]; then
+			dmsetup message "$mp" 0 fail_if_no_path
+		fi
+		stop_bdev_users "$bd"
+		echo "Unmounting $m from $bd" >> "$FULL"
+		umount "$m" || umount --lazy "$m"
+	fi
+	if is_mountpoint "$m"; then
+		echo "Error: unmounting $m failed"
+		return 1
+	fi
+}
+
+# Test whether fio supports command-line options "$@"
+test_fio_opt() {
+	local opt
+
+	for opt in "$@"; do
+		opt=${opt//=*}
+		fio --help |& grep -q -- "${opt}=" && continue
+		opt=${opt#--}
+		fio --cmdhelp=all |& grep -q "^${opt}[[:blank:]]" && continue
+		return 1
+	done
+}
+
+run_fio() {
+	local a args avail_kb="" bd="" d="" j opt output
+
+	args=("$@")
+	j=1
+	for opt in "${args[@]}"; do
+		case "$opt" in
+			--directory=*) d="${opt#--directory=}";;
+			--filename=*)  bd="${opt#--filename=}";;
+			--numjobs=*)   j="${opt#--numjobs=}";;
+			--output=*)    output="${opt#--output=}";;
+		esac
+	done
+	if [ -n "$d" ]; then
+		a=$(df "$d" | grep "^/" |
+			    {
+				    if read -r fs blocks used avail use mnt; then
+					    echo "$avail"
+					    echo "$fs $blocks $used $use $mnt" >/dev/null
+				    fi
+			    }
+		 )
+		avail_kb=$a
+	fi
+	if [ -n "$bd" ]; then
+		avail_kb=$(("$(blockdev --getsz "$bd")" / 2))
+	fi
+	if [ -n "$avail_kb" ]; then
+		args+=("--size=$(((avail_kb * 1024 * 7 / 10) / j & ~4095))")
+	fi
+	for opt in --exitall_on_error=1 --gtod_reduce=1 --aux-path=${fio_aux_path}
+	do
+		if test_fio_opt "$opt"; then
+			args+=("$opt")
+		fi
+	done
+	mkdir -p "${fio_aux_path}"
+	echo "fio ${args[*]}" >>"$FULL"
+	fio "${args[@]}" 2>&1 || return $?
+	if [ -n "$output" ]; then
+		# Return exit code 1 if no I/O has been performed.
+		grep -q ', io=[0-9].*, run=[0-9]' "$output"
+	fi
+}
+
+# Configure two null_blk instances.
+configure_null_blk() {
+	local i
+
+	modprobe null_blk nr_devices=0 || return $?
+	(
+		cd /sys/kernel/config/nullb || return $?
+		for i in nullb0 nullb1; do (
+			{ mkdir -p $i &&
+				  cd $i &&
+				  echo 0 > completion_nsec &&
+				  echo 512 > blocksize &&
+				  echo $((ramdisk_size>>20)) > size &&
+				  echo 1 > memory_backed &&
+				  echo 1 > power; } || exit $?
+		) done
+	)
+	ls -l /dev/nullb* &>>"$FULL"
+}
+
+unload_null_blk() {
+	local d
+
+	for d in /sys/kernel/config/nullb/*; do [ -d "$d" ] && rmdir "$d"; done
+	unload_module null_blk
+}
+
+shutdown_client() {
+	remove_mpath_devs &&
+		log_out &&
+		stop_client
+}
+
+# Undo setup()
+teardown() {
+	killall -9 multipathd >&/dev/null
+	rm -f /etc/multipath.conf
+	stop_target
+	unload_null_blk
+}
+
+# Set up test configuration
+setup() {
+	local i m modules
+
+	set -u
+
+	shutdown_client || return $?
+
+	if ! teardown; then
+		echo "teardown() failed"
+		return 1
+	fi
+
+	modules=(
+		configfs
+		dm-multipath
+		dm_mod
+		scsi_dh_alua
+		scsi_dh_emc
+		scsi_dh_rdac
+		scsi_mod
+	)
+	for m in "${modules[@]}"; do
+		[ -e "/sys/module/$m" ] || modprobe "$m" || return $?
+	done
+
+	configure_null_blk
+
+	if [ ! -e /etc/multipath.conf ]; then
+		(
+			srcdir=$PWD
+			cd /etc && ln -s "$srcdir/tests/nvmeof-mp/multipath.conf" .
+		)
+	fi
+	multipathd
+
+	# Load the I/O scheduler kernel modules
+	(
+		cd "/lib/modules/$(uname -r)/kernel/block" &&
+			for m in *.ko; do
+				[ -e "$m" ] && modprobe "${m%.ko}"
+			done
+	)
+
+	if [ -d /sys/kernel/debug/dynamic_debug ]; then
+		for m in ; do
+			echo "module $m +pmf" >/sys/kernel/debug/dynamic_debug/control
+		done
+	fi
+
+	start_target
+}
