ut/bdev_mt: add test case for ENOMEM handling with multiple io targets

Bdev layer has been recently refactored to share
internal module channels for *all* bdevs of the
same bdev module. This makes us return ENOMEM for I/O
of one bdev if another bdev of the same module (nvme, split, etc)
is entirely saturated. This is not the behavior we want,
as these bdevs may not have anything in common.

This `issue` has been fixed just now, but to prevent
it coming back again lets add a unit test for it.

This test case creates two bdevs using two separate
io_devices and checks if ENOMEM handling is somehow common
for these two. It must not be.

Change-Id: Ic32ef4b4347b8856d4b9f91107e6f188ad67978e
Suggested-by: Ben Walker <benjamin.walker@intel.com>
Signed-off-by: Dariusz Stojaczyk <dariuszx.stojaczyk@intel.com>
Reviewed-on: https://review.gerrithub.io/409997
Reviewed-by: Ben Walker <benjamin.walker@intel.com>
Reviewed-by: Jim Harris <james.r.harris@intel.com>
Reviewed-by: Daniel Verkamp <daniel.verkamp@intel.com>
Tested-by: SPDK Automated Test System <sys_sgsw@intel.com>
This commit is contained in:
Dariusz Stojaczyk 2018-05-04 05:57:52 +02:00 committed by Daniel Verkamp
parent 7ac765e66b
commit bfb73837de

View File

@ -1046,6 +1046,87 @@ enomem_multi_bdev(void)
teardown_test();
}
static void
enomem_multi_io_target(void)
{
struct spdk_io_channel *io_ch;
struct spdk_bdev_channel *bdev_ch;
struct ut_bdev_channel *ut_ch;
const uint32_t IO_ARRAY_SIZE = 64;
const uint32_t AVAIL = 20;
enum spdk_bdev_io_status status[IO_ARRAY_SIZE];
uint32_t i;
int new_io_device;
struct ut_bdev *second_bdev;
struct spdk_bdev_desc *second_desc;
struct spdk_bdev_channel *second_bdev_ch;
struct spdk_io_channel *second_ch;
int rc;
setup_test();
/* Create new io_target and a second bdev using it */
spdk_io_device_register(&new_io_device, stub_create_ch, stub_destroy_ch,
sizeof(struct ut_bdev_channel));
second_bdev = calloc(1, sizeof(*second_bdev));
SPDK_CU_ASSERT_FATAL(second_bdev != NULL);
register_bdev(second_bdev, "ut_bdev2", &new_io_device);
spdk_bdev_open(&second_bdev->bdev, true, NULL, NULL, &second_desc);
set_thread(0);
io_ch = spdk_bdev_get_io_channel(g_desc);
bdev_ch = spdk_io_channel_get_ctx(io_ch);
ut_ch = spdk_io_channel_get_ctx(bdev_ch->channel);
ut_ch->avail_cnt = AVAIL;
/* Different io_target should imply a different module_ch */
second_ch = spdk_bdev_get_io_channel(second_desc);
second_bdev_ch = spdk_io_channel_get_ctx(second_ch);
SPDK_CU_ASSERT_FATAL(bdev_ch->module_ch != second_bdev_ch->module_ch);
/* Saturate io_target through bdev A. */
for (i = 0; i < AVAIL; i++) {
status[i] = SPDK_BDEV_IO_STATUS_PENDING;
rc = spdk_bdev_read_blocks(g_desc, io_ch, NULL, 0, 1, enomem_done, &status[i]);
CU_ASSERT(rc == 0);
}
CU_ASSERT(TAILQ_EMPTY(&bdev_ch->module_ch->nomem_io));
/* Issue one more I/O to fill ENOMEM list. */
status[AVAIL] = SPDK_BDEV_IO_STATUS_PENDING;
rc = spdk_bdev_read_blocks(g_desc, io_ch, NULL, 0, 1, enomem_done, &status[AVAIL]);
CU_ASSERT(rc == 0);
SPDK_CU_ASSERT_FATAL(!TAILQ_EMPTY(&bdev_ch->module_ch->nomem_io));
/*
* Now submit I/O through the second bdev. This should go through and complete
* successfully because we're using a different io_device underneath.
*/
status[AVAIL] = SPDK_BDEV_IO_STATUS_PENDING;
rc = spdk_bdev_read_blocks(second_desc, second_ch, NULL, 0, 1, enomem_done, &status[AVAIL]);
CU_ASSERT(rc == 0);
SPDK_CU_ASSERT_FATAL(TAILQ_EMPTY(&second_bdev_ch->module_ch->nomem_io));
stub_complete_io(second_bdev->io_target, 1);
/* Cleanup; Complete outstanding I/O. */
stub_complete_io(g_bdev.io_target, AVAIL);
SPDK_CU_ASSERT_FATAL(TAILQ_EMPTY(&bdev_ch->module_ch->nomem_io));
/* Complete the ENOMEM I/O */
stub_complete_io(g_bdev.io_target, 1);
CU_ASSERT(bdev_ch->module_ch->io_outstanding == 0);
SPDK_CU_ASSERT_FATAL(TAILQ_EMPTY(&bdev_ch->module_ch->nomem_io));
CU_ASSERT(bdev_ch->module_ch->io_outstanding == 0);
spdk_put_io_channel(io_ch);
spdk_put_io_channel(second_ch);
spdk_bdev_close(second_desc);
unregister_bdev(second_bdev);
spdk_io_device_unregister(&new_io_device, NULL);
poll_threads();
free(second_bdev);
teardown_test();
}
static void
qos_dynamic_enable_done(void *cb_arg, int status)
{
@ -1177,6 +1258,7 @@ main(int argc, char **argv)
CU_add_test(suite, "io_during_qos_reset", io_during_qos_reset) == NULL ||
CU_add_test(suite, "enomem", enomem) == NULL ||
CU_add_test(suite, "enomem_multi_bdev", enomem_multi_bdev) == NULL ||
CU_add_test(suite, "enomem_multi_io_target", enomem_multi_io_target) == NULL ||
CU_add_test(suite, "qos_dynamic_enable", qos_dynamic_enable) == NULL
) {
CU_cleanup_registry();