From cabbe1b1792b7e0d7671f76f5fbdb9ce307179e8 Mon Sep 17 00:00:00 2001 From: Mateusz Kozlowski Date: Wed, 21 Aug 2019 10:38:53 +0200 Subject: [PATCH] bdev/zone: Register/unregister zoned bdev Add registration and unregistration of block zoned bdev. Attach it to the underlying bdev during creation and unattach at deletion. Signed-off-by: Mateusz Kozlowski Change-Id: I773aff6c7609952f28c02dd1794f0529a781b2e1 Reviewed-on: https://review.gerrithub.io/c/spdk/spdk/+/468033 Community-CI: Broadcom SPDK FC-NVMe CI Tested-by: SPDK CI Jenkins Reviewed-by: Shuhei Matsumoto Reviewed-by: Tomasz Zawadzki Reviewed-by: Jim Harris --- mk/spdk.lib_deps.mk | 2 +- module/bdev/zone_block/vbdev_zone_block.c | 424 ++++++++++++- test/unit/lib/bdev/Makefile | 2 +- .../lib/bdev/vbdev_zone_block.c/.gitignore | 1 + .../unit/lib/bdev/vbdev_zone_block.c/Makefile | 38 ++ .../vbdev_zone_block.c/vbdev_zone_block_ut.c | 569 ++++++++++++++++++ test/unit/unittest.sh | 1 + 7 files changed, 1021 insertions(+), 16 deletions(-) create mode 100644 test/unit/lib/bdev/vbdev_zone_block.c/.gitignore create mode 100644 test/unit/lib/bdev/vbdev_zone_block.c/Makefile create mode 100644 test/unit/lib/bdev/vbdev_zone_block.c/vbdev_zone_block_ut.c diff --git a/mk/spdk.lib_deps.mk b/mk/spdk.lib_deps.mk index 2b1d63d8a..1741f94e9 100644 --- a/mk/spdk.lib_deps.mk +++ b/mk/spdk.lib_deps.mk @@ -112,7 +112,6 @@ DEPDIRS-bdev_gpt := bdev conf json log thread util DEPDIRS-bdev_lvol := $(BDEV_DEPS) lvol blob blob_bdev DEPDIRS-bdev_rpc := $(BDEV_DEPS) -DEPDIRS-bdev_zone_block := $(BDEV_DEPS) DEPDIRS-bdev_error := $(BDEV_DEPS_CONF) DEPDIRS-bdev_malloc := $(BDEV_DEPS_CONF) copy @@ -120,6 +119,7 @@ DEPDIRS-bdev_split := $(BDEV_DEPS_CONF) DEPDIRS-bdev_compress := $(BDEV_DEPS_THREAD) reduce DEPDIRS-bdev_delay := $(BDEV_DEPS_THREAD) +DEPDIRS-bdev_zone_block := $(BDEV_DEPS_THREAD) DEPDIRS-bdev_aio := $(BDEV_DEPS_CONF_THREAD) DEPDIRS-bdev_crypto := $(BDEV_DEPS_CONF_THREAD) diff --git a/module/bdev/zone_block/vbdev_zone_block.c b/module/bdev/zone_block/vbdev_zone_block.c index 7c29ea5d6..e56d1f957 100644 --- a/module/bdev/zone_block/vbdev_zone_block.c +++ b/module/bdev/zone_block/vbdev_zone_block.c @@ -40,6 +40,374 @@ #include "spdk_internal/log.h" +static int zone_block_init(void); +static int zone_block_get_ctx_size(void); +static void zone_block_finish(void); +static int zone_block_config_json(struct spdk_json_write_ctx *w); +static void zone_block_examine(struct spdk_bdev *bdev); + +static struct spdk_bdev_module bdev_zoned_if = { + .name = "bdev_zoned_block", + .module_init = zone_block_init, + .module_fini = zone_block_finish, + .config_text = NULL, + .config_json = zone_block_config_json, + .examine_config = zone_block_examine, + .get_ctx_size = zone_block_get_ctx_size, +}; + +SPDK_BDEV_MODULE_REGISTER(bdev_zoned_block, &bdev_zoned_if) + +/* List of block vbdev names and their base bdevs via configuration file. + * Used so we can parse the conf once at init and use this list in examine(). + */ +struct bdev_zone_block_config { + char *vbdev_name; + char *bdev_name; + uint64_t zone_capacity; + uint64_t optimal_open_zones; + TAILQ_ENTRY(bdev_zone_block_config) link; +}; +static TAILQ_HEAD(, bdev_zone_block_config) g_bdev_configs = TAILQ_HEAD_INITIALIZER(g_bdev_configs); + +/* List of block vbdevs and associated info for each. */ +struct bdev_zone_block { + struct spdk_bdev bdev; /* the block zoned bdev */ + struct spdk_bdev_desc *base_desc; /* its descriptor we get from open */ + uint64_t zone_capacity; /* zone capacity */ + TAILQ_ENTRY(bdev_zone_block) link; +}; +static TAILQ_HEAD(, bdev_zone_block) g_bdev_nodes = TAILQ_HEAD_INITIALIZER(g_bdev_nodes); + +struct zone_block_io_channel { + struct spdk_io_channel *base_ch; /* IO channel of base device */ +}; + +struct zone_block_io { + /* bdev IO was issued to */ + struct bdev_zone_block *bdev_zone_block; +}; + +static int +zone_block_init(void) +{ + return 0; +} + +static void +zone_block_remove_config(struct bdev_zone_block_config *name) +{ + TAILQ_REMOVE(&g_bdev_configs, name, link); + free(name->bdev_name); + free(name->vbdev_name); + free(name); +} + +static void +zone_block_finish(void) +{ + struct bdev_zone_block_config *name; + + while ((name = TAILQ_FIRST(&g_bdev_configs))) { + zone_block_remove_config(name); + } +} + +static int +zone_block_get_ctx_size(void) +{ + return sizeof(struct zone_block_io); +} + +static int +zone_block_config_json(struct spdk_json_write_ctx *w) +{ + struct bdev_zone_block *bdev_node; + struct spdk_bdev *base_bdev = NULL; + + TAILQ_FOREACH(bdev_node, &g_bdev_nodes, link) { + base_bdev = spdk_bdev_desc_get_bdev(bdev_node->base_desc); + spdk_json_write_object_begin(w); + spdk_json_write_named_string(w, "method", "bdev_zone_block_create"); + spdk_json_write_named_object_begin(w, "params"); + spdk_json_write_named_string(w, "base_bdev", spdk_bdev_get_name(base_bdev)); + spdk_json_write_named_string(w, "name", spdk_bdev_get_name(&bdev_node->bdev)); + spdk_json_write_named_uint64(w, "zone_capacity", bdev_node->zone_capacity); + spdk_json_write_named_uint64(w, "optimal_open_zones", bdev_node->bdev.optimal_open_zones); + spdk_json_write_object_end(w); + spdk_json_write_object_end(w); + } + + return 0; +} + +/* Callback for unregistering the IO device. */ +static void +_device_unregister_cb(void *io_device) +{ + struct bdev_zone_block *bdev_node = io_device; + + free(bdev_node->bdev.name); + free(bdev_node); +} + +static int +zone_block_destruct(void *ctx) +{ + struct bdev_zone_block *bdev_node = (struct bdev_zone_block *)ctx; + + TAILQ_REMOVE(&g_bdev_nodes, bdev_node, link); + + /* Unclaim the underlying bdev. */ + spdk_bdev_module_release_bdev(spdk_bdev_desc_get_bdev(bdev_node->base_desc)); + + /* Close the underlying bdev. */ + spdk_bdev_close(bdev_node->base_desc); + + /* Unregister the io_device. */ + spdk_io_device_unregister(bdev_node, _device_unregister_cb); + + return 0; +} + +static void +zone_block_submit_request(struct spdk_io_channel *ch, struct spdk_bdev_io *bdev_io) +{ + spdk_bdev_io_complete(bdev_io, SPDK_BDEV_IO_STATUS_FAILED); +} + +static bool +zone_block_io_type_supported(void *ctx, enum spdk_bdev_io_type io_type) +{ + return false; +} + +static struct spdk_io_channel * +zone_block_get_io_channel(void *ctx) +{ + struct bdev_zone_block *bdev_node = (struct bdev_zone_block *)ctx; + + return spdk_get_io_channel(bdev_node); +} + +static int +zone_block_dump_info_json(void *ctx, struct spdk_json_write_ctx *w) +{ + struct bdev_zone_block *bdev_node = (struct bdev_zone_block *)ctx; + struct spdk_bdev *base_bdev = spdk_bdev_desc_get_bdev(bdev_node->base_desc); + + spdk_json_write_name(w, "zoned_block"); + spdk_json_write_object_begin(w); + spdk_json_write_named_string(w, "name", spdk_bdev_get_name(&bdev_node->bdev)); + spdk_json_write_named_string(w, "base_bdev", spdk_bdev_get_name(base_bdev)); + spdk_json_write_named_uint64(w, "zone_capacity", bdev_node->zone_capacity); + spdk_json_write_named_uint64(w, "optimal_open_zones", bdev_node->bdev.optimal_open_zones); + spdk_json_write_object_end(w); + + return 0; +} + +/* When we register our vbdev this is how we specify our entry points. */ +static const struct spdk_bdev_fn_table zone_block_fn_table = { + .destruct = zone_block_destruct, + .submit_request = zone_block_submit_request, + .io_type_supported = zone_block_io_type_supported, + .get_io_channel = zone_block_get_io_channel, + .dump_info_json = zone_block_dump_info_json, +}; + +static void +zone_block_base_bdev_hotremove_cb(void *ctx) +{ + struct bdev_zone_block *bdev_node, *tmp; + struct spdk_bdev *bdev_find = ctx; + + TAILQ_FOREACH_SAFE(bdev_node, &g_bdev_nodes, link, tmp) { + if (bdev_find == spdk_bdev_desc_get_bdev(bdev_node->base_desc)) { + spdk_bdev_unregister(&bdev_node->bdev, NULL, NULL); + } + } +} + +static int +_zone_block_ch_create_cb(void *io_device, void *ctx_buf) +{ + struct zone_block_io_channel *bdev_ch = ctx_buf; + struct bdev_zone_block *bdev_node = io_device; + + bdev_ch->base_ch = spdk_bdev_get_io_channel(bdev_node->base_desc); + if (!bdev_ch->base_ch) { + return -ENOMEM; + } + + return 0; +} + +static void +_zone_block_ch_destroy_cb(void *io_device, void *ctx_buf) +{ + struct zone_block_io_channel *bdev_ch = ctx_buf; + + spdk_put_io_channel(bdev_ch->base_ch); +} + +static int +zone_block_insert_name(const char *bdev_name, const char *vbdev_name, uint64_t zone_capacity, + uint64_t optimal_open_zones) +{ + struct bdev_zone_block_config *name; + + TAILQ_FOREACH(name, &g_bdev_configs, link) { + if (strcmp(vbdev_name, name->vbdev_name) == 0) { + SPDK_ERRLOG("block zoned bdev %s already exists\n", vbdev_name); + return -EEXIST; + } + if (strcmp(bdev_name, name->bdev_name) == 0) { + SPDK_ERRLOG("base bdev %s already claimed\n", bdev_name); + return -EEXIST; + } + } + + name = calloc(1, sizeof(*name)); + if (!name) { + SPDK_ERRLOG("could not allocate bdev_names\n"); + return -ENOMEM; + } + + name->bdev_name = strdup(bdev_name); + if (!name->bdev_name) { + SPDK_ERRLOG("could not allocate name->bdev_name\n"); + free(name); + return -ENOMEM; + } + + name->vbdev_name = strdup(vbdev_name); + if (!name->vbdev_name) { + SPDK_ERRLOG("could not allocate name->vbdev_name\n"); + free(name->bdev_name); + free(name); + return -ENOMEM; + } + + name->zone_capacity = zone_capacity; + name->optimal_open_zones = optimal_open_zones; + + TAILQ_INSERT_TAIL(&g_bdev_configs, name, link); + + return 0; +} + +static int +zone_block_register(struct spdk_bdev *base_bdev) +{ + struct bdev_zone_block_config *name, *tmp; + struct bdev_zone_block *bdev_node; + int rc = 0; + + /* Check our list of names from config versus this bdev and if + * there's a match, create the bdev_node & bdev accordingly. + */ + TAILQ_FOREACH_SAFE(name, &g_bdev_configs, link, tmp) { + if (strcmp(name->bdev_name, base_bdev->name) != 0) { + continue; + } + + if (spdk_bdev_is_zoned(base_bdev)) { + SPDK_ERRLOG("Base bdev %s is already a zoned bdev\n", base_bdev->name); + rc = -EEXIST; + goto free_config; + } + + bdev_node = calloc(1, sizeof(struct bdev_zone_block)); + if (!bdev_node) { + rc = -ENOMEM; + SPDK_ERRLOG("could not allocate bdev_node\n"); + goto free_config; + } + + /* The base bdev that we're attaching to. */ + bdev_node->bdev.name = strdup(name->vbdev_name); + if (!bdev_node->bdev.name) { + rc = -ENOMEM; + SPDK_ERRLOG("could not allocate bdev_node name\n"); + goto strdup_failed; + } + bdev_node->bdev.product_name = "zone_block"; + + /* Copy some properties from the underlying base bdev. */ + bdev_node->bdev.write_cache = base_bdev->write_cache; + bdev_node->bdev.required_alignment = base_bdev->required_alignment; + bdev_node->bdev.optimal_io_boundary = base_bdev->optimal_io_boundary; + bdev_node->bdev.blocklen = base_bdev->blocklen; + bdev_node->bdev.blockcnt = base_bdev->blockcnt; + bdev_node->bdev.write_unit_size = base_bdev->write_unit_size; + + bdev_node->bdev.md_interleave = base_bdev->md_interleave; + bdev_node->bdev.md_len = base_bdev->md_len; + bdev_node->bdev.dif_type = base_bdev->dif_type; + bdev_node->bdev.dif_is_head_of_md = base_bdev->dif_is_head_of_md; + bdev_node->bdev.dif_check_flags = base_bdev->dif_check_flags; + + bdev_node->bdev.zoned = true; + bdev_node->bdev.ctxt = bdev_node; + bdev_node->bdev.fn_table = &zone_block_fn_table; + bdev_node->bdev.module = &bdev_zoned_if; + + /* bdev specific info */ + bdev_node->bdev.zone_size = spdk_align64pow2(name->zone_capacity); + if (bdev_node->bdev.zone_size == 0) { + rc = -EINVAL; + SPDK_ERRLOG("invalid zone size\n"); + goto roundup_failed; + } + + bdev_node->zone_capacity = name->zone_capacity; + bdev_node->bdev.optimal_open_zones = name->optimal_open_zones; + bdev_node->bdev.max_open_zones = 0; + TAILQ_INSERT_TAIL(&g_bdev_nodes, bdev_node, link); + + spdk_io_device_register(bdev_node, _zone_block_ch_create_cb, _zone_block_ch_destroy_cb, + sizeof(struct zone_block_io_channel), + name->vbdev_name); + + rc = spdk_bdev_open(base_bdev, true, zone_block_base_bdev_hotremove_cb, + base_bdev, &bdev_node->base_desc); + if (rc) { + SPDK_ERRLOG("could not open bdev %s\n", spdk_bdev_get_name(base_bdev)); + goto open_failed; + } + + rc = spdk_bdev_module_claim_bdev(base_bdev, bdev_node->base_desc, bdev_node->bdev.module); + if (rc) { + SPDK_ERRLOG("could not claim bdev %s\n", spdk_bdev_get_name(base_bdev)); + goto claim_failed; + } + + rc = spdk_bdev_register(&bdev_node->bdev); + if (rc) { + SPDK_ERRLOG("could not register zoned bdev\n"); + goto register_failed; + } + } + + return rc; + +register_failed: + spdk_bdev_module_release_bdev(&bdev_node->bdev); +claim_failed: + spdk_bdev_close(bdev_node->base_desc); +open_failed: + TAILQ_REMOVE(&g_bdev_nodes, bdev_node, link); + spdk_io_device_unregister(bdev_node, NULL); +roundup_failed: + free(bdev_node->bdev.name); +strdup_failed: + free(bdev_node); +free_config: + zone_block_remove_config(name); + return rc; +} + int spdk_vbdev_zone_block_create(const char *bdev_name, const char *vbdev_name, uint64_t zone_capacity, uint64_t optimal_open_zones) @@ -47,18 +415,6 @@ spdk_vbdev_zone_block_create(const char *bdev_name, const char *vbdev_name, uint struct spdk_bdev *bdev = NULL; int rc = 0; - bdev = spdk_bdev_get_by_name(bdev_name); - - if (!bdev) { - SPDK_ERRLOG("Base bdev (%s) doesn't exist\n", bdev_name); - return -ENODEV; - } - - if (spdk_bdev_is_zoned(bdev)) { - SPDK_ERRLOG("Base bdev %s is already a zoned bdev\n", bdev_name); - return -EEXIST; - } - if (zone_capacity == 0) { SPDK_ERRLOG("Zone capacity can't be 0\n"); return -EINVAL; @@ -69,13 +425,53 @@ spdk_vbdev_zone_block_create(const char *bdev_name, const char *vbdev_name, uint return -EINVAL; } - return rc; + /* Insert the bdev into our global name list even if it doesn't exist yet, + * it may show up soon... + */ + rc = zone_block_insert_name(bdev_name, vbdev_name, zone_capacity, optimal_open_zones); + if (rc) { + return rc; + } + + bdev = spdk_bdev_get_by_name(bdev_name); + if (!bdev) { + /* This is not an error, even though the bdev is not present at this time it may + * still show up later. + */ + return 0; + } + + return zone_block_register(bdev); } void spdk_vbdev_zone_block_delete(const char *name, spdk_bdev_unregister_cb cb_fn, void *cb_arg) { - cb_fn(cb_arg, 0); + struct bdev_zone_block_config *name_node; + struct spdk_bdev *bdev = NULL; + + bdev = spdk_bdev_get_by_name(name); + if (!bdev || bdev->module != &bdev_zoned_if) { + cb_fn(cb_arg, -ENODEV); + return; + } + + TAILQ_FOREACH(name_node, &g_bdev_configs, link) { + if (strcmp(name_node->vbdev_name, bdev->name) == 0) { + zone_block_remove_config(name_node); + break; + } + } + + spdk_bdev_unregister(bdev, cb_fn, cb_arg); +} + +static void +zone_block_examine(struct spdk_bdev *bdev) +{ + zone_block_register(bdev); + + spdk_bdev_module_examine_done(&bdev_zoned_if); } SPDK_LOG_REGISTER_COMPONENT("vbdev_zone_block", SPDK_LOG_VBDEV_ZONE_BLOCK) diff --git a/test/unit/lib/bdev/Makefile b/test/unit/lib/bdev/Makefile index ccbf11204..303efaa50 100644 --- a/test/unit/lib/bdev/Makefile +++ b/test/unit/lib/bdev/Makefile @@ -34,7 +34,7 @@ SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../../..) include $(SPDK_ROOT_DIR)/mk/spdk.common.mk -DIRS-y = bdev.c part.c scsi_nvme.c gpt vbdev_lvol.c mt bdev_raid.c bdev_zone.c +DIRS-y = bdev.c part.c scsi_nvme.c gpt vbdev_lvol.c mt bdev_raid.c bdev_zone.c vbdev_zone_block.c DIRS-$(CONFIG_CRYPTO) += crypto.c diff --git a/test/unit/lib/bdev/vbdev_zone_block.c/.gitignore b/test/unit/lib/bdev/vbdev_zone_block.c/.gitignore new file mode 100644 index 000000000..a1d7547aa --- /dev/null +++ b/test/unit/lib/bdev/vbdev_zone_block.c/.gitignore @@ -0,0 +1 @@ +vbdev_zone_block_ut diff --git a/test/unit/lib/bdev/vbdev_zone_block.c/Makefile b/test/unit/lib/bdev/vbdev_zone_block.c/Makefile new file mode 100644 index 000000000..81a9575d5 --- /dev/null +++ b/test/unit/lib/bdev/vbdev_zone_block.c/Makefile @@ -0,0 +1,38 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Intel Corporation nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../../../..) + +TEST_FILE = vbdev_zone_block_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/test/unit/lib/bdev/vbdev_zone_block.c/vbdev_zone_block_ut.c b/test/unit/lib/bdev/vbdev_zone_block.c/vbdev_zone_block_ut.c new file mode 100644 index 000000000..af73866db --- /dev/null +++ b/test/unit/lib/bdev/vbdev_zone_block.c/vbdev_zone_block_ut.c @@ -0,0 +1,569 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spdk/stdinc.h" +#include "spdk_cunit.h" +#include "spdk/env.h" +#include "spdk_internal/mock.h" +#include "spdk/thread.h" +#include "common/lib/test_env.c" +#include "bdev/zone_block/vbdev_zone_block.c" +#include "bdev/zone_block/vbdev_zone_block_rpc.c" + +#define BLOCK_CNT (1024ul * 1024ul * 1024ul * 1024ul) +#define BLOCK_SIZE 4096 + +/* Globals */ +uint32_t g_io_comp_status; +uint8_t g_rpc_err; +uint8_t g_json_decode_obj_construct; +static TAILQ_HEAD(, spdk_bdev) g_bdev_list = TAILQ_HEAD_INITIALIZER(g_bdev_list); +void *g_rpc_req = NULL; +uint32_t g_rpc_req_size = 0; +static struct spdk_thread *g_thread; + +DEFINE_STUB_V(spdk_bdev_module_list_add, (struct spdk_bdev_module *bdev_module)); +DEFINE_STUB_V(spdk_bdev_close, (struct spdk_bdev_desc *desc)); +DEFINE_STUB(spdk_json_decode_string, int, (const struct spdk_json_val *val, void *out), 0); +DEFINE_STUB(spdk_json_decode_uint64, int, (const struct spdk_json_val *val, void *out), 0); +DEFINE_STUB_V(spdk_bdev_module_examine_done, (struct spdk_bdev_module *module)); +DEFINE_STUB(spdk_json_write_name, int, (struct spdk_json_write_ctx *w, const char *name), 0); +DEFINE_STUB(spdk_json_write_object_begin, int, (struct spdk_json_write_ctx *w), 0); +DEFINE_STUB(spdk_json_write_named_string, int, (struct spdk_json_write_ctx *w, + const char *name, const char *val), 0); +DEFINE_STUB(spdk_bdev_io_type_supported, bool, (struct spdk_bdev *bdev, + enum spdk_bdev_io_type io_type), true); +DEFINE_STUB(spdk_json_write_bool, int, (struct spdk_json_write_ctx *w, bool val), 0); +DEFINE_STUB(spdk_json_write_named_object_begin, int, (struct spdk_json_write_ctx *w, + const char *name), 0); +DEFINE_STUB(spdk_json_write_object_end, int, (struct spdk_json_write_ctx *w), 0); +DEFINE_STUB_V(spdk_rpc_register_method, (const char *method, spdk_rpc_method_handler func, + uint32_t state_mask)); +DEFINE_STUB_V(spdk_jsonrpc_end_result, (struct spdk_jsonrpc_request *request, + struct spdk_json_write_ctx *w)); +DEFINE_STUB(spdk_bdev_get_io_channel, struct spdk_io_channel *, (struct spdk_bdev_desc *desc), + (void *)1); + +int +spdk_bdev_open(struct spdk_bdev *bdev, bool write, spdk_bdev_remove_cb_t remove_cb, + void *remove_ctx, struct spdk_bdev_desc **_desc) +{ + *_desc = (void *)bdev; + return 0; +} + +struct spdk_bdev * +spdk_bdev_desc_get_bdev(struct spdk_bdev_desc *desc) +{ + return (void *)desc; +} + +int +spdk_bdev_register(struct spdk_bdev *bdev) +{ + CU_ASSERT_PTR_NULL(spdk_bdev_get_by_name(bdev->name)); + TAILQ_INSERT_TAIL(&g_bdev_list, bdev, internal.link); + + return 0; +} + +void +spdk_bdev_unregister(struct spdk_bdev *bdev, spdk_bdev_unregister_cb cb_fn, void *cb_arg) +{ + CU_ASSERT_EQUAL(spdk_bdev_get_by_name(bdev->name), bdev); + TAILQ_REMOVE(&g_bdev_list, bdev, internal.link); + + bdev->fn_table->destruct(bdev->ctxt); + + if (cb_fn) { + cb_fn(cb_arg, 0); + } +} + +int spdk_json_write_named_uint64(struct spdk_json_write_ctx *w, const char *name, uint64_t val) +{ + struct rpc_construct_zone_block *req = g_rpc_req; + if (strcmp(name, "zone_capacity") == 0) { + CU_ASSERT(req->zone_capacity == val); + } else if (strcmp(name, "optimal_open_zones") == 0) { + CU_ASSERT(req->optimal_open_zones == val); + } + + return 0; +} + +const char * +spdk_bdev_get_name(const struct spdk_bdev *bdev) +{ + return bdev->name; +} + +bool +spdk_bdev_is_zoned(const struct spdk_bdev *bdev) +{ + return bdev->zoned; +} + +int +spdk_json_write_string(struct spdk_json_write_ctx *w, const char *val) +{ + return 0; +} + +int +spdk_bdev_module_claim_bdev(struct spdk_bdev *bdev, struct spdk_bdev_desc *desc, + struct spdk_bdev_module *module) +{ + if (bdev->internal.claim_module != NULL) { + return -1; + } + bdev->internal.claim_module = module; + return 0; +} + +void +spdk_bdev_module_release_bdev(struct spdk_bdev *bdev) +{ + CU_ASSERT(bdev->internal.claim_module != NULL); + bdev->internal.claim_module = NULL; +} + +void +spdk_bdev_io_complete(struct spdk_bdev_io *bdev_io, enum spdk_bdev_io_status status) +{ + g_io_comp_status = ((status == SPDK_BDEV_IO_STATUS_SUCCESS) ? true : false); +} + +int +spdk_json_decode_object(const struct spdk_json_val *values, + const struct spdk_json_object_decoder *decoders, size_t num_decoders, + void *out) +{ + struct rpc_construct_zone_block *req, *_out; + + if (g_json_decode_obj_construct) { + req = g_rpc_req; + _out = out; + + _out->name = strdup(req->name); + SPDK_CU_ASSERT_FATAL(_out->name != NULL); + _out->base_bdev = strdup(req->base_bdev); + SPDK_CU_ASSERT_FATAL(_out->base_bdev != NULL); + _out->zone_capacity = req->zone_capacity; + _out->optimal_open_zones = req->optimal_open_zones; + } else { + memcpy(out, g_rpc_req, g_rpc_req_size); + } + + return 0; +} + +struct spdk_json_write_ctx * +spdk_jsonrpc_begin_result(struct spdk_jsonrpc_request *request) +{ + return (void *)1; +} + +static struct spdk_bdev * +create_nvme_bdev(void) +{ + struct spdk_bdev *base_bdev; + char *name = "Nvme0n1"; + base_bdev = calloc(1, sizeof(struct spdk_bdev)); + SPDK_CU_ASSERT_FATAL(base_bdev != NULL); + base_bdev->name = strdup(name); + SPDK_CU_ASSERT_FATAL(base_bdev->name != NULL); + base_bdev->blocklen = BLOCK_SIZE; + base_bdev->blockcnt = BLOCK_CNT; + base_bdev->write_unit_size = 1; + TAILQ_INSERT_TAIL(&g_bdev_list, base_bdev, internal.link); + + return base_bdev; +} + +static void +base_bdevs_cleanup(void) +{ + struct spdk_bdev *bdev; + struct spdk_bdev *bdev_next; + + if (!TAILQ_EMPTY(&g_bdev_list)) { + TAILQ_FOREACH_SAFE(bdev, &g_bdev_list, internal.link, bdev_next) { + free(bdev->name); + TAILQ_REMOVE(&g_bdev_list, bdev, internal.link); + free(bdev); + } + } +} + +struct spdk_bdev * +spdk_bdev_get_by_name(const char *bdev_name) +{ + struct spdk_bdev *bdev; + + if (!TAILQ_EMPTY(&g_bdev_list)) { + TAILQ_FOREACH(bdev, &g_bdev_list, internal.link) { + if (strcmp(bdev_name, bdev->name) == 0) { + return bdev; + } + } + } + + return NULL; +} + +void +spdk_jsonrpc_send_error_response(struct spdk_jsonrpc_request *request, + int error_code, const char *msg) +{ + g_rpc_err = 1; +} + +void +spdk_jsonrpc_send_error_response_fmt(struct spdk_jsonrpc_request *request, + int error_code, const char *fmt, ...) +{ + g_rpc_err = 1; +} + +static void +verify_config_present(const char *name, bool presence) +{ + struct bdev_zone_block_config *cfg; + bool cfg_found; + + cfg_found = false; + + TAILQ_FOREACH(cfg, &g_bdev_configs, link) { + if (cfg->vbdev_name != NULL) { + if (strcmp(name, cfg->vbdev_name) == 0) { + cfg_found = true; + break; + } + } + } + + if (presence == true) { + CU_ASSERT(cfg_found == true); + } else { + CU_ASSERT(cfg_found == false); + } +} + +static void +verify_bdev_present(const char *name, bool presence) +{ + struct bdev_zone_block *bdev; + bool bdev_found; + + bdev_found = false; + TAILQ_FOREACH(bdev, &g_bdev_nodes, link) { + if (strcmp(bdev->bdev.name, name) == 0) { + bdev_found = true; + break; + } + } + if (presence == true) { + CU_ASSERT(bdev_found == true); + } else { + CU_ASSERT(bdev_found == false); + } +} + +static void +create_test_req(struct rpc_construct_zone_block *r, const char *vbdev_name, const char *base_name, + uint64_t zone_capacity, uint64_t optimal_open_zones, bool create_base_bdev) +{ + r->name = strdup(vbdev_name); + SPDK_CU_ASSERT_FATAL(r->name != NULL); + r->base_bdev = strdup(base_name); + SPDK_CU_ASSERT_FATAL(r->base_bdev != NULL); + r->zone_capacity = zone_capacity; + r->optimal_open_zones = optimal_open_zones; + + if (create_base_bdev == true) { + create_nvme_bdev(); + } + g_rpc_req = r; + g_rpc_req_size = sizeof(*r); +} + +static void +free_test_req(struct rpc_construct_zone_block *r) +{ + free(r->name); + free(r->base_bdev); +} + +static void +initialize_create_req(struct rpc_construct_zone_block *r, const char *vbdev_name, + const char *base_name, + uint64_t zone_capacity, uint64_t optimal_open_zones, bool create_base_bdev) +{ + create_test_req(r, vbdev_name, base_name, zone_capacity, optimal_open_zones, create_base_bdev); + + g_rpc_err = 0; + g_json_decode_obj_construct = 1; +} + +static void +create_delete_req(struct rpc_delete_zone_block *r, const char *vbdev_name) +{ + r->name = strdup(vbdev_name); + SPDK_CU_ASSERT_FATAL(r->name != NULL); + + g_rpc_req = r; + g_rpc_req_size = sizeof(*r); + g_rpc_err = 0; + g_json_decode_obj_construct = 0; +} + +static void +verify_zone_config(struct rpc_construct_zone_block *r, bool presence) +{ + struct bdev_zone_block_config *cfg = NULL; + + TAILQ_FOREACH(cfg, &g_bdev_configs, link) { + if (strcmp(r->name, cfg->vbdev_name) == 0) { + if (presence == false) { + break; + } + CU_ASSERT(strcmp(r->base_bdev, cfg->bdev_name) == 0); + CU_ASSERT(r->zone_capacity == cfg->zone_capacity); + CU_ASSERT(r->optimal_open_zones == cfg->optimal_open_zones); + break; + } + } + + if (presence) { + CU_ASSERT(cfg != NULL); + } else { + CU_ASSERT(cfg == NULL); + } +} + +static void +verify_zone_bdev(struct rpc_construct_zone_block *r, bool presence) +{ + struct bdev_zone_block *bdev; + bool bdev_found; + + bdev_found = false; + TAILQ_FOREACH(bdev, &g_bdev_nodes, link) { + if (strcmp(bdev->bdev.name, r->name) == 0) { + bdev_found = true; + if (presence == false) { + break; + } + + CU_ASSERT(bdev->bdev.zoned == true); + CU_ASSERT(bdev->bdev.blockcnt == BLOCK_CNT); + CU_ASSERT(bdev->bdev.blocklen == BLOCK_SIZE); + CU_ASSERT(bdev->bdev.ctxt == bdev); + CU_ASSERT(bdev->bdev.fn_table == &zone_block_fn_table); + CU_ASSERT(bdev->bdev.module == &bdev_zoned_if); + CU_ASSERT(bdev->bdev.write_unit_size == 1); + CU_ASSERT(bdev->bdev.zone_size == spdk_align64pow2(r->zone_capacity)); + CU_ASSERT(bdev->bdev.optimal_open_zones == r->optimal_open_zones); + CU_ASSERT(bdev->bdev.max_open_zones == 0); + + break; + } + } + + if (presence == true) { + CU_ASSERT(bdev_found == true); + } else { + CU_ASSERT(bdev_found == false); + } +} + +static void +test_zone_block_create(void) +{ + struct rpc_construct_zone_block req; + struct rpc_delete_zone_block delete_req; + struct spdk_bdev *bdev; + char *name = "Nvme0n1"; + size_t num_zones = 20; + size_t zone_capacity = BLOCK_CNT / num_zones; + + CU_ASSERT(zone_block_init() == 0); + + /* Create zoned virtual device before nvme device */ + verify_config_present("zone_dev1", false); + verify_bdev_present("zone_dev1", false); + initialize_create_req(&req, "zone_dev1", name, zone_capacity, 1, false); + rpc_zone_block_create(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_zone_config(&req, true); + verify_zone_bdev(&req, false); + bdev = create_nvme_bdev(); + zone_block_examine(bdev); + verify_zone_bdev(&req, true); + free_test_req(&req); + + /* Delete bdev */ + create_delete_req(&delete_req, "zone_dev1"); + rpc_zone_block_delete(NULL, NULL); + verify_config_present("zone_dev1", false); + verify_bdev_present("zone_dev1", false); + CU_ASSERT(g_rpc_err == 0); + + /* Create zoned virtual device and verify its correctness */ + verify_config_present("zone_dev1", false); + verify_bdev_present("zone_dev1", false); + initialize_create_req(&req, "zone_dev1", name, zone_capacity, 1, false); + rpc_zone_block_create(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_zone_config(&req, true); + verify_zone_bdev(&req, true); + free_test_req(&req); + + /* Delete bdev */ + create_delete_req(&delete_req, "zone_dev1"); + rpc_zone_block_delete(NULL, NULL); + verify_config_present("zone_dev1", false); + verify_bdev_present("zone_dev1", false); + CU_ASSERT(g_rpc_err == 0); + + while (spdk_thread_poll(g_thread, 0, 0) > 0) {} + zone_block_finish(); + base_bdevs_cleanup(); + SPDK_CU_ASSERT_FATAL(TAILQ_EMPTY(&g_bdev_list)); +} + +static void +test_zone_block_create_invalid(void) +{ + struct rpc_construct_zone_block req; + struct rpc_delete_zone_block delete_req; + char *name = "Nvme0n1"; + size_t num_zones = 10; + size_t zone_capacity = BLOCK_CNT / num_zones; + + CU_ASSERT(zone_block_init() == 0); + + /* Create zoned virtual device and verify its correctness */ + verify_config_present("zone_dev1", false); + verify_bdev_present("zone_dev1", false); + initialize_create_req(&req, "zone_dev1", name, zone_capacity, 1, true); + rpc_zone_block_create(NULL, NULL); + CU_ASSERT(g_rpc_err == 0); + verify_zone_config(&req, true); + verify_zone_bdev(&req, true); + free_test_req(&req); + + /* Try to create another zoned virtual device on the same bdev */ + initialize_create_req(&req, "zone_dev2", name, zone_capacity, 1, false); + rpc_zone_block_create(NULL, NULL); + CU_ASSERT(g_rpc_err == 1); + verify_config_present("zone_dev2", false); + verify_bdev_present("zone_dev2", false); + free_test_req(&req); + + /* Try to create zoned virtual device on the zoned bdev */ + initialize_create_req(&req, "zone_dev2", "zone_dev1", zone_capacity, 1, false); + rpc_zone_block_create(NULL, NULL); + CU_ASSERT(g_rpc_err == 1); + verify_config_present("zone_dev2", false); + verify_bdev_present("zone_dev2", false); + free_test_req(&req); + + /* Unclaim the base bdev */ + create_delete_req(&delete_req, "zone_dev1"); + rpc_zone_block_delete(NULL, NULL); + verify_config_present("zone_dev1", false); + verify_bdev_present("zone_dev1", false); + CU_ASSERT(g_rpc_err == 0); + + /* Try to create zoned virtual device with 0 zone size */ + initialize_create_req(&req, "zone_dev2", name, 0, 1, false); + rpc_zone_block_create(NULL, NULL); + CU_ASSERT(g_rpc_err == 1); + verify_config_present("zone_dev2", false); + verify_bdev_present("zone_dev2", false); + free_test_req(&req); + + /* Try to create zoned virtual device with 0 optimal number of zones */ + initialize_create_req(&req, "zone_dev2", name, zone_capacity, 0, false); + rpc_zone_block_create(NULL, NULL); + verify_config_present("zone_dev2", false); + verify_bdev_present("zone_dev2", false); + CU_ASSERT(g_rpc_err == 1); + free_test_req(&req); + + while (spdk_thread_poll(g_thread, 0, 0) > 0) {} + + zone_block_finish(); + base_bdevs_cleanup(); + SPDK_CU_ASSERT_FATAL(TAILQ_EMPTY(&g_bdev_list)); +} + +int main(int argc, char **argv) +{ + CU_pSuite suite = NULL; + unsigned int num_failures; + + if (CU_initialize_registry() != CUE_SUCCESS) { + return CU_get_error(); + } + + suite = CU_add_suite("zone_block", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "test_zone_block_create", test_zone_block_create) == NULL || + CU_add_test(suite, "test_zone_block_create_invalid", test_zone_block_create_invalid) == NULL + ) { + CU_cleanup_registry(); + return CU_get_error(); + } + + g_thread = spdk_thread_create("test", NULL); + spdk_set_thread(g_thread); + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + + spdk_thread_exit(g_thread); + spdk_thread_destroy(g_thread); + + CU_cleanup_registry(); + + return num_failures; +} diff --git a/test/unit/unittest.sh b/test/unit/unittest.sh index c2a08f2ec..d88260cb4 100755 --- a/test/unit/unittest.sh +++ b/test/unit/unittest.sh @@ -61,6 +61,7 @@ $valgrind $testdir/lib/bdev/part.c/part_ut $valgrind $testdir/lib/bdev/scsi_nvme.c/scsi_nvme_ut $valgrind $testdir/lib/bdev/gpt/gpt.c/gpt_ut $valgrind $testdir/lib/bdev/vbdev_lvol.c/vbdev_lvol_ut +$valgrind $testdir/lib/bdev/vbdev_zone_block.c/vbdev_zone_block_ut if grep -q '#define SPDK_CONFIG_CRYPTO 1' $rootdir/include/spdk/config.h; then $valgrind $testdir/lib/bdev/crypto.c/crypto_ut