Skip to content

Commit 8158665

Browse files
ps-ushankaraxboe
authored andcommitted
selftests: ublk: add generic_06 for covering fault inject
Add one simple fault inject target, and verify if an application using ublk device sees an I/O error quickly after the ublk server dies. Signed-off-by: Uday Shankar <ushankar@purestorage.com> Signed-off-by: Ming Lei <ming.lei@redhat.com> Link: https://lore.kernel.org/r/20250416035444.99569-9-ming.lei@redhat.com Signed-off-by: Jens Axboe <axboe@kernel.dk>
1 parent e63d222 commit 8158665

File tree

5 files changed

+155
-3
lines changed

5 files changed

+155
-3
lines changed

tools/testing/selftests/ublk/Makefile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ TEST_PROGS += test_generic_02.sh
88
TEST_PROGS += test_generic_03.sh
99
TEST_PROGS += test_generic_04.sh
1010
TEST_PROGS += test_generic_05.sh
11+
TEST_PROGS += test_generic_06.sh
1112

1213
TEST_PROGS += test_null_01.sh
1314
TEST_PROGS += test_null_02.sh
@@ -31,7 +32,8 @@ TEST_GEN_PROGS_EXTENDED = kublk
3132

3233
include ../lib.mk
3334

34-
$(TEST_GEN_PROGS_EXTENDED): kublk.c null.c file_backed.c common.c stripe.c
35+
$(TEST_GEN_PROGS_EXTENDED): kublk.c null.c file_backed.c common.c stripe.c \
36+
fault_inject.c
3537

3638
check:
3739
shellcheck -x -f gcc *.sh
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
/*
4+
* Fault injection ublk target. Hack this up however you like for
5+
* testing specific behaviors of ublk_drv. Currently is a null target
6+
* with a configurable delay before completing each I/O. This delay can
7+
* be used to test ublk_drv's handling of I/O outstanding to the ublk
8+
* server when it dies.
9+
*/
10+
11+
#include "kublk.h"
12+
13+
static int ublk_fault_inject_tgt_init(const struct dev_ctx *ctx,
14+
struct ublk_dev *dev)
15+
{
16+
const struct ublksrv_ctrl_dev_info *info = &dev->dev_info;
17+
unsigned long dev_size = 250UL << 30;
18+
19+
dev->tgt.dev_size = dev_size;
20+
dev->tgt.params = (struct ublk_params) {
21+
.types = UBLK_PARAM_TYPE_BASIC,
22+
.basic = {
23+
.logical_bs_shift = 9,
24+
.physical_bs_shift = 12,
25+
.io_opt_shift = 12,
26+
.io_min_shift = 9,
27+
.max_sectors = info->max_io_buf_bytes >> 9,
28+
.dev_sectors = dev_size >> 9,
29+
},
30+
};
31+
32+
dev->private_data = (void *)(unsigned long)(ctx->fault_inject.delay_us * 1000);
33+
return 0;
34+
}
35+
36+
static int ublk_fault_inject_queue_io(struct ublk_queue *q, int tag)
37+
{
38+
const struct ublksrv_io_desc *iod = ublk_get_iod(q, tag);
39+
struct io_uring_sqe *sqe;
40+
struct __kernel_timespec ts = {
41+
.tv_nsec = (long long)q->dev->private_data,
42+
};
43+
44+
ublk_queue_alloc_sqes(q, &sqe, 1);
45+
io_uring_prep_timeout(sqe, &ts, 1, 0);
46+
sqe->user_data = build_user_data(tag, ublksrv_get_op(iod), 0, 1);
47+
48+
ublk_queued_tgt_io(q, tag, 1);
49+
50+
return 0;
51+
}
52+
53+
static void ublk_fault_inject_tgt_io_done(struct ublk_queue *q, int tag,
54+
const struct io_uring_cqe *cqe)
55+
{
56+
const struct ublksrv_io_desc *iod = ublk_get_iod(q, tag);
57+
58+
if (cqe->res != -ETIME)
59+
ublk_err("%s: unexpected cqe res %d\n", __func__, cqe->res);
60+
61+
if (ublk_completed_tgt_io(q, tag))
62+
ublk_complete_io(q, tag, iod->nr_sectors << 9);
63+
else
64+
ublk_err("%s: io not complete after 1 cqe\n", __func__);
65+
}
66+
67+
static void ublk_fault_inject_cmd_line(struct dev_ctx *ctx, int argc, char *argv[])
68+
{
69+
static const struct option longopts[] = {
70+
{ "delay_us", 1, NULL, 0 },
71+
{ 0, 0, 0, 0 }
72+
};
73+
int option_idx, opt;
74+
75+
ctx->fault_inject.delay_us = 0;
76+
while ((opt = getopt_long(argc, argv, "",
77+
longopts, &option_idx)) != -1) {
78+
switch (opt) {
79+
case 0:
80+
if (!strcmp(longopts[option_idx].name, "delay_us"))
81+
ctx->fault_inject.delay_us = strtoll(optarg, NULL, 10);
82+
}
83+
}
84+
}
85+
86+
static void ublk_fault_inject_usage(const struct ublk_tgt_ops *ops)
87+
{
88+
printf("\tfault_inject: [--delay_us us (default 0)]\n");
89+
}
90+
91+
const struct ublk_tgt_ops fault_inject_tgt_ops = {
92+
.name = "fault_inject",
93+
.init_tgt = ublk_fault_inject_tgt_init,
94+
.queue_io = ublk_fault_inject_queue_io,
95+
.tgt_io_done = ublk_fault_inject_tgt_io_done,
96+
.parse_cmd_line = ublk_fault_inject_cmd_line,
97+
.usage = ublk_fault_inject_usage,
98+
};

tools/testing/selftests/ublk/kublk.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ static const struct ublk_tgt_ops *tgt_ops_list[] = {
1212
&null_tgt_ops,
1313
&loop_tgt_ops,
1414
&stripe_tgt_ops,
15+
&fault_inject_tgt_ops,
1516
};
1617

1718
static const struct ublk_tgt_ops *ublk_find_tgt(const char *name)
@@ -1234,7 +1235,7 @@ static void __cmd_create_help(char *exe, bool recovery)
12341235
{
12351236
int i;
12361237

1237-
printf("%s %s -t [null|loop|stripe] [-q nr_queues] [-d depth] [-n dev_id]\n",
1238+
printf("%s %s -t [null|loop|stripe|fault_inject] [-q nr_queues] [-d depth] [-n dev_id]\n",
12381239
exe, recovery ? "recover" : "add");
12391240
printf("\t[--foreground] [--quiet] [-z] [--debug_mask mask] [-r 0|1 ] [-g 0|1]\n");
12401241
printf("\t[-e 0|1 ] [-i 0|1]\n");

tools/testing/selftests/ublk/kublk.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ struct stripe_ctx {
6868
unsigned int chunk_size;
6969
};
7070

71+
struct fault_inject_ctx {
72+
/* fault_inject */
73+
unsigned long delay_us;
74+
};
75+
7176
struct dev_ctx {
7277
char tgt_type[16];
7378
unsigned long flags;
@@ -81,14 +86,18 @@ struct dev_ctx {
8186
unsigned int fg:1;
8287
unsigned int recovery:1;
8388

89+
/* fault_inject */
90+
long long delay_us;
91+
8492
int _evtfd;
8593
int _shmid;
8694

8795
/* built from shmem, only for ublk_dump_dev() */
8896
struct ublk_dev *shadow_dev;
8997

9098
union {
91-
struct stripe_ctx stripe;
99+
struct stripe_ctx stripe;
100+
struct fault_inject_ctx fault_inject;
92101
};
93102
};
94103

@@ -384,6 +393,7 @@ static inline int ublk_queue_use_zc(const struct ublk_queue *q)
384393
extern const struct ublk_tgt_ops null_tgt_ops;
385394
extern const struct ublk_tgt_ops loop_tgt_ops;
386395
extern const struct ublk_tgt_ops stripe_tgt_ops;
396+
extern const struct ublk_tgt_ops fault_inject_tgt_ops;
387397

388398
void backing_file_tgt_deinit(struct ublk_dev *dev);
389399
int backing_file_tgt_init(struct ublk_dev *dev);
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/bin/bash
2+
# SPDX-License-Identifier: GPL-2.0
3+
4+
. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh
5+
6+
TID="generic_06"
7+
ERR_CODE=0
8+
9+
_prep_test "fault_inject" "fast cleanup when all I/Os of one hctx are in server"
10+
11+
# configure ublk server to sleep 2s before completing each I/O
12+
dev_id=$(_add_ublk_dev -t fault_inject -q 2 -d 1 --delay_us 2000000)
13+
_check_add_dev $TID $?
14+
15+
STARTTIME=${SECONDS}
16+
17+
dd if=/dev/urandom of=/dev/ublkb${dev_id} oflag=direct bs=4k count=1 status=none > /dev/null 2>&1 &
18+
dd_pid=$!
19+
20+
__ublk_kill_daemon ${dev_id} "DEAD"
21+
22+
wait $dd_pid
23+
dd_exitcode=$?
24+
25+
ENDTIME=${SECONDS}
26+
ELAPSED=$(($ENDTIME - $STARTTIME))
27+
28+
# assert that dd sees an error and exits quickly after ublk server is
29+
# killed. previously this relied on seeing an I/O timeout and so would
30+
# take ~30s
31+
if [ $dd_exitcode -eq 0 ]; then
32+
echo "dd unexpectedly exited successfully!"
33+
ERR_CODE=255
34+
fi
35+
if [ $ELAPSED -ge 5 ]; then
36+
echo "dd took $ELAPSED seconds to exit (>= 5s tolerance)!"
37+
ERR_CODE=255
38+
fi
39+
40+
_cleanup_test "fault_inject"
41+
_show_result $TID $ERR_CODE

0 commit comments

Comments
 (0)